update
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi import Body, Cookie, FastAPI, HTTPException, Request, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from . import db, sync
|
||||
from . import db, events, search, suggestions, sync
|
||||
from .config import IMAGE_BASE, STATIC_DIR
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
@@ -35,6 +37,7 @@ async def guarded_sync():
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
db.init()
|
||||
suggestions.init()
|
||||
scheduler.add_job(guarded_sync, "cron", hour=4, minute=30, id="daily-sync")
|
||||
scheduler.start()
|
||||
if not db.get_meta("last_sync"):
|
||||
@@ -71,6 +74,137 @@ def movie(movie_id: int):
|
||||
return found
|
||||
|
||||
|
||||
@app.get("/api/search")
|
||||
async def search_movies(q: str):
|
||||
"""Freie TMDB-Suche für Filme außerhalb des Katalogs."""
|
||||
if len(q.strip()) < 2:
|
||||
return []
|
||||
return await search.search(q.strip())
|
||||
|
||||
|
||||
@app.get("/api/search/{movie_id}")
|
||||
async def search_detail(movie_id: int):
|
||||
"""Volle Daten zu einem Suchtreffer. Erst der Katalog, dann TMDB."""
|
||||
found = db.get_movie(movie_id)
|
||||
if found:
|
||||
return found
|
||||
try:
|
||||
return await search.detail(movie_id)
|
||||
except Exception:
|
||||
raise HTTPException(status_code=404, detail="Film nicht gefunden")
|
||||
|
||||
|
||||
def sitzung(response: Response, sid):
|
||||
"""Vorhandene Sitzung weiterverwenden, sonst eine neue setzen."""
|
||||
if sid:
|
||||
return sid
|
||||
neu = suggestions.neue_session()
|
||||
response.set_cookie(
|
||||
suggestions.COOKIE,
|
||||
neu,
|
||||
max_age=60 * 60 * 24 * 365,
|
||||
httponly=True,
|
||||
samesite="lax",
|
||||
)
|
||||
return neu
|
||||
|
||||
|
||||
# Der letzte Dreh, damit eine Seite mitten in der Animation nachziehen kann
|
||||
letzter_dreh = {"ergebnis": None, "start": 0.0}
|
||||
|
||||
|
||||
@app.get("/api/events")
|
||||
async def ereignisse(request: Request):
|
||||
queue = events.anmelden()
|
||||
return StreamingResponse(
|
||||
events.stream(queue, request),
|
||||
media_type="text/event-stream",
|
||||
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/spin")
|
||||
def drehen():
|
||||
"""Einer dreht, alle sehen dasselbe Ergebnis."""
|
||||
laeuft = (
|
||||
letzter_dreh["ergebnis"]
|
||||
and time.monotonic() - letzter_dreh["start"] < suggestions.DREH_DAUER
|
||||
)
|
||||
if laeuft:
|
||||
return {"started": False, "reason": "dreht bereits"}
|
||||
|
||||
ergebnis = suggestions.ziehen()
|
||||
if not ergebnis:
|
||||
raise HTTPException(status_code=400, detail="Keine Vorschläge")
|
||||
|
||||
letzter_dreh["ergebnis"] = ergebnis
|
||||
letzter_dreh["start"] = time.monotonic()
|
||||
events.senden("spin", ergebnis)
|
||||
return {"started": True, **ergebnis}
|
||||
|
||||
|
||||
@app.get("/api/spin")
|
||||
def letzter():
|
||||
"""Für Seiten, die mitten im Dreh geladen werden."""
|
||||
ergebnis = letzter_dreh["ergebnis"]
|
||||
if not ergebnis:
|
||||
return {"running": False, "result": None}
|
||||
vergangen = time.monotonic() - letzter_dreh["start"]
|
||||
return {
|
||||
"running": vergangen < suggestions.DREH_DAUER,
|
||||
# Wer später dazukommt, überspringt den bereits gelaufenen Teil
|
||||
"elapsed": round(vergangen, 2),
|
||||
"result": ergebnis,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/suggestions")
|
||||
def liste_vorschlaege(response: Response, filmabend_sid: str | None = Cookie(None)):
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
eintraege = suggestions.alle()
|
||||
return {
|
||||
"items": [
|
||||
{k: v for k, v in e.items() if k != "session_id"} for e in eintraege
|
||||
],
|
||||
# Damit die Oberfläche den eigenen Vorschlag hervorheben kann
|
||||
"mine": next(
|
||||
(e["id"] for e in eintraege if e["session_id"] == sid),
|
||||
None,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/suggestions")
|
||||
async def vorschlagen(
|
||||
response: Response,
|
||||
movie_id: int = Body(..., embed=True),
|
||||
name: str | None = Body(None, embed=True),
|
||||
filmabend_sid: str | None = Cookie(None),
|
||||
):
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
|
||||
movie = db.get_movie(movie_id)
|
||||
if not movie:
|
||||
try:
|
||||
movie = await search.detail(movie_id)
|
||||
except Exception:
|
||||
raise HTTPException(status_code=404, detail="Film nicht gefunden")
|
||||
|
||||
# Ein zweiter Vorschlag ersetzt den ersten, statt abgelehnt zu werden
|
||||
eintrag = suggestions.ersetzen(sid, movie, name)
|
||||
events.senden("suggestions")
|
||||
return {k: v for k, v in eintrag.items() if k != "session_id"}
|
||||
|
||||
|
||||
@app.delete("/api/suggestions")
|
||||
def vorschlag_loeschen(response: Response, filmabend_sid: str | None = Cookie(None)):
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
entfernt = suggestions.loeschen(sid)
|
||||
if entfernt:
|
||||
events.senden("suggestions")
|
||||
return {"removed": entfernt}
|
||||
|
||||
|
||||
@app.post("/api/sync")
|
||||
async def trigger_sync():
|
||||
if sync_lock.locked():
|
||||
|
||||
Reference in New Issue
Block a user