Files
Vorrania/backend/app/routers/navigation.py
T
ScarriffleandClaude Opus 5 be267e70a5 Seitenleiste: Eintraege ausblendbar (Backend)
Zwei Ebenen in einer Tabelle, nach dem Vorbild von DashboardLayout:
user_id NULL ist die Vorgabe des Administrators, eine Zeile mit user_id
die zusaetzliche Auswahl eines Kontos. Beide Listen gelten zusammen - wer
etwas versteckt, versteckt es zusaetzlich; einblenden, was der
Administrator ausgeblendet hat, kann niemand. Das erspart einen dritten
Zustand je Eintrag.

Als Schluessel dient der Routenpfad; der ist ohnehin eindeutig, eine
zweite Benennung waere nur eine weitere Stelle zum Auseinanderlaufen.

Drei Routen: lesen (Vorgabe, eigene Auswahl und die fertige Vereinigung
in einem Zug), eigene Auswahl setzen, Vorgabe setzen (nur Admin).

Die Eindeutigkeit laeuft ueber einen Index auf coalesce(user_id, -1).
Ein schlichtes UNIQUE(user_id) haette zwei Vorgabezeilen zugelassen, weil
NULL in SQL nie gleich NULL ist - das war im Entwurf noch falsch.

Versteckt wird nur der Eintrag, nicht die Seite: Lesezeichen,
Dashboard-Karten und Lagerort-QR sollen weiter funktionieren.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-28 20:27:14 +01:00

106 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Welche Einträge der Seitenleiste jemand ausgeblendet hat.
Zwei Ebenen: die **Vorgabe des Administrators** gilt für alle (``user_id``
NULL), die **eigene Auswahl** eines Kontos kommt obendrauf. Beide Listen gelten
zusammen — wer etwas versteckt, versteckt es zusätzlich; einblenden, was der
Administrator ausgeblendet hat, kann niemand.
Die eigene Auswahl darf jeder für sich ändern (es betrifft nur seine Ansicht),
die Vorgabe nur ein Administrator.
Versteckt wird ausschliesslich der **Eintrag**, nicht die Seite: Lesezeichen,
Dashboard-Karten und Lagerort-QR verweisen weiter auf Routen, die hier
abgewählt sein können, und sollen weiter funktionieren.
"""
import json
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from ..database import get_db
from ..deps import get_current_user, require_admin
from ..models import NavigationSichtbarkeit, User
from ..schemas import NavigationOut, NavigationSetzen
router = APIRouter(prefix="/navigation", tags=["navigation"])
def _liste(zeile: NavigationSichtbarkeit | None) -> list[str]:
"""Die gespeicherte Liste – kaputter Inhalt gilt als leer.
Lieber eine vollständige Leiste als eine Fehlermeldung: Ein unlesbarer
Eintrag darf niemanden aussperren.
"""
if zeile is None:
return []
try:
werte = json.loads(zeile.versteckt)
except ValueError:
return []
return [w for w in werte if isinstance(w, str)] if isinstance(werte, list) else []
def _zeile(db: Session, user_id: int | None) -> NavigationSichtbarkeit | None:
return (
db.query(NavigationSichtbarkeit)
.filter(NavigationSichtbarkeit.user_id.is_(None) if user_id is None
else NavigationSichtbarkeit.user_id == user_id)
.first()
)
def _schreiben(db: Session, user_id: int | None, versteckt: list[str]) -> None:
"""Die Zeile setzen oder anlegen.
Doppelte und leere Einträge fallen weg; die Reihenfolge ist bedeutungslos
und wird sortiert, damit zwei gleiche Auswahlen auch gleich aussehen.
"""
sauber = sorted({w.strip() for w in versteckt if w and w.strip()})
zeile = _zeile(db, user_id)
if zeile is None:
zeile = NavigationSichtbarkeit(user_id=user_id, versteckt=json.dumps(sauber))
db.add(zeile)
else:
zeile.versteckt = json.dumps(sauber)
db.commit()
def _antwort(db: Session, user: User) -> NavigationOut:
vorgabe = _liste(_zeile(db, None))
eigen = _liste(_zeile(db, user.id))
return NavigationOut(
vorgabe=vorgabe, eigen=eigen, versteckt=sorted(set(vorgabe) | set(eigen))
)
@router.get("", response_model=NavigationOut)
def navigation_lesen(
db: Session = Depends(get_db),
user: User = Depends(get_current_user),
) -> NavigationOut:
"""Vorgabe, eigene Auswahl und die fertige Vereinigung."""
return _antwort(db, user)
@router.put("", response_model=NavigationOut)
def eigene_auswahl_setzen(
payload: NavigationSetzen,
db: Session = Depends(get_db),
user: User = Depends(get_current_user),
) -> NavigationOut:
"""Die eigene Auswahl – betrifft nur die eigene Ansicht."""
_schreiben(db, user.id, payload.versteckt)
return _antwort(db, user)
@router.put("/vorgabe", response_model=NavigationOut)
def vorgabe_setzen(
payload: NavigationSetzen,
db: Session = Depends(get_db),
user: User = Depends(require_admin),
) -> NavigationOut:
"""Die Vorgabe für alle."""
_schreiben(db, None, payload.versteckt)
return _antwort(db, user)