ajout de la syncho pour Enum.md avec init.sql

This commit is contained in:
951095 2025-09-19 11:36:22 +02:00
parent 3c5d53e48d
commit 55d4b4ff85
7 changed files with 356 additions and 25 deletions

View File

@ -58,10 +58,24 @@ Ouvre ton navigateur sur:
http://localhost:8081 http://localhost:8081
``` ```
**Email** : `admin@ptits-pas.fr` Par défaut un administrateur est créé par la migration d'initialisation (`migrations/01_init.sql`).
**Mot de passe** : `admin123` Pour des raisons de sécurité, le mot de passe n'est pas gardé en clair dans le README.
**Mot de passse pour se connecter au server local** : `admin123` Si tu veux un mot de passe connu en dev, génère le hash bcrypt puis modifie la migration
ou crée un seed SQL :
```sql
-- Exemple pour définir le mot de passe en dev :
UPDATE utilisateurs
SET password = crypt('admin123', gen_salt('bf'))
WHERE email = 'admin@ptits-pas.fr';
```
Exécute la commande suivante pour appliquer ce seed sur ta base dev :
```bash
docker exec -i ptitspas-postgres-standalone psql -U admin -d ptitpas_db -c "UPDATE utilisateurs SET password = crypt('admin123', gen_salt('bf')) WHERE email = 'admin@ptits-pas.fr';"
```
## Conseils et bonnes pratiques ## Conseils et bonnes pratiques
@ -71,6 +85,30 @@ http://localhost:8081
- Pour ajouter des scripts d'automatisation, crée un dossier `scripts/` - Pour ajouter des scripts d'automatisation, crée un dossier `scripts/`
- Documente les étapes spécifiques dans le README ou dans `docs/` - Documente les étapes spécifiques dans le README ou dans `docs/`
### Synchroniser les types ENUM avec les CSV d'import
Un utilitaire est fourni pour générer une migration sûre qui ajoute les valeurs
manquantes aux types ENUM en base en se basant sur les CSV présents dans
`bdd/data_test/`.
Générer la migration :
```bash
make sync-enums
```
Le fichier généré est `migrations/00_sync_enums.sql`. Relis ce fichier avant de
l'appliquer (il contient des blocs `DO $$ ... ALTER TYPE ... ADD VALUE` qui
ne suppriment rien mais modifient le type ENUM). Pour appliquer la migration
sur ta base locale :
```bash
# Après avoir démarré la base (make reset ou make demo)
docker exec -i ptitspas-postgres-standalone psql -U admin -d ptitpas_db -f migrations/00_sync_enums.sql
```
Attention : ne pas appliquer directement en production sans vérification.
--- ---
## Contact ## Contact

View File

@ -14,7 +14,8 @@ services:
ports: ports:
- "5433:5432" - "5433:5432"
volumes: volumes:
# Scripts de migration (ordre important → init, indexes, checks, triggers, import…) # Scripts de migration (ordre important → sync_enums, init, indexes, checks, triggers, import…)
- ./migrations/00_sync_enums.sql:/docker-entrypoint-initdb.d/00_sync_enums.sql
- ./migrations/01_init.sql:/docker-entrypoint-initdb.d/01_init.sql - ./migrations/01_init.sql:/docker-entrypoint-initdb.d/01_init.sql
- ./migrations/02_indexes.sql:/docker-entrypoint-initdb.d/02_indexes.sql - ./migrations/02_indexes.sql:/docker-entrypoint-initdb.d/02_indexes.sql
- ./migrations/03_checks.sql:/docker-entrypoint-initdb.d/03_checks.sql - ./migrations/03_checks.sql:/docker-entrypoint-initdb.d/03_checks.sql

View File

@ -69,4 +69,10 @@ psql:
stop: stop:
docker compose -f docker-compose.dev.yml down docker compose -f docker-compose.dev.yml down
j stop:
docker compose -f docker-compose.dev.yml down
sync-enums:
@echo "🔁 Génération de migrations/00_sync_enums.sql depuis les CSV"
@python3 scripts/sync_enums.py > migrations/00_sync_enums.sql
@echo "✅ migrations/00_sync_enums.sql générée. Relis le fichier avant de l'appliquer."

