diff --git a/database/BDD.sql b/database/BDD.sql index 042ca39..d07905f 100644 --- a/database/BDD.sql +++ b/database/BDD.sql @@ -46,19 +46,20 @@ CREATE TABLE utilisateurs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) NOT NULL UNIQUE, CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'), - password TEXT NOT NULL, + password TEXT, -- NULL avant création via token prenom VARCHAR(100), nom VARCHAR(100), genre genre_type, role role_type NOT NULL, statut statut_utilisateur_type DEFAULT 'en_attente', - mobile VARCHAR(20), - telephone_fixe VARCHAR(20), + telephone VARCHAR(20), -- Unifié (mobile privilégié) adresse TEXT, date_naissance DATE, - photo_url TEXT, + photo_url TEXT, -- Obligatoire pour AM, non utilisé pour parents consentement_photo BOOLEAN DEFAULT false, date_consentement_photo TIMESTAMPTZ, + token_creation_mdp VARCHAR(255), -- Token pour créer MDP après validation + token_creation_mdp_expire_le TIMESTAMPTZ, -- Expiration 7 jours changement_mdp_obligatoire BOOLEAN DEFAULT false, cree_le TIMESTAMPTZ DEFAULT now(), modifie_le TIMESTAMPTZ DEFAULT now(), @@ -68,16 +69,19 @@ CREATE TABLE utilisateurs ( situation_familiale situation_familiale_type ); +-- Index pour recherche par token +CREATE INDEX idx_utilisateurs_token_creation_mdp + ON utilisateurs(token_creation_mdp) + WHERE token_creation_mdp IS NOT NULL; + -- ========================================================== -- Table : assistantes_maternelles -- ========================================================== CREATE TABLE assistantes_maternelles ( id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE, numero_agrement VARCHAR(50), - date_agrement DATE, + date_agrement DATE NOT NULL, -- Obligatoire selon CDC v1.3 nir_chiffre CHAR(15), - annee_experience SMALLINT, - specialite VARCHAR (100), nb_max_enfants INT, place_disponible INT, biographie TEXT, @@ -100,7 +104,7 @@ CREATE TABLE enfants ( statut statut_enfant_type, prenom VARCHAR(100), nom VARCHAR(100), - genre genre_type, + genre genre_type NOT NULL, -- Obligatoire selon CDC date_naissance DATE, date_prevue_naissance DATE, photo_url TEXT, diff --git a/database/migrations/01_init.sql b/database/migrations/01_init.sql deleted file mode 100644 index a2076a4..0000000 --- a/database/migrations/01_init.sql +++ /dev/null @@ -1,277 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS "pgcrypto"; - --- ========================================================== --- ENUMS --- ========================================================== -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'role_type') THEN - CREATE TYPE role_type AS ENUM ('parent', 'gestionnaire', 'super_admin', 'assistante_maternelle','administrateur'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'genre_type') THEN - CREATE TYPE genre_type AS ENUM ('H', 'F', 'Autre'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_utilisateur_type') THEN - CREATE TYPE statut_utilisateur_type AS ENUM ('en_attente','actif','suspendu'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_enfant_type') THEN - CREATE TYPE statut_enfant_type AS ENUM ('a_naitre','actif','scolarise'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_dossier_type') THEN - CREATE TYPE statut_dossier_type AS ENUM ('envoye','accepte','refuse'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_contrat_type') THEN - CREATE TYPE statut_contrat_type AS ENUM ('brouillon','en_attente_signature','valide','resilie'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_avenant_type') THEN - CREATE TYPE statut_avenant_type AS ENUM ('propose','accepte','refuse'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'type_evenement_type') THEN - CREATE TYPE type_evenement_type AS ENUM ('absence_enfant','conge_am','conge_parent','arret_maladie_am','evenement_rpe'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_evenement_type') THEN - CREATE TYPE statut_evenement_type AS ENUM ('propose','valide','refuse'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_validation_type') THEN - CREATE TYPE statut_validation_type AS ENUM ('en_attente','valide','refuse'); - END IF; -END $$; - --- ========================================================== --- Table : utilisateurs --- ========================================================== -CREATE TABLE utilisateurs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - email VARCHAR(255) NOT NULL UNIQUE, - CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'), - password TEXT NOT NULL, - prenom VARCHAR(100), - nom VARCHAR(100), - genre genre_type, - role role_type NOT NULL, - statut statut_utilisateur_type DEFAULT 'en_attente', - telephone VARCHAR(20), - adresse TEXT, - photo_url TEXT, - consentement_photo BOOLEAN DEFAULT false, - date_consentement_photo TIMESTAMPTZ, - changement_mdp_obligatoire BOOLEAN DEFAULT false, - cree_le TIMESTAMPTZ DEFAULT now(), - modifie_le TIMESTAMPTZ DEFAULT now(), - ville VARCHAR(150), - code_postal VARCHAR(10), - mobile VARCHAR(20), - telephone_fixe VARCHAR(20), - profession VARCHAR(150), - situation_familiale VARCHAR(50), - date_naissance DATE -); - --- ========================================================== --- Table : assistantes_maternelles --- ========================================================== -CREATE TABLE assistantes_maternelles ( - id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE, - numero_agrement VARCHAR(50), - nir_chiffre CHAR(15), - nb_max_enfants INT, - biographie TEXT, - disponible BOOLEAN DEFAULT true, - ville_residence VARCHAR(100), - date_agrement DATE, - annee_experience SMALLINT, - specialite VARCHAR(100), - place_disponible INT -); - --- ========================================================== --- Table : parentschange les donnée de init --- ========================================================== -CREATE TABLE parents ( - id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE, - id_co_parent UUID REFERENCES utilisateurs(id) -); - --- ========================================================== --- Table : enfants --- ========================================================== -CREATE TABLE enfants ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - statut statut_enfant_type, - prenom VARCHAR(100), - nom VARCHAR(100), - genre genre_type, - date_naissance DATE, - date_prevue_naissance DATE, - photo_url TEXT, - consentement_photo BOOLEAN DEFAULT false, - date_consentement_photo TIMESTAMPTZ, - est_multiple BOOLEAN DEFAULT false -); - --- ========================================================== --- Table : enfants_parents --- ========================================================== -CREATE TABLE enfants_parents ( - id_parent UUID REFERENCES parents(id_utilisateur) ON DELETE CASCADE, - id_enfant UUID REFERENCES enfants(id) ON DELETE CASCADE, - PRIMARY KEY (id_parent, id_enfant) -); - --- ========================================================== --- Table : dossiers --- ========================================================== -CREATE TABLE dossiers ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - id_parent UUID REFERENCES parents(id_utilisateur) ON DELETE CASCADE, - id_enfant UUID REFERENCES enfants(id) ON DELETE CASCADE, - presentation TEXT, - type_contrat VARCHAR(50), - repas BOOLEAN DEFAULT false, - budget NUMERIC(10,2), - planning_souhaite JSONB, - statut statut_dossier_type DEFAULT 'envoye', - cree_le TIMESTAMPTZ DEFAULT now(), - modifie_le TIMESTAMPTZ DEFAULT now() -); - --- ========================================================== --- Table : messages --- ========================================================== -CREATE TABLE messages ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - id_dossier UUID REFERENCES dossiers(id) ON DELETE CASCADE, - id_expediteur UUID REFERENCES utilisateurs(id) ON DELETE CASCADE, - contenu TEXT, - re_redige_par_ia BOOLEAN DEFAULT false, - cree_le TIMESTAMPTZ DEFAULT now() -); - --- ========================================================== --- Table : contrats --- ========================================================== -CREATE TABLE contrats ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - id_dossier UUID UNIQUE REFERENCES dossiers(id) ON DELETE CASCADE, - planning JSONB, - tarif_horaire NUMERIC(6,2), - indemnites_repas NUMERIC(6,2), - date_debut DATE, - statut statut_contrat_type DEFAULT 'brouillon', - signe_parent BOOLEAN DEFAULT false, - signe_am BOOLEAN DEFAULT false, - finalise_le TIMESTAMPTZ, - cree_le TIMESTAMPTZ DEFAULT now(), - modifie_le TIMESTAMPTZ DEFAULT now() -); - --- ========================================================== --- Table : avenants_contrats --- ========================================================== -CREATE TABLE avenants_contrats ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - id_contrat UUID REFERENCES contrats(id) ON DELETE CASCADE, - modifications JSONB, - initie_par UUID REFERENCES utilisateurs(id), - statut statut_avenant_type DEFAULT 'propose', - cree_le TIMESTAMPTZ DEFAULT now(), - modifie_le TIMESTAMPTZ DEFAULT now() -); - --- ========================================================== --- Table : evenements --- ========================================================== -CREATE TABLE evenements ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - type type_evenement_type, - id_enfant UUID REFERENCES enfants(id) ON DELETE CASCADE, - id_am UUID REFERENCES utilisateurs(id), - id_parent UUID REFERENCES parents(id_utilisateur), - cree_par UUID REFERENCES utilisateurs(id), - date_debut TIMESTAMPTZ, - date_fin TIMESTAMPTZ, - commentaires TEXT, - statut statut_evenement_type DEFAULT 'propose', - delai_grace TIMESTAMPTZ, - urgent BOOLEAN DEFAULT false, - cree_le TIMESTAMPTZ DEFAULT now(), - modifie_le TIMESTAMPTZ DEFAULT now() -); - --- ========================================================== --- Table : signalements_bugs --- ========================================================== -CREATE TABLE signalements_bugs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - id_utilisateur UUID REFERENCES utilisateurs(id), - description TEXT, - cree_le TIMESTAMPTZ DEFAULT now() -); - --- ========================================================== --- Table : uploads --- ========================================================== -CREATE TABLE uploads ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - id_utilisateur UUID REFERENCES utilisateurs(id) ON DELETE SET NULL, - fichier_url TEXT NOT NULL, - type VARCHAR(50), - cree_le TIMESTAMPTZ DEFAULT now() -); - --- ========================================================== --- Table : notifications --- ========================================================== -CREATE TABLE notifications ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - id_utilisateur UUID REFERENCES utilisateurs(id) ON DELETE CASCADE, - contenu TEXT, - lu BOOLEAN DEFAULT false, - cree_le TIMESTAMPTZ DEFAULT now() -); - --- ========================================================== --- Table : validations --- ========================================================== -CREATE TABLE validations ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - id_utilisateur UUID REFERENCES utilisateurs(id), - type VARCHAR(50), - statut statut_validation_type DEFAULT 'en_attente', - cree_le TIMESTAMPTZ DEFAULT now(), - modifie_le TIMESTAMPTZ DEFAULT now(), - valide_par UUID REFERENCES utilisateurs(id), - commentaire TEXT -); - - --- ========================================================== --- Initialisation d'un administrateur par défaut --- ========================================================== - --- ========================================================== --- SEED: Super Administrateur par défaut --- ========================================================== --- Mot de passe: 4dm1n1strateur -INSERT INTO utilisateurs ( - id, - email, - password, - prenom, - nom, - role, - statut, - cree_le, - modifie_le -) -VALUES ( - gen_random_uuid(), - 'admin@ptits-pas.fr', - '$2b$12$Fo5ly1YlTj3O6lXf.IUgoeUqEebBGpmoM5zLbzZx.CueorSE7z2E2', - 'Admin', - 'Système', - 'super_admin', - 'actif', - now(), - now() -) -ON CONFLICT (email) DO NOTHING; diff --git a/database/migrations/02_indexes.sql b/database/migrations/02_indexes.sql deleted file mode 100644 index bd912c4..0000000 --- a/database/migrations/02_indexes.sql +++ /dev/null @@ -1,157 +0,0 @@ --- ============================================= --- 02_indexes.sql : Index FK + colonnes critiques --- ============================================= - --- Recommandation : exécuter après 01_init.sql - --- =========== --- UTILISATEURS --- =========== --- Recherche par email (insensibilité à la casse pour lookup) -CREATE INDEX IF NOT EXISTS idx_utilisateurs_lower_courriel - ON utilisateurs (LOWER(courriel)); - --- =========== --- ASSISTANTES_MATERNELLES --- =========== --- FK -> utilisateurs(id) -CREATE INDEX IF NOT EXISTS idx_am_id_utilisateur - ON assistantes_maternelles (id_utilisateur); - --- ======= --- PARENTS --- ======= --- FK -> utilisateurs(id) -CREATE INDEX IF NOT EXISTS idx_parents_id_utilisateur - ON parents (id_utilisateur); - --- Co-parent (nullable) -CREATE INDEX IF NOT EXISTS idx_parents_id_co_parent - ON parents (id_co_parent); - --- ======= --- ENFANTS --- ======= --- (souvent filtrés par statut / date_naissance ? à activer si besoin) --- CREATE INDEX IF NOT EXISTS idx_enfants_statut ON enfants (statut); --- CREATE INDEX IF NOT EXISTS idx_enfants_date_naissance ON enfants (date_naissance); - --- ================ --- ENFANTS_PARENTS (N:N) --- ================ --- PK composite déjà en place (id_parent, id_enfant), ajouter index individuels si jointures unilatérales fréquentes -CREATE INDEX IF NOT EXISTS idx_enfants_parents_id_parent - ON enfants_parents (id_parent); - -CREATE INDEX IF NOT EXISTS idx_enfants_parents_id_enfant - ON enfants_parents (id_enfant); - --- ======== --- DOSSIERS --- ======== --- FK -> parent / enfant -CREATE INDEX IF NOT EXISTS idx_dossiers_id_parent - ON dossiers (id_parent); - -CREATE INDEX IF NOT EXISTS idx_dossiers_id_enfant - ON dossiers (id_enfant); - --- Statut (filtrages "à traiter", "envoyé", etc.) -CREATE INDEX IF NOT EXISTS idx_dossiers_statut - ON dossiers (statut); - --- JSONB : si on fait des requêtes @> sur le planning souhaité --- CREATE INDEX IF NOT EXISTS idx_dossiers_planning_souhaite_gin --- ON dossiers USING GIN (planning_souhaite); - --- ======== --- MESSAGES --- ======== --- Filtrage par dossier + tri chronologique -CREATE INDEX IF NOT EXISTS idx_messages_id_dossier_cree_le - ON messages (id_dossier, cree_le); - --- Recherche par expéditeur -CREATE INDEX IF NOT EXISTS idx_messages_id_expediteur_cree_le - ON messages (id_expediteur, cree_le); - --- ========= --- CONTRATS --- ========= --- UNIQUE(id_dossier) existe déjà -> index implicite --- Tri / filtres fréquents -CREATE INDEX IF NOT EXISTS idx_contrats_statut - ON contrats (statut); - --- JSONB planning : activer si on requête par clé --- CREATE INDEX IF NOT EXISTS idx_contrats_planning_gin --- ON contrats USING GIN (planning); - --- ================== --- AVENANTS_CONTRATS --- ================== -CREATE INDEX IF NOT EXISTS idx_avenants_contrats_id_contrat_cree_le - ON avenants_contrats (id_contrat, cree_le); - -CREATE INDEX IF NOT EXISTS idx_avenants_contrats_initie_par - ON avenants_contrats (initie_par); - -CREATE INDEX IF NOT EXISTS idx_avenants_contrats_statut - ON avenants_contrats (statut); - --- ========= --- EVENEMENTS --- ========= --- Accès par enfant + période -CREATE INDEX IF NOT EXISTS idx_evenements_id_enfant_date_debut - ON evenements (id_enfant, date_debut); - --- Filtrage par auteur / AM / parent -CREATE INDEX IF NOT EXISTS idx_evenements_cree_par - ON evenements (cree_par); - -CREATE INDEX IF NOT EXISTS idx_evenements_id_am - ON evenements (id_am); - -CREATE INDEX IF NOT EXISTS idx_evenements_id_parent - ON evenements (id_parent); - -CREATE INDEX IF NOT EXISTS idx_evenements_type - ON evenements (type); - -CREATE INDEX IF NOT EXISTS idx_evenements_statut - ON evenements (statut); - --- ================= --- SIGNALEMENTS_BUGS --- ================= -CREATE INDEX IF NOT EXISTS idx_signalements_bugs_id_utilisateur_cree_le - ON signalements_bugs (id_utilisateur, cree_le); - --- ======= --- UPLOADS --- ======= -CREATE INDEX IF NOT EXISTS idx_uploads_id_utilisateur_cree_le - ON uploads (id_utilisateur, cree_le); - --- ============= --- NOTIFICATIONS --- ============= --- Requêtes fréquentes : non lues + ordre chrono -CREATE INDEX IF NOT EXISTS idx_notifications_user_lu_cree_le - ON notifications (id_utilisateur, lu, cree_le); - --- Option : index partiel pour "non lues" --- CREATE INDEX IF NOT EXISTS idx_notifications_non_lues --- ON notifications (id_utilisateur, cree_le) --- WHERE lu = false; - --- =========== --- VALIDATIONS --- =========== --- Requêtes par utilisateur validé, par statut et par date -CREATE INDEX IF NOT EXISTS idx_validations_id_utilisateur_cree_le - ON validations (id_utilisateur, cree_le); - -CREATE INDEX IF NOT EXISTS idx_validations_statut - ON validations (statut); diff --git a/database/migrations/03_checks.sql b/database/migrations/03_checks.sql deleted file mode 100644 index 9d1e159..0000000 --- a/database/migrations/03_checks.sql +++ /dev/null @@ -1,140 +0,0 @@ --- ============================================= --- 03_checks.sql : Contraintes CHECK & NOT NULL --- A exécuter après 01_init.sql (et 02_indexes.sql) --- ============================================= - --- ============== --- UTILISATEURS --- ============== --- (Regex email déjà présente dans 01_init.sql) --- Optionnel : forcer prenom/nom non vides si fournis -ALTER TABLE utilisateurs - ADD CONSTRAINT chk_utilisateurs_prenom_non_vide - CHECK (prenom IS NULL OR btrim(prenom) <> ''), - ADD CONSTRAINT chk_utilisateurs_nom_non_vide - CHECK (nom IS NULL OR btrim(nom) <> ''); - --- ========================= --- ASSISTANTES_MATERNELLES --- ========================= --- NIR : aujourd’hui en 15 chiffres (Sprint 2 : chiffrement) -ALTER TABLE assistantes_maternelles - ADD CONSTRAINT chk_am_nir_format - CHECK (nir_chiffre IS NULL OR nir_chiffre ~ '^[0-9]{15}$'), - ADD CONSTRAINT chk_am_nb_max_enfants - CHECK (nb_max_enfants IS NULL OR nb_max_enfants BETWEEN 0 AND 10), - ADD CONSTRAINT chk_am_ville_non_vide - CHECK (ville_residence IS NULL OR btrim(ville_residence) <> ''); - --- ========= --- PARENTS --- ========= --- Interdiction d’être co-parent de soi-même -ALTER TABLE parents - ADD CONSTRAINT chk_parents_co_parent_diff - CHECK (id_co_parent IS NULL OR id_co_parent <> id_utilisateur); - --- ========= --- ENFANTS --- ========= --- Cohérence statut / dates de naissance -ALTER TABLE enfants - ADD CONSTRAINT chk_enfants_dates_exclusives - CHECK (NOT (date_naissance IS NOT NULL AND date_prevue_naissance IS NOT NULL)), - ADD CONSTRAINT chk_enfants_statut_dates - CHECK ( - -- a_naitre => date_prevue_naissance requise - (statut = 'a_naitre' AND date_prevue_naissance IS NOT NULL) - OR - -- actif/scolarise => date_naissance requise - (statut IN ('actif','scolarise') AND date_naissance IS NOT NULL) - OR statut IS NULL -- si statut non encore fixé - ), - ADD CONSTRAINT chk_enfants_consentement_coherent - CHECK ( - (consentement_photo = true AND date_consentement_photo IS NOT NULL) - OR - (consentement_photo = false AND date_consentement_photo IS NULL) - ); - --- ================= --- ENFANTS_PARENTS --- ================= --- (PK composite déjà en place, rien à ajouter ici) - --- ======== --- DOSSIERS --- ======== -ALTER TABLE dossiers - ADD CONSTRAINT chk_dossiers_budget_nonneg - CHECK (budget IS NULL OR budget >= 0), - ADD CONSTRAINT chk_dossiers_type_contrat_non_vide - CHECK (type_contrat IS NULL OR btrim(type_contrat) <> ''), - ADD CONSTRAINT chk_dossiers_planning_json - CHECK (planning_souhaite IS NULL OR jsonb_typeof(planning_souhaite) = 'object'); - --- ======== --- MESSAGES --- ======== --- Contenu obligatoire, non vide -ALTER TABLE messages - ALTER COLUMN contenu SET NOT NULL; -ALTER TABLE messages - ADD CONSTRAINT chk_messages_contenu_non_vide - CHECK (btrim(contenu) <> ''); - --- ========= --- CONTRATS --- ========= -ALTER TABLE contrats - ADD CONSTRAINT chk_contrats_tarif_nonneg - CHECK (tarif_horaire IS NULL OR tarif_horaire >= 0), - ADD CONSTRAINT chk_contrats_indemnites_nonneg - CHECK (indemnites_repas IS NULL OR indemnites_repas >= 0); - --- ================== --- AVENANTS_CONTRATS --- ================== --- Rien de spécifique (statut enum déjà en place) - --- ========= --- EVENEMENTS --- ========= -ALTER TABLE evenements - ADD CONSTRAINT chk_evenements_dates_coherentes - CHECK (date_fin IS NULL OR date_debut IS NULL OR date_fin >= date_debut); - --- ================= --- SIGNALEMENTS_BUGS --- ================= --- Description obligatoire, non vide -ALTER TABLE signalements_bugs - ALTER COLUMN description SET NOT NULL; -ALTER TABLE signalements_bugs - ADD CONSTRAINT chk_bugs_description_non_vide - CHECK (btrim(description) <> ''); - --- ======= --- UPLOADS --- ======= --- URL obligatoire + format basique (chemin absolu ou http(s)) -ALTER TABLE uploads - ALTER COLUMN fichier_url SET NOT NULL; -ALTER TABLE uploads - ADD CONSTRAINT chk_uploads_url_format - CHECK (fichier_url ~ '^(https?://.+|/[^\\s]+)$'); - --- ============= --- NOTIFICATIONS --- ============= --- Contenu obligatoire, non vide -ALTER TABLE notifications - ALTER COLUMN contenu SET NOT NULL; -ALTER TABLE notifications - ADD CONSTRAINT chk_notifications_contenu_non_vide - CHECK (btrim(contenu) <> ''); - --- =========== --- VALIDATIONS --- =========== --- Rien de plus ici (Sprint 1 Ticket 8 enrichira la table) diff --git a/database/migrations/04_fk_policies.sql b/database/migrations/04_fk_policies.sql deleted file mode 100644 index e1144f1..0000000 --- a/database/migrations/04_fk_policies.sql +++ /dev/null @@ -1,190 +0,0 @@ --- ========================================================== --- 04_fk_policies.sql : normalisation des politiques ON DELETE --- A exécuter après 01_init.sql et 03_checks.sql --- ========================================================== - --- Helper: Drop FK d'une table/colonne si elle existe (par son/leurs noms de colonne) --- puis recrée la contrainte avec la clause fournie --- Utilise information_schema pour retrouver le nom de contrainte auto-généré --- NB: schema = public - --- ========== messages.id_expediteur -> utilisateurs.id : SET NULL (au lieu de CASCADE) -DO $$ -DECLARE - conname text; -BEGIN - SELECT tc.constraint_name INTO conname - FROM information_schema.table_constraints tc - JOIN information_schema.key_column_usage kcu - ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema - WHERE tc.table_schema='public' - AND tc.table_name='messages' - AND tc.constraint_type='FOREIGN KEY' - AND kcu.column_name='id_expediteur'; - IF conname IS NOT NULL THEN - EXECUTE format('ALTER TABLE public.messages DROP CONSTRAINT %I', conname); - END IF; - EXECUTE $sql$ - ALTER TABLE public.messages - ADD CONSTRAINT fk_messages_id_expediteur - FOREIGN KEY (id_expediteur) REFERENCES public.utilisateurs(id) ON DELETE SET NULL - $sql$; -END $$; - --- ========== parents.id_co_parent -> utilisateurs.id : SET NULL -DO $$ -DECLARE conname text; -BEGIN - SELECT tc.constraint_name INTO conname - FROM information_schema.table_constraints tc - JOIN information_schema.key_column_usage kcu - ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema - WHERE tc.table_schema='public' - AND tc.table_name='parents' - AND tc.constraint_type='FOREIGN KEY' - AND kcu.column_name='id_co_parent'; - IF conname IS NOT NULL THEN - EXECUTE format('ALTER TABLE public.parents DROP CONSTRAINT %I', conname); - END IF; - EXECUTE $sql$ - ALTER TABLE public.parents - ADD CONSTRAINT fk_parents_id_co_parent - FOREIGN KEY (id_co_parent) REFERENCES public.utilisateurs(id) ON DELETE SET NULL - $sql$; -END $$; - --- ========== avenants_contrats.initie_par -> utilisateurs.id : SET NULL -DO $$ -DECLARE conname text; -BEGIN - SELECT tc.constraint_name INTO conname - FROM information_schema.table_constraints tc - JOIN information_schema.key_column_usage kcu - ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema - WHERE tc.table_schema='public' - AND tc.table_name='avenants_contrats' - AND tc.constraint_type='FOREIGN KEY' - AND kcu.column_name='initie_par'; - IF conname IS NOT NULL THEN - EXECUTE format('ALTER TABLE public.avenants_contrats DROP CONSTRAINT %I', conname); - END IF; - EXECUTE $sql$ - ALTER TABLE public.avenants_contrats - ADD CONSTRAINT fk_avenants_contrats_initie_par - FOREIGN KEY (initie_par) REFERENCES public.utilisateurs(id) ON DELETE SET NULL - $sql$; -END $$; - --- ========== evenements.id_am -> utilisateurs.id : SET NULL -DO $$ -DECLARE conname text; -BEGIN - SELECT tc.constraint_name INTO conname - FROM information_schema.table_constraints tc - JOIN information_schema.key_column_usage kcu - ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema - WHERE tc.table_schema='public' - AND tc.table_name='evenements' - AND tc.constraint_type='FOREIGN KEY' - AND kcu.column_name='id_am'; - IF conname IS NOT NULL THEN - EXECUTE format('ALTER TABLE public.evenements DROP CONSTRAINT %I', conname); - END IF; - EXECUTE $sql$ - ALTER TABLE public.evenements - ADD CONSTRAINT fk_evenements_id_am - FOREIGN KEY (id_am) REFERENCES public.utilisateurs(id) ON DELETE SET NULL - $sql$; -END $$; - --- ========== evenements.id_parent -> parents.id_utilisateur : SET NULL -DO $$ -DECLARE conname text; -BEGIN - SELECT tc.constraint_name INTO conname - FROM information_schema.table_constraints tc - JOIN information_schema.key_column_usage kcu - ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema - WHERE tc.table_schema='public' - AND tc.table_name='evenements' - AND tc.constraint_type='FOREIGN KEY' - AND kcu.column_name='id_parent'; - IF conname IS NOT NULL THEN - EXECUTE format('ALTER TABLE public.evenements DROP CONSTRAINT %I', conname); - END IF; - EXECUTE $sql$ - ALTER TABLE public.evenements - ADD CONSTRAINT fk_evenements_id_parent - FOREIGN KEY (id_parent) REFERENCES public.parents(id_utilisateur) ON DELETE SET NULL - $sql$; -END $$; - --- ========== evenements.cree_par -> utilisateurs.id : SET NULL -DO $$ -DECLARE conname text; -BEGIN - SELECT tc.constraint_name INTO conname - FROM information_schema.table_constraints tc - JOIN information_schema.key_column_usage kcu - ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema - WHERE tc.table_schema='public' - AND tc.table_name='evenements' - AND tc.constraint_type='FOREIGN KEY' - AND kcu.column_name='cree_par'; - IF conname IS NOT NULL THEN - EXECUTE format('ALTER TABLE public.evenements DROP CONSTRAINT %I', conname); - END IF; - EXECUTE $sql$ - ALTER TABLE public.evenements - ADD CONSTRAINT fk_evenements_cree_par - FOREIGN KEY (cree_par) REFERENCES public.utilisateurs(id) ON DELETE SET NULL - $sql$; -END $$; - --- ========== signalements_bugs.id_utilisateur -> utilisateurs.id : SET NULL -DO $$ -DECLARE conname text; -BEGIN - SELECT tc.constraint_name INTO conname - FROM information_schema.table_constraints tc - JOIN information_schema.key_column_usage kcu - ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema - WHERE tc.table_schema='public' - AND tc.table_name='signalements_bugs' - AND tc.constraint_type='FOREIGN KEY' - AND kcu.column_name='id_utilisateur'; - IF conname IS NOT NULL THEN - EXECUTE format('ALTER TABLE public.signalements_bugs DROP CONSTRAINT %I', conname); - END IF; - EXECUTE $sql$ - ALTER TABLE public.signalements_bugs - ADD CONSTRAINT fk_signalements_bugs_id_utilisateur - FOREIGN KEY (id_utilisateur) REFERENCES public.utilisateurs(id) ON DELETE SET NULL - $sql$; -END $$; - --- ========== validations.id_utilisateur -> utilisateurs.id : SET NULL -DO $$ -DECLARE conname text; -BEGIN - SELECT tc.constraint_name INTO conname - FROM information_schema.table_constraints tc - JOIN information_schema.key_column_usage kcu - ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema - WHERE tc.table_schema='public' - AND tc.table_name='validations' - AND tc.constraint_type='FOREIGN KEY' - AND kcu.column_name='id_utilisateur'; - IF conname IS NOT NULL THEN - EXECUTE format('ALTER TABLE public.validations DROP CONSTRAINT %I', conname); - END IF; - EXECUTE $sql$ - ALTER TABLE public.validations - ADD CONSTRAINT fk_validations_id_utilisateur - FOREIGN KEY (id_utilisateur) REFERENCES public.utilisateurs(id) ON DELETE SET NULL - $sql$; -END $$; - --- NB: --- D'autres FK déjà correctes : CASCADE (assistantes_maternelles, parents, enfants_parents, dossiers, messages.id_dossier, contrats, avenants_contrats.id_contrat, evenements.id_enfant), SET NULL (uploads). --- On laisse ON UPDATE par défaut (NO ACTION), car les UUID ne changent pas. diff --git a/database/migrations/05_triggers.sql b/database/migrations/05_triggers.sql deleted file mode 100644 index 5d4b489..0000000 --- a/database/migrations/05_triggers.sql +++ /dev/null @@ -1,150 +0,0 @@ --- ============================================= --- 05_triggers.sql : Timestamps automatiques --- - Ajoute (si absent) cree_le DEFAULT now() et modifie_le DEFAULT now() --- - Crée un trigger BEFORE UPDATE pour mettre à jour modifie_le --- - Idempotent (DROP TRIGGER IF EXISTS / IF NOT EXISTS) --- A exécuter après 01_init.sql, 02_indexes.sql, 03_checks.sql --- ============================================= - --- 1) Fonction unique de mise à jour du timestamp -CREATE OR REPLACE FUNCTION set_modifie_le() -RETURNS TRIGGER AS $$ -BEGIN - NEW.modifie_le := NOW(); - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Helper macro-like: pour chaque table, s'assurer des colonnes & trigger --- (on ne peut pas faire de macro, donc on répète pour chaque table) - --- Liste des tables concernées : --- utilisateurs, assistantes_maternelles, parents, enfants, enfants_parents, --- dossiers, messages, contrats, avenants_contrats, evenements, --- signalements_bugs, uploads, notifications, validations - --- ========== UTILISATEURS -ALTER TABLE utilisateurs - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_utilisateurs_set_modifie_le ON utilisateurs; -CREATE TRIGGER trg_utilisateurs_set_modifie_le -BEFORE UPDATE ON utilisateurs -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== ASSISTANTES_MATERNELLES -ALTER TABLE assistantes_maternelles - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_am_set_modifie_le ON assistantes_maternelles; -CREATE TRIGGER trg_am_set_modifie_le -BEFORE UPDATE ON assistantes_maternelles -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== PARENTS -ALTER TABLE parents - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_parents_set_modifie_le ON parents; -CREATE TRIGGER trg_parents_set_modifie_le -BEFORE UPDATE ON parents -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== ENFANTS -ALTER TABLE enfants - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_enfants_set_modifie_le ON enfants; -CREATE TRIGGER trg_enfants_set_modifie_le -BEFORE UPDATE ON enfants -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== ENFANTS_PARENTS (table de liaison) -ALTER TABLE enfants_parents - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_enfants_parents_set_modifie_le ON enfants_parents; -CREATE TRIGGER trg_enfants_parents_set_modifie_le -BEFORE UPDATE ON enfants_parents -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== DOSSIERS -ALTER TABLE dossiers - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_dossiers_set_modifie_le ON dossiers; -CREATE TRIGGER trg_dossiers_set_modifie_le -BEFORE UPDATE ON dossiers -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== MESSAGES -ALTER TABLE messages - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_messages_set_modifie_le ON messages; -CREATE TRIGGER trg_messages_set_modifie_le -BEFORE UPDATE ON messages -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== CONTRATS -ALTER TABLE contrats - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_contrats_set_modifie_le ON contrats; -CREATE TRIGGER trg_contrats_set_modifie_le -BEFORE UPDATE ON contrats -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== AVENANTS_CONTRATS -ALTER TABLE avenants_contrats - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_avenants_contrats_set_modifie_le ON avenants_contrats; -CREATE TRIGGER trg_avenants_contrats_set_modifie_le -BEFORE UPDATE ON avenants_contrats -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== EVENEMENTS -ALTER TABLE evenements - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_evenements_set_modifie_le ON evenements; -CREATE TRIGGER trg_evenements_set_modifie_le -BEFORE UPDATE ON evenements -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== SIGNALEMENTS_BUGS -ALTER TABLE signalements_bugs - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_signalements_bugs_set_modifie_le ON signalements_bugs; -CREATE TRIGGER trg_signalements_bugs_set_modifie_le -BEFORE UPDATE ON signalements_bugs -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== UPLOADS -ALTER TABLE uploads - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_uploads_set_modifie_le ON uploads; -CREATE TRIGGER trg_uploads_set_modifie_le -BEFORE UPDATE ON uploads -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== NOTIFICATIONS -ALTER TABLE notifications - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_notifications_set_modifie_le ON notifications; -CREATE TRIGGER trg_notifications_set_modifie_le -BEFORE UPDATE ON notifications -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); - --- ========== VALIDATIONS -ALTER TABLE validations - ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(), - ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW(); -DROP TRIGGER IF EXISTS trg_validations_set_modifie_le ON validations; -CREATE TRIGGER trg_validations_set_modifie_le -BEFORE UPDATE ON validations -FOR EACH ROW EXECUTE FUNCTION set_modifie_le(); diff --git a/database/migrations/06_validations_enrich.sql b/database/migrations/06_validations_enrich.sql deleted file mode 100644 index 65bacff..0000000 --- a/database/migrations/06_validations_enrich.sql +++ /dev/null @@ -1,53 +0,0 @@ --- ========================================================== --- 06_validations_enrich.sql : Traçabilité complète des validations --- - Ajoute la colonne 'valide_par' (FK -> utilisateurs.id) --- - ON DELETE SET NULL pour conserver l'historique --- - Ajoute index utiles pour les requêtes (valideur, statut, date) --- A exécuter après : 01_init.sql, 02_indexes.sql, 03_checks.sql, 04_fk_policies.sql, 05_triggers.sql --- ========================================================== - -BEGIN; - --- 1) Colonne 'valide_par' si absente -ALTER TABLE validations - ADD COLUMN IF NOT EXISTS valide_par UUID NULL; - --- 2) FK vers utilisateurs(id), ON DELETE SET NULL -DO $$ -DECLARE conname text; -BEGIN - SELECT tc.constraint_name INTO conname - FROM information_schema.table_constraints tc - JOIN information_schema.key_column_usage kcu - ON tc.constraint_name = kcu.constraint_name - AND tc.table_schema = kcu.table_schema - WHERE tc.table_schema = 'public' - AND tc.table_name = 'validations' - AND tc.constraint_type= 'FOREIGN KEY' - AND kcu.column_name = 'valide_par'; - - IF conname IS NOT NULL THEN - EXECUTE format('ALTER TABLE public.validations DROP CONSTRAINT %I', conname); - END IF; - - EXECUTE $sql$ - ALTER TABLE public.validations - ADD CONSTRAINT fk_validations_valide_par - FOREIGN KEY (valide_par) REFERENCES public.utilisateurs(id) ON DELETE SET NULL - $sql$; -END $$; - --- 3) Index pour accélérer les recherches --- - qui a validé quoi récemment ? --- - toutes les validations par statut / par date -CREATE INDEX IF NOT EXISTS idx_validations_valide_par_cree_le - ON validations (valide_par, cree_le); - --- Certains existent peut-être déjà : on sécurise -CREATE INDEX IF NOT EXISTS idx_validations_id_utilisateur_cree_le - ON validations (id_utilisateur, cree_le); - -CREATE INDEX IF NOT EXISTS idx_validations_statut - ON validations (statut); - -COMMIT; diff --git a/database/migrations/07_import.sql b/database/migrations/07_import.sql deleted file mode 100644 index 957e8af..0000000 --- a/database/migrations/07_import.sql +++ /dev/null @@ -1,42 +0,0 @@ --- Script d'importation des données CSV dans la base Postgres du docker dev --- À exécuter dans le conteneur ou via psql connecté à la base --- psql -U admin -d ptitpas_db -f /docker-entrypoint-initdb.d/07_import.sql --- Exemple d'utilisation : - - --- Import utilisateurs -\copy utilisateurs FROM 'bdd/data_test/utilisateurs.csv' DELIMITER ',' CSV HEADER; - --- Import assistantes_maternelles -\copy assistantes_maternelles FROM 'bdd/data_test/assistantes_maternelles.csv' DELIMITER ',' CSV HEADER; - --- Import parents -\copy parents FROM 'bdd/data_test/parents.csv' DELIMITER ',' CSV HEADER; - --- Import enfants -\copy enfants FROM 'bdd/data_test/enfants.csv' DELIMITER ',' CSV HEADER; - --- Import enfants_parents -\copy enfants_parents FROM 'bdd/data_test/enfants_parents.csv' DELIMITER ',' CSV HEADER; - --- Import dossiers -\copy dossiers FROM 'bdd/data_test/dossiers.csv' DELIMITER ',' CSV HEADER; - --- Import contrats -\copy contrats FROM 'bdd/data_test/contrats.csv' DELIMITER ',' CSV HEADER; - --- Import validations -\copy validations FROM 'bdd/data_test/validations.csv' DELIMITER ',' CSV HEADER; - --- Import notifications -\copy notifications FROM 'bdd/data_test/notifications.csv' DELIMITER ',' CSV HEADER; - --- Import uploads -\copy uploads FROM 'bdd/data_test/uploads.csv' DELIMITER ',' CSV HEADER; - --- Import evenements -\copy evenements FROM 'bdd/data_test/evenements.csv' DELIMITER ',' CSV HEADER; - --- Remarque : --- Les chemins doivent être accessibles depuis le conteneur Docker (monter le dossier si besoin) --- Adapter l'utilisateur, la base et le chemin si nécessaire diff --git a/docker-compose.yml b/docker-compose.yml index e3dced5..1e97b62 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - - ./database/migrations/01_init.sql:/docker-entrypoint-initdb.d/01_init.sql + - ./database/BDD.sql:/docker-entrypoint-initdb.d/01_init.sql - postgres_data:/var/lib/postgresql/data networks: - ptitspas_network