From bbeac5af8642e0eee949d4c320b1012d66dd260e Mon Sep 17 00:00:00 2001 From: mathis Date: Tue, 25 Aug 2026 16:35:40 +0200 Subject: [PATCH] =?UTF-8?q?Ajout=20du=20script=20de=20base=20de=20donn?= =?UTF-8?q?=C3=A9es=20`scheme=5Fclean.sql`=20pour=20initialiser=20les=20ta?= =?UTF-8?q?bles,=20types,=20fonctions=20et=20commentaires.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ecosystem.config.cjs | 29 + public/index.html | 5 +- public/manifest.json | 14 +- scheme_clean.sql | 1624 ++++++++++++++++++++++++++++++ src/index.js | 6 + src/pages/gares/Gares.js | 6 +- src/pages/login/Login.css | 37 - src/pages/login/Login.js | 41 +- src/service-worker.js | 70 ++ src/serviceWorkerRegistration.js | 132 +++ 10 files changed, 1881 insertions(+), 83 deletions(-) create mode 100644 ecosystem.config.cjs create mode 100644 scheme_clean.sql create mode 100644 src/service-worker.js create mode 100644 src/serviceWorkerRegistration.js diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs new file mode 100644 index 0000000..eea924f --- /dev/null +++ b/ecosystem.config.cjs @@ -0,0 +1,29 @@ +module.exports = { + apps: [ + { + name: "ferrovia-panel", + cwd: "/var/www/ferrovia-panel", + + script: "npm", + args: "run start", + + instances: 1, + exec_mode: "fork", + + autorestart: true, + watch: false, + max_memory_restart: "1G", + + env: { + NODE_ENV: "production", + HOST: "127.0.0.1", + PORT: 3000 + }, + + time: true, + merge_logs: true, + out_file: "/var/www/ferrovia-panel/logs/out.log", + error_file: "/var/www/ferrovia-panel/logs/error.log" + } + ] +}; diff --git a/public/index.html b/public/index.html index 562f900..7805429 100644 --- a/public/index.html +++ b/public/index.html @@ -4,7 +4,10 @@ - + + + + diff --git a/public/manifest.json b/public/manifest.json index 080d6c7..e86320e 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "Admin Régions", + "name": "SNCF Admin - Gestion des régions", "icons": [ { "src": "favicon.ico", @@ -10,16 +10,20 @@ { "src": "logo192.png", "type": "image/png", - "sizes": "192x192" + "sizes": "192x192", + "purpose": "any maskable" }, { "src": "logo512.png", "type": "image/png", - "sizes": "512x512" + "sizes": "512x512", + "purpose": "any maskable" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", - "background_color": "#ffffff" + "background_color": "#ffffff", + "orientation": "portrait-primary", + "description": "Interface d'administration pour la gestion de l'exploitation régionale de la SNCF." } diff --git a/scheme_clean.sql b/scheme_clean.sql new file mode 100644 index 0000000..bdeb189 --- /dev/null +++ b/scheme_clean.sql @@ -0,0 +1,1624 @@ + + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +CREATE SCHEMA IF NOT EXISTS "public"; + +ALTER SCHEMA "public" OWNER TO "pg_database_owner"; + +COMMENT ON SCHEMA "public" IS 'standard public schema'; + +CREATE TYPE "public"."service_type" AS ENUM ( + 'TER', + 'TGV', + 'Intercités', + 'Fret' +); + +ALTER TYPE "public"."service_type" OWNER TO "postgres"; + +CREATE TYPE "public"."station_type" AS ENUM ( + 'interurbaine', + 'ville' +); + +ALTER TYPE "public"."station_type" OWNER TO "postgres"; + +CREATE TYPE "public"."transport_type" AS ENUM ( + 'bus', + 'tram', + 'metro', + 'tram-train', + 'train' +); + +ALTER TYPE "public"."transport_type" OWNER TO "postgres"; + +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='users_panel' 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.users_panel 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; +$_$; + +ALTER FUNCTION "public"."authenticate_user"("p_email" "text", "p_password" "text") OWNER TO "postgres"; + +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.users_panel (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 users_panel.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.users_panel u + WHERE u.id = new_id; +END; +$$; + +ALTER FUNCTION "public"."create_user_with_password"("p_email" "text", "p_nom" "text", "p_prenom" "text", "p_role" "text", "p_actif" boolean, "p_password" "text", "p_photo_url" "text", "p_region_admin" boolean, "p_region_id" "text") OWNER TO "postgres"; + +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; +$$; + +ALTER FUNCTION "public"."get_gare_details"("p_gare_id" "uuid") OWNER TO "postgres"; + +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; +$$; + +ALTER FUNCTION "public"."get_station_details"("p_station_id" "uuid") OWNER TO "postgres"; + +CREATE OR REPLACE FUNCTION "public"."handle_updated_at"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$; + +ALTER FUNCTION "public"."handle_updated_at"() OWNER TO "postgres"; + +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; +$$; + +ALTER FUNCTION "public"."import_gtfs_rpc"("payload" json) OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."set_updated_at"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$; + +ALTER FUNCTION "public"."set_updated_at"() OWNER TO "postgres"; + +CREATE OR REPLACE FUNCTION "public"."update_travaux_updated_at"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$; + +ALTER FUNCTION "public"."update_travaux_updated_at"() OWNER TO "postgres"; + +CREATE OR REPLACE FUNCTION "public"."update_updated_at_column"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$; + +ALTER FUNCTION "public"."update_updated_at_column"() OWNER TO "postgres"; + +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.users_panel + 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; +$$; + +ALTER FUNCTION "public"."update_user_password"("p_user_id" "uuid", "p_password" "text") OWNER TO "postgres"; + +CREATE OR REPLACE FUNCTION "public"."update_users_panel_updated_at"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$; + +ALTER FUNCTION "public"."update_users_panel_updated_at"() OWNER TO "postgres"; + +SET default_tablespace = ''; + +SET default_table_access_method = "heap"; + +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"[]))) +); + +ALTER TABLE "public"."actualites" OWNER TO "postgres"; + +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"."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 +); + +ALTER TABLE "public"."evenements" OWNER TO "postgres"; + +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 +); + +ALTER TABLE "public"."gare_quais" OWNER TO "postgres"; + +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 +); + +ALTER TABLE "public"."gare_transports" OWNER TO "postgres"; + +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", + "quais_hall" "jsonb" DEFAULT \'[]\'::"jsonb" +); + +ALTER TABLE "public"."gares" OWNER TO "postgres"; + +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) +); + +ALTER TABLE "public"."horaires" OWNER TO "postgres"; + +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" +); + +ALTER TABLE "public"."horaires_gares_desservies" OWNER TO "postgres"; + +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" OWNER TO "postgres"; + +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"[]))) +); + +ALTER TABLE "public"."info_trafic" OWNER TO "postgres"; + +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) +); + +ALTER TABLE "public"."lignes" OWNER TO "postgres"; + +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"[]))) +); + +ALTER TABLE "public"."lignes_gares" OWNER TO "postgres"; + +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" OWNER TO "postgres"; + +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) +); + +ALTER TABLE "public"."menus_regionaux" OWNER TO "postgres"; + +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 +); + +ALTER TABLE "public"."messagerie_messages" OWNER TO "postgres"; + +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 +); + +ALTER TABLE "public"."messagerie_participants" OWNER TO "postgres"; + +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 +); + +ALTER TABLE "public"."messagerie_threads" OWNER TO "postgres"; + +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"() +); + +ALTER TABLE "public"."parametres" OWNER TO "postgres"; + +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"[]))) +); + +ALTER TABLE "public"."perturbations" OWNER TO "postgres"; + +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"[]))) +); + +ALTER TABLE "public"."perturbations_modifications_parcours" OWNER TO "postgres"; + +COMMENT ON TABLE "public"."perturbations_modifications_parcours" IS 'Détails des modifications de parcours pour les perturbations'; + +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 +); + +ALTER TABLE "public"."services_annuels" OWNER TO "postgres"; + +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" +); + +ALTER TABLE "public"."tarifs" OWNER TO "postgres"; + +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" OWNER TO "postgres"; + +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")) +); + +ALTER TABLE "public"."travaux" OWNER TO "postgres"; + +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"() +); + +ALTER TABLE "public"."travaux_substitutions" OWNER TO "postgres"; + +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 +); + +ALTER TABLE "public"."types_train" OWNER TO "postgres"; + +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" OWNER TO "postgres"; + +ALTER SEQUENCE "public"."types_train_id_seq" OWNED BY "public"."types_train"."id"; + +CREATE TABLE IF NOT EXISTS "public"."users_panel" ( + "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 +); + +ALTER TABLE "public"."users_panel" OWNER TO "postgres"; + +COMMENT ON TABLE "public"."users_panel" IS 'Utilisateurs de l''application avec leurs informations et rôles'; + +COMMENT ON COLUMN "public"."users_panel"."id" IS 'Identifiant unique de l''utilisateur'; + +COMMENT ON COLUMN "public"."users_panel"."email" IS 'Adresse email de l''utilisateur (unique)'; + +COMMENT ON COLUMN "public"."users_panel"."nom" IS 'Nom de famille de l''utilisateur'; + +COMMENT ON COLUMN "public"."users_panel"."prenom" IS 'Prénom de l''utilisateur'; + +COMMENT ON COLUMN "public"."users_panel"."role" IS 'Rôle de l''utilisateur (admin, gestionnaire, utilisateur)'; + +COMMENT ON COLUMN "public"."users_panel"."actif" IS 'Indique si l''utilisateur est actif ou désactivé'; + +COMMENT ON COLUMN "public"."users_panel"."password_hash" IS 'Hash du mot de passe (ne pas stocker en clair)'; + +COMMENT ON COLUMN "public"."users_panel"."photo_url" IS 'URL de la photo de profil'; + +COMMENT ON COLUMN "public"."users_panel"."region_admin" IS 'Indique si l''utilisateur administre une région'; + +COMMENT ON COLUMN "public"."users_panel"."region_nom" IS 'Nom de la région administrée (si applicable)'; + +COMMENT ON COLUMN "public"."users_panel"."created_at" IS 'Date de création de l''utilisateur'; + +COMMENT ON COLUMN "public"."users_panel"."updated_at" IS 'Date de dernière modification de l''utilisateur'; + +COMMENT ON COLUMN "public"."users_panel"."region_id" IS 'Identifiant de la région administrée par l''utilisateur'; + +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" + DROP CONSTRAINT IF EXISTS "actualites_pkey"; +ALTER TABLE ONLY "public"."actualites" + ADD CONSTRAINT "actualites_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."evenements" + DROP CONSTRAINT IF EXISTS "evenements_pkey"; +ALTER TABLE ONLY "public"."evenements" + ADD CONSTRAINT "evenements_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."gare_quais" + DROP CONSTRAINT IF EXISTS "gare_quais_pkey"; +ALTER TABLE ONLY "public"."gare_quais" + ADD CONSTRAINT "gare_quais_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."gare_transports" + DROP CONSTRAINT IF EXISTS "gare_transports_pkey"; +ALTER TABLE ONLY "public"."gare_transports" + ADD CONSTRAINT "gare_transports_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."gares" + DROP CONSTRAINT IF EXISTS "gares_pkey"; +ALTER TABLE ONLY "public"."gares" + ADD CONSTRAINT "gares_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."horaires_gares_desservies" + DROP CONSTRAINT IF EXISTS "horaires_gares_desservies_pkey"; +ALTER TABLE ONLY "public"."horaires_gares_desservies" + ADD CONSTRAINT "horaires_gares_desservies_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."horaires" + DROP CONSTRAINT IF EXISTS "horaires_pkey"; +ALTER TABLE ONLY "public"."horaires" + ADD CONSTRAINT "horaires_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."info_trafic" + DROP CONSTRAINT IF EXISTS "info_trafic_pkey"; +ALTER TABLE ONLY "public"."info_trafic" + ADD CONSTRAINT "info_trafic_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."lignes_gares" + DROP CONSTRAINT IF EXISTS "lignes_gares_pkey"; +ALTER TABLE ONLY "public"."lignes_gares" + ADD CONSTRAINT "lignes_gares_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."lignes" + DROP CONSTRAINT IF EXISTS "lignes_pkey"; +ALTER TABLE ONLY "public"."lignes" + ADD CONSTRAINT "lignes_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."materiel-roulant" + DROP CONSTRAINT IF EXISTS "materiel-roulant_numero_serie_key"; +ALTER TABLE ONLY "public"."materiel-roulant" + ADD CONSTRAINT "materiel-roulant_numero_serie_key" UNIQUE ("numero_serie"); + +ALTER TABLE ONLY "public"."materiel-roulant" + DROP CONSTRAINT IF EXISTS "materiel-roulant_pkey"; +ALTER TABLE ONLY "public"."materiel-roulant" + ADD CONSTRAINT "materiel-roulant_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."menus_regionaux" + DROP CONSTRAINT IF EXISTS "menus_regionaux_pkey"; +ALTER TABLE ONLY "public"."menus_regionaux" + ADD CONSTRAINT "menus_regionaux_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."messagerie_messages" + DROP CONSTRAINT IF EXISTS "messagerie_messages_pkey"; +ALTER TABLE ONLY "public"."messagerie_messages" + ADD CONSTRAINT "messagerie_messages_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."messagerie_participants" + DROP CONSTRAINT IF EXISTS "messagerie_participants_pkey"; +ALTER TABLE ONLY "public"."messagerie_participants" + ADD CONSTRAINT "messagerie_participants_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."messagerie_participants" + DROP CONSTRAINT IF EXISTS "messagerie_participants_thread_id_user_id_key"; +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" + DROP CONSTRAINT IF EXISTS "messagerie_threads_pkey"; +ALTER TABLE ONLY "public"."messagerie_threads" + ADD CONSTRAINT "messagerie_threads_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."parametres" + DROP CONSTRAINT IF EXISTS "parametres_cle_key"; +ALTER TABLE ONLY "public"."parametres" + ADD CONSTRAINT "parametres_cle_key" UNIQUE ("cle"); + +ALTER TABLE ONLY "public"."parametres" + DROP CONSTRAINT IF EXISTS "parametres_pkey"; +ALTER TABLE ONLY "public"."parametres" + ADD CONSTRAINT "parametres_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."perturbations_modifications_parcours" + DROP CONSTRAINT IF EXISTS "perturbations_modifications_parcours_pkey"; +ALTER TABLE ONLY "public"."perturbations_modifications_parcours" + ADD CONSTRAINT "perturbations_modifications_parcours_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."perturbations" + DROP CONSTRAINT IF EXISTS "perturbations_pkey"; +ALTER TABLE ONLY "public"."perturbations" + ADD CONSTRAINT "perturbations_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."services_annuels" + DROP CONSTRAINT IF EXISTS "services_annuels_pkey"; +ALTER TABLE ONLY "public"."services_annuels" + ADD CONSTRAINT "services_annuels_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."tarifs" + DROP CONSTRAINT IF EXISTS "tarifs_pkey"; +ALTER TABLE ONLY "public"."tarifs" + ADD CONSTRAINT "tarifs_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."travaux" + DROP CONSTRAINT IF EXISTS "travaux_pkey"; +ALTER TABLE ONLY "public"."travaux" + ADD CONSTRAINT "travaux_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."travaux_substitutions" + DROP CONSTRAINT IF EXISTS "travaux_substitutions_pkey"; +ALTER TABLE ONLY "public"."travaux_substitutions" + ADD CONSTRAINT "travaux_substitutions_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."travaux_substitutions" + DROP CONSTRAINT IF EXISTS "travaux_substitutions_travaux_id_horaire_id_key"; +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" + DROP CONSTRAINT IF EXISTS "types_train_pkey"; +ALTER TABLE ONLY "public"."types_train" + ADD CONSTRAINT "types_train_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."users_panel" + DROP CONSTRAINT IF EXISTS "users_panel_email_key"; +ALTER TABLE ONLY "public"."users_panel" + ADD CONSTRAINT "users_panel_email_key" UNIQUE ("email"); + +ALTER TABLE ONLY "public"."users_panel" + DROP CONSTRAINT IF EXISTS "users_panel_pkey"; +ALTER TABLE ONLY "public"."users_panel" + ADD CONSTRAINT "users_panel_pkey" PRIMARY KEY ("id"); + +DROP INDEX IF EXISTS "idx_actualites_actif"; +CREATE INDEX "idx_actualites_actif" ON "public"."actualites" USING "btree" ("actif") WHERE ("deleted_at" IS NULL); + +DROP INDEX IF EXISTS "idx_actualites_created_by"; +CREATE INDEX "idx_actualites_created_by" ON "public"."actualites" USING "btree" ("created_by"); + +DROP INDEX IF EXISTS "idx_actualites_publication"; +CREATE INDEX "idx_actualites_publication" ON "public"."actualites" USING "btree" ("date_publication"); + +DROP INDEX IF EXISTS "idx_actualites_region_id"; +CREATE INDEX "idx_actualites_region_id" ON "public"."actualites" USING "btree" ("region_id"); + +DROP INDEX IF EXISTS "idx_evenements_actif"; +CREATE INDEX "idx_evenements_actif" ON "public"."evenements" USING "btree" ("actif") WHERE ("deleted_at" IS NULL); + +DROP INDEX IF EXISTS "idx_evenements_created_by"; +CREATE INDEX "idx_evenements_created_by" ON "public"."evenements" USING "btree" ("created_by"); + +DROP INDEX IF EXISTS "idx_evenements_dates"; +CREATE INDEX "idx_evenements_dates" ON "public"."evenements" USING "btree" ("date_debut", "date_fin"); + +DROP INDEX IF EXISTS "idx_evenements_region_id"; +CREATE INDEX "idx_evenements_region_id" ON "public"."evenements" USING "btree" ("region_id"); + +DROP INDEX IF EXISTS "idx_gare_quais_gare_id"; +CREATE INDEX "idx_gare_quais_gare_id" ON "public"."gare_quais" USING "btree" ("gare_id"); + +DROP INDEX IF EXISTS "idx_gare_transports_gare_id"; +CREATE INDEX "idx_gare_transports_gare_id" ON "public"."gare_transports" USING "btree" ("gare_id"); + +DROP INDEX IF EXISTS "idx_gares_desservies_horaire"; +CREATE INDEX "idx_gares_desservies_horaire" ON "public"."horaires_gares_desservies" USING "btree" ("horaire_id"); + +DROP INDEX IF EXISTS "idx_gares_desservies_ordre"; +CREATE INDEX "idx_gares_desservies_ordre" ON "public"."horaires_gares_desservies" USING "btree" ("horaire_id", "ordre"); + +DROP INDEX IF EXISTS "idx_hgd_gare_id"; +CREATE INDEX "idx_hgd_gare_id" ON "public"."horaires_gares_desservies" USING "btree" ("gare_id"); + +DROP INDEX IF EXISTS "idx_hgd_horaire_id"; +CREATE INDEX "idx_hgd_horaire_id" ON "public"."horaires_gares_desservies" USING "btree" ("horaire_id"); + +DROP INDEX IF EXISTS "idx_horaires_actif"; +CREATE INDEX "idx_horaires_actif" ON "public"."horaires" USING "btree" ("actif") WHERE ("deleted_at" IS NULL); + +DROP INDEX IF EXISTS "idx_horaires_gare_arrivee"; +CREATE INDEX "idx_horaires_gare_arrivee" ON "public"."horaires" USING "btree" ("gare_arrivee_id"); + +DROP INDEX IF EXISTS "idx_horaires_gare_depart"; +CREATE INDEX "idx_horaires_gare_depart" ON "public"."horaires" USING "btree" ("gare_depart_id"); + +DROP INDEX IF EXISTS "idx_horaires_ligne"; +CREATE INDEX "idx_horaires_ligne" ON "public"."horaires" USING "btree" ("ligne_id"); + +DROP INDEX IF EXISTS "idx_horaires_materiel"; +CREATE INDEX "idx_horaires_materiel" ON "public"."horaires" USING "btree" ("materiel_roulant_id"); + +DROP INDEX IF EXISTS "idx_horaires_numero_train"; +CREATE INDEX "idx_horaires_numero_train" ON "public"."horaires" USING "btree" ("numero_train"); + +DROP INDEX IF EXISTS "idx_horaires_region_id"; +CREATE INDEX "idx_horaires_region_id" ON "public"."horaires" USING "btree" ("region_id"); + +DROP INDEX IF EXISTS "idx_horaires_service_annuel"; +CREATE INDEX "idx_horaires_service_annuel" ON "public"."horaires" USING "btree" ("service_annuel_id"); + +DROP INDEX IF EXISTS "idx_horaires_substitution"; +CREATE INDEX "idx_horaires_substitution" ON "public"."horaires" USING "btree" ("est_substitution"); + +DROP INDEX IF EXISTS "idx_horaires_type_train"; +CREATE INDEX "idx_horaires_type_train" ON "public"."horaires" USING "btree" ("type_train_id"); + +DROP INDEX IF EXISTS "idx_info_trafic_actif"; +CREATE INDEX "idx_info_trafic_actif" ON "public"."info_trafic" USING "btree" ("actif") WHERE ("deleted_at" IS NULL); + +DROP INDEX IF EXISTS "idx_info_trafic_created_by"; +CREATE INDEX "idx_info_trafic_created_by" ON "public"."info_trafic" USING "btree" ("created_by"); + +DROP INDEX IF EXISTS "idx_info_trafic_dates"; +CREATE INDEX "idx_info_trafic_dates" ON "public"."info_trafic" USING "btree" ("date_debut", "date_fin"); + +DROP INDEX IF EXISTS "idx_info_trafic_region_id"; +CREATE INDEX "idx_info_trafic_region_id" ON "public"."info_trafic" USING "btree" ("region_id"); + +DROP INDEX IF EXISTS "idx_lignes_actif"; +CREATE INDEX "idx_lignes_actif" ON "public"."lignes" USING "btree" ("actif") WHERE ("deleted_at" IS NULL); + +DROP INDEX IF EXISTS "idx_lignes_fh_url"; +CREATE INDEX "idx_lignes_fh_url" ON "public"."lignes" USING "btree" ("fh_url"); + +DROP INDEX IF EXISTS "idx_lignes_gares_ligne"; +CREATE INDEX "idx_lignes_gares_ligne" ON "public"."lignes_gares" USING "btree" ("ligne_id"); + +DROP INDEX IF EXISTS "idx_lignes_gares_ordre"; +CREATE INDEX "idx_lignes_gares_ordre" ON "public"."lignes_gares" USING "btree" ("ligne_id", "ordre"); + +DROP INDEX IF EXISTS "idx_menus_regionaux_region_id"; +CREATE INDEX "idx_menus_regionaux_region_id" ON "public"."menus_regionaux" USING "btree" ("region_id"); + +DROP INDEX IF EXISTS "idx_messagerie_messages_created_at"; +CREATE INDEX "idx_messagerie_messages_created_at" ON "public"."messagerie_messages" USING "btree" ("created_at" DESC); + +DROP INDEX IF EXISTS "idx_messagerie_messages_thread_id"; +CREATE INDEX "idx_messagerie_messages_thread_id" ON "public"."messagerie_messages" USING "btree" ("thread_id"); + +DROP INDEX IF EXISTS "idx_messagerie_participants_user_id"; +CREATE INDEX "idx_messagerie_participants_user_id" ON "public"."messagerie_participants" USING "btree" ("user_id"); + +DROP INDEX IF EXISTS "idx_modif_parcours_gare"; +CREATE INDEX "idx_modif_parcours_gare" ON "public"."perturbations_modifications_parcours" USING "btree" ("gare_id"); + +DROP INDEX IF EXISTS "idx_modif_parcours_perturbation"; +CREATE INDEX "idx_modif_parcours_perturbation" ON "public"."perturbations_modifications_parcours" USING "btree" ("perturbation_id"); + +DROP INDEX IF EXISTS "idx_perturbations_actif"; +CREATE INDEX "idx_perturbations_actif" ON "public"."perturbations" USING "btree" ("actif") WHERE ("deleted_at" IS NULL); + +DROP INDEX IF EXISTS "idx_perturbations_created_by"; +CREATE INDEX "idx_perturbations_created_by" ON "public"."perturbations" USING "btree" ("created_by"); + +DROP INDEX IF EXISTS "idx_perturbations_dates"; +CREATE INDEX "idx_perturbations_dates" ON "public"."perturbations" USING "btree" ("date_debut", "date_fin"); + +DROP INDEX IF EXISTS "idx_perturbations_horaire"; +CREATE INDEX "idx_perturbations_horaire" ON "public"."perturbations" USING "btree" ("horaire_id"); + +DROP INDEX IF EXISTS "idx_perturbations_region_id"; +CREATE INDEX "idx_perturbations_region_id" ON "public"."perturbations" USING "btree" ("region_id"); + +DROP INDEX IF EXISTS "idx_perturbations_sillon_substitution"; +CREATE INDEX "idx_perturbations_sillon_substitution" ON "public"."perturbations" USING "btree" ("sillon_substitution_id"); + +DROP INDEX IF EXISTS "idx_perturbations_type"; +CREATE INDEX "idx_perturbations_type" ON "public"."perturbations" USING "btree" ("type_perturbation"); + +DROP INDEX IF EXISTS "idx_services_annuels_actif"; +CREATE INDEX "idx_services_annuels_actif" ON "public"."services_annuels" USING "btree" ("actif") WHERE ("deleted_at" IS NULL); + +DROP INDEX IF EXISTS "idx_services_annuels_dates"; +CREATE INDEX "idx_services_annuels_dates" ON "public"."services_annuels" USING "btree" ("date_debut", "date_fin"); + +DROP INDEX IF EXISTS "idx_tarifs_ordre"; +CREATE INDEX "idx_tarifs_ordre" ON "public"."tarifs" USING "btree" ("ordre"); + +DROP INDEX IF EXISTS "idx_tarifs_regions"; +CREATE INDEX "idx_tarifs_regions" ON "public"."tarifs" USING "gin" ("regions"); + +DROP INDEX IF EXISTS "idx_travaux_actif"; +CREATE INDEX "idx_travaux_actif" ON "public"."travaux" USING "btree" ("actif"); + +DROP INDEX IF EXISTS "idx_travaux_created_by"; +CREATE INDEX "idx_travaux_created_by" ON "public"."travaux" USING "btree" ("created_by"); + +DROP INDEX IF EXISTS "idx_travaux_dates"; +CREATE INDEX "idx_travaux_dates" ON "public"."travaux" USING "btree" ("date_debut", "date_fin"); + +DROP INDEX IF EXISTS "idx_travaux_deleted"; +CREATE INDEX "idx_travaux_deleted" ON "public"."travaux" USING "btree" ("deleted_at"); + +DROP INDEX IF EXISTS "idx_travaux_ligne_id"; +CREATE INDEX "idx_travaux_ligne_id" ON "public"."travaux" USING "btree" ("ligne_id"); + +DROP INDEX IF EXISTS "idx_travaux_region_id"; +CREATE INDEX "idx_travaux_region_id" ON "public"."travaux" USING "btree" ("region_id"); + +DROP INDEX IF EXISTS "idx_travaux_substitutions_attribuees_gin"; +CREATE INDEX "idx_travaux_substitutions_attribuees_gin" ON "public"."travaux" USING "gin" ("substitutions_attribuees"); + +DROP INDEX IF EXISTS "idx_travaux_substitutions_horaire"; +CREATE INDEX "idx_travaux_substitutions_horaire" ON "public"."travaux_substitutions" USING "btree" ("horaire_id"); + +DROP INDEX IF EXISTS "idx_travaux_substitutions_travaux"; +CREATE INDEX "idx_travaux_substitutions_travaux" ON "public"."travaux_substitutions" USING "btree" ("travaux_id"); + +DROP INDEX IF EXISTS "idx_types_train_code"; +CREATE INDEX "idx_types_train_code" ON "public"."types_train" USING "btree" ("code"); + +DROP INDEX IF EXISTS "idx_types_train_deleted_at"; +CREATE INDEX "idx_types_train_deleted_at" ON "public"."types_train" USING "btree" ("deleted_at"); + +DROP INDEX IF EXISTS "idx_users_panel_actif"; +CREATE INDEX "idx_users_panel_actif" ON "public"."users_panel" USING "btree" ("actif"); + +DROP INDEX IF EXISTS "idx_users_panel_email"; +CREATE INDEX "idx_users_panel_email" ON "public"."users_panel" USING "btree" ("email"); + +DROP INDEX IF EXISTS "idx_users_panel_region_id"; +CREATE INDEX "idx_users_panel_region_id" ON "public"."users_panel" USING "btree" ("region_id"); + +DROP INDEX IF EXISTS "idx_users_panel_role"; +CREATE INDEX "idx_users_panel_role" ON "public"."users_panel" USING "btree" ("role"); + +DROP INDEX IF EXISTS "users_panel_email_lower_idx"; +CREATE UNIQUE INDEX "users_panel_email_lower_idx" ON "public"."users_panel" USING "btree" ("lower"(("email")::"text")); + + +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_users_panel_set_updated_at" BEFORE UPDATE ON "public"."users_panel" 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_users_panel_updated_at" BEFORE UPDATE ON "public"."users_panel" FOR EACH ROW EXECUTE FUNCTION "public"."update_users_panel_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" + DROP CONSTRAINT IF EXISTS "evenements_gare_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "fk_horaires_gares_horaire_id"; +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" + DROP CONSTRAINT IF EXISTS "gare_quais_gare_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "gare_transports_gare_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "horaires_gare_arrivee_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "horaires_gare_depart_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "horaires_gares_desservies_gare_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "horaires_gares_desservies_quai_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "horaires_ligne_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "horaires_materiel_roulant_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "horaires_service_annuel_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "horaires_type_train_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "info_trafic_gare_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "lignes_gare_arrivee_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "lignes_gare_depart_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "lignes_gares_gare_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "lignes_gares_ligne_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "messagerie_messages_sender_id_fkey"; +ALTER TABLE ONLY "public"."messagerie_messages" + ADD CONSTRAINT "messagerie_messages_sender_id_fkey" FOREIGN KEY ("sender_id") REFERENCES "public"."users_panel"("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."messagerie_messages" + DROP CONSTRAINT IF EXISTS "messagerie_messages_thread_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "messagerie_participants_thread_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "messagerie_participants_user_id_fkey"; +ALTER TABLE ONLY "public"."messagerie_participants" + ADD CONSTRAINT "messagerie_participants_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users_panel"("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."perturbations" + DROP CONSTRAINT IF EXISTS "perturbations_horaire_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "perturbations_modifications_parcours_gare_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "perturbations_modifications_parcours_perturbation_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "perturbations_sillon_substitution_id_fkey"; +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"."travaux" + DROP CONSTRAINT IF EXISTS "travaux_created_by_fkey"; +ALTER TABLE ONLY "public"."travaux" + ADD CONSTRAINT "travaux_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "public"."users_panel"("id"); + +ALTER TABLE ONLY "public"."travaux" + DROP CONSTRAINT IF EXISTS "travaux_ligne_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "travaux_substitutions_horaire_id_fkey"; +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" + DROP CONSTRAINT IF EXISTS "travaux_substitutions_travaux_id_fkey"; +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 "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); + +GRANT USAGE ON SCHEMA "public" TO "postgres"; +GRANT USAGE ON SCHEMA "public" TO "anon"; +GRANT USAGE ON SCHEMA "public" TO "authenticated"; +GRANT USAGE ON SCHEMA "public" TO "service_role"; + +GRANT ALL ON FUNCTION "public"."authenticate_user"("p_email" "text", "p_password" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."authenticate_user"("p_email" "text", "p_password" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."authenticate_user"("p_email" "text", "p_password" "text") TO "service_role"; + +GRANT ALL ON FUNCTION "public"."create_user_with_password"("p_email" "text", "p_nom" "text", "p_prenom" "text", "p_role" "text", "p_actif" boolean, "p_password" "text", "p_photo_url" "text", "p_region_admin" boolean, "p_region_id" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."create_user_with_password"("p_email" "text", "p_nom" "text", "p_prenom" "text", "p_role" "text", "p_actif" boolean, "p_password" "text", "p_photo_url" "text", "p_region_admin" boolean, "p_region_id" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."create_user_with_password"("p_email" "text", "p_nom" "text", "p_prenom" "text", "p_role" "text", "p_actif" boolean, "p_password" "text", "p_photo_url" "text", "p_region_admin" boolean, "p_region_id" "text") TO "service_role"; + +GRANT ALL ON FUNCTION "public"."get_gare_details"("p_gare_id" "uuid") TO "anon"; +GRANT ALL ON FUNCTION "public"."get_gare_details"("p_gare_id" "uuid") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."get_gare_details"("p_gare_id" "uuid") TO "service_role"; + +GRANT ALL ON FUNCTION "public"."get_station_details"("p_station_id" "uuid") TO "anon"; +GRANT ALL ON FUNCTION "public"."get_station_details"("p_station_id" "uuid") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."get_station_details"("p_station_id" "uuid") TO "service_role"; + +GRANT ALL ON FUNCTION "public"."handle_updated_at"() TO "anon"; +GRANT ALL ON FUNCTION "public"."handle_updated_at"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."handle_updated_at"() TO "service_role"; + +GRANT ALL ON FUNCTION "public"."import_gtfs_rpc"("payload" json) TO "anon"; +GRANT ALL ON FUNCTION "public"."import_gtfs_rpc"("payload" json) TO "authenticated"; +GRANT ALL ON FUNCTION "public"."import_gtfs_rpc"("payload" json) TO "service_role"; + + +GRANT ALL ON FUNCTION "public"."set_updated_at"() TO "anon"; +GRANT ALL ON FUNCTION "public"."set_updated_at"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."set_updated_at"() TO "service_role"; + +GRANT ALL ON FUNCTION "public"."update_travaux_updated_at"() TO "anon"; +GRANT ALL ON FUNCTION "public"."update_travaux_updated_at"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."update_travaux_updated_at"() TO "service_role"; + +GRANT ALL ON FUNCTION "public"."update_updated_at_column"() TO "anon"; +GRANT ALL ON FUNCTION "public"."update_updated_at_column"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."update_updated_at_column"() TO "service_role"; + +GRANT ALL ON FUNCTION "public"."update_user_password"("p_user_id" "uuid", "p_password" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."update_user_password"("p_user_id" "uuid", "p_password" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."update_user_password"("p_user_id" "uuid", "p_password" "text") TO "service_role"; + +GRANT ALL ON FUNCTION "public"."update_users_panel_updated_at"() TO "anon"; +GRANT ALL ON FUNCTION "public"."update_users_panel_updated_at"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."update_users_panel_updated_at"() TO "service_role"; + +GRANT ALL ON TABLE "public"."actualites" TO "anon"; +GRANT ALL ON TABLE "public"."actualites" TO "authenticated"; +GRANT ALL ON TABLE "public"."actualites" TO "service_role"; + +GRANT ALL ON TABLE "public"."evenements" TO "anon"; +GRANT ALL ON TABLE "public"."evenements" TO "authenticated"; +GRANT ALL ON TABLE "public"."evenements" TO "service_role"; + +GRANT ALL ON TABLE "public"."gare_quais" TO "anon"; +GRANT ALL ON TABLE "public"."gare_quais" TO "authenticated"; +GRANT ALL ON TABLE "public"."gare_quais" TO "service_role"; + +GRANT ALL ON TABLE "public"."gare_transports" TO "anon"; +GRANT ALL ON TABLE "public"."gare_transports" TO "authenticated"; +GRANT ALL ON TABLE "public"."gare_transports" TO "service_role"; + +GRANT ALL ON TABLE "public"."gares" TO "anon"; +GRANT ALL ON TABLE "public"."gares" TO "authenticated"; +GRANT ALL ON TABLE "public"."gares" TO "service_role"; + +GRANT ALL ON TABLE "public"."horaires" TO "anon"; +GRANT ALL ON TABLE "public"."horaires" TO "authenticated"; +GRANT ALL ON TABLE "public"."horaires" TO "service_role"; + +GRANT ALL ON TABLE "public"."horaires_gares_desservies" TO "anon"; +GRANT ALL ON TABLE "public"."horaires_gares_desservies" TO "authenticated"; +GRANT ALL ON TABLE "public"."horaires_gares_desservies" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."horaires_gares_desservies_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."horaires_gares_desservies_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."horaires_gares_desservies_id_seq" TO "service_role"; + +GRANT ALL ON TABLE "public"."info_trafic" TO "anon"; +GRANT ALL ON TABLE "public"."info_trafic" TO "authenticated"; +GRANT ALL ON TABLE "public"."info_trafic" TO "service_role"; + +GRANT ALL ON TABLE "public"."lignes" TO "anon"; +GRANT ALL ON TABLE "public"."lignes" TO "authenticated"; +GRANT ALL ON TABLE "public"."lignes" TO "service_role"; + +GRANT ALL ON TABLE "public"."lignes_gares" TO "anon"; +GRANT ALL ON TABLE "public"."lignes_gares" TO "authenticated"; +GRANT ALL ON TABLE "public"."lignes_gares" TO "service_role"; + +GRANT ALL ON TABLE "public"."materiel-roulant" TO "anon"; +GRANT ALL ON TABLE "public"."materiel-roulant" TO "authenticated"; +GRANT ALL ON TABLE "public"."materiel-roulant" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."materiel-roulant_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."materiel-roulant_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."materiel-roulant_id_seq" TO "service_role"; + +GRANT ALL ON TABLE "public"."menus_regionaux" TO "anon"; +GRANT ALL ON TABLE "public"."menus_regionaux" TO "authenticated"; +GRANT ALL ON TABLE "public"."menus_regionaux" TO "service_role"; + +GRANT ALL ON TABLE "public"."messagerie_messages" TO "anon"; +GRANT ALL ON TABLE "public"."messagerie_messages" TO "authenticated"; +GRANT ALL ON TABLE "public"."messagerie_messages" TO "service_role"; + +GRANT ALL ON TABLE "public"."messagerie_participants" TO "anon"; +GRANT ALL ON TABLE "public"."messagerie_participants" TO "authenticated"; +GRANT ALL ON TABLE "public"."messagerie_participants" TO "service_role"; + +GRANT ALL ON TABLE "public"."messagerie_threads" TO "anon"; +GRANT ALL ON TABLE "public"."messagerie_threads" TO "authenticated"; +GRANT ALL ON TABLE "public"."messagerie_threads" TO "service_role"; + +GRANT ALL ON TABLE "public"."parametres" TO "anon"; +GRANT ALL ON TABLE "public"."parametres" TO "authenticated"; +GRANT ALL ON TABLE "public"."parametres" TO "service_role"; + +GRANT ALL ON TABLE "public"."perturbations" TO "anon"; +GRANT ALL ON TABLE "public"."perturbations" TO "authenticated"; +GRANT ALL ON TABLE "public"."perturbations" TO "service_role"; + +GRANT ALL ON TABLE "public"."perturbations_modifications_parcours" TO "anon"; +GRANT ALL ON TABLE "public"."perturbations_modifications_parcours" TO "authenticated"; +GRANT ALL ON TABLE "public"."perturbations_modifications_parcours" TO "service_role"; + +GRANT ALL ON TABLE "public"."services_annuels" TO "anon"; +GRANT ALL ON TABLE "public"."services_annuels" TO "authenticated"; +GRANT ALL ON TABLE "public"."services_annuels" TO "service_role"; + +GRANT ALL ON TABLE "public"."tarifs" TO "anon"; +GRANT ALL ON TABLE "public"."tarifs" TO "authenticated"; +GRANT ALL ON TABLE "public"."tarifs" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."tarifs_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."tarifs_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."tarifs_id_seq" TO "service_role"; + +GRANT ALL ON TABLE "public"."travaux" TO "anon"; +GRANT ALL ON TABLE "public"."travaux" TO "authenticated"; +GRANT ALL ON TABLE "public"."travaux" TO "service_role"; + +GRANT ALL ON TABLE "public"."travaux_substitutions" TO "anon"; +GRANT ALL ON TABLE "public"."travaux_substitutions" TO "authenticated"; +GRANT ALL ON TABLE "public"."travaux_substitutions" TO "service_role"; + +GRANT ALL ON TABLE "public"."types_train" TO "anon"; +GRANT ALL ON TABLE "public"."types_train" TO "authenticated"; +GRANT ALL ON TABLE "public"."types_train" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."types_train_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."types_train_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."types_train_id_seq" TO "service_role"; + +GRANT ALL ON TABLE "public"."users_panel" TO "anon"; +GRANT ALL ON TABLE "public"."users_panel" TO "authenticated"; +GRANT ALL ON TABLE "public"."users_panel" TO "service_role"; + +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"; + diff --git a/src/index.js b/src/index.js index d563c0f..82da38f 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; +import * as serviceWorkerRegistration from './serviceWorkerRegistration'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( @@ -15,3 +16,8 @@ root.render( // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: https://cra.link/PWA +serviceWorkerRegistration.register(); diff --git a/src/pages/gares/Gares.js b/src/pages/gares/Gares.js index 9b62b70..4f3fe4d 100644 --- a/src/pages/gares/Gares.js +++ b/src/pages/gares/Gares.js @@ -917,7 +917,7 @@ export default function Gares() { data-field="nom" value={formData.nom} placeholder="Ex: Paris Gare de Lyon" - onWcsChange={(e) => updateField('nom', e.detail?.value || '')} + onInput={(e) => updateField('nom', e.target.value || '')} /> @@ -977,7 +977,7 @@ export default function Gares() { data-quai-id={quai.clientId} value={quai.nom} placeholder="Nom du quai (ex: Voie A)" - onWcsChange={(e) => updateQuai(quai.clientId, 'nom', e.detail?.value || '')} + onInput={(e) => updateQuai(quai.clientId, 'nom', e.target.value || '')} /> updateQuai(quai.clientId, 'distance_metres', parseInt(e.detail?.value, 10) || 0)} + onInput={(e) => updateQuai(quai.clientId, 'distance_metres', parseInt(e.target.value, 10) || 0)} />
{quaisCounts[quai.nom] ? `${quaisCounts[quai.nom]} horaire(s)` : '0 horaire'} diff --git a/src/pages/login/Login.css b/src/pages/login/Login.css index 492d2b1..68a141a 100644 --- a/src/pages/login/Login.css +++ b/src/pages/login/Login.css @@ -166,43 +166,6 @@ } /* ── Mock / debug banner ── */ -.admin-login-mock-banner { - margin-top: 16px; - padding: 10px 14px; - background: #fffbeb; - border: 1px solid #f59e0b; - border-radius: 10px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - flex-wrap: wrap; -} - -.admin-login-mock-banner span { - font-size: 12px; - color: #92400e; - font-weight: 500; -} - -.admin-login-mock-btn { - background: #f59e0b; - color: #1c1917; - border: none; - border-radius: 6px; - padding: 5px 12px; - font-size: 12px; - font-weight: 700; - cursor: pointer; - transition: background 0.15s; - white-space: nowrap; -} - -.admin-login-mock-btn:hover { - background: #d97706; - color: #fff; -} - /* ── Responsive ── */ @media (max-width: 900px) { .admin-login-page { diff --git a/src/pages/login/Login.js b/src/pages/login/Login.js index eb22a6f..e809f0b 100644 --- a/src/pages/login/Login.js +++ b/src/pages/login/Login.js @@ -2,23 +2,6 @@ import React, { useRef, useState, useEffect } from 'react'; import supabase, { isSupabaseConfigured } from '../../supabaseClient'; import './Login.css'; -// ─── Compte mocké pour debug ──────────────────────────────────────────────── -// Désactiver en production en passant REACT_APP_ENABLE_MOCK_LOGIN=false -const MOCK_LOGIN_ENABLED = process.env.REACT_APP_ENABLE_MOCK_LOGIN !== 'false'; - -const MOCK_USER = { - id: 'mock-admin-001', - email: 'mock@ferrovia.dev', - nom: 'Mock', - prenom: 'Admin', - name: 'Mock Admin', - role: 'admin', - permissions: {}, - region_admin: true, - region_id: null, - region_nom: null, -}; -// ──────────────────────────────────────────────────────────────────────────── async function tryFallbackAuth(email, password) { try { @@ -64,13 +47,7 @@ export default function Login({ onLogin }) { // nothing for now }, []); - // ── Login mocké (debug uniquement) ────────────────────────────────────── - const handleMockLogin = () => { - console.info('[Mock Login] Connexion avec le compte de debug', MOCK_USER); - try { window.localStorage.setItem('app_user', JSON.stringify(MOCK_USER)); } catch (_) {} - onLogin(MOCK_USER); - }; - // ──────────────────────────────────────────────────────────────────────── + const handleSubmit = async (e) => { if (e && e.preventDefault) e.preventDefault(); @@ -125,6 +102,9 @@ export default function Login({ onLogin }) { const user = Array.isArray(effectiveData) ? (effectiveData.length ? effectiveData[0] : null) : effectiveData; if (user && user.id) { + // S'assurer que permissions est au moins un objet vide pour que le RBAC applique les templates de base + user.permissions = user.permissions || {}; + // Complète le profil avec les infos régionales nécessaires au RBAC front. try { const { data: profile } = await supabase @@ -221,19 +201,6 @@ export default function Login({ onLogin }) { Activer mon Compte
- {/* ── Bannière de debug (mock login) ── */} - {MOCK_LOGIN_ENABLED && ( -
- 🛠 Mode debug — connexion instantanée sans Supabase - -
- )} diff --git a/src/service-worker.js b/src/service-worker.js new file mode 100644 index 0000000..43afc69 --- /dev/null +++ b/src/service-worker.js @@ -0,0 +1,70 @@ +/* eslint-disable no-restricted-globals */ + +// This service worker can be customized! +// See https://developers.google.com/web/tools/workbox/modules +// for the list of available Workbox modules, or add any other +// code you'd like. +// You can also remove this file if you'd prefer not to use a +// service worker, and the Workbox build step will be skipped. + +import { clientsClaim } from 'workbox-core'; +import { ExpirationPlugin } from 'workbox-expiration'; +import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; +import { registerRoute } from 'workbox-routing'; +import { StaleWhileRevalidate } from 'workbox-strategies'; + +clientsClaim(); + +// Precache all of the assets generated by your build process. +// Their URLs are injected into the manifest variable below. +// This variable must be present somewhere in your service worker file, +// even if you decide not to use precaching. See https://cra.link/PWA +precacheAndRoute(self.__WB_MANIFEST); + +// Set up App Shell-style routing, so that all navigation requests +// are fulfilled with your index.html shell. Learn more at +// https://developers.google.com/web/fundamentals/architecture/app-shell +const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$'); +registerRoute( + // Return false to exempt requests from being fulfilled by index.html. + ({ request, url }) => { + // If this isn't a navigation, skip. + if (request.mode !== 'navigate') { + return false; + } // If this is a URL that starts with /_, skip. + + if (url.pathname.startsWith('/_')) { + return false; + } // If this looks like a URL for a resource, because it contains // a file extension, skip. + + if (url.pathname.match(fileExtensionRegexp)) { + return false; + } // Return true to signal that we want to use the handler. + + return true; + }, + createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') +); + +// An example runtime caching route for requests that aren't handled by the +// precache, in this case same-origin .png requests like those from in public/ +registerRoute( + // Add in any other file extensions or routing criteria as needed. + ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst. + new StaleWhileRevalidate({ + cacheName: 'images', + plugins: [ + // Ensure that once this runtime cache reaches a maximum size the + // least-recently used images are removed. + new ExpirationPlugin({ maxEntries: 50 }), + ], + }) +); + +// This allows the web app to trigger skipWaiting via +// registration.waiting.postMessage({type: 'SKIP_WAITING'}) +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); diff --git a/src/serviceWorkerRegistration.js b/src/serviceWorkerRegistration.js new file mode 100644 index 0000000..82025cd --- /dev/null +++ b/src/serviceWorkerRegistration.js @@ -0,0 +1,132 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.0/8 are considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export function register(config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://cra.link/PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl, config) { + navigator.serviceWorker + .register(swUrl) + .then((registration) => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://cra.link/PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch((error) => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl, config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl, { + headers: { 'Service-Worker': 'script' }, + }) + .then((response) => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then((registration) => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready + .then((registration) => { + registration.unregister(); + }) + .catch((error) => { + console.error(error.message); + }); + } +}