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:
1 parent
1d7497bead
commit
0fb3521c2f
34 files changed
+833
-245
No files matched your search
+22
-17
@@ -22,6 +22,7 @@ from .auth import current_user
|
||||
from .config import WIKIPEDIA_CONTACT
|
||||
from .common import ValidationError, clean_text, now_iso
|
||||
from .db import transaction
|
||||
from .freunde import friend_ids
|
||||
from .projects import UPLOAD_DIR, UPLOAD_NAME, remove_unused
|
||||
|
||||
router = APIRouter()
|
||||
@@ -51,15 +52,18 @@ def _city(row) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _others(conn, user_id: int) -> list:
|
||||
"""Städte der anderen Konten (nur zum Anzeigen auf der Karte), gruppiert nach Konto."""
|
||||
def _others(conn, user: dict) -> list:
|
||||
"""Städte von Freunden (nur zum Anzeigen auf der Karte), gruppiert nach Konto; Administratoren sehen alle."""
|
||||
rows = conn.execute(
|
||||
"""SELECT c.*, u.display_name FROM travel_cities c JOIN users u ON u.id = c.user_id
|
||||
WHERE c.user_id != ? AND c.wish = 0 ORDER BY u.display_name COLLATE NOCASE, u.id, c.name""",
|
||||
(user_id,),
|
||||
(user["id"],),
|
||||
).fetchall()
|
||||
allowed = None if user["isAdmin"] else friend_ids(conn, user["id"])
|
||||
people = {}
|
||||
for r in rows:
|
||||
if allowed is not None and r["user_id"] not in allowed:
|
||||
continue
|
||||
person = people.setdefault(r["user_id"], {"id": r["user_id"], "name": r["display_name"], "cities": []})
|
||||
person["cities"].append(_city(r))
|
||||
return list(people.values())
|
||||
@@ -88,7 +92,8 @@ def _visits(conn, user_id: int) -> list:
|
||||
]
|
||||
|
||||
|
||||
def _state(conn, user_id: int) -> dict:
|
||||
def _state(conn, user: dict) -> dict:
|
||||
user_id = user["id"]
|
||||
cities = conn.execute("SELECT * FROM travel_cities WHERE user_id = ? ORDER BY name", (user_id,)).fetchall()
|
||||
countries = conn.execute("SELECT code FROM travel_countries WHERE user_id = ? ORDER BY code", (user_id,)).fetchall()
|
||||
homes = conn.execute(
|
||||
@@ -103,7 +108,7 @@ def _state(conn, user_id: int) -> dict:
|
||||
for h in homes
|
||||
],
|
||||
"visits": _visits(conn, user_id),
|
||||
"others": _others(conn, user_id),
|
||||
"others": _others(conn, user),
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +118,7 @@ def _state(conn, user_id: int) -> dict:
|
||||
@router.get("/api/reisen")
|
||||
def get_travel(user: dict = Depends(current_user)):
|
||||
with transaction() as conn:
|
||||
return _state(conn, user["id"])
|
||||
return _state(conn, user)
|
||||
|
||||
|
||||
def _number(value, field: str, low: float, high: float) -> float:
|
||||
@@ -156,7 +161,7 @@ def add_city(data: dict = Body(...), user: dict = Depends(current_user)):
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (user_id, geoname_id) DO NOTHING""",
|
||||
values,
|
||||
)
|
||||
return _state(conn, user["id"])
|
||||
return _state(conn, user)
|
||||
|
||||
|
||||
@router.delete("/api/reisen/staedte/{city_id}")
|
||||
@@ -171,7 +176,7 @@ def delete_city(city_id: int, user: dict = Depends(current_user)):
|
||||
if not cur.rowcount:
|
||||
raise HTTPException(404, "Stadt nicht gefunden.")
|
||||
remove_unused(conn, photos)
|
||||
return _state(conn, user["id"])
|
||||
return _state(conn, user)
|
||||
|
||||
|
||||
@router.patch("/api/reisen/staedte/{city_id}")
|
||||
@@ -184,7 +189,7 @@ def update_city(city_id: int, data: dict = Body(...), user: dict = Depends(curre
|
||||
if data["wish"] and conn.execute("SELECT 1 FROM travel_visits WHERE city_id = ?", (city_id,)).fetchone():
|
||||
raise ValidationError("Für diese Stadt sind schon Besuche eingetragen.")
|
||||
conn.execute("UPDATE travel_cities SET wish = ? WHERE id = ?", (int(data["wish"]), city_id))
|
||||
return _state(conn, user["id"])
|
||||
return _state(conn, user)
|
||||
|
||||
|
||||
# ---------- Wohnorte (mit "seit"-Datum) ----------
|
||||
@@ -228,7 +233,7 @@ def add_home(data: dict = Body(...), user: dict = Depends(current_user)):
|
||||
since = _date(data.get("since"), "Seit", False)
|
||||
with transaction() as conn:
|
||||
_add_home(conn, user["id"], city, since)
|
||||
return _state(conn, user["id"])
|
||||
return _state(conn, user)
|
||||
|
||||
|
||||
@router.delete("/api/reisen/wohnorte/{home_id}")
|
||||
@@ -237,7 +242,7 @@ def delete_home(home_id: int, user: dict = Depends(current_user)):
|
||||
cur = conn.execute("DELETE FROM travel_homes WHERE id = ? AND user_id = ?", (home_id, user["id"]))
|
||||
if not cur.rowcount:
|
||||
raise HTTPException(404, "Wohnort nicht gefunden.")
|
||||
return _state(conn, user["id"])
|
||||
return _state(conn, user)
|
||||
|
||||
|
||||
@router.post("/api/reisen/besuche/{visit_id}/gewohnt")
|
||||
@@ -258,7 +263,7 @@ def lived_there(visit_id: int, user: dict = Depends(current_user)):
|
||||
_add_home(conn, user["id"], dict(city), visit["date_from"])
|
||||
if before and not later and before["geoname_id"] != city["geoname_id"]:
|
||||
_add_home(conn, user["id"], dict(before), after)
|
||||
return _state(conn, user["id"])
|
||||
return _state(conn, user)
|
||||
|
||||
|
||||
# ---------- Besuche: Zeitraum, Begleitung, Notizen, Fotos ----------
|
||||
@@ -345,7 +350,7 @@ def add_visit(city_id: int, data: dict = Body(...), user: dict = Depends(current
|
||||
).lastrowid
|
||||
_save_photos(conn, visit_id, photos)
|
||||
conn.execute("UPDATE travel_cities SET wish = 0 WHERE id = ?", (city_id,))
|
||||
return _state(conn, user["id"])
|
||||
return _state(conn, user)
|
||||
|
||||
|
||||
@router.put("/api/reisen/besuche/{visit_id}")
|
||||
@@ -359,7 +364,7 @@ def update_visit(visit_id: int, data: dict = Body(...), user: dict = Depends(cur
|
||||
(date_from, date_to, companions, notes, None if continued is None else int(continued), visit_id),
|
||||
)
|
||||
remove_unused(conn, _save_photos(conn, visit_id, photos))
|
||||
return _state(conn, user["id"])
|
||||
return _state(conn, user)
|
||||
|
||||
|
||||
@router.delete("/api/reisen/besuche/{visit_id}")
|
||||
@@ -369,7 +374,7 @@ def delete_visit(visit_id: int, user: dict = Depends(current_user)):
|
||||
photos = [r["file"] for r in conn.execute("SELECT file FROM travel_visit_photos WHERE visit_id = ?", (visit_id,))]
|
||||
conn.execute("DELETE FROM travel_visits WHERE id = ?", (visit_id,))
|
||||
remove_unused(conn, photos)
|
||||
return _state(conn, user["id"])
|
||||
return _state(conn, user)
|
||||
|
||||
|
||||
def _country_code(code: str) -> str:
|
||||
@@ -384,14 +389,14 @@ def mark_country(code: str, user: dict = Depends(current_user)):
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO travel_countries (user_id, code) VALUES (?, ?)", (user["id"], _country_code(code))
|
||||
)
|
||||
return _state(conn, user["id"])
|
||||
return _state(conn, user)
|
||||
|
||||
|
||||
@router.delete("/api/reisen/laender/{code}")
|
||||
def unmark_country(code: str, user: dict = Depends(current_user)):
|
||||
with transaction() as conn:
|
||||
conn.execute("DELETE FROM travel_countries WHERE user_id = ? AND code = ?", (user["id"], _country_code(code)))
|
||||
return _state(conn, user["id"])
|
||||
return _state(conn, user)
|
||||
|
||||
|
||||
# ---------- Städtesuche ----------
|
||||
|
||||
Reference in new issue
Block a user