import asyncio import logging from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.responses import Response as FastAPIResponse from fastapi.staticfiles import StaticFiles from app.database import init_database from app.routers import auth, library, users from app.services.jellyfin import get_poster from app.services.library_sync import run_periodic_sync logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s") @asynccontextmanager async def lifespan(app: FastAPI): await init_database() sync_task = asyncio.create_task(run_periodic_sync()) yield sync_task.cancel() try: await sync_task except asyncio.CancelledError: pass app = FastAPI(title="Movie Night", lifespan=lifespan) app.include_router(auth.router, prefix="/api/auth", tags=["auth"]) app.include_router(library.router, prefix="/api/library", tags=["library"]) app.include_router(users.router, prefix="/api", tags=["users"]) # Mood router imported lazily to avoid circular imports during early phases try: from app.routers import mood app.include_router(mood.router, prefix="/api", tags=["mood"]) except ImportError: pass @app.get("/api/poster/{item_id}") async def poster_proxy(item_id: str): image_data = await get_poster(item_id) if image_data is None: return FastAPIResponse(status_code=404) return FastAPIResponse( content=image_data, media_type="image/jpeg", headers={"Cache-Control": "public, max-age=86400"}, ) # Static files must be mounted last (catches all non-API routes) app.mount("/", StaticFiles(directory="app/static", html=True), name="static")