La Voix du Peuple · Consultation citoyenne
{title_esc}
{f'
{organizer_esc}
' if organizer_esc else ""}
{subject_esc}
Exporté le {now_str} · {idea_count} contribution(s) intégrée(s)
{body_html}
"""
return Response(html_content, mimetype="text/html")
# ─── Routes Admin ─────────────────────────────────────────────────────────────
@app.post("/api/admin/login")
@limiter.limit("10 per minute")
def admin_login():
"""Vérifie le mot de passe admin. Retourne ok si correct."""
secret = _get_admin_secret()
if not secret:
return jsonify({"error": "admin_not_configured", "message": "ADMIN_SECRET non configuré."}), 503
data = request.get_json(silent=True) or {}
password = data.get("password", "")
if password != secret:
logger.warning("Tentative de connexion admin échouée")
return jsonify({"error": "unauthorized", "message": "Mot de passe incorrect."}), 401
logger.info("Connexion admin réussie")
return jsonify({"ok": True, "token": _sign_admin_token(secret)})
@app.get("/api/admin/stats")
@require_admin
def admin_stats():
"""Statistiques détaillées pour l'administrateur (toutes contributions confondues)."""
stats = get_stats()
return jsonify(stats)
@app.get("/api/admin/ideas")
@require_admin
@limiter.limit("120 per minute")
def admin_list_ideas():
"""Liste toutes les contributions avec filtres et pagination."""
status = request.args.get("status", "all")
page = max(1, int(request.args.get("page", 1)))
per_page = min(100, max(10, int(request.args.get("per_page", 50))))
search = request.args.get("q", "").strip()
if status not in ("all", "accepted", "rejected", "flagged"):
status = "all"
ideas, total = get_ideas_admin(status=status, page=page, per_page=per_page, search=search)
return jsonify({
"ideas": [serialize_idea_admin(i) for i in ideas],
"total": total,
"page": page,
"perPage": per_page,
"pages": max(1, -(-total // per_page)),
})
@app.delete("/api/admin/ideas/