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
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""API galerie admin — impression et suppression de photos."""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from fastapi import APIRouter, Request, Query
|
||||
from fastapi.responses import JSONResponse
|
||||
@@ -46,6 +47,9 @@ async def admin_get_photos(
|
||||
request: Request,
|
||||
page: int = Query(default=1, ge=1),
|
||||
limit: int = Query(default=24, ge=1, le=100),
|
||||
event_slug: str = Query(default=None),
|
||||
date_from: float = Query(default=None),
|
||||
date_to: float = Query(default=None),
|
||||
):
|
||||
"""Liste des photos pour la galerie admin, annotées avec leurs demandes d'impression."""
|
||||
if not _require_auth(request):
|
||||
@@ -53,23 +57,54 @@ async def admin_get_photos(
|
||||
|
||||
pb = request.app.state.photobooth_service
|
||||
printer_svc = request.app.state.printer_service
|
||||
cfg = request.app.state.config
|
||||
event_svc = getattr(request.app.state, "event_service", None)
|
||||
media_dir = Path(cfg.photobooth.media_dir)
|
||||
|
||||
# Récupère toutes les photos
|
||||
all_photos = await pb.get_media_collection(limit=500)
|
||||
photos = [p for p in all_photos if _is_image(p)]
|
||||
|
||||
# Enrichit chaque photo avec mtime depuis le disque
|
||||
mtime_index: dict[str, float] = {}
|
||||
if media_dir.exists():
|
||||
for f in media_dir.iterdir():
|
||||
mtime_index[f.stem] = f.stat().st_mtime
|
||||
|
||||
for p in photos:
|
||||
pid = _get_id(p)
|
||||
p["photo_id"] = pid
|
||||
mt = mtime_index.get(pid, 0.0)
|
||||
p["mtime"] = mt
|
||||
p["date_iso"] = datetime.fromtimestamp(mt).strftime("%Y-%m-%d") if mt else ""
|
||||
p["date_label"] = datetime.fromtimestamp(mt).strftime("%d/%m/%Y %H:%M") if mt else ""
|
||||
|
||||
# Filtre par événement
|
||||
if event_slug and event_svc:
|
||||
ev = await event_svc.get_event_by_slug(event_slug)
|
||||
if ev:
|
||||
t0 = ev["started_at"] or 0.0
|
||||
t1 = ev["ended_at"] or datetime.now().timestamp()
|
||||
photos = [p for p in photos if t0 <= p["mtime"] <= t1]
|
||||
|
||||
# Filtre par plage de dates
|
||||
if date_from is not None:
|
||||
photos = [p for p in photos if p["mtime"] >= date_from]
|
||||
if date_to is not None:
|
||||
photos = [p for p in photos if p["mtime"] <= date_to]
|
||||
|
||||
total = len(photos)
|
||||
start = (page - 1) * limit
|
||||
page_photos = photos[start:start + limit]
|
||||
|
||||
# Construit les URLs
|
||||
for p in page_photos:
|
||||
pid = _get_id(p)
|
||||
p["photo_id"] = pid
|
||||
pid = p["photo_id"]
|
||||
p["full_url"] = pb.media_url(pid)
|
||||
p["thumb_url"] = pb.thumbnail_url(pid)
|
||||
p["download_url"] = f"/api/gallery/download/{pid}"
|
||||
|
||||
# Croise avec la file d'impression en attente (une seule requête SQLite)
|
||||
# Croise avec la file d'impression en attente
|
||||
try:
|
||||
pending_map = await printer_svc.get_pending_by_photo_id()
|
||||
for p in page_photos:
|
||||
|
||||
@@ -63,9 +63,9 @@ async def update_event(request: Request, payload: EventUpdate):
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
+17
-10
@@ -44,25 +44,32 @@ async def api_gallery_photos(
|
||||
|
||||
# Filtre par événement si demandé
|
||||
if event_slug:
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
event_svc = getattr(request.app.state, "event_service", None)
|
||||
if event_svc:
|
||||
ev = await event_svc.get_event_by_slug(event_slug)
|
||||
if ev:
|
||||
started_at = ev["started_at"] or 0.0
|
||||
ended_at = ev["ended_at"] or datetime.now().timestamp()
|
||||
media_dir = Path(cfg.photobooth.media_dir)
|
||||
# Construire un index mtime par nom de fichier
|
||||
# Construire un index mtime par stem ET par nom complet
|
||||
mtime_index: dict[str, float] = {}
|
||||
if media_dir.exists():
|
||||
for f in media_dir.iterdir():
|
||||
mtime_index[f.name] = f.stat().st_mtime
|
||||
# Filtrer les photos par mtime
|
||||
for media_dir_candidate in [
|
||||
Path(cfg.photobooth.media_dir),
|
||||
Path("/home/pi/photobooth-data/media/processed_full"),
|
||||
Path("/home/pi/photobooth-data/media"),
|
||||
]:
|
||||
if media_dir_candidate.exists():
|
||||
for f in media_dir_candidate.iterdir():
|
||||
if f.is_file():
|
||||
mt = f.stat().st_mtime
|
||||
mtime_index[f.name] = mt # uuid.jpg
|
||||
mtime_index[f.stem] = mt # uuid
|
||||
break
|
||||
|
||||
def _in_event(p: dict) -> bool:
|
||||
pid = _get_id(p)
|
||||
mtime = mtime_index.get(pid, mtime_index.get(pid + ".jpg", 0.0))
|
||||
return started_at <= mtime <= ended_at
|
||||
stem = pid.rsplit(".", 1)[0] if "." in pid else pid
|
||||
mt = mtime_index.get(pid) or mtime_index.get(stem) or 0.0
|
||||
return mt > 0 and started_at <= mt <= ended_at
|
||||
photos = [p for p in photos if _in_event(p)]
|
||||
|
||||
total = len(photos)
|
||||
|
||||
Reference in New Issue
Block a user