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:
2026-09-16 11:32:31 +02:00
parent 2007eab006
commit 9b1461978b
37 changed files with 6255 additions and 4 deletions
@@ -0,0 +1,80 @@
-- Migration : bucket Storage `event-photos` (privé) et policies RLS
-- Réf. : docs/modele-donnees.md, docs/cahier-des-charges.md (section
-- "Storage bucket"), CLAUDE.md ("lecture des photos jamais publique").
--
-- Logique inchangée par rapport à la version du 2026-09-15 (le passage au
-- schéma `photobooth` ne concerne que Postgres/PostgREST : les policies
-- storage.objects et la config du bucket sont indépendantes de l'exposition
-- de schéma, cf. consigne de tâche). Seule différence : la fonction
-- public.storage_event_photos_upload_allowed() vit désormais dans le
-- fichier 20260916090400_create_public_functions.sql (regroupée avec les
-- autres fonctions public.*) au lieu d'être définie ici.
--
-- Décision actée (bucket privé) : le bucket `event-photos` est PRIVÉ
-- (public = false). Aucune lecture publique/anonyme des fichiers. La
-- consultation admin passe exclusivement par des URLs signées générées côté
-- Edge Function `admin-gallery` (service_role).
--
-- file_size_limit / allowed_mime_types (relecture sécurité 2026-09-15,
-- conservés à l'identique) :
-- - file_size_limit = 6 291 456 octets (6 Mo) = MAX_OUTPUT_BYTES dans
-- src/e/upload.js (garde-fou côté client après compression).
-- - allowed_mime_types = uniquement 'image/jpeg' : la pipeline de
-- compression (browser-image-compression, fileType: "image/jpeg", y
-- compris pour les HEIC convertis au préalable via heic2any) force
-- systématiquement une sortie JPEG. ⚠️ À mettre à jour si le front
-- change un jour cette pipeline.
--
-- Convention de chemin utilisée côté front (src/e/upload.js) :
-- {event_id}/{uuid}.{ext} -- ex : 8f3.../a1b2.jpg
--
-- ⚠️ Ne pas appliquer depuis cet agent (Kévin applique lui-même).
-- ---------------------------------------------------------------------------
-- Bucket
-- ---------------------------------------------------------------------------
insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
values (
'event-photos',
'event-photos',
false,
6291456,
array['image/jpeg']
)
on conflict (id) do update
set public = false,
file_size_limit = excluded.file_size_limit,
allowed_mime_types = excluded.allowed_mime_types;
-- RLS est activé par défaut sur storage.objects sur les projets Supabase
-- hébergés. Cette ligne est défensive (idempotente) pour les environnements
-- où ce ne serait pas déjà le cas (ex. stack Supabase locale/self-managed).
alter table storage.objects enable row level security;
-- ---------------------------------------------------------------------------
-- Policies storage.objects
-- ---------------------------------------------------------------------------
-- INSERT anonyme autorisé uniquement dans le bucket event-photos, et
-- uniquement si l'event_id déduit du chemin est actif et non expiré
-- (public.storage_event_photos_upload_allowed, définie dans
-- 20260916090400_create_public_functions.sql).
create policy "anon_insert_event_photos_if_event_active"
on storage.objects
for insert
to anon
with check (
bucket_id = 'event-photos'
and public.storage_event_photos_upload_allowed(name)
);
-- Aucune policy SELECT anon/authenticated sur ce bucket : lecture jamais
-- publique. La consultation admin passe exclusivement par des URLs signées
-- générées par l'Edge Function service_role `admin-gallery`, cf.
-- supabase/functions/admin-gallery/index.ts.
-- Aucune policy UPDATE : un invité ne modifie jamais un fichier déjà déposé.
-- Aucune policy DELETE publique : la suppression des fichiers (job
-- d'expiration J+7) passe par l'Edge Function service_role `expire-events`,
-- hors RLS — cf. supabase/functions/expire-events/index.ts et
-- 20260916090600_expire_events_job.sql.