ajout de la syncho pour Enum.md avec init.sql
This commit is contained in:
parent
3c5d53e48d
commit
55d4b4ff85
44
README.md
44
README.md
@ -58,10 +58,24 @@ Ouvre ton navigateur sur :
|
||||
http://localhost:8081
|
||||
```
|
||||
|
||||
**Email** : `admin@ptits-pas.fr`
|
||||
**Mot de passe** : `admin123`
|
||||
Par défaut un administrateur est créé par la migration d'initialisation (`migrations/01_init.sql`).
|
||||
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
|
||||
@ -71,6 +85,30 @@ http://localhost:8081
|
||||
- Pour ajouter des scripts d'automatisation, crée un dossier `scripts/`
|
||||
- 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
|
||||
|
||||
@ -14,7 +14,8 @@ services:
|
||||
ports:
|
||||
- "5433:5432"
|
||||
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/02_indexes.sql:/docker-entrypoint-initdb.d/02_indexes.sql
|
||||
- ./migrations/03_checks.sql:/docker-entrypoint-initdb.d/03_checks.sql
|
||||
|
||||
8
makefile
8
makefile
@ -69,4 +69,10 @@ psql:
|
||||
|
||||
stop:
|
||||
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."
|
||||
183
migrations/00_sync_enums.sql
Normal file
183
migrations/00_sync_enums.sql
Normal 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$$;
|
||||
|
||||
@ -262,7 +262,10 @@ INSERT INTO utilisateurs (
|
||||
VALUES (
|
||||
gen_random_uuid(),
|
||||
'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',
|
||||
'PtitsPas',
|
||||
'super_admin',
|
||||
|
||||
100
scripts/sync_enums.py
Normal file
100
scripts/sync_enums.py
Normal 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()
|
||||
38
verify.log
38
verify.log
@ -6,7 +6,7 @@
|
||||
|
||||
executed_at
|
||||
-------------------------------
|
||||
2025-09-19 08:16:02.305781+00
|
||||
2025-09-19 09:15:01.473058+00
|
||||
(1 row)
|
||||
|
||||
=== 1) Comptes & répartition par rôle ==========================
|
||||
@ -126,49 +126,49 @@
|
||||
=== 13) Performance : EXPLAIN sur requêtes clés ===============
|
||||
QUERY PLAN
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Limit (cost=11.31..11.31 rows=3 width=89) (actual time=0.027..0.029 rows=0 loops=1)
|
||||
-> Sort (cost=11.31..11.31 rows=3 width=89) (actual time=0.026..0.027 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.032..0.033 rows=0 loops=1)
|
||||
Sort Key: cree_le DESC
|
||||
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)
|
||||
-> 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)
|
||||
Planning Time: 0.837 ms
|
||||
Execution Time: 0.079 ms
|
||||
Planning Time: 1.468 ms
|
||||
Execution Time: 0.521 ms
|
||||
(10 rows)
|
||||
|
||||
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 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
|
||||
Execution Time: 0.099 ms
|
||||
Planning Time: 0.117 ms
|
||||
Execution Time: 0.025 ms
|
||||
(4 rows)
|
||||
|
||||
QUERY PLAN
|
||||
------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Limit (cost=9.52..9.53 rows=2 width=73) (actual time=0.020..0.021 rows=0 loops=1)
|
||||
-> Sort (cost=9.52..9.53 rows=2 width=73) (actual time=0.019..0.020 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.011..0.012 rows=0 loops=1)
|
||||
Sort Key: cree_le DESC
|
||||
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))
|
||||
-> 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))
|
||||
Planning Time: 0.122 ms
|
||||
Execution Time: 0.043 ms
|
||||
Planning Time: 0.087 ms
|
||||
Execution Time: 0.027 ms
|
||||
(10 rows)
|
||||
|
||||
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 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))
|
||||
Planning Time: 1.115 ms
|
||||
Execution Time: 0.476 ms
|
||||
Planning Time: 0.062 ms
|
||||
Execution Time: 0.032 ms
|
||||
(7 rows)
|
||||
|
||||
=== 14) JSONB : exemples de filtrage ===========================
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user