diff --git a/backend/routes/moderation.py b/backend/routes/moderation.py index a039942..c4a3aee 100644 --- a/backend/routes/moderation.py +++ b/backend/routes/moderation.py @@ -170,22 +170,20 @@ async def mod_patch_user(uid: int, data: dict, user=Depends(require_moderator)): @router.get("/fotos") async def mod_fotos(user=Depends(require_moderator)): with db() as conn: - try: - rows = conn.execute(""" - SELECT s.id, s.rasse_slug, s.foto_url, s.created_at, - COALESCE(s.rights_confirmed, 0) AS rights_confirmed, - u.name AS user_name, - r.name AS rasse_name, r.foto_url AS aktuell_foto - FROM wiki_foto_submissions s - LEFT JOIN users u ON u.id=s.user_id - LEFT JOIN wiki_rassen r ON r.slug=s.rasse_slug - WHERE s.status='pending' - ORDER BY s.created_at ASC - LIMIT 50 - """).fetchall() - return [dict(r) for r in rows] - except Exception: - return [] + rows = conn.execute(""" + SELECT s.id, s.foto_url, s.created_at, + COALESCE(s.rights_confirmed, 0) AS rights_confirmed, + u.name AS user_name, + r.name AS rasse_name, r.slug AS rasse_slug, + r.foto_url AS aktuell_foto + FROM wiki_foto_submissions s + LEFT JOIN users u ON u.id = s.user_id + LEFT JOIN wiki_rassen r ON r.id = s.rasse_id + WHERE s.status = 'pending' + ORDER BY s.created_at ASC + LIMIT 50 + """).fetchall() + return [dict(r) for r in rows] # ------------------------------------------------------------------ @@ -193,32 +191,10 @@ async def mod_fotos(user=Depends(require_moderator)): # ------------------------------------------------------------------ @router.patch("/fotos/{foto_id}") async def mod_foto_action(foto_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: - sub = conn.execute( - "SELECT id, rasse_slug, foto_url FROM wiki_foto_submissions WHERE id=?", - (foto_id,) - ).fetchone() - if not sub: - raise HTTPException(404, "Einreichung nicht gefunden.") - - if action == "approve": - conn.execute( - "UPDATE wiki_foto_submissions SET status='approved' WHERE id=?", - (foto_id,) - ) - conn.execute( - "UPDATE wiki_rassen SET foto_url=? WHERE slug=?", - (sub["foto_url"], sub["rasse_slug"]) - ) - else: - reason = data.get("reject_reason", "Nicht geeignet.") - conn.execute( - "UPDATE wiki_foto_submissions SET status='rejected', reject_reason=? WHERE id=?", - (reason, foto_id) - ) - - return {"ok": True} + """Delegiert an die wiki-Route — dort ist die vollständige Logik (inkl. Datei-Kopie).""" + from routes.wiki import review_submission, ReviewModel + model = ReviewModel( + action=data.get("action", ""), + reject_reason=data.get("reject_reason", ""), + ) + return await review_submission(foto_id, model, user) diff --git a/backend/static/js/pages/moderation.js b/backend/static/js/pages/moderation.js index 285fd42..9f2fc15 100644 --- a/backend/static/js/pages/moderation.js +++ b/backend/static/js/pages/moderation.js @@ -164,10 +164,12 @@ window.Page_moderation = (() => {
${fotos.map(f => ` -
- +
+ + +
${_esc(f.rasse_name || f.rasse_slug)}
@@ -183,18 +185,18 @@ window.Page_moderation = (() => { background:#fef9c3;color:#92400e">⚠ Keine Bestätigung`}
${f.aktuell_foto ? ` +
Aktuell:
Aktuell -
aktuelles Foto
- ` : ''} + margin-bottom:var(--space-3)"> + ` : `
Noch kein Foto vorhanden
`}
+ data-id="${f.id}" style="flex:1">✓ Freigeben + data-id="${f.id}" style="color:var(--c-danger)">✗ Ablehnen
`).join('')} @@ -204,23 +206,26 @@ window.Page_moderation = (() => { el.querySelectorAll('.mod-foto-approve').forEach(btn => { btn.addEventListener('click', async () => { btn.disabled = true; + btn.textContent = '…'; try { await API.patch(`/moderation/fotos/${btn.dataset.id}`, { action: 'approve' }); - UI.toast.success('Foto freigegeben.'); + UI.toast('Foto freigegeben.', 'success'); await _loadFotos(el); - } catch (e) { UI.toast.error(e.message); btn.disabled = false; } + } catch (e) { UI.toast(e.message, 'danger'); btn.disabled = false; btn.textContent = '✓ Freigeben'; } }); }); el.querySelectorAll('.mod-foto-reject').forEach(btn => { btn.addEventListener('click', async () => { + const reason = prompt('Ablehnungsgrund (optional, wird dem User angezeigt):'); + if (reason === null) return; btn.disabled = true; try { await API.patch(`/moderation/fotos/${btn.dataset.id}`, { action: 'reject', - reject_reason: 'Nicht geeignet.' + reject_reason: reason || 'Foto entspricht nicht den Anforderungen.' }); - UI.toast.success('Foto abgelehnt.'); + UI.toast('Einreichung abgelehnt.', 'info'); await _loadFotos(el); } catch (e) { UI.toast.error(e.message); btn.disabled = false; } }); diff --git a/backend/static/sw.js b/backend/static/sw.js index 5088f26..9e9ba94 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v363'; +const CACHE_VERSION = 'by-v364'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten