2025-09-21 13:27:50 +02:00
"""
Subscription routes for API Gateway - Direct subscription endpoints
"""
from fastapi import APIRouter , Request , Response , HTTPException , Path
from fastapi . responses import JSONResponse
import httpx
import logging
from typing import Optional
from app . core . config import settings
logger = logging . getLogger ( __name__ )
router = APIRouter ( )
# ================================================================
# SUBSCRIPTION ENDPOINTS - Direct routing to tenant service
# ================================================================
2025-10-07 07:15:07 +02:00
@router.api_route ( " /tenants/subscriptions/ {tenant_id} / { path:path} " , methods = [ " GET " , " POST " , " PUT " , " DELETE " , " OPTIONS " ] )
2025-09-21 13:27:50 +02:00
async def proxy_subscription_endpoints ( request : Request , tenant_id : str = Path ( . . . ) , path : str = " " ) :
""" Proxy subscription requests directly to tenant service """
2025-10-07 07:15:07 +02:00
target_path = f " /api/v1/tenants/subscriptions/ { tenant_id } / { path } " . rstrip ( " / " )
2025-09-21 13:27:50 +02:00
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /subscriptions/plans " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_subscription_plans ( request : Request ) :
""" Proxy subscription plans request to tenant service """
2025-10-15 16:12:49 +02:00
target_path = " /plans "
2025-09-25 14:30:47 +02:00
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /plans " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_plans ( request : Request ) :
""" Proxy plans request to tenant service """
2025-10-15 16:12:49 +02:00
target_path = " /plans "
2025-09-21 13:27:50 +02:00
return await _proxy_to_tenant_service ( request , target_path )
2025-10-31 11:54:19 +01:00
@router.api_route ( " /subscriptions/ {tenant_id} /invoices " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_invoices ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy invoices request to tenant service """
target_path = f " /api/v1/subscriptions/ { tenant_id } /invoices "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /subscriptions/ {tenant_id} /status " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_subscription_status ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy subscription status request to tenant service """
target_path = f " /api/v1/subscriptions/ { tenant_id } /status "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /subscriptions/cancel " , methods = [ " POST " , " OPTIONS " ] )
async def proxy_subscription_cancel ( request : Request ) :
""" Proxy subscription cancellation request to tenant service """
target_path = " /api/v1/subscriptions/cancel "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /subscriptions/reactivate " , methods = [ " POST " , " OPTIONS " ] )
async def proxy_subscription_reactivate ( request : Request ) :
""" Proxy subscription reactivation request to tenant service """
target_path = " /api/v1/subscriptions/reactivate "
return await _proxy_to_tenant_service ( request , target_path )
2025-09-21 13:27:50 +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 )
async def _proxy_request ( request : Request , target_path : str , service_url : str ) :
""" 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 "
}
)
try :
url = f " { service_url } { target_path } "
# Forward headers and add user/tenant context
headers = dict ( request . headers )
headers . pop ( " host " , None )
# Add user context headers if available
if hasattr ( request . state , ' user ' ) and request . state . user :
user = request . state . user
headers [ " x-user-id " ] = str ( user . get ( ' user_id ' , ' ' ) )
headers [ " x-user-email " ] = str ( user . get ( ' email ' , ' ' ) )
headers [ " x-user-role " ] = str ( user . get ( ' role ' , ' user ' ) )
headers [ " x-user-full-name " ] = str ( user . get ( ' full_name ' , ' ' ) )
headers [ " x-tenant-id " ] = str ( user . get ( ' tenant_id ' , ' ' ) )
2026-01-10 21:45:37 +01:00
# Add subscription context headers
if user . get ( ' subscription_tier ' ) :
headers [ " x-subscription-tier " ] = str ( user . get ( ' subscription_tier ' , ' ' ) )
logger . debug ( f " Forwarding subscription tier: { user . get ( ' subscription_tier ' ) } " )
if user . get ( ' subscription_status ' ) :
headers [ " x-subscription-status " ] = str ( user . get ( ' subscription_status ' , ' ' ) )
logger . debug ( f " Forwarding subscription status: { user . get ( ' subscription_status ' ) } " )
logger . info ( f " Forwarding subscription request to { url } with user context: user_id= { user . get ( ' user_id ' ) } , email= { user . get ( ' email ' ) } , subscription_tier= { user . get ( ' subscription_tier ' , ' not_set ' ) } " )
2025-09-21 13:27:50 +02:00
else :
logger . warning ( f " No user context available when forwarding subscription request to { url } " )
# Get request body if present
body = None
if request . method in [ " POST " , " PUT " , " PATCH " ] :
body = await request . body ( )
# Add query parameters
params = dict ( request . query_params )
timeout_config = httpx . Timeout (
connect = 30.0 ,
read = 60.0 ,
write = 30.0 ,
pool = 30.0
)
async with httpx . AsyncClient ( timeout = timeout_config ) as client :
response = await client . request (
method = request . method ,
url = url ,
headers = headers ,
content = body ,
params = params
)
# 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
return JSONResponse (
status_code = response . status_code ,
content = content
)
except Exception as e :
logger . error ( f " Unexpected error proxying subscription request to { service_url } { target_path } : { e } " )
raise HTTPException (
status_code = 500 ,
detail = " Internal gateway error "
)