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:
@@ -0,0 +1,44 @@
|
||||
-- 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
|
||||
-- "<event_id>/", 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 "<event_id>/" (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);
|
||||
Reference in New Issue
Block a user