View File

@ -0,0 +1,183 @@
-- Generated by scripts/sync_enums.py — review before applying
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_contrat_type'
AND e.enumlabel = 'brouillon'
) THEN
ALTER TYPE statut_contrat_type ADD VALUE 'brouillon';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_dossier_type'
AND e.enumlabel = 'envoye'
) THEN
ALTER TYPE statut_dossier_type ADD VALUE 'envoye';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_enfant_type'
AND e.enumlabel = 'actif'
) THEN
ALTER TYPE statut_enfant_type ADD VALUE 'actif';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_enfant_type'
AND e.enumlabel = 'scolarise'
) THEN
ALTER TYPE statut_enfant_type ADD VALUE 'scolarise';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'genre_type'
AND e.enumlabel = 'F'
) THEN
ALTER TYPE genre_type ADD VALUE 'F';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'genre_type'
AND e.enumlabel = 'H'
) THEN
ALTER TYPE genre_type ADD VALUE 'H';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'type_evenement_type'
AND e.enumlabel = 'absence_enfant'
) THEN
ALTER TYPE type_evenement_type ADD VALUE 'absence_enfant';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_evenement_type'
AND e.enumlabel = 'propose'
) THEN
ALTER TYPE statut_evenement_type ADD VALUE 'propose';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'role_type'
AND e.enumlabel = 'administrateur'
) THEN
ALTER TYPE role_type ADD VALUE 'administrateur';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'role_type'
AND e.enumlabel = 'assistante_maternelle'
) THEN
ALTER TYPE role_type ADD VALUE 'assistante_maternelle';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'role_type'
AND e.enumlabel = 'gestionnaire'
) THEN
ALTER TYPE role_type ADD VALUE 'gestionnaire';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'role_type'
AND e.enumlabel = 'parent'
) THEN
ALTER TYPE role_type ADD VALUE 'parent';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_utilisateur_type'
AND e.enumlabel = 'actif'
) THEN
ALTER TYPE statut_utilisateur_type ADD VALUE 'actif';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_validation_type'
AND e.enumlabel = 'valide'
) THEN
ALTER TYPE statut_validation_type ADD VALUE 'valide';
END IF;
END$$;

View File

