Ergänzung Datenschutzerklärung und Impressum, Fristenformular in Einstellungen
This commit is contained in:
183
backend/app/runtime_settings.py
Normal file
183
backend/app/runtime_settings.py
Normal file
@@ -0,0 +1,183 @@
|
||||
"""Fristen, die sich zur Laufzeit einstellen lassen.
|
||||
|
||||
Bis hierher standen sie verstreut: teils als Konstante im Quelltext
|
||||
(`INVITE_DAYS = 14`), teils in der Umgebung (`CLEANUP_DELETED_DAYS`),
|
||||
teils in der Einstellungstabelle. Wer nachsehen wollte, was tatsächlich
|
||||
gilt, musste an drei Stellen suchen - und die Datenschutzerklärung
|
||||
behauptete Werte, die niemand überprüfen konnte.
|
||||
|
||||
Jetzt an einer Stelle, mit klarer Rangfolge:
|
||||
|
||||
Einstellungstabelle → Umgebungsvariable → Vorgabewert hier
|
||||
|
||||
Die Umgebungsvariable wirkt also als Startwert; sobald jemand in der
|
||||
Verwaltung etwas ändert, gilt der gespeicherte Wert. Das entspricht dem
|
||||
Verhalten der Selbstregistrierung.
|
||||
|
||||
Fristen, die NICHT hierhergehören, weil sie außerhalb der Anwendung
|
||||
liegen: die Aufbewahrung der Webserver-Protokolle (Sache der
|
||||
Protokollrotation auf dem Server) und alles, was fremde Dienste mit
|
||||
ihren eigenen Daten tun.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.config import settings
|
||||
from app.deps import get_setting, set_setting
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Duration:
|
||||
key: str
|
||||
label: str
|
||||
unit: str # "Tage" | "Monate" | "Stunden"
|
||||
default: int
|
||||
minimum: int
|
||||
maximum: int
|
||||
# Was passiert, wenn 0 eingetragen wird? None = nicht erlaubt.
|
||||
zero_means: str | None = None
|
||||
note: str = ""
|
||||
|
||||
|
||||
# Reihenfolge bestimmt die Darstellung in der Verwaltung.
|
||||
DURATIONS: tuple[Duration, ...] = (
|
||||
# ---- Konten ----
|
||||
Duration(
|
||||
"auto_deactivate_months", "Konto deaktivieren nach", "Monaten",
|
||||
default=12, minimum=0, maximum=600, zero_means="abgeschaltet",
|
||||
note="ohne Anmeldung; Administratorkonten sind ausgenommen",
|
||||
),
|
||||
Duration(
|
||||
"auto_delete_months", "Konto löschen nach", "Monaten",
|
||||
default=12, minimum=0, maximum=600, zero_means="abgeschaltet",
|
||||
note="Deaktivierung",
|
||||
),
|
||||
# ---- Listen ----
|
||||
Duration(
|
||||
"auto_archive_months", "Liste archivieren nach", "Monaten",
|
||||
default=12, minimum=0, maximum=600, zero_means="abgeschaltet",
|
||||
note="ohne Öffnen; gilt je Person getrennt",
|
||||
),
|
||||
Duration(
|
||||
"archive_delete_months", "Liste löschen nach", "Monaten",
|
||||
default=12, minimum=0, maximum=600, zero_means="abgeschaltet",
|
||||
note="im Archiv des Eigentümers; Vorwarnung 7 Tage und 1 Tag vorher",
|
||||
),
|
||||
# ---- Aufbewahrung ----
|
||||
Duration(
|
||||
"cleanup_deleted_days", "Gelöschtes endgültig entfernen nach", "Tagen",
|
||||
default=30, minimum=1, maximum=365,
|
||||
note="so lange kann ein Gerät offline bleiben und vom Löschen "
|
||||
"noch erfahren",
|
||||
),
|
||||
Duration(
|
||||
"cleanup_ops_days", "Quittungen der Warteschlange aufbewahren", "Tage",
|
||||
default=7, minimum=1, maximum=90,
|
||||
note="verhindern, dass eine wiederholte Änderung doppelt ausgeführt wird",
|
||||
),
|
||||
Duration(
|
||||
"product_cache_days", "Produktabfragen gültig", "Tage",
|
||||
default=180, minimum=1, maximum=3650,
|
||||
note="danach wird derselbe Strichcode erneut abgefragt",
|
||||
),
|
||||
# ---- Sitzungen und Links ----
|
||||
Duration(
|
||||
"session_days", "Angemeldet bleiben", "Tage",
|
||||
default=30, minimum=1, maximum=365,
|
||||
note="danach ist eine erneute Anmeldung nötig",
|
||||
),
|
||||
Duration(
|
||||
"invite_days", "Einladung gültig", "Tage",
|
||||
default=14, minimum=1, maximum=90,
|
||||
note="danach muss sie erneut versendet werden",
|
||||
),
|
||||
Duration(
|
||||
"welcome_days", "Willkommenslink gültig", "Tage",
|
||||
default=14, minimum=1, maximum=90,
|
||||
note="Link zum Festlegen des ersten Passworts",
|
||||
),
|
||||
Duration(
|
||||
"verify_hours", "Bestätigungslink gültig", "Stunden",
|
||||
default=24, minimum=1, maximum=720,
|
||||
note="Bestätigung der E-Mail-Adresse nach der Registrierung",
|
||||
),
|
||||
Duration(
|
||||
"reset_hours", "Passwortlink gültig", "Stunden",
|
||||
default=1, minimum=1, maximum=72,
|
||||
note="kurz gehalten: Der Link erlaubt die Übernahme des Kontos",
|
||||
),
|
||||
Duration(
|
||||
"email_change_hours", "Adresswechsel bestätigen binnen", "Stunden",
|
||||
default=48, minimum=1, maximum=336,
|
||||
note="bei Administratorkonten müssen beide Adressen zustimmen",
|
||||
),
|
||||
)
|
||||
|
||||
BY_KEY = {d.key: d for d in DURATIONS}
|
||||
|
||||
|
||||
def _env_default(entry: Duration) -> int:
|
||||
"""Startwert aus der Umgebung, falls es dort ein passendes Feld gibt."""
|
||||
value = getattr(settings, entry.key, None)
|
||||
if isinstance(value, int) and not isinstance(value, bool):
|
||||
return value
|
||||
return entry.default
|
||||
|
||||
|
||||
def get_duration(db: Session, key: str) -> int:
|
||||
"""Aktuell geltender Wert."""
|
||||
entry = BY_KEY[key]
|
||||
raw = get_setting(db, key, "")
|
||||
if not raw:
|
||||
return _env_default(entry)
|
||||
try:
|
||||
value = int(raw)
|
||||
except ValueError:
|
||||
log.warning(
|
||||
"Einstellung %s ist keine Zahl (%r) - verwende %d",
|
||||
key, raw, _env_default(entry),
|
||||
)
|
||||
return _env_default(entry)
|
||||
|
||||
if entry.zero_means and value == 0:
|
||||
return 0
|
||||
return max(entry.minimum, min(value, entry.maximum))
|
||||
|
||||
|
||||
def set_duration(db: Session, key: str, value: int) -> int:
|
||||
entry = BY_KEY[key]
|
||||
if entry.zero_means and value == 0:
|
||||
clamped = 0
|
||||
else:
|
||||
clamped = max(entry.minimum, min(int(value), entry.maximum))
|
||||
set_setting(db, key, str(clamped))
|
||||
return clamped
|
||||
|
||||
|
||||
def all_durations(db: Session) -> dict[str, int]:
|
||||
return {entry.key: get_duration(db, entry.key) for entry in DURATIONS}
|
||||
|
||||
|
||||
def describe_durations(db: Session) -> list[dict]:
|
||||
"""Beschreibung für die Verwaltungsoberfläche - Beschriftung,
|
||||
Einheit, Grenzen und Bedeutung der Null kommen aus dieser Datei,
|
||||
damit die Oberfläche sie nicht doppelt vorhalten muss."""
|
||||
return [
|
||||
{
|
||||
"key": entry.key,
|
||||
"label": entry.label,
|
||||
"unit": entry.unit,
|
||||
"value": get_duration(db, entry.key),
|
||||
"default": _env_default(entry),
|
||||
"minimum": entry.minimum,
|
||||
"maximum": entry.maximum,
|
||||
"zero_means": entry.zero_means,
|
||||
"note": entry.note,
|
||||
}
|
||||
for entry in DURATIONS
|
||||
]
|
||||
Reference in New Issue
Block a user