"""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 and old_slug and old_slug != new_slug: # Archive l'ancien événement await event_svc.archive_event(old_slug) # 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}