2025-07-26 18:46:52 +02:00
# gateway/app/routes/tenant.py - COMPLETELY UPDATED
2025-07-17 19:46:41 +02:00
"""
2025-07-26 18:46:52 +02:00
Tenant routes for API Gateway - Handles all tenant - scoped endpoints
2025-07-17 19:46:41 +02:00
"""
2025-07-26 18:46:52 +02:00
from fastapi import APIRouter , Request , Response , HTTPException , Path
2025-07-17 19:46:41 +02:00
from fastapi . responses import JSONResponse
import httpx
import logging
2025-07-26 18:46:52 +02:00
from typing import Optional
2025-07-17 19:46:41 +02:00
from app . core . config import settings
2026-01-12 22:15:11 +01:00
from app . core . header_manager import header_manager
2025-07-17 19:46:41 +02:00
logger = logging . getLogger ( __name__ )
router = APIRouter ( )
2025-07-26 18:46:52 +02:00
# ================================================================
# TENANT MANAGEMENT ENDPOINTS
# ================================================================
2025-07-20 23:15:57 +02:00
@router.post ( " /register " )
2025-07-17 19:46:41 +02:00
async def create_tenant ( request : Request ) :
""" Proxy tenant creation to tenant service """
2025-07-26 18:46:52 +02:00
return await _proxy_to_tenant_service ( request , " /api/v1/tenants/register " )
2025-07-17 19:46:41 +02:00
2025-07-26 18:46:52 +02:00
@router.get ( " / {tenant_id} " )
async def get_tenant ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Get specific tenant details """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } " )
@router.put ( " / {tenant_id} " )
async def update_tenant ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Update tenant details """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } " )
@router.get ( " / {tenant_id} /members " )
async def get_tenant_members ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Get tenant members """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /members " )
2026-01-05 19:42:35 +01:00
@router.post ( " / {tenant_id} /members " )
async def add_tenant_member ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Add a team member to tenant """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /members " )
@router.post ( " / {tenant_id} /members/with-user " )
async def add_tenant_member_with_user ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Add a team member to tenant with user creation """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /members/with-user " )
@router.put ( " / {tenant_id} /members/ {member_user_id} /role " )
async def update_member_role ( request : Request , tenant_id : str = Path ( . . . ) , member_user_id : str = Path ( . . . ) ) :
""" Update team member role """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /members/ { member_user_id } /role " )
@router.delete ( " / {tenant_id} /members/ {member_user_id} " )
async def remove_tenant_member ( request : Request , tenant_id : str = Path ( . . . ) , member_user_id : str = Path ( . . . ) ) :
""" Remove team member from tenant """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /members/ { member_user_id } " )
@router.post ( " / {tenant_id} /transfer-ownership " )
async def transfer_tenant_ownership ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Transfer tenant ownership to another admin """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /transfer-ownership " )
@router.get ( " / {tenant_id} /admins " )
async def get_tenant_admins ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Get all admins for a tenant """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /admins " )
2025-11-30 09:12:40 +01:00
@router.get ( " / {tenant_id} /hierarchy " )
async def get_tenant_hierarchy ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Get tenant hierarchy information """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /hierarchy " )
2025-12-05 20:07:01 +01:00
@router.api_route ( " / {tenant_id} /children " , methods = [ " GET " , " OPTIONS " ] )
async def get_tenant_children ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Get tenant children """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /children " )
2025-12-18 13:26:32 +01:00
2025-12-18 20:12:32 +01:00
@router.api_route ( " / {tenant_id} /bulk-children " , methods = [ " POST " , " OPTIONS " ] )
async def proxy_bulk_children ( request : Request , tenant_id : str = Path ( . . . ) ) :
2025-12-18 13:26:32 +01:00
""" Proxy bulk children creation requests to tenant service """
2025-12-18 20:12:32 +01:00
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /bulk-children " )
2025-12-18 13:26:32 +01:00
2025-12-05 20:07:01 +01:00
@router.api_route ( " / {tenant_id} /children/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_children ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant children requests to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /children/ { path } " . rstrip ( " / " )
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " / {tenant_id} /access/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_access ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant access requests to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /access/ { path } " . rstrip ( " / " )
return await _proxy_to_tenant_service ( request , target_path )
2025-10-03 14:09:34 +02:00
@router.get ( " / {tenant_id} /my-access " )
async def get_tenant_my_access ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Get current user ' s access level for a tenant """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /my-access " )
2025-08-02 18:38:14 +02:00
@router.get ( " /user/ {user_id} " )
async def get_user_tenants ( request : Request , user_id : str = Path ( . . . ) ) :
""" Get all tenant memberships for a user (admin only) """
2025-09-12 19:31:24 +02:00
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/users/ { user_id } " )
2025-08-02 18:38:14 +02:00
2025-08-15 22:40:19 +02:00
@router.get ( " /user/ {user_id} /owned " )
async def get_user_owned_tenants ( request : Request , user_id : str = Path ( . . . ) ) :
""" Get all tenants owned by a user """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/user/ { user_id } /owned " )
2025-12-17 16:28:58 +01:00
@router.get ( " /user/ {user_id} /tenants " )
async def get_user_all_tenants ( request : Request , user_id : str = Path ( . . . ) ) :
""" Get all tenants accessible by a user (both owned and member tenants) """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/user/ { user_id } /tenants " )
2025-08-03 14:42:33 +02:00
@router.delete ( " /user/ {user_id} /memberships " )
async def delete_user_tenants ( request : Request , user_id : str = Path ( . . . ) ) :
""" Get all tenant memberships for a user (admin only) """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/user/ { user_id } /memberships " )
2025-10-23 07:44:54 +02:00
# ================================================================
# TENANT SETTINGS ENDPOINTS
# ================================================================
@router.get ( " / {tenant_id} /settings " )
async def get_tenant_settings ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Get all settings for a tenant """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /settings " )
@router.put ( " / {tenant_id} /settings " )
async def update_tenant_settings ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Update tenant settings """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /settings " )
@router.get ( " / {tenant_id} /settings/ {category} " )
async def get_category_settings ( request : Request , tenant_id : str = Path ( . . . ) , category : str = Path ( . . . ) ) :
""" Get settings for a specific category """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /settings/ { category } " )
@router.put ( " / {tenant_id} /settings/ {category} " )
async def update_category_settings ( request : Request , tenant_id : str = Path ( . . . ) , category : str = Path ( . . . ) ) :
""" Update settings for a specific category """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /settings/ { category } " )
@router.post ( " / {tenant_id} /settings/ {category} /reset " )
async def reset_category_settings ( request : Request , tenant_id : str = Path ( . . . ) , category : str = Path ( . . . ) ) :
""" Reset a category to default values """
return await _proxy_to_tenant_service ( request , f " /api/v1/tenants/ { tenant_id } /settings/ { category } /reset " )
2025-09-21 13:27:50 +02:00
# ================================================================
# TENANT SUBSCRIPTION ENDPOINTS
# ================================================================
@router.api_route ( " / {tenant_id} /subscriptions/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_subscriptions ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant subscription requests to tenant service """
target_path = f " /api/v1/subscriptions/ { tenant_id } / { path } " . rstrip ( " / " )
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /subscriptions/plans " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_available_plans ( request : Request ) :
""" Proxy available plans request to tenant service """
target_path = " /api/v1/plans/available "
return await _proxy_to_tenant_service ( request , target_path )
2025-12-05 20:07:01 +01:00
# ================================================================
# BATCH OPERATIONS ENDPOINTS
# IMPORTANT: Route order matters! Keep specific routes before wildcards:
# 1. Exact matches first (/batch/sales-summary)
# 2. Wildcard paths second (/batch{path:path})
# 3. Tenant-scoped wildcards last (/{tenant_id}/batch{path:path})
# ================================================================
@router.api_route ( " /batch/sales-summary " , methods = [ " POST " ] )
async def proxy_batch_sales_summary ( request : Request ) :
""" Proxy batch sales summary request to sales service """
target_path = " /api/v1/batch/sales-summary "
return await _proxy_to_sales_service ( request , target_path )
@router.api_route ( " /batch/production-summary " , methods = [ " POST " ] )
async def proxy_batch_production_summary ( request : Request ) :
""" Proxy batch production summary request to production service """
target_path = " /api/v1/batch/production-summary "
return await _proxy_to_production_service ( request , target_path )
@router.api_route ( " /batch { path:path} " , methods = [ " POST " , " GET " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_batch_operations ( request : Request , path : str = " " ) :
""" Proxy batch operations that span multiple tenants to appropriate services """
# For batch operations, route based on the path after /batch/
if path . startswith ( " /sales-summary " ) :
# Route batch sales summary to sales service
# The sales service batch endpoints are at /api/v1/batch/... not /api/v1/sales/batch/...
target_path = f " /api/v1/batch { path } "
return await _proxy_to_sales_service ( request , target_path )
elif path . startswith ( " /production-summary " ) :
# Route batch production summary to production service
# The production service batch endpoints are at /api/v1/batch/... not /api/v1/production/batch/...
target_path = f " /api/v1/batch { path } "
return await _proxy_to_production_service ( request , target_path )
else :
# Default to sales service for other batch operations
# The service batch endpoints are at /api/v1/batch/... not /api/v1/sales/batch/...
target_path = f " /api/v1/batch { path } "
return await _proxy_to_sales_service ( request , target_path )
@router.api_route ( " / {tenant_id} /batch { path:path} " , methods = [ " POST " , " GET " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_batch_operations ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant-scoped batch operations to appropriate services """
# For tenant-scoped batch operations, route based on the path after /batch/
if path . startswith ( " /sales-summary " ) :
# Route tenant batch sales summary to sales service
# The sales service batch endpoints are at /api/v1/batch/... not /api/v1/sales/batch/...
target_path = f " /api/v1/batch { path } "
return await _proxy_to_sales_service ( request , target_path )
elif path . startswith ( " /production-summary " ) :
# Route tenant batch production summary to production service
# The production service batch endpoints are at /api/v1/batch/... not /api/v1/production/batch/...
target_path = f " /api/v1/batch { path } "
return await _proxy_to_production_service ( request , target_path )
else :
# Default to sales service for other tenant batch operations
# The service batch endpoints are at /api/v1/batch/... not /api/v1/sales/batch/...
target_path = f " /api/v1/batch { path } "
return await _proxy_to_sales_service ( request , target_path )
2025-07-26 18:46:52 +02:00
# ================================================================
# TENANT-SCOPED DATA SERVICE ENDPOINTS
# ================================================================
2025-07-26 21:10:54 +02:00
@router.api_route ( " / {tenant_id} /sales { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_all_tenant_sales_alternative ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy all tenant sales requests - handles both base and sub-paths """
base_path = f " /api/v1/tenants/ { tenant_id } /sales "
2025-12-05 20:07:01 +01:00
2025-07-26 21:10:54 +02:00
# If path is empty or just "/", use base path
if not path or path == " / " or path == " " :
target_path = base_path
else :
# Ensure path starts with "/"
if not path . startswith ( " / " ) :
path = " / " + path
target_path = base_path + path
2025-12-05 20:07:01 +01:00
2025-08-12 18:17:30 +02:00
return await _proxy_to_sales_service ( request , target_path )
2025-07-26 18:46:52 +02:00
2025-12-05 20:07:01 +01:00
@router.api_route ( " / {tenant_id} /enterprise/batch { path:path} " , methods = [ " POST " , " GET " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_enterprise_batch ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy enterprise batch requests (spanning multiple tenants within an enterprise) to appropriate services """
# Forward to orchestrator service for enterprise-level operations
target_path = f " /api/v1/tenants/ { tenant_id } /enterprise/batch { path } " . rstrip ( " / " )
return await _proxy_to_orchestrator_service ( request , target_path , tenant_id = tenant_id )
2025-07-26 18:46:52 +02:00
@router.api_route ( " / {tenant_id} /weather/ { path:path} " , methods = [ " GET " , " POST " , " OPTIONS " ] )
async def proxy_tenant_weather ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
2025-08-12 18:17:30 +02:00
""" Proxy tenant weather requests to external service """
2025-07-26 18:46:52 +02:00
target_path = f " /api/v1/tenants/ { tenant_id } /weather/ { path } " . rstrip ( " / " )
2025-08-12 18:17:30 +02:00
return await _proxy_to_external_service ( request , target_path )
2025-07-26 18:46:52 +02:00
2025-07-27 22:58:18 +02:00
@router.api_route ( " / {tenant_id} /traffic/ { path:path} " , methods = [ " GET " , " POST " , " OPTIONS " ] )
async def proxy_tenant_traffic ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
2025-08-12 18:17:30 +02:00
""" Proxy tenant traffic requests to external service """
2025-07-27 22:58:18 +02:00
target_path = f " /api/v1/tenants/ { tenant_id } /traffic/ { path } " . rstrip ( " / " )
2025-08-12 18:17:30 +02:00
return await _proxy_to_external_service ( request , target_path )
2025-07-27 22:58:18 +02:00
2025-10-09 14:11:02 +02:00
@router.api_route ( " / {tenant_id} /external/ { path:path} " , methods = [ " GET " , " POST " , " OPTIONS " ] )
async def proxy_tenant_external ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant external service requests (v2.0 city-based optimized endpoints) """
2025-11-14 07:23:56 +01:00
# Route to external service with normal path structure
2025-10-09 14:11:02 +02:00
target_path = f " /api/v1/tenants/ { tenant_id } /external/ { path } " . rstrip ( " / " )
return await _proxy_to_external_service ( request , target_path )
2025-11-01 21:35:03 +01:00
# Service-specific analytics routes (must come BEFORE the general analytics route)
@router.api_route ( " / {tenant_id} /procurement/analytics/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_procurement_analytics ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant procurement analytics requests to procurement service """
target_path = f " /api/v1/tenants/ { tenant_id } /procurement/analytics/ { path } " . rstrip ( " / " )
return await _proxy_to_procurement_service ( request , target_path , tenant_id = tenant_id )
@router.api_route ( " / {tenant_id} /inventory/analytics/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_inventory_analytics ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant inventory analytics requests to inventory service """
target_path = f " /api/v1/tenants/ { tenant_id } /inventory/analytics/ { path } " . rstrip ( " / " )
return await _proxy_to_inventory_service ( request , target_path , tenant_id = tenant_id )
@router.api_route ( " / {tenant_id} /production/analytics/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_production_analytics ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant production analytics requests to production service """
target_path = f " /api/v1/tenants/ { tenant_id } /production/analytics/ { path } " . rstrip ( " / " )
return await _proxy_to_production_service ( request , target_path , tenant_id = tenant_id )
@router.api_route ( " / {tenant_id} /sales/analytics/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_sales_analytics ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant sales analytics requests to sales service """
target_path = f " /api/v1/tenants/ { tenant_id } /sales/analytics/ { path } " . rstrip ( " / " )
return await _proxy_to_sales_service ( request , target_path )
2025-07-26 18:46:52 +02:00
@router.api_route ( " / {tenant_id} /analytics/ { path:path} " , methods = [ " GET " , " POST " , " OPTIONS " ] )
async def proxy_tenant_analytics ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
2025-11-01 21:35:03 +01:00
""" Proxy tenant analytics requests to sales service (fallback for non-service-specific analytics) """
2025-07-26 18:46:52 +02:00
target_path = f " /api/v1/tenants/ { tenant_id } /analytics/ { path } " . rstrip ( " / " )
2025-08-12 18:17:30 +02:00
return await _proxy_to_sales_service ( request , target_path )
2025-07-26 18:46:52 +02:00
2025-11-05 13:34:56 +01:00
# ================================================================
# TENANT-SCOPED AI INSIGHTS ENDPOINTS
# ================================================================
@router.api_route ( " / {tenant_id} /insights { path:path} " , methods = [ " GET " , " POST " , " PUT " , " PATCH " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_insights ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant AI insights requests to AI insights service """
target_path = f " /api/v1/tenants/ { tenant_id } /insights { path } " . rstrip ( " / " )
return await _proxy_to_ai_insights_service ( request , target_path , tenant_id = tenant_id )
2025-08-13 17:39:35 +02:00
@router.api_route ( " / {tenant_id} /onboarding/ { path:path} " , methods = [ " GET " , " POST " , " OPTIONS " ] )
2025-11-01 21:35:03 +01:00
async def proxy_tenant_onboarding ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
2025-12-27 21:30:42 +01:00
""" Proxy tenant onboarding requests to tenant service """
2025-08-13 17:39:35 +02:00
target_path = f " /api/v1/tenants/ { tenant_id } /onboarding/ { path } " . rstrip ( " / " )
2025-12-27 21:30:42 +01:00
return await _proxy_to_tenant_service ( request , target_path )
2025-08-13 17:39:35 +02:00
2025-07-26 18:46:52 +02:00
# ================================================================
# TENANT-SCOPED TRAINING SERVICE ENDPOINTS
# ================================================================
@router.api_route ( " / {tenant_id} /training/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_training ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant training requests to training service """
target_path = f " /api/v1/tenants/ { tenant_id } /training/ { path } " . rstrip ( " / " )
2025-09-05 12:55:26 +02:00
return await _proxy_to_training_service ( request , target_path , tenant_id = tenant_id )
2025-07-26 18:46:52 +02:00
@router.api_route ( " / {tenant_id} /models/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_models ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant model requests to training service """
target_path = f " /api/v1/tenants/ { tenant_id } /models/ { path } " . rstrip ( " / " )
2025-09-05 12:55:26 +02:00
return await _proxy_to_training_service ( request , target_path , tenant_id = tenant_id )
2025-07-26 18:46:52 +02:00
2025-09-20 22:11:05 +02:00
@router.api_route ( " / {tenant_id} /statistics " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_tenant_statistics ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy tenant statistics requests to training service """
target_path = f " /api/v1/tenants/ { tenant_id } /statistics "
return await _proxy_to_training_service ( request , target_path , tenant_id = tenant_id )
2025-07-26 18:46:52 +02:00
# ================================================================
# TENANT-SCOPED FORECASTING SERVICE ENDPOINTS
# ================================================================
2025-10-09 14:11:02 +02:00
@router.api_route ( " / {tenant_id} /forecasting/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_forecasting ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant forecasting requests to forecasting service """
target_path = f " /api/v1/tenants/ { tenant_id } /forecasting/ { path } " . rstrip ( " / " )
return await _proxy_to_forecasting_service ( request , target_path , tenant_id = tenant_id )
2025-12-05 20:07:01 +01:00
@router.api_route ( " / {tenant_id} /forecasting/enterprise/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_forecasting_enterprise ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant forecasting enterprise requests to forecasting service """
target_path = f " /api/v1/tenants/ { tenant_id } /forecasting/enterprise/ { path } " . rstrip ( " / " )
return await _proxy_to_forecasting_service ( request , target_path , tenant_id = tenant_id )
2025-07-26 18:46:52 +02:00
@router.api_route ( " / {tenant_id} /forecasts/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_forecasts ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant forecast requests to forecasting service """
target_path = f " /api/v1/tenants/ { tenant_id } /forecasts/ { path } " . rstrip ( " / " )
2025-08-15 23:11:53 +02:00
return await _proxy_to_forecasting_service ( request , target_path , tenant_id = tenant_id )
2025-07-26 18:46:52 +02:00
@router.api_route ( " / {tenant_id} /predictions/ { path:path} " , methods = [ " GET " , " POST " , " OPTIONS " ] )
async def proxy_tenant_predictions ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant prediction requests to forecasting service """
target_path = f " /api/v1/tenants/ { tenant_id } /predictions/ { path } " . rstrip ( " / " )
return await _proxy_to_forecasting_service ( request , target_path )
# ================================================================
# TENANT-SCOPED NOTIFICATION SERVICE ENDPOINTS
# ================================================================
@router.api_route ( " / {tenant_id} /notifications/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_notifications ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant notification requests to notification service """
target_path = f " /api/v1/tenants/ { tenant_id } /notifications/ { path } " . rstrip ( " / " )
return await _proxy_to_notification_service ( request , target_path )
2025-07-20 23:43:42 +02:00
2025-10-21 19:50:07 +02:00
# ================================================================
# TENANT-SCOPED ALERT ANALYTICS ENDPOINTS (Must come BEFORE inventory alerts)
# ================================================================
2025-11-27 15:52:40 +01:00
# Exact match for /alerts endpoint (without additional path)
@router.api_route ( " / {tenant_id} /alerts " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_alerts_list ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy tenant alerts list requests to alert processor service """
target_path = f " /api/v1/tenants/ { tenant_id } /alerts "
return await _proxy_to_alert_processor_service ( request , target_path , tenant_id = tenant_id )
2025-10-21 19:50:07 +02:00
@router.api_route ( " / {tenant_id} /alerts/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_alert_analytics ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant alert analytics requests to alert processor service """
target_path = f " /api/v1/tenants/ { tenant_id } /alerts/ { path } " . rstrip ( " / " )
return await _proxy_to_alert_processor_service ( request , target_path , tenant_id = tenant_id )
2025-08-14 13:26:59 +02:00
# ================================================================
# TENANT-SCOPED INVENTORY SERVICE ENDPOINTS
# ================================================================
2025-09-09 12:02:41 +02:00
@router.api_route ( " / {tenant_id} /alerts { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
2025-10-21 19:50:07 +02:00
async def proxy_tenant_alerts_inventory ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant alerts requests to inventory service (legacy/food-safety alerts) """
2025-09-09 12:02:41 +02:00
target_path = f " /api/v1/tenants/ { tenant_id } /alerts { path } " . rstrip ( " / " )
return await _proxy_to_inventory_service ( request , target_path , tenant_id = tenant_id )
2025-08-14 13:26:59 +02:00
@router.api_route ( " / {tenant_id} /inventory/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_inventory ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant inventory requests to inventory service """
# The inventory service expects /api/v1/tenants/{tenant_id}/inventory/{path}
# Keep the full path structure for inventory service
target_path = f " /api/v1/tenants/ { tenant_id } /inventory/ { path } " . rstrip ( " / " )
2025-08-15 23:11:53 +02:00
return await _proxy_to_inventory_service ( request , target_path , tenant_id = tenant_id )
2025-08-14 13:26:59 +02:00
2025-09-15 15:31:27 +02:00
# Specific route for ingredients without additional path
@router.api_route ( " / {tenant_id} /ingredients " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_ingredients_base ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy tenant ingredient requests to inventory service (base path) """
target_path = f " /api/v1/tenants/ { tenant_id } /ingredients "
return await _proxy_to_inventory_service ( request , target_path , tenant_id = tenant_id )
2025-12-14 19:05:37 +01:00
@router.api_route ( " / {tenant_id} /ingredients/count " , methods = [ " GET " ] )
async def proxy_tenant_ingredients_count ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy tenant ingredient count requests to inventory service """
2025-12-29 14:48:24 +01:00
# Inventory service uses RouteBuilder('inventory').build_base_route("ingredients/count")
# which generates /api/v1/tenants/{tenant_id}/inventory/ingredients/count
target_path = f " /api/v1/tenants/ { tenant_id } /inventory/ingredients/count "
2025-12-14 19:05:37 +01:00
return await _proxy_to_inventory_service ( request , target_path , tenant_id = tenant_id )
2025-09-09 21:39:12 +02:00
@router.api_route ( " / {tenant_id} /ingredients/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
2025-09-15 15:31:27 +02:00
async def proxy_tenant_ingredients_with_path ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant ingredient requests to inventory service (with additional path) """
2025-08-14 13:26:59 +02:00
# The inventory service ingredient endpoints are now tenant-scoped: /api/v1/tenants/{tenant_id}/ingredients/{path}
# Keep the full tenant path structure
2025-09-09 21:39:12 +02:00
target_path = f " /api/v1/tenants/ { tenant_id } /ingredients/ { path } " . rstrip ( " / " )
return await _proxy_to_inventory_service ( request , target_path , tenant_id = tenant_id )
@router.api_route ( " / {tenant_id} /stock/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_stock ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant stock requests to inventory service """
# The inventory service stock endpoints are now tenant-scoped: /api/v1/tenants/{tenant_id}/stock/{path}
target_path = f " /api/v1/tenants/ { tenant_id } /stock/ { path } " . rstrip ( " / " )
2025-08-15 23:11:53 +02:00
return await _proxy_to_inventory_service ( request , target_path , tenant_id = tenant_id )
2025-08-14 13:26:59 +02:00
2025-09-09 22:27:52 +02:00
@router.api_route ( " / {tenant_id} /dashboard/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_dashboard ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
2025-11-07 21:49:31 +00:00
""" Proxy tenant dashboard requests to orchestrator service """
# The orchestrator service dashboard endpoints are tenant-scoped: /api/v1/tenants/{tenant_id}/dashboard/{path}
2025-09-09 22:27:52 +02:00
target_path = f " /api/v1/tenants/ { tenant_id } /dashboard/ { path } " . rstrip ( " / " )
2025-11-07 21:49:31 +00:00
return await _proxy_to_orchestrator_service ( request , target_path , tenant_id = tenant_id )
2025-09-09 22:27:52 +02:00
2025-09-17 16:06:30 +02:00
@router.api_route ( " / {tenant_id} /transformations " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_transformations_base ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy tenant transformations requests to inventory service (base path) """
target_path = f " /api/v1/tenants/ { tenant_id } /transformations "
return await _proxy_to_inventory_service ( request , target_path , tenant_id = tenant_id )
@router.api_route ( " / {tenant_id} /transformations/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_transformations_with_path ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant transformations requests to inventory service (with additional path) """
# The inventory service transformations endpoints are tenant-scoped: /api/v1/tenants/{tenant_id}/transformations/{path}
target_path = f " /api/v1/tenants/ { tenant_id } /transformations/ { path } " . rstrip ( " / " )
return await _proxy_to_inventory_service ( request , target_path , tenant_id = tenant_id )
2025-10-23 07:44:54 +02:00
@router.api_route ( " / {tenant_id} /sustainability/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_sustainability ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant sustainability requests to inventory service """
# The inventory service sustainability endpoints are tenant-scoped: /api/v1/tenants/{tenant_id}/sustainability/{path}
target_path = f " /api/v1/tenants/ { tenant_id } /sustainability/ { path } " . rstrip ( " / " )
return await _proxy_to_inventory_service ( request , target_path , tenant_id = tenant_id )
2025-08-23 16:30:45 +02:00
# ================================================================
# TENANT-SCOPED PRODUCTION SERVICE ENDPOINTS
# ================================================================
@router.api_route ( " / {tenant_id} /production/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_production ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant production requests to production service """
target_path = f " /api/v1/tenants/ { tenant_id } /production/ { path } " . rstrip ( " / " )
return await _proxy_to_production_service ( request , target_path , tenant_id = tenant_id )
2025-10-31 11:54:19 +01:00
# ================================================================
# TENANT-SCOPED ORCHESTRATOR SERVICE ENDPOINTS
# ================================================================
2025-11-30 09:12:40 +01:00
@router.api_route ( " / {tenant_id} /enterprise/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_enterprise ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant enterprise dashboard requests to orchestrator service """
target_path = f " /api/v1/tenants/ { tenant_id } /enterprise/ { path } " . rstrip ( " / " )
return await _proxy_to_orchestrator_service ( request , target_path , tenant_id = tenant_id )
2025-10-31 11:54:19 +01:00
@router.api_route ( " / {tenant_id} /orchestrator/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_orchestrator ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant orchestrator requests to orchestrator service """
target_path = f " /api/v1/tenants/ { tenant_id } /orchestrator/ { path } " . rstrip ( " / " )
return await _proxy_to_orchestrator_service ( request , target_path , tenant_id = tenant_id )
2025-08-23 16:30:45 +02:00
# ================================================================
# TENANT-SCOPED ORDERS SERVICE ENDPOINTS
# ================================================================
2025-10-21 19:50:07 +02:00
@router.api_route ( " / {tenant_id} /orders " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_orders_base ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy tenant orders requests to orders service (base path) """
target_path = f " /api/v1/tenants/ { tenant_id } /orders "
return await _proxy_to_orders_service ( request , target_path , tenant_id = tenant_id )
2025-08-23 16:30:45 +02:00
@router.api_route ( " / {tenant_id} /orders/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
2025-10-21 19:50:07 +02:00
async def proxy_tenant_orders_with_path ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant orders requests to orders service (with additional path) """
2025-08-23 16:30:45 +02:00
target_path = f " /api/v1/tenants/ { tenant_id } /orders/ { path } " . rstrip ( " / " )
return await _proxy_to_orders_service ( request , target_path , tenant_id = tenant_id )
2025-09-09 12:02:41 +02:00
@router.api_route ( " / {tenant_id} /customers/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_customers ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant customers requests to orders service """
2025-10-27 16:33:26 +01:00
target_path = f " /api/v1/tenants/ { tenant_id } /orders/customers/ { path } " . rstrip ( " / " )
2025-09-09 12:02:41 +02:00
return await _proxy_to_orders_service ( request , target_path , tenant_id = tenant_id )
@router.api_route ( " / {tenant_id} /procurement/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_procurement ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
2025-10-30 21:08:07 +01:00
""" Proxy tenant procurement requests to procurement service """
2025-11-05 13:34:56 +01:00
# For all procurement routes, we need to maintain the /procurement/ part in the path
# The procurement service now uses standardized paths with RouteBuilder
target_path = f " /api/v1/tenants/ { tenant_id } /procurement/ { path } " . rstrip ( " / " )
2025-10-30 21:08:07 +01:00
return await _proxy_to_procurement_service ( request , target_path , tenant_id = tenant_id )
2025-09-09 12:02:41 +02:00
2025-09-04 23:19:53 +02:00
# ================================================================
# TENANT-SCOPED SUPPLIER SERVICE ENDPOINTS
# ================================================================
@router.api_route ( " / {tenant_id} /suppliers " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
2025-10-24 13:05:04 +02:00
async def proxy_tenant_suppliers_base ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy tenant supplier requests to suppliers service (base path) """
target_path = f " /api/v1/tenants/ { tenant_id } /suppliers "
return await _proxy_to_suppliers_service ( request , target_path , tenant_id = tenant_id )
2025-12-14 19:05:37 +01:00
@router.api_route ( " / {tenant_id} /suppliers/count " , methods = [ " GET " ] )
async def proxy_tenant_suppliers_count ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy tenant supplier count requests to suppliers service """
2025-12-29 14:48:24 +01:00
# Suppliers service uses RouteBuilder('suppliers').build_operations_route("count")
# which generates /api/v1/tenants/{tenant_id}/suppliers/operations/count
target_path = f " /api/v1/tenants/ { tenant_id } /suppliers/operations/count "
2025-12-14 19:05:37 +01:00
return await _proxy_to_suppliers_service ( request , target_path , tenant_id = tenant_id )
2025-10-24 13:05:04 +02:00
@router.api_route ( " / {tenant_id} /suppliers/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_suppliers_with_path ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant supplier requests to suppliers service (with additional path) """
target_path = f " /api/v1/tenants/ { tenant_id } /suppliers/ { path } " . rstrip ( " / " )
2025-09-04 23:19:53 +02:00
return await _proxy_to_suppliers_service ( request , target_path , tenant_id = tenant_id )
2025-11-05 13:34:56 +01:00
# NOTE: Purchase orders are now accessed via the main procurement route:
# /api/v1/tenants/{tenant_id}/procurement/purchase-orders/*
# Legacy route removed to enforce standardized structure
2025-09-04 23:19:53 +02:00
@router.api_route ( " / {tenant_id} /deliveries { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_deliveries ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant delivery requests to suppliers service """
target_path = f " /api/v1/tenants/ { tenant_id } /deliveries { path } " . rstrip ( " / " )
return await _proxy_to_suppliers_service ( request , target_path , tenant_id = tenant_id )
2025-11-30 09:12:40 +01:00
# ================================================================
# TENANT-SCOPED LOCATIONS ENDPOINTS
# ================================================================
@router.api_route ( " / {tenant_id} /locations " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_locations_base ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy tenant locations requests to tenant service (base path) """
target_path = f " /api/v1/tenants/ { tenant_id } /locations "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " / {tenant_id} /locations/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_locations_with_path ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant locations requests to tenant service (with additional path) """
target_path = f " /api/v1/tenants/ { tenant_id } /locations/ { path } " . rstrip ( " / " )
return await _proxy_to_tenant_service ( request , target_path )
# ================================================================
# TENANT-SCOPED DISTRIBUTION SERVICE ENDPOINTS
# ================================================================
@router.api_route ( " / {tenant_id} /distribution/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_distribution ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant distribution requests to distribution service """
target_path = f " /api/v1/tenants/ { tenant_id } /distribution/ { path } " . rstrip ( " / " )
return await _proxy_to_distribution_service ( request , target_path , tenant_id = tenant_id )
2025-09-19 21:39:04 +02:00
# ================================================================
# TENANT-SCOPED RECIPES SERVICE ENDPOINTS
# ================================================================
@router.api_route ( " / {tenant_id} /recipes " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_recipes_base ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy tenant recipes requests to recipes service (base path) """
target_path = f " /api/v1/tenants/ { tenant_id } /recipes "
return await _proxy_to_recipes_service ( request , target_path , tenant_id = tenant_id )
2025-12-14 19:05:37 +01:00
@router.api_route ( " / {tenant_id} /recipes/count " , methods = [ " GET " ] )
async def proxy_tenant_recipes_count ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy tenant recipes count requests to recipes service """
target_path = f " /api/v1/tenants/ { tenant_id } /recipes/count "
return await _proxy_to_recipes_service ( request , target_path , tenant_id = tenant_id )
2025-09-19 21:39:04 +02:00
@router.api_route ( " / {tenant_id} /recipes/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_recipes_with_path ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant recipes requests to recipes service (with additional path) """
target_path = f " /api/v1/tenants/ { tenant_id } /recipes/ { path } " . rstrip ( " / " )
return await _proxy_to_recipes_service ( request , target_path , tenant_id = tenant_id )
2025-10-07 07:15:07 +02:00
# ================================================================
# TENANT-SCOPED POS SERVICE ENDPOINTS
# ================================================================
@router.api_route ( " / {tenant_id} /pos/ { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
async def proxy_tenant_pos ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy tenant POS requests to POS service """
target_path = f " /api/v1/tenants/ { tenant_id } /pos/ { path } " . rstrip ( " / " )
return await _proxy_to_pos_service ( request , target_path , tenant_id = tenant_id )
2025-07-26 18:46:52 +02:00
# ================================================================
# PROXY HELPER FUNCTIONS
# ================================================================
async def _proxy_to_tenant_service ( request : Request , target_path : str ) :
""" Proxy request to tenant service """
return await _proxy_request ( request , target_path , settings . TENANT_SERVICE_URL )
2025-08-12 18:17:30 +02:00
async def _proxy_to_sales_service ( request : Request , target_path : str ) :
""" Proxy request to sales service """
return await _proxy_request ( request , target_path , settings . SALES_SERVICE_URL )
async def _proxy_to_external_service ( request : Request , target_path : str ) :
""" Proxy request to external service """
return await _proxy_request ( request , target_path , settings . EXTERNAL_SERVICE_URL )
2025-07-26 18:46:52 +02:00
2025-09-05 12:55:26 +02:00
async def _proxy_to_training_service ( request : Request , target_path : str , tenant_id : str = None ) :
2025-07-26 18:46:52 +02:00
""" Proxy request to training service """
2025-09-05 12:55:26 +02:00
return await _proxy_request ( request , target_path , settings . TRAINING_SERVICE_URL , tenant_id = tenant_id )
2025-07-26 18:46:52 +02:00
2025-08-15 23:11:53 +02:00
async def _proxy_to_forecasting_service ( request : Request , target_path : str , tenant_id : str = None ) :
2025-07-26 18:46:52 +02:00
""" Proxy request to forecasting service """
2025-08-15 23:11:53 +02:00
return await _proxy_request ( request , target_path , settings . FORECASTING_SERVICE_URL , tenant_id = tenant_id )
2025-07-26 18:46:52 +02:00
async def _proxy_to_notification_service ( request : Request , target_path : str ) :
""" Proxy request to notification service """
return await _proxy_request ( request , target_path , settings . NOTIFICATION_SERVICE_URL )
2025-08-15 23:11:53 +02:00
async def _proxy_to_inventory_service ( request : Request , target_path : str , tenant_id : str = None ) :
2025-08-14 13:26:59 +02:00
""" Proxy request to inventory service """
2025-08-15 23:11:53 +02:00
return await _proxy_request ( request , target_path , settings . INVENTORY_SERVICE_URL , tenant_id = tenant_id )
2025-08-14 13:26:59 +02:00
2025-08-23 16:30:45 +02:00
async def _proxy_to_production_service ( request : Request , target_path : str , tenant_id : str = None ) :
""" Proxy request to production service """
return await _proxy_request ( request , target_path , settings . PRODUCTION_SERVICE_URL , tenant_id = tenant_id )
async def _proxy_to_orders_service ( request : Request , target_path : str , tenant_id : str = None ) :
""" Proxy request to orders service """
return await _proxy_request ( request , target_path , settings . ORDERS_SERVICE_URL , tenant_id = tenant_id )
2025-09-04 23:19:53 +02:00
async def _proxy_to_suppliers_service ( request : Request , target_path : str , tenant_id : str = None ) :
""" Proxy request to suppliers service """
return await _proxy_request ( request , target_path , settings . SUPPLIERS_SERVICE_URL , tenant_id = tenant_id )
2025-09-19 21:39:04 +02:00
async def _proxy_to_recipes_service ( request : Request , target_path : str , tenant_id : str = None ) :
""" Proxy request to recipes service """
return await _proxy_request ( request , target_path , settings . RECIPES_SERVICE_URL , tenant_id = tenant_id )
2025-10-07 07:15:07 +02:00
async def _proxy_to_pos_service ( request : Request , target_path : str , tenant_id : str = None ) :
""" Proxy request to POS service """
return await _proxy_request ( request , target_path , settings . POS_SERVICE_URL , tenant_id = tenant_id )
2025-10-30 21:08:07 +01:00
async def _proxy_to_procurement_service ( request : Request , target_path : str , tenant_id : str = None ) :
""" Proxy request to procurement service """
return await _proxy_request ( request , target_path , settings . PROCUREMENT_SERVICE_URL , tenant_id = tenant_id )
2025-10-21 19:50:07 +02:00
async def _proxy_to_alert_processor_service ( request : Request , target_path : str , tenant_id : str = None ) :
""" Proxy request to alert processor service """
return await _proxy_request ( request , target_path , settings . ALERT_PROCESSOR_SERVICE_URL , tenant_id = tenant_id )
2025-10-31 11:54:19 +01:00
async def _proxy_to_orchestrator_service ( request : Request , target_path : str , tenant_id : str = None ) :
""" Proxy request to orchestrator service """
return await _proxy_request ( request , target_path , settings . ORCHESTRATOR_SERVICE_URL , tenant_id = tenant_id )
2025-11-05 13:34:56 +01:00
async def _proxy_to_ai_insights_service ( request : Request , target_path : str , tenant_id : str = None ) :
""" Proxy request to AI insights service """
return await _proxy_request ( request , target_path , settings . AI_INSIGHTS_SERVICE_URL , tenant_id = tenant_id )
2025-11-30 09:12:40 +01:00
async def _proxy_to_distribution_service ( request : Request , target_path : str , tenant_id : str = None ) :
""" Proxy request to distribution service """
return await _proxy_request ( request , target_path , settings . DISTRIBUTION_SERVICE_URL , tenant_id = tenant_id )
2025-08-15 23:11:53 +02:00
async def _proxy_request ( request : Request , target_path : str , service_url : str , tenant_id : str = None ) :
2025-07-26 18:46:52 +02:00
""" Generic proxy function with enhanced error handling """
# Handle OPTIONS requests directly for CORS
if request . method == " OPTIONS " :
return Response (
status_code = 200 ,
headers = {
" Access-Control-Allow-Origin " : settings . CORS_ORIGINS_LIST ,
" Access-Control-Allow-Methods " : " GET, POST, PUT, DELETE, OPTIONS " ,
" Access-Control-Allow-Headers " : " Content-Type, Authorization, X-Tenant-ID " ,
" Access-Control-Allow-Credentials " : " true " ,
" Access-Control-Max-Age " : " 86400 "
}
)
2025-07-20 23:43:42 +02:00
try :
2025-07-26 18:46:52 +02:00
url = f " { service_url } { target_path } "
2026-01-12 14:24:14 +01:00
2026-01-12 22:15:11 +01:00
# Use unified HeaderManager for consistent header forwarding
headers = header_manager . get_all_headers_for_proxy ( request )
# Add tenant ID header if provided (override if needed)
2025-08-15 23:11:53 +02:00
if tenant_id :
2026-01-12 22:15:11 +01:00
headers [ " x-tenant-id " ] = tenant_id
# Debug logging
user_context = getattr ( request . state , ' user ' , None )
if user_context :
logger . info ( f " Forwarding request to { url } with user context: user_id= { user_context . get ( ' user_id ' ) } , email= { user_context . get ( ' email ' ) } , tenant_id= { tenant_id } , subscription_tier= { user_context . get ( ' subscription_tier ' , ' not_set ' ) } " )
2025-09-05 12:55:26 +02:00
else :
logger . warning ( f " No user context available when forwarding request to { url } . request.state.user: { getattr ( request . state , ' user ' , ' NOT_SET ' ) } " )
2025-09-04 23:19:53 +02:00
2025-07-20 23:43:42 +02:00
# Get request body if present
body = None
2025-10-06 15:27:01 +02:00
files = None
data = None
2025-07-26 18:46:52 +02:00
if request . method in [ " POST " , " PUT " , " PATCH " ] :
2025-10-06 15:27:01 +02:00
content_type = request . headers . get ( " content-type " , " " )
logger . info ( f " Processing { request . method } request with content-type: { content_type } " )
# Handle multipart/form-data (file uploads)
if " multipart/form-data " in content_type :
logger . info ( " Detected multipart/form-data, parsing form... " )
# For multipart/form-data, we need to re-parse and forward as files
form = await request . form ( )
logger . info ( f " Form parsed, found { len ( form ) } fields: { list ( form . keys ( ) ) } " )
# Extract files and form fields separately
files_dict = { }
data_dict = { }
for key , value in form . items ( ) :
if hasattr ( value , ' file ' ) : # It's a file
# Read file content
file_content = await value . read ( )
files_dict [ key ] = ( value . filename , file_content , value . content_type )
logger . info ( f " Found file field ' { key } ' : filename= { value . filename } , size= { len ( file_content ) } , type= { value . content_type } " )
else : # It's a regular form field
data_dict [ key ] = value
logger . info ( f " Found form field ' { key } ' : value= { value } " )
files = files_dict if files_dict else None
data = data_dict if data_dict else None
logger . info ( f " Forwarding multipart request with files= { list ( files . keys ( ) ) if files else None } , data= { list ( data . keys ( ) ) if data else None } " )
2026-01-12 22:15:11 +01:00
# For multipart requests, we need to get fresh headers since httpx will set content-type
# Get all headers again to ensure we have the complete set
headers = header_manager . get_all_headers_for_proxy ( request )
# httpx will automatically set content-type for multipart, so we don't need to remove it
2025-10-06 15:27:01 +02:00
else :
# For other content types, use body as before
body = await request . body ( )
logger . info ( f " Using raw body, size: { len ( body ) } bytes " )
2025-07-26 18:46:52 +02:00
# Add query parameters
params = dict ( request . query_params )
2025-10-06 15:27:01 +02:00
2025-07-29 12:45:39 +02:00
timeout_config = httpx . Timeout (
connect = 30.0 , # Connection timeout
read = 600.0 , # Read timeout: 10 minutes (was 30s)
write = 30.0 , # Write timeout
pool = 30.0 # Pool timeout
)
2025-10-06 15:27:01 +02:00
2025-07-29 12:45:39 +02:00
async with httpx . AsyncClient ( timeout = timeout_config ) as client :
2025-07-20 23:43:42 +02:00
response = await client . request (
2025-07-26 18:46:52 +02:00
method = request . method ,
2025-07-20 23:43:42 +02:00
url = url ,
headers = headers ,
content = body ,
2025-10-06 15:27:01 +02:00
files = files ,
data = data ,
2025-07-26 18:46:52 +02:00
params = params
2025-07-17 19:46:41 +02:00
)
2025-07-26 18:46:52 +02:00
# Handle different response types
if response . headers . get ( " content-type " , " " ) . startswith ( " application/json " ) :
try :
content = response . json ( )
except :
content = { " message " : " Invalid JSON response from service " }
else :
content = response . text
2025-07-17 19:46:41 +02:00
return JSONResponse (
status_code = response . status_code ,
2025-07-26 18:46:52 +02:00
content = content
2025-07-17 19:46:41 +02:00
)
2025-07-26 18:46:52 +02:00
except Exception as e :
logger . error ( f " Unexpected error proxying to { service_url } { target_path } : { e } " )
raise HTTPException (
status_code = 500 ,
detail = " Internal gateway error "
2025-10-27 16:33:26 +01:00
)