| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253 |
- """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"))
|