from contextlib import asynccontextmanager from typing import Any from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from app.core.config import get_settings from app.core.database import Base, engine from app.core.errors import register_error_handlers from app.core.module_loader import import_side_modules, register_routers @asynccontextmanager async def lifespan(app: FastAPI): # Import every module's models/importers/ingest so that: # - Base.metadata knows all tables before create_all # - importer & ingest registries are populated (decorator side effects) import_side_modules() # v1 table-creation strategy: create_all on startup (Alembic later). Base.metadata.create_all(bind=engine) yield def create_app() -> FastAPI: settings = get_settings() app = FastAPI( title="LifeTrack API", version="1.0.0", docs_url="/api/docs", redoc_url=None, openapi_url="/api/openapi.json", lifespan=lifespan, ) if settings.cors_origins: app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) register_error_handlers(app) register_routers(app) # auto-discovery, see architecture ยง3.4 @app.get("/api/healthz", include_in_schema=False) def healthz() -> dict[str, str]: return {"status": "ok"} # Bare aliases (no /api prefix) for container/orchestrator probes. @app.get("/healthz", include_in_schema=False) def healthz_alias() -> dict[str, str]: return {"status": "ok"} @app.get("/openapi.json", include_in_schema=False) def openapi_alias() -> JSONResponse: schema: dict[str, Any] = app.openapi() return JSONResponse(schema) return app app = create_app()