Base de données
L'application utilise PostgreSQL 16 comme stockage principal, accédé via Prisma 5 (ORM côté Node.js).
Schéma
Le schéma de référence vit dans apps/api/prisma/schema.prisma. Il est généré et modifié via Prisma — aucun SQL manuel n'est introduit en dehors des migrations Prisma.
Entités principales : User, Course, Module, Chapter, Section, ContentBlock, Resource, Quiz, QuizQuestion, Enrollment, SectionProgress, QuizAttempt, Certificate, Payment, RefreshToken. Voir scope produit pour la sémantique métier.
Migrations
En dev
# Créer une migration depuis un changement de schema.prisma
npm --workspace apps/api exec prisma migrate dev --name <description-courte>
Cette commande :
- Crée une shadow database temporaire pour détecter les opérations destructives
- Génère un fichier SQL dans
apps/api/prisma/migrations/<timestamp>_<description>/ - L'applique sur ta base locale
- Régénère le client TypeScript
Le privilège CREATEDB est requis sur l'utilisateur Postgres (pour la shadow DB).
En prod ou CI
# Applique les migrations existantes sans en créer
npm --workspace apps/api exec prisma migrate deploy
# Régénère le client TypeScript après chaque migration
npm --workspace apps/api exec prisma generate
migrate deploy ne crée jamais de shadow DB et ne modifie jamais le schéma — il applique strictement les migrations versionnées dans prisma/migrations/.
Revue obligatoire
Toute migration doit être relue avant merge. Cherche en particulier :
DROP COLUMN— perte de données potentielle, prévoir une stratégie (renommage temporaire ?)DROP TABLE— idemALTER TYPE ... ADD VALUE(enum) — sur Postgres, certaines opérations sortent de la transaction par défaut- Renommage de colonne — Prisma génère un drop + create, pas un rename : à transformer manuellement si besoin de préserver les données
- Changement de type non compatible — vérifier la conversion implicite
Rollback
Prisma ne fait pas de rollback automatique : le concept est de toujours avancer. Si une migration prod casse :
- Créer une nouvelle migration qui annule les changements
- Restaurer depuis un backup pré-migration (cf. ci-dessous)
Sauvegarde
Backup logique (pg_dump)
# Dump complet d'une base
pg_dump -Fc -h host -U user -d mombiz_dev > backup-$(date +%Y%m%d-%H%M%S).dump
-Fc: format custom binaire, compressé, restaurable avecpg_restore- Adapter
-h,-U,-daux credentials prod
Backup base entière
Pour les snapshots VPS / disques :
- Volume Docker :
docker run --rm -v <volume>:/data -v $(pwd):/backup alpine tar czf /backup/postgres-data-$(date +%Y%m%d).tar.gz /data - VPS managé : utiliser le snapshot du provider (Hetzner Storage Box, OVH Backup)
Fréquence recommandée
| Environnement | Fréquence | Rétention |
|---|---|---|
| Dev local | Manuel avant changement risqué | — |
| Staging | Quotidien automatisé | 7 jours |
| Production | Toutes les 6 h + avant chaque déploiement | 30 jours minimum |
Tester la restauration au moins une fois par mois sur un environnement isolé. Un backup non testé est un fichier inerte.
Restauration
Depuis un dump pg_restore
# Avant de restaurer : sauvegarder l'état courant (au cas où)
pg_dump -Fc ... > pre-restore-$(date +%s).dump
# Restaurer
pg_restore -h host -U user -d mombiz_dev --clean --if-exists backup.dump
--clean: drop les tables avant de restaurer--if-exists: ne pas erreur si la table n'existe pas
Depuis un volume Docker
docker stop dev-postgres
docker run --rm -v dev-postgres-data:/data -v $(pwd):/backup alpine \
sh -c "rm -rf /data/* && tar xzf /backup/postgres-data-YYYYMMDD.tar.gz -C /"
docker start dev-postgres
Provisionnement (Postgres mutualisé)
Le Postgres de dev est mutualisé entre projets (un seul conteneur partagé). Le rôle mombiz et la base mombiz_dev propres à MomBiz sont versionnés dans devstack/init/*.sql (idempotents) et appliqués au conteneur partagé via :
npm run db:provision
Cible devstack-postgres par défaut (surchargeable : DEVSTACK_PG_CONTAINER=<nom> npm run db:provision). Détails et cas du conteneur neuf : devstack/README.md. Voir ADR 0055.
Seed (jeu de données démo)
Le seed est modulaire (apps/api/scripts/seed/) : un orchestrateur index.ts enchaîne users → courses → enrollments. Ajouter un domaine = un fichier *.seed.ts + une ligne dans index.ts. Lancement depuis la racine :
npm run db:seed
Comptes injectés. Connexion par email OU username (format dev civilite.nom), mot de passe commun test@1234 :
| Username | Rôle | Notes | |
|---|---|---|---|
admin | admin@demo.mombiz | ADMIN | Back-office complet |
mme.diop | fatou@demo.mombiz | TEACHER | 2 cours publiés |
mme.traore | aicha@demo.mombiz | TEACHER | 1 cours publié |
mme.sow | aminata@demo.mombiz | LEARNER | Inscrite, ~50 % de progression sur 1 cours |
mme.fall | bintou@demo.mombiz | LEARNER | Inscrite à un cours |
mme.keita | sara@demo.mombiz | LEARNER | Non inscrite (tester catalogue) |
Le seed wipe les tables métier (TRUNCATE … CASCADE) avant réinsertion ; les users sont upsertés (idempotent, conserve l'admin racine).
Reset complet
npm run db:reset # drop tout le schéma + migrations + seed
Garde-fou : db:reset refuse une DATABASE_URL non-locale sauf ALLOW_RESET=1 explicite (à n'utiliser qu'après un pg_dump). Ne jamais reset staging/prod — y migrer via npm run db:deploy. Le seed comme le reset effacent les données : à ne jamais lancer en production.
Soft delete
Toutes les entités métier disposent d'un champ deletedAt: DateTime?. Les "suppressions" mettent à jour ce timestamp ; aucun DELETE SQL n'est émis.
Conséquences pratiques :
- Les requêtes filtrent par défaut
WHERE deletedAt IS NULL - L'admin peut afficher les entités supprimées via le filtre
includeDeleted=true - Pour purger réellement (RGPD, demande d'effacement) : nécessite une intervention manuelle SQL ou un script dédié — jamais via Prisma standard
Voir aussi
- Sécurité — politique de mot de passe, JWT, RBAC
- Conventions de code — Base de données