153 lines
4.8 KiB
Python
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}
|