Files
horror/backend/app/features.py
2026-09-16 12:09:38 +02:00

221 lines
7.0 KiB
Python

"""Was der Seite noch fehlt.
Jeder darf aufschreiben, was er sich wünscht, und für Wünsche anderer
stimmen — eine Stimme je Person. Ergänzungen hängen als Anmerkungen unter
dem Wunsch, damit der ursprüngliche Text stehen bleibt und die Diskussion
nachvollziehbar ist. Der Host hakt ab, was umgesetzt ist: der Wunsch bleibt
sichtbar, rutscht aber ans Ende.
Ändern darf jeder nur die eigenen, wie bei den Filmsammlungen.
"""
from datetime import datetime, timezone
from . import users
from .db import connect
from .users import WEM, besitzer, gehoert_mir
SCHEMA = """
CREATE TABLE IF NOT EXISTS features (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
user_id INTEGER,
text TEXT NOT NULL,
created_at TEXT NOT NULL,
done_at TEXT
);
CREATE TABLE IF NOT EXISTS feature_notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
feature_id INTEGER NOT NULL,
session_id TEXT NOT NULL,
user_id INTEGER,
text TEXT NOT NULL,
created_at TEXT NOT NULL
);
"""
# Eigenes Statement, weil users.je_person() die Tabelle daraus neu bauen
# könnte — hier ohne UNIQUE in der Spaltendefinition, die Eindeutigkeit
# kommt aus den Indizes je Person.
VOTES_SCHEMA = """
CREATE TABLE IF NOT EXISTS feature_votes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
feature_id INTEGER NOT NULL,
session_id TEXT NOT NULL,
user_id INTEGER,
created_at TEXT NOT NULL
);
"""
MAX_LAENGE = 500
def _jetzt():
return datetime.now(timezone.utc).isoformat(timespec="seconds")
def init():
with connect() as conn:
conn.executescript(SCHEMA)
conn.executescript(VOTES_SCHEMA)
users.user_id_nachruesten(conn, "features")
# Die Tabelle stand vor der Spalte schon in bestehenden Datenbanken
spalten = {r[1] for r in conn.execute("PRAGMA table_info(features)")}
if "done_at" not in spalten:
conn.execute("ALTER TABLE features ADD COLUMN done_at TEXT")
# Eine Stimme je Person und Wunsch
users.je_person(conn, "feature_votes", VOTES_SCHEMA, "feature_id")
def alle(session_id):
"""Alle Wünsche: offene nach Stimmen, erledigte dahinter.
Jeder Wunsch bringt den Namen des Verfassers, die Stimmenzahl, die
eigene Stimme und seine Anmerkungen mit.
"""
ich = besitzer(session_id)
with connect() as conn:
rows = conn.execute(
"SELECT f.*, u.name, "
" (SELECT COUNT(*) FROM feature_votes v WHERE v.feature_id = f.id) "
" AS votes "
"FROM features f "
"LEFT JOIN users u ON u.id = f.user_id "
"ORDER BY f.done_at IS NOT NULL, votes DESC, f.created_at DESC"
).fetchall()
meine_stimmen = {
r["feature_id"]
for r in conn.execute(
f"SELECT feature_id FROM feature_votes WHERE {WEM}",
(ich, ich, session_id),
)
}
notizen = conn.execute(
"SELECT n.*, u.name FROM feature_notes n "
"LEFT JOIN users u ON u.id = n.user_id "
"ORDER BY n.created_at ASC"
).fetchall()
je_wunsch = {}
for n in notizen:
je_wunsch.setdefault(n["feature_id"], []).append(
{
"id": n["id"],
"text": n["text"],
"name": n["name"],
"created_at": n["created_at"],
"mine": gehoert_mir(n, session_id, ich),
}
)
return [
{
**{
k: v
for k, v in dict(r).items()
if k not in ("session_id", "user_id")
},
"mine": gehoert_mir(r, session_id, ich),
"voted": r["id"] in meine_stimmen,
"notes": je_wunsch.get(r["id"], []),
}
for r in rows
]
def anlegen(session_id, text):
"""Gibt None zurück, wenn nichts dasteht."""
text = (text or "").strip()[:MAX_LAENGE]
if not text:
return None
with connect() as conn:
conn.execute(
"INSERT INTO features (session_id, user_id, text, created_at) "
"VALUES (?, ?, ?, ?)",
(session_id, besitzer(session_id), text, _jetzt()),
)
return alle(session_id)
def aendern(feature_id, text):
"""Der Host räumt auf: Tippfehler, Dopplungen, unklare Formulierungen."""
text = (text or "").strip()[:MAX_LAENGE]
if not text:
return 0
with connect() as conn:
return conn.execute(
"UPDATE features SET text = ? WHERE id = ?", (text, feature_id)
).rowcount
def erledigen(feature_id, erledigt):
"""Der Host hakt ab — oder holt einen Wunsch wieder hervor."""
with connect() as conn:
return conn.execute(
"UPDATE features SET done_at = ? WHERE id = ?",
(_jetzt() if erledigt else None, feature_id),
).rowcount
def abstimmen(feature_id, session_id):
"""Stimme setzen oder zurücknehmen. Gibt None zurück, wenn es den
Wunsch nicht gibt, sonst ob die Stimme jetzt steht."""
ich = besitzer(session_id)
with connect() as conn:
weg = conn.execute(
f"DELETE FROM feature_votes WHERE feature_id = ? AND {WEM}",
(feature_id, ich, ich, session_id),
).rowcount
if weg:
return False
da = conn.execute(
"SELECT 1 FROM features WHERE id = ?", (feature_id,)
).fetchone()
if not da:
return None
conn.execute(
"INSERT INTO feature_votes (feature_id, session_id, user_id, created_at) "
"VALUES (?, ?, ?, ?)",
(feature_id, session_id, ich, _jetzt()),
)
return True
def anmerken(feature_id, session_id, text):
"""Eine Ergänzung unter den Wunsch hängen."""
text = (text or "").strip()[:MAX_LAENGE]
if not text:
return False
with connect() as conn:
da = conn.execute(
"SELECT 1 FROM features WHERE id = ?", (feature_id,)
).fetchone()
if not da:
return False
conn.execute(
"INSERT INTO feature_notes "
"(feature_id, session_id, user_id, text, created_at) "
"VALUES (?, ?, ?, ?, ?)",
(feature_id, session_id, besitzer(session_id), text, _jetzt()),
)
return True
def anmerkung_loeschen(note_id):
"""Ohne Sitzungsbindung — den Knopf zeigt die Oberfläche nur dem Host."""
with connect() as conn:
return conn.execute(
"DELETE FROM feature_notes WHERE id = ?", (note_id,)
).rowcount
def loeschen(feature_id):
"""Ohne Sitzungsbindung — den Knopf zeigt die Oberfläche nur dem
Verfasser und dem Host. Stimmen und Anmerkungen gehen mit."""
with connect() as conn:
conn.execute("DELETE FROM feature_votes WHERE feature_id = ?", (feature_id,))
conn.execute("DELETE FROM feature_notes WHERE feature_id = ?", (feature_id,))
return conn.execute(
"DELETE FROM features WHERE id = ?", (feature_id,)
).rowcount