from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from ..config import get_settings from ..database import get_db from ..deps import get_current_user, require_admin from ..models import Setting, User from ..schemas import SettingOut from ..services.vererbung import globale_oeffnungsfrist router = APIRouter(prefix="/settings", tags=["settings"]) EXPIRY_WARNING_KEY = "expiry_warning_days" # Ab wie viel Prozent Übereinstimmung ein Artikel beim Kassenzettel-Scan als # Treffer vorgeschlagen wird. RECEIPT_THRESHOLD_KEY = "receipt_match_threshold" RECEIPT_THRESHOLD_DEFAULT = 45 # Wie lange eine Anmeldung gilt, in Tagen. 0 = unbegrenzt. SESSION_DAYS_KEY = "session_days" # Zehn Jahre. Wer laenger will, nimmt "unbegrenzt" – ein Token mit einem # Ablauf im naechsten Jahrhundert waere dasselbe, nur unehrlicher. SESSION_DAYS_MAX = 3650 # Wie lange eine angebrochene Packung gilt, wenn weder Artikel noch Kategorie # etwas sagen. Pflichtwert: Ohne ihn haette eine offene Packung kein # Verbrauchsdatum, und das Anbrechen waere sinnlos. OPEN_DAYS_KEY = "open_days" OPEN_DAYS_MAX = 3650 def get_expiry_warning_days(db: Session) -> int: row = db.get(Setting, EXPIRY_WARNING_KEY) if row is None: return get_settings().expiry_warning_days_default try: return int(row.value) except ValueError: return get_settings().expiry_warning_days_default def get_receipt_match_threshold(db: Session) -> int: """Schwellwert (0–100) für Kassenzettel-Treffer; auf sinnvollen Bereich geklemmt.""" row = db.get(Setting, RECEIPT_THRESHOLD_KEY) if row is None: return RECEIPT_THRESHOLD_DEFAULT try: return max(0, min(100, int(row.value))) except ValueError: return RECEIPT_THRESHOLD_DEFAULT def get_session_days(db: Session) -> int: """Gueltigkeitsdauer einer Anmeldung in Tagen; **0 heisst unbegrenzt**. Ohne Eintrag gilt der Rueckfallwert aus der Umgebung (``JWT_EXPIRE_MINUTES``), damit eine frische Installation nicht erst eingestellt werden muss. """ vorgabe = max(1, round(get_settings().jwt_expire_minutes / (60 * 24))) row = db.get(Setting, SESSION_DAYS_KEY) if row is None: return vorgabe try: return max(0, min(SESSION_DAYS_MAX, int(row.value))) except ValueError: return vorgabe def get_session_minutes(db: Session) -> int: """Dasselbe in Minuten, wie :func:`create_access_token` es erwartet. Die 0 fuer "unbegrenzt" ueberlebt die Umrechnung unveraendert. """ return get_session_days(db) * 24 * 60 @router.get("", response_model=list[SettingOut]) def list_settings( db: Session = Depends(get_db), _: User = Depends(get_current_user) ) -> list[SettingOut]: rows = db.query(Setting).all() known = {r.key: r.value for r in rows} known.setdefault(EXPIRY_WARNING_KEY, str(get_expiry_warning_days(db))) known.setdefault(RECEIPT_THRESHOLD_KEY, str(get_receipt_match_threshold(db))) known.setdefault(SESSION_DAYS_KEY, str(get_session_days(db))) known.setdefault(OPEN_DAYS_KEY, str(globale_oeffnungsfrist(db))) return [SettingOut(key=k, value=v) for k, v in known.items()] @router.put("/{key}", response_model=SettingOut) def set_setting( key: str, value: str, db: Session = Depends(get_db), _: User = Depends(require_admin), ) -> SettingOut: if key == OPEN_DAYS_KEY: # Gleiche Begruendung wie unten: ein Tippfehler darf nicht still auf die # Vorgabe zurueckfallen, waehrend der Admin glaubt, er haette etwas gesetzt. try: tage = int(value) except ValueError: raise HTTPException(400, "Haltbarkeit nach dem Anbrechen muss eine Zahl in Tagen sein") if not 1 <= tage <= OPEN_DAYS_MAX: raise HTTPException(400, f"Haltbarkeit nach dem Anbrechen: 1 bis {OPEN_DAYS_MAX} Tage") value = str(tage) if key == SESSION_DAYS_KEY: # Ohne diese Pruefung wuerde get_session_days einen Tippfehler still # auf die Vorgabe zurueckdrehen – und der Admin glaubte, es sei gesetzt. try: tage = int(value) except ValueError: raise HTTPException(400, "Sitzungsdauer muss eine Zahl in Tagen sein (0 = unbegrenzt)") if not 0 <= tage <= SESSION_DAYS_MAX: raise HTTPException(400, f"Sitzungsdauer: 0 (unbegrenzt) bis {SESSION_DAYS_MAX} Tage") value = str(tage) row = db.get(Setting, key) if row is None: row = Setting(key=key, value=value) db.add(row) else: row.value = value db.commit() return SettingOut(key=key, value=value)