Files
photobooth-qr/docs/cahier-des-charges.md
T

15 KiB

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-16 (fin de journée) — voir docs/decisions.md pour les décisions actées et le SQL complet dans supabase/migrations/.

  • Page publique d'upload (/e/{slug}) — déployée et testée en réel sur https://photobooth.assistantaikev.eu/e/{slug}, upload confirmé depuis un vrai téléphone.
  • Page admin galerie + export ZIP (/admin/{slug}?token=...) — déployée et testée en réel, ZIP en streaming fonctionnel.
  • Génération du QR code (export PNG/SVG imprimable) — déployée (/admin/qrcode/), + carton imprimable A5 bonus (/admin/print-template/).
  • Schéma Supabase (schéma dédié photobooth, jamais exposé via PostgREST, accès via fonctions SECURITY DEFINER) — appliqué et vérifié sur le projet définitif kevin-lecou-hub.
  • Compression image côté client avant upload — browser-image-compression, testée en conditions réelles.
  • Gestion de l'expiration — blocage upload actif, job SQL pq_expire_eventsexpire_events (pg_cron) actif et vérifié planifié. Purge des fichiers Storage : Edge Function expire-events déployée et testée manuellement ; déclenchement automatique (workflow n8n) créé mais pas encore activé (credentials à finaliser côté Kévin).
  • Mention RGPD — déplacée d'un bloc permanent vers une modale de bienvenue au 1er passage par événement (demande Kévin), texte légal inchangé, toujours consultable via un lien dédié.
  • Edge Functions admin (admin-gallery, expire-events) — déployées et testées en réel sur kevin-lecou-hub.
  • Nouveau : limite de photos par invité, réglable par événement (max_photos_per_guest), appliquée côté serveur.
  • Nouveau : 3 passes de direction artistique (palette punchy corail/prune/doré, mode sombre automatique, finition animations/micro-interactions) — DA jugée "pas mal" par Kévin mais pas encore validée définitivement (retour direct : "pas encore vraiment fan des couleurs"), itération à poursuivre.
  • 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.