Files
photoBooth/backend/services/event_service.py
T
admin 9a006f1457
🚀 Deploy — JH Photomaton / 🔍 Vérification (push) Has been cancelled
🚀 Deploy — JH Photomaton / 🍓 Deploy sur le Pi (push) Has been cancelled
all
2026-07-17 00:44:22 +02:00

126 lines
4.5 KiB
Python

"""Service de gestion des événements et statistiques.
Chaque événement (soirée, mariage, fête...) a ses propres compteurs :
- photos_taken : incrémenté par le webhook 'finished' de photobooth-app
- print_requests : incrémenté à chaque demande d'impression dans la file
- prints_done : incrémenté à chaque impression réussie
- downloads : incrémenté à chaque téléchargement via /api/gallery/download/
"""
from __future__ import annotations
import logging
import re
import unicodedata
from datetime import datetime
from pathlib import Path
import aiosqlite
logger = logging.getLogger(__name__)
def slugify(name: str) -> str:
"""Transforme un nom d'événement en slug ASCII sans accents ni espaces."""
# Normalise les caractères Unicode (enlève les accents)
nfkd = unicodedata.normalize("NFD", name)
ascii_str = "".join(c for c in nfkd if unicodedata.category(c) != "Mn")
ascii_str = ascii_str.lower()
# Garde lettres, chiffres, espaces, tirets
ascii_str = re.sub(r"[^\w\s-]", "", ascii_str)
# Remplace espaces/tirets multiples par un underscore
ascii_str = re.sub(r"[\s_-]+", "_", ascii_str)
return ascii_str.strip("_") or "evenement"
_SCHEMA = """
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
started_at REAL NOT NULL,
ended_at REAL,
photos_taken INTEGER NOT NULL DEFAULT 0,
print_requests INTEGER NOT NULL DEFAULT 0,
prints_done INTEGER NOT NULL DEFAULT 0,
downloads INTEGER NOT NULL DEFAULT 0
);
"""
_VALID_COUNTERS = {"photos_taken", "print_requests", "prints_done", "downloads"}
class EventService:
def __init__(self):
self._db: aiosqlite.Connection | None = None
async def init_db(self, db_path: Path):
db_path.parent.mkdir(parents=True, exist_ok=True)
self._db = await aiosqlite.connect(str(db_path))
self._db.row_factory = aiosqlite.Row
await self._db.execute(_SCHEMA)
await self._db.commit()
logger.info("EventService initialisé : %s", db_path)
async def ensure_event(self, slug: str, name: str, started_at: float):
"""Crée la ligne pour cet événement si elle n'existe pas encore."""
await self._db.execute(
"""INSERT INTO events (name, slug, started_at)
VALUES (?, ?, ?)
ON CONFLICT(slug) DO UPDATE SET name = excluded.name""",
(name, slug, started_at),
)
await self._db.commit()
async def archive_event(self, slug: str):
"""Marque un événement comme terminé (ended_at = maintenant)."""
await self._db.execute(
"UPDATE events SET ended_at = ? WHERE slug = ? AND ended_at IS NULL",
(datetime.now().timestamp(), slug),
)
await self._db.commit()
async def increment(self, slug: str, counter: str):
"""Incrémente un compteur de l'événement identifié par son slug.
Crée automatiquement une ligne si elle n'existe pas.
"""
if counter not in _VALID_COUNTERS:
logger.warning("Compteur inconnu : %s", counter)
return
# Upsert : insère si absent, sinon incrémente
await self._db.execute(
f"""INSERT INTO events (name, slug, started_at, {counter})
VALUES (?, ?, unixepoch(), 1)
ON CONFLICT(slug) DO UPDATE SET {counter} = {counter} + 1""",
(slug, slug),
)
await self._db.commit()
async def get_stats(self, slug: str) -> dict:
"""Retourne les stats pour un slug donné."""
async with self._db.execute(
"""SELECT photos_taken, print_requests, prints_done, downloads
FROM events WHERE slug = ?""",
(slug,),
) as cur:
row = await cur.fetchone()
if not row:
return {"photos_taken": 0, "print_requests": 0, "prints_done": 0, "downloads": 0}
return dict(row)
async def get_history(self) -> list[dict]:
"""Retourne tous les événements triés du plus récent au plus ancien."""
async with self._db.execute(
"""SELECT id, name, slug, started_at, ended_at,
photos_taken, print_requests, prints_done, downloads
FROM events
ORDER BY started_at DESC"""
) as cur:
rows = await cur.fetchall()
return [dict(r) for r in rows]
async def close(self):
if self._db:
await self._db.close()