main.py 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. """FastAPI application entry point.
  2. Importing this module must NOT require a live database connection (the engine is
  3. created lazily in :mod:`app.db.session`), so the OpenAPI schema can be exported
  4. offline.
  5. """
  6. from __future__ import annotations
  7. import os
  8. from fastapi import FastAPI
  9. from fastapi.responses import FileResponse
  10. from fastapi.staticfiles import StaticFiles
  11. from app.api.funnels import router as funnels_router
  12. app = FastAPI(
  13. title="hs-data API",
  14. version="0.1.0",
  15. description=(
  16. "Group-buy (拼团) funnel service over ads_trd_group_funnel_daily "
  17. "(single day, full history) + ads_trd_group_funnel_rolling (7d/30d)."
  18. ),
  19. )
  20. app.include_router(funnels_router)
  21. @app.get("/health", tags=["meta"])
  22. async def health() -> dict[str, str]:
  23. """Liveness probe. Does not touch the database."""
  24. return {"status": "ok"}
  25. # Optional single-origin static hosting: when STATIC_DIR points at a built SPA
  26. # (apps/web/dist), this process serves the frontend alongside /api on one port,
  27. # so the deployed page can call the real API same-origin (no CORS, no proxy).
  28. # Unset locally / in tests → api-only, Vite serves the frontend. Registered
  29. # AFTER the router so /api and /health keep priority; the GET catch-all does
  30. # SPA fallback (real files served, unknown routes → index.html).
  31. _STATIC_DIR = os.environ.get("STATIC_DIR")
  32. if _STATIC_DIR and os.path.isdir(_STATIC_DIR):
  33. _assets = os.path.join(_STATIC_DIR, "assets")
  34. if os.path.isdir(_assets):
  35. app.mount("/assets", StaticFiles(directory=_assets), name="assets")
  36. @app.get("/{full_path:path}", include_in_schema=False)
  37. async def spa_fallback(full_path: str) -> FileResponse:
  38. candidate = os.path.join(_STATIC_DIR, full_path)
  39. if full_path and os.path.isfile(candidate):
  40. return FileResponse(candidate)
  41. return FileResponse(os.path.join(_STATIC_DIR, "index.html"))