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>
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
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")
|
||||
Reference in New Issue
Block a user