Feature: Moderation SLA — Altersanzeige + Overdue-Alarm täglich 12:00, SW by-v591
This commit is contained in:
parent
87039994ce
commit
d00284184b
4 changed files with 113 additions and 6 deletions
|
|
@ -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'<tr><td style="padding:6px 12px;font-weight:600;color:#c45000">{label}</td>'
|
||||
f'<td style="padding:6px 12px;font-size:18px;font-weight:800;color:#c45000">{count}</td></tr>'
|
||||
for label, count in overdue.items()
|
||||
)
|
||||
html = f"""\
|
||||
<!DOCTYPE html><html lang="de"><head><meta charset="utf-8"></head>
|
||||
<body style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#f5f0ea;margin:0;padding:0">
|
||||
<div style="max-width:560px;margin:24px auto;background:#fff;border-radius:14px;overflow:hidden;box-shadow:0 2px 16px rgba(0,0,0,.08)">
|
||||
<div style="background:linear-gradient(135deg,#c45000,#e8733a);padding:22px 28px;color:#fff">
|
||||
<div style="font-size:20px;font-weight:800;margin-bottom:2px">⚠️ Moderation überfällig</div>
|
||||
<div style="opacity:.88;font-size:13px">{now_str} · SLA: {SLA_H}h</div>
|
||||
</div>
|
||||
<div style="padding:22px 28px">
|
||||
<p style="color:#444;margin:0 0 16px">Folgende Einträge warten seit mehr als {SLA_H} Stunden auf Bearbeitung:</p>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:14px">
|
||||
<thead><tr style="border-bottom:2px solid #f0e8dc">
|
||||
<th style="text-align:left;padding:6px 12px;color:#888;font-size:11px;text-transform:uppercase;letter-spacing:.06em">Bereich</th>
|
||||
<th style="text-align:left;padding:6px 12px;color:#888;font-size:11px;text-transform:uppercase;letter-spacing:.06em">Anzahl</th>
|
||||
</tr></thead>
|
||||
<tbody>{rows_html}</tbody>
|
||||
</table>
|
||||
<div style="margin-top:20px">
|
||||
<a href="https://banyaro.app/app/admin" style="display:inline-block;background:#c45000;color:#fff;
|
||||
text-decoration:none;padding:10px 22px;border-radius:8px;font-weight:700;font-size:14px">
|
||||
→ Admin-Panel öffnen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:12px 28px;background:#fdf6ef;font-size:11px;color:#aaa;text-align:center">
|
||||
Ban Yaro · banyaro.app
|
||||
</div>
|
||||
</div></body></html>"""
|
||||
|
||||
plain = f"Ban Yaro — Moderation überfällig ({now_str})\n\nSeit >{SLA_H}h offen:\n" + \
|
||||
"\n".join(f" • {l}: {c}" for l, c in overdue.items()) + \
|
||||
"\n\nhttps://banyaro.app/app/admin"
|
||||
|
||||
try:
|
||||
await send_email(admin, f"⚠️ Ban Yaro — Moderation überfällig ({', '.join(overdue)})", html, plain)
|
||||
logger.info(f"Moderation-Overdue-Mail gesendet: {overdue}")
|
||||
except Exception as e:
|
||||
logger.error(f"Moderation-Overdue-Mail fehlgeschlagen: {e}")
|
||||
|
||||
|
||||
def _action_items_html(metrics: dict) -> str:
|
||||
items = [
|
||||
("jobs_pending", "Bewerbungen offen"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue