- Contribuer à Trackdéchets
- Mise en route
- Tests unitaires
- Tests d'intégration
- Tests end-to-end (e2e)
- Créer une PR
- Déploiement
- Migrations
- Réindexation Elasticsearch des BSDs
- Guides
- Mettre à jour le changelog
- Mettre à jour la documentation
- Utiliser un backup de base de donnée
- Créer un tampon de signature pour la génération PDF
- Nourrir la base de donnée avec des données par défaut
- Ajouter une nouvelle icône
- Clefs de signature token OpenID
- Reindexer un bordereau individuel
- Réindexer un type de bordereau
- Dépannage
- Installer Node.js
- Installer Docker et Docker Compose
-
Cloner le dépôt sur votre machine.
git clone [email protected]:MTES-MCT/trackdechets.git cd trackdechets git checkout --track origin/dev
-
Configurer les variables d'environnements :
- Renommer le ficher
.env.model
en.env
et le compléter en demandant les infos à un développeur de l'équipe - Créer un fichier
.env
dansfront/
en s'inspirant du fichier.env.recette
- Renommer le ficher
-
Mapper les différentes URLs sur localhost dans votre fichier
host
127.0.0.1 api.trackdechets.local 127.0.0.1 trackdechets.local 127.0.0.1 developers.trackdechets.local 127.0.0.1 es.trackdechets.local 127.0.0.1 notifier.trackdechets.local 127.0.0.1 storybook.trackdechets.local
Pour rappel, le fichier host est dans
C:\Windows\System32\drivers\etc
sous windows,/etc/hosts
ou/private/etc/hosts
sous Linux et MacLa valeur des URLs doit correspondre aux variables d'environnement
API_HOST
,NOTIFIER_HOST
,UI_HOST
,DEVELOPERS_HOST
,STORYBOOK_HOST
etELASTIC_SEARCH_HOST
-
Démarrer les containers de bases de données
docker compose docker-compose.yml up -d
NB: Pour éviter les envois de mails intempestifs, veillez à configurer la variable
EMAIL_BACKEND
surconsole
. -
Installez les dépendances de l'application localement
npm install
-
Synchroniser la base de données avec le schéma prisma.
Les modèles de données sont définis dans les fichiers
libs/back/prisma/src/schema.prisma
. Afin de synchroniser les tables PostgreSQL, il faut lancer une déploiement prismanpx prisma db push
-
Initialiser l'index Elastic Search.
Les données sont indexées dans une base de donnée Elastic Search pour la recherche. Il est nécessaire de créer l'index et l'alias afin de commencer à indexer des documents. À noter que ce script peut aussi être utiliser pour indexer tous les documents en base de donnée.
npx nx run back:reindex-all-bsds-bulk -- -f
-
Lancer les services.
Il est conseiller de lancer les services dans différents terminaux pour plus de lisibilité:
> npx nx run api:serve # API > npx nx run front:serve # Frontend > npx nx run-many --parallel=4 -t serve --projects=tag:backend:background # Services annexes: notifier & queues
-
Accéder aux différents services.
C'est prêt ! Rendez-vous sur l'URL
UI_HOST
configurée dans votre fichier.env
(par ex:http://trackdechets.local
) pour commencer à utiliser l'application ou surAPI_HOST
(par exhttp://api.trackdechets.local
) pour accéder au playground GraphQL.
Lors du développement, vous aurez sûrement besoin de pull des mises à jour depuis le repo. Après avoir pull le repo localement, vous pouvez utiliser la commande :
npm run afterpull
Qui automatise les tâches redondantes (mettre à jour les packages, appliquer les nouvelles migrations, générer les types back et front).
L'utilisation de Docker sur MacOS avec puce Apple est problématique car il n'existe pas d'image officielle pour Elasticsearch@6. Par ailleurs des problèmes de networking existe sur l'image Docker utilisée pour le back.
- Installer
postgres
,redis
,elasticsearch@6
,nginx
etmongodb
brew install postgresql
brew install redis
brew install nginx
brew tap mongodb/brew
brew update
brew install [email protected]
brew install elasticsearch@6
-
Installer PostgreSQL 14 avec Postgres.app. Par défaut un utilisateur est crée avec votre nom d'user MacOS et un mot de passe vide.
-
Se connecter à la base PostgresSQL avec la commande
psql
puis créer la DB :create database prisma
. -
Lancer les différents services :
brew services start redis
brew services start nginx
brew services start mongodb-community
brew services start elasticsearch@6
puis vérifier qu'ils tournent avec brew services list
. Vous pouvez vérifier également que Nginx est bien démarré en allant sur http://localhost:8080
.
En cas d'erreur à l'exécution d'elasticsearch (jdk.app corrupted ou autre), il est possible de faire l'installation en téléchargeant directement les binaires depuis Le site d'elasticsearch. Vous pouvez ensuite ajouter le dossier bin/ à votre PATH ou démarrer elasticsearch en vous rendant dans le dossier directement.
- Configurer Nginx pour servir l'API, l'UI et le notifier en créant les fichiers suivants :
# fichier /opt/homebrew/etc/nginx/servers/api.trackdechets.local
server {
listen 80;
listen [::]:80;
server_name api.trackdechets.local;
location / {
proxy_pass http://localhost:4000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# /opt/homebrew/etc/nginx/servers/notifier.trackdechets.local
server {
listen 80;
listen [::]:80;
server_name notifier.trackdechets.local;
location / {
proxy_pass http://localhost:4001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 4h;
}
}
# /opt/homebrew/etc/nginx/servers/trackdechets.local
server {
listen 80;
listen [::]:80;
server_name trackdechets.local;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
# /opt/homebrew/etc/nginx/servers/storybook.trackdechets.local
server {
listen 80;
listen [::]:80;
server_name storybook.trackdechets.local;
location / {
proxy_pass http://localhost:6006;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
Si vous voulez utiliser le domaine es.trackdechets.local pour elasticsearch, ajouter:
# /opt/homebrew/etc/nginx/servers/es.trackdechets.local
server {
listen 80;
listen [::]:80;
server_name es.trackdechets.local;
location / {
proxy_pass http://localhost:9200;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Re-charger la config et redémarrer NGINX
brew services reload nginx
brew services restart nginx
- Mapper les différentes URLs sur localhost dans votre fichier
host
# /etc/hosts
127.0.0.1 api.trackdechets.local
127.0.0.1 trackdechets.local
127.0.0.1 notifier.trackdechets.local
127.0.0.1 storybook.trackdechets.local
Si vous avez mappé le domaine es.trackdechets.local dans la config nginx:
127.0.0.1 es.trackdechets.local
- Installer
nvm
brew install nvm
nvm install 20 // version pour le back
echo v20 > .nvmrc
nvm use && npm install && npm nx run back:codegen
-
Ajouter un fichier
.env
dans le répertoire racine en copiant le fichier .env.model et un fichier.env
dans le répertoirefront
en copiant le fichier front/.env.model. (demander à un dev) -
Pousser le schéma de la base de données dans la table
prisma
et ajouter des données de tests en ajoutant un fichierseed.dev.ts
dans le répertoireback/prisma
(demander à un dev) :
npx prisma db push
npx prisma db seed
-
Créer l'index Elasticsearch :
npx nx run back:reindex-all-bsds-bulk -- -f
. Puis vérifier qu'un index a bien été crée :curl localhost:9200/_cat/indices
(ou via elasticvue) -
Créer un utilisateur Mongo :
mongosh
> use admin
> db.createUser({user: "trackdechets" ,pwd: "password", roles: ["userAdminAnyDatabase", "dbAdminAnyDatabase", "readWriteAnyDatabase"]})
- Démarrer le
back
et lefront
:
npx nx run-many -t serve
ou pour démarrer dans des consoles différentes:
npx nx run api:serve # API
npx nx run front:serve # Frontend
npx nx run-many --parallel=4 -t serve --projects=tag:backend:background
- (Optionnel) Démarrer Storybook
npx nx run front:storybook
- URL API : http://api.trackdechets.local/
- URL UI : http://trackdechets.local
- Storybook UI : http://storybook.trackdechets.local
- Formatage/analyse du code avec prettier et eslint.
- Typage du code avec les fichiers générées par GraphQL Codegen
back/src/generated/graphql/types.ts
pour le backlibs/front/codegen-ui/src/generated/graphql/types.ts
pour le front
La commande pour faire tourner tous les tests unitaires est la suivante :
docker compose -f docker-compose.test.yml up
Il est également possible de faire tourner les tests unitaires sur l'environnement de dev
en se connectant à chacun des containers. Par exemple :
- Démarrer les différents services
docker compose up -d
- Faire tourner les tests back
npx nx run back:test # run all the tests npx nx run back:test --testFile src/path/to/my-function.test.ts # run only one test
- Faire tourner les tests front
npx nx run front:test
Ce sont tous les tests ayant l'extension .integration.ts
et nécessitant le setup d'une base de données de test. Ils nécessitent de démarrer les containers Docker (ou d'avoir un setup local), puis de lancer les queues.
npm run bg:integration # Démarrage des queues en background, nécessaires aux tests
npx nx run back:test:integration # Lancement des tests d'intégration
Il est également possible de faire tourner chaque test de façon indépendante:
npx nx run back:test:integration --testFile workflow.integration.ts
Les tests e2e utilisent Playwright (documentation officielle ici).
Commencez par:
npm i
Puis il faut installer chromium pour playwright:
npx playwright install chromium --with-deps
Vu que les tests e2e fonctionnent comme les tests d'intégration, à savoir qu'ils repartent d'une base vierge à chaque fois, vous pouvez utiliser les .env.integration
(back & front) pour les tests e2e.
- Lancer la DB, ES etc.
- Démarrer les services TD avec:
npx nx run-many -t serve --configuration=integration --projects=api,front,tag:backend:background --parallel=6
- Lancer les tests:
# Console seulement
npx nx run e2e:cli --configuration=integration
# Avec l'UI
npx nx run e2e:ui --configuration=integration
Pour tester un seul fichier:
# Console seulement
npx nx run e2e:cli --file companies.spec.ts --configuration=integration
# Avec l'UI
npx nx run e2e:ui --file companies.spec.ts --configuration=integration
Il est aussi possible de débugguer pas à pas, avec l'UI:
npx nx run e2e:debug --file companies.spec.ts --configuration=integration
Playwright vous permet de jouer votre cahier de recette dans un navigateur et d'enregistrer vos actions. Plusieurs outils sont disponibles pour par exemple faire des assertions sur les pages.
Pour lancer le recorder:
npx playwright codegen trackdechets.local --viewport-size=1920,1080
Le code généré apparaît dans une fenêtre à part. Vous pouvez le copier et le coller dans des fichiers de specs.
Pour prendre un screenshot de la page qui pose problème, modifier playwright.config.ts pour changer le mode headless:
headless: false
Puis placer dans le code, à l'endroit problématique:
const buffer = await page.screenshot();
console.log(buffer.toString('base64'));
// Ou alors, méthode toute faite dans debug.ts
await logScreenshot(page);
Puis utiliser un site comme celui-ci pour transformer le log en base64 en image.
Pour observer les requêtes, vous pouvez utiliser (doc ici):
page.on('request', request => console.log('>>', request.method(), request.url()));
page.on('response', response => console.log('<<', response.status(), response.url()));
// Ou alors, méthode toute faite dans debug.ts pour capturer uniquement les calls d'API
debugApiCalls(page);
- Créer une nouvelle branche à partir et à destination de la branche
dev
. - Implémenter vos changements et penser à mettre à jour la documentation et le changelog (en ajoutant un bloc "Next release" si il n'existe pas encore).
- Une fois la PR complète, passer la branche de "draft" à "ready to review" et demander la revue à au moins 1 autre développeur de l'équipe (idéalement 2). Si possible faire un rebase (éviter le merge autant que possible) de la branche
dev
pour être bien à jour et la CI au vert avant la revue. - Une fois que la PR est approuvée et que les changements demandées ont été apportées, l'auteur de la PR peut la merger dans
dev
.
Note : l'équipe n'a pas de conventions strictes concernant le nom des branches et les messages de commit mais compte sur le bon sens de chacun.
Le déploiement est géré par Scalingo à l'aide des fichiers de configuration Procfile
et .buildpacks
placés dans le front et l'api.
Chaque update de la branche dev
déclenche un déploiement sur l'environnement de recette. Chaque update de la branche master
déclenche un déploiement sur les environnements sandbox et prod. Le déroulement dans le détails d'une mise en production est le suivant:
- Faire le cahier de recette pour vérifier qu'il n'y a pas eu de régression sur les fonctionnalités critiques de l'application (login, signup, rattachement établissement, invitation collaborateur, création BSD)
- Balayer le tableau Favro "Recette du xx/xx/xx" pour vérifier que l'étiquette "Recette OK --> EN PROD" a bien été ajoutée sur toutes les cartes.
- Mettre à jour le Changelog.md avec un nouveau numéro de version (versionnage calendaire)
- Créer une PR
dev
->master
- Au besoin résoudre les conflits entre
master
etdev
en fusionnantmaster
dansdev
(Éviter de Squash & Merge) - Faire une relecture des différents changements apportés aux modèles de données et scripts de migration.
- Si possible faire tourner les migrations sur une copie de la base de prod en local.
- S'assurer que les nouvelles variables d'environnement (Cf
.env.model
) ont bien été ajoutée sur Scalingo dans les environnements sandbox et prod respectivement pour les applicationsfront
etapi
- Merger la PR (Éviter de Squash & Merge) et suivre l'avancement de la CI github.
- Suivre l'avancement du déploiement sur Scalingo respectivement pour le front, l'api et la doc.
Les migrations de modèle sont gérées avec Prisma migrate.
Le workflow est le suivant:
- modification du schéma de la base de donnée, dans le fichier
libs/back/prisma/src/schema.prisma
- génération de la migration correspondante en jouant
npx prisma migrate dev
. Le CLI demandera de nommer sa migration. Les migrations peuvent être retrouvées danslibs/back/prisma/src/migrations
- si on souhaite modifier le SQL généré par Prisma avant qu'il soit appliqué, il est possible de jouer
npx prisma migrate dev --create-only
. C'est notamment utile lorsque l'on souhaite utiliser des fonctionnalitées non supportées par Prisma (ex: index partiel)
Pour plus d'informations sur l'utilisation de Prisma migrate, allez consulter leur documentation.
Les scripts sont gérés par le projet libs/back/scripts
.
Pour générer un script, on utilise npx nx run @td/scripts:generate
. Le CLI demandera alors à nommer le script, et un boilerplate d'écriture de script sera généré. Les fichiers sont générés dans le dossier libs/back/scripts/src/scripts
.
Pour jouer les scripts, on utilise npx nx run @td/scripts:migrate
. Les scripts exécutés avec succès sont sauvegardés en base de données pour s'assurer qu'ils ne sont joués qu'une seule fois.
Depuis un one-off container de taille XL
- Réindexation globale sans downtime en utilisant les workers d'indexation La réindexation ne sera déclenchée que si la version du mapping ES a changé
FORCE_LOGGER_CONSOLE=true npx nx run back:reindex-all-bsds-bulk -- --useQueue
- Réindexation globale sans downtime depuis la console (le travail ne sera pas parallélisé) La réindexation ne sera déclenchée que si la version du mapping ES a changé
FORCE_LOGGER_CONSOLE=true npx nx run back:reindex-all-bsds-bulk
- Réindexation globale sans downtime en utilisant les workers d'indexation Le paramètre -f permet de forcer la réindexation même si le mapping n'a pas changé
FORCE_LOGGER_CONSOLE=true npx nx run back:reindex-all-bsds-bulk -- --useQueue -f
- Réindexation globale sans downtime depuis la console (le travail ne sera pas parallélisé) Le paramètre -f permet de forcer la réindexation même si le mapping n'a pas changé
FORCE_LOGGER_CONSOLE=true npx nx run back:reindex-all-bsds-bulk -- -f
- Réindexation de tous les bordereaux d'un certain type (en place)
FORCE_LOGGER_CONSOLE=true npx nx run back:reindex-partial-in-place BSFF
- Réindexation de tous les bordereaux d'un certain type (en supprimant tous les bordereaux de ce type avant)
FORCE_LOGGER_CONSOLE=true npx nx run back:reindex-partial-in-place -- -f BSFF
- Réindexation de tous les bordereaux depuis une certaine date (en place)
FORCE_LOGGER_CONSOLE=true npx nx run back:reindex-partial-in-place -- --since 2023-03-01
- Se rendre sur Scalingo pour ajouter 1 worker
bulkindexqueuemaster
(en charge d'ajouter les chunks) en 2XL et plusieurs workersbulkindexqueue
(en charge de process les chunks). - On peut retenir la configuration suivante pour les workers
bulkindexqueue
:- 4 workers de taille 2XL
- BULK_INDEX_BATCH_SIZE=1000
- BULK_INDEX_JOB_CONCURRENCY=1
- Se connecter à la prod avec un one-off container de taille XL
- Lancer la commande
FORCE_LOGGER_CONSOLE=true npx nx run back:reindex-all-bsds-bulk -- --useQueue -f
(si la version de l'index a été bump, on peut omettre le-f
) - Suivre l'évolution des jobs d'indexation sur le dashboard bull, l'URL est visible dans le fichier `src/queue/bull-board.ts``. Il est nécessaire de se connecter à l'UI Trackdéchets avec un compte admin pour y avoir accès.
- Relancer au besoin les "indexChunk" jobs qui ont failed (c'est possible si ES se retrouve momentanément surchargé).
- Si les workers d'indexation crashent avec une erreur mémoire, ce sera visible dans les logs Scalingo. Il est possible alors que la taille des chunks soient trop importante. Diminuer alors la valeur BULK_INDEX_BATCH_SIZE, cleaner tous les jobs de la queue avant de relancer une réindexation complète. Il peut être opportun de de diminuer la taille des chunks.
- Si ES est surchargé, il peut être opportun de diminuer le nombre de workers.
- À la fin de la réindexation, set le nombre de workers
bulkindexqueuemaster
etbulkindexqueue
à 0.
Si les données de raison sociale et d'adresses enregistrés sur les bordereaux sont erronnées suite à un dysfonctionnement de l'index SIRENE, un rattrapage peut être effectué à postériori grâce au script suivante. Tous les bordereaux qui ont été crées ou modifiés entre ces deux dates seront mis à jour.
npx nx run back:sirenify-bulk --since 2024-04-01 --before 2024-04-03
Les jobs de "sirenification" sont dépilés par le worker bulkindexqueue
qui doit donc être démarré sur Scalingo avant de lancer le script.
Une commande permet de générer et téléverser sur un bucket S3 les modèles vierges des bsds BSDD, BSDA, BSVHU et BSFF.
Les variables d'environnement S3_BSD_TEMPLATES_*
doivent être renseignées.
Lancer la commande
npx nx run back:generate-bsds-templates
Le changelog est basé sur Keep a Changelog, et le projet suit un schéma de versionning inspiré de Calendar Versioning.
Il est possible de documenter les changements à venir en ajoutant une section "Next release".
Les nouvelles fonctionnalités impactant l'API doivent être documentées dans la documentation technique ./doc
en même temps que leur développement. Si possible faire également un post sur le forum technique.
Il est possible d'importer un backup d'une base de donnée d'un environnement afin de le tester en local. La procédure qui suit aura pour effet de remplacer vos données en local par les données du backup.
Un script d'automatisation a été mis en place. Il permet de restaurer soit un backup local, soit le dernier backup de la base de donnée distante choisie.
Pour les backups distants, assurez vous d'avoir correctement configuré les variables d'environnement suivantes dans votre fichier .env
local:
SCALINGO_TOKEN
- clé d'API Scalingo
$ pwd
~/dev/trackdechets
$ cd scripts
$ sudo chmod +x restore-db.sh # Si le fichier n'est pas exécutable
$ ./restore-db.sh
# Laissez vous guider...
# La première question détermine si vous souhaitez utiliser un backup distant ou local
Un script permettant de faire un dump partiel d'une DB a été créé. Il part d'un BSD spécifique qui doit être testé, et traverse récursivement la db pour trouver tous les objets qui y sont reliés, de façon à avoir un environnement de test complet pour reproduire un problème.
Etapes préliminaires:
- créer une nouvelle DB vide et la mettre dans la variable DATABASE_URL du fichier .env
- appliquer
npx prisma migrate dev
- ouvrir un tunnel SSH vers la db à dumper en utilisant le client scalingo
scalingo login
(nécéssite d'avoir une clé SSH renseignée dans Scalingo)scalingo -a <id de la db scalingo> db-tunnel SCALINGO_POSTGRESQL_URL
- ajouter l'url de la DB tunnelée dans TUNNELED_DB dans le fichier .env. Utiliser un utilisateur read-only pour l'accès, voir avec l'équipe pour en créer un ou obtenir ses credentials.
Utilisation du script:
$ npx nx run partial-backup:run
Le script vous demandera l'id du BSD de départ (utiliser le readableId "BSD-..." pour les BSDD/Form), puis se chargera de charger tous les objets en relation. Une fois le chargement fait, vous aurez un aperçu des données sous cette forme :
What will be copied :
{
Bsdasri: 3,
Company: 297,
AnonymousCompany: 3,
User: 78,
TransporterReceipt: 65,
CompanyAssociation: 408,
MembershipRequest: 65,
VhuAgrement: 54,
BrokerReceipt: 12,
AccessToken: 77,
Grant: 12,
Application: 3,
WorkerCertification: 35,
SignatureAutomation: 40,
TraderReceipt: 11,
UserActivationHash: 3,
UserResetPasswordHash: 11,
FeatureFlag: 1
}
Si les informations semblent raisonnables, vous pouvez accepter d'écrire dans votre DB de destination en tapant "Y".
Si une erreur survient lors du processus d'écriture, il est possible que ce soit dû à:
- le schema utilisé en local ne correspond pas à celui de la db source
- le schema Prisma ne correspond pas au schema de la db source
- la DB de destination n'est pas vide
- le schema de la DB de destination n'a pas été créé (
npx prisma migrate dev
)
Si tout se passe correctement, il ne vous reste plus qu'à reconstruire l'index elastic avec les données chargées en appliquant npx nx run back:reindex-all-bsds-bulk -- -f
.
- Télécharger un backup de la base de donnée nommée
prisma
que vous souhaitez restaurer - Démarrer le conteneur postgres
docker compose -f docker-compose.dev.yml up --build postgres
- Copier le fichier de backup à l'intérieur du conteneur
# docker cp <fichier backup> <nom du container postgres>:<chemin où copier> # exemple : docker cp backup trackdechets_postgres_1:/var/backups
- Accéder au conteneur postgres
docker exec -it $(docker ps -aqf "name=trackdechets_postgres") bash
- Restaurer le backup depus le conteneur postgres
dropdb -U trackdechets prisma createdb -U trackdechets prisma psql -U trackdechets prisma psql (13.3) Type "help" for help. prisma=# create schema default$default # quit psql CTRL-D pg_restore -U trackdechets -d prisma --clean /var/backups/backup
- Quitter le shell du conteneur postgres
- Appliquer les migrations présentent dans votre branche actuelle du code source
Il est possible de créer de nouveaux tampons à partir du fichier stamp.drawio.png. C'est un fichier PNG valide que l'on peut éditer directement dans Visual Code avec l'extension Draw.io VS Code Integration
Il peut être assez fastidieux de devoir recréer des comptes de tests régulièrement en local. Pour palier à ce problème, il est possible de nourrir la base de donnée Prisma avec des données par défaut.
- Créer le fichier
back/prisma/seed.dev.ts
en se basant sur le modèleback/prisma/seed.model.ts
. - Démarrer les containers
postgres
ettd-api
- (Optionnel) Reset de la base de données
3.1 Dans le container
postgres
:psql -U trackdechets -d prisma -c "DROP SCHEMA \"default\$default\" CASCADE;"
pour supprimer les données existantes 3.2 Dans le containertd-api
:npx prisma db push --preview-feature
pour recréer les tables - Dans le container
td-api
:npx prisma db seed --preview-feature
pour nourrir la base de données.
Au cas où il serait nécessaire d'ajouter un objet à la base de données, vous pouvez utiliser le script "object-creator". Pour celà, modifiez le fichier libs/back/object-creator/src/objects.ts
en ajoutant des objets en respectant le format démontré en exemple.
Vous pouvez ensuite utiliser npx nx run object-creator:run
et si tout se passe bien, les objets seront créés dans la base de donnée spécifiée dans la variable d'environnement "DATABASE_URL".
Les icônes utilisées dans l'application front viennent de https://streamlineicons.com/. Nous détenons une license qui nous permet d'utiliser jusqu'à 100 icônes (cf Streamline Icons Premium License).
Voilà la procédure pour ajouter une icône au fichier Icons.tsx
:
- Se connecter sur streamlineicons.
- Copier le SVG de l'icône concerné.
- Convertir le SVG en JSX et l'ajouter au fichier (adapter le code selon les exemples existants : props, remplacer
width
/height
et"currentColor"
).
Pour s'y retrouver plus facilement, suivre la convention de nommage en place et utiliser le nom donné par streamlineicons.
Une clef de signature RSA est nécessaire pour signer les tokens d'identité d'Openid.
openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key
Le contenu de pkcs8.key va dans la vairable d'env OIDC_PRIVATE_KEY. Le contenu de publickey.crt est destiné aux applications clientes d'OpenId connect.
npx nx run back:reindex-bsd BSD-XYZ123
npx nx run back:reindex-partial-in-place -- bsdasri -f
Si la commande pour créer la base de données ne fonctionne pas (npx prisma db push
), il est possible que le symbole $ dans le nom de la base (default$default) pose problème. Deux solutions:
- Encapsulez l'URI de la base avec des guillements simple, ie:
DATABASE_URL='postgresql://username:password@postgres:5432/prisma?schema=default$default'
- Enlevez complètement le paramètre schema:
DATABASE_URL=postgresql://username:password@postgres:5432/prisma
Vous pouvez vérifier vos indexes Eslastic Search avec la commande suivante:
curl -X GET "localhost:9200/_cat/indices"
Si les indexes sont incomplets ou si la commande a échoué, vous pouvez vous connecter au container de l'API et passer en mode verbose avant de relancer l'indexation:
# Pour se connecter au container de l'API
docker exec -it $(docker ps -qf "name=td-api") bash
export FORCE_LOGGER_CONSOLE=true
npx nx run back:reindex-all-bsds-bulk -- -f
Si le problème remonté est un "Segmentation fault", il est probable que la mémoire allouée au container soit insuffisante. Vous pouvez contourner le problème en limitant la taille des batches (dans votre .env):
BULK_INDEX_BATCH_SIZE=100
Vous pouvez également augmenter la taille mémoire allouée au container Docker, dans un fichier docker-compose.override.yml
placé à la racine du répo:
[...]
postgres:
deploy:
resources:
limits:
memory: 2G
reservations:
memory: 128M
[...]
elasticsearch:
environment:
- "ES_JAVA_OPTS=-Xms1G -Xmx1G"
[...]
Si l'indexation ne fonctionne pas et que vous voyez des erreurs de type:
[TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block]
dans les logs elastic, il est probable que votre disque dur soit plein à plus de 95% et que elastic passe donc en read&delete only. Pour résoudre le problème, en admettant qu'il reste quand même un peu de place pour créer l'index (quelques Go max), il faut ajouter cette ligne:
cluster.routing.allocation.disk.threshold_enabled: false
au fichier elasticsearch.yml qui se trouve généralement dans elasticsearch/config/.
Si une erreur NGINX "413 Request Entity too large" interromp le process
{"meta":{"body":"<html>\r\n<head><title>413 Request Entity Too Large</title></head>\r\n<body>\r\n<center><h1>413 Request Entity Too Large</h1></center>\r\n<hr><center>nginx/1.25.5</center>
[...]
et que vous utilisez Elastic derrière le proxy NGINX (es.trackdechets.local), il faut augmenter la taille max de body acceptée par NGINX. Pour celà ajouter cette ligne:
client_max_body_size 100M;
dans le fichier nginx.conf à l'intérieur du bloc "http" ou "server" (qui se trouve généralement dans le dossier nginx/ ou nginx/conf/). redémarrez ensuite nginx pour appliquer la config.
La validation et le parsing des données entrantes est gérée en interne par la librairie Zod.
La déclaration d'un schéma Zod pour un type de bordereau donnée se fait en composant plusieurs étapes :
- Déclaration d'un schéma de validation "statique" permettant de définir les types de chaque champ et des règles de validation simples (ex: un email doit ressembler à un email, un N°SIRET doit faire 14 caractères, il peut y avoir au maximum 2 plaques d'immatriculations) ainsi que des valeurs par défaut.
- Déclaration de règles de validation plus complexes (via la méthode
superRefine
) faisant intervenir plusieurs champs ou des appels asynchrones à la base de données (ex: la raison du refus doit être renseignée si le déchet est refusé, la date de l'opération doit être postérieure à la date de l'acceptation, les identifiants des bordereaux à regrouper doivent correspondre à des bordereaux en attente de regroupement, etc). - Déclaration de
transformers
permettant de modifier les données (ex: auto-compléter les récépissés transporteurs à partir de la base de données, auto-compléter le nom et l'adresse à partir de la base SIRENE, etc).
Le schéma ainsi obtenu permet de centraliser tout le process de validation et de transformation des données et Zod nous permet d'inférer deux types :
- le type attendu en entrée du parsing Zod.
- le type obtenu en sortie du parsing Zod.
Ces types nous servent de "pivots" entre les données entrantes provenant de la couche GraphQL et le format de données de la couche Prisma.
Deux méthodes permettant respectivement de convertir les données GraphQL ou les données Prisma vers le format Zod :
graphQlInputToZodBsd(input: GraphQLBsdInput): ZodBsd
: permet de convertir les données d'input GraphQL vers le format Zod.prismaToZodBsda(bsd: PrismaBsd): ZodBsd
: permet de convertir les données
Dans le cas d'une mutation de création, une version simplifiée du process pourra ressembler à :
function createBsdResolver(_, { input }: MutationCreateBsdArgs, context: GraphQLContext) {
const user = checkIsAuthenticated(context);
await checkCanCreate(user, input);
const zodBsd = await graphQlInputToZodBsd(input);
const bsd = await parseBsdAsync(
{ ...zodBsd, isDraft },
{
user,
//
currentSignatureType: !isDraft ? "EMISSION" : undefined
}
);
const bsdData: Prisma.CreateBsdInput = {...
// calcule ici le payload prisma à partir des données
// obtenues en sortie de parsing, il faut notament gérer
// la création / connexion / déconnexion d'objets liés (ex: transporteurs, packagings, etc).
}
const created = await repository.create({data: bsdData})
// [...]
}
Dans le cas d'une mutation d'update, on ne peut pas simplement valider les données entrantes, il faut aussi vérifier que le bordereau obtenu suite à l'update sera toujours valide. En effet beaucoup de règles de validation s'appliquent sur plusieurs champs, si je modifie un des champ je veux m'assurer que sa valeur est toujours cohérente avec les données persistées en base. Je dois par ailleurs vérifier qu'on n'est pas en train de modifier un champ qui a été verrouillée par signature tout en permettant de renvoyer les mêmes données. On passe alors par une méthode dont la signature est la suivante :
type Output = {
parsedBsd: ParsedZodBsd;
updatedFields: string[];
};
function mergeInputAndParseBsdAsync(persisted: PrismaBsd, input: GraphQLBsdInput, context: BsdValidationContext): Output {
const zodPersisted = prismaToZodBsd(persisted);
const zodInput = await graphQlInputToZodBsd(input);
// On voit ici l'utilité du schéma Zod comme type pivot entre les données
// entrantes de la couche GraphQL et les données de la couche prisma
const bsd: ZodBsff = {
...zodPersisted,
...zodInput
};
// Calcule la signature courante à partir des données si elle n'est
// pas fourni via le contexte
const currentSignatureType = context.currentSignatureType ?? getCurrentSignatureType(zodPersisted);
const contextWithSignature = {
...context,
currentSignatureType
};
// Vérifie que l'on n'est pas en train de modifier des données
// vérrouillées par signature.
const updatedFields = await checkBsdSealedFields(
zodPersisted
bsd,
contextWithSignature
);
const parsedBsd = await parseBsdAsync(bsd, contextWithSignature);
return { parsedBsff, updatedFields };
}
Le workflow simplifié dans la mutation d'update
ressemble alors à ça :
function updateBsdResolver(_, { id, input }: MutationUpdateBsdArgs, context: GraphQLContext) {
const user = checkIsAuthenticated(context);
await checkCanUpdate(user, input);
const persisted = await getBsdOrNotFound({id})
const { parsedBsd, updatedFields } = mergeInputAndParseBsdAsync(persisted, input, {})
if (updatedFields.length === 0){
// évite de faire un update "à blanc" si l'input ne modifie rien
return expandBsdFromDb(persisted)
}
const bsdData: Prisma.CreateBsdInput = {...
// calcule ici le payload prisma à partir des données
// obtenues en sortie de parsing, il faut notament gérer
// la création / connexion / déconnexion d'objets liés (ex: transporteurs, packagings, etc).
}
const updated = await repository.update({ data: bsdData })
// [...]
}
Une modification de signature ne modifie pas les données mais nécessite quand même de réaliser le parsing car on va vérifier que les données sont toujours cohérentes avec le type de signature apposée, pour vérifier par exemple que les champs requis à cette étape sont bien présents. La signature courant est alors passée explicitement via le contexte de validation.
function signBsdResolver(_, { id, input }: MutationUpdateBsdArgs, context: GraphQLContext) {
const user = checkIsAuthenticated(context);
await checkCanSign(user, input);
const persisted = await getBsdOrNotFound({ id: input.id });
const signatureType = getBsdSignatureType(input.type);
const zodBsd = prismaToZodBsd(persisted);
// Check that all necessary fields are filled
await parseBsdAsync(zodBsd, {
user,
currentSignatureType: signatureType
});
const signed = await repository.update({ data: { ...input, signatureDate: new Date() } });
}
Le cycle de vie du bordereau implique que le remplissage des champs se fasse au fur et à mesure et que des signatures viennent "verrouiller" certains champs. Les règles métier relatives aux champs requis et verrouillés sont définies dans des tableurs (ex pour le BSFF : BSFF - Informations requises et scellées).
La sémantique de définition pour chaque champ requis / verrouillée est très similaire : ¨un champ est requis / scellé à partir de telle signature si telle condition est remplie sur le bordereau¨. D'où l'idée de créer un fichier de définition commun rules
permettant de regrouper la définition des champs verrouillés / requis. Exemple pour le BSDA
export const bsdaEditionRules: BsdaEditionRules = {
// [...]
emitterCompanySiret: {
readableFieldName: "le SIRET de l'entreprise émettrice",
sealed: { from: "EMISSION" },
required: {
from: "EMISSION",
when: bsda => !bsda.emitterIsPrivateIndividual
}
}
/// [...]
La vérification sur les champs requis se fait dans un refinement
synchrone checkRequiredRules
tandis que la vérification sur les champs scellés se fait via la méthode checkSealedFields
dans la fonction mergeInputAndParseBsdAsync
.