"""FastAPI application entry point. Importing this module must NOT require a live database connection (the engine is created lazily in :mod:`app.db.session`), so the OpenAPI schema can be exported offline. """ from __future__ import annotations import os from fastapi import FastAPI from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from app.api.funnels import router as funnels_router app = FastAPI( title="hs-data API", version="0.1.0", description=( "Group-buy (ζ‹Όε›’) funnel service over ads_trd_group_funnel_daily " "(single day, full history) + ads_trd_group_funnel_rolling (7d/30d)." ), ) app.include_router(funnels_router) @app.get("/health", tags=["meta"]) async def health() -> dict[str, str]: """Liveness probe. Does not touch the database.""" return {"status": "ok"} # Optional single-origin static hosting: when STATIC_DIR points at a built SPA # (apps/web/dist), this process serves the frontend alongside /api on one port, # so the deployed page can call the real API same-origin (no CORS, no proxy). # Unset locally / in tests β†’ api-only, Vite serves the frontend. Registered # AFTER the router so /api and /health keep priority; the GET catch-all does # SPA fallback (real files served, unknown routes β†’ index.html). _STATIC_DIR = os.environ.get("STATIC_DIR") if _STATIC_DIR and os.path.isdir(_STATIC_DIR): _assets = os.path.join(_STATIC_DIR, "assets") if os.path.isdir(_assets): app.mount("/assets", StaticFiles(directory=_assets), name="assets") @app.get("/{full_path:path}", include_in_schema=False) async def spa_fallback(full_path: str) -> FileResponse: candidate = os.path.join(_STATIC_DIR, full_path) if full_path and os.path.isfile(candidate): return FileResponse(candidate) return FileResponse(os.path.join(_STATIC_DIR, "index.html"))