Files
admin c89924ea18
🚀 Deploy — JH Photomaton / 🔍 Vérification (push) Has been cancelled
🚀 Deploy — JH Photomaton / 🍓 Deploy sur le Pi (push) Has been cancelled
feat: admin gallery - lightbox with icons, filters by date/event/status; fix: event badges - only current slug shows En cours; fix: archive_all_open on new event; fix: mtime_index uses stem for event photo filter
2026-07-17 15:59:51 +02:00

153 lines
4.8 KiB
Python

"""API de gestion des événements et statistiques."""
from __future__ import annotations
import io
import logging
import zipfile
from datetime import datetime
from pathlib import Path
from fastapi import APIRouter, Request
from fastapi.responses import StreamingResponse, JSONResponse
from pydantic import BaseModel
from backend.services.event_service import slugify
logger = logging.getLogger(__name__)
router = APIRouter()
class EventUpdate(BaseModel):
name: str
new_event: bool = False # True = archive l'ancien et crée un nouveau
@router.get("/event")
async def get_current_event(request: Request):
"""Retourne l'événement en cours avec ses statistiques."""
cfg = request.app.state.config
event_svc = request.app.state.event_service
ev = cfg.event
stats = await event_svc.get_stats(ev.slug)
return {
"name": ev.name,
"slug": ev.slug,
"started_at": ev.started_at,
"started_at_iso": (
datetime.fromtimestamp(ev.started_at).isoformat()
if ev.started_at
else None
),
"stats": stats,
}
@router.put("/event")
async def update_event(request: Request, payload: EventUpdate):
"""Met à jour l'événement en cours.
Si new_event=True : archive le précédent et démarre un nouvel événement
(les stats repartent de zéro).
Si new_event=False : renomme l'événement en cours (même slug ou nouveau slug,
les stats existantes restent sous l'ancien slug).
"""
cfg = request.app.state.config
config_svc = request.app.state.config_service
event_svc = request.app.state.event_service
new_slug = slugify(payload.name)
now = datetime.now().timestamp()
old_slug = cfg.event.slug
if payload.new_event:
# Archive TOUS les événements encore ouverts (ended_at IS NULL)
await event_svc.archive_all_open()
# Crée ou met à jour la ligne dans la DB
await event_svc.ensure_event(new_slug, payload.name, now if payload.new_event else cfg.event.started_at or now)
# Persiste dans settings.yaml
started = now if payload.new_event or not cfg.event.started_at else cfg.event.started_at
config_svc.save_event_config(payload.name, new_slug, started)
stats = await event_svc.get_stats(new_slug)
return {
"ok": True,
"name": payload.name,
"slug": new_slug,
"started_at": started,
"new_event": payload.new_event,
"stats": stats,
}
@router.get("/event/{slug}/export")
async def export_event_zip(request: Request, slug: str):
"""Exporte toutes les photos d'un événement dans un fichier ZIP.
Les photos sont sélectionnées par timestamp de fichier (mtime) entre
started_at et ended_at de l'événement. Pour l'événement en cours,
ended_at = maintenant.
"""
event_svc = request.app.state.event_service
cfg = request.app.state.config
ev = await event_svc.get_event_by_slug(slug)
if not ev:
return JSONResponse({"error": f"Événement '{slug}' introuvable"}, status_code=404)
started_at = ev["started_at"] or 0.0
ended_at = ev["ended_at"] or datetime.now().timestamp()
media_dir = Path(cfg.photobooth.media_dir)
if not media_dir.exists():
return JSONResponse({"error": "Dossier média introuvable"}, status_code=500)
# Sélectionner les photos dans la plage de l'événement
photos = []
for f in sorted(media_dir.iterdir()):
if f.suffix.lower() not in (".jpg", ".jpeg", ".png"):
continue
mtime = f.stat().st_mtime
if started_at <= mtime <= ended_at:
photos.append(f)
if not photos:
return JSONResponse({"error": "Aucune photo pour cet événement"}, status_code=404)
# Créer le ZIP en mémoire
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
for photo in photos:
zf.write(photo, photo.name)
buf.seek(0)
filename = f"{slug}_By_LSDW_{datetime.fromtimestamp(started_at).strftime('%Y-%m-%d')}.zip"
return StreamingResponse(
buf,
media_type="application/zip",
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
)
@router.get("/event/history")
async def get_event_history(request: Request):
"""Retourne tous les événements passés avec leurs statistiques."""
event_svc = request.app.state.event_service
history = await event_svc.get_history()
# Ajoute les dates ISO pour l'affichage
for ev in history:
if ev.get("started_at"):
ev["started_at_iso"] = datetime.fromtimestamp(ev["started_at"]).strftime("%d/%m/%Y %H:%M")
if ev.get("ended_at"):
ev["ended_at_iso"] = datetime.fromtimestamp(ev["ended_at"]).strftime("%d/%m/%Y %H:%M")
else:
ev["ended_at_iso"] = None
return {"events": history}