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).
233 lines
14 KiB
Markdown
233 lines
14 KiB
Markdown
# Cahier des charges — Application Photobooth QR Code
|
|
**Porteur de projet :** Kévin Lecou
|
|
**Marque :** Personnelle (Kévin Lecou) — usage v1 perso, ouverture commerciale v2/v3
|
|
**Durée de dev estimée :** ~1 journée (V1)
|
|
**Date de rédaction :** 13/09/2026
|
|
|
|
---
|
|
|
|
## 🧭 1. Contexte & objectif
|
|
|
|
Application web (pas d'appli native, pas de compte requis) permettant aux invités d'un événement (mariage, anniversaire, soirée) d'uploader leurs photos via un QR code affiché sur place. Les photos sont centralisées, accompagnées d'un nom/message optionnel façon livre d'or, et récupérables en un clic par l'organisateur (Kévin ou son client) sous forme de ZIP.
|
|
|
|
**V1 (maintenant) :** usage personnel de Kévin, pour ses propres événements.
|
|
**V2/V3 (plus tard) :** produit facturé à l'événement pour des clients externes (mariages, entreprises), potentiellement piloté via n8n/Notion.
|
|
|
|
> ⚠️ Point à garder en tête dès la V1 : même si l'usage est perso pour l'instant, autant construire la structure de données comme si chaque "événement" était déjà un client — ça évite de tout refaire en V2. C'est déjà comme ça que je décris le modèle de données plus bas.
|
|
|
|
---
|
|
|
|
## ⚙️ 2. Stack technique imposée
|
|
|
|
| Brique | Outil | Rôle |
|
|
|---|---|---|
|
|
| Backend / DB / Storage | **Supabase** (déjà connecté) | Stockage des photos, table événements, table photos, gestion RLS |
|
|
| Front | À définir par le dev (recommandé : léger, mobile-first, pas de framework lourd) | Page upload + page galerie admin |
|
|
| Automatisation (V2) | **n8n** | Création d'événement, notifications, facturation |
|
|
| Suivi client (V2) | **Notion** | Base "Événements" (statut, date, lien, facturation) |
|
|
| Notifications (V2) | **Telegram** | Alerte "X nouvelles photos" |
|
|
| Hébergement front | À proposer par le dev — critère : **écoconception** (hébergeur mutualisé/vert type Scalingo, o2switch, ou hébergement statique très léger type Cloudflare Pages/Netlify avec faible empreinte) |
|
|
| Génération QR code | Lib front ou back (ex: `qrcode` npm) — pas de service tiers payant nécessaire |
|
|
|
|
**Contrainte transverse :** sobriété numérique — compression des images côté client avant upload (réduit stockage Supabase + bande passante + empreinte carbone).
|
|
|
|
---
|
|
|
|
## 🗂️ 3. Modèle de données (Supabase)
|
|
|
|
### Table `events`
|
|
| Champ | Type | Notes |
|
|
|---|---|---|
|
|
| id | uuid (PK) | généré auto |
|
|
| slug | text unique | utilisé dans l'URL publique (ex: `/e/mariage-julie-marc`) |
|
|
| nom | text | nom de l'événement |
|
|
| date_evenement | date | |
|
|
| created_at | timestamptz | |
|
|
| expire_at | timestamptz | **date_evenement + 7 jours** (décidé : le compteur démarre à la date de l'événement, pas à la création de la fiche ni à la 1ère photo) |
|
|
| statut | enum | `actif`, `expire`, `telecharge`, `archive` |
|
|
| admin_token | text (secret) | token unique pour accéder à la galerie admin + déclencher le ZIP |
|
|
| client_nom | text (nullable) | vide en V1, utilisé en V2 pour la facturation |
|
|
| facture_montant | numeric (nullable) | V2 |
|
|
|
|
### Table `photos`
|
|
| Champ | Type | Notes |
|
|
|---|---|---|
|
|
| id | uuid (PK) | |
|
|
| event_id | uuid (FK → events) | |
|
|
| url_storage | text | chemin dans le bucket Supabase |
|
|
| nom_invite | text (nullable) | champ "livre d'or" |
|
|
| message | text (nullable) | message optionnel associé à la photo |
|
|
| uploaded_at | timestamptz | |
|
|
|
|
### Storage bucket
|
|
- 1 bucket par environnement (`event-photos`), organisé en sous-dossiers par `event_id`.
|
|
- **RLS (Row Level Security) :**
|
|
- Écriture (upload) : **anonyme autorisée**, mais uniquement sur un `event_id` existant et **actif** (pas expiré) — pas d'auth invité, le lien/QR = le ticket d'entrée.
|
|
- Lecture publique des fichiers : **non** — seul l'admin (via `admin_token`) peut lister/voir les photos. Les invités uploadent en aveugle, ils ne voient pas la galerie des autres.
|
|
- Suppression : réservée à l'admin_token, ou automatique après expiration (cf. §6).
|
|
|
|
---
|
|
|
|
## ✅ 4. Fonctionnalités V1 (scope figé pour le dev)
|
|
|
|
### Côté invité (page publique, mobile-first)
|
|
1. Scan du QR code → arrive sur `/e/{slug}`.
|
|
2. Page simple : nom de l'événement, bouton "Ajouter une photo" (accès caméra + galerie du téléphone).
|
|
3. Champ optionnel : "Ton prénom" + "Un petit mot" (livre d'or).
|
|
4. Upload → compression client → confirmation visuelle ("Merci ! Ta photo est bien envoyée 🎉").
|
|
5. **Pas de galerie visible côté invité** (confirmé : upload only, pas de consultation).
|
|
6. **Pas de téléchargement invité** (confirmé).
|
|
7. Si l'événement est expiré/archivé → message "Cet événement n'accepte plus de photos".
|
|
|
|
### Côté admin (Kévin uniquement, V1)
|
|
1. Accès via URL secrète type `/admin/{slug}?token={admin_token}` (décidé : pas de vrai login en V1, juste le token dans l'URL — token long et non devinable, généré aléatoirement, suffisant pour un usage perso).
|
|
2. Vue galerie : toutes les photos de l'événement + nom/message associé.
|
|
3. Bouton **"Télécharger tout en ZIP"**.
|
|
4. Statut de l'événement (actif / expire dans X jours / expiré).
|
|
|
|
### QR code
|
|
- Généré automatiquement à la création de l'événement (lien direct vers `/e/{slug}`).
|
|
- Exportable en image (PNG/SVG) pour impression sur le support physique (comme sur ta photo de référence).
|
|
|
|
---
|
|
|
|
## 🔁 5. Flux fonctionnel (schéma)
|
|
|
|
```
|
|
[Création événement] (manuel en V1, via un simple formulaire ou directement en base)
|
|
│
|
|
▼
|
|
Génère : slug + admin_token + QR code + expire_at (J+7)
|
|
│
|
|
▼
|
|
[QR code imprimé / affiché lors de la soirée]
|
|
│
|
|
▼
|
|
[Invité scanne] → page /e/{slug}
|
|
│
|
|
├─ Événement actif ? ──NON──▶ "Événement clos"
|
|
│
|
|
OUI
|
|
▼
|
|
Formulaire upload (photo + prénom + message optionnel)
|
|
│
|
|
▼
|
|
Compression image (client-side)
|
|
│
|
|
▼
|
|
Upload → Supabase Storage + insert ligne `photos`
|
|
│
|
|
▼
|
|
Confirmation "Merci !"
|
|
|
|
--- Côté admin, à tout moment ---
|
|
|
|
[Kévin ouvre /admin/{slug}?token={admin_token}]
|
|
│
|
|
▼
|
|
Liste des photos + noms/messages
|
|
│
|
|
▼
|
|
Bouton "Télécharger ZIP"
|
|
│
|
|
▼
|
|
Génération ZIP (backend) → téléchargement
|
|
│
|
|
▼
|
|
statut passe à "telecharge" (mais les fichiers restent en ligne :
|
|
décidé → pas de suppression immédiate même après téléchargement du ZIP)
|
|
|
|
--- Expiration ---
|
|
|
|
[J+7 après la date de l'événement] (décidé : basé sur date_evenement, pas sur la création de la fiche)
|
|
│
|
|
▼
|
|
Cron/tâche planifiée Supabase (Edge Function + pg_cron) :
|
|
statut → "expire", upload bloqué
|
|
│
|
|
▼
|
|
Suppression effective des fichiers (storage + lignes `photos`) à J+7,
|
|
que le ZIP ait été téléchargé ou non (décidé : le J+7 prime dans tous les cas —
|
|
⚠️ à bien faire comprendre au client/à l'organisateur que s'il n'a pas téléchargé
|
|
son ZIP avant J+7, les photos sont perdues)
|
|
```
|
|
|
|
**Erreurs à gérer explicitement (pour le dev) :**
|
|
- Upload d'un fichier non-image → rejeté avec message clair.
|
|
- Fichier trop volumineux même après compression → limite fixée (ex : 15 Mo max avant compression, ~2-3 Mo après).
|
|
- Connexion coupée pendant l'upload → retry ou message d'erreur explicite (pas d'échec silencieux).
|
|
- Événement inexistant (mauvais slug) → page 404 propre, pas d'erreur technique visible.
|
|
- Deux invités uploadent en simultané → pas de conflit attendu (chaque upload = une ligne indépendante), mais à tester en charge légère (ex: 20-30 uploads simultanés, taille réaliste d'une soirée).
|
|
- Quota Supabase (plan gratuit) approché/dépassé → **⚠️ point ouvert : je n'ai pas les seuils exacts à jour du plan gratuit Supabase (ils évoluent), à vérifier par le dev directement sur la doc Supabase au moment du dev plutôt que de se fier à un chiffre que je pourrais avoir en tête et qui serait faux.**
|
|
|
|
---
|
|
|
|
## 🔐 6. RGPD — à intégrer dans le script (V1)
|
|
|
|
Comme les invités uploadent des photos pouvant contenir des visages d'autres personnes, quelques éléments minimaux sont à intégrer. **Je ne suis pas juriste et ceci ne remplace pas un avis légal — mais voici la base habituellement recommandée pour ce type d'outil :**
|
|
|
|
1. **Mention d'information** affichée sur la page d'upload avant envoi (courte, pas besoin de case à cocher bloquante en V1 perso, mais recommandé dès la V2 commerciale) :
|
|
> *"En envoyant une photo, tu acceptes qu'elle soit visible par l'organisateur de l'événement et éventuellement partagée dans le cadre de cette soirée. Les photos sont automatiquement supprimées après [durée]. Pour toute demande de suppression, contacte [email/contact organisateur]."*
|
|
|
|
2. **Durée de conservation limitée** : cohérent avec ton besoin (7 jours / jusqu'à téléchargement du ZIP) — c'est déjà une bonne pratique RGPD (minimisation).
|
|
|
|
3. **Droit à la suppression** : prévoir un moyen simple (même juste un email de contact affiché) pour qu'une personne photographiée demande le retrait d'une photo la concernant.
|
|
|
|
4. **Pas de données personnelles obligatoires** : le prénom/message sont optionnels — bon réflexe déjà prévu.
|
|
|
|
5. **Pour la V2 commerciale** (clients externes) : il faudra à ce moment-là un texte de mentions légales plus formel, et probablement une case à cocher explicite selon le nombre d'invités et le contexte — **⚠️ à faire valider par un professionnel du droit avant la V2 commerciale**, je peux t'aider à préparer un brouillon mais je ne peux pas garantir la conformité légale complète.
|
|
|
|
---
|
|
|
|
## 📦 7. Livrables attendus du développeur
|
|
|
|
État au 2026-09-15 (fin de journée) — voir `docs/decisions.md` pour les décisions actées et le SQL complet dans `supabase/migrations/`.
|
|
|
|
- [x] Page publique d'upload (`/e/{slug}`) — code écrit (`src/e/`), compression + HEIC + gestion d'erreurs. **Non testée en navigateur réel** (pas de projet Supabase live pour l'instant).
|
|
- [x] Page admin galerie + export ZIP (`/admin/{slug}?token=...`) — code écrit (`src/admin/gallery/`), consomme l'Edge Function `admin-gallery`. **Non testée en réel**, dépend de l'Edge Function ci-dessous.
|
|
- [x] Génération du QR code (export PNG/SVG imprimable) — code écrit (`src/admin/qrcode/`), page démo utilisable dès maintenant.
|
|
- [ ] Schéma Supabase (tables `pq_events`/`pq_photos` + RLS + policies Storage) — **SQL écrit et relu (sécurité OK après corrections), PAS ENCORE APPLIQUÉ**. En attente de validation finale de Kévin avant `apply_migration`.
|
|
- [x] Compression image côté client avant upload — `browser-image-compression`, intégrée dans `src/e/upload.js`.
|
|
- [x] Gestion de l'expiration (statut événement, blocage upload, suppression J+7) — blocage upload prêt côté schéma (`event_accepts_uploads`) ; job SQL `pq_expire_events` (pg_cron) écrit. **Reste ouvert** : mécanisme de déclenchement de la suppression des fichiers Storage (pg_net vs n8n, cf. `docs/decisions.md`) — à trancher avant implémentation.
|
|
- [x] Mention RGPD intégrée sur la page upload — texte de `docs/rgpd.md` repris verbatim.
|
|
- [x] Edge Function admin (liste photos + ZIP) — code écrit (`supabase/functions/admin-gallery/`), ZIP en streaming. **Non déployée, dépendance `zip.js` non testée en environnement réel** — à vérifier via `supabase functions serve` avant tout déploiement.
|
|
- [ ] Documentation courte : comment créer un nouvel événement manuellement (en attendant l'automatisation n8n en V2) — pas encore rédigée.
|
|
- [ ] Code source versionné (repo Git — **à préciser : GitHub perso de Kévin, ou autre ?** ⚠️ point ouvert) — dépôt local Git existant, pas de remote configuré à ce stade.
|
|
|
|
---
|
|
|
|
## 💰 8. Budget & contraintes
|
|
|
|
- Budget outils externes : **léger accepté** (ex: nom de domaine, éventuel plan Supabase payant si besoin, hébergement front à petit coût). Pas de contrainte "0€ strict".
|
|
- Écoconception à privilégier dans les choix d'hébergement et de dépendances (pas de lib front lourde inutile).
|
|
- Dev réalisé par un tiers (freelance/dev externe) sur la base de ce document.
|
|
|
|
---
|
|
|
|
## 🚀 9. Roadmap V2/V3 (hors scope dev V1, pour information)
|
|
|
|
- Création d'événement automatisée via **n8n** (formulaire → génère event + QR + entrée Notion).
|
|
- Notification **Telegram** en temps réel ("5 nouvelles photos ajoutées").
|
|
- Base **Notion "Événements"** : statut, client, date, lien galerie, montant facturé.
|
|
- Authentification admin renforcée (vrai login) si ouverture à plusieurs clients simultanés.
|
|
- Facturation à l'événement (tarif à définir).
|
|
- Éventuelle marque dédiée si le produit décolle (distincte du nom personnel Kévin Lecou).
|
|
|
|
---
|
|
|
|
## ❓ 10. Décisions actées
|
|
|
|
1. ✅ Le J+7 démarre à la **date de l'événement** (`date_evenement`), pas à la création de la fiche ni à la 1ère photo.
|
|
2. ✅ Sécurité admin V1 : **token secret dans l'URL**, pas de vrai login.
|
|
3. ✅ Le J+7 **prime toujours** : suppression effective des photos à J+7 même si le ZIP a déjà été téléchargé (pas de suppression anticipée au moment du téléchargement).
|
|
4. ⚠️ **Repo de code — point encore ouvert.** Tu ne sais pas trop, donc voici ma recommandation concrète : crée un compte **GitHub gratuit à ton nom** (ou au nom "Kévin Lecou" / futur nom de marque), en **dépôt privé**. Pourquoi :
|
|
- C'est toi le porteur du projet et celui qui va potentiellement le revendre en V2/V3 → tu dois être **propriétaire du code**, pas le dev.
|
|
- Un repo privé gratuit suffit largement pour ce volume de code.
|
|
- Ça te permet de changer de dev plus tard sans dépendre de qui que ce soit.
|
|
- Si tu n'as pas encore de compte GitHub, je peux te faire un guide pas-à-pas (2 minutes, gratuit) — dis-le moi.
|
|
- Alternative si tu préfères tout garder sur ton VPS : un dépôt Git auto-hébergé (ex: via Gitea, léger) est possible vu que tu as déjà un VPS pour n8n — mais GitHub reste plus simple si tu ne gères pas encore ça au quotidien.
|
|
5. ✅ Nom de domaine : **probablement sur ton VPS/serveur existant** — pas de coût supplémentaire à prévoir a priori, à confirmer avec le dev que ton VPS peut effectivement héberger le front + les redirections DNS nécessaires.
|
|
|
|
---
|
|
|
|
*Document rédigé pour servir de brief à un développeur externe. Le point marqué ⚠️ reste à trancher (repo de code) — une recommandation est proposée en attendant ta décision finale.* |