from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI, HTTPException from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles import app.models # noqa: F401 — registers models on Base.metadata before create_all from app.config import settings from app.db import Base, engine from app.routes.auth import router as auth_router from app.routes.codex import router as codex_router from app.ws import AUDIO_DIR from app.ws import router as ws_router FRONTEND_DIST = Path(__file__).resolve().parent.parent.parent / "frontend" / "dist" @asynccontextmanager async def lifespan(app: FastAPI): AUDIO_DIR.mkdir(parents=True, exist_ok=True) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield app = FastAPI(title="Quantumancy", lifespan=lifespan) app.include_router(auth_router) app.include_router(codex_router) app.include_router(ws_router) @app.get("/healthz") async def healthz(): return {"status": "ok"} app.mount( "/assets", StaticFiles(directory=FRONTEND_DIST / "assets", check_dir=False), name="frontend-assets", ) app.mount( "/audio", StaticFiles(directory=AUDIO_DIR, check_dir=False), name="spirit-audio", ) @app.get("/{full_path:path}") async def serve_spa(full_path: str): index_file = FRONTEND_DIST / "index.html" if not index_file.exists(): raise HTTPException( status_code=503, detail="Frontend not built. Run `npm run build` in frontend/ and restart.", ) return FileResponse(index_file)