import contextlib import logging from collections.abc import AsyncIterator import fastapi from fansifter_common.api import error_handlers from fansifter_common.context.asgi.middleware import CorrelationIdMiddleware from fastapi.middleware import Middleware from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from owslogger.asgi import RequestLoggingMiddleware from resonance_engine.adapters.db import db from resonance_engine.adapters.kafka import kafka_producer from resonance_engine.api.routers import ( auth, config, dashboard, dsp, fandata, infra, tasks, ) from resonance_engine.config import settings from resonance_engine.dsp.gateway import dsp_gateway logger = logging.getLogger(__name__) def get_app() -> fastapi.FastAPI: """Get FastAPI application instance.""" @contextlib.asynccontextmanager async def lifespan(_app: fastapi.FastAPI) -> AsyncIterator[None]: with db, kafka_producer, dsp_gateway: yield None app = fastapi.FastAPI( title="Resonance Engine", description="Resonance Engine API", version=settings.service_version, debug=settings.debug, lifespan=lifespan, middleware=[ Middleware(CorrelationIdMiddleware), Middleware( RequestLoggingMiddleware, exclude_paths=[ "/hello/", "/openapi.json", "/docs", "/redoc", "/dashboard/assets*", "/favicon.ico", ], logger=logger, ), Middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ), ], ) # Register exception error handlers error_handlers.register_defaults(app) assets_dir = settings.dashboard_dir / "assets" if assets_dir.exists(): app.mount( "/dashboard/assets", StaticFiles(directory=settings.dashboard_dir / "assets"), name="assets", ) # Infra routes app.include_router(infra.router) # API routes (no prefix — API lives at root) app.include_router(auth.router) app.include_router(config.router) app.include_router(dsp.router) app.include_router(fandata.router) app.include_router(tasks.router) # SPA catch-all must be last. app.include_router(dashboard.router) return app