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:
+20
-25
@@ -15,18 +15,7 @@ from app.core.config import settings
|
||||
from app.core.database import Base, engine
|
||||
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.http.routers import health
|
||||
from app.interfaces.realtime.bus import install as install_realtime_bus
|
||||
from app.interfaces.realtime.bus import start_relay, stop_relay
|
||||
from app.interfaces.realtime.router import realtime_manager
|
||||
@@ -99,13 +88,22 @@ async def _record_startup_failure(exc: BaseException) -> None:
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""Build the *public* app.
|
||||
|
||||
This is the only app uvicorn binds. It deliberately exposes no REST API: the
|
||||
application surface is reachable only over the ``/api/ws`` WebSocket (live
|
||||
feeds + RPC). ``/health`` stays as an HTTP liveness probe because deploy
|
||||
orchestration and the browser's status check both need a plain GET, and a
|
||||
probe is not an application endpoint.
|
||||
"""
|
||||
application = FastAPI(
|
||||
title=settings.app_name,
|
||||
version="0.1.0",
|
||||
description="AI Workforce Platform — standalone multi-tenant SaaS.",
|
||||
lifespan=lifespan,
|
||||
docs_url="/docs",
|
||||
openapi_url="/openapi.json",
|
||||
# No REST API on the public surface, so the interactive docs are off.
|
||||
docs_url=None,
|
||||
openapi_url=None,
|
||||
)
|
||||
|
||||
# Starlette applies middleware outermost-first in reverse registration
|
||||
@@ -128,17 +126,10 @@ def create_app() -> FastAPI:
|
||||
|
||||
register_exception_handlers(application)
|
||||
|
||||
# The only public routes: a liveness probe and the single WebSocket that
|
||||
# carries every application call (live events + RPC). All HTTP handlers live
|
||||
# in the internal app reached through that socket.
|
||||
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")
|
||||
# The live feed: one WebSocket per browser, every push on it.
|
||||
application.include_router(realtime_router, prefix="/api")
|
||||
|
||||
@application.get("/", tags=["meta"])
|
||||
@@ -146,8 +137,12 @@ def create_app() -> FastAPI:
|
||||
return {
|
||||
"name": settings.app_name,
|
||||
"version": "0.1.0",
|
||||
"docs": "/docs",
|
||||
"health": "/health",
|
||||
"websocket": "/api/ws",
|
||||
"note": (
|
||||
"All application calls travel over the WebSocket at /api/ws "
|
||||
"(live feeds + RPC). There is no REST API on this server."
|
||||
),
|
||||
}
|
||||
|
||||
return application
|
||||
|
||||
Reference in New Issue
Block a user