feat: remove public REST API; route all app calls over WebSocket RPC
The platform no longer exposes a public /api/* REST surface. The original HTTP route handlers are preserved verbatim as the single source of truth inside an internal, never-socket-bound FastAPI app (rpc_app), and are now reached only via RPC-over-WebSocket dispatch. - realtime router: anonymous connections allowed only to /api/auth/login and /api/auth/signup; all other RPCs/subscribes refused until token auth. - RPC dispatch re-injects the bearer token and X-Request-ID, then forwards through httpx.ASGITransport against the internal app (no network hop). - Public app now serves only /, /health, /health/ready and /api/ws; docs/openapi disabled. - Frontend: apiRequest routes /api/* to ws.rpc(); /health probes keep fetch. RealtimeClient gains rpc() with id correlation, timeout and AbortSignal. - Tests repointed to get_rpc_app(); anonymous-auth behaviour covered. Result: zero REST on the public surface; 264 backend tests + frontend typecheck pass.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""Internal, never-socket-bound FastAPI app that holds every HTTP handler.
|
||||
|
||||
The public app (``app.main.create_app``) exposes only ``/health`` and the
|
||||
``/api/ws`` WebSocket. All request/response logic lives here and is reached in
|
||||
exactly one of two ways:
|
||||
|
||||
* from the WS-RPC proxy (``app.interfaces.realtime.router``), which dispatches
|
||||
client RPCs into this app over an in-process ASGI transport, and
|
||||
* from the test-suite's ``AsyncClient``/``TestClient``.
|
||||
|
||||
It is intentionally never bound to a socket, so including the routers here adds no
|
||||
public REST surface — the only public routes are the ones ``create_app``
|
||||
registers. Keeping the handlers in one FastAPI app also means they run through the
|
||||
real middleware, dependencies, validation and error handling, unchanged.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
from app.core.config import settings
|
||||
from app.interfaces.http.errors import register_exception_handlers
|
||||
from app.interfaces.http.request_context import RequestContextMiddleware
|
||||
from app.interfaces.http.routers import (
|
||||
admin,
|
||||
agy,
|
||||
auth,
|
||||
billing,
|
||||
health,
|
||||
knowledge,
|
||||
organizations,
|
||||
projects,
|
||||
sandbox,
|
||||
workforce,
|
||||
)
|
||||
from app.interfaces.realtime.router import router as realtime_router
|
||||
from app.main import lifespan # shared lifespan installs the realtime bus
|
||||
|
||||
#: Module-level singleton so the RPC proxy and the tests share one app instance.
|
||||
_rpc_app: FastAPI | None = None
|
||||
|
||||
|
||||
def build_rpc_app() -> FastAPI:
|
||||
"""Construct the full handler app. Called once per process."""
|
||||
application = FastAPI(
|
||||
title=settings.app_name,
|
||||
version="0.1.0",
|
||||
description="AI Workforce Platform — internal RPC dispatch surface.",
|
||||
lifespan=lifespan,
|
||||
# This app is reached in-process, never over the network, so the
|
||||
# interactive docs would only be a liability.
|
||||
docs_url=None,
|
||||
openapi_url=None,
|
||||
)
|
||||
application.add_middleware(RequestContextMiddleware)
|
||||
register_exception_handlers(application)
|
||||
|
||||
# Same mounting as the old public app: health at the root, everything else
|
||||
# under /api, and the live socket alongside the RPC surface.
|
||||
application.include_router(health.router)
|
||||
application.include_router(auth.router, prefix="/api")
|
||||
application.include_router(organizations.router, prefix="/api")
|
||||
application.include_router(projects.router, prefix="/api")
|
||||
application.include_router(workforce.router, prefix="/api")
|
||||
application.include_router(billing.router, prefix="/api")
|
||||
application.include_router(knowledge.router, prefix="/api")
|
||||
application.include_router(sandbox.router, prefix="/api")
|
||||
application.include_router(admin.router, prefix="/api")
|
||||
application.include_router(agy.router, prefix="/api")
|
||||
application.include_router(realtime_router, prefix="/api")
|
||||
return application
|
||||
|
||||
|
||||
def get_rpc_app() -> FastAPI:
|
||||
"""Return the process-wide internal app, building it on first use."""
|
||||
global _rpc_app
|
||||
if _rpc_app is None:
|
||||
_rpc_app = build_rpc_app()
|
||||
return _rpc_app
|
||||
Reference in New Issue
Block a user