Skip to content
betagouvPublic

About

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.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Repository files navigation

Fondation

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.

Organisation du monorepo

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

Architecture

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
Loading

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).

Installation et lancement

Toutes les commandes ci-dessous se lancent depuis la racine du dépôt.

  1. Installation des dépendances
pnpm install
  1. Copier le fichier .env.example vers .env
cp apps/api/.env.example apps/api/.env

Le 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.

  1. 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 deploy

Pour la base de test :

pnpm --filter api exec dotenvx run -f .env.e2e -f .env -- prisma migrate deploy

Warning

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
  1. Générer le code
pnpm --filter api prisma:generate   # client Prisma + requêtes TypedSQL

Important

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.

  1. Initialiser les buckets S3 (une seule fois)
node apps/api/scripts/init-buckets.js
  1. Lancement de l'application
pnpm --filter api dev       # back sur :3000, Swagger sur /openapi
pnpm --filter client dev    # front sur :5173
  1. 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_PARQUET

Le 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.

  1. Accès à l'application : http://localhost:5173

Données fictives

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.zip

L'archive s'envoie avec un compte administrateur : menu "Administration", puis "Import LOLFI manuel" (/admin/lolfi en local). Le suivi est sur /admin/jobs.

Remettre à zéro

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  # efface

Puis 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_URL

Dans 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_3234

Enfin, 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.

Mesure d'audience

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.

Tests

Les tests unitaires :

pnpm --filter api test
pnpm --filter client test

Les 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 test

Playwright

Les 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:e2e

Démarrer l'appli front

pnpm --filter client dev

Ensuite démarrer Playwright

pnpm --filter client playwright test --ui   # ou `test:e2e` pour un lancement sans interface

Génération du SDK front

Pour 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.

Générer le client

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:generate

Note

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.

Storybook

Le catalogue des composants du front est construit avec Storybook :

pnpm --filter client storybook

Il 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").

Contribution

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 prepush

Le 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.

Sécurité des dépendances

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ée

Tout 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.

About

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.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages