2026-03-14 19:20:56 -07:00
|
|
|
import asyncio
|
|
|
|
|
import logging
|
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
|
|
2026-03-14 20:26:52 -07:00
|
|
|
from fastapi import FastAPI
|
2026-03-14 19:20:56 -07:00
|
|
|
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}")
|
2026-03-14 20:26:52 -07:00
|
|
|
async def poster_proxy(item_id: str):
|
2026-03-14 19:20:56 -07:00
|
|
|
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")
|