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,77 @@
|
||||
-- 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);
|
||||
Reference in New Issue
Block a user