"""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