Files
ferrovia-panel/schema_migration_propre.sql
T
mathis e017166cb5 RELEASE V.1.2.0 - Voici les fonctionnalités :
-> Mode sombre
-> Updater
-> Patch Host
2026-08-26 00:22:06 +02:00

1301 lines
57 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'
);
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", "region_admin" boolean, "region_id" "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, '
|| 'COALESCE(u.region_admin, false)::boolean as region_admin, '
|| 'u.region_id::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;
$_$;
CREATE OR REPLACE FUNCTION "public"."create_user_with_password"("p_email" "text", "p_nom" "text", "p_prenom" "text", "p_role" "text" DEFAULT 'utilisateur'::"text", "p_actif" boolean DEFAULT true, "p_password" "text" DEFAULT NULL::"text", "p_photo_url" "text" DEFAULT NULL::"text", "p_region_admin" boolean DEFAULT false, "p_region_id" "text" DEFAULT NULL::"text") RETURNS TABLE("id" "uuid", "email" "text", "nom" "text", "prenom" "text", "role" "text", "actif" boolean, "photo_url" "text", "region_admin" boolean, "region_id" "text")
LANGUAGE "plpgsql" SECURITY DEFINER
AS $$
DECLARE
new_id uuid;
BEGIN
INSERT INTO public.utilisateurs (email, nom, prenom, role, actif, password_hash, photo_url, region_admin, region_id, created_at)
VALUES (
lower(p_email),
p_nom,
p_prenom,
p_role,
p_actif,
CASE WHEN p_password IS NOT NULL AND p_password != '' THEN crypt(p_password, gen_salt('bf')) ELSE NULL END,
p_photo_url,
p_region_admin,
p_region_id,
NOW()
)
RETURNING utilisateurs.id INTO new_id;
RETURN QUERY
SELECT u.id, u.email::text, u.nom::text, u.prenom::text, u.role::text,
u.actif, u.photo_url::text, u.region_admin, u.region_id::text
FROM public.utilisateurs u
WHERE u.id = new_id;
END;
$$;
CREATE OR REPLACE FUNCTION "public"."get_gare_details"("p_gare_id" "uuid") RETURNS TABLE("id" "uuid", "nom" "text", "type" "public"."station_type", "services" "public"."service_type"[], "quais" "jsonb", "transports" "jsonb", "created_at" timestamp with time zone, "updated_at" timestamp with time zone)
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;
$$;
CREATE OR REPLACE FUNCTION "public"."get_station_details"("p_station_id" "uuid") RETURNS TABLE("id" "uuid", "nom" "text", "type" "public"."station_type", "services" "public"."service_type"[], "quais" "jsonb", "transports" "jsonb", "created_at" timestamp with time zone, "updated_at" timestamp with time zone)
LANGUAGE "plpgsql" STABLE SECURITY DEFINER
AS $$
BEGIN
RETURN QUERY
SELECT
s.id,
s.nom,
s.type,
s.services,
COALESCE(
(SELECT jsonb_agg(jsonb_build_object('id', q.id, 'nom', q.nom, 'distance_metres', q.distance_metres))
FROM public.station_quais q WHERE q.station_id = s.id),
'[]'::jsonb
) as quais,
COALESCE(
(SELECT jsonb_agg(jsonb_build_object('id', t.id, 'type', t.type, 'ligne', t.ligne))
FROM public.station_transports t WHERE t.station_id = s.id),
'[]'::jsonb
) as transports,
s.created_at,
s.updated_at
FROM public.stations s
WHERE s.id = p_station_id AND s.deleted_at IS NULL;
END;
$$;
CREATE OR REPLACE FUNCTION "public"."handle_updated_at"() RETURNS "trigger"
LANGUAGE "plpgsql"
AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$;
CREATE OR REPLACE FUNCTION "public"."import_gtfs_rpc"("payload" json) RETURNS json
LANGUAGE "plpgsql"
AS $$
DECLARE
res json := json_build_object('status', 'ok', 'created', 0, 'warnings', json_build_array());
BEGIN
-- Exemple minimal : cette fonction doit être étendue selon vos règles métier.
-- Ici on ne fait rien, juste renvoyer un résultat.
RETURN res;
EXCEPTION WHEN OTHERS THEN
RETURN json_build_object('status', 'error', 'message', SQLERRM);
END;
$$;
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;
$$;
CREATE OR REPLACE FUNCTION "public"."set_updated_at"() RETURNS "trigger"
LANGUAGE "plpgsql"
AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$;
CREATE OR REPLACE FUNCTION "public"."update_travaux_updated_at"() RETURNS "trigger"
LANGUAGE "plpgsql"
AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$;
CREATE OR REPLACE FUNCTION "public"."update_updated_at_column"() RETURNS "trigger"
LANGUAGE "plpgsql"
AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$;
CREATE OR REPLACE FUNCTION "public"."update_user_password"("p_user_id" "uuid", "p_password" "text") RETURNS "void"
LANGUAGE "plpgsql" SECURITY DEFINER
AS $$
BEGIN
IF p_password IS NULL OR p_password = '' THEN
RAISE EXCEPTION 'Le mot de passe ne peut pas être vide';
END IF;
UPDATE public.utilisateurs
SET password_hash = crypt(p_password, gen_salt('bf')),
updated_at = NOW()
WHERE id = p_user_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Utilisateur non trouvé';
END IF;
END;
$$;
CREATE OR REPLACE FUNCTION "public"."update_utilisateurs_updated_at"() RETURNS "trigger"
LANGUAGE "plpgsql"
AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$;
CREATE TABLE IF NOT EXISTS "public"."actualites" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"titre" character varying(255) NOT NULL,
"contenu" "text" NOT NULL,
"resume" character varying(500),
"categorie" character varying(50),
"image_url" character varying(500),
"date_publication" timestamp with time zone DEFAULT "now"() NOT NULL,
"auteur" character varying(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,
"region_id" character varying(100),
"created_by" "uuid",
"image_couverture" "text",
CONSTRAINT "actualites_categorie_check" CHECK ((("categorie")::"text" = ANY ((ARRAY['general'::character varying, 'service'::character varying, 'promotion'::character varying, 'partenariat'::character varying, 'autre'::character varying])::"text"[])))
);
COMMENT ON TABLE "public"."actualites" IS 'Actualités et nouvelles du réseau';
COMMENT ON COLUMN "public"."actualites"."region_id" IS 'Identifiant de la région concernée par l''actualité';
COMMENT ON COLUMN "public"."actualites"."created_by" IS 'Utilisateur ayant créé l''actualité';
CREATE TABLE IF NOT EXISTS "public"."audit_logs" (
"id" bigint NOT NULL,
"table_name" "text" NOT NULL,
"operation" "text" NOT NULL,
"record_id" "uuid",
"changed_by" "uuid",
"old_data" "jsonb",
"new_data" "jsonb",
"changed_at" timestamp with time zone DEFAULT "now"() NOT NULL
);
CREATE SEQUENCE IF NOT EXISTS "public"."audit_logs_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE "public"."audit_logs_id_seq" OWNED BY "public"."audit_logs"."id";
CREATE TABLE IF NOT EXISTS "public"."evenements" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"titre" character varying(255) NOT NULL,
"description" "text",
"lieu" character varying(255),
"gare_id" "uuid",
"date_debut" timestamp with time zone NOT NULL,
"date_fin" timestamp with time zone,
"image_url" character varying(500),
"lien_externe" character varying(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,
"region_id" character varying(100),
"created_by" "uuid",
"visible_site" boolean DEFAULT true
);
COMMENT ON TABLE "public"."evenements" IS 'Événements à venir dans les gares ou sur le réseau';
COMMENT ON COLUMN "public"."evenements"."region_id" IS 'Identifiant de la région concernée par l''événement';
COMMENT ON COLUMN "public"."evenements"."created_by" IS 'Utilisateur ayant créé l''événement';
CREATE TABLE IF NOT EXISTS "public"."gare_quais" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"gare_id" "uuid" NOT NULL,
"nom" "text" NOT NULL,
"distance_metres" integer DEFAULT 0 NOT NULL,
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL
);
CREATE TABLE IF NOT EXISTS "public"."gare_transports" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"gare_id" "uuid" NOT NULL,
"type" "public"."transport_type" NOT NULL,
"ligne" "text",
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL
);
CREATE TABLE IF NOT EXISTS "public"."gares" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"nom" "text" NOT NULL,
"type" "public"."station_type" DEFAULT 'ville'::"public"."station_type" NOT NULL,
"services" "public"."service_type"[] DEFAULT '{}'::"public"."service_type"[] NOT NULL,
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
"updated_at" timestamp with time zone DEFAULT "now"() NOT NULL,
"deleted_at" timestamp with time zone,
"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": []
}';
CREATE TABLE IF NOT EXISTS "public"."horaires" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"numero_train" character varying(50) NOT NULL,
"type_train" character varying(255),
"type_train_id" bigint,
"gare_depart_id" "uuid",
"gare_depart_nom" character varying(255),
"heure_depart" time without time zone,
"gare_arrivee_id" "uuid",
"gare_arrivee_nom" character varying(255),
"heure_arrivee" time without time zone,
"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,
"circule_dimanches_feries" boolean DEFAULT false,
"jours_personnalises" "jsonb" DEFAULT '[]'::"jsonb",
"jours_non_circulation" "jsonb" DEFAULT '[]'::"jsonb",
"materiel_roulant_id" bigint,
"ligne_id" "uuid",
"ligne_nom" character varying(255),
"service_annuel_id" "uuid",
"service_annuel_nom" character varying(255),
"est_substitution" boolean DEFAULT false,
"motif_substitution" character varying(255),
"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,
"gares_desservies" "jsonb" DEFAULT '[]'::"jsonb",
"composition_train" "jsonb" DEFAULT '[]'::"jsonb",
"substitution_disponible" boolean DEFAULT false,
"region_id" character varying(100)
);
COMMENT ON TABLE "public"."horaires" IS 'Table principale des horaires (sillons) de trains';
COMMENT ON COLUMN "public"."horaires"."numero_train" IS 'Numéro unique du train (ex: TER 12345)';
COMMENT ON COLUMN "public"."horaires"."type_train" IS 'Type de train (TER, TGV, Intercités, etc.)';
COMMENT ON COLUMN "public"."horaires"."type_train_id" IS 'Référence vers la table types_train pour le type de train';
COMMENT ON COLUMN "public"."horaires"."jours_personnalises" IS 'Dates spécifiques de circulation au format JSON';
COMMENT ON COLUMN "public"."horaires"."jours_non_circulation" IS 'Dates où le train ne circule pas au format JSON';
COMMENT ON COLUMN "public"."horaires"."ligne_id" IS 'Identifiant de la ligne associée au sillon';
COMMENT ON COLUMN "public"."horaires"."ligne_nom" IS 'Nom de la ligne associée (copie pour lecture)';
COMMENT ON COLUMN "public"."horaires"."service_annuel_id" IS 'Identifiant du service annuel associé (SA)';
COMMENT ON COLUMN "public"."horaires"."service_annuel_nom" IS 'Nom du service annuel associé (copie pour lecture)';
COMMENT ON COLUMN "public"."horaires"."est_substitution" IS 'Indique si ce sillon est une substitution autocar';
COMMENT ON COLUMN "public"."horaires"."motif_substitution" IS 'Motif de la substitution (travaux, etc.)';
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}, ...]';
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}, ...]';
COMMENT ON COLUMN "public"."horaires"."substitution_disponible" IS 'Indique si cet horaire est disponible comme sillon de substitution';
COMMENT ON COLUMN "public"."horaires"."region_id" IS 'Identifiant de la région associée à cet horaire';
CREATE TABLE IF NOT EXISTS "public"."horaires_gares_desservies" (
"id" bigint NOT NULL,
"gare_id" "uuid",
"gare_nom" character varying(255) NOT NULL,
"heure_arrivee" time without time zone,
"heure_depart" time without time zone,
"ordre" integer DEFAULT 0 NOT NULL,
"created_at" timestamp with time zone DEFAULT "now"(),
"updated_at" timestamp with time zone DEFAULT "now"(),
"horaire_id" "uuid",
"quai" character varying(50) DEFAULT NULL::character varying,
"quai_id" "uuid"
);
COMMENT ON TABLE "public"."horaires_gares_desservies" IS 'Gares intermédiaires desservies par chaque horaire';
COMMENT ON COLUMN "public"."horaires_gares_desservies"."ordre" IS 'Position de la gare dans le parcours (1 = première après départ)';
COMMENT ON COLUMN "public"."horaires_gares_desservies"."quai" IS 'Numéro ou nom du quai attribué à cet arrêt (ex: "Voie 3", "Quai A")';
CREATE SEQUENCE IF NOT EXISTS "public"."horaires_gares_desservies_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE "public"."horaires_gares_desservies_id_seq" OWNED BY "public"."horaires_gares_desservies"."id";
CREATE TABLE IF NOT EXISTS "public"."info_trafic" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"titre" character varying(255) NOT NULL,
"description" "text",
"type" character varying(50) NOT NULL,
"severite" character varying(20) NOT NULL,
"ligne" character varying(100),
"gare_id" "uuid",
"date_debut" timestamp with time zone DEFAULT "now"() NOT NULL,
"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,
"region_id" character varying(100),
"created_by" "uuid",
CONSTRAINT "info_trafic_severite_check" CHECK ((("severite")::"text" = ANY ((ARRAY['info'::character varying, 'warning'::character varying, 'critical'::character varying])::"text"[]))),
CONSTRAINT "info_trafic_type_check" CHECK ((("type")::"text" = ANY ((ARRAY['perturbation'::character varying, 'travaux'::character varying, 'greve'::character varying, 'meteo'::character varying, 'autre'::character varying])::"text"[])))
);
COMMENT ON TABLE "public"."info_trafic" IS 'Informations de trafic (perturbations, travaux, etc.)';
COMMENT ON COLUMN "public"."info_trafic"."region_id" IS 'Identifiant de la région concernée par l''info trafic';
COMMENT ON COLUMN "public"."info_trafic"."created_by" IS 'Utilisateur ayant créé l''info trafic';
CREATE TABLE IF NOT EXISTS "public"."lignes" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"nom" character varying(255) NOT NULL,
"code" character varying(50),
"gare_depart_id" "uuid",
"gare_arrivee_id" "uuid",
"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,
"fh_url" "text",
"region" character varying(100)
);
COMMENT ON TABLE "public"."lignes" IS 'Lignes de train avec gares de départ et d''arrivée';
COMMENT ON COLUMN "public"."lignes"."fh_url" IS 'URL publique du fichier PDF de la fiche horaire de la ligne (stocké dans Supabase Storage)';
CREATE TABLE IF NOT EXISTS "public"."lignes_gares" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"ligne_id" "uuid",
"gare_id" "uuid",
"ordre" integer NOT NULL,
"desserte" character varying(20) NOT NULL,
"created_at" timestamp with time zone DEFAULT "now"(),
CONSTRAINT "lignes_gares_desserte_check" CHECK ((("desserte")::"text" = ANY ((ARRAY['tous'::character varying, 'partielle'::character varying])::"text"[])))
);
COMMENT ON TABLE "public"."lignes_gares" IS 'Gares présentes sur le tracé d''une ligne avec type de desserte';
CREATE TABLE IF NOT EXISTS "public"."materiel-roulant" (
"id" bigint NOT NULL,
"nom" "text",
"nom_technique" "text",
"capacite" integer,
"image_url" "text",
"type_train" "text",
"numero_serie" "text",
"created_at" timestamp with time zone DEFAULT "now"(),
"updated_at" timestamp with time zone
);
ALTER TABLE "public"."materiel-roulant" ALTER COLUMN "id" ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME "public"."materiel-roulant_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
CREATE TABLE IF NOT EXISTS "public"."menus_regionaux" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"nom" character varying(200) NOT NULL,
"description" "text",
"actif" boolean DEFAULT false,
"structure" "jsonb" DEFAULT '[]'::"jsonb",
"created_at" timestamp with time zone DEFAULT "now"(),
"updated_at" timestamp with time zone DEFAULT "now"(),
"region_id" character varying(64)
);
CREATE TABLE IF NOT EXISTS "public"."messagerie_messages" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"thread_id" "uuid" NOT NULL,
"sender_id" "uuid" NOT NULL,
"contenu" "text" NOT NULL,
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL
);
CREATE TABLE IF NOT EXISTS "public"."messagerie_participants" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"thread_id" "uuid" NOT NULL,
"user_id" "uuid" NOT NULL,
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL
);
CREATE TABLE IF NOT EXISTS "public"."messagerie_threads" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
"updated_at" timestamp with time zone DEFAULT "now"() NOT NULL
);
CREATE TABLE IF NOT EXISTS "public"."page_personnalisation" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"page" "text" NOT NULL,
"config" "jsonb" DEFAULT '{}'::"jsonb",
"created_at" timestamp with time zone DEFAULT "now"(),
"updated_at" timestamp with time zone DEFAULT "now"()
);
CREATE TABLE IF NOT EXISTS "public"."parametres" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"cle" character varying(100) NOT NULL,
"valeur" "jsonb",
"created_at" timestamp with time zone DEFAULT "now"(),
"updated_at" timestamp with time zone DEFAULT "now"()
);
CREATE TABLE IF NOT EXISTS "public"."perturbations" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"horaire_id" "uuid" NOT NULL,
"type_perturbation" character varying(50) NOT NULL,
"duree_retard_minutes" integer,
"cause" character varying(100) NOT NULL,
"cause_details" "text",
"sillon_substitution_id" "uuid",
"date_debut" timestamp with time zone DEFAULT "now"() NOT NULL,
"date_fin" timestamp with time zone,
"actif" boolean DEFAULT true,
"notes" "text",
"created_at" timestamp with time zone DEFAULT "now"(),
"updated_at" timestamp with time zone DEFAULT "now"(),
"deleted_at" timestamp with time zone,
"region_id" character varying(100),
"created_by" "uuid",
"perturbations_arrets" "jsonb",
"jours_perturbation" "jsonb",
CONSTRAINT "perturbations_cause_check" CHECK ((("cause")::"text" = ANY ((ARRAY['incident_technique'::character varying, 'accident_personne'::character varying, 'conditions_meteorologiques'::character varying, 'travaux'::character varying, 'mouvement_social'::character varying, 'intervention_police'::character varying, 'affluence'::character varying, 'malveillance'::character varying, 'autre'::character varying])::"text"[]))),
CONSTRAINT "perturbations_type_perturbation_check" CHECK ((("type_perturbation")::"text" = ANY ((ARRAY['retard'::character varying, 'suppression'::character varying, 'modification_parcours'::character varying])::"text"[])))
);
COMMENT ON TABLE "public"."perturbations" IS 'Table des perturbations affectant les horaires de trains';
COMMENT ON COLUMN "public"."perturbations"."type_perturbation" IS 'Type de perturbation: retard, suppression, ou modification_parcours';
COMMENT ON COLUMN "public"."perturbations"."duree_retard_minutes" IS 'Durée du retard en minutes (applicable uniquement pour type=retard)';
COMMENT ON COLUMN "public"."perturbations"."cause" IS 'Cause de la perturbation';
COMMENT ON COLUMN "public"."perturbations"."sillon_substitution_id" IS 'Train de substitution proposé (si suppression ou modification)';
COMMENT ON COLUMN "public"."perturbations"."region_id" IS 'Identifiant de la région concernée par la perturbation';
COMMENT ON COLUMN "public"."perturbations"."created_by" IS 'Utilisateur ayant créé la perturbation';
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
}, ...]';
COMMENT ON COLUMN "public"."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"}]';
CREATE TABLE IF NOT EXISTS "public"."perturbations_modifications_parcours" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"perturbation_id" "uuid" NOT NULL,
"type_modification" character varying(20) NOT NULL,
"gare_id" "uuid",
"gare_nom" character varying(255) NOT NULL,
"nouvelle_heure_arrivee" time without time zone,
"nouvelle_heure_depart" time without time zone,
"ordre" integer,
"created_at" timestamp with time zone DEFAULT "now"(),
CONSTRAINT "perturbations_modifications_parcours_type_modification_check" CHECK ((("type_modification")::"text" = ANY ((ARRAY['gare_supprimee'::character varying, 'gare_ajoutee'::character varying, 'horaire_modifie'::character varying])::"text"[])))
);
COMMENT ON TABLE "public"."perturbations_modifications_parcours" IS 'Détails des modifications de parcours pour les perturbations';
CREATE TABLE IF NOT EXISTS "public"."profils_utilisateurs" (
"id" "uuid" NOT NULL,
"email" "text" NOT NULL,
"prenom" "text",
"nom" "text",
"date_naissance" "date",
"telephone" "text",
"carte_reduction" "text",
"gares_favorites" "text"[] DEFAULT '{}'::"text"[],
"lignes_favorites" "text"[] DEFAULT '{}'::"text"[],
"created_at" timestamp with time zone DEFAULT "now"(),
"updated_at" timestamp with time zone DEFAULT "now"(),
"cartes_bancaires" "jsonb" DEFAULT '[]'::"jsonb",
"mode_paiement_favori" "text",
"commandes" "jsonb" DEFAULT '[]'::"jsonb",
"panier" "jsonb" DEFAULT '[]'::"jsonb",
"trajets_favoris" "text"[] DEFAULT '{}'::"text"[]
);
COMMENT ON COLUMN "public"."profils_utilisateurs"."cartes_bancaires" IS 'Tableau JSON de cartes bancaires enregistrées. Chaque objet : {id, label, last4, expiry, brand, is_favorite}. Ne stocke JAMAIS le numéro complet.';
COMMENT ON COLUMN "public"."profils_utilisateurs"."mode_paiement_favori" IS 'Mode de paiement préféré : carte, paypal, apple_pay, google_pay ou null.';
COMMENT ON COLUMN "public"."profils_utilisateurs"."commandes" IS 'Historique des commandes. Chaque objet = commande complète avec items, passager, total, date et statut.';
COMMENT ON COLUMN "public"."profils_utilisateurs"."panier" IS 'Panier en cours. Tableau JSON d''items non encore payés.';
COMMENT ON COLUMN "public"."profils_utilisateurs"."trajets_favoris" IS 'Trajets favoris de l''utilisateur (ex: Dijon → Besançon).';
CREATE TABLE IF NOT EXISTS "public"."services_annuels" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"nom" character varying(255) NOT NULL,
"code" character varying(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
);
COMMENT ON TABLE "public"."services_annuels" IS 'Services annuels attribuables aux horaires';
CREATE TABLE IF NOT EXISTS "public"."tarifs" (
"id" integer NOT NULL,
"nom" character varying(255) NOT NULL,
"type" character varying(50) NOT NULL,
"description" "text",
"prix_minimum" numeric(10,2),
"prix_par_km" numeric(10,3),
"age_maximum" integer,
"regions" "text"[] NOT NULL,
"date_debut" "date",
"date_fin" "date",
"created_at" timestamp without time zone DEFAULT "now"(),
"updated_at" timestamp without time zone DEFAULT "now"(),
"ordre" integer DEFAULT 0 NOT NULL,
"page_dediee" boolean DEFAULT false,
"infos" "jsonb" DEFAULT '{}'::"jsonb"
);
CREATE SEQUENCE IF NOT EXISTS "public"."tarifs_id_seq"
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE "public"."tarifs_id_seq" OWNED BY "public"."tarifs"."id";
CREATE TABLE IF NOT EXISTS "public"."travaux" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"titre" "text" NOT NULL,
"description" "text",
"date_debut" "date" NOT NULL,
"date_fin" "date" NOT NULL,
"jours_travaux" "text"[] NOT NULL,
"heure_debut" time without time zone NOT NULL,
"heure_fin" time without time zone 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,
"ligne_id" "uuid",
"region_id" character varying(100),
"created_by" "uuid",
"substitutions_attribuees" "jsonb" DEFAULT '[]'::"jsonb",
"ligne_nom" "text",
CONSTRAINT "valid_dates" CHECK (("date_fin" >= "date_debut")),
CONSTRAINT "valid_hours" CHECK (("heure_fin" > "heure_debut"))
);
COMMENT ON TABLE "public"."travaux" IS 'Table principale pour la gestion des travaux sur les lignes';
COMMENT ON COLUMN "public"."travaux"."jours_travaux" IS 'Jours de la semaine où les travaux ont lieu';
COMMENT ON COLUMN "public"."travaux"."actif" IS 'Indique si les travaux sont actifs';
COMMENT ON COLUMN "public"."travaux"."deleted_at" IS 'Date de suppression (soft delete)';
COMMENT ON COLUMN "public"."travaux"."region_id" IS 'Identifiant de la région concernée par les travaux';
COMMENT ON COLUMN "public"."travaux"."created_by" IS 'Utilisateur ayant créé les travaux';
CREATE TABLE IF NOT EXISTS "public"."travaux_substitutions" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"travaux_id" "uuid" NOT NULL,
"horaire_id" "uuid" NOT NULL,
"ordre" integer DEFAULT 0,
"created_at" timestamp with time zone DEFAULT "now"()
);
COMMENT ON TABLE "public"."travaux_substitutions" IS 'Table de liaison entre travaux et horaires de substitution';
COMMENT ON COLUMN "public"."travaux_substitutions"."ordre" IS 'Ordre d''affichage des substitutions';
CREATE TABLE IF NOT EXISTS "public"."types_train" (
"id" bigint NOT NULL,
"nom" character varying(255) NOT NULL,
"code" character varying(50),
"description" "text",
"logo_url" "text",
"couleur" character varying(20) DEFAULT '#0074d9'::character varying,
"created_at" timestamp with time zone DEFAULT "now"(),
"updated_at" timestamp with time zone DEFAULT "now"(),
"deleted_at" timestamp with time zone
);
COMMENT ON TABLE "public"."types_train" IS 'Types de train configurables avec logos et couleurs';
CREATE SEQUENCE IF NOT EXISTS "public"."types_train_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE "public"."types_train_id_seq" OWNED BY "public"."types_train"."id";
CREATE TABLE IF NOT EXISTS "public"."utilisateurs" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"email" character varying(255) NOT NULL,
"nom" character varying(100) NOT NULL,
"prenom" character varying(100) NOT NULL,
"role" character varying(50) DEFAULT 'utilisateur'::character varying NOT NULL,
"actif" boolean DEFAULT true NOT NULL,
"password_hash" character varying(255),
"photo_url" "text",
"region_admin" boolean DEFAULT false,
"region_nom" character varying(255),
"created_at" timestamp with time zone DEFAULT "now"(),
"updated_at" timestamp with time zone DEFAULT "now"(),
"region_id" character varying(100),
"permissions" "jsonb" DEFAULT '{}'::"jsonb" NOT NULL
);
COMMENT ON TABLE "public"."utilisateurs" IS 'Utilisateurs de l''application avec leurs informations et rôles';
COMMENT ON COLUMN "public"."utilisateurs"."id" IS 'Identifiant unique de l''utilisateur';
COMMENT ON COLUMN "public"."utilisateurs"."email" IS 'Adresse email de l''utilisateur (unique)';
COMMENT ON COLUMN "public"."utilisateurs"."nom" IS 'Nom de famille de l''utilisateur';
COMMENT ON COLUMN "public"."utilisateurs"."prenom" IS 'Prénom de l''utilisateur';
COMMENT ON COLUMN "public"."utilisateurs"."role" IS 'Rôle de l''utilisateur (admin, gestionnaire, utilisateur)';
COMMENT ON COLUMN "public"."utilisateurs"."actif" IS 'Indique si l''utilisateur est actif ou désactivé';
COMMENT ON COLUMN "public"."utilisateurs"."password_hash" IS 'Hash du mot de passe (ne pas stocker en clair)';
COMMENT ON COLUMN "public"."utilisateurs"."photo_url" IS 'URL de la photo de profil';
COMMENT ON COLUMN "public"."utilisateurs"."region_admin" IS 'Indique si l''utilisateur administre une région';
COMMENT ON COLUMN "public"."utilisateurs"."region_nom" IS 'Nom de la région administrée (si applicable)';
COMMENT ON COLUMN "public"."utilisateurs"."created_at" IS 'Date de création de l''utilisateur';
COMMENT ON COLUMN "public"."utilisateurs"."updated_at" IS 'Date de dernière modification de l''utilisateur';
COMMENT ON COLUMN "public"."utilisateurs"."region_id" IS 'Identifiant de la région administrée par l''utilisateur';
ALTER TABLE ONLY "public"."audit_logs" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."audit_logs_id_seq"'::"regclass");
ALTER TABLE ONLY "public"."horaires_gares_desservies" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."horaires_gares_desservies_id_seq"'::"regclass");
ALTER TABLE ONLY "public"."tarifs" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."tarifs_id_seq"'::"regclass");
ALTER TABLE ONLY "public"."types_train" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."types_train_id_seq"'::"regclass");
ALTER TABLE ONLY "public"."actualites"
ADD CONSTRAINT "actualites_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."audit_logs"
ADD CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."evenements"
ADD CONSTRAINT "evenements_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."gare_quais"
ADD CONSTRAINT "gare_quais_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."gare_transports"
ADD CONSTRAINT "gare_transports_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."gares"
ADD CONSTRAINT "gares_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."horaires_gares_desservies"
ADD CONSTRAINT "horaires_gares_desservies_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."horaires"
ADD CONSTRAINT "horaires_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."info_trafic"
ADD CONSTRAINT "info_trafic_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."lignes_gares"
ADD CONSTRAINT "lignes_gares_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."lignes"
ADD CONSTRAINT "lignes_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."materiel-roulant"
ADD CONSTRAINT "materiel-roulant_numero_serie_key" UNIQUE ("numero_serie");
ALTER TABLE ONLY "public"."materiel-roulant"
ADD CONSTRAINT "materiel-roulant_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."menus_regionaux"
ADD CONSTRAINT "menus_regionaux_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."messagerie_messages"
ADD CONSTRAINT "messagerie_messages_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."messagerie_participants"
ADD CONSTRAINT "messagerie_participants_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."messagerie_participants"
ADD CONSTRAINT "messagerie_participants_thread_id_user_id_key" UNIQUE ("thread_id", "user_id");
ALTER TABLE ONLY "public"."messagerie_threads"
ADD CONSTRAINT "messagerie_threads_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."page_personnalisation"
ADD CONSTRAINT "page_personnalisation_page_key" UNIQUE ("page");
ALTER TABLE ONLY "public"."page_personnalisation"
ADD CONSTRAINT "page_personnalisation_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."parametres"
ADD CONSTRAINT "parametres_cle_key" UNIQUE ("cle");
ALTER TABLE ONLY "public"."parametres"
ADD CONSTRAINT "parametres_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."perturbations_modifications_parcours"
ADD CONSTRAINT "perturbations_modifications_parcours_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."perturbations"
ADD CONSTRAINT "perturbations_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."profils_utilisateurs"
ADD CONSTRAINT "profils_utilisateurs_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."services_annuels"
ADD CONSTRAINT "services_annuels_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."tarifs"
ADD CONSTRAINT "tarifs_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."travaux"
ADD CONSTRAINT "travaux_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."travaux_substitutions"
ADD CONSTRAINT "travaux_substitutions_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."travaux_substitutions"
ADD CONSTRAINT "travaux_substitutions_travaux_id_horaire_id_key" UNIQUE ("travaux_id", "horaire_id");
ALTER TABLE ONLY "public"."types_train"
ADD CONSTRAINT "types_train_pkey" PRIMARY KEY ("id");
ALTER TABLE ONLY "public"."utilisateurs"
ADD CONSTRAINT "utilisateurs_email_key" UNIQUE ("email");
ALTER TABLE ONLY "public"."utilisateurs"
ADD CONSTRAINT "utilisateurs_pkey" PRIMARY KEY ("id");
CREATE INDEX "idx_actualites_actif" ON "public"."actualites" USING "btree" ("actif") WHERE ("deleted_at" IS NULL);
CREATE INDEX "idx_actualites_created_by" ON "public"."actualites" USING "btree" ("created_by");
CREATE INDEX "idx_actualites_publication" ON "public"."actualites" USING "btree" ("date_publication");
CREATE INDEX "idx_actualites_region_id" ON "public"."actualites" USING "btree" ("region_id");
CREATE INDEX "idx_evenements_actif" ON "public"."evenements" USING "btree" ("actif") WHERE ("deleted_at" IS NULL);
CREATE INDEX "idx_evenements_created_by" ON "public"."evenements" USING "btree" ("created_by");
CREATE INDEX "idx_evenements_dates" ON "public"."evenements" USING "btree" ("date_debut", "date_fin");
CREATE INDEX "idx_evenements_region_id" ON "public"."evenements" USING "btree" ("region_id");
CREATE INDEX "idx_gare_quais_gare_id" ON "public"."gare_quais" USING "btree" ("gare_id");
CREATE INDEX "idx_gare_transports_gare_id" ON "public"."gare_transports" USING "btree" ("gare_id");
CREATE INDEX "idx_gares_desservies_horaire" ON "public"."horaires_gares_desservies" USING "btree" ("horaire_id");
CREATE INDEX "idx_gares_desservies_ordre" ON "public"."horaires_gares_desservies" USING "btree" ("horaire_id", "ordre");
CREATE INDEX "idx_hgd_gare_id" ON "public"."horaires_gares_desservies" USING "btree" ("gare_id");
CREATE INDEX "idx_hgd_horaire_id" ON "public"."horaires_gares_desservies" USING "btree" ("horaire_id");
CREATE INDEX "idx_horaires_actif" ON "public"."horaires" USING "btree" ("actif") WHERE ("deleted_at" IS NULL);
CREATE INDEX "idx_horaires_gare_arrivee" ON "public"."horaires" USING "btree" ("gare_arrivee_id");
CREATE INDEX "idx_horaires_gare_depart" ON "public"."horaires" USING "btree" ("gare_depart_id");
CREATE INDEX "idx_horaires_ligne" ON "public"."horaires" USING "btree" ("ligne_id");
CREATE INDEX "idx_horaires_materiel" ON "public"."horaires" USING "btree" ("materiel_roulant_id");
CREATE INDEX "idx_horaires_numero_train" ON "public"."horaires" USING "btree" ("numero_train");
CREATE INDEX "idx_horaires_region_id" ON "public"."horaires" USING "btree" ("region_id");
CREATE INDEX "idx_horaires_service_annuel" ON "public"."horaires" USING "btree" ("service_annuel_id");
CREATE INDEX "idx_horaires_substitution" ON "public"."horaires" USING "btree" ("est_substitution");
CREATE INDEX "idx_horaires_type_train" ON "public"."horaires" USING "btree" ("type_train_id");
CREATE INDEX "idx_info_trafic_actif" ON "public"."info_trafic" USING "btree" ("actif") WHERE ("deleted_at" IS NULL);
CREATE INDEX "idx_info_trafic_created_by" ON "public"."info_trafic" USING "btree" ("created_by");
CREATE INDEX "idx_info_trafic_dates" ON "public"."info_trafic" USING "btree" ("date_debut", "date_fin");
CREATE INDEX "idx_info_trafic_region_id" ON "public"."info_trafic" USING "btree" ("region_id");
CREATE INDEX "idx_lignes_actif" ON "public"."lignes" USING "btree" ("actif") WHERE ("deleted_at" IS NULL);
CREATE INDEX "idx_lignes_fh_url" ON "public"."lignes" USING "btree" ("fh_url");
CREATE INDEX "idx_lignes_gares_ligne" ON "public"."lignes_gares" USING "btree" ("ligne_id");
CREATE INDEX "idx_lignes_gares_ordre" ON "public"."lignes_gares" USING "btree" ("ligne_id", "ordre");
CREATE INDEX "idx_menus_regionaux_region_id" ON "public"."menus_regionaux" USING "btree" ("region_id");
CREATE INDEX "idx_messagerie_messages_created_at" ON "public"."messagerie_messages" USING "btree" ("created_at" DESC);
CREATE INDEX "idx_messagerie_messages_thread_id" ON "public"."messagerie_messages" USING "btree" ("thread_id");
CREATE INDEX "idx_messagerie_participants_user_id" ON "public"."messagerie_participants" USING "btree" ("user_id");
CREATE INDEX "idx_modif_parcours_gare" ON "public"."perturbations_modifications_parcours" USING "btree" ("gare_id");
CREATE INDEX "idx_modif_parcours_perturbation" ON "public"."perturbations_modifications_parcours" USING "btree" ("perturbation_id");
CREATE INDEX "idx_perturbations_actif" ON "public"."perturbations" USING "btree" ("actif") WHERE ("deleted_at" IS NULL);
CREATE INDEX "idx_perturbations_created_by" ON "public"."perturbations" USING "btree" ("created_by");
CREATE INDEX "idx_perturbations_dates" ON "public"."perturbations" USING "btree" ("date_debut", "date_fin");
CREATE INDEX "idx_perturbations_horaire" ON "public"."perturbations" USING "btree" ("horaire_id");
CREATE INDEX "idx_perturbations_region_id" ON "public"."perturbations" USING "btree" ("region_id");
CREATE INDEX "idx_perturbations_sillon_substitution" ON "public"."perturbations" USING "btree" ("sillon_substitution_id");
CREATE INDEX "idx_perturbations_type" ON "public"."perturbations" USING "btree" ("type_perturbation");
CREATE INDEX "idx_profils_email" ON "public"."profils_utilisateurs" USING "btree" ("email");
CREATE INDEX "idx_services_annuels_actif" ON "public"."services_annuels" USING "btree" ("actif") WHERE ("deleted_at" IS NULL);
CREATE INDEX "idx_services_annuels_dates" ON "public"."services_annuels" USING "btree" ("date_debut", "date_fin");
CREATE INDEX "idx_tarifs_ordre" ON "public"."tarifs" USING "btree" ("ordre");
CREATE INDEX "idx_tarifs_regions" ON "public"."tarifs" USING "gin" ("regions");
CREATE INDEX "idx_travaux_actif" ON "public"."travaux" USING "btree" ("actif");
CREATE INDEX "idx_travaux_created_by" ON "public"."travaux" USING "btree" ("created_by");
CREATE INDEX "idx_travaux_dates" ON "public"."travaux" USING "btree" ("date_debut", "date_fin");
CREATE INDEX "idx_travaux_deleted" ON "public"."travaux" USING "btree" ("deleted_at");
CREATE INDEX "idx_travaux_ligne_id" ON "public"."travaux" USING "btree" ("ligne_id");
CREATE INDEX "idx_travaux_region_id" ON "public"."travaux" USING "btree" ("region_id");
CREATE INDEX "idx_travaux_substitutions_attribuees_gin" ON "public"."travaux" USING "gin" ("substitutions_attribuees");
CREATE INDEX "idx_travaux_substitutions_horaire" ON "public"."travaux_substitutions" USING "btree" ("horaire_id");
CREATE INDEX "idx_travaux_substitutions_travaux" ON "public"."travaux_substitutions" USING "btree" ("travaux_id");
CREATE INDEX "idx_types_train_code" ON "public"."types_train" USING "btree" ("code");
CREATE INDEX "idx_types_train_deleted_at" ON "public"."types_train" USING "btree" ("deleted_at");
CREATE INDEX "idx_utilisateurs_actif" ON "public"."utilisateurs" USING "btree" ("actif");
CREATE INDEX "idx_utilisateurs_email" ON "public"."utilisateurs" USING "btree" ("email");
CREATE INDEX "idx_utilisateurs_region_id" ON "public"."utilisateurs" USING "btree" ("region_id");
CREATE INDEX "idx_utilisateurs_role" ON "public"."utilisateurs" USING "btree" ("role");
CREATE UNIQUE INDEX "utilisateurs_email_lower_idx" ON "public"."utilisateurs" USING "btree" ("lower"(("email")::"text"));
CREATE OR REPLACE TRIGGER "set_profils_updated_at" BEFORE UPDATE ON "public"."profils_utilisateurs" FOR EACH ROW EXECUTE FUNCTION "public"."handle_updated_at"();
CREATE OR REPLACE TRIGGER "tr_gares_audit" AFTER INSERT OR DELETE OR UPDATE ON "public"."gares" FOR EACH ROW EXECUTE FUNCTION "public"."log_audit"();
CREATE OR REPLACE TRIGGER "tr_gares_set_updated_at" BEFORE UPDATE ON "public"."gares" FOR EACH ROW EXECUTE FUNCTION "public"."set_updated_at"();
CREATE OR REPLACE TRIGGER "tr_utilisateurs_audit" AFTER INSERT OR DELETE OR UPDATE ON "public"."utilisateurs" FOR EACH ROW EXECUTE FUNCTION "public"."log_audit"();
CREATE OR REPLACE TRIGGER "tr_utilisateurs_set_updated_at" BEFORE UPDATE ON "public"."utilisateurs" FOR EACH ROW EXECUTE FUNCTION "public"."set_updated_at"();
CREATE OR REPLACE TRIGGER "trigger_update_travaux_updated_at" BEFORE UPDATE ON "public"."travaux" FOR EACH ROW EXECUTE FUNCTION "public"."update_travaux_updated_at"();
CREATE OR REPLACE TRIGGER "trigger_update_utilisateurs_updated_at" BEFORE UPDATE ON "public"."utilisateurs" FOR EACH ROW EXECUTE FUNCTION "public"."update_utilisateurs_updated_at"();
CREATE OR REPLACE TRIGGER "update_actualites_updated_at" BEFORE UPDATE ON "public"."actualites" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"();
CREATE OR REPLACE TRIGGER "update_evenements_updated_at" BEFORE UPDATE ON "public"."evenements" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"();
CREATE OR REPLACE TRIGGER "update_horaires_gares_desservies_updated_at" BEFORE UPDATE ON "public"."horaires_gares_desservies" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"();
CREATE OR REPLACE TRIGGER "update_horaires_updated_at" BEFORE UPDATE ON "public"."horaires" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"();
CREATE OR REPLACE TRIGGER "update_info_trafic_updated_at" BEFORE UPDATE ON "public"."info_trafic" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"();
CREATE OR REPLACE TRIGGER "update_lignes_updated_at" BEFORE UPDATE ON "public"."lignes" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"();
CREATE OR REPLACE TRIGGER "update_perturbations_updated_at" BEFORE UPDATE ON "public"."perturbations" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"();
CREATE OR REPLACE TRIGGER "update_services_annuels_updated_at" BEFORE UPDATE ON "public"."services_annuels" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"();
ALTER TABLE ONLY "public"."evenements"
ADD CONSTRAINT "evenements_gare_id_fkey" FOREIGN KEY ("gare_id") REFERENCES "public"."gares"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."horaires_gares_desservies"
ADD CONSTRAINT "fk_horaires_gares_horaire_id" FOREIGN KEY ("horaire_id") REFERENCES "public"."horaires"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."gare_quais"
ADD CONSTRAINT "gare_quais_gare_id_fkey" FOREIGN KEY ("gare_id") REFERENCES "public"."gares"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."gare_transports"
ADD CONSTRAINT "gare_transports_gare_id_fkey" FOREIGN KEY ("gare_id") REFERENCES "public"."gares"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."horaires"
ADD CONSTRAINT "horaires_gare_arrivee_id_fkey" FOREIGN KEY ("gare_arrivee_id") REFERENCES "public"."gares"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."horaires"
ADD CONSTRAINT "horaires_gare_depart_id_fkey" FOREIGN KEY ("gare_depart_id") REFERENCES "public"."gares"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."horaires_gares_desservies"
ADD CONSTRAINT "horaires_gares_desservies_gare_id_fkey" FOREIGN KEY ("gare_id") REFERENCES "public"."gares"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."horaires_gares_desservies"
ADD CONSTRAINT "horaires_gares_desservies_quai_id_fkey" FOREIGN KEY ("quai_id") REFERENCES "public"."gare_quais"("id");
ALTER TABLE ONLY "public"."horaires"
ADD CONSTRAINT "horaires_ligne_id_fkey" FOREIGN KEY ("ligne_id") REFERENCES "public"."lignes"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."horaires"
ADD CONSTRAINT "horaires_materiel_roulant_id_fkey" FOREIGN KEY ("materiel_roulant_id") REFERENCES "public"."materiel-roulant"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."horaires"
ADD CONSTRAINT "horaires_service_annuel_id_fkey" FOREIGN KEY ("service_annuel_id") REFERENCES "public"."services_annuels"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."horaires"
ADD CONSTRAINT "horaires_type_train_id_fkey" FOREIGN KEY ("type_train_id") REFERENCES "public"."types_train"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."info_trafic"
ADD CONSTRAINT "info_trafic_gare_id_fkey" FOREIGN KEY ("gare_id") REFERENCES "public"."gares"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."lignes"
ADD CONSTRAINT "lignes_gare_arrivee_id_fkey" FOREIGN KEY ("gare_arrivee_id") REFERENCES "public"."gares"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."lignes"
ADD CONSTRAINT "lignes_gare_depart_id_fkey" FOREIGN KEY ("gare_depart_id") REFERENCES "public"."gares"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."lignes_gares"
ADD CONSTRAINT "lignes_gares_gare_id_fkey" FOREIGN KEY ("gare_id") REFERENCES "public"."gares"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."lignes_gares"
ADD CONSTRAINT "lignes_gares_ligne_id_fkey" FOREIGN KEY ("ligne_id") REFERENCES "public"."lignes"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."messagerie_messages"
ADD CONSTRAINT "messagerie_messages_sender_id_fkey" FOREIGN KEY ("sender_id") REFERENCES "public"."utilisateurs"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."messagerie_messages"
ADD CONSTRAINT "messagerie_messages_thread_id_fkey" FOREIGN KEY ("thread_id") REFERENCES "public"."messagerie_threads"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."messagerie_participants"
ADD CONSTRAINT "messagerie_participants_thread_id_fkey" FOREIGN KEY ("thread_id") REFERENCES "public"."messagerie_threads"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."messagerie_participants"
ADD CONSTRAINT "messagerie_participants_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."utilisateurs"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."perturbations"
ADD CONSTRAINT "perturbations_horaire_id_fkey" FOREIGN KEY ("horaire_id") REFERENCES "public"."horaires"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."perturbations_modifications_parcours"
ADD CONSTRAINT "perturbations_modifications_parcours_gare_id_fkey" FOREIGN KEY ("gare_id") REFERENCES "public"."gares"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."perturbations_modifications_parcours"
ADD CONSTRAINT "perturbations_modifications_parcours_perturbation_id_fkey" FOREIGN KEY ("perturbation_id") REFERENCES "public"."perturbations"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."perturbations"
ADD CONSTRAINT "perturbations_sillon_substitution_id_fkey" FOREIGN KEY ("sillon_substitution_id") REFERENCES "public"."horaires"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."profils_utilisateurs"
ADD CONSTRAINT "profils_utilisateurs_id_fkey" FOREIGN KEY ("id") REFERENCES "auth"."users"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."travaux"
ADD CONSTRAINT "travaux_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "public"."utilisateurs"("id");
ALTER TABLE ONLY "public"."travaux"
ADD CONSTRAINT "travaux_ligne_id_fkey" FOREIGN KEY ("ligne_id") REFERENCES "public"."lignes"("id") ON DELETE SET NULL;
ALTER TABLE ONLY "public"."travaux_substitutions"
ADD CONSTRAINT "travaux_substitutions_horaire_id_fkey" FOREIGN KEY ("horaire_id") REFERENCES "public"."horaires"("id") ON DELETE CASCADE;
ALTER TABLE ONLY "public"."travaux_substitutions"
ADD CONSTRAINT "travaux_substitutions_travaux_id_fkey" FOREIGN KEY ("travaux_id") REFERENCES "public"."travaux"("id") ON DELETE CASCADE;
CREATE POLICY "Insertion profil propre" ON "public"."profils_utilisateurs" FOR INSERT WITH CHECK (("auth"."uid"() = "id"));
CREATE POLICY "Lecture profil propre" ON "public"."profils_utilisateurs" FOR SELECT USING (("auth"."uid"() = "id"));
CREATE POLICY "Mise à jour profil propre" ON "public"."profils_utilisateurs" FOR UPDATE USING (("auth"."uid"() = "id")) WITH CHECK (("auth"."uid"() = "id"));
CREATE POLICY "Suppression profil propre" ON "public"."profils_utilisateurs" FOR DELETE USING (("auth"."uid"() = "id"));
CREATE POLICY "menus_delete" ON "public"."menus_regionaux" FOR DELETE USING (true);
CREATE POLICY "menus_insert" ON "public"."menus_regionaux" FOR INSERT WITH CHECK (true);
ALTER TABLE "public"."menus_regionaux" ENABLE ROW LEVEL SECURITY;
CREATE POLICY "menus_select" ON "public"."menus_regionaux" FOR SELECT USING (true);
CREATE POLICY "menus_update" ON "public"."menus_regionaux" FOR UPDATE USING (true);
ALTER TABLE "public"."messagerie_messages" ENABLE ROW LEVEL SECURITY;
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")))));
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"))));
ALTER TABLE "public"."messagerie_participants" ENABLE ROW LEVEL SECURITY;
CREATE POLICY "messagerie_participants_insert" ON "public"."messagerie_participants" FOR INSERT TO "anon" WITH CHECK (true);
CREATE POLICY "messagerie_participants_select" ON "public"."messagerie_participants" FOR SELECT TO "anon" USING (true);
ALTER TABLE "public"."messagerie_threads" ENABLE ROW LEVEL SECURITY;
CREATE POLICY "messagerie_threads_insert" ON "public"."messagerie_threads" FOR INSERT TO "anon" WITH CHECK (true);
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"))));
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);
ALTER TABLE "public"."parametres" ENABLE ROW LEVEL SECURITY;
CREATE POLICY "parametres_delete_all" ON "public"."parametres" FOR DELETE USING (true);
CREATE POLICY "parametres_insert_all" ON "public"."parametres" FOR INSERT WITH CHECK (true);
CREATE POLICY "parametres_select_all" ON "public"."parametres" FOR SELECT USING (true);
CREATE POLICY "parametres_update_all" ON "public"."parametres" FOR UPDATE USING (true);
ALTER TABLE "public"."profils_utilisateurs" ENABLE ROW LEVEL SECURITY;
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "postgres";
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "anon";
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "authenticated";
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "service_role";
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "postgres";
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "anon";
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "authenticated";
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "service_role";
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "postgres";
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "anon";
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "authenticated";
ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "service_role";