"""Die Runde: wer mitmacht, und wer gerade wer ist. Der Host pflegt die Namensliste. Alle anderen wählen sich einen davon aus. Kein Passwort — für acht Freunde, die sich ohnehin über Discord hören, wäre das nur eine Hürde ohne Nutzen. Die Zuordnung hängt an derselben Cookie-Sitzung wie Vorschläge und Wertungen. Damit steht der Name serverseitig fest und kommt nicht mehr aus einem Textfeld, in das jeder schreiben kann, was er will. """ import sqlite3 from datetime import datetime, timezone from .db import connect SCHEMA = """ CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS user_sessions ( session_id TEXT PRIMARY KEY, user_id INTEGER NOT NULL, seit TEXT NOT NULL ); """ def init(): with connect() as conn: conn.executescript(SCHEMA) def alle(): with connect() as conn: rows = conn.execute("SELECT * FROM users ORDER BY name COLLATE NOCASE").fetchall() return [dict(r) for r in rows] def anlegen(name): """Gibt None zurück, wenn der Name schon vergeben ist.""" name = (name or "").strip()[:40] if not name: return None try: with connect() as conn: conn.execute( "INSERT INTO users (name, created_at) VALUES (?, ?)", (name, datetime.now(timezone.utc).isoformat(timespec="seconds")), ) except sqlite3.IntegrityError: return None return von_name(name) def von_name(name): with connect() as conn: row = conn.execute("SELECT * FROM users WHERE name = ?", (name,)).fetchone() return dict(row) if row else None def entfernen(user_id): with connect() as conn: conn.execute("DELETE FROM user_sessions WHERE user_id = ?", (user_id,)) return conn.execute("DELETE FROM users WHERE id = ?", (user_id,)).rowcount def waehlen(session_id, user_id): """Gibt None zurück, wenn es den Namen nicht gibt.""" with connect() as conn: da = conn.execute("SELECT * FROM users WHERE id = ?", (user_id,)).fetchone() if not da: return None conn.execute( "INSERT INTO user_sessions (session_id, user_id, seit) VALUES (?, ?, ?) " "ON CONFLICT (session_id) DO UPDATE SET " "user_id = excluded.user_id, seit = excluded.seit", ( session_id, user_id, datetime.now(timezone.utc).isoformat(timespec="seconds"), ), ) return dict(da) def abwaehlen(session_id): with connect() as conn: return conn.execute( "DELETE FROM user_sessions WHERE session_id = ?", (session_id,) ).rowcount def aktuell(session_id): """Der Benutzer dieser Sitzung, oder None.""" with connect() as conn: row = conn.execute( "SELECT u.* FROM users u " "JOIN user_sessions s ON s.user_id = u.id " "WHERE s.session_id = ?", (session_id,), ).fetchone() return dict(row) if row else None def besitzer(session_id): """Die user_id dieser Sitzung, oder None ohne gewählten Namen. Sammlungen, Vorschläge und Wertungen werden damit beschriftet. Ein Namenswechsel im selben Browser lässt die alten Einträge dann in Ruhe — sie gehören weiter dem Namen, unter dem sie entstanden sind. """ with connect() as conn: row = conn.execute( "SELECT user_id FROM user_sessions WHERE session_id = ?", (session_id,), ).fetchone() return row["user_id"] if row else None def gehoert_mir(zeile, session_id, user_id): """Ein Eintrag gehört mir, wenn der Name passt — sonst die Sitzung.""" if zeile["user_id"] is not None or user_id is not None: return zeile["user_id"] == user_id return zeile["session_id"] == session_id # Dasselbe als WHERE-Klausel. Parameter: (user_id, user_id, session_id) WEM = "(user_id = ? OR (? IS NULL AND user_id IS NULL AND session_id = ?))" def user_id_nachruesten(conn, tabelle): """Spalte ergänzen 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 je_person(conn, tabelle, schema, *schluessel): """Eindeutig je Person statt je Sitzung. Früher stand das UNIQUE auf der Sitzung — dann blockierte der Eintrag des einen Namens den des nächsten, sobald beide denselben Browser benutzten. Mit Namen zählt jetzt der Name, ohne Namen die Sitzung. Das alte UNIQUE steckt in der Spaltendefinition und lässt sich nicht ändern — die Tabelle wird dafür nach `schema` neu gebaut. Hat ein Name aus mehreren Browsern doppelt eingetragen, bleibt der jüngste Eintrag. """ user_id_nachruesten(conn, tabelle) alt = conn.execute( "SELECT sql FROM sqlite_master WHERE type='table' AND name=?", (tabelle,) ).fetchone() if alt and "UNIQUE" in alt["sql"]: spalten = ", ".join( r[1] for r in conn.execute(f"PRAGMA table_info({tabelle})") ) conn.executescript( f"ALTER TABLE {tabelle} RENAME TO {tabelle}_alt;" f"{schema}" f"INSERT INTO {tabelle} ({spalten}) " f"SELECT {spalten} FROM {tabelle}_alt;" f"DROP TABLE {tabelle}_alt;" ) mit_name = ", ".join((*schluessel, "user_id")) ohne_name = ", ".join((*schluessel, "session_id")) conn.execute( f"DELETE FROM {tabelle} WHERE user_id IS NOT NULL AND id NOT IN " f"(SELECT MAX(id) FROM {tabelle} GROUP BY {mit_name})" ) conn.executescript( f"CREATE UNIQUE INDEX IF NOT EXISTS {tabelle}_je_benutzer " f"ON {tabelle} ({mit_name}) WHERE user_id IS NOT NULL;" f"CREATE UNIQUE INDEX IF NOT EXISTS {tabelle}_je_sitzung " f"ON {tabelle} ({ohne_name}) WHERE user_id IS NULL;" ) def name_von(session_id): """Nur der Name, für Vorschläge und Wertungen.""" benutzer = aktuell(session_id) return benutzer["name"] if benutzer else None