Base de Données
Vue d'ensemble
La base de données utilise PostgreSQL via Supabase avec Row Level Security (RLS).
Schéma
Tables
product
Représente un événement/collection de dédicaces.
CREATE TABLE product (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
created_by UUID REFERENCES auth.users(id),
date_event DATE,
plan TEXT DEFAULT NULL,
session_id TEXT,
close BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
| Colonne | Type | Description |
|---|---|---|
id | BIGSERIAL | Identifiant unique |
name | TEXT | Nom de l'événement |
description | TEXT | Description/introduction |
created_by | UUID | ID utilisateur créateur |
date_event | DATE | Date de l'événement |
plan | TEXT | Plan souscrit (null, "basic") |
session_id | TEXT | ID session Stripe |
close | BOOLEAN | Événement fermé aux contributions |
created_at | TIMESTAMP | Date de création |
product_items
Représente un message/dédicace.
CREATE TABLE product_items (
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
product_id BIGINT REFERENCES product(id) ON DELETE CASCADE,
participant_id TEXT,
participant_name TEXT,
message_media TEXT,
message_type message_type_enum DEFAULT 'TEXT',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TYPE message_type_enum AS ENUM ('TEXT', 'VIDEO');
| Colonne | Type | Description |
|---|---|---|
id | TEXT | Identifiant unique (UUID) |
product_id | BIGINT | ID de l'événement |
participant_id | TEXT | ID du participant (UUID ou email) |
participant_name | TEXT | Nom affiché du participant |
message_media | TEXT | Contenu (HTML texte ou chemin vidéo) |
message_type | ENUM | Type : TEXT ou VIDEO |
created_at | TIMESTAMP | Date de création |
updated_at | TIMESTAMP | Date de modification |
Index
-- Recherche par événement
CREATE INDEX idx_product_items_product_id ON product_items(product_id);
-- Recherche par participant
CREATE INDEX idx_product_items_participant_id ON product_items(participant_id);
-- Événements par créateur
CREATE INDEX idx_product_created_by ON product(created_by);
-- Événements par date
CREATE INDEX idx_product_date_event ON product(date_event);
Row Level Security (RLS)
Activer RLS
ALTER TABLE product ENABLE ROW LEVEL SECURITY;
ALTER TABLE product_items ENABLE ROW LEVEL SECURITY;
Politiques sur product
-- SELECT : propriétaire voit ses événements
CREATE POLICY "product_select_owner" ON product
FOR SELECT TO authenticated
USING (created_by = auth.uid());
-- INSERT : utilisateurs authentifiés
CREATE POLICY "product_insert_auth" ON product
FOR INSERT TO authenticated
WITH CHECK (created_by = auth.uid());
-- UPDATE : propriétaire uniquement
CREATE POLICY "product_update_owner" ON product
FOR UPDATE TO authenticated
USING (created_by = auth.uid())
WITH CHECK (created_by = auth.uid());
-- DELETE : propriétaire uniquement
CREATE POLICY "product_delete_owner" ON product
FOR DELETE TO authenticated
USING (created_by = auth.uid());
Politiques sur product_items
-- SELECT : tout le monde (pour participants anonymes)
CREATE POLICY "product_items_select_all" ON product_items
FOR SELECT TO anon, authenticated
USING (true);
-- INSERT : tout le monde (messages anonymes)
CREATE POLICY "product_items_insert_all" ON product_items
FOR INSERT TO anon, authenticated
WITH CHECK (true);
-- UPDATE : participant ou propriétaire événement
CREATE POLICY "product_items_update" ON product_items
FOR UPDATE TO authenticated
USING (
participant_id = auth.uid()::text
OR EXISTS (
SELECT 1 FROM product
WHERE product.id = product_items.product_id
AND product.created_by = auth.uid()
)
);
-- DELETE : propriétaire événement uniquement
CREATE POLICY "product_items_delete_owner" ON product_items
FOR DELETE TO authenticated
USING (
EXISTS (
SELECT 1 FROM product
WHERE product.id = product_items.product_id
AND product.created_by = auth.uid()
)
);
Requêtes Courantes
Récupérer les événements d'un utilisateur
const { data, error } = await supabase
.from('product')
.select('*')
.eq('created_by', userId)
.order('created_at', { ascending: false });
Récupérer un événement avec ses messages
const { data, error } = await supabase
.from('product')
.select(`
*,
product_items (
id,
participant_name,
message_media,
message_type,
created_at
)
`)
.eq('id', productId)
.single();
Créer un message texte
const { data, error } = await supabase
.from('product_items')
.insert({
product_id: productId,
participant_id: participantId,
participant_name: name,
message_media: htmlContent,
message_type: 'TEXT'
})
.select()
.single();
Créer un message vidéo
// 1. Upload vidéo
const videoPath = `video_${productId}/dedicaces_${participantId}.webm`;
await supabase.storage
.from('video_dedicace')
.upload(videoPath, videoFile);
// 2. Créer entrée
const { data, error } = await supabase
.from('product_items')
.insert({
product_id: productId,
participant_id: participantId,
participant_name: name,
message_media: videoPath,
message_type: 'VIDEO'
});
Compter les messages par événement
const { data, error } = await supabase
.from('product_items')
.select('product_id', { count: 'exact' })
.eq('product_id', productId);
const count = data?.length || 0;
Mettre à jour après paiement
const { error } = await supabase
.from('product')
.update({
plan: 'basic',
session_id: stripeSessionId
})
.eq('id', productId);
Migrations
Exemple de Migration
-- 20240301000000_add_close_column.sql
-- Ajouter colonne close
ALTER TABLE product ADD COLUMN IF NOT EXISTS close BOOLEAN DEFAULT FALSE;
-- Index pour filtrer les événements ouverts
CREATE INDEX IF NOT EXISTS idx_product_close ON product(close);
-- Mettre à jour les anciennes entrées
UPDATE product SET close = FALSE WHERE close IS NULL;
Commandes
# Générer une migration depuis les changements
npx supabase db diff --schema public -f nom_migration
# Appliquer en local
npx supabase db push
# Appliquer en production
npx supabase db push --db-url="postgres://user:pass@host:5432/postgres"
Types TypeScript
Générer les types depuis le schéma :
npx supabase gen types typescript --local > src/types/database.ts
Résultat :
export interface Database {
public: {
Tables: {
product: {
Row: {
id: number;
name: string;
description: string | null;
created_by: string;
date_event: string | null;
plan: string | null;
session_id: string | null;
close: boolean;
created_at: string;
};
Insert: {
name: string;
description?: string;
created_by: string;
date_event?: string;
};
Update: {
name?: string;
description?: string;
plan?: string;
session_id?: string;
close?: boolean;
};
};
product_items: {
Row: {
id: string;
product_id: number;
participant_id: string | null;
participant_name: string | null;
message_media: string | null;
message_type: 'TEXT' | 'VIDEO';
created_at: string;
updated_at: string;
};
Insert: {
product_id: number;
participant_name?: string;
message_media?: string;
message_type?: 'TEXT' | 'VIDEO';
};
Update: {
participant_name?: string;
message_media?: string;
};
};
};
};
}