-- Migration : création de la table `photobooth.photos` -- Réf. : docs/modele-donnees.md (section "Table `photos`") -- -- Table déplacée de `public.pq_photos` (2026-09-15) vers `photobooth.photos` -- (2026-09-16), cf. 20260916090100_create_events_table.sql pour le contexte -- du changement de schéma. -- -- ⚠️ Ne pas appliquer depuis cet agent (Kévin applique lui-même). create table if not exists photobooth.photos ( id uuid primary key default gen_random_uuid(), event_id uuid not null references photobooth.events (id) on delete cascade, -- Contrainte d'isolation multi-tenant (héritée de la relecture sécurité du -- 2026-09-15, conservée à l'identique) : url_storage DOIT commencer par -- "/", pour empêcher qu'une ligne photobooth.photos de l'event A -- pointe vers un objet Storage de l'event B. S'applique à TOUT rôle qui -- insère, y compris le rôle propriétaire des fonctions SECURITY DEFINER -- (postgres) — défense en profondeur même si, avec ce nouveau schéma, le -- seul chemin d'écriture "normal" est déjà revérifié explicitement dans -- public.insert_public_photo() (cf. 20260916090400_create_public_functions.sql). url_storage text not null check (starts_with(url_storage, event_id::text || '/')), -- Bornes alignées sur les `maxlength` du formulaire (src/e/index.html : -- 60 pour le prénom, 300 pour le message). nom_invite text check (char_length(nom_invite) <= 60), message text check (char_length(message) <= 300), uploaded_at timestamptz not null default now() ); comment on table photobooth.photos is 'Photos/vidéos déposées par les invités pour un événement donné. Schéma photobooth jamais exposé via PostgREST : seul chemin d''écriture pour anon = public.insert_public_photo().'; comment on column photobooth.photos.event_id is 'Événement associé (photobooth.events). Suppression en cascade si l''événement est supprimé.'; comment on column photobooth.photos.url_storage is 'Chemin du fichier dans Supabase Storage (bucket `event-photos`, chemin `{event_id}/{uuid}.{ext}`). CHECK : doit commencer par "/" (isolation multi-tenant).'; comment on column photobooth.photos.nom_invite is 'Nom saisi par l''invité (optionnel, aucune donnée d''identification obligatoire, cf. docs/rgpd.md). Limité à 60 caractères (aligné sur src/e/index.html).'; comment on column photobooth.photos.message is 'Message laissé par l''invité (optionnel). Limité à 300 caractères (aligné sur src/e/index.html).'; -- Index pour les accès en lecture par événement (galerie admin, export ZIP, -- job d'expiration/suppression). create index if not exists idx_photos_event_id on photobooth.photos (event_id);