Aller au contenu principal

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 :

  1. Crée une shadow database temporaire pour détecter les opérations destructives
  2. Génère un fichier SQL dans apps/api/prisma/migrations/<timestamp>_<description>/
  3. L'applique sur ta base locale
  4. 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 — idem
  • ALTER 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 :

  1. Créer une nouvelle migration qui annule les changements
  2. 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 avec pg_restore
  • Adapter -h, -U, -d aux 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

EnvironnementFréquenceRétention
Dev localManuel avant changement risqué
StagingQuotidien automatisé7 jours
ProductionToutes les 6 h + avant chaque déploiement30 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 userscoursesenrollments. 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 :

UsernameEmailRôleNotes
adminadmin@demo.mombizADMINBack-office complet
mme.diopfatou@demo.mombizTEACHER2 cours publiés
mme.traoreaicha@demo.mombizTEACHER1 cours publié
mme.sowaminata@demo.mombizLEARNERInscrite, ~50 % de progression sur 1 cours
mme.fallbintou@demo.mombizLEARNERInscrite à un cours
mme.keitasara@demo.mombizLEARNERNon 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