Ajoute la V1 fonctionnelle : upload invité, galerie admin, QR code, backend Supabase

Schéma dédié `photobooth` (jamais exposé via PostgREST, accès exclusivement
via fonctions SECURITY DEFINER dans public) avec RLS, policies Storage sur
bucket privé event-photos, Edge Functions admin-gallery et expire-events,
et job pg_cron d'expiration J+7. Le tout déployé et testé en conditions
réelles sur le projet Supabase kevin-lecou-hub avant relecture sécurité
(faille d'abus sur insert_public_photo corrigée en cours de route).

Côté front : page upload invité (compression + HEIC + RGPD), galerie admin
avec export ZIP, générateur de QR code autonome. Workflow n8n de purge
Storage J+7 fourni (à activer manuellement côté n8n).
This commit is contained in:
2026-09-16 11:32:31 +02:00
parent 2007eab006
commit 9b1461978b
37 changed files with 6255 additions and 4 deletions
+102
View File
@@ -0,0 +1,102 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Ajouter une photo</title>
<!--
Cette page est servie pour toute URL du type /e/<slug> (le slug est lu
côté client depuis window.location.pathname, voir upload.js).
Ça suppose une règle de réécriture côté serveur (ex. nginx try_files)
qui renvoie ce fichier pour toute requête sous /e/* — à mettre en place
lors du déploiement, hors scope de ce fichier front.
-->
<meta name="robots" content="noindex, nofollow">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="page">
<header class="page__header">
<h1 id="event-name">Ajouter une photo</h1>
</header>
<section id="status-loading" class="state-block" role="status">
<p>Chargement de l'événement…</p>
</section>
<section id="status-error" class="state-block state-block--error" hidden role="alert">
<p id="status-error-message"></p>
<button type="button" id="retry-btn" hidden>Réessayer</button>
</section>
<section id="success-block" class="state-block state-block--success" hidden role="status">
<p>Merci&nbsp;! Ta photo est bien envoyée 🎉</p>
<button type="button" id="add-another-btn">Ajouter une autre photo</button>
</section>
<form id="upload-form" class="upload-form" hidden novalidate>
<div class="field">
<label for="photo-input">Ta photo</label>
<!-- Pas d'attribut "capture" : on laisse le téléphone proposer à la
fois l'appareil photo ET la galerie (capture forcerait l'un ou
l'autre selon les navigateurs). -->
<input
type="file"
id="photo-input"
name="photo"
accept="image/*,.heic,.heif"
required
>
</div>
<img id="preview" class="preview" alt="Aperçu de la photo choisie" hidden>
<div class="field">
<label for="nom-input">Ton prénom <span class="optional">(facultatif)</span></label>
<input
type="text"
id="nom-input"
name="nom"
maxlength="60"
autocomplete="given-name"
placeholder="Ex : Camille"
>
</div>
<div class="field">
<label for="message-input">Un petit mot <span class="optional">(facultatif)</span></label>
<textarea
id="message-input"
name="message"
maxlength="300"
rows="3"
placeholder="Ton petit mot pour l'occasion…"
></textarea>
</div>
<p class="legal-notice">
En déposant une photo, vous acceptez qu'elle soit stockée temporairement
dans le cadre de cet événement et mise à disposition de l'organisateur.
Vos photos sont automatiquement supprimées 7 jours après la date de
l'événement, sans action de votre part. Aucune photo n'est utilisée à
d'autres fins, ni transmise à des tiers. Vous pouvez demander la
suppression anticipée d'une photo en contactant l'organisateur de
l'événement.
</p>
<p id="progress-text" class="progress-text" aria-live="polite"></p>
<button type="submit" id="submit-btn">Envoyer ma photo</button>
</form>
</main>
<!--
config.js n'est PAS commité (voir .gitignore) : copier config.example.js
en config.js et renseigner les vraies valeurs Supabase avant déploiement.
Chargé en script classique (donc synchrone, avant le module) pour garantir
que window.__PHOTOBOOTH_CONFIG__ existe avant l'exécution de upload.js.
-->
<script src="config.js"></script>
<script type="module" src="upload.js"></script>
</body>
</html>