| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- 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)
|