Sprint 15: Zeitzone-Fix, Gewichts-Sync, Öffnungszeiten, KI-Bericht, POI-Moderation — SW by-v432, APP_VER 411

- client_time: Browser-Lokalzeit bei allen Creates mitschicken (Tagebuch, Notizen,
  Forum, Verlorener Hund, Routen) — kein UTC-Versatz mehr bei Einträgen
- Gewicht-Sync: health typ=gewicht schreibt dogs.gewicht_kg, einmalige Migration
- Praxen: opening_hours + lat/lon/osm_id in tieraerzte-Tabelle, OSM-Nearby-Lookup,
  Öffnungszeiten in Karte und Detailansicht
- KI-Gesundheitsbericht: alle 2 Wochen automatisch, ki_health_reports-Tabelle,
  Frontend-Banner mit Archiv (letzten 5 Berichte)
- POI-Korrekturen: User schlägt Öffnungszeiten-Änderung vor, Moderatoren-Tab
  genehmigt/lehnt ab, user_edited-Flag schützt vor Overpass-Überschreibung
- timeutils.py: safe_client_time() zentral für alle Routen
This commit is contained in:
rene 2026-04-26 15:38:50 +02:00
parent 679dbdd862
commit 06bd8525ed
21 changed files with 724 additions and 75 deletions

View file

@ -45,11 +45,20 @@ async def mod_stats(user=Depends(require_moderator)):
except Exception:
pass
pending_poi_edits = 0
try:
pending_poi_edits = conn.execute(
"SELECT COUNT(*) FROM osm_poi_edits WHERE status='pending'"
).fetchone()[0]
except Exception:
pass
return {
"open_reports": open_reports,
"pending_fotos": pending_fotos,
"banned_users": banned_users,
"pending_zuchter": pending_zuchter,
"open_reports": open_reports,
"pending_fotos": pending_fotos,
"banned_users": banned_users,
"pending_zuchter": pending_zuchter,
"pending_poi_edits": pending_poi_edits,
}
@ -207,3 +216,56 @@ async def mod_foto_action(foto_id: int, data: dict, user=Depends(require_moderat
reject_reason=data.get("reject_reason", ""),
)
return await review_submission(foto_id, model, user)
# ------------------------------------------------------------------
# GET /api/moderation/poi-edits — ausstehende POI-Korrekturen
# ------------------------------------------------------------------
@router.get("/poi-edits")
async def mod_poi_edits(user=Depends(require_moderator)):
with db() as conn:
rows = conn.execute("""
SELECT e.id, e.osm_id, e.poi_name, e.field,
e.old_value, e.new_value, e.status,
e.created_at, e.resolved_at,
u.name AS einreicher_name
FROM osm_poi_edits e
JOIN users u ON u.id = e.user_id
ORDER BY e.status ASC, e.created_at DESC
LIMIT 100
""").fetchall()
return [dict(r) for r in rows]
# ------------------------------------------------------------------
# PATCH /api/moderation/poi-edits/{id} — approve / reject
# ------------------------------------------------------------------
@router.patch("/poi-edits/{edit_id}")
async def mod_poi_edit_action(edit_id: int, data: dict,
user=Depends(require_moderator)):
action = data.get("action")
if action not in ("approve", "reject"):
raise HTTPException(400, "action muss 'approve' oder 'reject' sein.")
with db() as conn:
edit = conn.execute(
"SELECT * FROM osm_poi_edits WHERE id=?", (edit_id,)
).fetchone()
if not edit:
raise HTTPException(404, "Korrektur nicht gefunden.")
if edit["status"] != "pending":
raise HTTPException(409, "Korrektur wurde bereits bearbeitet.")
if action == "approve":
conn.execute(
f"UPDATE osm_pois SET {edit['field']}=?, user_edited=1 WHERE osm_id=?",
(edit["new_value"], edit["osm_id"])
)
conn.execute(
"""UPDATE osm_poi_edits SET status=?, mod_id=?, resolved_at=datetime('now')
WHERE id=?""",
(action + "d", user["id"], edit_id)
)
return {"status": action + "d"}