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>
106 lines
3.5 KiB
Python
106 lines
3.5 KiB
Python
"""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)
|