@ -262,7 +262,10 @@ INSERT INTO utilisateurs (
VALUES ( VALUES (
gen_random_uuid(), gen_random_uuid(),
'admin@ptits-pas.fr', 'admin@ptits-pas.fr',
'admin123', -- ⚠️ à remplacer par le hash réel du mot de passe -- Use a bcrypt hash via pgcrypto for the default admin password in dev.
-- In dev you can keep a known password by using: crypt('admin123', gen_salt('bf'))
-- For security, avoid committing plaintext passwords in migrations for production.
crypt('admin123', gen_salt('bf')),
'Admin', 'Admin',
'PtitsPas', 'PtitsPas',
'super_admin', 'super_admin',

100
scripts/sync_enums.py Normal file
View File

@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""
scripts/sync_enums.py
Génère une migration SQL non destructive pour ajouter les valeurs d'ENUM
manquantes en se basant sur les CSV de `bdd/data_test`.
Usage:
python3 scripts/sync_enums.py > migrations/00_sync_enums.sql
Relire le SQL généré avant application en production.
"""
from pathlib import Path
import csv
from collections import defaultdict
import sys
# Mapping colonne CSV -> nom du type enum en base
# NOTE: mapping is now based on filename (more precise)
CSV_DIR = Path('bdd/data_test')
# Define per-file mappings: filename stem -> {column_name: enum_type}
FILE_COLUMN_ENUM_MAP = {
'utilisateurs': {
'role': 'role_type',
'genre': 'genre_type',
'statut': 'statut_utilisateur_type',
},
'enfants': {
'statut': 'statut_enfant_type',
'genre': 'genre_type',
},
'contrats': {
'statut': 'statut_contrat_type',
},
'dossiers': {
'statut': 'statut_dossier_type',
},
'evenements': {
'type': 'type_evenement_type',
'statut': 'statut_evenement_type',
},
'validations': {
'statut': 'statut_validation_type',
}
}
def quote_sql_literal(s: str) -> str:
return "'" + s.replace("'", "''") + "'"
def discover_values():
# heuristique : map common column names to enums
enum_values = defaultdict(set)
for p in sorted(CSV_DIR.glob('*.csv')):
try:
with p.open(newline='', encoding='utf-8') as fh:
reader = csv.DictReader(fh)
fname = p.stem
per_file_map = FILE_COLUMN_ENUM_MAP.get(fname, {})
for row in reader:
for col, enum in per_file_map.items():
if col in row and row[col] is not None:
v = row[col].strip()
if v != '':
enum_values[enum].add(v)
except Exception as e:
print(f"-- Error reading {p}: {e}", file=sys.stderr)
return enum_values
def emit_sql(enum_values):
lines = []
lines.append('-- Generated by scripts/sync_enums.py — review before applying')
for enum_type, vals in enum_values.items():
if not vals:
continue
for v in sorted(vals):
lit = quote_sql_literal(v)
block = f"""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = {quote_sql_literal(enum_type)}
AND e.enumlabel = {lit}
) THEN
ALTER TYPE {enum_type} ADD VALUE {lit};
END IF;
END$$;
"""
lines.append(block)
return '\n'.join(lines)
def main():
enum_values = discover_values()
sql = emit_sql(enum_values)
print(sql)
if __name__ == '__main__':
main()

View File

@ -6,7 +6,7 @@
executed_at executed_at
------------------------------- -------------------------------
2025-09-19 08:16:02.305781+00 2025-09-19 09:15:01.473058+00
(1 row) (1 row)
=== 1) Comptes & répartition par rôle ========================== === 1) Comptes & répartition par rôle ==========================
@ -126,49 +126,49 @@
=== 13) Performance : EXPLAIN sur requêtes clés =============== === 13) Performance : EXPLAIN sur requêtes clés ===============
QUERY PLAN QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=11.31..11.31 rows=3 width=89) (actual time=0.027..0.029 rows=0 loops=1) Limit (cost=11.31..11.31 rows=3 width=89) (actual time=0.035..0.036 rows=0 loops=1)
-> Sort (cost=11.31..11.31 rows=3 width=89) (actual time=0.026..0.027 rows=0 loops=1) -> Sort (cost=11.31..11.31 rows=3 width=89) (actual time=0.032..0.033 rows=0 loops=1)
Sort Key: cree_le DESC Sort Key: cree_le DESC
Sort Method: quicksort Memory: 25kB Sort Method: quicksort Memory: 25kB
-> Bitmap Heap Scan on messages m (cost=4.17..11.28 rows=3 width=89) (actual time=0.021..0.021 rows=0 loops=1) -> Bitmap Heap Scan on messages m (cost=4.17..11.28 rows=3 width=89) (actual time=0.023..0.024 rows=0 loops=1)
Recheck Cond: (id_dossier = 'dddddddd-dddd-dddd-dddd-dddddddddddd'::uuid) Recheck Cond: (id_dossier = 'dddddddd-dddd-dddd-dddd-dddddddddddd'::uuid)
-> Bitmap Index Scan on idx_messages_id_dossier_cree_le (cost=0.00..4.17 rows=3 width=0) (actual time=0.011..0.011 rows=0 loops=1) -> Bitmap Index Scan on idx_messages_id_dossier_cree_le (cost=0.00..4.17 rows=3 width=0) (actual time=0.012..0.013 rows=0 loops=1)
Index Cond: (id_dossier = 'dddddddd-dddd-dddd-dddd-dddddddddddd'::uuid) Index Cond: (id_dossier = 'dddddddd-dddd-dddd-dddd-dddddddddddd'::uuid)
Planning Time: 0.837 ms Planning Time: 1.468 ms
Execution Time: 0.079 ms Execution Time: 0.521 ms
(10 rows) (10 rows)
QUERY PLAN QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------------------------------------------------------------
Index Scan using idx_evenements_id_enfant_date_debut on evenements ev (cost=0.15..8.17 rows=1 width=161) (actual time=0.006..0.007 rows=0 loops=1) Index Scan using idx_evenements_id_enfant_date_debut on evenements ev (cost=0.15..8.17 rows=1 width=161) (actual time=0.006..0.007 rows=0 loops=1)
Index Cond: ((id_enfant = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid) AND (date_debut >= '2025-01-01 00:00:00+00'::timestamp with time zone)) Index Cond: ((id_enfant = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid) AND (date_debut >= '2025-01-01 00:00:00+00'::timestamp with time zone))
Planning Time: 0.107 ms Planning Time: 0.117 ms
Execution Time: 0.099 ms Execution Time: 0.025 ms
(4 rows) (4 rows)
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=9.52..9.53 rows=2 width=73) (actual time=0.020..0.021 rows=0 loops=1) Limit (cost=9.52..9.53 rows=2 width=73) (actual time=0.012..0.013 rows=0 loops=1)
-> Sort (cost=9.52..9.53 rows=2 width=73) (actual time=0.019..0.020 rows=0 loops=1) -> Sort (cost=9.52..9.53 rows=2 width=73) (actual time=0.011..0.012 rows=0 loops=1)
Sort Key: cree_le DESC Sort Key: cree_le DESC
Sort Method: quicksort Memory: 25kB Sort Method: quicksort Memory: 25kB
-> Bitmap Heap Scan on notifications n (cost=4.17..9.51 rows=2 width=73) (actual time=0.014..0.014 rows=0 loops=1) -> Bitmap Heap Scan on notifications n (cost=4.17..9.51 rows=2 width=73) (actual time=0.007..0.008 rows=0 loops=1)
Recheck Cond: ((id_utilisateur = '33333333-3333-3333-3333-333333333333'::uuid) AND (NOT lu)) Recheck Cond: ((id_utilisateur = '33333333-3333-3333-3333-333333333333'::uuid) AND (NOT lu))
-> Bitmap Index Scan on idx_notifications_user_lu_cree_le (cost=0.00..4.17 rows=2 width=0) (actual time=0.008..0.008 rows=0 loops=1) -> Bitmap Index Scan on idx_notifications_user_lu_cree_le (cost=0.00..4.17 rows=2 width=0) (actual time=0.004..0.005 rows=0 loops=1)
Index Cond: ((id_utilisateur = '33333333-3333-3333-3333-333333333333'::uuid) AND (lu = false)) Index Cond: ((id_utilisateur = '33333333-3333-3333-3333-333333333333'::uuid) AND (lu = false))
Planning Time: 0.122 ms Planning Time: 0.087 ms
Execution Time: 0.043 ms Execution Time: 0.027 ms
(10 rows) (10 rows)
QUERY PLAN QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------------------------------------------------------------------------
Sort (cost=8.18..8.18 rows=1 width=267) (actual time=0.429..0.431 rows=0 loops=1) Sort (cost=8.18..8.18 rows=1 width=267) (actual time=0.017..0.018 rows=0 loops=1)
Sort Key: cree_le DESC Sort Key: cree_le DESC
Sort Method: quicksort Memory: 25kB Sort Method: quicksort Memory: 25kB
-> Index Scan using idx_dossiers_id_parent_enfant_statut_cree_le on dossiers d (cost=0.15..8.17 rows=1 width=267) (actual time=0.419..0.419 rows=0 loops=1) -> Index Scan using idx_dossiers_id_parent_enfant_statut_cree_le on dossiers d (cost=0.15..8.17 rows=1 width=267) (actual time=0.012..0.012 rows=0 loops=1)
Index Cond: ((id_parent = '33333333-3333-3333-3333-333333333333'::uuid) AND (id_enfant = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid)) Index Cond: ((id_parent = '33333333-3333-3333-3333-333333333333'::uuid) AND (id_enfant = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid))
Planning Time: 1.115 ms Planning Time: 0.062 ms
Execution Time: 0.476 ms Execution Time: 0.032 ms
(7 rows) (7 rows)
=== 14) JSONB : exemples de filtrage =========================== === 14) JSONB : exemples de filtrage ===========================