import asyncio import logging from apscheduler.schedulers.asyncio import AsyncIOScheduler from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from . import db, sync from .config import IMAGE_BASE, STATIC_DIR logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") log = logging.getLogger("app") app = FastAPI(title="Netflix Horror DE") scheduler = AsyncIOScheduler() sync_lock = asyncio.Lock() app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:5173"], allow_methods=["*"], allow_headers=["*"], ) async def guarded_sync(): if sync_lock.locked(): log.info("Sync läuft bereits") return None async with sync_lock: return await sync.run() @app.on_event("startup") async def startup(): db.init() scheduler.add_job(guarded_sync, "cron", hour=4, minute=30, id="daily-sync") scheduler.start() if not db.get_meta("last_sync"): log.info("Leere Datenbank, starte ersten Sync im Hintergrund") asyncio.create_task(guarded_sync()) @app.get("/api/health") def health(): return {"ok": True} @app.get("/api/status") def status(): return { "last_sync": db.get_meta("last_sync"), "movie_count": int(db.get_meta("movie_count") or 0), "canon_count": int(db.get_meta("canon_count") or 0), "syncing": sync_lock.locked(), "image_base": IMAGE_BASE, } @app.get("/api/movies") def movies(): return db.all_movies() @app.get("/api/movies/{movie_id}") def movie(movie_id: int): found = db.get_movie(movie_id) if not found: raise HTTPException(status_code=404, detail="Film nicht gefunden") return found @app.post("/api/sync") async def trigger_sync(): if sync_lock.locked(): return {"started": False, "reason": "läuft bereits"} asyncio.create_task(guarded_sync()) return {"started": True} if STATIC_DIR.is_dir(): app.mount("/", StaticFiles(directory=STATIC_DIR, html=True), name="static")