Donner au CSM (Conseil Supérieur de la Magistrature) les moyens d'un travail efficace et de qualité afin de concourir à la continuité du fonctionnement de l'institution judiciaire et de contribuer à une RH vertueuse du corps de la magistrature.
Le projet est un workspace pnpm composé de :
- apps/api : le back-end Nest.js + Prisma
- apps/client : le front-end React + Vite
- apps/api-e2e : les tests bout-en-bout de l'api
- packages/lolfi : générateur d'archives LOLFI, utilisé par les tests bout-en-bout de l'api et par ceux de Playwright
flowchart LR
user([Utilisateur])
subgraph rie[RIE - Réseau Interministériel de l'État]
lolfi["LOLFI<br/>SIRH du Ministère de la Justice"]
end
sdv["Serveur relais SDV<br/>raccordé au RIE et à Internet"]
subgraph scaleway[Scaleway]
storage[(Stockage objet S3)]
end
subgraph scalingo[Scalingo]
front["Client React<br/>servi par NGINX"]
back[API Nest.js]
db[(PostgreSQL)]
oneoff[["One-off<br/>ingestion XML"]]
gotenberg[["Gotenberg"]]
end
user -- https --> front
user -- https --> back
lolfi --> sdv
sdv -- https --> back
back -- Prisma --> db
back <-- http --> gotenberg
back -- lance --> oneoff
oneoff --> db
back -- s3/https --> storage
LOLFI est le SIRH du ministère de la Justice qui suit les carrières des magistrats : ses extractions XML sont la source des données de nomination. Le RIE n'étant pas connecté à Internet, un serveur relais fourni par le prestataire SDV, raccordé aux deux réseaux, transmet ces fichiers chiffrés à l'api.
Gotenberg convertit en PDF le HTML des documents produits par l'api (ordre du jour, PV et
notice de restitution). Il n'est pas déployé depuis ce dépôt mais depuis
betagouv/fondation-gotenberg dans sa
propre application Scalingo fondation-gotenberg.
Le stockage objet, hébergé chez Scaleway et compatible S3, contient les pièces jointes ainsi que les PDF générés, qui y sont mis en cache pour éviter de les reconstruire à chaque consultation. En local, il est émulé par MinIO (voir l'installation).
Toutes les commandes ci-dessous se lancent depuis la racine du dépôt.
- Installation des dépendances
pnpm install- Copier le fichier
.env.examplevers.env
cp apps/api/.env.example apps/api/.envLe fichier apps/api/.env (gitignoré) contient toutes les variables nécessaires pour démarrer
l'application localement. DATABASE_URL pointe vers un Postgres local lancé dans Docker
(étape 3), sur le port non-standard 5435 pour ne pas entrer en conflit avec un Postgres
déjà présent sur la machine.
- Démarrer les services et la base de données
docker compose --file apps/api/test/docker-compose-test.yaml up -d # Postgres :5435 + MinIO/S3 :9000 + Gotenberg :9091
pnpm --filter api prisma migrate deployPour la base de test :
pnpm --filter api exec dotenvx run -f .env.e2e -f .env -- prisma migrate deployWarning
Il est très facile de lancer les tests sur la base locale par erreur. Toujours passer
par les scripts du package.json de l'api (comme start:e2e), qui chargent .env.e2e
avant .env pour cibler la base de test.
Note
Postgres suit la version majeure de la production (17). Chaque version majeure a son
propre volume (fondation_postgres-17) : après un changement de version, la base repart
vide et il faut réappliquer les migrations de la base locale et de la base de test. Pour
garder ses données, les sauvegarder avant de changer d'image puis les restaurer :
docker exec postgres-test pg_dumpall -U fondation > ~/fondation.sql # avant
docker exec -i postgres-test psql -U fondation -d postgres < ~/fondation.sql # après- Générer le code
pnpm --filter api prisma:generate # client Prisma + requêtes TypedSQLImportant
prisma generate --sql se connecte à la base pour typer les requêtes de prisma/sql/ :
Postgres doit donc tourner et les migrations être appliquées (étape 3). Ce code n'est pas
versionné, à relancer après chaque pnpm install.
- Initialiser les buckets S3 (une seule fois)
node apps/api/scripts/init-buckets.js- Lancement de l'application
pnpm --filter api dev # back sur :3000, Swagger sur /openapi
pnpm --filter client dev # front sur :5173- Créer un utilisateur pour se connecter
pnpm --filter api build
pnpm --filter api exec node --env-file .env dist/cli user register \
--email jean@example.fr \
--firstname Jean \
--lastname Moulin \
--gender MALE \
--role MEMBRE_DU_PARQUETLe CLI est interactif et demandera les informations manquantes si nécessaire, à commencer par le mot de passe :
password: *****
repeat password: *****
Rôles disponibles : MEMBRE_DU_SIEGE, MEMBRE_DU_PARQUET, MEMBRE_COMMUN,
ADJOINT_SECRETAIRE_GENERAL (enum PrismaRoleEnum dans
apps/api/prisma/schemas/identity.prisma).
Le droit administrateur s'ajoute au rôle ADJOINT_SECRETAIRE_GENERAL avec l'option --admin.
Il est recommandé de créer un membre commun et un agent du secrétariat général administrateur.
- Accès à l'application : http://localhost:5173
Le local et staging ne contiennent aucune donnée réelle. On y ingère une archive LOLFI fictive : 9 transparences, toujours les mêmes, avec de vraies juridictions et fonctions mais des magistrats inventés. Les cas rares que l'application traite à part (audition, fiche de juridiction, détachement, outre-mer) y sont garantis.
pnpm --filter lolfi generate:fictitious-archive # => packages/lolfi/LOLFI_CSM_fictitious.zipL'archive s'envoie avec un compte administrateur : menu "Administration", puis "Import LOLFI manuel"
(/admin/lolfi en local). Le suivi est sur /admin/jobs.
Le script empty-local-or-staging-except-accounts.mjs
vide la base et le bucket, sauf les comptes de connexion. Il refuse toute autre base que celle du
local ou de staging. Il n'efface rien sans --confirm suivi du nom de la base. Il ne se lance
jamais pendant une ingestion.
En local :
pnpm --filter api local:empty-except-accounts # affiche ce qui serait effacé
pnpm --filter api local:empty-except-accounts --confirm fondation # effacePuis on réingère l'archive.
Sur staging, le script se lance depuis un poste : il n'est pas livré sur Scalingo. Dans un premier terminal, ouvrir un tunnel vers la base (avec la clé SSH déclarée sur Scalingo) :
scalingo --region osc-secnum-fr1 --app fondation-api-staging db-tunnel -i ~/.ssh/<clé> --port 10055 SCALINGO_POSTGRESQL_URLDans un second terminal, depuis la racine du dépôt :
staging() { scalingo --region osc-secnum-fr1 --app fondation-api-staging "$@"; }
# 1. Sauvegarder la base (l'identifiant de l'addon est donné par `staging addons`)
staging --addon <addon> backups-create
# 2. Pointer le script sur staging, à travers le tunnel
export DATABASE_URL=$(staging env-get SCALINGO_POSTGRESQL_URL | sed -E 's#@[^/]+/#@127.0.0.1:10055/#; s#\?.*##')
export S3_BUCKET=$(staging env-get S3_BUCKET)
export S3_ACCESS_KEY=$(staging env-get S3_ACCESS_KEY)
export S3_SECRET_KEY=$(staging env-get S3_SECRET_KEY)
# 3. Copier les comptes hors du dépôt, pour pouvoir les remettre en cas de problème
pg_dump "$DATABASE_URL" --data-only --column-inserts -t identity_and_access_context.users > ~/comptes-staging.sql
# 4. Vérifier ce qui serait effacé, puis effacer
node apps/api/scripts/empty-local-or-staging-except-accounts.mjs
node apps/api/scripts/empty-local-or-staging-except-accounts.mjs --confirm fondation_a_3234Enfin, envoyer l'archive par la page "Import LOLFI manuel" de staging. Supprimer
~/comptes-staging.sql, qui contient les empreintes des mots de passe, puis fermer les deux terminaux.
Le client envoie ses vues de page au Matomo mutualisé de beta.gouv, site 273. Les URL sont
anonymisées avant l'envoi : les identifiants sont remplacés par le nom du paramètre de route
(/magistrats/:magistratId), voir apps/client/src/utils/matomo.ts.
Sans les variables VITE_MATOMO_*, la mesure est désactivée. Elles ne sont renseignées que dans
l'environnement production de GitHub. Rien n'est donc envoyé en local ni en recette.
Les tests unitaires :
pnpm --filter api test
pnpm --filter client testLes tests bout-en-bout de l'api (apps/api-e2e) démarrent l'api eux-mêmes : ils ont seulement besoin des services de l'étape 3 et de la base de test migrée.
pnpm --filter api-e2e testLes tests Playwright exécutent l'application dans un mode particulier pour faciliter les tests, sans trop s'éloigner du comportement de production.
Il faut déjà démarrer l'application back des tests. Playwright est capable de l'exécuter lui-même. S'il y a un bug, les logs seront cependant plus lisibles dans un processus séparé.
pnpm --filter api start:e2eDémarrer l'appli front
pnpm --filter client devEnsuite démarrer Playwright
pnpm --filter client playwright test --ui # ou `test:e2e` pour un lancement sans interfacePour générer le code du contrat d'interface entre le front et le back, on utilise OpenAPI dans un mode code-first.
La spécification est générée directement depuis nos contrôleurs Nest, grâce à
Swagger UI est exposé par défaut sur /openapi.
Le SDK front est généré avec @hey-api/openapi-ts et ces plugins :
On ne génère volontairement pas le client @tanstack/react-query puisque ce dernier n'offre pas la fonctionnalité de namespacing disponible dans le sdk directement.
La configuration de l'outil est disponible dans apps/client/openapi-ts.config.ts.
openapi-ts utilise directement la spécification exposée par Nest : le back doit donc tourner (étape 6) avant de lancer la génération depuis la racine.
pnpm run openapi:generateNote
Le code généré est directement embarqué dans le dépôt de code pour faciliter les choses.
L'api est exploitée dans des hooks custom qui retournent la Query ou la Mutation
de @tanstack/react-query dans le dossier apps/client/src/queries.
Les types générés sont importables depuis @api/types et les conventions d'écriture de ces
hooks sont décrites dans CLAUDE.md.
Le catalogue des composants du front est construit avec Storybook :
pnpm --filter client storybookIl est accessible sur localhost:6006. Une version en ligne est
déployée sur Scalingo à chaque push sur develop (lien disponible en interne).
Pour créer une story, voir le guide Storybook. Un guide d'utilisation à destination du métier est intégré au Storybook (page "Guide / Bienvenue").
Note
Le fichier CLAUDE.md contient de nombreuses informations relatives au style utilisé dans le projet. Il s'adresse aux LLMs mais sert aussi de référence aux développeurs.
Autrement, ne pas hésiter à se référer à ce document : https://github.com/zakirullin/cognitive-load/blob/main/README.md
Les projets front et back utilisent des conventions légèrement différentes. Pour les expliciter,
oxlint et oxfmt
ont chacun une configuration définie dans chaque projet (.oxlintrc.json, .oxfmtrc.json).
Pour chaque pull request, on vérifie que le code proposé respecte ces conventions. Pour éviter des cycles de CI inutiles, on peut lancer les mêmes vérifications en local depuis la racine :
pnpm run prepushLe hook husky prepush peut automatiser ce lancement avant
chaque push (installation avec npx husky).
Globalement, configurer son IDE pour utiliser ces configurations est le mieux. Le workspace PNPM peut cependant parfois provoquer des conflits.
Une fois la pull request fusionnée dans develop, on déploie staging avec bin/deploy-staging.sh.
PNPM est configuré par défaut avec les options suivantes dans le fichier pnpm-workspace.yaml :
preferFrozenLockfile: true # n'installe que les dépendances du lockfile
minimumReleaseAge: 10080 # attend au moins 7 jours avant de proposer une dépendance
allowBuilds: # liste blanche des paquets autorisés à exécuter un script d'install
'@nestjs/core': true
'@prisma/client': false
'@swc/core': true # compilation native nécessaire
'@prisma/engines': false
'@scarf/scarf': false
'@sentry/cli': false
bcrypt: true # compilation native nécessaire
prisma: false # généré manuellement (voir installation) car requiert une base lancéeTout paquet absent de allowBuilds est empêché d'exécuter ses scripts d'installation
(postinstall, etc.), pour se protéger des attaques par supply chain
(cf. SHAI-HULUD 2). On n'autorise
que le strict nécessaire : bcrypt, @nestjs/core et @swc/core pour leur compilation native.
Warning
Ces mesures n'empêchent pas la plus grande vigilance avant d'installer une dépendance. Chaque installation de dépendance doit être justifiée.