Aller au contenu principal

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()
);
ColonneTypeDescription
idBIGSERIALIdentifiant unique
nameTEXTNom de l'événement
descriptionTEXTDescription/introduction
created_byUUIDID utilisateur créateur
date_eventDATEDate de l'événement
planTEXTPlan souscrit (null, "basic")
session_idTEXTID session Stripe
closeBOOLEANÉvénement fermé aux contributions
created_atTIMESTAMPDate 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');
ColonneTypeDescription
idTEXTIdentifiant unique (UUID)
product_idBIGINTID de l'événement
participant_idTEXTID du participant (UUID ou email)
participant_nameTEXTNom affiché du participant
message_mediaTEXTContenu (HTML texte ou chemin vidéo)
message_typeENUMType : TEXT ou VIDEO
created_atTIMESTAMPDate de création
updated_atTIMESTAMPDate 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;
};
};
};
};
}