Freunde, privates Teilen, Artikelrechte, Reiserouten

- Freunde (freunde.py, Tabelle friendships): suchen, anfragen, annehmen,
  ablehnen, entfernen; neue Seite "Freunde" im Menü
- Vorratslisten und Kanban-Projekte immer privat: sichtbar für Ersteller und
  Mitglieder; teilen nur mit Freunden (Admins: mit allen); Kanban-Mitglieder
  (Tabelle kanban_project_members), Bearbeiter nur aus dem Projekt,
  ausgetretene Mitglieder verlieren ihre Zuteilung
- Artikel: lesen alle; bearbeiten Autor, Mitarbeitende verknüpfter
  Kanban-Projekte und Admins; verknüpfen nur bearbeitbare Artikel
  (sonst ließen sich Rechte erschleichen); Board-Liste auf der Artikelseite
  zeigt nur sichtbare Boards
- Administratoren sehen und bearbeiten alles
- Reisen: Städte nur von Freunden (Admins: alle); "Reiserouten zeigen"
  zeichnet die Strecken der Kilometer-Rechnung als Bögen
- Migration Schritt 6: bisher gemeinsame Listen und alle Projekte bekommen
  die vorhandenen Konten als Mitglieder, ohne Ersteller → erster Admin;
  erste Liste einer neuen Installation gehört dem ersten Konto
- Tests: test_freunde.py, test_zugriff.py, bestehende auf Privat-Modell
  umgestellt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
guyg1474andClaude Opus 5.5 committed 2026-10-15 21:49:45 +02:00
1 parent 1d7497bead
commit 0fb3521c2f
34 files changed
+833 -245

No files matched your search

+25 -2
View File
@@ -22,8 +22,8 @@ log = logging.getLogger(__name__)
def start(conn) -> None:
"""Neue, leere Datenbank: erste Liste anlegen und gleich auf den neuesten Stand setzen."""
conn.execute("INSERT INTO shopping_lists (name, everyone, position, created_at) VALUES ('Zuhause', 1, 1, ?)", (now_iso(),))
"""Neue, leere Datenbank: erste Liste anlegen (bekommt das erste Konto) und gleich auf den neuesten Stand setzen."""
conn.execute("INSERT INTO shopping_lists (name, everyone, position, created_at) VALUES ('Zuhause', 0, 1, ?)", (now_iso(),))
conn.execute(f"PRAGMA user_version = {len(STEPS)}")
@@ -270,6 +270,28 @@ def _v5_wohnorte(conn) -> None:
_add_column(conn, "travel_visits", "continued", "INTEGER")
# ---------- Schritt 6: Listen und Kanban-Projekte privat ----------
def _v6_privat(conn) -> None:
"""Listen und Kanban-Projekte sind ab jetzt privat. Damit niemand den Zugriff verliert, bekommen bisher
gemeinsame Listen und alle Projekte die vorhandenen Konten als Mitglieder; ohne Ersteller gehören sie
dem ersten Administrator."""
owner = conn.execute("SELECT id FROM users ORDER BY is_admin DESC, id LIMIT 1").fetchone()
if owner:
conn.execute("UPDATE shopping_lists SET created_by = ? WHERE created_by IS NULL", (owner["id"],))
conn.execute("UPDATE kanban_projects SET created_by = ? WHERE created_by IS NULL", (owner["id"],))
conn.execute(
"""INSERT OR IGNORE INTO shopping_list_members (list_id, user_id)
SELECT l.id, u.id FROM shopping_lists l JOIN users u ON u.id != l.created_by WHERE l.everyone = 1"""
)
conn.execute("UPDATE shopping_lists SET everyone = 0")
conn.execute(
"""INSERT OR IGNORE INTO kanban_project_members (project_id, user_id)
SELECT k.id, u.id FROM kanban_projects k JOIN users u ON u.id != k.created_by"""
)
# Reihenfolge = Versionsnummer (Index + 1). Nur anhängen, nie umsortieren oder entfernen.
STEPS = [
_v1_altbestand,
@@ -277,4 +299,5 @@ STEPS = [
_v3_kategorien,
_v4_wunschliste,
_v5_wohnorte,
_v6_privat,
]