update
This commit is contained in:
@@ -9,7 +9,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from . import db, events, search, suggestions, sync
|
||||
from . import db, events, search, streamproxy, suggestions, sync, watched
|
||||
from .config import IMAGE_BASE, STATIC_DIR
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
@@ -19,9 +19,23 @@ app = FastAPI(title="Netflix Horror DE")
|
||||
scheduler = AsyncIOScheduler()
|
||||
sync_lock = asyncio.Lock()
|
||||
|
||||
class GzipOhneLivekanal(GZipMiddleware):
|
||||
"""Kompression für alles außer den Ereignisstrom.
|
||||
|
||||
GZipMiddleware sammelt die Antwort, bevor sie ausgeht. Bei Server-Sent
|
||||
Events heißt das: Es kommt gar nichts an, solange der Puffer nicht voll
|
||||
ist — und der wird bei kurzen Ereignissen nie voll.
|
||||
"""
|
||||
|
||||
async def __call__(self, scope, receive, send):
|
||||
if scope["type"] == "http" and scope["path"].startswith("/api/events"):
|
||||
await self.app(scope, receive, send)
|
||||
return
|
||||
await super().__call__(scope, receive, send)
|
||||
|
||||
|
||||
# Der Katalog ist reines JSON und schrumpft dabei auf etwa ein Fünftel.
|
||||
# Der Live-Kanal bleibt außen vor: gepufferte Ereignisse kämen zu spät.
|
||||
app.add_middleware(GZipMiddleware, minimum_size=1024)
|
||||
app.add_middleware(GzipOhneLivekanal, minimum_size=1024)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
@@ -43,6 +57,7 @@ async def guarded_sync():
|
||||
async def startup():
|
||||
db.init()
|
||||
suggestions.init()
|
||||
watched.init()
|
||||
scheduler.add_job(guarded_sync, "cron", hour=4, minute=30, id="daily-sync")
|
||||
scheduler.start()
|
||||
if not db.get_meta("last_sync"):
|
||||
@@ -198,14 +213,7 @@ async def vorschlagen(
|
||||
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")
|
||||
|
||||
movie = await film_holen(movie_id)
|
||||
# Ein zweiter Vorschlag ersetzt den ersten, statt abgelehnt zu werden
|
||||
eintrag = suggestions.ersetzen(sid, movie, name)
|
||||
events.senden("suggestions")
|
||||
@@ -221,6 +229,90 @@ def vorschlag_loeschen(response: Response, filmabend_sid: str | None = Cookie(No
|
||||
return {"removed": entfernt}
|
||||
|
||||
|
||||
async def film_holen(movie_id: int):
|
||||
"""Erst der Katalog, dann TMDB — Suchtreffer stehen nicht in der DB."""
|
||||
movie = db.get_movie(movie_id)
|
||||
if movie:
|
||||
return movie
|
||||
try:
|
||||
return await search.detail(movie_id)
|
||||
except Exception:
|
||||
raise HTTPException(status_code=404, detail="Film nicht gefunden")
|
||||
|
||||
|
||||
@app.get("/api/watched")
|
||||
def liste_gesehen(response: Response, filmabend_sid: str | None = Cookie(None)):
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
return watched.alle(sid)
|
||||
|
||||
|
||||
@app.post("/api/watched")
|
||||
async def als_gesehen(movie_id: int = Body(..., embed=True)):
|
||||
"""Der Host trägt ein, was am Abend lief."""
|
||||
eintrag = watched.markieren(await film_holen(movie_id))
|
||||
events.senden("watched")
|
||||
return eintrag
|
||||
|
||||
|
||||
@app.delete("/api/watched/{watched_id}")
|
||||
def gesehen_entfernen(watched_id: int):
|
||||
entfernt = watched.entfernen(watched_id)
|
||||
if entfernt:
|
||||
events.senden("watched")
|
||||
return {"removed": entfernt}
|
||||
|
||||
|
||||
@app.patch("/api/watched/{watched_id}")
|
||||
def gesehen_aendern(
|
||||
watched_id: int,
|
||||
watched_at: str | None = Body(None, embed=True),
|
||||
hidden: bool | None = Body(None, embed=True),
|
||||
):
|
||||
"""Der Host rückt das Datum nach oder blendet einen Film aus."""
|
||||
geaendert = 0
|
||||
if watched_at:
|
||||
geaendert += watched.datum_setzen(watched_id, watched_at)
|
||||
if hidden is not None:
|
||||
geaendert += watched.sichtbarkeit(watched_id, hidden)
|
||||
if not geaendert:
|
||||
raise HTTPException(status_code=404, detail="Film steht nicht in der Liste")
|
||||
events.senden("watched")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.post("/api/watched/{watched_id}/rating")
|
||||
def bewerten(
|
||||
watched_id: int,
|
||||
response: Response,
|
||||
stars: int = Body(..., embed=True),
|
||||
name: str | None = Body(None, embed=True),
|
||||
filmabend_sid: str | None = Cookie(None),
|
||||
):
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
stars = max(1, min(watched.MAX_STERNE, stars))
|
||||
eintrag = watched.bewerten(watched_id, sid, stars, name)
|
||||
if not eintrag:
|
||||
raise HTTPException(status_code=404, detail="Film steht nicht in der Liste")
|
||||
events.senden("watched")
|
||||
return eintrag
|
||||
|
||||
|
||||
@app.delete("/api/suggestions/alle")
|
||||
def runde_zuruecksetzen():
|
||||
"""Host leert die Runde: Vorschläge weg, Rad wieder leer."""
|
||||
entfernt = suggestions.alles_loeschen()
|
||||
letzter_dreh["ergebnis"] = None
|
||||
letzter_dreh["start"] = 0.0
|
||||
events.senden("reset")
|
||||
return {"removed": entfernt}
|
||||
|
||||
|
||||
@app.get("/stream/{pfad:path}")
|
||||
async def stream(request: Request, pfad: str):
|
||||
"""HLS von MediaMTX unter eigenem Ursprung ausliefern."""
|
||||
return await streamproxy.weiterleiten(request, pfad)
|
||||
|
||||
|
||||
@app.post("/api/sync")
|
||||
async def trigger_sync():
|
||||
if sync_lock.locked():
|
||||
|
||||
Reference in New Issue
Block a user