Files
horror/backend/app/watched.py
2026-09-09 17:58:52 +02:00

207 lines
6.5 KiB
Python

"""Bereits geschaute Filme und ihre Bewertungen.
Der Host trägt ein, was am Abend lief. Bewerten darf jeder, eine Stimme
je Sitzung — dieselbe Cookie-Sitzung wie bei den Vorschlägen. Der Name ist
freiwillig, ohne ihn zählt die Stimme trotzdem.
Titel und Poster werden mitgespeichert, damit ein Eintrag lesbar bleibt,
auch wenn der Film später aus dem Katalog fällt.
"""
from datetime import datetime, timezone
from .db import connect
from .users import besitzer, gehoert_mir
SCHEMA = """
CREATE TABLE IF NOT EXISTS watched (
id INTEGER PRIMARY KEY AUTOINCREMENT,
movie_id INTEGER NOT NULL UNIQUE,
title TEXT NOT NULL,
year INTEGER,
poster_path TEXT,
watched_at TEXT NOT NULL,
hidden INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS ratings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
watched_id INTEGER NOT NULL,
session_id TEXT NOT NULL,
stars INTEGER NOT NULL,
name TEXT,
created_at TEXT NOT NULL,
UNIQUE (watched_id, session_id)
);
"""
MAX_STERNE = 5
def _spalte_user_id(conn, tabelle):
"""Nachrüsten und aus der Sitzung füllen — einmalig je Datenbank."""
spalten = {r[1] for r in conn.execute(f"PRAGMA table_info({tabelle})")}
if "user_id" in spalten:
return
conn.execute(f"ALTER TABLE {tabelle} ADD COLUMN user_id INTEGER")
conn.execute(
f"UPDATE {tabelle} SET user_id = ("
" SELECT user_id FROM user_sessions s WHERE s.session_id = "
f" {tabelle}.session_id)"
)
def init():
with connect() as conn:
conn.executescript(SCHEMA)
_spalte_user_id(conn, "ratings")
# Die Tabelle stand vor der Spalte schon in bestehenden Datenbanken
spalten = {r["name"] for r in conn.execute("PRAGMA table_info(watched)")}
if "hidden" not in spalten:
conn.execute(
"ALTER TABLE watched ADD COLUMN hidden INTEGER NOT NULL DEFAULT 0"
)
def _jetzt():
return datetime.now(timezone.utc).isoformat(timespec="seconds")
def alle(session_id):
"""Zuletzt geschaut zuerst, mit Schnitt und der eigenen Stimme."""
with connect() as conn:
filme = conn.execute(
"SELECT * FROM watched ORDER BY watched_at DESC, id DESC"
).fetchall()
stimmen = conn.execute(
"SELECT id, watched_id, session_id, user_id, stars, name FROM ratings "
"ORDER BY created_at ASC"
).fetchall()
ich = besitzer(session_id)
je_film = {}
for s in stimmen:
je_film.setdefault(s["watched_id"], []).append(s)
out = []
for film in filme:
eigene = je_film.get(film["id"], [])
werte = [s["stars"] for s in eigene]
out.append(
{
**dict(film),
"ratings": [
{"id": s["id"], "stars": s["stars"], "name": s["name"]}
for s in eigene
],
"average": round(sum(werte) / len(werte), 1) if werte else None,
"count": len(werte),
"mine": next(
(
s["stars"]
for s in eigene
if gehoert_mir(s, session_id, ich)
),
None,
),
}
)
return out
def markieren(movie):
"""Film als geschaut eintragen. Ein zweiter Klick ändert nichts."""
with connect() as conn:
conn.execute(
"INSERT OR IGNORE INTO watched "
"(movie_id, title, year, poster_path, watched_at) "
"VALUES (?, ?, ?, ?, ?)",
(
movie["id"],
movie["title"],
movie.get("year"),
movie.get("poster_path"),
_jetzt(),
),
)
row = conn.execute(
"SELECT * FROM watched WHERE movie_id = ?", (movie["id"],)
).fetchone()
return dict(row)
def entfernen(watched_id):
with connect() as conn:
conn.execute("DELETE FROM ratings WHERE watched_id = ?", (watched_id,))
return conn.execute(
"DELETE FROM watched WHERE id = ?", (watched_id,)
).rowcount
def datum_setzen(watched_id, tag):
"""tag im Format JJJJ-MM-TT. Mittags in UTC, damit die Anzeige in
keiner Zeitzone auf den Nachbartag rutscht."""
with connect() as conn:
return conn.execute(
"UPDATE watched SET watched_at = ? WHERE id = ?",
(f"{tag}T12:00:00+00:00", watched_id),
).rowcount
def sichtbarkeit(watched_id, versteckt):
"""Versteckte Filme sieht nur der Host."""
with connect() as conn:
return conn.execute(
"UPDATE watched SET hidden = ? WHERE id = ?",
(1 if versteckt else 0, watched_id),
).rowcount
def bewerten(watched_id, session_id, stars, name=None):
"""Gibt None zurück, wenn der Film gar nicht in der Liste steht."""
with connect() as conn:
da = conn.execute(
"SELECT 1 FROM watched WHERE id = ?", (watched_id,)
).fetchone()
if not da:
return None
# Eine Stimme je Person — die eigene alte fliegt vorher raus.
# Mit Namen zählt der Name, ohne Namen die Sitzung.
ich = besitzer(session_id)
conn.execute(
"DELETE FROM ratings WHERE watched_id = ? AND "
"(user_id = ? OR (? IS NULL AND user_id IS NULL AND session_id = ?))",
(watched_id, ich, ich, session_id),
)
conn.execute(
"INSERT INTO ratings "
"(watched_id, session_id, user_id, stars, name, created_at) "
"VALUES (?, ?, ?, ?, ?, ?) "
"ON CONFLICT (watched_id, session_id) DO UPDATE SET "
"user_id = excluded.user_id, stars = excluded.stars, "
"name = excluded.name, created_at = excluded.created_at",
(
watched_id,
session_id,
ich,
stars,
(name or "").strip()[:40] or None,
_jetzt(),
),
)
return {"watched_id": watched_id, "stars": stars}
def stimme_loeschen(rating_id):
"""Der Host räumt eine einzelne Wertung weg."""
with connect() as conn:
return conn.execute(
"DELETE FROM ratings WHERE id = ?", (rating_id,)
).rowcount
def ids():
"""movie_ids der geschauten Filme, für die Detailansicht."""
with connect() as conn:
return [r["movie_id"] for r in conn.execute("SELECT movie_id FROM watched")]