From da70bbfac3be8094e72858e96773fb3f58b15839 Mon Sep 17 00:00:00 2001 From: mscadm Date: Mon, 13 Jul 2026 14:55:57 +0000 Subject: [PATCH] Fix: run_retention_cleanup fehlte im vorherigen Push (Fehler mit stale Datei behoben) --- db.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/db.py b/db.py index 2793399..482b743 100644 --- a/db.py +++ b/db.py @@ -270,3 +270,54 @@ def update_ticket_status(tenant_id, ticket_id, status, actor_label): VALUES (%s,%s,%s,%s)""", (ticket_id, tenant_id, actor_label, "Status geaendert auf '%s'." % status), ) + + +# ── Aufbewahrungsfrist-Bereinigung (DSGVO Speicherbegrenzung) ───────────────── +_RETENTION_LOCK_KEY = 727271 # feste Postgres-Advisory-Lock-ID fuer diesen Job + + +def run_retention_cleanup(): + """Loescht abgeschlossene Tickets und Audit-Log-Eintraege, die aelter sind + als die pro Mandant hinterlegte Aufbewahrungsfrist (retention_tickets_days / + retention_audit_days). Offene/laufende Tickets werden NIE geloescht, nur + solche im Status 'Geloest'/'Geschlossen'. + + Nutzt einen Postgres-Advisory-Lock: laeuft der Job bereits (z. B. durch + einen anderen Worker-Prozess), wird dieser Aufruf uebersprungen statt + parallel zu loeschen. + """ + with get_conn() as conn: + with conn.cursor() as cur: + cur.execute("SELECT pg_try_advisory_lock(%s)", (_RETENTION_LOCK_KEY,)) + got_lock = cur.fetchone()[0] + if not got_lock: + return {"skipped": "lock_not_acquired"} + try: + with _dict_cursor(conn) as cur: + cur.execute("SELECT id, name, retention_tickets_days, retention_audit_days FROM tenants") + tenants = cur.fetchall() + summary = [] + for t in tenants: + tid = t["id"] + with conn.cursor() as cur: + cur.execute( + """DELETE FROM tickets WHERE tenant_id=%s AND status IN ('Geloest','Geschlossen') + AND updated_at < now() - (%s || ' days')::interval""", + (tid, t["retention_tickets_days"]), + ) + tickets_deleted = cur.rowcount + cur.execute( + """DELETE FROM audit_log WHERE tenant_id=%s AND aktion <> 'retention_cleanup' + AND zeit < now() - (%s || ' days')::interval""", + (tid, t["retention_audit_days"]), + ) + audit_deleted = cur.rowcount + if tickets_deleted or audit_deleted: + log_audit(tid, None, "retention_cleanup", "tenant", str(tid), + {"tickets_deleted": tickets_deleted, "audit_log_deleted": audit_deleted}) + summary.append({"tenant_id": tid, "tenant": t["name"], + "tickets_deleted": tickets_deleted, "audit_log_deleted": audit_deleted}) + return {"summary": summary} + finally: + with conn.cursor() as cur: + cur.execute("SELECT pg_advisory_unlock(%s)", (_RETENTION_LOCK_KEY,))