from __future__ import annotations import logging from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from app import __version__ from app.config import Settings from app.db import init_db from app.pool import PoolValidationError, load_pool_from_file from app.room import RoomManager from app.routes_admin import router as admin_router from app.routes_student import router as student_router log = logging.getLogger("quiz") def create_app(settings: Settings | None = None) -> FastAPI: settings = settings or Settings.from_env() rooms = RoomManager(settings) @asynccontextmanager async def lifespan(_app: FastAPI): await init_db(settings.db_path) try: pool = load_pool_from_file(settings.pool_path) except PoolValidationError as exc: log.error("Pool load failed at %s: %s", settings.pool_path, exc) log.error("Server is starting without an active session.") log.error("Drop a valid pool JSON at %s and restart.", settings.pool_path) else: sid = pool.get("session_id", settings.default_session_id) await rooms.ensure_single_session(sid, pool) rooms.canonical_sid = sid log.info("Session ready: sid=%s title=%r questions=%d", sid, pool["title"], len(pool["questions"])) yield app = FastAPI(title="Live In-Lecture Quiz Portal", lifespan=lifespan) app.state.settings = settings app.state.rooms = rooms @app.get("/healthz") async def healthz(): return { "ok": True, "version": __version__, "sessions_active": await rooms.sessions_active(), "ws_clients": rooms.ws_client_count(), } app.mount("/static", StaticFiles(directory="static"), name="static") app.include_router(admin_router(settings, rooms)) app.include_router(student_router(settings, rooms)) return app app = create_app()