diff --git a/backend/scheduler.py b/backend/scheduler.py index 69bdbcc..a4ce739 100644 --- a/backend/scheduler.py +++ b/backend/scheduler.py @@ -100,6 +100,14 @@ def start(): replace_existing=True, misfire_grace_time=1800, ) + # Täglich 12:00 — Moderation-Overdue-Check + _scheduler.add_job( + _job_moderation_overdue, + CronTrigger(hour=12, minute=0), + id="moderation_overdue", + replace_existing=True, + misfire_grace_time=1800, + ) # 1. Feb / Mai / Aug / Nov 07:00 — Quartalsbericht _scheduler.add_job( _job_quarterly_report, @@ -117,7 +125,7 @@ def start(): misfire_grace_time=3600, ) _scheduler.start() - logger.info("Scheduler gestartet — Health-Reminder 08:00, Giftköder-Archiv 03:00, Wetter-Alert 07:30, Meilenstein-Check 00:05, Event-Import So 02:00, Rassen-Seed monatlich 1. des Monats, Status-Report täglich 06:00, Quartalsbericht 1. Feb/Mai/Aug/Nov 07:00. OSM-Cache: on-demand (kein Prewarm).") + logger.info("Scheduler gestartet — Health-Reminder 08:00, Giftköder-Archiv 03:00, Wetter-Alert 07:30, Meilenstein-Check 00:05, Event-Import So 02:00, Rassen-Seed monatlich 1. des Monats, Status-Report täglich 06:00, Moderation-Overdue 12:00, Quartalsbericht 1. Feb/Mai/Aug/Nov 07:00. OSM-Cache: on-demand (kein Prewarm).") def stop(): @@ -650,6 +658,87 @@ async def _job_ki_health_report(): # ------------------------------------------------------------------ +async def _job_moderation_overdue(): + """Sendet Alarm-Mail wenn Moderations-Einträge seit >24h offen sind.""" + import os + from mailer import send_email + + admin = os.getenv("ADMIN_EMAIL", "") + if not admin: + return + + SLA_H = 24 + threshold = f"datetime('now', '-{SLA_H} hours')" + + overdue = {} + try: + with db() as conn: + n = conn.execute(f"SELECT COUNT(*) FROM job_applications WHERE status IN ('pending','reviewing') AND created_at < {threshold}").fetchone()[0] + if n: overdue["Bewerbungen"] = n + n = conn.execute(f"SELECT COUNT(*) FROM users WHERE breeder_status='pending' AND created_at < {threshold}").fetchone()[0] + if n: overdue["Züchter-Anträge"] = n + n = conn.execute(f"SELECT COUNT(*) FROM forum_reports WHERE resolved=0 AND created_at < {threshold}").fetchone()[0] + if n: overdue["Forum-Meldungen"] = n + n = conn.execute(f"SELECT COUNT(*) FROM wiki_foto_submissions WHERE status='pending' AND created_at < {threshold}").fetchone()[0] + if n: overdue["Foto-Einreichungen"] = n + n = conn.execute(f"SELECT COUNT(*) FROM osm_poi_edits WHERE status='pending' AND created_at < {threshold}").fetchone()[0] + if n: overdue["POI-Korrekturen"] = n + n = conn.execute(f"SELECT COUNT(*) FROM wiki_zuchter WHERE verified=0 AND created_at < {threshold}").fetchone()[0] + if n: overdue["Züchter-Einreichungen (Wiki)"] = n + except Exception as e: + logger.error(f"Moderation-Overdue-Check: DB-Fehler: {e}") + return + + if not overdue: + logger.info("Moderation-Overdue-Check: Alles im SLA.") + return + + now_str = datetime.now(tz=_TZ).strftime("%d.%m.%Y %H:%M") + rows_html = "".join( + f'
| Rasse | Name / Zwingername | -Ort | VDH | Website | + | Ort | VDH | Alter | Website | |
|---|---|---|---|---|---|---|---|---|---|---|
| ${_esc(z.name)}${z.zwingername ? ` ${_esc(z.zwingername)}` : ''} |
${_esc([z.plz, z.ort, z.bundesland].filter(Boolean).join(' '))} | ${z.vdh_mitglied ? ` VDH` : '—'} | +${_ageLabel(z.created_at)} | ${z.website ? `Link` : '—'} |
@@ -1599,7 +1613,8 @@ window.Page_admin = (() => {
${_esc(f.rasse_name)}
- von ${_esc(f.user_name)}
+ von ${_esc(f.user_name)}
+ ${_ageLabel(f.created_at)}
${f.aktuell_foto ? `
-
+
${_esc(r.target_type)} #${r.target_id} · Gemeldet von ${_esc(r.melder_name || '?')}
+ ${_ageLabel(r.created_at)}
Grund: ${_esc(r.grund)}
@@ -1682,6 +1698,7 @@ window.Page_admin = (() => {
Alt |
Neu |
Von |
+ Alter |
| | ${_esc(e.old_value || '—')} | ${_esc(e.new_value || '—')} | ${_esc(e.einreicher_name || '?')} | +${_ageLabel(e.created_at)} |