"""Gemeinsame Helfer fürs Backend: Eingabefehler, Texte prüfen, Zeitstempel, Kontenliste.""" import re from datetime import datetime, timedelta, timezone class ValidationError(ValueError): """Ungültige Eingabe; main.py macht daraus HTTP 400 mit {"error": Meldung}.""" def clean_text(value, field: str, max_len: int, *, required: bool = True, collapse: bool = True) -> str: """Text säubern und prüfen. collapse: Leerraum (auch Zeilenumbrüche) zu einem Leerzeichen zusammenfassen.""" if not isinstance(value, str): value = "" v = re.sub(r"\s+", " ", value).strip() if collapse else value.strip() if required and not v: raise ValidationError(f"{field} fehlt.") if len(v) > max_len: raise ValidationError(f"{field} ist zu lang (max. {max_len} Zeichen).") return v # ---------- Zeitstempel: ISO 8601 in UTC mit Millisekunden, z. B. 2026-10-15T08:57:47.533Z ---------- # Einheitliches Format, damit Vergleiche als Text in SQLite stimmen (done_at <= ? usw.). def iso(moment: datetime) -> str: return moment.astimezone(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z") def now_iso() -> str: return iso(datetime.now(timezone.utc)) def iso_ago(delta: timedelta) -> str: return iso(datetime.now(timezone.utc) - delta) def iso_after(value: str, delta: timedelta) -> str: return iso(datetime.fromisoformat(value.replace("Z", "+00:00")) + delta) # ---------- Konten ---------- def user_list(conn) -> list: """Alle Konten für Auswahlfelder (Bearbeiter, Freigaben), alphabetisch.""" rows = conn.execute("SELECT id, display_name FROM users ORDER BY display_name COLLATE NOCASE").fetchall() return [{"id": u["id"], "name": u["display_name"]} for u in rows]