import asyncio from typing import Annotated from core_domain import ServiceDescriptor, ServiceHealth from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response from sqlalchemy import text from sqlalchemy.orm import Session from app.bootstrap.settings import ApiGatewaySettings from app.db.session import get_db from app.domain.repositories import ApiKeyRepository, GatewayRequestAuditRepository from app.infrastructure.api_keys import generate_api_key, get_api_key_prefix, hash_api_key from app.infrastructure.proxy import ProxyServiceName, ProxyTarget, ServiceProxy from app.schemas.gateway import ( ApiKeyCreateRequest, ApiKeyCreateResponse, ApiKeyListRequest, ApiKeyResponse, ApiKeyStatusPostRequest, ApiKeyStatusUpdateRequest, GatewayAuditServiceStats, GatewayAuditStatsResponse, GatewayRequestAuditResponse, GatewayServicesHealthResponse, ) router = APIRouter() DbSession = Annotated[Session, Depends(get_db)] @router.get("/health", response_model=ServiceDescriptor) def health_check(db: DbSession) -> ServiceDescriptor: db.execute(text("SELECT 1")) return ServiceDescriptor(name="api-gateway") @router.get("/ready", response_model=ServiceHealth) def readiness_check(db: DbSession) -> ServiceHealth: db.execute(text("SELECT 1")) return ServiceHealth(service="api-gateway", status="ok", database="ok") @router.post("/gateway/api-keys", response_model=ApiKeyCreateResponse) def create_api_key( payload: ApiKeyCreateRequest, db: DbSession) -> ApiKeyCreateResponse: api_key = generate_api_key() entity = ApiKeyRepository(db).create( name=payload.name, key_prefix=get_api_key_prefix(api_key), key_hash=hash_api_key(api_key), scopes=payload.scopes, expires_time=payload.expires_time) return ApiKeyCreateResponse( id=entity.id, name=entity.name, key_prefix=entity.key_prefix, api_key=api_key, status=entity.status, scopes=entity.scopes, expires_time=entity.expires_time, created_time=entity.created_time) @router.get("/gateway/api-keys", response_model=list[ApiKeyResponse]) def list_api_keys( db: DbSession) -> list[ApiKeyResponse]: return [ ApiKeyResponse.from_entity(item) for item in ApiKeyRepository(db).list_all() ] @router.post("/gateway/api-keys/list", response_model=list[ApiKeyResponse]) def list_api_keys_post( payload: ApiKeyListRequest, db: DbSession) -> list[ApiKeyResponse]: return [ ApiKeyResponse.from_entity(item) for item in ApiKeyRepository(db).list_all() ] @router.patch("/gateway/api-keys/{api_key_id}/status", response_model=ApiKeyResponse) def update_api_key_status( api_key_id: str, payload: ApiKeyStatusUpdateRequest, db: DbSession) -> ApiKeyResponse: entity = ApiKeyRepository(db).update_status( api_key_id=api_key_id, status=payload.status) if entity is None: raise HTTPException(status_code=404, detail=f"api key not found: {api_key_id}") return ApiKeyResponse.from_entity(entity) @router.post("/gateway/api-keys/status", response_model=ApiKeyResponse) def update_api_key_status_post( payload: ApiKeyStatusPostRequest, db: DbSession) -> ApiKeyResponse: entity = ApiKeyRepository(db).update_status( api_key_id=payload.api_key_id, status=payload.status) if entity is None: raise HTTPException(status_code=404, detail=f"api key not found: {payload.api_key_id}") return ApiKeyResponse.from_entity(entity) @router.get("/gateway/audits", response_model=list[GatewayRequestAuditResponse]) def list_gateway_audits( db: DbSession, request_id: Annotated[str | None, Query()] = None, target_service: Annotated[str | None, Query()] = None, limit: Annotated[int, Query(ge=1, le=500)] = 100) -> list[GatewayRequestAuditResponse]: items = GatewayRequestAuditRepository(db).list_by_scope( request_id=request_id, target_service=target_service, limit=limit) return [GatewayRequestAuditResponse.from_entity(item) for item in items] @router.get("/gateway/audits/stats", response_model=GatewayAuditStatsResponse) def gateway_audit_stats( db: DbSession) -> GatewayAuditStatsResponse: rows = GatewayRequestAuditRepository(db).stats_by_service() services = [ GatewayAuditServiceStats( target_service=target_service, request_count=request_count, error_count=error_count, average_duration_ms=round(average_duration_ms, 2)) for target_service, request_count, error_count, average_duration_ms in rows ] return GatewayAuditStatsResponse( total_request_count=sum(item.request_count for item in services), total_error_count=sum(item.error_count for item in services), services=services) def get_gateway_settings() -> ApiGatewaySettings: return ApiGatewaySettings() def get_service_proxy( settings: Annotated[ApiGatewaySettings, Depends(get_gateway_settings)]) -> ServiceProxy: return ServiceProxy(settings=settings, timeout_seconds=settings.proxy_timeout_seconds) GatewaySettingsDep = Annotated[ApiGatewaySettings, Depends(get_gateway_settings)] ServiceProxyDep = Annotated[ServiceProxy, Depends(get_service_proxy)] def build_proxy_targets(settings: ApiGatewaySettings) -> dict[ProxyServiceName, ProxyTarget]: return { "session-service": ProxyTarget( service_name="session-service", base_url=settings.session_service_url, path_prefix="/sessions", health_path="/sessions/health"), "tool-service": ProxyTarget( service_name="tool-service", base_url=settings.tool_service_url, path_prefix="/tools", health_path="/tools/health"), "model-gateway-service": ProxyTarget( service_name="model-gateway-service", base_url=settings.model_gateway_service_url, path_prefix="/models", health_path="/models/health"), "model-provider-service": ProxyTarget( service_name="model-provider-service", base_url=settings.model_gateway_service_url, path_prefix="/models/providers", health_path="/models/health"), "code-runner-service": ProxyTarget( service_name="code-runner-service", base_url=settings.code_runner_service_url, path_prefix="/code", health_path="/code/health"), "agent-service": ProxyTarget( service_name="agent-service", base_url=settings.agent_service_url, path_prefix="/agents", health_path="/agents/health"), "memory-service": ProxyTarget( service_name="memory-service", base_url=settings.memory_service_url, path_prefix="/memories", health_path="/memories/health"), "team-service": ProxyTarget( service_name="team-service", base_url=settings.team_service_url, path_prefix="/teams", health_path="/teams/health"), "skill-service": ProxyTarget( service_name="skill-service", base_url=settings.skill_service_url, path_prefix="/skills", health_path="/skills/health"), "human-service": ProxyTarget( service_name="human-service", base_url=settings.human_service_url, path_prefix="/human", health_path="/human/health"), "knowledge-service": ProxyTarget( service_name="knowledge-service", base_url=settings.knowledge_service_url, path_prefix="/knowledge", health_path="/knowledge/health"), "event-service": ProxyTarget( service_name="event-service", base_url=settings.event_service_url, path_prefix="/events", health_path="/events/health"), "identity-service": ProxyTarget( service_name="identity-service", base_url=settings.auth_service_url, path_prefix="/identity", health_path="/identity/health"), "scheduler-service": ProxyTarget( service_name="scheduler-service", base_url=settings.scheduler_service_url, path_prefix="/scheduler", health_path="/scheduler/health"), } @router.get("/gateway/services/health", response_model=GatewayServicesHealthResponse) async def downstream_health_check( settings: GatewaySettingsDep) -> GatewayServicesHealthResponse: targets = build_proxy_targets(settings) health_proxy = ServiceProxy( settings=settings, timeout_seconds=settings.downstream_health_timeout_seconds) downstream_services = await asyncio.gather( *[health_proxy.check_health(target) for target in targets.values()] ) status = "ok" if all(item.status == "ok" for item in downstream_services) else "degraded" return GatewayServicesHealthResponse( status=status, downstream_services=downstream_services) @router.api_route( "/gateway/sessions", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) @router.api_route( "/gateway/sessions/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def proxy_session_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["session-service"], path=path) @router.api_route( "/gateway/agents", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) @router.api_route( "/gateway/agents/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def proxy_agent_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["agent-service"], path=path) @router.api_route( "/gateway/memories", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) @router.api_route( "/gateway/memories/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def proxy_memory_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["memory-service"], path=path) @router.api_route( "/gateway/teams", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) @router.api_route( "/gateway/teams/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def proxy_team_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["team-service"], path=path) @router.api_route( "/gateway/skills", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) @router.api_route( "/gateway/skills/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def proxy_skill_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["skill-service"], path=path) @router.api_route( "/gateway/human", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) @router.api_route( "/gateway/human/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def proxy_human_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["human-service"], path=path) @router.api_route( "/gateway/knowledge", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) @router.api_route( "/gateway/knowledge/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def proxy_knowledge_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["knowledge-service"], path=path) @router.api_route( "/gateway/events", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) @router.api_route( "/gateway/events/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def proxy_event_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["event-service"], path=path) @router.api_route( "/gateway/identity", methods=["POST"]) @router.api_route( "/gateway/identity/{path:path}", methods=["POST"]) async def proxy_identity_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["identity-service"], path=path) @router.api_route( "/gateway/scheduler", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) @router.api_route( "/gateway/scheduler/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def proxy_scheduler_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["scheduler-service"], path=path) @router.api_route( "/gateway/tools", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) @router.api_route( "/gateway/tools/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def proxy_tool_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["tool-service"], path=path) @router.api_route( "/gateway/models", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) @router.api_route( "/gateway/models/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def proxy_model_gateway_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["model-gateway-service"], path=path) @router.api_route( "/gateway/model-providers", methods=["POST"]) @router.api_route( "/gateway/model-providers/{path:path}", methods=["POST"]) async def proxy_model_provider_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["model-provider-service"], path=path) @router.api_route( "/gateway/code", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) @router.api_route( "/gateway/code/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) async def proxy_code_runner_service( request: Request, settings: GatewaySettingsDep, proxy: ServiceProxyDep, path: str = "") -> Response: return await proxy.forward( request=request, target=build_proxy_targets(settings)["code-runner-service"], path=path)