Backend: galerie session /s/{code} + upload API
Ajoute deux routes FastAPI :
- GET /s/{code} → galerie HTML mobile avec auto-refresh 15s
- POST /api/sessions/{code}/upload → stockage photo dans session_{code}/
Corrige layout admin : titre sm au lieu de lg, compteur sans chevauchement.
Wizard step 2 : code affiché en lg (96px) au lieu de xxl (300px).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@ from contextlib import asynccontextmanager
|
||||
import cv2
|
||||
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, UploadFile, File
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.responses import FileResponse, JSONResponse, HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from backend.config import (
|
||||
@@ -155,6 +155,63 @@ async def page_principale():
|
||||
return FileResponse(str(RACINE / "frontend" / "index.html"))
|
||||
|
||||
|
||||
@app.post("/api/sessions/{code}/upload")
|
||||
async def api_session_upload(code: str, fichier: UploadFile = File(...)):
|
||||
"""Upload d'une photo dans une session identifiée par code à 9 chiffres."""
|
||||
if not code.isdigit() or len(code) != 9:
|
||||
return JSONResponse({"erreur": "Code invalide"}, status_code=400)
|
||||
dossier = DOSSIER_PHOTOS / f"session_{code}"
|
||||
dossier.mkdir(parents=True, exist_ok=True)
|
||||
dest = dossier / fichier.filename
|
||||
dest.write_bytes(await fichier.read())
|
||||
return {"ok": True, "fichier": fichier.filename}
|
||||
|
||||
|
||||
@app.get("/s/{code}", response_class=HTMLResponse)
|
||||
async def galerie_session(code: str):
|
||||
"""Galerie mobile pour une session photo (QR code)."""
|
||||
if not code.isdigit() or len(code) != 9:
|
||||
return HTMLResponse("<h1>Code invalide</h1>", status_code=404)
|
||||
dossier = DOSSIER_PHOTOS / f"session_{code}"
|
||||
cfg = charger_config()
|
||||
nom_event = cfg.get("evenement", {}).get("nom", "Photobooth")
|
||||
photos = sorted(dossier.glob("*.jpg"), key=lambda p: p.stat().st_mtime, reverse=True) if dossier.exists() else []
|
||||
items = ""
|
||||
for p in photos:
|
||||
items += f'<div class="photo"><img src="/data/photos/session_{code}/{p.name}" loading="lazy" onclick="agrandir(this)"></div>\n'
|
||||
if not items:
|
||||
items = '<p class="vide">Les photos apparaîtront ici après chaque prise 📷</p>'
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="fr"><head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>{nom_event} — Galerie</title>
|
||||
<style>
|
||||
*{{margin:0;padding:0;box-sizing:border-box}}
|
||||
body{{background:#0a0a1a;color:#fff;font-family:system-ui,sans-serif;min-height:100vh}}
|
||||
header{{background:#111;padding:1rem;text-align:center;border-bottom:2px solid #e91e63}}
|
||||
header h1{{font-size:1.4rem;color:#e91e63}}
|
||||
header p{{font-size:.85rem;color:#aaa;margin-top:.3rem}}
|
||||
.grille{{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:6px;padding:8px}}
|
||||
.photo img{{width:100%;aspect-ratio:3/2;object-fit:cover;border-radius:6px;cursor:pointer;transition:transform .2s}}
|
||||
.photo img:active{{transform:scale(.97)}}
|
||||
.vide{{text-align:center;padding:3rem;color:#666;font-size:1.1rem}}
|
||||
.lightbox{{display:none;position:fixed;inset:0;background:#000d;align-items:center;justify-content:center;z-index:99}}
|
||||
.lightbox.open{{display:flex}}
|
||||
.lightbox img{{max-width:95vw;max-height:95vh;border-radius:8px}}
|
||||
.lightbox::after{{content:"✕";position:absolute;top:1rem;right:1.2rem;font-size:2rem;cursor:pointer;color:#fff}}
|
||||
</style></head><body>
|
||||
<header><h1>📷 {nom_event}</h1><p>Code session : {code}</p></header>
|
||||
<div class="grille">{items}</div>
|
||||
<div class="lightbox" id="lb" onclick="this.classList.remove('open')"><img id="lb-img" src=""></div>
|
||||
<script>
|
||||
function agrandir(el){{document.getElementById('lb-img').src=el.src;document.getElementById('lb').classList.add('open')}}
|
||||
// Rafraîchissement auto toutes les 15s
|
||||
setTimeout(()=>location.reload(), 15000)
|
||||
</script>
|
||||
</body></html>"""
|
||||
return HTMLResponse(html)
|
||||
|
||||
|
||||
# --- API Config ---
|
||||
|
||||
@app.get("/api/config")
|
||||
|
||||
Reference in New Issue
Block a user