commit 2007eab006d053af48371de407807518414e9dd9 Author: Kevin Lecou Date: Tue Sep 15 21:44:00 2026 +0200 Initialisation du projet photobooth-qr Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_014akYEuf44fM6EiUvrmoBzS diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..be9ad56 --- /dev/null +++ b/.env.example @@ -0,0 +1,18 @@ +# Copier ce fichier en .env et remplir avec les vraies valeurs. +# Ne jamais commiter le fichier .env réel. + +# URL du projet Supabase +SUPABASE_URL= + +# Clé publique (anon) Supabase — utilisable côté client +SUPABASE_ANON_KEY= + +# Clé service_role Supabase — usage backend/admin UNIQUEMENT, +# jamais exposée côté client +SUPABASE_SERVICE_ROLE_KEY= + +# Nom du bucket Supabase Storage utilisé pour les photos +SUPABASE_STORAGE_BUCKET=photos + +# URL de base de l'application (utilisée pour générer les liens QR code) +APP_BASE_URL= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c01f48e --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Dépendances +node_modules/ + +# Variables d'environnement +.env +.env.local +.env.*.local + +# Build +dist/ +build/ +.cache/ +.parcel-cache/ + +# Supabase (local) +supabase/.branches/ +supabase/.temp/ + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# OS / éditeurs +.DS_Store +Thumbs.db +.vscode/ +.idea/ + +# Divers +*.zip +*.tmp diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1144bea --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,65 @@ +# CLAUDE.md — photobooth-qr + +## Contexte + +Application web de galerie photo événementielle par QR code. + +Principe : les invités d'un événement (mariage, soirée, etc.) scannent un QR +code, arrivent sur une page d'upload, et déposent leurs photos/vidéos qui +atterrissent dans une galerie consultable par l'organisateur/admin. À la fin, +l'admin peut exporter toutes les photos en ZIP. + +Développé par **Kévin Lecou**, auto-entrepreneur, mixologue (projet +**Rêves Liquides**). Ce projet est distinct de Rêves Liquides mais peut lui +servir d'outil. + +- **V1** : usage personnel de Kévin (ses propres événements / tests). +- **V2 / V3** : vente du service à des clients externes (mariages, + événementiel professionnel). Le projet doit donc être pensé dès le début + pour être **multi-clients et réutilisable**, sans pour autant sur-ingénierer + la V1. + +## Stack imposée + +- **Supabase** pour la base de données (Postgres) et le stockage (Storage). +- **Front léger, mobile-first, sans framework lourd** (pas de React/Vue/Next + sauf décision contraire explicite plus tard). HTML/CSS/JS vanilla ou + micro-lib, en priorité. +- **Pas d'application native.** +- **Pas de compte invité** : les invités uploadent sans créer de compte, + sans authentification. + +## Contraintes non négociables + +1. **Écoconception** + - Compression image **côté client obligatoire** avant upload (jamais + envoyer une photo brute non compressée vers Supabase Storage). + - Minimiser le nombre de requêtes réseau et le poids des pages. + - Toujours proposer, par défaut, le choix le plus sobre en dépendances + et en requêtes réseau — pas de librairie ajoutée "par confort" si une + solution native (fetch, Canvas API, etc.) suffit. + +2. **RGPD** + - Mention légale obligatoire sur la page d'upload (voir `docs/rgpd.md`). + - Suppression automatique des données (photos + métadonnées) à échéance + fixée (voir `docs/decisions.md` pour la règle exacte : J+7 après la + date de l'événement, indépendamment du téléchargement du ZIP). + +3. **Pas de dette technique inutile** + - Le projet est pensé pour durer et être réutilisé sur plusieurs clients + (multi-tenant logique via la table `events`). + - Éviter les raccourcis qui compliqueraient le passage V1 → V2/V3 + (ex : éviter de hardcoder des valeurs propres à un seul événement). + - Mais ne pas sur-architecturer non plus : pas de fonctionnalité pour un + besoin hypothétique non encore confirmé par Kévin. + +## Mode de collaboration + +- **Avant toute décision structurante** (schéma de base de données, + mécanisme d'authentification/admin, choix d'hébergement, changement de + stack), **demander confirmation à Kévin** avant de trancher seul et + d'implémenter. Proposer les options avec leurs compromis, pas juste une + question ouverte. +- Toute décision actée est à consigner dans `docs/decisions.md`. +- Ne pas inventer de credentials, URLs, ou valeurs de config manquantes — + demander si besoin (cf. règle globale dans `~/.claude/CLAUDE.md`). diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b96a1f --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# photobooth-qr + +Galerie photo événementielle par QR code : les invités d'un événement +scannent un QR code, uploadent leurs photos depuis leur téléphone (sans +compte, sans app), et l'organisateur récupère toutes les photos dans une +galerie admin exportable en ZIP. + +## Objectif + +- **V1** : usage personnel (test grandeur nature sur mes propres événements). +- **V2 / V3** : produit vendu à des clients (mariages, événementiel). + +## Stack + +- **Supabase** — base de données Postgres + Storage. +- **Front** — HTML/CSS/JS léger, mobile-first, sans framework lourd. +- Pas d'app native, pas de compte invité. + +## Documentation + +- [`docs/cahier-des-charges.md`](docs/cahier-des-charges.md) — spécifications fonctionnelles. +- [`docs/architecture.md`](docs/architecture.md) — schéma de flux général. +- [`docs/modele-donnees.md`](docs/modele-donnees.md) — schéma des tables Supabase. +- [`docs/rgpd.md`](docs/rgpd.md) — mention légale et politique de conservation. +- [`docs/decisions.md`](docs/decisions.md) — journal des décisions actées. + +## Lancer en local + +_À compléter au fur et à mesure du développement._ + +```bash +# 1. Copier les variables d'environnement +cp .env.example .env +# puis remplir .env avec les vraies valeurs Supabase + +# 2. Installer les dépendances (si un tooling est ajouté) +# npm install + +# 3. Lancer le front en local +# (à définir : serveur statique simple, ex. npx serve src/) +``` + +## Statut + +Projet en phase de cadrage — structure et documentation en place, +développement pas encore commencé. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..763e6b7 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,55 @@ +# Architecture + +## Vue d'ensemble + +Le système repose sur un flux simple, sans compte invité, avec Supabase +comme unique backend (base de données + stockage de fichiers). + +1. Un événement est créé dans la table `events` avec un `slug` unique (utilisé + dans l'URL publique) et un `admin_token` (utilisé pour l'accès admin). +2. Un QR code pointe vers l'URL publique d'upload de l'événement + (`/e/`). +3. Un invité scanne le QR code, arrive sur la page d'upload, compresse ses + photos côté client, puis les envoie vers Supabase Storage. Une ligne est + créée dans la table `photos` pour chaque fichier. +4. L'organisateur (admin) accède à la galerie via une URL contenant son + `admin_token` (`/admin/?token=...`), consulte les photos, et peut + déclencher un export ZIP de l'ensemble des photos de l'événement. +5. À J+7 après la **date de l'événement** (pas la date de création), les + photos et les métadonnées associées sont automatiquement supprimées, + que le ZIP ait été téléchargé ou non (voir `rgpd.md` et `decisions.md`). + +## Schéma de flux + +```mermaid +flowchart TD + A[Invité scanne le QR code] --> B[Page d'upload publique /e/slug] + B --> C[Compression image côté client] + C --> D[(Supabase Storage\nbucket photos)] + C --> E[(Table photos\nSupabase DB)] + D --> F[Galerie admin /admin/slug?token=...] + E --> F + F --> G[Export ZIP de toutes les photos] + H[Date événement + 7 jours] --> I[Suppression automatique\nphotos + storage] + E -.expire_at atteint.-> I + D -.expire_at atteint.-> I +``` + +## Composants + +| Composant | Rôle | Techno | +|---|---|---| +| Page upload (publique) | Formulaire de dépôt photo, sans auth | HTML/CSS/JS léger | +| Compression image | Réduction de poids avant envoi | Canvas API / lib légère côté client | +| Supabase Storage | Stockage des fichiers photo | Supabase Storage (bucket `photos`) | +| Supabase DB | Métadonnées événements et photos | Supabase Postgres | +| Page galerie admin | Consultation + export ZIP | HTML/CSS/JS léger | +| Job d'expiration | Suppression auto à J+7 | Fonction planifiée (Supabase Edge Function / cron), à définir | + +## Points à trancher avec Kévin avant implémentation + +- Mécanisme exact du job d'expiration (cron Supabase, Edge Function planifiée, + ou script sur le VPS existant). +- Génération et hébergement du QR code (généré à la volée ou stocké). +- Bibliothèque de compression image côté client (ou implémentation maison via + Canvas API pour rester sobre en dépendances). diff --git a/docs/cahier-des-charges.md b/docs/cahier-des-charges.md new file mode 100644 index 0000000..4878dfc --- /dev/null +++ b/docs/cahier-des-charges.md @@ -0,0 +1 @@ +# Cahier des charges diff --git a/docs/decisions.md b/docs/decisions.md new file mode 100644 index 0000000..52a9a20 --- /dev/null +++ b/docs/decisions.md @@ -0,0 +1,19 @@ +# Journal des décisions + +Ce fichier consigne les décisions structurantes actées pour le projet, avec +la date à laquelle elles ont été prises. Toute nouvelle décision structurante +(schéma DB, auth, hébergement, etc.) doit être ajoutée ici après validation +par Kévin. + +## 2026-09-15 + +- **Expiration des données** : suppression à **J+7 à partir de la date de + l'événement** (`date_evenement`), et non à partir de la date de création + de l'événement dans le système. +- **Sécurité admin (V1)** : accès à la galerie admin via un **token secret + dans l'URL** (`admin_token`), pas de système de login/mot de passe. Réévaluer + ce choix avant V2/V3 si le nombre de clients augmente. +- **Suppression des données** : effective à J+7 **dans tous les cas**, y + compris si l'organisateur a déjà téléchargé le ZIP d'export des photos. +- **Hébergement** : utilisation du **VPS existant de Kévin** + (148.230.114.175), pas de nouvel hébergement dédié pour l'instant. diff --git a/docs/modele-donnees.md b/docs/modele-donnees.md new file mode 100644 index 0000000..e2e9ee8 --- /dev/null +++ b/docs/modele-donnees.md @@ -0,0 +1,64 @@ +# Modèle de données + +Deux tables Supabase (Postgres). Pas de table `users` côté invité : les +invités n'ont pas de compte. L'admin n'a pas de compte non plus en V1 — son +accès repose sur un token secret (voir `decisions.md`). + +## Table `events` + +| Colonne | Type | Description | +|---|---|---| +| `id` | `uuid` (PK, défaut `gen_random_uuid()`) | Identifiant unique de l'événement | +| `slug` | `text`, unique | Identifiant lisible utilisé dans l'URL publique d'upload (`/e/`) | +| `nom` | `text` | Nom de l'événement (ex : "Mariage Julie & Marc") | +| `date_evenement` | `date` | Date de l'événement, sert de référence pour l'expiration | +| `created_at` | `timestamptz`, défaut `now()` | Date de création de l'événement dans le système | +| `expire_at` | `timestamptz` | Date/heure d'expiration calculée (`date_evenement` + 7 jours) | +| `statut` | `text` | Statut de l'événement : `actif`, `expire`, `archive` (à affiner) | +| `admin_token` | `text`, unique | Token secret donnant accès à la galerie admin | +| `client_nom` | `text`, nullable | Nom du client (utile en V2/V3 quand le service est vendu) | +| `facture_montant` | `numeric`, nullable | Montant facturé au client (utile en V2/V3) | + +## Table `photos` + +| Colonne | Type | Description | +|---|---|---| +| `id` | `uuid` (PK, défaut `gen_random_uuid()`) | Identifiant unique de la photo | +| `event_id` | `uuid` (FK → `events.id`, `on delete cascade`) | Événement associé | +| `url_storage` | `text` | Chemin/URL du fichier dans Supabase Storage | +| `nom_invite` | `text`, nullable | Nom saisi par l'invité (optionnel) | +| `message` | `text`, nullable | Message laissé par l'invité (optionnel) | +| `uploaded_at` | `timestamptz`, défaut `now()` | Date d'upload | + +## Row Level Security (RLS) + +RLS activé sur les deux tables. Règles à valider avec Kévin avant +implémentation définitive, principe retenu pour l'instant : + +### `events` + +- **Lecture publique** : non autorisée directement (pas de `SELECT` public + sur `events`). L'accès public à l'upload passe par une vérification côté + fonction/API sur le `slug` et le `statut = 'actif'`, sans exposer toute la + ligne (notamment pas `admin_token`). +- **Lecture admin** : autorisée si la requête présente l'`admin_token` + correspondant (comparaison côté fonction Postgres/Edge Function plutôt que + policy RLS naïve, pour éviter d'exposer le token dans une clause `WHERE` + côté client). +- **Écriture** : réservée à l'admin/back-office (pas de policy publique + d'insertion/update sur `events`). + +### `photos` + +- **Insertion (upload) anonyme autorisée** si et seulement si : + - l'`event_id` correspond à un événement dont le `statut = 'actif'`, et + - la date courante est antérieure à `expire_at`. +- **Lecture** réservée à l'admin (accès via `admin_token` validé), jamais + de lecture publique de la liste des photos d'un événement. +- **Update / delete** : pas d'update par les invités ; delete réservé au + job d'expiration automatique (ou à l'admin, à confirmer). + +> ⚠️ Le détail exact des policies RLS (SQL) sera écrit dans +> `supabase/migrations/` au moment de l'implémentation, et validé avec +> Kévin avant application — cf. règle "décision structurante" dans +> `CLAUDE.md`. diff --git a/docs/rgpd.md b/docs/rgpd.md new file mode 100644 index 0000000..3efca9f --- /dev/null +++ b/docs/rgpd.md @@ -0,0 +1,47 @@ +# RGPD + +> ⚠️ **Ce document n'est pas un avis juridique formel.** Il pose les +> principes retenus pour le projet, à faire valider par un professionnel du +> droit (avocat, service juridique) avant tout usage commercial (V2/V3) +> impliquant des clients externes. + +## Mention légale (page d'upload) + +Texte à afficher, de façon visible, sur la page d'upload avant que l'invité +ne dépose une photo : + +> 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. + +## Politique de conservation + +- **Durée de conservation** : 7 jours à compter de la **date de + l'événement** (`date_evenement`), et non de la date de création de + l'événement dans le système ni de la date d'upload de chaque photo. +- **Suppression automatique et systématique** : à l'échéance, les photos + (fichiers dans Supabase Storage) et les métadonnées associées (lignes de + la table `photos`) sont supprimées, **même si l'organisateur a déjà + téléchargé le ZIP d'export**. Le téléchargement du ZIP transfère la + responsabilité de conservation à l'organisateur ; le système ne conserve + pas de copie au-delà de l'échéance. +- **Données minimales collectées côté invité** : uniquement ce que l'invité + choisit de fournir (nom, message), aucune collecte d'email, de + numéro de téléphone ou de données de compte (pas de compte invité). +- **Pas de traçage** : pas de cookies de suivi ni d'analytics tiers sur la + page d'upload, conformément à la contrainte d'écoconception et de + sobriété du projet. + +## Points à valider avec Kévin (et si besoin un juriste) avant V2/V3 + +- Rédaction définitive des CGU/mentions légales pour un usage commercial + auprès de clients externes (mariages, événementiel pro). +- Statut du client (organisateur) comme responsable de traitement, et de + Kévin/l'outil comme sous-traitant, au sens RGPD — à formaliser + contractuellement en V2/V3. +- Procédure de demande de suppression anticipée par un invité (droit à + l'effacement) avant l'échéance des 7 jours.