184 lines
6.1 KiB
Python
184 lines
6.1 KiB
Python
"""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
|
|
]
|