Add per-key OpenAPI document and Swagger UI portal
GET /openapi.json (X-API-Key authenticated) merges the upstream OpenAPI documents into one spec scoped to the calling key: only granted operations, paths rewritten to gateway routes, component schemas namespaced per service. GET /docs serves a Swagger UI portal that loads the key-scoped spec and injects the key into try-it-out requests. Discovery now caches the raw upstream spec documents (same 5-minute TTL), and FastAPI's built-in /docs and /openapi.json are disabled in favor of the portal routes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
77d7a50fa9
commit
d5878f6130
@@ -15,6 +15,7 @@ SPEC_PATHS = ("/openapi.json", "/swagger.json", "/api-docs")
|
||||
SYNC_TTL_SECONDS = 300
|
||||
_last_sync: dict[int, float] = {} # service id -> monotonic time of last attempt
|
||||
_spec_found: dict[int, bool] = {} # service id -> did the last attempt find a spec
|
||||
_spec_cache: dict[int, dict] = {} # service id -> raw OpenAPI document of last fetch
|
||||
|
||||
|
||||
def fetch_spec(base_url: str, verify_tls: bool = True) -> dict | None:
|
||||
@@ -51,9 +52,18 @@ def sync_service(db: Session, service: Service, force: bool = False) -> tuple[bo
|
||||
_spec_found[service.id] = spec is not None
|
||||
if spec is None:
|
||||
return False, False
|
||||
_spec_cache[service.id] = spec
|
||||
return True, apply_spec(db, service, spec)
|
||||
|
||||
|
||||
def get_spec(db: Session, service: Service) -> dict | None:
|
||||
"""Raw upstream OpenAPI document, refreshed through the same TTL cache as
|
||||
the endpoint sync. Falls back to the last known document if the upstream
|
||||
is temporarily unreachable."""
|
||||
sync_service(db, service)
|
||||
return _spec_cache.get(service.id)
|
||||
|
||||
|
||||
def validate_service(db: Session, service: Service) -> dict:
|
||||
"""Connectivity probe for the admin UI. Prefers the OpenAPI document —
|
||||
finding one both proves reachability and refreshes the endpoint cache —
|
||||
@@ -63,6 +73,7 @@ def validate_service(db: Session, service: Service) -> dict:
|
||||
if spec is not None:
|
||||
_last_sync[service.id] = time.monotonic()
|
||||
_spec_found[service.id] = True
|
||||
_spec_cache[service.id] = spec
|
||||
apply_spec(db, service, spec)
|
||||
return {"ok": True, "spec_found": True,
|
||||
"endpoints": len(service.endpoints),
|
||||
|
||||
Reference in New Issue
Block a user