Files
photobooth-qr/supabase/migrations/20260916090100_create_events_table.sql
KevinLecou 9b1461978b 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).
2026-09-16 11:32:31 +02:00

78 lines
3.9 KiB
PL/PgSQL

-- Migration : création de la table `photobooth.events`
-- Réf. : docs/modele-donnees.md (section "Table `events`"),
-- docs/decisions.md (expiration J+7, admin_token dans l'URL)
--
-- Table déplacée de `public.pq_events` (2026-09-15) vers `photobooth.events`
-- (2026-09-16) : l'isolation multi-projets vient désormais du schéma dédié
-- `photobooth` (jamais exposé via PostgREST, cf.
-- 20260916090000_create_photobooth_schema.sql), plus besoin de préfixe de
-- table.
--
-- ⚠️ Ne pas appliquer depuis cet agent (Kévin applique lui-même).
-- pgcrypto est nécessaire pour gen_random_bytes() (génération de l'admin_token).
create extension if not exists pgcrypto with schema extensions;
create table if not exists photobooth.events (
id uuid primary key default gen_random_uuid(),
slug text not null unique,
nom text not null,
date_evenement date not null,
created_at timestamptz not null default now(),
-- expire_at est calculé automatiquement (trigger ci-dessous) à partir de
-- date_evenement + 7 jours, conformément à docs/decisions.md.
expire_at timestamptz,
-- Statut modélisé en texte + CHECK (plutôt qu'un type ENUM Postgres) car
-- modele-donnees.md indique explicitement "à affiner" : un CHECK est plus
-- simple à faire évoluer par migration qu'un type ENUM.
statut text not null default 'actif'
check (statut in ('actif', 'expire', 'archive')),
-- Token secret d'accès admin. 32 octets aléatoires (256 bits) encodés en
-- hexadécimal (pas de caractères spéciaux, utilisable tel quel dans une URL).
admin_token text not null unique
default encode(extensions.gen_random_bytes(32), 'hex'),
client_nom text,
facture_montant numeric check (facture_montant is null or facture_montant >= 0)
);
comment on table photobooth.events is
'Un événement (mariage, soirée, etc.) : point d''entrée multi-tenant du service photobooth-qr. Schéma photobooth jamais exposé via PostgREST, cf. 20260916090000_create_photobooth_schema.sql.';
comment on column photobooth.events.slug is
'Identifiant lisible utilisé dans l''URL publique d''upload (/e/<slug>).';
comment on column photobooth.events.expire_at is
'Calculé automatiquement = date_evenement + 7 jours (trigger trg_events_set_expire_at). RGPD : suppression à cette échéance, cf. docs/rgpd.md.';
comment on column photobooth.events.statut is
'Statut de l''événement : actif, expire, archive.';
comment on column photobooth.events.admin_token is
'Token secret donnant accès à la galerie admin (/admin/<slug>?token=...). Jamais exposé à anon (seule la fonction public.get_event_for_admin, réservée à service_role, le renvoie).';
comment on column photobooth.events.client_nom is
'Nom du client, utile en V2/V3 (vente du service).';
comment on column photobooth.events.facture_montant is
'Montant facturé au client, utile en V2/V3.';
-- Calcule systématiquement expire_at à partir de date_evenement, pour
-- garantir que la règle "J+7 après la date de l'événement" (docs/decisions.md)
-- est toujours respectée, indépendamment de ce que l'appelant tente d'insérer.
-- Fonction interne au schéma photobooth (pas un point de contrat public,
-- pas besoin d'être dans public).
create or replace function photobooth.events_set_expire_at()
returns trigger
language plpgsql
as $$
begin
new.expire_at := new.date_evenement + interval '7 days';
return new;
end;
$$;
drop trigger if exists trg_events_set_expire_at on photobooth.events;
create trigger trg_events_set_expire_at
before insert or update of date_evenement on photobooth.events
for each row
execute function photobooth.events_set_expire_at();
-- Index utile pour le futur job d'expiration (recherche des événements
-- actifs dont expire_at est dépassé).
create index if not exists idx_events_statut_expire_at
on photobooth.events (statut, expire_at);