2764 lines
114 KiB
PL/PgSQL
2764 lines
114 KiB
PL/PgSQL
CREATE TYPE public.service_type AS ENUM ('TER', 'TGV', 'Intercités', 'Fret');
|
|
CREATE TYPE public.station_type AS ENUM ('interurbaine', 'ville');
|
|
CREATE TYPE public.transport_type AS ENUM ('bus', 'tram', 'metro', 'tram-train', 'train');
|
|
-- Script SQL pour Supabase — Base app_ferrovia (exécuter dans SQL editor Supabase)
|
|
|
|
-- Extensions requises
|
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
|
|
-- Table des utilisateurs
|
|
CREATE TABLE IF NOT EXISTS public.utilisateurs (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email text NOT NULL UNIQUE,
|
|
nom text,
|
|
prenom text,
|
|
password_hash text NOT NULL,
|
|
role text NOT NULL DEFAULT 'user',
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS utilisateurs_email_lower_idx ON public.utilisateurs ((lower(email)));
|
|
|
|
-- Table d'audit
|
|
CREATE TABLE IF NOT EXISTS public.audit_logs (
|
|
id bigserial PRIMARY KEY,
|
|
table_name text NOT NULL,
|
|
operation text NOT NULL,
|
|
record_id uuid,
|
|
changed_by uuid,
|
|
old_data jsonb,
|
|
new_data jsonb,
|
|
changed_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Trigger pour updated_at
|
|
CREATE OR REPLACE FUNCTION public.set_updated_at()
|
|
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
NEW.updated_at = now();
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS tr_utilisateurs_set_updated_at ON public.utilisateurs;
|
|
CREATE TRIGGER tr_utilisateurs_set_updated_at
|
|
BEFORE UPDATE ON public.utilisateurs
|
|
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
|
|
-- Fonction d'audit
|
|
CREATE OR REPLACE FUNCTION public.log_audit()
|
|
RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER AS $$
|
|
DECLARE
|
|
v_changed_by_text text;
|
|
v_changed_by uuid;
|
|
BEGIN
|
|
v_changed_by_text := current_setting('jwt.claims.user_id', true);
|
|
IF v_changed_by_text IS NOT NULL AND v_changed_by_text <> '' THEN
|
|
BEGIN
|
|
v_changed_by := v_changed_by_text::uuid;
|
|
EXCEPTION WHEN others THEN
|
|
v_changed_by := NULL;
|
|
END;
|
|
ELSE
|
|
v_changed_by := NULL;
|
|
END IF;
|
|
|
|
IF (TG_OP = 'INSERT') THEN
|
|
INSERT INTO public.audit_logs(table_name, operation, record_id, changed_by, new_data, changed_at)
|
|
VALUES (TG_TABLE_NAME, 'INSERT', NEW.id, v_changed_by, row_to_json(NEW)::jsonb, now());
|
|
RETURN NEW;
|
|
ELSIF (TG_OP = 'UPDATE') THEN
|
|
INSERT INTO public.audit_logs(table_name, operation, record_id, changed_by, old_data, new_data, changed_at)
|
|
VALUES (TG_TABLE_NAME, 'UPDATE', NEW.id, v_changed_by, row_to_json(OLD)::jsonb, row_to_json(NEW)::jsonb, now());
|
|
RETURN NEW;
|
|
ELSIF (TG_OP = 'DELETE') THEN
|
|
INSERT INTO public.audit_logs(table_name, operation, record_id, changed_by, old_data, changed_at)
|
|
VALUES (TG_TABLE_NAME, 'DELETE', OLD.id, v_changed_by, row_to_json(OLD)::jsonb, now());
|
|
RETURN OLD;
|
|
END IF;
|
|
RETURN NULL;
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS tr_utilisateurs_audit ON public.utilisateurs;
|
|
CREATE TRIGGER tr_utilisateurs_audit
|
|
AFTER INSERT OR UPDATE OR DELETE ON public.utilisateurs
|
|
FOR EACH ROW EXECUTE FUNCTION public.log_audit();
|
|
|
|
-- Fonction RPC d'authentification
|
|
CREATE OR REPLACE FUNCTION public.authenticate_user(p_email text, p_password text)
|
|
RETURNS TABLE(id uuid, email text, nom text, prenom text, name text, role text) LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$
|
|
DECLARE
|
|
has_nom boolean;
|
|
has_prenom boolean;
|
|
has_fullname boolean;
|
|
has_deleted_at boolean;
|
|
sql text;
|
|
BEGIN
|
|
SELECT EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='utilisateurs' AND column_name='nom'),
|
|
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='utilisateurs' AND column_name='prenom'),
|
|
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='utilisateurs' AND column_name='full_name'),
|
|
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='utilisateurs' AND column_name='deleted_at')
|
|
INTO has_nom, has_prenom, has_fullname, has_deleted_at;
|
|
|
|
IF has_nom AND has_prenom THEN
|
|
sql := 'SELECT u.id::uuid, u.email::text, u.nom::text, u.prenom::text, (COALESCE(u.nom,'''') || '' '' || COALESCE(u.prenom, ''''))::text as name, u.role::text '
|
|
|| 'FROM public.utilisateurs u '
|
|
|| 'WHERE lower(u.email)=lower($1) AND u.password_hash = crypt($2, u.password_hash)';
|
|
ELSIF has_fullname THEN
|
|
sql := 'SELECT u.id::uuid, u.email::text, NULL::text as nom, NULL::text as prenom, u.full_name::text as name, u.role::text '
|
|
|| 'FROM public.utilisateurs u '
|
|
|| 'WHERE lower(u.email)=lower($1) AND u.password_hash = crypt($2, u.password_hash)';
|
|
ELSE
|
|
RAISE EXCEPTION 'Table public.utilisateurs : colonnes nom/prenom ou full_name introuvables';
|
|
END IF;
|
|
|
|
IF has_deleted_at THEN
|
|
sql := sql || ' AND u.deleted_at IS NULL';
|
|
END IF;
|
|
|
|
RETURN QUERY EXECUTE sql USING p_email, p_password;
|
|
END;
|
|
$$;
|
|
|
|
-- Grants (à adapter selon politique RLS)
|
|
GRANT EXECUTE ON FUNCTION public.authenticate_user(text, text) TO anon;
|
|
GRANT SELECT ON public.utilisateurs TO anon;
|
|
|
|
-- Insérer un utilisateur de test (admin)
|
|
-- Utiliser nom/prenom pour compatibilité avec le schéma principal
|
|
INSERT INTO public.utilisateurs (email, nom, prenom, password_hash, role)
|
|
VALUES (
|
|
'admin@example.com',
|
|
'Admin',
|
|
'Example',
|
|
crypt('password', gen_salt('bf')),
|
|
'admin'
|
|
) ON CONFLICT (email) DO UPDATE
|
|
SET password_hash = EXCLUDED.password_hash,
|
|
nom = EXCLUDED.nom,
|
|
prenom = EXCLUDED.prenom,
|
|
role = EXCLUDED.role,
|
|
updated_at = now();
|
|
|
|
-- =============================================
|
|
-- TABLES EQUIVALENTES POUR 'GARES' (doublon pour compatibilité)
|
|
-- =============================================
|
|
|
|
-- Table principale des gares (nommage français)
|
|
CREATE TABLE IF NOT EXISTS public.gares (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
nom text NOT NULL,
|
|
type station_type NOT NULL DEFAULT 'ville',
|
|
services service_type[] NOT NULL DEFAULT '{}',
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
|
|
-- Table des quais pour gares
|
|
CREATE TABLE IF NOT EXISTS public.gare_quais (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
gare_id uuid NOT NULL REFERENCES public.gares(id) ON DELETE CASCADE,
|
|
nom text NOT NULL,
|
|
distance_metres integer NOT NULL DEFAULT 0,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Table des transports pour gares
|
|
CREATE TABLE IF NOT EXISTS public.gare_transports (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
gare_id uuid NOT NULL REFERENCES public.gares(id) ON DELETE CASCADE,
|
|
type transport_type NOT NULL,
|
|
ligne text,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Index pour les recherches
|
|
CREATE INDEX IF NOT EXISTS idx_gare_quais_gare_id ON public.gare_quais(gare_id);
|
|
CREATE INDEX IF NOT EXISTS idx_gare_transports_gare_id ON public.gare_transports(gare_id);
|
|
|
|
-- Trigger pour updated_at sur gares
|
|
DROP TRIGGER IF EXISTS tr_gares_set_updated_at ON public.gares;
|
|
CREATE TRIGGER tr_gares_set_updated_at
|
|
BEFORE UPDATE ON public.gares
|
|
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
|
|
-- Trigger d'audit pour gares
|
|
DROP TRIGGER IF EXISTS tr_gares_audit ON public.gares;
|
|
CREATE TRIGGER tr_gares_audit
|
|
AFTER INSERT OR UPDATE OR DELETE ON public.gares
|
|
FOR EACH ROW EXECUTE FUNCTION public.log_audit();
|
|
|
|
-- Grants pour les tables gares
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON public.gares TO anon;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON public.gare_quais TO anon;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON public.gare_transports TO anon;
|
|
|
|
-- Fonction RPC pour récupérer une gare (gare) avec ses quais et transports
|
|
CREATE OR REPLACE FUNCTION public.get_gare_details(p_gare_id uuid)
|
|
RETURNS TABLE(
|
|
id uuid,
|
|
nom text,
|
|
type station_type,
|
|
services service_type[],
|
|
quais jsonb,
|
|
transports jsonb,
|
|
created_at timestamptz,
|
|
updated_at timestamptz
|
|
) LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
g.id,
|
|
g.nom,
|
|
g.type,
|
|
g.services,
|
|
COALESCE(
|
|
(SELECT jsonb_agg(jsonb_build_object('id', q.id, 'nom', q.nom, 'distance_metres', q.distance_metres))
|
|
FROM public.gare_quais q WHERE q.gare_id = g.id),
|
|
'[]'::jsonb
|
|
) as quais,
|
|
COALESCE(
|
|
(SELECT jsonb_agg(jsonb_build_object('id', t.id, 'type', t.type, 'ligne', t.ligne))
|
|
FROM public.gare_transports t WHERE t.gare_id = g.id),
|
|
'[]'::jsonb
|
|
) as transports,
|
|
g.created_at,
|
|
g.updated_at
|
|
FROM public.gares g
|
|
WHERE g.id = p_gare_id AND g.deleted_at IS NULL;
|
|
END;
|
|
$$;
|
|
|
|
GRANT EXECUTE ON FUNCTION public.get_gare_details(uuid) TO anon;
|
|
|
|
|
|
-- ============================================
|
|
-- TABLE DES MATERIELS ROULANTS
|
|
-- ============================================
|
|
create table if not exists "materiel-roulant" (
|
|
id bigint generated always as identity primary key,
|
|
nom text,
|
|
nom_technique text,
|
|
capacite integer,
|
|
image_url text,
|
|
type_train text,
|
|
numero_serie text unique,
|
|
created_at timestamptz default now(),
|
|
updated_at timestamptz
|
|
);
|
|
-- ============================================================================
|
|
-- TABLE : utilisateurs
|
|
-- Description : Gestion des utilisateurs de l'application
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS utilisateurs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email VARCHAR(255) NOT NULL UNIQUE,
|
|
nom VARCHAR(100) NOT NULL,
|
|
prenom VARCHAR(100) NOT NULL,
|
|
role VARCHAR(50) NOT NULL DEFAULT 'utilisateur',
|
|
actif BOOLEAN NOT NULL DEFAULT true,
|
|
-- Champs additionnels
|
|
password_hash VARCHAR(255),
|
|
photo_url TEXT,
|
|
region_admin BOOLEAN DEFAULT false,
|
|
region_nom VARCHAR(255),
|
|
permissions JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Si la table existe déjà, ajouter les colonnes manquantes (migration sûre)
|
|
ALTER TABLE utilisateurs ADD COLUMN IF NOT EXISTS password_hash VARCHAR(255);
|
|
ALTER TABLE utilisateurs ADD COLUMN IF NOT EXISTS photo_url TEXT;
|
|
ALTER TABLE utilisateurs ADD COLUMN IF NOT EXISTS region_admin BOOLEAN DEFAULT false;
|
|
ALTER TABLE utilisateurs ADD COLUMN IF NOT EXISTS region_nom VARCHAR(255);
|
|
ALTER TABLE utilisateurs ADD COLUMN IF NOT EXISTS permissions JSONB NOT NULL DEFAULT '{}'::jsonb;
|
|
ALTER TABLE utilisateurs ADD COLUMN IF NOT EXISTS actif BOOLEAN NOT NULL DEFAULT true;
|
|
|
|
-- Index sur l'email pour optimiser les recherches
|
|
CREATE INDEX IF NOT EXISTS idx_utilisateurs_email ON utilisateurs(email);
|
|
|
|
-- Index sur le rôle
|
|
CREATE INDEX IF NOT EXISTS idx_utilisateurs_role ON utilisateurs(role);
|
|
|
|
-- Index sur le statut actif
|
|
CREATE INDEX IF NOT EXISTS idx_utilisateurs_actif ON utilisateurs(actif);
|
|
|
|
-- Commentaires
|
|
COMMENT ON TABLE utilisateurs IS 'Utilisateurs de l''application avec leurs informations et rôles';
|
|
COMMENT ON COLUMN utilisateurs.id IS 'Identifiant unique de l''utilisateur';
|
|
COMMENT ON COLUMN utilisateurs.email IS 'Adresse email de l''utilisateur (unique)';
|
|
COMMENT ON COLUMN utilisateurs.nom IS 'Nom de famille de l''utilisateur';
|
|
COMMENT ON COLUMN utilisateurs.prenom IS 'Prénom de l''utilisateur';
|
|
COMMENT ON COLUMN utilisateurs.role IS 'Rôle de l''utilisateur (admin, gestionnaire, utilisateur)';
|
|
COMMENT ON COLUMN utilisateurs.actif IS 'Indique si l''utilisateur est actif ou désactivé';
|
|
COMMENT ON COLUMN utilisateurs.created_at IS 'Date de création de l''utilisateur';
|
|
COMMENT ON COLUMN utilisateurs.updated_at IS 'Date de dernière modification de l''utilisateur';
|
|
COMMENT ON COLUMN utilisateurs.password_hash IS 'Hash du mot de passe (ne pas stocker en clair)';
|
|
COMMENT ON COLUMN utilisateurs.photo_url IS 'URL de la photo de profil';
|
|
COMMENT ON COLUMN utilisateurs.region_admin IS 'Indique si l''utilisateur administre une région';
|
|
COMMENT ON COLUMN utilisateurs.region_nom IS 'Nom de la région administrée (si applicable)';
|
|
COMMENT ON COLUMN utilisateurs.permissions IS 'Permissions par fonctionnalite stockees en JSONB';
|
|
|
|
-- Fonction pour mettre à jour automatiquement updated_at
|
|
CREATE OR REPLACE FUNCTION update_utilisateurs_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Trigger pour updated_at
|
|
DROP TRIGGER IF EXISTS trigger_update_utilisateurs_updated_at ON utilisateurs;
|
|
CREATE TRIGGER trigger_update_utilisateurs_updated_at
|
|
BEFORE UPDATE ON utilisateurs
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_utilisateurs_updated_at();
|
|
|
|
-- =====================================================
|
|
-- Fonction RPC d'authentification utilisant la table utilisateurs
|
|
-- Cette fonction vérifie le mot de passe via pgcrypto/crypt
|
|
-- Retourne id, email, nom, prenom, role si succès
|
|
-- =====================================================
|
|
|
|
-- S'assurer que pgcrypto est disponible
|
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
|
|
CREATE OR REPLACE FUNCTION public.authenticate_user(p_email text, p_password text)
|
|
RETURNS TABLE(id uuid, email text, nom text, prenom text, name text, role text) LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$
|
|
DECLARE
|
|
has_deleted_at boolean;
|
|
sql text;
|
|
BEGIN
|
|
SELECT EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='utilisateurs' AND column_name='deleted_at')
|
|
INTO has_deleted_at;
|
|
|
|
sql := 'SELECT u.id::uuid, u.email::text, u.nom::text, u.prenom::text, (COALESCE(u.nom, '''') || '' '' || COALESCE(u.prenom, ''''))::text as name, u.role::text '
|
|
|| 'FROM public.utilisateurs u '
|
|
|| 'WHERE lower(u.email) = lower($1) '
|
|
|| 'AND u.password_hash IS NOT NULL '
|
|
|| 'AND u.password_hash = crypt($2, u.password_hash) '
|
|
|| 'AND coalesce(u.actif, true) = true';
|
|
|
|
IF has_deleted_at THEN
|
|
sql := sql || ' AND u.deleted_at IS NULL';
|
|
END IF;
|
|
|
|
RETURN QUERY EXECUTE sql USING p_email, p_password;
|
|
END;
|
|
$$;
|
|
|
|
-- Grants pour permettre l'appel depuis le client (adapter selon politiques RLS)
|
|
GRANT EXECUTE ON FUNCTION public.authenticate_user(text, text) TO anon;
|
|
GRANT SELECT ON public.utilisateurs TO anon;
|
|
|
|
-- Inserer un utilisateur admin de test (mot de passe: 'password')
|
|
INSERT INTO utilisateurs (email, nom, prenom, password_hash, role, actif)
|
|
VALUES (
|
|
'admin@example.com',
|
|
'Admin',
|
|
'Example',
|
|
crypt('password', gen_salt('bf')),
|
|
'admin',
|
|
true
|
|
)
|
|
ON CONFLICT (email) DO UPDATE
|
|
SET password_hash = EXCLUDED.password_hash,
|
|
nom = EXCLUDED.nom,
|
|
prenom = EXCLUDED.prenom,
|
|
role = EXCLUDED.role,
|
|
actif = EXCLUDED.actif,
|
|
updated_at = NOW();
|
|
|
|
-- FIN du fichier
|
|
-- ============================================================================
|
|
-- TABLE: types_train
|
|
-- Description: Stocke les types de train configurables (TER, TGV, etc.)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS types_train (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
nom VARCHAR(255) NOT NULL,
|
|
code VARCHAR(50),
|
|
description TEXT,
|
|
logo_url TEXT,
|
|
couleur VARCHAR(20) DEFAULT '#0074d9',
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
deleted_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Index
|
|
CREATE INDEX IF NOT EXISTS idx_types_train_deleted_at ON types_train(deleted_at);
|
|
CREATE INDEX IF NOT EXISTS idx_types_train_code ON types_train(code);
|
|
|
|
-- ============================================================================
|
|
-- MODIFICATION TABLE: horaires
|
|
-- Ajout de la colonne ligne et modification de type_train_id
|
|
-- ============================================================================
|
|
|
|
-- -- Ajouter colonne ligne si elle n'existe pas
|
|
-- DO $$
|
|
-- BEGIN
|
|
-- IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='horaires') AND NOT EXISTS (
|
|
-- SELECT 1 FROM information_schema.columns
|
|
-- WHERE table_name='horaires' AND column_name='ligne'
|
|
-- ) THEN
|
|
-- ALTER TABLE horaires ADD COLUMN ligne VARCHAR(255);
|
|
-- END IF;
|
|
-- END $$;
|
|
--
|
|
-- -- Ajouter colonne type_train_id si elle n'existe pas (relation vers types_train)
|
|
-- DO $$
|
|
-- BEGIN
|
|
-- IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='horaires') AND NOT EXISTS (
|
|
-- SELECT 1 FROM information_schema.columns
|
|
-- WHERE table_name='horaires' AND column_name='type_train_id'
|
|
-- ) THEN
|
|
-- ALTER TABLE horaires ADD COLUMN type_train_id BIGINT REFERENCES types_train(id);
|
|
-- END IF;
|
|
-- END $$;
|
|
--
|
|
-- -- Index
|
|
-- CREATE INDEX IF NOT EXISTS idx_horaires_ligne ON horaires(ligne);
|
|
-- CREATE INDEX IF NOT EXISTS idx_horaires_type_train_id ON horaires(type_train_id);
|
|
|
|
-- ============================================================================
|
|
-- DONNÉES INITIALES: types_train
|
|
-- Pré-remplir avec des types de train classiques
|
|
-- ============================================================================
|
|
|
|
INSERT INTO types_train (nom, code, description, couleur) VALUES
|
|
('TER', 'TER', 'Transport Express Régional', '#0088ce'),
|
|
('TGV', 'TGV', 'Train à Grande Vitesse', '#d2232a'),
|
|
('Intercités', 'IC', 'Trains Intercités', '#00a88f'),
|
|
('TGV INOUI', 'TGVI', 'TGV INOUI', '#d2232a'),
|
|
('OUIGO', 'OUI', 'OUIGO', '#ed008c'),
|
|
('Transilien', 'TRANS', 'Trains Transilien Île-de-France', '#8d5da8')
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- ============================================================================
|
|
-- COMMENTAIRES
|
|
-- ============================================================================
|
|
|
|
COMMENT ON TABLE types_train IS 'Types de train configurables avec logos et couleurs';
|
|
-- COMMENT ON COLUMN horaires.ligne IS 'Ligne de train (ex: Paris-Lyon, ligne N, etc.)';
|
|
-- COMMENT ON COLUMN horaires.type_train_id IS 'Référence vers le type de train configuré';
|
|
-- ============================================================================
|
|
-- TABLES POUR LES LIGNES ET SERVICES ANNUELS
|
|
-- ============================================================================
|
|
|
|
-- Table des lignes
|
|
CREATE TABLE IF NOT EXISTS lignes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
nom VARCHAR(255) NOT NULL,
|
|
code VARCHAR(50),
|
|
gare_depart_id UUID REFERENCES gares(id) ON DELETE SET NULL,
|
|
gare_arrivee_id UUID REFERENCES gares(id) ON DELETE SET NULL,
|
|
actif BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
deleted_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Table des gares sur le tracé d'une ligne
|
|
CREATE TABLE IF NOT EXISTS lignes_gares (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
ligne_id UUID REFERENCES lignes(id) ON DELETE CASCADE,
|
|
gare_id UUID REFERENCES gares(id) ON DELETE CASCADE,
|
|
ordre INTEGER NOT NULL,
|
|
desserte VARCHAR(20) NOT NULL CHECK (desserte IN ('tous', 'partielle')),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Table des services annuels
|
|
CREATE TABLE IF NOT EXISTS services_annuels (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
nom VARCHAR(255) NOT NULL,
|
|
code VARCHAR(50),
|
|
date_debut DATE NOT NULL,
|
|
date_fin DATE NOT NULL,
|
|
description TEXT,
|
|
actif BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
deleted_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
--
|
|
-- -- Ajouter la colonne service_annuel_id à la table horaires si elle n'existe pas
|
|
-- DO $$
|
|
-- BEGIN
|
|
-- IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='horaires') AND NOT EXISTS (
|
|
-- SELECT 1 FROM information_schema.columns
|
|
-- WHERE table_name = 'horaires' AND column_name = 'service_annuel_id'
|
|
-- ) THEN
|
|
-- ALTER TABLE horaires ADD COLUMN service_annuel_id UUID REFERENCES services_annuels(id) ON DELETE SET NULL;
|
|
-- END IF;
|
|
-- END $$;
|
|
--
|
|
-- -- Ajouter la colonne ligne_id à la table horaires si elle n'existe pas
|
|
-- DO $$
|
|
-- BEGIN
|
|
-- IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='horaires') AND NOT EXISTS (
|
|
-- SELECT 1 FROM information_schema.columns
|
|
-- WHERE table_name = 'horaires' AND column_name = 'ligne_id'
|
|
-- ) THEN
|
|
-- ALTER TABLE horaires ADD COLUMN ligne_id UUID REFERENCES lignes(id) ON DELETE SET NULL;
|
|
-- END IF;
|
|
-- END $$;
|
|
|
|
-- Index pour améliorer les performances
|
|
CREATE INDEX IF NOT EXISTS idx_lignes_actif ON lignes(actif) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_lignes_gares_ligne ON lignes_gares(ligne_id);
|
|
CREATE INDEX IF NOT EXISTS idx_lignes_gares_ordre ON lignes_gares(ligne_id, ordre);
|
|
CREATE INDEX IF NOT EXISTS idx_services_annuels_actif ON services_annuels(actif) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_services_annuels_dates ON services_annuels(date_debut, date_fin);
|
|
|
|
-- Trigger pour mettre à jour updated_at automatiquement
|
|
DROP TRIGGER IF EXISTS update_lignes_updated_at ON lignes;
|
|
CREATE TRIGGER update_lignes_updated_at
|
|
BEFORE UPDATE ON lignes
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
DROP TRIGGER IF EXISTS update_services_annuels_updated_at ON services_annuels;
|
|
CREATE TRIGGER update_services_annuels_updated_at
|
|
BEFORE UPDATE ON services_annuels
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- Commentaires sur les tables
|
|
COMMENT ON TABLE lignes IS 'Lignes de train avec gares de départ et d''arrivée';
|
|
COMMENT ON TABLE lignes_gares IS 'Gares présentes sur le tracé d''une ligne avec type de desserte';
|
|
COMMENT ON TABLE services_annuels IS 'Services annuels attribuables aux horaires';
|
|
-- ============================================================================
|
|
-- TABLES POUR LE SYSTÈME DE GESTION DES HORAIRES (SILLONS)
|
|
-- ============================================================================
|
|
|
|
-- Table principale des horaires (sillons)
|
|
CREATE TABLE IF NOT EXISTS horaires (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Informations générales
|
|
numero_train VARCHAR(20) NOT NULL,
|
|
type_train VARCHAR(50),
|
|
type_train_id BIGINT REFERENCES types_train(id) ON DELETE SET NULL,
|
|
|
|
-- Gare de départ
|
|
gare_depart_id UUID REFERENCES gares(id) ON DELETE SET NULL,
|
|
gare_depart_nom VARCHAR(255),
|
|
heure_depart TIME NOT NULL,
|
|
|
|
-- Gare d'arrivée (destination)
|
|
gare_arrivee_id UUID REFERENCES gares(id) ON DELETE SET NULL,
|
|
gare_arrivee_nom VARCHAR(255),
|
|
heure_arrivee TIME NOT NULL,
|
|
|
|
-- Jours de circulation (base)
|
|
circule_lundi BOOLEAN DEFAULT true,
|
|
circule_mardi BOOLEAN DEFAULT true,
|
|
circule_mercredi BOOLEAN DEFAULT true,
|
|
circule_jeudi BOOLEAN DEFAULT true,
|
|
circule_vendredi BOOLEAN DEFAULT true,
|
|
circule_samedi BOOLEAN DEFAULT true,
|
|
circule_dimanche BOOLEAN DEFAULT true,
|
|
|
|
-- Jours spéciaux
|
|
circule_jours_feries BOOLEAN DEFAULT false,
|
|
circule_dimanches_feries BOOLEAN DEFAULT false,
|
|
|
|
-- Jours personnalisés (dates spécifiques de circulation)
|
|
jours_personnalises JSONB DEFAULT '[]'::jsonb,
|
|
-- Format: [{"date": "2026-01-15", "circule": true}, ...]
|
|
|
|
-- Jours de non-circulation (dates où le train ne circule pas)
|
|
jours_non_circulation JSONB DEFAULT '[]'::jsonb,
|
|
-- Format: ["2026-01-20", "2026-02-14", ...]
|
|
|
|
-- Matériel roulant associé
|
|
materiel_roulant_id BIGINT REFERENCES "materiel-roulant"(id) ON DELETE SET NULL,
|
|
|
|
-- Lien vers la table des lignes (optionnel)
|
|
ligne_id UUID REFERENCES lignes(id) ON DELETE SET NULL,
|
|
ligne_nom VARCHAR(255),
|
|
|
|
-- Service Annuel associé (SA)
|
|
service_annuel_id UUID REFERENCES services_annuels(id) ON DELETE SET NULL,
|
|
service_annuel_nom VARCHAR(255),
|
|
|
|
-- Substitution (autocar)
|
|
est_substitution BOOLEAN DEFAULT false,
|
|
motif_substitution VARCHAR(255),
|
|
|
|
-- Gares desservies (snapshot JSON — copie dénormalisée pour lecture rapide)
|
|
gares_desservies JSONB DEFAULT '[]'::jsonb,
|
|
-- Format: [{"gare_id": "...", "gare_nom": "...", "heure_arrivee": "HH:MM", "heure_depart": "HH:MM", "ordre": 1}, ...]
|
|
|
|
-- Composition du train (liste ordonnée des matériels roulants)
|
|
composition_train JSONB DEFAULT '[]'::jsonb,
|
|
-- Format: [{"materiel_id": 1, "materiel_nom": "Régiolis", "materiel_nom_technique": "X73500", "materiel_image_url": "...", "position": 1}, ...]
|
|
|
|
-- Métadonnées
|
|
actif BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
deleted_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Table des gares desservies (arrêts intermédiaires)
|
|
CREATE TABLE IF NOT EXISTS horaires_gares_desservies (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
horaire_id UUID NOT NULL REFERENCES horaires(id) ON DELETE CASCADE,
|
|
|
|
-- Informations de la gare desservie
|
|
gare_id UUID REFERENCES gares(id) ON DELETE SET NULL,
|
|
gare_nom VARCHAR(255) NOT NULL,
|
|
|
|
-- Horaires à cette gare
|
|
heure_arrivee TIME,
|
|
heure_depart TIME,
|
|
|
|
-- Ordre dans le parcours (1 = première gare desservie après départ)
|
|
ordre INTEGER NOT NULL DEFAULT 0,
|
|
|
|
-- Métadonnées
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Index pour améliorer les performances
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_numero_train ON horaires(numero_train);
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_gare_depart ON horaires(gare_depart_id);
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_gare_arrivee ON horaires(gare_arrivee_id);
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_actif ON horaires(actif) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_type_train ON horaires(type_train_id);
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_materiel ON horaires(materiel_roulant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_ligne ON horaires(ligne_id);
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_service_annuel ON horaires(service_annuel_id);
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_substitution ON horaires(est_substitution);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_gares_desservies_horaire ON horaires_gares_desservies(horaire_id);
|
|
CREATE INDEX IF NOT EXISTS idx_gares_desservies_ordre ON horaires_gares_desservies(horaire_id, ordre);
|
|
|
|
-- Trigger pour mettre à jour updated_at automatiquement
|
|
DROP TRIGGER IF EXISTS update_horaires_updated_at ON horaires;
|
|
CREATE TRIGGER update_horaires_updated_at
|
|
BEFORE UPDATE ON horaires
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
DROP TRIGGER IF EXISTS update_horaires_gares_desservies_updated_at ON horaires_gares_desservies;
|
|
CREATE TRIGGER update_horaires_gares_desservies_updated_at
|
|
BEFORE UPDATE ON horaires_gares_desservies
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- Commentaires sur les tables
|
|
COMMENT ON TABLE horaires IS 'Table principale des horaires (sillons) de trains';
|
|
COMMENT ON TABLE horaires_gares_desservies IS 'Gares intermédiaires desservies par chaque horaire';
|
|
|
|
COMMENT ON COLUMN horaires.numero_train IS 'Numéro unique du train (ex: TER 12345)';
|
|
COMMENT ON COLUMN horaires.type_train IS 'Type de train (TER, TGV, Intercités, etc.)';
|
|
COMMENT ON COLUMN horaires.type_train_id IS 'Référence vers la table types_train pour le type de train';
|
|
COMMENT ON COLUMN horaires.jours_personnalises IS 'Dates spécifiques de circulation au format JSON';
|
|
COMMENT ON COLUMN horaires.jours_non_circulation IS 'Dates où le train ne circule pas au format JSON';
|
|
COMMENT ON COLUMN horaires.est_substitution IS 'Indique si ce sillon est une substitution autocar';
|
|
COMMENT ON COLUMN horaires.motif_substitution IS 'Motif de la substitution (travaux, etc.)';
|
|
COMMENT ON COLUMN horaires.ligne_id IS 'Identifiant de la ligne associée au sillon';
|
|
COMMENT ON COLUMN horaires.ligne_nom IS 'Nom de la ligne associée (copie pour lecture)';
|
|
COMMENT ON COLUMN horaires.service_annuel_id IS 'Identifiant du service annuel associé (SA)';
|
|
COMMENT ON COLUMN horaires.service_annuel_nom IS 'Nom du service annuel associé (copie pour lecture)';
|
|
|
|
COMMENT ON COLUMN horaires.gares_desservies IS 'Snapshot JSON des gares desservies (copie dénormalisée synchronisée avec horaires_gares_desservies)';
|
|
COMMENT ON COLUMN horaires.composition_train IS 'Composition ordonnée du train : liste JSON des matériels roulants affectés au sillon (position, id, nom, image)';
|
|
|
|
COMMENT ON COLUMN horaires_gares_desservies.ordre IS 'Position de la gare dans le parcours (1 = première après départ)';
|
|
|
|
-- ============================================================================
|
|
-- TABLES POUR LE SYSTÈME DE GESTION DES PERTURBATIONS
|
|
-- ============================================================================
|
|
|
|
-- Table principale des perturbations
|
|
CREATE TABLE IF NOT EXISTS perturbations (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Référence à l'horaire perturbé
|
|
horaire_id UUID NOT NULL REFERENCES horaires(id) ON DELETE CASCADE,
|
|
|
|
-- Type de perturbation
|
|
type_perturbation VARCHAR(50) NOT NULL CHECK (type_perturbation IN ('retard', 'suppression', 'modification_parcours')),
|
|
|
|
-- Informations sur le retard (si applicable)
|
|
duree_retard_minutes INTEGER, -- Durée du retard en minutes
|
|
|
|
-- Cause de la perturbation
|
|
cause VARCHAR(100) NOT NULL CHECK (cause IN (
|
|
'incident_technique',
|
|
'accident_personne',
|
|
'conditions_meteorologiques',
|
|
'travaux',
|
|
'mouvement_social',
|
|
'intervention_police',
|
|
'affluence',
|
|
'malveillance',
|
|
'autre'
|
|
)),
|
|
cause_details TEXT, -- Détails supplémentaires sur la cause
|
|
|
|
-- Sillon de substitution (si suppression ou modification de parcours)
|
|
sillon_substitution_id UUID REFERENCES horaires(id) ON DELETE SET NULL,
|
|
|
|
-- Période de validité de la perturbation
|
|
date_debut TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
date_fin TIMESTAMP WITH TIME ZONE,
|
|
|
|
-- Statut
|
|
actif BOOLEAN DEFAULT true,
|
|
|
|
-- Notes internes
|
|
notes TEXT,
|
|
|
|
-- Snapshot complet des perturbations par arrêt (état du formulaire)
|
|
-- Permet de restaurer exactement le formulaire lors de la modification
|
|
perturbations_arrets JSONB DEFAULT NULL,
|
|
|
|
-- Métadonnées
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
deleted_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Table des modifications de parcours (gares supprimées/ajoutées)
|
|
CREATE TABLE IF NOT EXISTS perturbations_modifications_parcours (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
perturbation_id UUID NOT NULL REFERENCES perturbations(id) ON DELETE CASCADE,
|
|
|
|
-- Type de modification
|
|
type_modification VARCHAR(20) NOT NULL CHECK (type_modification IN ('gare_supprimee', 'gare_ajoutee', 'horaire_modifie')),
|
|
|
|
-- Gare concernée
|
|
gare_id UUID REFERENCES gares(id) ON DELETE SET NULL,
|
|
gare_nom VARCHAR(255) NOT NULL,
|
|
|
|
-- Nouvel horaire si modification
|
|
nouvelle_heure_arrivee TIME,
|
|
nouvelle_heure_depart TIME,
|
|
|
|
-- Ordre dans le parcours (pour les gares ajoutées)
|
|
ordre INTEGER,
|
|
|
|
-- Métadonnées
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Index pour améliorer les performances
|
|
CREATE INDEX IF NOT EXISTS idx_perturbations_horaire ON perturbations(horaire_id);
|
|
CREATE INDEX IF NOT EXISTS idx_perturbations_type ON perturbations(type_perturbation);
|
|
CREATE INDEX IF NOT EXISTS idx_perturbations_actif ON perturbations(actif) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_perturbations_dates ON perturbations(date_debut, date_fin);
|
|
CREATE INDEX IF NOT EXISTS idx_perturbations_sillon_substitution ON perturbations(sillon_substitution_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_modif_parcours_perturbation ON perturbations_modifications_parcours(perturbation_id);
|
|
CREATE INDEX IF NOT EXISTS idx_modif_parcours_gare ON perturbations_modifications_parcours(gare_id);
|
|
|
|
-- Trigger pour mettre à jour updated_at automatiquement
|
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ language 'plpgsql';
|
|
|
|
DROP TRIGGER IF EXISTS update_perturbations_updated_at ON perturbations;
|
|
CREATE TRIGGER update_perturbations_updated_at
|
|
BEFORE UPDATE ON perturbations
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- Commentaires sur les tables
|
|
COMMENT ON TABLE perturbations IS 'Table des perturbations affectant les horaires de trains';
|
|
COMMENT ON TABLE perturbations_modifications_parcours IS 'Détails des modifications de parcours pour les perturbations';
|
|
|
|
COMMENT ON COLUMN perturbations.type_perturbation IS 'Type de perturbation: retard, suppression, ou modification_parcours';
|
|
COMMENT ON COLUMN perturbations.duree_retard_minutes IS 'Durée du retard en minutes (applicable uniquement pour type=retard)';
|
|
COMMENT ON COLUMN perturbations.cause IS 'Cause de la perturbation';
|
|
COMMENT ON COLUMN perturbations.sillon_substitution_id IS 'Train de substitution proposé (si suppression ou modification)';
|
|
-- ============================================================================
|
|
-- TABLES POUR LE SYSTÈME D'INFORMATIONS
|
|
-- ============================================================================
|
|
|
|
-- Table des informations trafic
|
|
CREATE TABLE IF NOT EXISTS info_trafic (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
titre VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
type VARCHAR(50) NOT NULL CHECK (type IN ('perturbation', 'travaux', 'greve', 'meteo', 'autre')),
|
|
severite VARCHAR(20) NOT NULL CHECK (severite IN ('info', 'warning', 'critical')),
|
|
ligne VARCHAR(100),
|
|
gare_id UUID REFERENCES gares(id) ON DELETE SET NULL,
|
|
date_debut TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
date_fin TIMESTAMP WITH TIME ZONE,
|
|
actif BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
deleted_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Table des événements
|
|
CREATE TABLE IF NOT EXISTS evenements (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
titre VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
lieu VARCHAR(255),
|
|
gare_id UUID REFERENCES gares(id) ON DELETE SET NULL,
|
|
date_debut TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
date_fin TIMESTAMP WITH TIME ZONE,
|
|
image_url VARCHAR(500),
|
|
lien_externe VARCHAR(500),
|
|
actif BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
deleted_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Table des actualités
|
|
CREATE TABLE IF NOT EXISTS actualites (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
titre VARCHAR(255) NOT NULL,
|
|
contenu TEXT NOT NULL,
|
|
resume VARCHAR(500),
|
|
categorie VARCHAR(50) CHECK (categorie IN ('general', 'service', 'promotion', 'partenariat', 'autre')),
|
|
image_url VARCHAR(500),
|
|
date_publication TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
auteur VARCHAR(100),
|
|
actif BOOLEAN DEFAULT true,
|
|
mise_en_avant BOOLEAN DEFAULT false,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
deleted_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Index pour améliorer les performances
|
|
CREATE INDEX IF NOT EXISTS idx_info_trafic_actif ON info_trafic(actif) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_info_trafic_dates ON info_trafic(date_debut, date_fin);
|
|
CREATE INDEX IF NOT EXISTS idx_evenements_actif ON evenements(actif) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_evenements_dates ON evenements(date_debut, date_fin);
|
|
CREATE INDEX IF NOT EXISTS idx_actualites_actif ON actualites(actif) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_actualites_publication ON actualites(date_publication);
|
|
|
|
-- Trigger pour mettre à jour updated_at automatiquement
|
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ language 'plpgsql';
|
|
|
|
DROP TRIGGER IF EXISTS update_info_trafic_updated_at ON info_trafic;
|
|
CREATE TRIGGER update_info_trafic_updated_at
|
|
BEFORE UPDATE ON info_trafic
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
DROP TRIGGER IF EXISTS update_evenements_updated_at ON evenements;
|
|
CREATE TRIGGER update_evenements_updated_at
|
|
BEFORE UPDATE ON evenements
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
DROP TRIGGER IF EXISTS update_actualites_updated_at ON actualites;
|
|
CREATE TRIGGER update_actualites_updated_at
|
|
BEFORE UPDATE ON actualites
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- Commentaires sur les tables
|
|
COMMENT ON TABLE info_trafic IS 'Informations de trafic (perturbations, travaux, etc.)';
|
|
COMMENT ON TABLE evenements IS 'Événements à venir dans les gares ou sur le réseau';
|
|
COMMENT ON TABLE actualites IS 'Actualités et nouvelles du réseau';
|
|
-- ============================================================================
|
|
-- MIGRATION: Système de Gestion d'Annonces Sonores
|
|
-- ============================================================================
|
|
-- À exécuter dans l'interface SQL de Supabase (SQL Editor)
|
|
-- ============================================================================
|
|
|
|
-- 1. TABLE banque_mots
|
|
-- Métadonnées des fichiers audio individuels (mots/sons) de la banque
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS public.banque_mots (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
label TEXT NOT NULL, -- Libellé affiché (ex: "Bonjour", "Mesdames et messieurs")
|
|
nom_fichier TEXT NOT NULL, -- Nom du fichier dans le storage (ex: "bonjour.mp3")
|
|
storage_path TEXT NOT NULL, -- Chemin complet dans le bucket (ex: "mots/salutation_bonjour.mp3")
|
|
categorie TEXT NOT NULL DEFAULT 'general', -- 'salutation' | 'signal' | 'destination' | 'numero' | 'temps' | 'perturbation' | 'quai' | 'general'
|
|
duree_ms INTEGER, -- Durée du fichier audio en millisecondes
|
|
format TEXT NOT NULL DEFAULT 'mp3', -- 'mp3' | 'wav' | 'ogg'
|
|
taille_bytes BIGINT, -- Taille du fichier en octets
|
|
description TEXT, -- Description optionnelle
|
|
actif BOOLEAN NOT NULL DEFAULT true,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
deleted_at TIMESTAMPTZ
|
|
);
|
|
|
|
-- Index pour recherche rapide par catégorie et label
|
|
CREATE INDEX IF NOT EXISTS idx_banque_mots_categorie ON public.banque_mots(categorie);
|
|
CREATE INDEX IF NOT EXISTS idx_banque_mots_label ON public.banque_mots(label);
|
|
CREATE INDEX IF NOT EXISTS idx_banque_mots_actif ON public.banque_mots(actif) WHERE deleted_at IS NULL;
|
|
|
|
-- 2. TABLE annonces_templates
|
|
-- Templates d'annonces définis par concaténation de mots de la banque
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS public.annonces_templates (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
nom TEXT NOT NULL, -- Nom du template (ex: "Départ standard")
|
|
type_annonce TEXT NOT NULL, -- 'depart' | 'arrivee' | 'manoeuvre' | 'passage' | 'retard' | 'supprime'
|
|
description TEXT, -- Description optionnelle du template
|
|
-- Séquence ordonnée des éléments du template
|
|
-- Chaque élément: { type: 'mot' | 'variable' | 'pause', mot_id?: UUID, variable?: string, pause_ms?: number }
|
|
sequence JSONB NOT NULL DEFAULT '[]', -- Array d'éléments ordonnés
|
|
variables JSONB NOT NULL DEFAULT '[]', -- Array des noms de variables attendues (ex: ["numero_train", "destination"])
|
|
tts_texte TEXT, -- Texte TTS alternatif pour prévisualisation rapide
|
|
actif BOOLEAN NOT NULL DEFAULT true,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
deleted_at TIMESTAMPTZ
|
|
);
|
|
|
|
-- Index par type d'annonce
|
|
CREATE INDEX IF NOT EXISTS idx_templates_type ON public.annonces_templates(type_annonce);
|
|
CREATE INDEX IF NOT EXISTS idx_templates_actif ON public.annonces_templates(actif) WHERE deleted_at IS NULL;
|
|
|
|
-- 3. TABLE annonces_enregistrees
|
|
-- Annonces complètes générées et sauvegardées
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS public.annonces_enregistrees (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
nom TEXT NOT NULL, -- Nom de l'annonce (ex: "TER 12345 départ Paris 14h30")
|
|
type_annonce TEXT NOT NULL, -- Même enum que annonces_templates.type_annonce
|
|
template_id UUID REFERENCES public.annonces_templates(id),
|
|
storage_path TEXT NOT NULL, -- Chemin dans le bucket storage
|
|
nom_fichier TEXT NOT NULL, -- Nom du fichier final
|
|
variables_values JSONB NOT NULL DEFAULT '{}', -- Valeurs des variables utilisées
|
|
mode_generation TEXT NOT NULL DEFAULT 'tts', -- 'tts' | 'concatenation'
|
|
duree_ms INTEGER,
|
|
taille_bytes BIGINT,
|
|
gare_id UUID REFERENCES public.gares(id),
|
|
diffuse BOOLEAN NOT NULL DEFAULT false,
|
|
date_diffusion TIMESTAMPTZ,
|
|
notes TEXT,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
deleted_at TIMESTAMPTZ
|
|
);
|
|
|
|
-- Index de recherche
|
|
CREATE INDEX IF NOT EXISTS idx_annonces_type ON public.annonces_enregistrees(type_annonce);
|
|
CREATE INDEX IF NOT EXISTS idx_annonces_gare ON public.annonces_enregistrees(gare_id);
|
|
CREATE INDEX IF NOT EXISTS idx_annonces_created ON public.annonces_enregistrees(created_at DESC);
|
|
|
|
-- 4. TRIGGER updated_at automatique
|
|
-- ============================================================================
|
|
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = now();
|
|
RETURN NEW;
|
|
END;
|
|
$$ language 'plpgsql';
|
|
|
|
DROP TRIGGER IF EXISTS set_banque_mots_updated_at ON public.banque_mots;
|
|
CREATE TRIGGER set_banque_mots_updated_at
|
|
BEFORE UPDATE ON public.banque_mots
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
|
|
|
DROP TRIGGER IF EXISTS set_templates_updated_at ON public.annonces_templates;
|
|
CREATE TRIGGER set_templates_updated_at
|
|
BEFORE UPDATE ON public.annonces_templates
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
|
|
|
DROP TRIGGER IF EXISTS set_annonces_updated_at ON public.annonces_enregistrees;
|
|
CREATE TRIGGER set_annonces_updated_at
|
|
BEFORE UPDATE ON public.annonces_enregistrees
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
|
|
|
-- 5. RLS (Row Level Security)
|
|
-- ============================================================================
|
|
ALTER TABLE public.banque_mots ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.annonces_templates ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.annonces_enregistrees ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY "Authenticated users can read banque_mots"
|
|
ON public.banque_mots FOR SELECT TO authenticated
|
|
USING (deleted_at IS NULL);
|
|
|
|
CREATE POLICY "Authenticated users can insert banque_mots"
|
|
ON public.banque_mots FOR INSERT TO authenticated
|
|
WITH CHECK (true);
|
|
|
|
CREATE POLICY "Authenticated users can update banque_mots"
|
|
ON public.banque_mots FOR UPDATE TO authenticated
|
|
USING (true);
|
|
|
|
CREATE POLICY "Authenticated users can read templates"
|
|
ON public.annonces_templates FOR SELECT TO authenticated
|
|
USING (deleted_at IS NULL);
|
|
|
|
CREATE POLICY "Authenticated users can insert templates"
|
|
ON public.annonces_templates FOR INSERT TO authenticated
|
|
WITH CHECK (true);
|
|
|
|
CREATE POLICY "Authenticated users can update templates"
|
|
ON public.annonces_templates FOR UPDATE TO authenticated
|
|
USING (true);
|
|
|
|
CREATE POLICY "Authenticated users can read annonces"
|
|
ON public.annonces_enregistrees FOR SELECT TO authenticated
|
|
USING (deleted_at IS NULL);
|
|
|
|
CREATE POLICY "Authenticated users can insert annonces"
|
|
ON public.annonces_enregistrees FOR INSERT TO authenticated
|
|
WITH CHECK (true);
|
|
|
|
CREATE POLICY "Authenticated users can update annonces"
|
|
ON public.annonces_enregistrees FOR UPDATE TO authenticated
|
|
USING (true);
|
|
|
|
-- 6. STORAGE BUCKET
|
|
-- ============================================================================
|
|
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
|
|
VALUES (
|
|
'banque-annonces',
|
|
'banque-annonces',
|
|
true,
|
|
52428800, -- 50 MB max par fichier
|
|
ARRAY['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/wave', 'audio/ogg', 'audio/webm']
|
|
)
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
-- Politiques Storage
|
|
CREATE POLICY "Public read access on banque-annonces"
|
|
ON storage.objects FOR SELECT TO public
|
|
USING (bucket_id = 'banque-annonces');
|
|
|
|
CREATE POLICY "Authenticated users can upload to banque-annonces"
|
|
ON storage.objects FOR INSERT TO authenticated
|
|
WITH CHECK (bucket_id = 'banque-annonces');
|
|
|
|
CREATE POLICY "Authenticated users can delete from banque-annonces"
|
|
ON storage.objects FOR DELETE TO authenticated
|
|
USING (bucket_id = 'banque-annonces');
|
|
|
|
-- ============================================================================
|
|
-- CONVENTION DE NOMMAGE DES FICHIERS AUDIO
|
|
-- ============================================================================
|
|
-- Fichiers dans banque-annonces/mots/ :
|
|
-- Format: [categorie]_[label-kebab-case].[ext]
|
|
-- Exemples:
|
|
-- salutation_bonjour.mp3
|
|
-- salutation_mesdames-et-messieurs.mp3
|
|
-- signal_gong-arrivee.mp3
|
|
-- signal_gong-depart.mp3
|
|
-- destination_paris-gare-de-lyon.mp3
|
|
-- temps_a-destination-de.mp3
|
|
-- quai_voie-1.mp3
|
|
-- numero_ter.mp3
|
|
-- perturbation_ce-train-est-supprime.mp3
|
|
-- general_attention.mp3
|
|
--
|
|
-- Annonces générées dans banque-annonces/annonces/ :
|
|
-- Format: [type]_[YYYY-MM-DD]_[uuid-court].[ext]
|
|
-- Exemple: depart_2026-07-02_a1b2c3.mp3
|
|
-- ============================================================================
|
|
-- Table pour la gestion des pages (CMS)
|
|
CREATE TABLE IF NOT EXISTS pages (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
titre TEXT NOT NULL,
|
|
slug TEXT UNIQUE NOT NULL,
|
|
contenu JSONB DEFAULT '[]', -- Liste ordonnée de composants
|
|
statut TEXT DEFAULT 'brouillon', -- 'publie', 'brouillon'
|
|
region_id VARCHAR(255),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
deleted_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Index pour accélérer la recherche par slug et region
|
|
CREATE INDEX IF NOT EXISTS idx_pages_slug ON pages(slug);
|
|
CREATE INDEX IF NOT EXISTS idx_pages_region ON pages(region_id);
|
|
-- ============================================================================
|
|
-- MIGRATION : Ajout du système de régions
|
|
-- Description : Ajout des colonnes region_id/region_nom aux tables concernées
|
|
-- Date : 2026-02-07
|
|
-- ============================================================================
|
|
|
|
-- ============================================================================
|
|
-- TABLE : utilisateurs - Ajout colonne region_id
|
|
-- ============================================================================
|
|
ALTER TABLE utilisateurs ADD COLUMN IF NOT EXISTS region_id VARCHAR(100);
|
|
COMMENT ON COLUMN utilisateurs.region_id IS 'Identifiant de la région administrée par l''utilisateur';
|
|
|
|
-- Index sur region_id
|
|
CREATE INDEX IF NOT EXISTS idx_utilisateurs_region_id ON utilisateurs(region_id);
|
|
|
|
-- ============================================================================
|
|
-- TABLE : info_trafic - Ajout colonnes région
|
|
-- ============================================================================
|
|
ALTER TABLE info_trafic ADD COLUMN IF NOT EXISTS region_id VARCHAR(100);
|
|
ALTER TABLE info_trafic ADD COLUMN IF NOT EXISTS created_by UUID REFERENCES utilisateurs(id);
|
|
|
|
COMMENT ON COLUMN info_trafic.region_id IS 'Identifiant de la région concernée par l''info trafic';
|
|
COMMENT ON COLUMN info_trafic.created_by IS 'Utilisateur ayant créé l''info trafic';
|
|
|
|
-- Index
|
|
CREATE INDEX IF NOT EXISTS idx_info_trafic_region_id ON info_trafic(region_id);
|
|
CREATE INDEX IF NOT EXISTS idx_info_trafic_created_by ON info_trafic(created_by);
|
|
|
|
-- ============================================================================
|
|
-- TABLE : evenements - Ajout colonnes région
|
|
-- ============================================================================
|
|
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS region_id VARCHAR(100);
|
|
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS created_by UUID REFERENCES utilisateurs(id);
|
|
|
|
COMMENT ON COLUMN evenements.region_id IS 'Identifiant de la région concernée par l''événement';
|
|
COMMENT ON COLUMN evenements.created_by IS 'Utilisateur ayant créé l''événement';
|
|
|
|
-- Index
|
|
CREATE INDEX IF NOT EXISTS idx_evenements_region_id ON evenements(region_id);
|
|
CREATE INDEX IF NOT EXISTS idx_evenements_created_by ON evenements(created_by);
|
|
|
|
-- ============================================================================
|
|
-- TABLE : actualites - Ajout colonnes région
|
|
-- ============================================================================
|
|
ALTER TABLE actualites ADD COLUMN IF NOT EXISTS region_id VARCHAR(100);
|
|
ALTER TABLE actualites ADD COLUMN IF NOT EXISTS created_by UUID REFERENCES utilisateurs(id);
|
|
|
|
COMMENT ON COLUMN actualites.region_id IS 'Identifiant de la région concernée par l''actualité';
|
|
COMMENT ON COLUMN actualites.created_by IS 'Utilisateur ayant créé l''actualité';
|
|
|
|
-- Index
|
|
CREATE INDEX IF NOT EXISTS idx_actualites_region_id ON actualites(region_id);
|
|
CREATE INDEX IF NOT EXISTS idx_actualites_created_by ON actualites(created_by);
|
|
|
|
-- ============================================================================
|
|
-- TABLE : perturbations - Ajout colonnes région (si applicable)
|
|
-- ============================================================================
|
|
ALTER TABLE perturbations ADD COLUMN IF NOT EXISTS region_id VARCHAR(100);
|
|
ALTER TABLE perturbations ADD COLUMN IF NOT EXISTS created_by UUID REFERENCES utilisateurs(id);
|
|
|
|
COMMENT ON COLUMN perturbations.region_id IS 'Identifiant de la région concernée par la perturbation';
|
|
COMMENT ON COLUMN perturbations.created_by IS 'Utilisateur ayant créé la perturbation';
|
|
|
|
-- Index
|
|
CREATE INDEX IF NOT EXISTS idx_perturbations_region_id ON perturbations(region_id);
|
|
CREATE INDEX IF NOT EXISTS idx_perturbations_created_by ON perturbations(created_by);
|
|
|
|
-- ============================================================================
|
|
-- TABLE : travaux - Ajout colonnes région (si la table existe)
|
|
-- ============================================================================
|
|
-- Note: À décommenter si la table travaux existe déjà
|
|
-- ALTER TABLE travaux ADD COLUMN IF NOT EXISTS region_id VARCHAR(100);
|
|
-- ALTER TABLE travaux ADD COLUMN IF NOT EXISTS created_by UUID REFERENCES utilisateurs(id);
|
|
-- COMMENT ON COLUMN travaux.region_id IS 'Identifiant de la région concernée par les travaux';
|
|
-- COMMENT ON COLUMN travaux.created_by IS 'Utilisateur ayant créé les travaux';
|
|
-- CREATE INDEX IF NOT EXISTS idx_travaux_region_id ON travaux(region_id);
|
|
-- CREATE INDEX IF NOT EXISTS idx_travaux_created_by ON travaux(created_by);
|
|
|
|
-- ============================================================================
|
|
-- VUES : Vues pratiques pour filtrer par région
|
|
-- ============================================================================
|
|
|
|
-- Vue : Informations par région
|
|
CREATE OR REPLACE VIEW v_info_trafic_par_region AS
|
|
SELECT
|
|
it.*,
|
|
u.nom as createur_nom,
|
|
u.prenom as createur_prenom,
|
|
u.email as createur_email
|
|
FROM info_trafic it
|
|
LEFT JOIN utilisateurs u ON it.created_by = u.id
|
|
ORDER BY it.created_at DESC;
|
|
|
|
-- Vue : Événements par région
|
|
CREATE OR REPLACE VIEW v_evenements_par_region AS
|
|
SELECT
|
|
e.*,
|
|
u.nom as createur_nom,
|
|
u.prenom as createur_prenom,
|
|
u.email as createur_email
|
|
FROM evenements e
|
|
LEFT JOIN utilisateurs u ON e.created_by = u.id
|
|
ORDER BY e.created_at DESC;
|
|
|
|
-- Vue : Actualités par région
|
|
CREATE OR REPLACE VIEW v_actualites_par_region AS
|
|
SELECT
|
|
a.*,
|
|
u.nom as createur_nom,
|
|
u.prenom as createur_prenom,
|
|
u.email as createur_email
|
|
FROM actualites a
|
|
LEFT JOIN utilisateurs u ON a.created_by = u.id
|
|
ORDER BY a.created_at DESC;
|
|
|
|
-- ============================================================================
|
|
-- DONNÉES DE TEST (optionnel)
|
|
-- ============================================================================
|
|
|
|
-- Exemple : Associer un utilisateur existant à une région
|
|
-- UPDATE utilisateurs SET region_id = 'ile-de-france' WHERE email = 'admin@example.com';
|
|
|
|
-- ============================================================================
|
|
-- NETTOYAGE : Supprimer l'ancienne colonne region_nom si elle existe
|
|
-- ============================================================================
|
|
-- Note: Migrer les données avant de supprimer
|
|
-- UPDATE utilisateurs SET region_id = LOWER(REPLACE(region_nom, ' ', '-')) WHERE region_nom IS NOT NULL;
|
|
-- ALTER TABLE utilisateurs DROP COLUMN IF EXISTS region_nom;
|
|
|
|
-- ============================================================================
|
|
-- MIGRATION : Amélioration des tables événements et actualités
|
|
-- Description : Ajout des champs pour gestion complète des événements et actualités
|
|
-- Date : 2026-02-07
|
|
-- ============================================================================
|
|
|
|
-- ============================================================================
|
|
-- TABLE : evenements - Ajout colonnes manquantes
|
|
-- ============================================================================
|
|
|
|
-- Ajouter colonne visible_site (correspond à "Visible sur le site")
|
|
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS visible_site BOOLEAN DEFAULT true;
|
|
|
|
-- S'assurer que region_id existe (ajouté précédemment mais on vérifie)
|
|
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS region_id VARCHAR(100);
|
|
|
|
-- S'assurer que created_by existe (ajouté précédemment mais on vérifie)
|
|
ALTER TABLE evenements ADD COLUMN IF NOT EXISTS created_by UUID REFERENCES utilisateurs(id);
|
|
|
|
-- Commentaires
|
|
COMMENT ON COLUMN evenements.visible_site IS 'Indique si l''événement est visible sur le site public';
|
|
COMMENT ON COLUMN evenements.region_id IS 'Identifiant de la région concernée par l''événement';
|
|
COMMENT ON COLUMN evenements.created_by IS 'Utilisateur ayant créé l''événement';
|
|
COMMENT ON COLUMN evenements.titre IS 'Titre de l''événement';
|
|
COMMENT ON COLUMN evenements.description IS 'Description détaillée de l''événement';
|
|
|
|
-- Index supplémentaires
|
|
CREATE INDEX IF NOT EXISTS idx_evenements_visible_site ON evenements(visible_site) WHERE deleted_at IS NULL;
|
|
|
|
-- ============================================================================
|
|
-- TABLE : actualites - Ajout colonnes manquantes
|
|
-- ============================================================================
|
|
|
|
-- Renommer image_url en image_couverture pour plus de clarté
|
|
-- Note: PostgreSQL ne permet pas de renommer facilement, on ajoute une nouvelle colonne
|
|
ALTER TABLE actualites ADD COLUMN IF NOT EXISTS image_couverture VARCHAR(500);
|
|
|
|
-- Copier les données de image_url vers image_couverture si image_url existe
|
|
UPDATE actualites SET image_couverture = image_url WHERE image_couverture IS NULL AND image_url IS NOT NULL;
|
|
|
|
-- S'assurer que region_id existe
|
|
ALTER TABLE actualites ADD COLUMN IF NOT EXISTS region_id VARCHAR(100);
|
|
|
|
-- S'assurer que created_by existe
|
|
ALTER TABLE actualites ADD COLUMN IF NOT EXISTS created_by UUID REFERENCES utilisateurs(id);
|
|
|
|
-- Commentaires
|
|
COMMENT ON COLUMN actualites.image_couverture IS 'URL de l''image de couverture de l''actualité';
|
|
COMMENT ON COLUMN actualites.region_id IS 'Identifiant de la région concernée par l''actualité';
|
|
COMMENT ON COLUMN actualites.created_by IS 'Utilisateur ayant créé l''actualité';
|
|
COMMENT ON COLUMN actualites.titre IS 'Titre de l''actualité';
|
|
COMMENT ON COLUMN actualites.contenu IS 'Contenu complet de l''actualité (peut être HTML)';
|
|
COMMENT ON COLUMN actualites.resume IS 'Résumé court de l''actualité';
|
|
|
|
-- ============================================================================
|
|
-- VUES : Amélioration des vues existantes
|
|
-- ============================================================================
|
|
|
|
-- Vue améliorée pour les événements
|
|
CREATE OR REPLACE VIEW v_evenements_complets AS
|
|
SELECT
|
|
e.*,
|
|
u.nom as createur_nom,
|
|
u.prenom as createur_prenom,
|
|
u.email as createur_email,
|
|
g.nom as gare_nom,
|
|
g.ville as gare_ville
|
|
FROM evenements e
|
|
LEFT JOIN utilisateurs u ON e.created_by = u.id
|
|
LEFT JOIN gares g ON e.gare_id = g.id
|
|
WHERE e.deleted_at IS NULL
|
|
ORDER BY e.date_debut DESC;
|
|
|
|
-- Vue améliorée pour les actualités
|
|
CREATE OR REPLACE VIEW v_actualites_completes AS
|
|
SELECT
|
|
a.*,
|
|
u.nom as createur_nom,
|
|
u.prenom as createur_prenom,
|
|
u.email as createur_email
|
|
FROM actualites a
|
|
LEFT JOIN utilisateurs u ON a.created_by = u.id
|
|
WHERE a.deleted_at IS NULL
|
|
ORDER BY a.date_publication DESC;
|
|
|
|
-- ============================================================================
|
|
-- DONNÉES DE TEST (optionnel - à décommenter si besoin)
|
|
-- ============================================================================
|
|
|
|
/*
|
|
-- Exemple d'événement
|
|
INSERT INTO evenements (titre, description, lieu, date_debut, date_fin, visible_site, region_id, actif)
|
|
VALUES (
|
|
'Portes ouvertes Gare de Lyon',
|
|
'Venez découvrir les coulisses de la gare de Lyon lors de nos portes ouvertes exceptionnelles.',
|
|
'Gare de Lyon - Paris',
|
|
NOW() + INTERVAL '7 days',
|
|
NOW() + INTERVAL '7 days' + INTERVAL '8 hours',
|
|
true,
|
|
'ile-de-france',
|
|
true
|
|
);
|
|
|
|
-- Exemple d'actualité
|
|
INSERT INTO actualites (titre, contenu, resume, image_couverture, region_id, actif)
|
|
VALUES (
|
|
'Nouvelle ligne TER en service',
|
|
'Une nouvelle ligne TER relie désormais Paris à Orléans avec des arrêts supplémentaires pour faciliter vos déplacements quotidiens. Cette ligne propose 20 allers-retours par jour...',
|
|
'Mise en service d''une nouvelle ligne TER Paris-Orléans',
|
|
'/images/actualites/ter-paris-orleans.jpg',
|
|
'ile-de-france',
|
|
true
|
|
);
|
|
*/
|
|
|
|
-- ============================================================================
|
|
-- NETTOYAGE : Migration de l'ancienne colonne image_url (optionnel)
|
|
-- ============================================================================
|
|
|
|
/*
|
|
-- À exécuter APRÈS avoir vérifié que toutes les données sont migrées
|
|
-- ALTER TABLE actualites DROP COLUMN IF EXISTS image_url;
|
|
*/
|
|
|
|
-- ============================================================================
|
|
-- PERMISSIONS RLS (à adapter selon vos besoins)
|
|
-- ============================================================================
|
|
|
|
-- Activer RLS sur les tables (optionnel - à adapter selon votre architecture Auth)
|
|
-- ALTER TABLE evenements ENABLE ROW LEVEL SECURITY;
|
|
-- ALTER TABLE actualites ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Exemple de policies (à adapter)
|
|
/*
|
|
-- Tout le monde peut lire les événements visibles
|
|
CREATE POLICY "Les événements visibles sont publics"
|
|
ON evenements FOR SELECT
|
|
USING (visible_site = true AND actif = true AND deleted_at IS NULL);
|
|
|
|
-- Seuls les créateurs et admins peuvent modifier
|
|
CREATE POLICY "Les créateurs peuvent modifier leurs événements"
|
|
ON evenements FOR UPDATE
|
|
USING (created_by = auth.uid() OR auth.jwt() -> 'user_metadata' ->> 'role' = 'admin');
|
|
|
|
-- Tout le monde peut lire les actualités actives
|
|
CREATE POLICY "Les actualités actives sont publiques"
|
|
ON actualites FOR SELECT
|
|
USING (actif = true AND deleted_at IS NULL);
|
|
|
|
-- Seuls les créateurs et admins peuvent modifier
|
|
CREATE POLICY "Les créateurs peuvent modifier leurs actualités"
|
|
ON actualites FOR UPDATE
|
|
USING (created_by = auth.uid() OR auth.jwt() -> 'user_metadata' ->> 'role' = 'admin');
|
|
*/
|
|
|
|
-- Création de la table regles_quais
|
|
CREATE TABLE IF NOT EXISTS public.regles_quais (
|
|
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
|
gare_id UUID NOT NULL REFERENCES public.gares(id) ON DELETE CASCADE,
|
|
ligne_id UUID REFERENCES public.lignes(id) ON DELETE CASCADE,
|
|
type_regle TEXT NOT NULL CHECK (type_regle IN ('INTERDIT', 'PREFERENTIEL')),
|
|
quais_concernes JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Index pour la recherche par gare
|
|
CREATE INDEX IF NOT EXISTS idx_regles_quais_gare ON public.regles_quais(gare_id);
|
|
-- Migration : Ajout de la colonne fh_url à la table lignes
|
|
-- Stocke l'URL publique du PDF de la fiche horaire associée à la ligne
|
|
|
|
ALTER TABLE lignes
|
|
ADD COLUMN IF NOT EXISTS fh_url TEXT DEFAULT NULL;
|
|
|
|
COMMENT ON COLUMN lignes.fh_url IS 'URL publique du fichier PDF de la fiche horaire de la ligne (stocké dans Supabase Storage)';
|
|
-- Migration: create table horaires_substitutions
|
|
-- Cette table est utilisée par l'API front-end (src/api/horairesSubstitutions.js)
|
|
CREATE TABLE IF NOT EXISTS horaires_substitutions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Référence éventuelle vers un horaire source
|
|
horaire_id UUID REFERENCES horaires(id) ON DELETE SET NULL,
|
|
|
|
-- Région (filtrage RBAC)
|
|
region_id UUID,
|
|
region VARCHAR(255),
|
|
|
|
-- Informations générales (copie similaire à `horaires`)
|
|
numero_train VARCHAR(50),
|
|
type_train VARCHAR(100),
|
|
type_train_id BIGINT,
|
|
|
|
gare_depart_id UUID,
|
|
gare_depart_nom VARCHAR(255),
|
|
heure_depart TIME,
|
|
|
|
gare_arrivee_id UUID,
|
|
gare_arrivee_nom VARCHAR(255),
|
|
heure_arrivee TIME,
|
|
|
|
-- Période de validité de la substitution
|
|
date_debut TIMESTAMP WITH TIME ZONE,
|
|
date_fin TIMESTAMP WITH TIME ZONE,
|
|
|
|
-- Jours / exceptions
|
|
circule_lundi BOOLEAN DEFAULT true,
|
|
circule_mardi BOOLEAN DEFAULT true,
|
|
circule_mercredi BOOLEAN DEFAULT true,
|
|
circule_jeudi BOOLEAN DEFAULT true,
|
|
circule_vendredi BOOLEAN DEFAULT true,
|
|
circule_samedi BOOLEAN DEFAULT true,
|
|
circule_dimanche BOOLEAN DEFAULT true,
|
|
circule_jours_feries BOOLEAN DEFAULT false,
|
|
|
|
jours_personnalises JSONB DEFAULT '[]'::jsonb,
|
|
jours_non_circulation JSONB DEFAULT '[]'::jsonb,
|
|
|
|
materiel_roulant_id BIGINT,
|
|
|
|
ligne_id UUID,
|
|
ligne_nom VARCHAR(255),
|
|
|
|
service_annuel_id UUID,
|
|
service_annuel_nom VARCHAR(255),
|
|
|
|
-- Indique qu'il s'agit d'une substitution
|
|
est_substitution BOOLEAN DEFAULT true,
|
|
motif_substitution VARCHAR(255),
|
|
|
|
-- Snapshot des gares desservies et composition
|
|
gares_desservies JSONB DEFAULT '[]'::jsonb,
|
|
composition_train JSONB DEFAULT '[]'::jsonb,
|
|
|
|
-- Métadonnées
|
|
actif BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
deleted_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Indexes utiles
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_substitutions_region ON horaires_substitutions(region_id);
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_substitutions_horaire ON horaires_substitutions(horaire_id);
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_substitutions_actif ON horaires_substitutions(actif) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_horaires_substitutions_datefin ON horaires_substitutions(date_fin);
|
|
|
|
-- Trigger pour updated_at si la fonction existe
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'update_updated_at_column') THEN
|
|
DROP TRIGGER IF EXISTS update_horaires_substitutions_updated_at ON horaires_substitutions;
|
|
CREATE TRIGGER update_horaires_substitutions_updated_at
|
|
BEFORE UPDATE ON horaires_substitutions
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
END IF;
|
|
END$$;
|
|
|
|
COMMENT ON TABLE horaires_substitutions IS 'Table des horaires de substitution (autocar / remplacement)';
|
|
COMMENT ON COLUMN horaires_substitutions.gares_desservies IS 'Liste JSON des gares desservies par la substitution';
|
|
COMMENT ON COLUMN horaires_substitutions.composition_train IS 'Composition JSON du matériel roulant (position, id, nom)';
|
|
COMMENT ON COLUMN horaires_substitutions.date_fin IS 'Date/heure de fin de validité de la substitution';
|
|
|
|
-- ============================================================================
|
|
-- MIGRATION : Ajout de la colonne composition_train (JSONB) dans la table horaires
|
|
-- Date : 2026-03-01
|
|
-- Description : Stocke la composition complète et ordonnée des matériels roulants
|
|
-- affectés à un horaire (sillon). Chaque élément contient l'id,
|
|
-- le nom, le nom technique, l'URL de l'image et la position.
|
|
--
|
|
-- ⚠️ À EXÉCUTER dans l'éditeur SQL Supabase :
|
|
-- https://supabase.com/dashboard → votre projet → SQL Editor
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE public.horaires
|
|
ADD COLUMN IF NOT EXISTS composition_train JSONB DEFAULT '[]'::jsonb;
|
|
|
|
COMMENT ON COLUMN public.horaires.composition_train IS
|
|
'Composition ordonnée du train : liste JSON des matériels roulants affectés au sillon. '
|
|
'Format : [{"materiel_id": 1, "materiel_nom": "Régiolis", "materiel_nom_technique": "X73500", "materiel_image_url": "https://...", "position": 1}, ...]';
|
|
|
|
-- ============================================================================
|
|
-- MIGRATION : Ajout de la colonne informations (JSONB) dans la table gares
|
|
-- Date : 2026-03-01
|
|
-- Description : Stocke toutes les informations enrichies d'une gare :
|
|
-- adresse, horaires, accessibilité, services, voiture, vélo,
|
|
-- et lignes de transports en commun (bus, métro, tramway, car régional).
|
|
--
|
|
-- ⚠️ À EXÉCUTER dans l'éditeur SQL Supabase :
|
|
-- https://supabase.com/dashboard → votre projet → SQL Editor
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE public.gares
|
|
ADD COLUMN IF NOT EXISTS informations JSONB DEFAULT '{}'::jsonb;
|
|
|
|
COMMENT ON COLUMN public.gares.informations IS
|
|
'Informations enrichies de la gare au format JSON. Structure :
|
|
{
|
|
-- Informations Générales
|
|
"adresse": "20 Bd Diderot, 75012 Paris",
|
|
"horaires_guichet": "6h00 - 21h30",
|
|
"horaires_gare": "5h00 - 00h30",
|
|
"distributeur_ter": true,
|
|
|
|
-- Accessibilité
|
|
"service_assistance": true,
|
|
|
|
-- Services
|
|
"espace_attente": true,
|
|
"distributeur_tgv": false,
|
|
"objets_trouves": true,
|
|
"toilettes": true,
|
|
"wifi_gratuit": true,
|
|
"photomaton": false,
|
|
"defibrillateur": true,
|
|
"galeries_marchandes": false,
|
|
|
|
-- Voiture
|
|
"depose_minute": true,
|
|
"espace_taxis": true,
|
|
"espace_vtc": false,
|
|
"location_voiture": false,
|
|
"parking": true,
|
|
|
|
-- Vélo
|
|
"velo_libre_service": true,
|
|
"parking_velos": true,
|
|
|
|
-- Transports en commun enrichis
|
|
"lignes_bus": [{"nom": "29", "terminus_a": "Gare Saint-Lazare", "terminus_b": "Montempoivre"}],
|
|
"lignes_metro": [{"nom": "1", "terminus_a": "La Défense", "terminus_b": "Château de Vincennes"}],
|
|
"lignes_tramway": [],
|
|
"lignes_car_regional": []
|
|
}';
|
|
|
|
-- ============================================================================
|
|
-- MIGRATION : Ajout de la colonne gares_desservies (JSONB) dans la table horaires
|
|
-- Date : 2026-03-01
|
|
-- Description : Ajoute une colonne JSONB "gares_desservies" dans la table horaires
|
|
-- afin de stocker un snapshot dénormalisé des gares desservies.
|
|
-- Cette colonne est alimentée à chaque création/modification d'horaire
|
|
-- et est synchronisée avec la table relationnelle horaires_gares_desservies.
|
|
-- ============================================================================
|
|
|
|
-- Ajout de la colonne si elle n'existe pas encore
|
|
ALTER TABLE public.horaires
|
|
ADD COLUMN IF NOT EXISTS gares_desservies JSONB DEFAULT '[]'::jsonb;
|
|
|
|
-- Commentaire explicatif sur la colonne
|
|
COMMENT ON COLUMN public.horaires.gares_desservies IS
|
|
'Snapshot JSON des gares desservies (copie dénormalisée synchronisée avec horaires_gares_desservies). '
|
|
'Format : [{"gare_id": "uuid", "gare_nom": "Nom Gare", "heure_arrivee": "HH:MM", "heure_depart": "HH:MM", "ordre": 1}, ...]';
|
|
|
|
-- ============================================================================
|
|
-- Rétro-remplissage : synchroniser la colonne JSON depuis la table relationnelle
|
|
-- pour tous les horaires existants ayant déjà des gares desservies.
|
|
-- ============================================================================
|
|
UPDATE public.horaires h
|
|
SET gares_desservies = (
|
|
SELECT COALESCE(
|
|
jsonb_agg(
|
|
jsonb_build_object(
|
|
'gare_id', gd.gare_id,
|
|
'gare_nom', gd.gare_nom,
|
|
'heure_arrivee', gd.heure_arrivee,
|
|
'heure_depart', gd.heure_depart,
|
|
'ordre', gd.ordre
|
|
) ORDER BY gd.ordre
|
|
),
|
|
'[]'::jsonb
|
|
)
|
|
FROM public.horaires_gares_desservies gd
|
|
WHERE gd.horaire_id = h.id
|
|
)
|
|
WHERE EXISTS (
|
|
SELECT 1 FROM public.horaires_gares_desservies gd WHERE gd.horaire_id = h.id
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- (Optionnel) Trigger pour maintenir automatiquement la colonne JSON à jour
|
|
-- à chaque INSERT / UPDATE / DELETE dans horaires_gares_desservies.
|
|
-- Décommenter si vous souhaitez une synchronisation automatique côté base.
|
|
-- ============================================================================
|
|
|
|
-- CREATE OR REPLACE FUNCTION public.sync_horaire_gares_desservies_json()
|
|
-- RETURNS trigger LANGUAGE plpgsql AS $$
|
|
-- DECLARE
|
|
-- v_horaire_id UUID;
|
|
-- BEGIN
|
|
-- IF TG_OP = 'DELETE' THEN
|
|
-- v_horaire_id := OLD.horaire_id;
|
|
-- ELSE
|
|
-- v_horaire_id := NEW.horaire_id;
|
|
-- END IF;
|
|
--
|
|
-- UPDATE public.horaires
|
|
-- SET gares_desservies = (
|
|
-- SELECT COALESCE(
|
|
-- jsonb_agg(
|
|
-- jsonb_build_object(
|
|
-- 'gare_id', gd.gare_id,
|
|
-- 'gare_nom', gd.gare_nom,
|
|
-- 'heure_arrivee', gd.heure_arrivee,
|
|
-- 'heure_depart', gd.heure_depart,
|
|
-- 'ordre', gd.ordre
|
|
-- ) ORDER BY gd.ordre
|
|
-- ),
|
|
-- '[]'::jsonb
|
|
-- )
|
|
-- FROM public.horaires_gares_desservies gd
|
|
-- WHERE gd.horaire_id = v_horaire_id
|
|
-- )
|
|
-- WHERE id = v_horaire_id;
|
|
--
|
|
-- RETURN NULL;
|
|
-- END;
|
|
-- $$;
|
|
--
|
|
-- DROP TRIGGER IF EXISTS tr_sync_gares_desservies_json ON public.horaires_gares_desservies;
|
|
-- CREATE TRIGGER tr_sync_gares_desservies_json
|
|
-- AFTER INSERT OR UPDATE OR DELETE ON public.horaires_gares_desservies
|
|
-- FOR EACH ROW EXECUTE FUNCTION public.sync_horaire_gares_desservies_json();
|
|
|
|
-- Migration : ajout de la colonne jours_perturbation à la table perturbations
|
|
-- Permet de stocker plusieurs jours uniques ou périodes pour une perturbation
|
|
|
|
ALTER TABLE perturbations
|
|
ADD COLUMN IF NOT EXISTS jours_perturbation JSONB DEFAULT NULL;
|
|
|
|
COMMENT ON COLUMN perturbations.jours_perturbation IS
|
|
'Liste des jours/périodes de perturbation. Format : [{type: "jour"|"periode", date: "YYYY-MM-DD", dateDebut: "YYYY-MM-DD", dateFin: "YYYY-MM-DD"}]';
|
|
|
|
-- Ajoute un champ JSONB pour stocker les permissions par fonctionnalite.
|
|
ALTER TABLE IF EXISTS public.utilisateurs
|
|
ADD COLUMN IF NOT EXISTS permissions jsonb NOT NULL DEFAULT '{}'::jsonb;
|
|
|
|
-- Securise les lignes existantes si la colonne etait nullable.
|
|
UPDATE public.utilisateurs
|
|
SET permissions = '{}'::jsonb
|
|
WHERE permissions IS NULL;
|
|
|
|
-- ============================================================================
|
|
-- MIGRATION : Ajout de la colonne perturbations_arrets (JSONB) dans perturbations
|
|
-- Date : 2026-03-01
|
|
-- Description : Stocke l'état complet des perturbations par arrêt tel que
|
|
-- configuré dans le formulaire de création / modification.
|
|
-- Ceci permet au formulaire d'édition de restaurer exactement
|
|
-- le même état que le formulaire de création.
|
|
--
|
|
-- ⚠️ À EXÉCUTER dans l'éditeur SQL Supabase :
|
|
-- https://supabase.com/dashboard → votre projet → SQL Editor
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE public.perturbations
|
|
ADD COLUMN IF NOT EXISTS perturbations_arrets JSONB DEFAULT NULL;
|
|
|
|
COMMENT ON COLUMN public.perturbations.perturbations_arrets IS
|
|
'Snapshot complet des perturbations par arrêt (état du formulaire). '
|
|
'Format : [{
|
|
"index": 0,
|
|
"gare_nom": "Paris Gare de Lyon",
|
|
"heure_arrivee": null,
|
|
"heure_depart": "07:30",
|
|
"type_perturbation": "retard",
|
|
"retard_minutes": 15,
|
|
"retard_cumule": 0,
|
|
"arret_prolonge_minutes": 0,
|
|
"cause_arret": "Incident technique",
|
|
"is_nouveau_terminus": false,
|
|
"is_nouvelle_origine": false,
|
|
"isFirst": true,
|
|
"isLast": false,
|
|
"manual_retard": true,
|
|
"manual_arret_prolonge": false
|
|
}, ...]';
|
|
|
|
-- ============================================================================
|
|
-- MIGRATION: Ajout du système de gestion des perturbations
|
|
-- Date: 2026-02-06
|
|
-- Description: Ajoute les tables nécessaires pour gérer les perturbations
|
|
-- affectant les horaires de trains
|
|
-- ============================================================================
|
|
|
|
-- Vérifier que les tables n'existent pas déjà
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'perturbations') THEN
|
|
RAISE NOTICE 'La table perturbations existe déjà, migration annulée';
|
|
RETURN;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- Exécuter le script principal
|
|
-- supabase_perturbations_tables.sql a déjà été importé précédemment
|
|
|
|
-- Vérification post-migration
|
|
DO $$
|
|
DECLARE
|
|
perturbations_count INTEGER;
|
|
modifications_count INTEGER;
|
|
BEGIN
|
|
-- Vérifier que les tables ont été créées
|
|
SELECT COUNT(*) INTO perturbations_count
|
|
FROM information_schema.tables
|
|
WHERE table_name = 'perturbations';
|
|
|
|
SELECT COUNT(*) INTO modifications_count
|
|
FROM information_schema.tables
|
|
WHERE table_name = 'perturbations_modifications_parcours';
|
|
|
|
IF perturbations_count = 1 AND modifications_count = 1 THEN
|
|
RAISE NOTICE '✓ Migration réussie : Tables créées avec succès';
|
|
ELSE
|
|
RAISE EXCEPTION '✗ Échec de la migration : Les tables n''ont pas été créées correctement';
|
|
END IF;
|
|
|
|
-- Afficher les colonnes créées
|
|
RAISE NOTICE 'Colonnes de la table perturbations:';
|
|
FOR rec IN (
|
|
SELECT column_name, data_type
|
|
FROM information_schema.columns
|
|
WHERE table_name = 'perturbations'
|
|
ORDER BY ordinal_position
|
|
) LOOP
|
|
RAISE NOTICE ' - % (%)', rec.column_name, rec.data_type;
|
|
END LOOP;
|
|
|
|
END $$;
|
|
|
|
-- Test d'insertion (rollback automatique)
|
|
DO $$
|
|
DECLARE
|
|
test_horaire_id UUID;
|
|
test_perturbation_id UUID;
|
|
BEGIN
|
|
-- Récupérer un horaire existant pour le test
|
|
SELECT id INTO test_horaire_id FROM horaires LIMIT 1;
|
|
|
|
IF test_horaire_id IS NULL THEN
|
|
RAISE NOTICE 'Aucun horaire disponible pour le test';
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Test d'insertion d'une perturbation
|
|
INSERT INTO perturbations (
|
|
horaire_id,
|
|
type_perturbation,
|
|
duree_retard_minutes,
|
|
cause,
|
|
cause_details,
|
|
actif
|
|
) VALUES (
|
|
test_horaire_id,
|
|
'retard',
|
|
15,
|
|
'incident_technique',
|
|
'Test de migration',
|
|
false
|
|
) RETURNING id INTO test_perturbation_id;
|
|
|
|
IF test_perturbation_id IS NOT NULL THEN
|
|
RAISE NOTICE '✓ Test d''insertion réussi';
|
|
-- Supprimer le test
|
|
DELETE FROM perturbations WHERE id = test_perturbation_id;
|
|
ELSE
|
|
RAISE EXCEPTION '✗ Échec du test d''insertion';
|
|
END IF;
|
|
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
RAISE NOTICE '✗ Erreur lors du test : %', SQLERRM;
|
|
END $$;
|
|
|
|
-- Afficher un résumé
|
|
SELECT
|
|
'Migration terminée' as statut,
|
|
NOW() as date_migration,
|
|
(SELECT COUNT(*) FROM perturbations) as nombre_perturbations,
|
|
(SELECT COUNT(*) FROM perturbations_modifications_parcours) as nombre_modifications;
|
|
|
|
-- ============================================================================
|
|
-- ROLLBACK (à utiliser en cas de problème)
|
|
-- ============================================================================
|
|
-- DROP TABLE IF EXISTS perturbations_modifications_parcours CASCADE;
|
|
-- DROP TABLE IF EXISTS perturbations CASCADE;
|
|
-- ============================================================================
|
|
-- Migration : Ajout du champ "quai" dans horaires_gares_desservies
|
|
-- Date : 2026-03-02
|
|
-- ============================================================================
|
|
|
|
-- 1. Ajouter la colonne quai à la table horaires_gares_desservies
|
|
ALTER TABLE horaires_gares_desservies
|
|
ADD COLUMN IF NOT EXISTS quai VARCHAR(50) DEFAULT NULL;
|
|
|
|
COMMENT ON COLUMN horaires_gares_desservies.quai IS 'Numéro ou nom du quai attribué à cet arrêt (ex: "Voie 3", "Quai A")';
|
|
|
|
-- 2. Mettre à jour l'index pour accélérer les recherches par gare_id
|
|
CREATE INDEX IF NOT EXISTS idx_hgd_gare_id ON horaires_gares_desservies(gare_id);
|
|
CREATE INDEX IF NOT EXISTS idx_hgd_horaire_id ON horaires_gares_desservies(horaire_id);
|
|
|
|
-- Note : Le snapshot JSONB horaires.gares_desservies sera mis à jour
|
|
-- automatiquement via l'application lors de la sauvegarde des quais.
|
|
|
|
-- ============================================================================
|
|
-- MIGRATION : Ajout de la colonne quais_hall (JSONB) dans la table gares
|
|
-- Date : 2026-06-10
|
|
-- Description : Stocke l'association entre les halls et les quais au format JSON.
|
|
-- Structure :
|
|
-- {
|
|
-- "hall": {
|
|
-- "Nom du hall": ["Voie A", "Voie B"]
|
|
-- }
|
|
-- }
|
|
--
|
|
-- ⚠️ À EXÉCUTER dans l'éditeur SQL Supabase :
|
|
-- https://supabase.com/dashboard → votre projet → SQL Editor
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE public.gares
|
|
ADD COLUMN IF NOT EXISTS quais_hall JSONB DEFAULT '{"hall": {}}'::jsonb;
|
|
|
|
COMMENT ON COLUMN public.gares.quais_hall IS
|
|
'Association des quais aux halls sous forme JSON : {"hall": {"Nom du Hall": ["Quai 1", "Quai 2"]}}';
|
|
-- Ajout du champ region_id (slug) à la table menus_regionaux
|
|
ALTER TABLE menus_regionaux ADD COLUMN IF NOT EXISTS region_id VARCHAR(64);
|
|
-- Index pour accélérer la recherche par région
|
|
CREATE INDEX IF NOT EXISTS idx_menus_regionaux_region_id ON menus_regionaux(region_id);
|
|
-- Migration: Ajouter la colonne "region" à la table lignes
|
|
-- et créer la table "parametres" pour les paramètres du site
|
|
|
|
-- 1. Ajout de la colonne region à la table lignes
|
|
ALTER TABLE lignes ADD COLUMN IF NOT EXISTS region VARCHAR(100);
|
|
|
|
-- 2. Création de la table parametres
|
|
CREATE TABLE IF NOT EXISTS parametres (
|
|
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
cle VARCHAR(100) NOT NULL UNIQUE,
|
|
valeur JSONB,
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- Insérer les paramètres par défaut
|
|
INSERT INTO parametres (cle, valeur) VALUES
|
|
('general', '{"nom_site": "", "description_site": "", "region": "", "logo_header_url": ""}'),
|
|
('tarifs', '{"billets": [], "abonnements": [], "remises": []}')
|
|
ON CONFLICT (cle) DO NOTHING;
|
|
|
|
-- 3. Ajout de la colonne quai_defaut à horaires_gares_desservies (pour attribution des quais)
|
|
ALTER TABLE horaires_gares_desservies ADD COLUMN IF NOT EXISTS quai_id UUID REFERENCES gare_quais(id);
|
|
|
|
-- 4. Politique RLS pour parametres (accès complet pour les utilisateurs authentifiés)
|
|
ALTER TABLE parametres ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY "parametres_select_all" ON parametres FOR SELECT USING (true);
|
|
CREATE POLICY "parametres_insert_all" ON parametres FOR INSERT WITH CHECK (true);
|
|
CREATE POLICY "parametres_update_all" ON parametres FOR UPDATE USING (true);
|
|
CREATE POLICY "parametres_delete_all" ON parametres FOR DELETE USING (true);
|
|
|
|
ALTER TABLE tarifs
|
|
ADD COLUMN IF NOT EXISTS details JSONB;
|
|
|
|
-- Migration : table menus_regionaux
|
|
CREATE TABLE IF NOT EXISTS menus_regionaux (
|
|
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
nom VARCHAR(200) NOT NULL,
|
|
description TEXT,
|
|
actif BOOLEAN DEFAULT false,
|
|
structure JSONB DEFAULT '[]'::jsonb,
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- RLS
|
|
ALTER TABLE menus_regionaux ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY "menus_select" ON menus_regionaux FOR SELECT USING (true);
|
|
CREATE POLICY "menus_insert" ON menus_regionaux FOR INSERT WITH CHECK (true);
|
|
CREATE POLICY "menus_update" ON menus_regionaux FOR UPDATE USING (true);
|
|
CREATE POLICY "menus_delete" ON menus_regionaux FOR DELETE USING (true);
|
|
|
|
-- Tables de messagerie interne (V1)
|
|
|
|
CREATE TABLE IF NOT EXISTS public.messagerie_threads (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.messagerie_participants (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
thread_id UUID NOT NULL REFERENCES public.messagerie_threads(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES public.utilisateurs(id) ON DELETE CASCADE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE(thread_id, user_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.messagerie_messages (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
thread_id UUID NOT NULL REFERENCES public.messagerie_threads(id) ON DELETE CASCADE,
|
|
sender_id UUID NOT NULL REFERENCES public.utilisateurs(id) ON DELETE CASCADE,
|
|
contenu TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_messagerie_participants_user_id ON public.messagerie_participants(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_messagerie_messages_thread_id ON public.messagerie_messages(thread_id);
|
|
CREATE INDEX IF NOT EXISTS idx_messagerie_messages_created_at ON public.messagerie_messages(created_at DESC);
|
|
|
|
-- RLS simple: participant du thread uniquement
|
|
ALTER TABLE public.messagerie_threads ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.messagerie_participants ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.messagerie_messages ENABLE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS messagerie_threads_select ON public.messagerie_threads;
|
|
CREATE POLICY messagerie_threads_select ON public.messagerie_threads
|
|
FOR SELECT TO anon
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.messagerie_participants p
|
|
WHERE p.thread_id = messagerie_threads.id
|
|
)
|
|
);
|
|
|
|
DROP POLICY IF EXISTS messagerie_threads_insert ON public.messagerie_threads;
|
|
CREATE POLICY messagerie_threads_insert ON public.messagerie_threads
|
|
FOR INSERT TO anon
|
|
WITH CHECK (true);
|
|
|
|
DROP POLICY IF EXISTS messagerie_threads_update ON public.messagerie_threads;
|
|
CREATE POLICY messagerie_threads_update ON public.messagerie_threads
|
|
FOR UPDATE TO anon
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.messagerie_participants p
|
|
WHERE p.thread_id = messagerie_threads.id
|
|
)
|
|
)
|
|
WITH CHECK (true);
|
|
|
|
DROP POLICY IF EXISTS messagerie_participants_select ON public.messagerie_participants;
|
|
CREATE POLICY messagerie_participants_select ON public.messagerie_participants
|
|
FOR SELECT TO anon
|
|
USING (true);
|
|
|
|
DROP POLICY IF EXISTS messagerie_participants_insert ON public.messagerie_participants;
|
|
CREATE POLICY messagerie_participants_insert ON public.messagerie_participants
|
|
FOR INSERT TO anon
|
|
WITH CHECK (true);
|
|
|
|
DROP POLICY IF EXISTS messagerie_messages_select ON public.messagerie_messages;
|
|
CREATE POLICY messagerie_messages_select ON public.messagerie_messages
|
|
FOR SELECT TO anon
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.messagerie_participants p
|
|
WHERE p.thread_id = messagerie_messages.thread_id
|
|
)
|
|
);
|
|
|
|
DROP POLICY IF EXISTS messagerie_messages_insert ON public.messagerie_messages;
|
|
CREATE POLICY messagerie_messages_insert ON public.messagerie_messages
|
|
FOR INSERT TO anon
|
|
WITH CHECK (
|
|
EXISTS (
|
|
SELECT 1 FROM public.messagerie_participants p
|
|
WHERE p.thread_id = messagerie_messages.thread_id
|
|
AND p.user_id = messagerie_messages.sender_id
|
|
)
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- MIGRATION : Création du bucket Storage "materiel-roulant"
|
|
-- Date : 2026-03-01
|
|
-- Description : Crée le bucket public "materiel-roulant" dans Supabase Storage
|
|
-- et configure les policies RLS.
|
|
--
|
|
-- ⚠️ IMPORTANT — Authentification custom :
|
|
-- Cette application utilise une auth custom (RPC authenticate_user + localStorage)
|
|
-- et NON supabase.auth.signInWithPassword(). Par conséquent auth.uid() est
|
|
-- toujours NULL côté Supabase. Les policies utilisent donc le rôle "anon"
|
|
-- (clé publique anonyme) pour autoriser les opérations Storage depuis le front.
|
|
--
|
|
-- ⚠️ À EXÉCUTER UNE SEULE FOIS dans l'éditeur SQL de votre projet Supabase
|
|
-- (https://supabase.com/dashboard → votre projet → SQL Editor)
|
|
-- ============================================================================
|
|
|
|
-- 1. Créer le bucket s'il n'existe pas encore
|
|
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
|
|
VALUES (
|
|
'materiel-roulant',
|
|
'materiel-roulant',
|
|
true,
|
|
10485760,
|
|
ARRAY['image/jpeg','image/png','image/webp','image/gif','image/svg+xml']
|
|
)
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
-- ============================================================================
|
|
-- 2. Policies RLS sur storage.objects
|
|
-- ============================================================================
|
|
|
|
-- Lecture publique
|
|
DROP POLICY IF EXISTS "materiel-roulant - lecture publique" ON storage.objects;
|
|
CREATE POLICY "materiel-roulant - lecture publique"
|
|
ON storage.objects FOR SELECT
|
|
USING (bucket_id = 'materiel-roulant');
|
|
|
|
-- Upload (anon car auth custom — auth.uid() toujours NULL)
|
|
DROP POLICY IF EXISTS "materiel-roulant - upload authentifié" ON storage.objects;
|
|
CREATE POLICY "materiel-roulant - upload authentifié"
|
|
ON storage.objects FOR INSERT
|
|
WITH CHECK (bucket_id = 'materiel-roulant');
|
|
|
|
-- Mise à jour
|
|
DROP POLICY IF EXISTS "materiel-roulant - update authentifié" ON storage.objects;
|
|
CREATE POLICY "materiel-roulant - update authentifié"
|
|
ON storage.objects FOR UPDATE
|
|
USING (bucket_id = 'materiel-roulant');
|
|
|
|
-- Suppression
|
|
DROP POLICY IF EXISTS "materiel-roulant - delete authentifié" ON storage.objects;
|
|
CREATE POLICY "materiel-roulant - delete authentifié"
|
|
ON storage.objects FOR DELETE
|
|
USING (bucket_id = 'materiel-roulant');
|
|
|
|
-- Création de la table "tarifs" pour gérer les offres de billets, abonnements, remises par région
|
|
CREATE TABLE IF NOT EXISTS tarifs (
|
|
id SERIAL PRIMARY KEY,
|
|
nom VARCHAR(255) NOT NULL,
|
|
type VARCHAR(50) NOT NULL, -- billet, abonnement, remise
|
|
description TEXT,
|
|
prix_minimum NUMERIC(10,2),
|
|
prix_par_km NUMERIC(10,3),
|
|
age_maximum INTEGER,
|
|
regions TEXT[] NOT NULL, -- tableau d'identifiants de régions
|
|
date_debut DATE,
|
|
date_fin DATE,
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
-- Index pour recherche rapide par région
|
|
CREATE INDEX IF NOT EXISTS idx_tarifs_regions ON tarifs USING GIN (regions);
|
|
|
|
-- v2: add trigger to keep updated_at in sync
|
|
CREATE OR REPLACE FUNCTION touch_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_touch_tarifs_updated_at ON tarifs;
|
|
CREATE TRIGGER trg_touch_tarifs_updated_at
|
|
BEFORE UPDATE ON tarifs
|
|
FOR EACH ROW
|
|
EXECUTE PROCEDURE touch_updated_at();
|
|
|
|
|
|
-- ============================================================================
|
|
-- CRÉATION DES TABLES POUR LA GESTION DES TRAVAUX
|
|
-- ============================================================================
|
|
|
|
-- Table principale des travaux
|
|
CREATE TABLE IF NOT EXISTS travaux (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
titre TEXT NOT NULL,
|
|
description TEXT,
|
|
date_debut DATE NOT NULL,
|
|
date_fin DATE NOT NULL,
|
|
jours_travaux TEXT[] NOT NULL, -- ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche']
|
|
heure_debut TIME NOT NULL,
|
|
heure_fin TIME NOT NULL,
|
|
actif BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
|
deleted_at TIMESTAMP WITH TIME ZONE,
|
|
|
|
CONSTRAINT valid_dates CHECK (date_fin >= date_debut),
|
|
CONSTRAINT valid_hours CHECK (heure_fin > heure_debut)
|
|
);
|
|
|
|
-- Table de liaison entre travaux et sillons de substitution
|
|
CREATE TABLE IF NOT EXISTS travaux_substitutions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
travaux_id UUID NOT NULL REFERENCES travaux(id) ON DELETE CASCADE,
|
|
horaire_id UUID NOT NULL REFERENCES horaires(id) ON DELETE CASCADE,
|
|
ordre INTEGER DEFAULT 0, -- Ordre d'affichage des substitutions
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
|
|
|
UNIQUE(travaux_id, horaire_id)
|
|
);
|
|
|
|
-- Index pour améliorer les performances
|
|
CREATE INDEX IF NOT EXISTS idx_travaux_dates ON travaux(date_debut, date_fin);
|
|
CREATE INDEX IF NOT EXISTS idx_travaux_actif ON travaux(actif);
|
|
CREATE INDEX IF NOT EXISTS idx_travaux_deleted ON travaux(deleted_at);
|
|
CREATE INDEX IF NOT EXISTS idx_travaux_substitutions_travaux ON travaux_substitutions(travaux_id);
|
|
CREATE INDEX IF NOT EXISTS idx_travaux_substitutions_horaire ON travaux_substitutions(horaire_id);
|
|
|
|
-- Fonction pour mettre à jour updated_at automatiquement
|
|
CREATE OR REPLACE FUNCTION update_travaux_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = now();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Trigger pour mettre à jour updated_at
|
|
DROP TRIGGER IF EXISTS trigger_update_travaux_updated_at ON travaux;
|
|
CREATE TRIGGER trigger_update_travaux_updated_at
|
|
BEFORE UPDATE ON travaux
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_travaux_updated_at();
|
|
|
|
-- Commentaires sur les tables
|
|
COMMENT ON TABLE travaux IS 'Table principale pour la gestion des travaux sur les lignes';
|
|
COMMENT ON TABLE travaux_substitutions IS 'Table de liaison entre travaux et horaires de substitution';
|
|
|
|
COMMENT ON COLUMN travaux.jours_travaux IS 'Jours de la semaine où les travaux ont lieu';
|
|
COMMENT ON COLUMN travaux.actif IS 'Indique si les travaux sont actifs';
|
|
COMMENT ON COLUMN travaux.deleted_at IS 'Date de suppression (soft delete)';
|
|
COMMENT ON COLUMN travaux_substitutions.ordre IS 'Ordre d''affichage des substitutions';
|
|
|
|
-- ============================================================================
|
|
-- DONNÉES DE TEST (optionnel)
|
|
-- ============================================================================
|
|
|
|
-- Exemple de travaux
|
|
INSERT INTO travaux (titre, description, date_debut, date_fin, jours_travaux, heure_debut, heure_fin, actif)
|
|
VALUES
|
|
('Travaux ligne Paris-Lyon', 'Rénovation des voies', '2026-03-01', '2026-03-31', ARRAY['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi'], '09:00', '17:00', true),
|
|
('Maintenance gare de Lyon', 'Travaux de maintenance planifiés', '2026-04-15', '2026-04-20', ARRAY['samedi', 'dimanche'], '06:00', '22:00', true)
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- Afficher le résultat
|
|
SELECT
|
|
'Tables travaux créées avec succès' as statut,
|
|
(SELECT COUNT(*) FROM travaux) as nombre_travaux,
|
|
(SELECT COUNT(*) FROM travaux_substitutions) as nombre_substitutions;
|
|
|
|
-- ============================================================================
|
|
-- CORRECTIF : Policies RLS du bucket "banque-annonces" et des tables SQL
|
|
-- Date : 2026-07-02
|
|
--
|
|
-- Problème : L'application utilise la clé anon et une authentification custom. Les
|
|
-- policies de banque_mots et de storage.objects qui exigent un utilisateur
|
|
-- authentifié (TO authenticated) bloquent l'INSERT/UPDATE/DELETE et provoquent
|
|
-- l'erreur "new row violates row-level security policy".
|
|
--
|
|
-- Solution : Recréer des policies pour permettre SELECT, INSERT, UPDATE, DELETE
|
|
-- sur le bucket 'banque-annonces' et sur les tables de la base de données.
|
|
--
|
|
-- ⚠️ À EXÉCUTER dans l'éditeur SQL Supabase :
|
|
-- https://app.supabase.com → votre projet → SQL Editor
|
|
-- ============================================================================
|
|
|
|
-- 1. Réglage des politiques RLS pour les tables de base de données
|
|
-- ============================================================================
|
|
|
|
-- Table: banque_mots
|
|
DROP POLICY IF EXISTS "Authenticated users can read banque_mots" ON public.banque_mots;
|
|
DROP POLICY IF EXISTS "Authenticated users can insert banque_mots" ON public.banque_mots;
|
|
DROP POLICY IF EXISTS "Authenticated users can update banque_mots" ON public.banque_mots;
|
|
|
|
CREATE POLICY "Public read banque_mots" ON public.banque_mots FOR SELECT USING (deleted_at IS NULL);
|
|
CREATE POLICY "Public insert banque_mots" ON public.banque_mots FOR INSERT WITH CHECK (true);
|
|
CREATE POLICY "Public update banque_mots" ON public.banque_mots FOR UPDATE USING (true);
|
|
CREATE POLICY "Public delete banque_mots" ON public.banque_mots FOR DELETE USING (true);
|
|
|
|
-- Table: annonces_templates
|
|
DROP POLICY IF EXISTS "Authenticated users can read templates" ON public.annonces_templates;
|
|
DROP POLICY IF EXISTS "Authenticated users can insert templates" ON public.annonces_templates;
|
|
DROP POLICY IF EXISTS "Authenticated users can update templates" ON public.annonces_templates;
|
|
|
|
CREATE POLICY "Public read templates" ON public.annonces_templates FOR SELECT USING (deleted_at IS NULL);
|
|
CREATE POLICY "Public insert templates" ON public.annonces_templates FOR INSERT WITH CHECK (true);
|
|
CREATE POLICY "Public update templates" ON public.annonces_templates FOR UPDATE USING (true);
|
|
CREATE POLICY "Public delete templates" ON public.annonces_templates FOR DELETE USING (true);
|
|
|
|
-- Table: annonces_enregistrees
|
|
DROP POLICY IF EXISTS "Authenticated users can read annonces" ON public.annonces_enregistrees;
|
|
DROP POLICY IF EXISTS "Authenticated users can insert annonces" ON public.annonces_enregistrees;
|
|
DROP POLICY IF EXISTS "Authenticated users can update annonces" ON public.annonces_enregistrees;
|
|
|
|
CREATE POLICY "Public read annonces" ON public.annonces_enregistrees FOR SELECT USING (deleted_at IS NULL);
|
|
CREATE POLICY "Public insert annonces" ON public.annonces_enregistrees FOR INSERT WITH CHECK (true);
|
|
CREATE POLICY "Public update annonces" ON public.annonces_enregistrees FOR UPDATE USING (true);
|
|
CREATE POLICY "Public delete annonces" ON public.annonces_enregistrees FOR DELETE USING (true);
|
|
|
|
|
|
-- 2. Réglage des politiques RLS du Bucket Storage "banque-annonces"
|
|
-- ============================================================================
|
|
|
|
-- Supprimer les anciennes politiques storage si elles existent
|
|
DROP POLICY IF EXISTS "Public read access on banque-annonces" ON storage.objects;
|
|
DROP POLICY IF EXISTS "Authenticated users can upload to banque-annonces" ON storage.objects;
|
|
DROP POLICY IF EXISTS "Authenticated users can delete from banque-annonces" ON storage.objects;
|
|
DROP POLICY IF EXISTS "Public upload access on banque-annonces" ON storage.objects;
|
|
DROP POLICY IF EXISTS "Public update access on banque-annonces" ON storage.objects;
|
|
DROP POLICY IF EXISTS "Public delete access on banque-annonces" ON storage.objects;
|
|
|
|
-- Lecture publique pour tous
|
|
CREATE POLICY "Public read access on banque-annonces"
|
|
ON storage.objects FOR SELECT
|
|
USING (bucket_id = 'banque-annonces');
|
|
|
|
-- Insertion publique (Upload)
|
|
CREATE POLICY "Public upload access on banque-annonces"
|
|
ON storage.objects FOR INSERT
|
|
WITH CHECK (bucket_id = 'banque-annonces');
|
|
|
|
-- Mise à jour publique (Upsert)
|
|
CREATE POLICY "Public update access on banque-annonces"
|
|
ON storage.objects FOR UPDATE
|
|
USING (bucket_id = 'banque-annonces');
|
|
|
|
-- Suppression publique (Delete)
|
|
CREATE POLICY "Public delete access on banque-annonces"
|
|
ON storage.objects FOR DELETE
|
|
USING (bucket_id = 'banque-annonces');
|
|
-- ============================================================================
|
|
-- CORRECTIF : Policies RLS du bucket "fiches-horaires"
|
|
-- Date : 2026-03-01
|
|
--
|
|
-- Problème : L'application utilise la clé anon et une authentification custom. Les
|
|
-- policies de storage.objects qui exigent auth.uid() bloquent l'INSERT
|
|
-- et provoquent l'erreur "new row violates row-level security policy".
|
|
--
|
|
-- Solution : Recréer des policies pour permettre l'INSERT/UPDATE/DELETE/SELECT
|
|
-- sur le bucket 'fiches-horaires' en se basant sur bucket_id seulement.
|
|
--
|
|
-- ⚠️ À EXÉCUTER dans l'éditeur SQL Supabase :
|
|
-- https://app.supabase.com → votre projet → SQL Editor
|
|
-- ============================================================================
|
|
|
|
-- Supprimer d'abord les policies existantes (si elles existent)
|
|
DROP POLICY IF EXISTS "fiches-horaires - lecture publique" ON storage.objects;
|
|
DROP POLICY IF EXISTS "fiches-horaires - upload" ON storage.objects;
|
|
DROP POLICY IF EXISTS "fiches-horaires - update" ON storage.objects;
|
|
DROP POLICY IF EXISTS "fiches-horaires - delete" ON storage.objects;
|
|
|
|
-- Créer des policies permissives basées sur le bucket_id
|
|
CREATE POLICY "fiches-horaires - lecture publique"
|
|
ON storage.objects FOR SELECT
|
|
USING (bucket_id = 'fiches-horaires');
|
|
|
|
CREATE POLICY "fiches-horaires - upload"
|
|
ON storage.objects FOR INSERT
|
|
WITH CHECK (bucket_id = 'fiches-horaires');
|
|
|
|
CREATE POLICY "fiches-horaires - update"
|
|
ON storage.objects FOR UPDATE
|
|
USING (bucket_id = 'fiches-horaires');
|
|
|
|
CREATE POLICY "fiches-horaires - delete"
|
|
ON storage.objects FOR DELETE
|
|
USING (bucket_id = 'fiches-horaires');
|
|
|
|
-- Note : Si vous souhaitez restreindre les uploads aux utilisateurs authentifiés,
|
|
-- remplacez la clause WITH CHECK (bucket_id = 'fiches-horaires') par
|
|
-- WITH CHECK (bucket_id = 'fiches-horaires' AND auth.uid() IS NOT NULL)
|
|
-- et assurez-vous que vos utilisateurs sont bien authentifiés côté front.
|
|
|
|
-- ============================================================================
|
|
-- CORRECTIF : Policies RLS du bucket "materiel-roulant"
|
|
-- Date : 2026-03-01
|
|
--
|
|
-- Problème : L'application utilise une authentification custom (RPC authenticate_user
|
|
-- + localStorage). supabase.auth.signIn() n'est jamais appelé, donc
|
|
-- auth.uid() est toujours NULL → la policy WITH CHECK auth.uid() IS NOT NULL
|
|
-- bloque tous les uploads.
|
|
--
|
|
-- Solution : Policies ouvertes sur le bucket (accès par clé anon publique).
|
|
-- Le bucket est déjà créé — ce script corrige uniquement les policies.
|
|
--
|
|
-- ⚠️ À EXÉCUTER dans l'éditeur SQL Supabase :
|
|
-- https://supabase.com/dashboard → votre projet → SQL Editor
|
|
-- ============================================================================
|
|
|
|
-- Supprimer toutes les policies existantes du bucket
|
|
DROP POLICY IF EXISTS "materiel-roulant - lecture publique" ON storage.objects;
|
|
DROP POLICY IF EXISTS "materiel-roulant - upload authentifié" ON storage.objects;
|
|
DROP POLICY IF EXISTS "materiel-roulant - update authentifié" ON storage.objects;
|
|
DROP POLICY IF EXISTS "materiel-roulant - delete authentifié" ON storage.objects;
|
|
|
|
-- Recréer les policies sans condition auth.uid()
|
|
-- (l'app utilise la clé anon, auth.uid() est toujours NULL)
|
|
|
|
CREATE POLICY "materiel-roulant - lecture publique"
|
|
ON storage.objects FOR SELECT
|
|
USING (bucket_id = 'materiel-roulant');
|
|
|
|
CREATE POLICY "materiel-roulant - upload authentifié"
|
|
ON storage.objects FOR INSERT
|
|
WITH CHECK (bucket_id = 'materiel-roulant');
|
|
|
|
CREATE POLICY "materiel-roulant - update authentifié"
|
|
ON storage.objects FOR UPDATE
|
|
USING (bucket_id = 'materiel-roulant');
|
|
|
|
CREATE POLICY "materiel-roulant - delete authentifié"
|
|
ON storage.objects FOR DELETE
|
|
USING (bucket_id = 'materiel-roulant');
|
|
|
|
-- Migration: repair foreign key between horaires_gares_desservies.horaire_id and horaires.id
|
|
-- This script will:
|
|
-- 1) check the column types of horaires.id and horaires_gares_desservies.horaire_id
|
|
-- 2) if types match and FK doesn't exist, create the FK constraint
|
|
-- 3) otherwise, print a NOTICE explaining the mismatch for manual resolution
|
|
|
|
DO $$
|
|
DECLARE
|
|
t_horaire TEXT;
|
|
t_horaire_fk TEXT;
|
|
cnt INT;
|
|
BEGIN
|
|
-- ensure target tables exist
|
|
IF NOT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'horaires' AND relkind = 'r') THEN
|
|
RAISE NOTICE 'Table public.horaires does not exist in this database/schema.';
|
|
RETURN;
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'horaires_gares_desservies' AND relkind = 'r') THEN
|
|
RAISE NOTICE 'Table public.horaires_gares_desservies does not exist in this database/schema.';
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- get formatted types
|
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
|
INTO t_horaire
|
|
FROM pg_attribute a
|
|
WHERE a.attrelid = 'public.horaires'::regclass AND a.attname = 'id';
|
|
|
|
SELECT pg_catalog.format_type(a.atttypid, a.atttypmod)
|
|
INTO t_horaire_fk
|
|
FROM pg_attribute a
|
|
WHERE a.attrelid = 'public.horaires_gares_desservies'::regclass AND a.attname = 'horaire_id';
|
|
|
|
RAISE NOTICE 'Detected types -> horaires.id: %, horaires_gares_desservies.horaire_id: %', t_horaire, t_horaire_fk;
|
|
|
|
-- check existing FK from horaires_gares_desservies to horaires
|
|
SELECT count(*) INTO cnt
|
|
FROM pg_constraint c
|
|
WHERE c.conrelid = 'public.horaires_gares_desservies'::regclass
|
|
AND c.confrelid = 'public.horaires'::regclass
|
|
AND c.contype = 'f';
|
|
|
|
IF cnt > 0 THEN
|
|
RAISE NOTICE 'A foreign-key constraint from horaires_gares_desservies to horaires already exists (count=%). No action taken.', cnt;
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- If types match, create the FK
|
|
IF t_horaire IS NOT NULL AND t_horaire_fk IS NOT NULL AND t_horaire = t_horaire_fk THEN
|
|
EXECUTE 'ALTER TABLE public.horaires_gares_desservies
|
|
ADD CONSTRAINT fk_horaires_gares_horaire_id
|
|
FOREIGN KEY (horaire_id)
|
|
REFERENCES public.horaires(id)
|
|
ON DELETE CASCADE';
|
|
RAISE NOTICE 'Foreign key constraint fk_horaires_gares_horaire_id created successfully.';
|
|
ELSE
|
|
RAISE NOTICE 'Column types do not match or could not be determined. Manual intervention required.';
|
|
RAISE NOTICE 'Detected types -> horaires.id: %, horaires_gares_desservies.horaire_id: %', t_horaire, t_horaire_fk;
|
|
RAISE NOTICE 'If both should be UUID, consider converting [or recreating] the columns and then re-run this script.';
|
|
RAISE NOTICE 'If both should be BIGINT, consider altering types to bigint. Do not run automatic conversion without backup.';
|
|
END IF;
|
|
END $$;
|
|
-- ============================================================================
|
|
-- DONNÉES DE TEST: Perturbations
|
|
-- Description: Jeux de données pour tester le système de perturbations
|
|
-- ============================================================================
|
|
|
|
-- Note: Ces données sont à insérer APRÈS avoir des horaires existants dans la base
|
|
|
|
-- Exemple 1: Retard sur un train
|
|
-- Remplacer 'HORAIRE_ID_1' par un véritable ID d'horaire existant
|
|
INSERT INTO perturbations (
|
|
horaire_id,
|
|
type_perturbation,
|
|
duree_retard_minutes,
|
|
cause,
|
|
cause_details,
|
|
date_debut,
|
|
date_fin,
|
|
actif,
|
|
notes
|
|
) VALUES (
|
|
(SELECT id FROM horaires LIMIT 1), -- Premier horaire de la base
|
|
'retard',
|
|
25,
|
|
'incident_technique',
|
|
'Panne de signalisation à Paris-Montparnasse',
|
|
NOW(),
|
|
NOW() + INTERVAL '2 hours',
|
|
true,
|
|
'Retard estimé de 25 minutes sur l''ensemble du parcours'
|
|
);
|
|
|
|
-- Exemple 2: Suppression de train avec substitution
|
|
INSERT INTO perturbations (
|
|
horaire_id,
|
|
type_perturbation,
|
|
duree_retard_minutes,
|
|
cause,
|
|
cause_details,
|
|
sillon_substitution_id,
|
|
date_debut,
|
|
date_fin,
|
|
actif,
|
|
notes
|
|
) VALUES (
|
|
(SELECT id FROM horaires LIMIT 1 OFFSET 1), -- Deuxième horaire
|
|
'suppression',
|
|
NULL,
|
|
'mouvement_social',
|
|
'Grève nationale du personnel roulant',
|
|
(SELECT id FROM horaires LIMIT 1 OFFSET 2), -- Train de substitution
|
|
NOW(),
|
|
NOW() + INTERVAL '1 day',
|
|
true,
|
|
'Train supprimé - Report sur le train suivant disponible'
|
|
);
|
|
|
|
-- Exemple 3: Modification de parcours
|
|
DO $$
|
|
DECLARE
|
|
v_perturbation_id UUID;
|
|
v_horaire_id UUID;
|
|
BEGIN
|
|
-- Récupérer un horaire avec des gares desservies
|
|
SELECT h.id INTO v_horaire_id
|
|
FROM horaires h
|
|
WHERE EXISTS (
|
|
SELECT 1 FROM horaires_gares_desservies hgd
|
|
WHERE hgd.horaire_id = h.id
|
|
)
|
|
LIMIT 1;
|
|
|
|
IF v_horaire_id IS NULL THEN
|
|
RAISE NOTICE 'Aucun horaire avec gares desservies trouvé, test ignoré';
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Créer la perturbation
|
|
INSERT INTO perturbations (
|
|
horaire_id,
|
|
type_perturbation,
|
|
duree_retard_minutes,
|
|
cause,
|
|
cause_details,
|
|
date_debut,
|
|
actif,
|
|
notes
|
|
) VALUES (
|
|
v_horaire_id,
|
|
'modification_parcours',
|
|
NULL,
|
|
'travaux',
|
|
'Travaux de maintenance sur la voie entre Lyon et Valence',
|
|
NOW(),
|
|
true,
|
|
'Modification du parcours : certaines gares ne seront pas desservies'
|
|
) RETURNING id INTO v_perturbation_id;
|
|
|
|
-- Ajouter des modifications de parcours
|
|
-- Supprimer une gare du parcours
|
|
INSERT INTO perturbations_modifications_parcours (
|
|
perturbation_id,
|
|
type_modification,
|
|
gare_id,
|
|
gare_nom,
|
|
ordre
|
|
) SELECT
|
|
v_perturbation_id,
|
|
'gare_supprimee',
|
|
gare_id,
|
|
gare_nom,
|
|
ordre
|
|
FROM horaires_gares_desservies
|
|
WHERE horaire_id = v_horaire_id
|
|
LIMIT 1;
|
|
|
|
RAISE NOTICE 'Perturbation de modification de parcours créée avec succès';
|
|
END $$;
|
|
|
|
-- Exemple 4: Retard causé par les conditions météorologiques
|
|
INSERT INTO perturbations (
|
|
horaire_id,
|
|
type_perturbation,
|
|
duree_retard_minutes,
|
|
cause,
|
|
cause_details,
|
|
date_debut,
|
|
date_fin,
|
|
actif,
|
|
notes
|
|
) VALUES (
|
|
(SELECT id FROM horaires LIMIT 1 OFFSET 3),
|
|
'retard',
|
|
45,
|
|
'conditions_meteorologiques',
|
|
'Fortes chutes de neige en Auvergne-Rhône-Alpes',
|
|
NOW(),
|
|
NOW() + INTERVAL '4 hours',
|
|
true,
|
|
'Circulation ralentie sur l''ensemble des lignes'
|
|
);
|
|
|
|
-- Exemple 5: Perturbation inactive (historique)
|
|
INSERT INTO perturbations (
|
|
horaire_id,
|
|
type_perturbation,
|
|
duree_retard_minutes,
|
|
cause,
|
|
cause_details,
|
|
date_debut,
|
|
date_fin,
|
|
actif,
|
|
notes
|
|
) VALUES (
|
|
(SELECT id FROM horaires LIMIT 1 OFFSET 4),
|
|
'retard',
|
|
10,
|
|
'affluence',
|
|
'Affluence exceptionnelle lors du départ',
|
|
NOW() - INTERVAL '1 day',
|
|
NOW() - INTERVAL '1 day' + INTERVAL '30 minutes',
|
|
false,
|
|
'Perturbation terminée - Retard résorbé'
|
|
);
|
|
|
|
-- Afficher un résumé des perturbations de test créées
|
|
SELECT
|
|
'Données de test créées' as statut,
|
|
COUNT(*) as nombre_perturbations_test,
|
|
COUNT(*) FILTER (WHERE actif = true) as perturbations_actives,
|
|
COUNT(*) FILTER (WHERE actif = false) as perturbations_inactives
|
|
FROM perturbations
|
|
WHERE notes LIKE '%test%' OR notes LIKE '%Test%';
|
|
|
|
-- Afficher les perturbations créées
|
|
SELECT
|
|
p.id,
|
|
p.type_perturbation,
|
|
p.duree_retard_minutes,
|
|
p.cause,
|
|
p.actif,
|
|
h.numero_train,
|
|
h.gare_depart_nom,
|
|
h.gare_arrivee_nom
|
|
FROM perturbations p
|
|
JOIN horaires h ON p.horaire_id = h.id
|
|
ORDER BY p.created_at DESC
|
|
LIMIT 10;
|
|
|
|
-- ============================================================================
|
|
-- NETTOYAGE DES DONNÉES DE TEST
|
|
-- ============================================================================
|
|
-- Décommenter pour supprimer toutes les données de test
|
|
/*
|
|
DELETE FROM perturbations_modifications_parcours
|
|
WHERE perturbation_id IN (
|
|
SELECT id FROM perturbations
|
|
WHERE notes LIKE '%test%' OR notes LIKE '%Test%'
|
|
);
|
|
|
|
DELETE FROM perturbations
|
|
WHERE notes LIKE '%test%' OR notes LIKE '%Test%';
|
|
|
|
SELECT 'Données de test supprimées' as statut;
|
|
*/
|
|
-- MIGRATION : Ajout de la colonne quais_hall (JSONB) dans la table gares
|
|
|
|
ALTER TABLE public.gares
|
|
ADD COLUMN IF NOT EXISTS quais_hall JSONB DEFAULT '{"hall": {}}'::jsonb;
|
|
|
|
COMMENT ON COLUMN public.gares.quais_hall IS
|
|
'Association des quais aux halls sous forme JSON : {"hall": {"Nom du Hall": ["Quai 1", "Quai 2"]}}';
|
|
-- Migration: 20260611000000_secure_all_tables.sql
|
|
-- Description: Enable RLS on all public tables and restrict INSERT/UPDATE/DELETE to authenticated users.
|
|
|
|
DO $$
|
|
DECLARE
|
|
r RECORD;
|
|
BEGIN
|
|
FOR r IN SELECT tablename FROM pg_tables WHERE schemaname = 'public'
|
|
LOOP
|
|
-- Enable RLS
|
|
EXECUTE 'ALTER TABLE public.' || quote_ident(r.tablename) || ' ENABLE ROW LEVEL SECURITY;';
|
|
|
|
-- Clean up existing potentially insecure policies to be sure
|
|
EXECUTE 'DROP POLICY IF EXISTS "Public read access" ON public.' || quote_ident(r.tablename) || ';';
|
|
EXECUTE 'DROP POLICY IF EXISTS "Enable insert for authenticated users only" ON public.' || quote_ident(r.tablename) || ';';
|
|
EXECUTE 'DROP POLICY IF EXISTS "Enable update for authenticated users only" ON public.' || quote_ident(r.tablename) || ';';
|
|
EXECUTE 'DROP POLICY IF EXISTS "Enable delete for authenticated users only" ON public.' || quote_ident(r.tablename) || ';';
|
|
|
|
-- Read policy (SELECT): Allow public read
|
|
EXECUTE 'CREATE POLICY "Public read access" ON public.' || quote_ident(r.tablename) || ' FOR SELECT USING (true);';
|
|
|
|
-- Insert policy: Allow anon (app uses custom auth, no Supabase Auth)
|
|
EXECUTE 'CREATE POLICY "Public insert access" ON public.' || quote_ident(r.tablename) || ' FOR INSERT WITH CHECK (true);';
|
|
|
|
-- Update policy: Allow anon
|
|
EXECUTE 'CREATE POLICY "Public update access" ON public.' || quote_ident(r.tablename) || ' FOR UPDATE USING (true);';
|
|
|
|
-- Delete policy: Prevent hard deletion for everyone (prevents third party deletion)
|
|
EXECUTE 'CREATE POLICY "Prevent hard deletion" ON public.' || quote_ident(r.tablename) || ' FOR DELETE USING (false);';
|
|
END LOOP;
|
|
END $$;
|
|
-- Migration: 20260611000001_fix_secure_all_tables.sql
|
|
-- Description: Fix RLS policies to allow public insert/update since app uses custom auth, but strictly prevent delete.
|
|
|
|
DO $$
|
|
DECLARE
|
|
r RECORD;
|
|
BEGIN
|
|
FOR r IN SELECT tablename FROM pg_tables WHERE schemaname = 'public'
|
|
LOOP
|
|
-- Clean up the overly restrictive policies from previous migration
|
|
EXECUTE 'DROP POLICY IF EXISTS "Enable insert for authenticated users only" ON public.' || quote_ident(r.tablename) || ';';
|
|
EXECUTE 'DROP POLICY IF EXISTS "Enable update for authenticated users only" ON public.' || quote_ident(r.tablename) || ';';
|
|
EXECUTE 'DROP POLICY IF EXISTS "Enable delete for authenticated users only" ON public.' || quote_ident(r.tablename) || ';';
|
|
|
|
-- Insert policy: Allow anon (app uses custom auth, no Supabase Auth)
|
|
EXECUTE 'DROP POLICY IF EXISTS "Public insert access" ON public.' || quote_ident(r.tablename) || ';';
|
|
EXECUTE 'CREATE POLICY "Public insert access" ON public.' || quote_ident(r.tablename) || ' FOR INSERT WITH CHECK (true);';
|
|
|
|
-- Update policy: Allow anon
|
|
EXECUTE 'DROP POLICY IF EXISTS "Public update access" ON public.' || quote_ident(r.tablename) || ';';
|
|
EXECUTE 'CREATE POLICY "Public update access" ON public.' || quote_ident(r.tablename) || ' FOR UPDATE USING (true);';
|
|
|
|
-- Delete policy: Prevent hard deletion for everyone (prevents third party deletion)
|
|
EXECUTE 'DROP POLICY IF EXISTS "Prevent hard deletion" ON public.' || quote_ident(r.tablename) || ';';
|
|
EXECUTE 'CREATE POLICY "Prevent hard deletion" ON public.' || quote_ident(r.tablename) || ' FOR DELETE USING (false);';
|
|
END LOOP;
|
|
END $$;
|
|
-- ============================================================================
|
|
-- MIGRATION : types_train — ajout de la colonne "logo_color_url"
|
|
--
|
|
-- Ajoute une colonne "logo_color_url" pour stocker l'URL du logo en couleur.
|
|
--
|
|
-- À exécuter dans : Supabase Dashboard > SQL Editor
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE "public"."types_train"
|
|
ADD COLUMN IF NOT EXISTS "logo_color_url" text;
|
|
-- ============================================================================
|
|
-- MIGRATION : info_gares — gare_ids uuid[] + ligne_ids uuid[]
|
|
--
|
|
-- Remplace les colonnes scalaires gare_id / ligne_id par des colonnes tableau
|
|
-- afin d'autoriser plusieurs gares et plusieurs lignes par info.
|
|
--
|
|
-- À exécuter dans : Supabase Dashboard > SQL Editor
|
|
-- ============================================================================
|
|
|
|
-- 1. Ajouter les nouvelles colonnes tableaux (nullables, par défaut vide)
|
|
ALTER TABLE "public"."info_gares"
|
|
ADD COLUMN IF NOT EXISTS "gare_ids" uuid[] NOT NULL DEFAULT '{}',
|
|
ADD COLUMN IF NOT EXISTS "ligne_ids" uuid[] NOT NULL DEFAULT '{}';
|
|
|
|
-- 2. Migrer les données existantes depuis les colonnes scalaires
|
|
UPDATE "public"."info_gares"
|
|
SET
|
|
gare_ids = CASE WHEN gare_id IS NOT NULL THEN ARRAY[gare_id] ELSE '{}' END,
|
|
ligne_ids = CASE WHEN ligne_id IS NOT NULL THEN ARRAY[ligne_id] ELSE '{}' END
|
|
WHERE deleted_at IS NULL;
|
|
|
|
-- 3. Supprimer les anciennes colonnes scalaires
|
|
-- (décommentez les lignes ci-dessous après avoir vérifié la migration)
|
|
-- ALTER TABLE "public"."info_gares" DROP COLUMN IF EXISTS "gare_id";
|
|
-- ALTER TABLE "public"."info_gares" DROP COLUMN IF EXISTS "ligne_id";
|
|
|
|
-- NOTE : les colonnes gare_id / ligne_id sont conservées pour l'instant.
|
|
-- Après validation, décommentez le bloc DROP COLUMN ci-dessus et ré-exécutez.
|
|
|
|
-- 4. Index GIN pour de meilleures performances sur les requêtes @> (contient)
|
|
CREATE INDEX IF NOT EXISTS idx_info_gares_gare_ids ON "public"."info_gares" USING GIN (gare_ids);
|
|
CREATE INDEX IF NOT EXISTS idx_info_gares_ligne_ids ON "public"."info_gares" USING GIN (ligne_ids);
|
|
-- ============================================================================
|
|
-- MIGRATION : info_gares — ajout de la colonne "type"
|
|
--
|
|
-- Ajoute une colonne "type" pour catégoriser les infos gares.
|
|
-- Les valeurs possibles sont gérées côté applicatif :
|
|
-- 'Information', 'Avertissement', 'Alerte', 'Urgence', 'Alerte Méteo'
|
|
--
|
|
-- À exécuter dans : Supabase Dashboard > SQL Editor
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE "public"."info_gares"
|
|
ADD COLUMN IF NOT EXISTS "type" text NOT NULL DEFAULT 'Information';
|
|
-- ============================================================================
|
|
-- FIX RLS : table info_gares
|
|
--
|
|
-- La table info_gares a RLS activé mais aucune policy n'est définie,
|
|
-- ce qui bloque toutes les opérations (INSERT, UPDATE, DELETE, SELECT).
|
|
--
|
|
-- Ce script suit le même pattern que les autres tables du projet
|
|
-- (menus_regionaux, parametres) : accès ouvert à tous les rôles
|
|
-- utilisant la clé anon/service (gestion côté application).
|
|
--
|
|
-- À exécuter dans : Supabase Dashboard > SQL Editor
|
|
-- ============================================================================
|
|
|
|
-- 1. S'assurer que RLS est bien activé sur la table
|
|
ALTER TABLE "public"."info_gares" ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- 2. Supprimer les éventuelles policies existantes (idempotent)
|
|
DROP POLICY IF EXISTS "info_gares_select" ON "public"."info_gares";
|
|
DROP POLICY IF EXISTS "info_gares_insert" ON "public"."info_gares";
|
|
DROP POLICY IF EXISTS "info_gares_update" ON "public"."info_gares";
|
|
DROP POLICY IF EXISTS "info_gares_delete" ON "public"."info_gares";
|
|
|
|
-- 3. Créer les 4 policies CRUD (lecture, insertion, modification, suppression)
|
|
CREATE POLICY "info_gares_select"
|
|
ON "public"."info_gares"
|
|
FOR SELECT
|
|
USING (true);
|
|
|
|
CREATE POLICY "info_gares_insert"
|
|
ON "public"."info_gares"
|
|
FOR INSERT
|
|
WITH CHECK (true);
|
|
|
|
CREATE POLICY "info_gares_update"
|
|
ON "public"."info_gares"
|
|
FOR UPDATE
|
|
USING (true);
|
|
|
|
CREATE POLICY "info_gares_delete"
|
|
ON "public"."info_gares"
|
|
FOR DELETE
|
|
USING (true);
|