""" Route Builder for standardized URL structure Ensures consistent API patterns across all microservices """ from enum import Enum from typing import Optional class RouteCategory(Enum): """Categories of API routes with different access patterns""" BASE = "base" # Atomic CRUD operations on resources DASHBOARD = "dashboard" # Dashboard data and summaries ANALYTICS = "analytics" # Analytics endpoints (tier-gated) OPERATIONS = "operations" # Service-specific operations class RouteBuilder: """ Builder for creating standardized API routes URL Structure: - Base: /api/v1/tenants/{tenant_id}/{service}/{resource} - Dashboard: /api/v1/tenants/{tenant_id}/{service}/dashboard/{operation} - Analytics: /api/v1/tenants/{tenant_id}/{service}/analytics/{operation} - Operations: /api/v1/tenants/{tenant_id}/{service}/operations/{operation} """ API_VERSION = "v1" BASE_PATH = f"/api/{API_VERSION}" def __init__(self, service_name: str): """ Initialize route builder for a specific service Args: service_name: Name of the service (e.g., 'inventory', 'production') """ self.service_name = service_name def build_base_route(self, resource: str, include_tenant_prefix: bool = True) -> str: """ Build base CRUD route for a resource Args: resource: Resource name (e.g., 'ingredients', 'batches') include_tenant_prefix: Whether to include /tenants/{tenant_id} prefix Returns: Route path Example: builder = RouteBuilder('inventory') builder.build_base_route('ingredients') # Returns: '/api/v1/tenants/{tenant_id}/inventory/ingredients' """ if include_tenant_prefix: return f"{self.BASE_PATH}/tenants/{{tenant_id}}/{self.service_name}/{resource}" return f"{self.BASE_PATH}/{self.service_name}/{resource}" def build_dashboard_route(self, operation: str, include_tenant_prefix: bool = True) -> str: """ Build dashboard route Args: operation: Dashboard operation (e.g., 'summary', 'capacity-status') include_tenant_prefix: Whether to include /tenants/{tenant_id} prefix Returns: Route path Example: builder = RouteBuilder('production') builder.build_dashboard_route('summary') # Returns: '/api/v1/tenants/{tenant_id}/production/dashboard/summary' """ if include_tenant_prefix: return f"{self.BASE_PATH}/tenants/{{tenant_id}}/{self.service_name}/dashboard/{operation}" return f"{self.BASE_PATH}/{self.service_name}/dashboard/{operation}" def build_analytics_route(self, operation: str, include_tenant_prefix: bool = True) -> str: """ Build analytics route (tier-gated: professional/enterprise) Args: operation: Analytics operation (e.g., 'equipment-efficiency', 'trends') include_tenant_prefix: Whether to include /tenants/{tenant_id} prefix Returns: Route path Example: builder = RouteBuilder('production') builder.build_analytics_route('equipment-efficiency') # Returns: '/api/v1/tenants/{tenant_id}/production/analytics/equipment-efficiency' """ if include_tenant_prefix: return f"{self.BASE_PATH}/tenants/{{tenant_id}}/{self.service_name}/analytics/{operation}" return f"{self.BASE_PATH}/{self.service_name}/analytics/{operation}" def build_operations_route(self, operation: str, include_tenant_prefix: bool = True) -> str: """ Build service operations route Args: operation: Operation name (e.g., 'schedule-batch', 'stock-adjustment') include_tenant_prefix: Whether to include /tenants/{tenant_id} prefix Returns: Route path Example: builder = RouteBuilder('production') builder.build_operations_route('schedule-batch') # Returns: '/api/v1/tenants/{tenant_id}/production/operations/schedule-batch' """ if include_tenant_prefix: return f"{self.BASE_PATH}/tenants/{{tenant_id}}/{self.service_name}/operations/{operation}" return f"{self.BASE_PATH}/{self.service_name}/operations/{operation}" def build_resource_detail_route(self, resource: str, id_param: str = "id", include_tenant_prefix: bool = True) -> str: """ Build route for individual resource details Args: resource: Resource name id_param: Name of the ID parameter include_tenant_prefix: Whether to include /tenants/{tenant_id} prefix Returns: Route path Example: builder = RouteBuilder('inventory') builder.build_resource_detail_route('ingredients', 'ingredient_id') # Returns: '/api/v1/tenants/{tenant_id}/inventory/ingredients/{ingredient_id}' """ base = self.build_base_route(resource, include_tenant_prefix) return f"{base}/{{{id_param}}}" def build_nested_resource_route( self, parent_resource: str, parent_id_param: str, child_resource: str, include_tenant_prefix: bool = True ) -> str: """ Build route for nested resources Args: parent_resource: Parent resource name parent_id_param: Parent ID parameter name child_resource: Child resource name include_tenant_prefix: Whether to include /tenants/{tenant_id} prefix Returns: Route path Example: builder = RouteBuilder('inventory') builder.build_nested_resource_route('ingredients', 'ingredient_id', 'stock') # Returns: '/api/v1/tenants/{tenant_id}/inventory/ingredients/{ingredient_id}/stock' """ base = self.build_resource_detail_route(parent_resource, parent_id_param, include_tenant_prefix) return f"{base}/{child_resource}" def build_resource_action_route( self, resource: str, id_param: str, action: str, include_tenant_prefix: bool = True ) -> str: """ Build route for resource-specific actions Args: resource: Resource name id_param: ID parameter name action: Action name include_tenant_prefix: Whether to include /tenants/{tenant_id} prefix Returns: Route path Example: builder = RouteBuilder('pos') builder.build_resource_action_route('configurations', 'config_id', 'test-connection') # Returns: '/api/v1/tenants/{tenant_id}/pos/configurations/{config_id}/test-connection' """ base = self.build_resource_detail_route(resource, id_param, include_tenant_prefix) return f"{base}/{action}" def build_global_route(self, path: str) -> str: """ Build global route without tenant context Args: path: Path after service name Returns: Route path Example: builder = RouteBuilder('pos') builder.build_global_route('supported-systems') # Returns: '/api/v1/pos/supported-systems' """ return f"{self.BASE_PATH}/{self.service_name}/{path}" def build_webhook_route(self, path: str) -> str: """ Build webhook route (no tenant context) Args: path: Webhook path Returns: Route path Example: builder = RouteBuilder('pos') builder.build_webhook_route('{pos_system}') # Returns: '/api/v1/webhooks/{pos_system}' """ return f"{self.BASE_PATH}/webhooks/{path}" def build_custom_route( self, category: RouteCategory, path_segments: list, include_tenant_prefix: bool = True ) -> str: """ Build custom route with specified category and path segments Args: category: Route category path_segments: List of path segments after category include_tenant_prefix: Whether to include /tenants/{tenant_id} prefix Returns: Route path Example: builder = RouteBuilder('inventory') builder.build_custom_route(RouteCategory.DASHBOARD, ['food-safety', 'compliance']) # Returns: '/api/v1/tenants/{tenant_id}/inventory/dashboard/food-safety/compliance' """ base_prefix = f"{self.BASE_PATH}/tenants/{{tenant_id}}" if include_tenant_prefix else self.BASE_PATH if category == RouteCategory.BASE: return f"{base_prefix}/{self.service_name}/{'/'.join(path_segments)}" elif category == RouteCategory.DASHBOARD: return f"{base_prefix}/{self.service_name}/dashboard/{'/'.join(path_segments)}" elif category == RouteCategory.ANALYTICS: return f"{base_prefix}/{self.service_name}/analytics/{'/'.join(path_segments)}" elif category == RouteCategory.OPERATIONS: return f"{base_prefix}/{self.service_name}/operations/{'/'.join(path_segments)}" # Fallback to base return f"{base_prefix}/{self.service_name}/{'/'.join(path_segments)}" @staticmethod def get_route_pattern(category: RouteCategory, service_name: Optional[str] = None) -> str: """ Get regex pattern for matching routes of a specific category Args: category: Route category service_name: Optional service name to filter Returns: Regex pattern for route matching Example: RouteBuilder.get_route_pattern(RouteCategory.ANALYTICS) # Returns: r'^/api/v1/tenants/[^/]+/[^/]+/analytics/.*' RouteBuilder.get_route_pattern(RouteCategory.ANALYTICS, 'production') # Returns: r'^/api/v1/tenants/[^/]+/production/analytics/.*' """ service_pattern = service_name if service_name else "[^/]+" if category == RouteCategory.BASE: return rf"^/api/v1/tenants/[^/]+/{service_pattern}/(?!dashboard|analytics|operations)[^/]+.*" elif category == RouteCategory.DASHBOARD: return rf"^/api/v1/tenants/[^/]+/{service_pattern}/dashboard/.*" elif category == RouteCategory.ANALYTICS: return rf"^/api/v1/tenants/[^/]+/{service_pattern}/analytics/.*" elif category == RouteCategory.OPERATIONS: return rf"^/api/v1/tenants/[^/]+/{service_pattern}/operations/.*" return rf"^/api/v1/tenants/[^/]+/{service_pattern}/.*"