from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from starlette.middleware.gzip import GZipMiddleware from logsetup import setup_logging setup_logging() from config import FRONTEND_DIST, STORAGE_DIR from database import init_db, close_db from guide import reconcile_guides from routes import router @asynccontextmanager async def lifespan(app: FastAPI): (STORAGE_DIR / "topics").mkdir(parents=True, exist_ok=True) await init_db() await reconcile_guides() yield await close_db() class CachedStatic(StaticFiles): """StaticFiles with Cache-Control: hashed assets forever (immutable), index.html never cached (it always points at the current asset hashes).""" async def get_response(self, path, scope): resp = await super().get_response(path, scope) if path.startswith("assets/"): resp.headers["Cache-Control"] = "public, max-age=31536000, immutable" else: resp.headers["Cache-Control"] = "no-cache" return resp app = FastAPI(title="Creator", lifespan=lifespan) # gzip for the JS/CSS bundle + large JSON responses (~1.39 MB JS → ~400 KB). app.add_middleware(GZipMiddleware, minimum_size=500) app.include_router(router) if FRONTEND_DIST.exists(): app.mount("/", CachedStatic(directory=FRONTEND_DIST, html=True), name="frontend")