Files
movie-night/app/main.py
T
kbondelie 3d5de06b44 Initial commit — Movie Night media discovery app
AI-powered web app that recommends unwatched movies from a Jellyfin
library based on natural language mood input. Jellyfin auth, modular
LLM backend (Claude/OpenAI/Ollama), two-tier pre-filter + AI ranking,
mobile-responsive dark theme UI with poster cards and deep links.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 19:20:56 -07:00

60 lines
1.8 KiB
Python

import asyncio
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
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, request: Request):
from app.routers.auth import get_current_user
await get_current_user(request)
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")