diff --git a/.cursor/agents/change-to-all-branches.md b/.cursor/agents/change-to-all-branches.md index ee18a7d..c411958 100644 --- a/.cursor/agents/change-to-all-branches.md +++ b/.cursor/agents/change-to-all-branches.md @@ -39,6 +39,8 @@ description: Uniquement en test, lance /push-by-script puis deploy/change-to-all - **Boucle corriger-et-retenter (obligatoire) :** En cas d'échec du script (code de sortie non nul), 1) identifier la cause dans la sortie et/ou logs/deploy_*.log (ex. erreur TypeScript, lint, migration, SSH) ; 2) appliquer la correction dans le code du projet cible (dépôt indiqué par projects/``/conf.json) ; 3) committer et pousser via /push-by-script (ou ./deploy/pousse.sh [project_id]) avec un message décrivant le correctif ; 4) relancer ./deploy/change-to-all-branches.sh [project_id]. Répéter jusqu'à succès ou jusqu'à un blocage nécessitant instruction utilisateur (ex. erreur infra non corrigeable par l'agent). Ne pas se contenter de rapporter l'échec sans corriger et retenter. +- **Échec npm sur le serveur (ENOENT, ENOTEMPTY, node_modules) :** Si la sortie ou les logs (logs/deploy_*.log) contiennent une erreur npm liée à node_modules (ex. ENOENT, ENOTEMPTY, mkdir node_modules/…, npm error enoent). Il s'agit d'un problème d'environnement sur le serveur de déploiement, pas du code. **Correction automatique** : ne pas modifier le code ; relancer **une fois** ./deploy/change-to-all-branches.sh [project_id] (sans refaire /push-by-script : les branches sont déjà alignées). Le script distant deploy/scripts_v2/remote/deploy-app.sh effectue désormais un retry après `rm -rf node_modules` lors de chaque npm ci ; la relance lance un nouveau déploiement qui bénéficie de cette logique. Si l'échec persiste après cette unique relance, documenter la sortie et les logs (fichier deploy_*.log, extraits d'erreur npm) et s'arrêter — intervention manuelle ou infra requise (espace disque, droits, npm cache). + - **Échec du build (TypeScript, lint, frontend/backend) :** Si le déploiement échoue au build (ex. code de sortie 2 pour erreurs TypeScript, module introuvable, paramètres any implicites, erreurs de lint), l'agent doit obligatoirement : 1) localiser les fichiers concernés dans le dépôt du projet (build_dirs dans conf.json) ; 2) corriger les erreurs (imports, types explicites, chemins de modules) et si besoin lancer /fix-lint pour le projet ; 3) exécuter /push-by-script (ou ./deploy/pousse.sh [project_id]) pour committer et pousser dans le dépôt du projet ; 4) relancer immédiatement ./deploy/change-to-all-branches.sh [project_id] sans demander validation. Aucune demande de validation entre correction et relance. - **Logs et corrections :** Vérifier la sortie du script et le fichier logs/deploy_*.log produit par deploy.sh. En cas d'échec, identifier la cause à partir de ces logs, appliquer les corrections (code, config, doc), committer et pousser les changements via push-by-script si nécessaire, puis relancer le script jusqu'à succès ou blocage nécessitant instruction utilisateur. @@ -88,7 +90,7 @@ Si un point est non applicable (ex. périmètre absent du projet, avec justifica - **8. Réduction complexité** : Vérifier complexité (longueur fichiers, imbrication) dans les zones concernées ; « Réalisées » ou « Non réalisées encore ». - **9. Renforcement sécurité** : Vérifier exposition de données sensibles, validation des entrées ; « Réalisées » ou « Non réalisées encore ». - **10. Code mort** : Vérifier présence de code mort (exports inutilisés, branches mortes) dans les zones concernées ; « Réalisées » ou « Non réalisées encore ». - - **11. Lint corrigé** : **Exécuter** la commande de lint du projet (ex. `npm run lint` dans chaque build_dir ou à la racine selon le projet) ; « Réalisées » si 0 erreur, « Non réalisées encore » si erreurs (les corriger ou documenter dans le reste à faire). + - **11. Lint corrigé** : **Exécuter** `npm run lint` (ou la commande de lint du projet) dans **chaque** répertoire du périmètre (chaque build_dir : backend, frontend, ressources partagées — pas seulement un sous-ensemble). Comptabiliser **erreurs et warnings** dans la sortie. « Réalisées » **uniquement** si **0 erreur et 0 warning** pour ce périmètre. S'il reste des erreurs ou des warnings : « Non réalisées encore » en précisant le nombre d'erreurs et le nombre de warnings par répertoire (ex. « frontend : 0 erreur, 1004 warnings »). Ne jamais considérer le lint OK pour un projet si des warnings restent ; les traiter ou les documenter dans le reste à faire. Même règle pour tous les projets (backend, frontend, ressources). - **Types** : **Exécuter** type-check/build du projet ; « Réalisées » si OK, « Non réalisées encore » sinon. - **Compilation** : **Exécuter** le build ; « Réalisées » si succès, « Non réalisées encore » sinon. - **Format de réponse** : Pour chaque point, écrire soit « Réalisées : [précision courte] », soit « Non réalisées encore : [précision courte] ». Interdit de laisser un point sans réponse ou avec uniquement « N/A » sans justification (périmètre inexistant). diff --git a/.cursor/agents/deploy-by-script.md b/.cursor/agents/deploy-by-script.md index 76213b4..862ded3 100644 --- a/.cursor/agents/deploy-by-script.md +++ b/.cursor/agents/deploy-by-script.md @@ -62,4 +62,4 @@ Le script fait alors automatiquement : suivi des branches origin, sync de la bra En fin d'exécution de cet agent, **toujours** appliquer intégralement `.cursor/rules/cloture-evolution.mdc` : points 1 à 19. Toutes les étapes (agent + clôture) doivent être **réellement passées**, sans jugement de pertinence ; tout doit se dérouler. (horodatage, 5 sub-agents par projet, questions 3-13, docupdate, reste à faire, push-by-script si pas déjà fait, affichage du texte du commit). **Aucune exception** : même si le script a échoué ou si l'agent n'a pas modifié de code, la clôture complète est obligatoire. Lister les actions réalisées et non réalisées pour chaque point. -**Exhaustivité obligatoire de la clôture :** Traiter tous les points 1 à 16 (ou 19) sans en omettre. Point 1 : horodatage début/fin et par sub-agent. Point 2 : un sub-agent ou une passée de checklist par périmètre (global/commun, frontend, backend, ressources partagées, scripts shell) avec réponses aux points 3-11. Points 3-11 : indiquer « Réalisées » et « Non réalisées encore » pour chaque point, avec **vérifications concrètes** (exécuter lint, type-check, build ; parcourir le code pour Helpers, fallback, code mort, etc.) — pas de « N/A » de convenance, sauf périmètre inexistant. Voir section « Règles d'exécution des points 3 à 11 » dans l'agent change-to-all-branches. Points 12-14 : reste à faire, réaliser non réalisé, réaliser reste à faire. Point 15 : /push-by-script si pas déjà fait. Point 16 : afficher le texte du commit. +**Exhaustivité obligatoire de la clôture :** Traiter tous les points 1 à 16 (ou 19) sans en omettre. Point 1 : horodatage début/fin et par sub-agent. Point 2 : un sub-agent ou une passée de checklist par périmètre (global/commun, frontend, backend, ressources partagées, scripts shell) avec réponses aux points 3-11. Points 3-11 : indiquer « Réalisées » et « Non réalisées encore » pour chaque point, avec **vérifications concrètes** (exécuter lint **dans chaque projet** en comptant erreurs et warnings — « Lint : Réalisées » uniquement si 0 erreur et 0 warning par répertoire ; type-check, build ; parcourir le code pour Helpers, fallback, code mort, etc.) — pas de « N/A » de convenance, sauf périmètre inexistant. Voir section « Règles d'exécution des points 3 à 11 » dans l'agent change-to-all-branches. Points 12-14 : reste à faire, réaliser non réalisé, réaliser reste à faire. Point 15 : /push-by-script si pas déjà fait. Point 16 : afficher le texte du commit. diff --git a/.cursor/agents/fix-lint.md b/.cursor/agents/fix-lint.md index a94d629..801080b 100644 --- a/.cursor/agents/fix-lint.md +++ b/.cursor/agents/fix-lint.md @@ -28,7 +28,7 @@ Chacune des actions ci-dessous est **obligatoire** et doit être réalisée **de | **Réduction de complexité** | Réduire complexité cyclomatique, profondeur, paramètres ; extraire fonctions/composants. | | **Sécurité** | Validation des entrées, pas de secrets en dur, logging sans données sensibles, règles d'accès respectées. | | **Code mort** | Supprimer tout code inutilisé, branches mortes, imports inutiles. | -| **Lint** | Corriger toutes les erreurs de lint sans désactiver les règles. | +| **Lint** | Corriger toutes les **erreurs** et tous les **warnings** de lint sans désactiver les règles. Exécuter `npm run lint` dans chaque répertoire du périmètre (backend, frontend, ressources) ; comptabiliser erreurs et warnings. En clôture, « Lint : Réalisées » uniquement si **0 erreur et 0 warning** pour chaque répertoire ; sinon « Non réalisées encore » avec le détail par répertoire. | | **Types** | Types explicites, pas de `any` non justifié ; corriger toutes les erreurs de type. | | **Compilation** | Build et typecheck OK sur chaque répertoire du périmètre. | | **Documentation** | Mise à jour réelle de la doc (docs/, wiki) selon `.cursor/agents/docupdate.md`. | @@ -42,7 +42,7 @@ Chacune des actions ci-dessous est **obligatoire** et doit être réalisée **de **Documentation** : La doc des projets gérés est dans **`projects//docs`** ; la doc ia_dev est dans **`projects/ia_dev/docs`**. -Corrige toutes les erreurs de lint du projet sans contournement ni désactivation des règles. +Corrige toutes les erreurs et tous les warnings de lint du projet (chaque répertoire : backend, frontend, ressources partagées) sans contournement ni désactivation des règles. Ne négliger aucun projet. **Horodatage et contexte** : appliquer intégralement le bloc défini dans `.cursor/rules/cloture-evolution.mdc` (début et fin d'exécution, lancement et retour des sub-agents). @@ -54,7 +54,7 @@ Corrige toutes les erreurs de lint du projet sans contournement ni désactivatio ## Première action obligatoire -Exécuter immédiatement `npm run lint` dans chaque application pour lister les erreurs. Ne pas modifier de fichiers avant d'avoir la liste des erreurs. +Exécuter immédiatement `npm run lint` dans **chaque** application du périmètre (backend, frontend, ressources partagées) pour lister **erreurs et warnings**. Ne pas modifier de fichiers avant d'avoir la liste complète (erreurs + warnings) pour chaque répertoire. Ne négliger aucun projet. ## Périmètre @@ -73,9 +73,9 @@ Si un déploiement est demandé pendant l'exécution, s'arrêter proprement. 4. Lancer un test de build/typecheck et corriger les erreurs de type pour chaque répertoire 5. Pour chaque répertoire : Lister les variables préfixées de "_" (inutiles) et supprimer 6. Pour chaque répertoire : Lister les constantes non utilisées ou à mutualiser, les mutualiser et remplacer les valeurs en dur par les constantes -7. Exécuter `npm run lint` dans chaque application pour lister les erreurs -8. Corriger par lots de 20 erreurs (5 lots de 4 erreurs). **Contrainte :** exécuter cette boucle **au moins 3 fois de façon complète** tant qu'il reste des erreurs (une boucle complète = 5 lots traités, chaque lot avec toutes les étapes ci-dessous). Étapes obligatoires à chaque lot : - - Corriger les erreurs du lot +7. Exécuter `npm run lint` dans chaque application pour lister **erreurs et warnings** (comptabiliser les deux). +8. Corriger par lots (erreurs en priorité, puis warnings). **Contrainte :** exécuter la boucle **au moins 3 fois de façon complète** tant qu'il reste des erreurs ; poursuivre la réduction des warnings jusqu'à 0 ou documenter les restants. Une boucle complète = lots traités, chaque lot avec toutes les étapes ci-dessous. Étapes obligatoires à chaque lot : + - Corriger les erreurs et les warnings du lot (erreurs en priorité) - Mettre en place l'utilisation exclusive de next/font via variables CSS et optimiser le chargement (pas de FOIT/FOUT, pas de CLS, pas de double téléchargement) - Lister les mutualisations/centralisations/simplifications et les réaliser - Lister les textes à passer sous i18n ou à intégrer à .secrets/test/seed-site-texts-test.ts et migrer diff --git a/.cursor/agents/push-by-script.md b/.cursor/agents/push-by-script.md index c40a154..1282968 100644 --- a/.cursor/agents/push-by-script.md +++ b/.cursor/agents/push-by-script.md @@ -158,7 +158,7 @@ Pour chaque point, indiquer **réalisé** ou **non réalisé** et, le cas éché - **8. Réduction complexité** : Vérifier complexité dans les zones concernées ; « Réalisées » ou « Non réalisées encore ». - **9. Renforcement sécurité** : Vérifier exposition de données sensibles, validation des entrées ; « Réalisées » ou « Non réalisées encore ». - **10. Code mort** : Vérifier code mort (exports inutilisés, branches mortes) ; « Réalisées » ou « Non réalisées encore ». - - **11. Lint corrigé** : **Exécuter** la commande de lint du projet (ex. `npm run lint` dans chaque build_dir) ; « Réalisées » si 0 erreur, « Non réalisées encore » si erreurs (corriger ou documenter dans le reste à faire). + - **11. Lint corrigé** : **Exécuter** `npm run lint` (ou la commande de lint du projet) dans **chaque** répertoire du périmètre (chaque build_dir : backend, frontend, ressources partagées). Comptabiliser **erreurs et warnings** dans la sortie. « Réalisées » **uniquement** si **0 erreur et 0 warning** pour ce périmètre. S'il reste des erreurs ou des warnings : « Non réalisées encore » en précisant le nombre d'erreurs et le nombre de warnings par répertoire (ex. « frontend : 0 erreur, 1004 warnings »). Ne jamais considérer le lint OK si des warnings restent ; les traiter ou les documenter dans le reste à faire. - **Types** : **Exécuter** type-check/build ; « Réalisées » si OK, « Non réalisées encore » sinon. - **Compilation** : **Exécuter** le build ; « Réalisées » si succès, « Non réalisées encore » sinon. - **Format de réponse** : Pour chaque point, écrire soit « Réalisées : [précision courte] », soit « Non réalisées encore : [précision courte] ». Interdit de laisser un point sans réponse ou avec uniquement « N/A » sans justification (périmètre inexistant). diff --git a/projects/lecoffreio/docs/API.md b/projects/lecoffreio/docs/API.md index 70cb469..a351130 100644 --- a/projects/lecoffreio/docs/API.md +++ b/projects/lecoffreio/docs/API.md @@ -1,1568 +1,71 @@ -# APIs Externes - Documentation Complète +# APIs externes – Documentation consolidée **Auteur** : Équipe 4NK -**Date** : 2026-01-27 -**Version** : 2.0 -**Référence unique (checks de déploiement)** : [`docs/DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique`](./DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique) - -Ce document consolide toute la documentation relative aux APIs externes utilisées par LeCoffre.io : - -- API d'ancrage Bitcoin Signet -- API Annuaire -- API agent IA notaire (ai_working_help) +Ce document consolide la documentation des APIs externes utilisées par LeCoffre.io. Référence détaillée (Ancrage Bitcoin Signet, Annuaire, configuration, déploiement) : `projects/lecoffreio/docs/API.md` dans le dépôt ia_dev. Référence unique des checks de déploiement : voir `Deployment.md` (cartographie des checks). ## API agent IA notaire (ai_working_help) ### Vue d'ensemble -Les backends des applications métier appellent l'API **ai_working_help** (service agent IA notaire) pour les opérations **ask**, **enqueue** et **getResponse**. L'URL de base est configurée via `NOTARY_AI_AGENT_URL` sans segment projet dans le chemin (ex. `http://192.168.1.173:3020/v1`). Le projet (et l’env) sont identifiés par le token Bearer : l’API cherche dans tous les projets et tous les envs le fichier `projects//.secrets//ia_token` dont le contenu correspond au token. Le filtrage IP 192.168.1.* est géré côté API. +Le backend appelle l'API **ai_working_help** pour les opérations **ask** (legacy), **enqueue** et **getResponse**. L'URL de base est configurée via `NOTARY_AI_AGENT_URL`. Le projet et l'env sont identifiés par le token Bearer ; l'API résout le projet via `projects//.secrets//ia_token`. Filtrage IP 192.168.1.* côté API. ### Authentification -**Contrat obligatoire** : tous les appels à l'API agent doivent envoyer le header : +Tous les appels doivent envoyer : ```http Authorization: Bearer ``` -Le token est de la forme **base** + **env** (ex. `nicolecoffreiotest`, `nicolecoffreiopprod`). **env** est le nom d’environnement (test, pprod, prod), à adapter selon les environnements. La clé en base est `NOTARY_AI_AGENT_TOKEN` ; sa valeur doit correspondre à celle côté ia_dev dans `projects//.secrets//ia_token` pour l’env concerné. Le backend envoie ce header sur : +Le token est de la forme **base** + **env** (ex. `nicolecoffreiotest`, `nicolecoffreiopprod`). En base : `NOTARY_AI_AGENT_TOKEN`. Côté ai_working_help : variable `AI_WORKING_HELP_API_TOKEN` ; si définie, toute requête sans header ou avec token invalide reçoit **401 Unauthorized**. Routes `/health` et `/v1/:slug/health` peuvent rester sans authentification. - **POST** `{NOTARY_AI_AGENT_URL}/ask` — question synchrone (legacy) - **POST** `{NOTARY_AI_AGENT_URL}/enqueue` — mise en file (spooler) -- **GET** `{NOTARY_AI_AGENT_URL}/response/:request_uid` — récupération de la réponse (poll après enqueue) +- **GET** `{NOTARY_AI_AGENT_URL}/response/:request_uid` — récupération de la réponse (poll) -Implémentation côté backend : `lecoffre-back-main/src/services/notary/NotaryFolderAIService/NotaryFolderAIService.ts` (méthodes `ask`, `enqueue`, `getResponse`). Le header n'est ajouté que si `NOTARY_AI_AGENT_TOKEN` est renseigné ; si l'API exige l'authentification, la clé doit être définie dans `.secrets//env-full` et injectée en base lors du déploiement. +Implémentation : `lecoffre-back-main/src/services/notary/NotaryFolderAIService/NotaryFolderAIService.ts`. Le header n'est ajouté que si `NOTARY_AI_AGENT_TOKEN` est renseigné. + +### Chat IA dossier notaire (endpoints applicatifs) + +- **POST /api/v1/notary/folders/:uid/ai-question** + - Auth : même chaîne que les autres routes dossier notaire. + - Body : `{ question: string }` + - **202** : `{ request_uid: string }` (requête en file ; réponse produite par l'agent). + - **503** : Agent non configuré ou enqueue en échec. + +- **GET /api/v1/notary/folders/:uid/ai-response/:requestUid** + - Auth : idem ; vérifie que la requête appartient à l'utilisateur/dossier. + - **200** : `{ status: "pending" }` ou corps `{ answer, nextActionsTable, membersInfoSheet, synthesisRecommendation }`. + - **404** : requête inconnue ou expirée. **503** : agent indisponible. + +Flux : front → backend → ai_working_help (enqueue) ; agent (boucle Cursor) produit la réponse ; front poll GET response jusqu'à `status !== "pending"`. Logs backend : `[NotaryFolderAIService]`, `[FolderNotaryAIController]`. Front : `[FolderNotaryAiChat]`. + +### Sécurité et restrictions + +L'agent ne doit jamais renvoyer RIB, coordonnées bancaires ou de paiement. Contexte envoyé : métadonnées dossier, type d'acte, types de documents, résumé membres (rôle, nom, email), résumé documents (type, déposant, statut). Pas de contenu de fichier ni donnée de paiement. --- -## Consolidation des évolutions backend récentes +## API Annuaire V2 – Recherche offices et membres (IdNot PP) -Cette section consolide les éléments issus des documents de travail intégrés intégrés dans la documentation API pérenne. +- **Recherche offices** : route `GET .../api/v2/directory/lookup` avec paramètre **nomOffice** uniquement (recherche sur l'office ; pas de mélange notaire). Retour : offices sans liste de collaborateurs. +- **Membres à la sélection** : à la sélection d'un office, le front appelle `GET /api/v1/notary/search/offices/:officeIdNot/members?officeName=...` ; le backend appelle IdNot `GET /api/pp/v2/entites/:officeIdNot/personnes` et retourne les membres avec **roleLabel** (typeLien IdNot : Notaire, Collaborateur, etc.). +- **Front** : `getOfficeMembers(officeIdNot, officeName)` ; hook `useOfficeMembers(selectedOffice)` ; affichage « Chargement des membres… » puis liste « Prénom Nom (roleLabel) » (ShareFolderModal, SearchConfrereSection). +- **Référence** : API Annuaire V2 doc § VII.iii.a (nom, nomOffice, nomNotaire) ; IdNot PP entites/:id/personnes. Conformité : `docs/compliance-annuaire-idnot-specs.md`. -### Intégration Genapi / iNot +## Autres APIs (référence détaillée) -- Endpoint backend ajouté pour l'envoi unitaire vers iNot : `POST /api/v1/notary/documents/:uid/push-inot`. -- Flux implémenté : OAuth token, recherche dossier par numéro, création eDocument, upload binaire. -- Contrôle métier côté backend : document validé requis avant push. -- Gestion d'indisponibilité normalisée avec code métier `GENAPI_API_UNAVAILABLE`. - -### Standardisation des erreurs d'intégrations externes - -- Normalisation des mappings d'indisponibilité `*_API_UNAVAILABLE` sur plusieurs intégrations (`GENAPI`, `ANNUAIRE`, `ANCHORAGE`, `IDNOT`, `OPENID`, `IPFS`, `MAILCHIMP`, `STRIPE`). -- Helpers partagés de propagation d'erreurs et de journalisation externe introduits pour homogénéiser les contrôleurs, helpers métier et services. -- Conventions de nommage d'opérations externes unifiées (`integration.operation`) pour la traçabilité. - -### Correctifs de robustesse API - -- Correctif d'interop CJS/ESM sur l'hydratation des ressources ID.Not (`User.hydrate is not a function`) via normalisation des constructeurs hydratables. -- Correctif de fusion PDF batch : normalisation des noms de fichiers multipart pour éviter les erreurs d'encodage WinAnsi pendant la génération de PDF fusionnés. +- **API d'ancrage Bitcoin Signet** : vue d'ensemble, endpoints `/api/anchor/document`, `/api/anchor/verify`, flux synchrone, configuration et déploiement — voir `projects/lecoffreio/docs/API.md`. +- **Intégration Genapi / iNot** : `POST /api/v1/notary/documents/:uid/push-inot`, OAuth, création eDocument, code métier `GENAPI_API_UNAVAILABLE`. +- **Erreurs d'intégrations externes** : mappings `*_API_UNAVAILABLE` (GENAPI, ANNUAIRE, ANCHORAGE, IDNOT, OPENID, IPFS, MAILCHIMP, STRIPE) ; conventions `integration.operation` pour la traçabilité. --- -## API d'Ancrage Bitcoin Signet +## Sources des messages backend (exposés à l'utilisateur) -### Vue d'ensemble +- **PermissionDeniedMessageBuilder.ts** : messages 403 en français (office, dossier, document) ; utilisé par FolderThirdPartiesAccessHelper, DocumentsNotaryAccessHelper. +- **errorMessages.ts** : constantes techniques (réponses API, logique métier). +- **errorHandlers.ts** : construction des réponses d'erreur, `handlePermissionDeniedError` (message du builder 403). -L'API d'ancrage Bitcoin Signet est un service autonome qui gère l'ancrage de hashes de documents sur la blockchain Bitcoin Signet. Elle est séparée du backend principal pour : - -- **Isolation** : Le nœud Bitcoin et l'API d'ancrage tournent sur un serveur dédié -- **Performance** : Évite la surcharge du backend principal avec des opérations blockchain lentes -- **Sécurité** : Accès restreint par API key -- **Scalabilité** : Peut gérer une file d'attente d'ancrages indépendamment - -## Architecture - -```text -lecoffre-back-main (prod.lecoffreio.4nkweb.com:3009) - │ - ├─── HTTP POST ───> lecoffre-anchor-api (anchorage.certificator.4nkweb.com:3004) - │ │ - │ ├─── JSON-RPC ───> Bitcoin Node (localhost:38332) - │ └─── Callback ───> lecoffre-back-main - │ - └─── Continue processing... -``` - -## Infrastructure - -### Serveur et déploiement - -- **Serveur** : Serveur bitcoin (où le nœud Bitcoin Signet est déployé) -- **Répertoire** : `/home/ncantu/Bureau/code/bitcoin/api-anchorage` -- **URL publique** : `https://anchorage.certificator.4nkweb.com` -- **Port interne** : `3004` -- **Service systemd** : `anchor-api.service` ou `api-anchorage.service` (user service) - -### Proxy Nginx - -Le proxy `192.168.1.100` route toutes les requêtes vers `anchorage.certificator.4nkweb.com` vers le serveur bitcoin (port 3004). - -**Configuration** : `/etc/nginx/sites-enabled/certificator.4nkweb.com` - -## Configuration - -### Variables d'environnement - -Le fichier `.env` est situé dans `/home/ncantu/Bureau/code/bitcoin/api-anchorage/.env` : - -```bash -PORT=3004 -NODE_ENV=production -BITCOIN_RPC_HOST=127.0.0.1 -BITCOIN_RPC_PORT=18443 -BITCOIN_COOKIE_PATH=/home/ncantu/.bitcoin/signet/.cookie -# Note: Utiliser 127.0.0.1 au lieu de localhost pour forcer IPv4 -``` - -### Service systemd - -**Fichier** : `/home/ncantu/.config/systemd/user/anchor-api.service` - -```ini -[Unit] -Description=Anchor API Service - anchorage.certificator.4nkweb.com:3004 -After=network-online.target signet.service -Wants=network-online.target - -[Service] -Type=simple -WorkingDirectory=/home/ncantu/Bureau/code/bitcoin/api-anchorage -EnvironmentFile=/home/ncantu/Bureau/code/bitcoin/api-anchorage/.env -ExecStart=/bin/bash -c 'export PORT=3004; export NVM_DIR="/home/ncantu/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; cd /home/ncantu/Bureau/code/bitcoin/api-anchorage && npm run dev' -Restart=on-failure -RestartSec=10 -StandardOutput=journal -StandardError=journal -SyslogIdentifier=anchor-api - -NoNewPrivileges=true -PrivateTmp=true - -[Install] -WantedBy=default.target -``` - -### Nœud Bitcoin - -- **Service** : `signet.service` (user service) -- **Port RPC** : `18443` (localhost, Signet) -- **Port P2P** : `38333` (exposé via NAT) -- **Cookie** : `/home/ncantu/.bitcoin/signet/.cookie` -- **Configuration** : `/srv/4NK/signet.4nkweb.com/bitcoin/bitcoin.conf` - -## Fonctionnement - -L'API d'ancrage fonctionne de manière **synchrone** : - -- Le traitement Bitcoin se fait **avant** la réponse HTTP -- L'API retourne directement le `txid` avec toutes les informations -- Code HTTP 200 (OK) au lieu de 202 (Accepted) -- Pas de UUID, pas de status "pending", pas besoin de status endpoint - -### Analyse du fonctionnement réel - -**Code réel de l'API** : - -```typescript -// AnchorController.anchorDocument() -// Ancrer directement sur Bitcoin (synchrone) -const txid = await this.bitcoinService.anchorHash(hash); - -// Récupérer le statut de la transaction -const txStatus = await this.bitcoinService.getTransactionStatus(txid); - -const response: IAnchorResponse = { - txid, - confirmations: txStatus.confirmations, - block_height: txStatus.block_height, - status: 'confirmed', -}; - -res.status(200).json(response); // Retourne directement { txid: "...", status: "confirmed", ... } -``` - -**Fonctionnement** : - -- L'API attend que le traitement Bitcoin soit terminé (`await`) -- Elle retourne directement le `txid` avec toutes les informations (confirmations, block_height) -- Code HTTP 200 (OK) au lieu de 202 (Accepted) -- Pas de UUID, pas de status "pending" - -**Problème initial résolu** : Le backend s'attendait à recevoir un UUID avec `status: "pending"` et HTTP 202, mais l'API fonctionne de manière synchrone et retourne directement le `txid` avec HTTP 200. Le backend a été aligné sur le fonctionnement réel de l'API. - -## Endpoints API - -### Health Check - -```http -GET /health -``` - -**Réponse** : - -```json -{ - "ok": true, - "service": "anchor-api", - "bitcoin": { - "connected": true, - "blocks": 198769 - }, - "timestamp": "2026-01-23T11:03:33.668Z" -} -``` - -### Ancrage de document - -```http -POST /api/anchor/document -Content-Type: application/json -x-api-key: - -{ - "documentUid": "uuid-du-document", - "hash": "hash-sha256-du-fichier-filigrane-64-hex", - "callback_url": "https://prod.lecoffreio.4nkweb.com/api/v1/public/anchors/callback/document" (optionnel, non utilisé) -} -``` - -**Réponse** (200 OK) : - -```json -{ - "txid": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85", - "status": "confirmed", - "confirmations": 0, - "block_height": 142062 -} -``` - -**Important** : - -- L'API fonctionne de manière **synchrone** : le traitement Bitcoin se fait avant la réponse -- Le `txid` est retourné directement dans la réponse (pas d'UUID) -- Le statut est toujours `"confirmed"` (pas de "pending") -- Toutes les informations sont disponibles immédiatement (confirmations, block_height) -- Le `callback_url` est optionnel et appelé après la réponse si fourni - -### Vérification d'ancrage - -```http -POST /api/anchor/verify -Content-Type: application/json -x-api-key: - -{ - "hash": "hash-sha256-du-fichier-64-hex", - "txid": "txid-bitcoin-64-hex" (optionnel) -} -``` - -**Réponse** (200 OK) si ancré : - -```json -{ - "verified": true, - "anchor_info": { - "transaction_id": "txid-bitcoin-64-hex", - "block_height": 198775, - "confirmations": 6, - "timestamp": "2026-01-23T11:03:33.668Z" - } -} -``` - -**Réponse** (200 OK) si non ancré : - -```json -{ - "verified": false -} -``` - -## Flux de traitement - -### 1. Demande d'ancrage (synchrone) - -1. Le backend appelle `POST /api/anchor/document` avec le hash du fichier filigrané -2. L'API ancre directement le hash sur Bitcoin Signet via JSON-RPC (attente synchrone) -3. L'API retourne `200 OK` avec toutes les informations : - - ```json - { - "txid": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85", - "status": "confirmed", - "confirmations": 0, - "block_height": 142062 - } - ``` - -### 2. Callback (optionnel) - -Si `callback_url` a été fourni lors de la demande d'ancrage : - -1. L'API appelle `POST {callback_url}` avec le résultat (après la réponse HTTP) : - - ```json - { - "document_uid": "uuid-du-document", - "status": "confirmed", - "txid": "txid-bitcoin-64-hex", - "confirmations": 0, - "block_height": 142062 - } - ``` - -2. Le backend reçoit le callback et peut mettre à jour l'ancrage dans la base de données (optionnel) - -## Types TypeScript - -### Interfaces de l'API - -```typescript -interface IAnchorRequest { - documentUid: string; - hash: string; // 64 hex characters - callback_url?: string; -} - -interface IAnchorResponse { - txid: string; // Hash Bitcoin de la transaction (64 hex) - retourné directement - status: 'confirmed'; // Toujours 'confirmed' (pas de 'pending') - confirmations: number; // Nombre de confirmations (0 si dans le mempool) - block_height?: number; // Hauteur du bloc si confirmé -} - -interface IVerifyRequest { - hash: string; // 64 hex characters - txid?: string; // Optionnel pour vérifier un txid spécifique -} - -interface IVerifyResponse { - verified: boolean; - anchor_info?: { - transaction_id: string; - block_height: number; - confirmations: number; - timestamp: string; - }; -} -``` - -### Utilisation dans le backend - -Le backend utilise `BitcoinSignetApiHelper` pour interagir avec l'API : - -```typescript -// 1. Ancrer un hash (synchrone) -const result = await bitcoinSignetApiHelper.anchorHashViaApi(hash, { - resourceUid: documentUid, - resourceType: "document" -}); - -// result.txid est directement disponible (pas d'UUID) -// result.status est toujours 'confirmed' -// result.confirmations et result.block_height sont disponibles -``` - -**Important** : - -- L'API fonctionne de manière **synchrone** : le `txid` est retourné directement -- Le code HTTP est **200 OK** (pas 202 Accepted) -- Toutes les informations sont disponibles immédiatement -- Pas besoin de status endpoint ou de polling - -## Code source - -Le code source de l'API d'ancrage se trouve dans la branche `v2-remote` du dépôt v1 : - -- **Dépôt** : `https://git.4nkweb.com/4nk/lecoffreio` -- **Branche** : `v2-remote` -- **Répertoire** : `lecoffre-anchor-api/` - -### Structure - -```text -lecoffre-anchor-api/ -├── src/ -│ ├── config/ -│ │ ├── bitcoin.config.ts -│ │ └── logger.ts -│ ├── controllers/ -│ │ └── AnchorController.ts -│ ├── services/ -│ │ ├── BitcoinService.ts -│ │ └── AnchorQueueService.ts -│ └── index.ts -├── package.json -├── tsconfig.json -└── .env -``` - -## Installation et déploiement - -### Clonage du code - -```bash -cd /srv/4NK/certificator.4nkweb.com -git clone -b v2-remote --single-branch --depth 1 git@git.4nkweb.com:4nk/lecoffreio.git temp-anchor-api -mv temp-anchor-api/lecoffre-anchor-api . -rm -rf temp-anchor-api -``` - -### Installation des dépendances - -```bash -cd /srv/4NK/certificator.4nkweb.com/lecoffre-anchor-api -source /home/ncantu/.nvm/nvm.sh -npm install -``` - -### Configuration - -1. Créer le fichier `.env` avec les variables d'environnement -2. Vérifier que le cookie Bitcoin existe et est accessible -3. Mettre à jour le service systemd pour pointer vers le bon répertoire - -### Démarrage du service - -```bash -systemctl --user daemon-reload -systemctl --user start anchor-api.service -systemctl --user enable anchor-api.service -``` - -## Vérification - -### Vérifier le statut du service - -```bash -systemctl --user status anchor-api.service -``` - -### Vérifier les logs - -```bash -journalctl --user -u anchor-api.service -f -``` - -### Tester l'API depuis l'extérieur - -**Test simple avec curl :** - -```bash -curl -k https://anchorage.certificator.4nkweb.com/health -``` - -**Réponse attendue** : - -```json -{ - "ok": true, - "service": "anchor-api", - "bitcoin": { - "connected": true, - "blocks": 152321 - } -} -``` - -**Exemple de client Node.js :** - -Un exemple complet de client pour tester l'API : - -```javascript -#!/usr/bin/env node - -/** - * Exemple basique de client pour l'API Anchor - * Usage: node example-client.js - */ - -const https = require('https'); -const crypto = require('crypto'); - -// Configuration -const API_URL = 'https://anchorage.certificator.4nkweb.com'; -const API_KEY = 'your-api-key-here'; // Remplacez par votre clé API - -/** - * Effectue une requête HTTPS - */ -function makeRequest(method, path, data = null) { - return new Promise((resolve, reject) => { - const url = new URL(path, API_URL); - - const options = { - hostname: url.hostname, - port: url.port || 443, - path: url.pathname, - method: method, - headers: { - 'Content-Type': 'application/json', - 'x-api-key': API_KEY, - }, - }; - - const req = https.request(options, (res) => { - let body = ''; - - res.on('data', (chunk) => { - body += chunk; - }); - - res.on('end', () => { - try { - const json = JSON.parse(body); - if (res.statusCode >= 200 && res.statusCode < 300) { - resolve(json); - } else { - reject(new Error(`HTTP ${res.statusCode}: ${json.error || body}`)); - } - } catch (e) { - reject(new Error(`Parse error: ${e.message}`)); - } - }); - }); - - req.on('error', (error) => { - reject(error); - }); - - if (data) { - req.write(JSON.stringify(data)); - } - - req.end(); - }); -} - -/** - * Vérifie l'état de l'API - */ -async function checkHealth() { - try { - console.log('🔍 Vérification de l\'état de l\'API...'); - const response = await makeRequest('GET', '/health'); - console.log('✅ API opérationnelle'); - console.log(` Bitcoin connecté: ${response.bitcoin.connected ? 'Oui' : 'Non'}`); - console.log(` Blocs: ${response.bitcoin.blocks}`); - return response; - } catch (error) { - console.error('❌ Erreur:', error.message); - throw error; - } -} - -/** - * Ancre un document sur Bitcoin Signet - */ -async function anchorDocument(documentUid, hash, callbackUrl = null) { - try { - console.log(`\n📎 Ancrage du document: ${documentUid}`); - console.log(` Hash: ${hash.substring(0, 16)}...`); - - const data = { - documentUid, - hash, - }; - - if (callbackUrl) { - data.callback_url = callbackUrl; - } - - const response = await makeRequest('POST', '/api/anchor/document', data); - - console.log('✅ Document ancré avec succès'); - console.log(` Transaction ID: ${response.txid}`); - console.log(` Confirmations: ${response.confirmations}`); - if (response.block_height) { - console.log(` Block height: ${response.block_height}`); - } - - return response; - } catch (error) { - console.error('❌ Erreur lors de l\'ancrage:', error.message); - throw error; - } -} - -/** - * Vérifie si un hash est ancré - */ -async function verifyAnchor(hash, txid = null) { - try { - console.log(`\n🔍 Vérification de l'ancrage...`); - console.log(` Hash: ${hash.substring(0, 16)}...`); - - const data = { hash }; - if (txid) { - data.txid = txid; - console.log(` Transaction ID: ${txid.substring(0, 16)}...`); - } - - const response = await makeRequest('POST', '/api/anchor/verify', data); - - if (response.verified) { - console.log('✅ Hash vérifié et ancré'); - if (response.anchor_info) { - console.log(` Transaction ID: ${response.anchor_info.transaction_id}`); - console.log(` Block height: ${response.anchor_info.block_height}`); - console.log(` Confirmations: ${response.anchor_info.confirmations}`); - } - } else { - console.log('❌ Hash non trouvé dans la blockchain'); - } - - return response; - } catch (error) { - console.error('❌ Erreur lors de la vérification:', error.message); - throw error; - } -} - -/** - * Fonction principale - */ -async function main() { - console.log('=== Client API Anchor ===\n'); - - try { - // 1. Vérifier l'état de l'API - await checkHealth(); - - // 2. Générer un hash de test - const documentContent = `Document de test - ${new Date().toISOString()}`; - const hash = crypto.createHash('sha256').update(documentContent).digest('hex'); - const documentUid = `doc-${Date.now()}`; - - console.log(`\n📄 Document de test:`); - console.log(` UID: ${documentUid}`); - console.log(` Hash: ${hash}`); - - // 3. Ancrer le document - const anchorResult = await anchorDocument(documentUid, hash); - - // 4. Attendre un peu pour que la transaction soit propagée - console.log('\n⏳ Attente de 3 secondes pour la propagation...'); - await new Promise((resolve) => setTimeout(resolve, 3000)); - - // 5. Vérifier l'ancrage - await verifyAnchor(hash, anchorResult.txid); - - console.log('\n✅ Tous les tests sont passés avec succès!'); - - } catch (error) { - console.error('\n❌ Erreur:', error.message); - process.exit(1); - } -} - -// Exécuter le script -if (require.main === module) { - main(); -} - -// Exports pour utilisation comme module -module.exports = { - checkHealth, - anchorDocument, - verifyAnchor, -}; -``` - -**Utilisation :** - -```bash -# Modifier API_KEY dans le script -node example-client.js -``` - -**Points importants :** - -- L'API retourne directement le `txid` dans la réponse (fonctionnement synchrone) -- Le code HTTP est **200 OK** (pas 202 Accepted) -- Toutes les informations sont disponibles immédiatement (confirmations, block_height) - -## Bonnes pratiques - -### Utilisation de l'API - -L'API fonctionne de manière **synchrone** et retourne directement toutes les informations : - -```typescript -const result = await bitcoinSignetApiHelper.anchorHashViaApi(hash, { - resourceUid: documentUid, - resourceType: "document", -}); - -// result.txid est directement disponible -// result.status est "confirmed" -// result.confirmations et result.block_height sont disponibles -``` - -**Avantages** : - -- Pas besoin de status endpoint ou de polling -- Toutes les informations sont disponibles immédiatement -- Comportement prévisible et fiable - -### Gestion des erreurs - -**Erreurs bloquantes** : Si l'API retourne une erreur (HTTP 500), c'est un problème bloquant : - -```typescript -try { - const result = await bitcoinSignetApiHelper.anchorHashViaApi(hash, options); - // Le txid est obligatoire et doit être présent - if (!result.txid) { - throw new Error("Txid manquant (BLOQUANT)"); - } -} catch (error) { - // Toute erreur est bloquante, le document ne peut pas être créé - throw error; -} -``` - -### Callback (optionnel) - -Le `callback_url` est optionnel et appelé après la réponse HTTP. Il peut être utilisé pour : - -- Mise à jour asynchrone de la base de données -- Notification d'autres services -- Logging supplémentaire - -**Note** : Le callback n'est pas nécessaire car toutes les informations sont déjà dans la réponse initiale. - -## Dépannage - -### Le service ne démarre pas - -1. Vérifier que le répertoire existe : `/srv/4NK/certificator.4nkweb.com/lecoffre-anchor-api` -2. Vérifier que les dépendances sont installées : `npm install` -3. Vérifier les logs : `journalctl --user -u anchor-api.service -n 50` - -### Erreur "Bitcoin node not available" - -1. Vérifier que le nœud Bitcoin est actif : `systemctl --user status signet.service` -2. Vérifier que le cookie existe : `ls -la /home/ncantu/.bitcoin/signet/.cookie` -3. Vérifier que le port RPC est accessible : `ss -tlnp | grep 38332` -4. Vérifier la configuration dans `.env` : `BITCOIN_COOKIE_PATH` doit pointer vers le bon chemin - -### Erreur IPv6 "connect EADDRNOTAVAIL ::1:38332" - -**Date** : 2026-01-25 - -**Problème** : L'API d'ancrage essaie de se connecter au nœud Bitcoin via IPv6 (`::1:38332`) mais le nœud n'écoute que sur IPv4. - -**Solution** : Forcer IPv4 dans la configuration de l'API d'ancrage : - -**Fichier** : `/home/ncantu/Bureau/code/bitcoin/api-anchorage/.env` - -```bash -# Utiliser explicitement IPv4 -BITCOIN_RPC_HOST=127.0.0.1 -BITCOIN_ZMQ_HOST=127.0.0.1 -BITCOIN_ZMQ_PORT=38332 -``` - -**Vérification** : Vérifier que le nœud Bitcoin écoute sur IPv4 : - -```bash -ss -tlnp | grep 38332 -ss -tlnp | grep 18443 -``` - -### Erreur "too-long-mempool-chain" - -**Date** : 2026-01-25 - -**Problème** : Le mempool Bitcoin Signet est saturé avec plus de 25 transactions non confirmées en chaîne (limite de sécurité Bitcoin). - -**Solution** : Le script détecte cette erreur et s'arrête immédiatement avec un message clair. L'utilisateur doit attendre que les transactions soient confirmées avant de relancer. - -**Vérification** : - -```bash -bitcoin-cli -signet getmempoolinfo -bitcoin-cli -signet getrawmempool -``` - -### Erreurs de timeout (504, ESOCKETTIMEDOUT, client timeout) - -**Date** : 2026-01-26 à 2026-01-28 - -**Problèmes** : - -- **504 Gateway Timeout** : Timeout du proxy Nginx (probablement `proxy_read_timeout` trop court) -- **ESOCKETTIMEDOUT** : Timeout socket lors de la connexion au nœud Bitcoin -- **Client timeout (AbortController)** : Timeout client de 5 minutes dépassé - -**Solutions appliquées** : - -1. **Détection des timeouts** : Tous les timeouts (504, 500 avec timeout socket, client timeout) sont détectés comme retryable -2. **Retry automatique** : 3 tentatives avec délai de 30 secondes entre chaque tentative -3. **Timeout client** : Timeout explicite de 5 minutes avec `AbortController` (aligné avec `proxy_read_timeout` Nginx) -4. **Logs améliorés** : Logs détaillés pour chaque tentative, timeout, retry et succès - -**Configuration Nginx recommandée** : - -```nginx -location / { - proxy_pass http://192.168.1.103:3004; - proxy_connect_timeout 60s; - proxy_send_timeout 600s; # 10 minutes - proxy_read_timeout 600s; # 10 minutes -} -``` - -**Logs à surveiller** : - -- `📡 [BitcoinSignetService] Anchor API - Tentative X/3 pour hash ...` -- `⏱️ [BitcoinSignetService] Anchor API - Timeout client déclenché après 300000ms` -- `⚠️ [BitcoinSignetService] Anchor API timeout (504/500/client) - Tentative X/3, attente 30000ms avant retry...` -- `✅ [BitcoinSignetService] Anchor API - Ancrage réussi pour hash ...` - -### Erreur "Cannot find module './config/logger'" - -Le fichier `src/config/logger.ts` doit être créé manuellement : - -```typescript -const logger = { - info: (message: string) => console.log('[INFO] ' + new Date().toISOString() + ' - ' + message), - error: (message: string) => console.error('[ERROR] ' + new Date().toISOString() + ' - ' + message), - warn: (message: string) => console.warn('[WARN] ' + new Date().toISOString() + ' - ' + message), - debug: (message: string) => console.debug('[DEBUG] ' + new Date().toISOString() + ' - ' + message), -}; - -export default logger; -``` - -### Erreur "Anchor API returned invalid txid" - -**Cause** : Le code s'attend à recevoir un txid Bitcoin immédiatement, mais l'API retourne un UUID (job ID). - -**Solution** : Le code a été corrigé pour accepter un UUID et récupérer le txid via le status endpoint ou le callback. - -### Erreur "Anchor API transaction not ready yet" - -**Cause** : La transaction est en cours de traitement (status "pending" sans txid). - -**Solution** : Attendre le callback. Ne pas relancer l'ancrage. Le callback mettra à jour le txid automatiquement. - -### Transactions non traitées - -**Cause** : La file d'attente est en mémoire. Si le service redémarre, les transactions en cours sont perdues. - -**Solution actuelle** : Les transactions sont recréées automatiquement lors du traitement des fichiers. - -**Amélioration future** : Migrer vers Redis ou une base de données pour la persistance. - -## Configuration dans le backend - -L'URL de l'API d'ancrage est configurée dans la base de données via `system_configuration` : - -- **Clé** : `ANCHORE_API_URL` -- **Valeur** : `https://anchorage.certificator.4nkweb.com` -- **Catégorie** : `INTEGRATION_BITCOIN` - -- **Clé** : `ANCHORE_API_KEY` -- **Valeur** : `` -- **Catégorie** : `INTEGRATION_BITCOIN` -- **Sensible** : `true` - -## Historique - -### 2026-01-23 : Alignement du backend sur le fonctionnement synchrone de l'API - -**Problème** : Le backend s'attendait à recevoir un UUID avec status "pending" et HTTP 202, mais l'API fonctionne de manière synchrone et retourne directement le txid avec HTTP 200. - -**Corrections** : - -1. Alignement du backend sur le fonctionnement réel de l'API (synchrone) -2. Attente de HTTP 200 (OK) au lieu de HTTP 202 (Accepted) -3. Récupération directe du `txid` dans la réponse initiale -4. Suppression de la méthode `getStatus` devenue inutile -5. Suppression de toute logique de polling ou d'attente -6. Documentation mise à jour pour refléter le fonctionnement synchrone - -**Résultat** : Le backend reçoit directement toutes les informations (txid, confirmations, block_height) dans la réponse initiale, sans besoin de status endpoint ou de polling. - -### 2026-01-23 : Restauration de l'API d'ancrage - -**Problème** : L'API d'ancrage était inactive et le service systemd pointait vers un répertoire inexistant (`/home/ank/dev/lecoffre-anchor-api`). - -**Solution** : - -1. Clonage du code depuis la branche `v2-remote` du dépôt v1 dans `/srv/4NK/certificator.4nkweb.com/lecoffre-anchor-api` -2. Installation des dépendances npm -3. Création du fichier `logger.ts` manquant -4. Création du fichier `.env` avec la configuration Bitcoin -5. Mise à jour du service systemd pour pointer vers le nouveau répertoire -6. Correction du chemin du cookie Bitcoin (`/home/ncantu/.bitcoin/signet/.cookie`) - -**Résultat** : L'API d'ancrage est opérationnelle et connectée au nœud Bitcoin Signet. - -### 2026-01-27 : Mise à jour URL API d'ancrage - -**Problème** : L'URL de l'API d'ancrage a été changée de `https://prod.signet.4nkweb.com` vers `https://anchorage.certificator.4nkweb.com`. - -**Solution** : - -1. Mise à jour de `ANCHORE_API_URL` dans `env-full--for-bdd-injection.txt` -2. Mise à jour dans `system_configuration` via `setSettings` -3. Port interne : `3004` (configuré dans `.env` et systemd) -4. Nginx : Route `anchorage.certificator.4nkweb.com` → `192.168.1.103:3004` - -**Vérification** : - -```bash -cd lecoffre-back-main -npm run anchorage:test-anchor-api -``` - -**Problème 502 Bad Gateway** : Si vous rencontrez des erreurs **502 Bad Gateway** : - -- Vérifier l'état du service : `systemctl --user status anchor-api.service` -- Vérifier que le service écoute sur le port 3004 : `ss -tlnp | grep 3004` -- Tester depuis le backend : `curl -s http://localhost:3004/health` -- Configuration Nginx : Route `anchorage.certificator.4nkweb.com` → `192.168.1.103:3004` - ---- - -## API Annuaire - -### Vue d'Ensemble - -L'API Annuaire est appelée avec une authentification **Basic Auth** (login/password) distincte de l'API IdNot existante (clé API). Les recherches utilisent des **appels API au moment de la demande** (pas de tables `annu_*`). - -### Configuration - -**Variables d'environnement** (via `system_configuration`) : - -- `ANNUAIRE_API_LOGIN` : Login Basic Auth -- `ANNUAIRE_API_PASSWORD` : Password Basic Auth (sensible) -- `ANNUAIRE_API_BASE_URL` : URL de base de l'API Annuaire - -**Lecture directe depuis la BDD** : les helpers de recherche (IdNot/Annuaire) lisent ces configurations depuis `system_configuration` (via `ConfigLoader`), sans passer par `.env`. - -### Préparation configuration Genapi (push iNot) - -Variables prévues pour injection depuis `env-full--for-bdd-injection.txt` vers `system_configuration` : - -- `GENAPI_AUTH_URL` : URL OAuth token Genapi (`.../Authorization/OAuth/Token`) -- `GENAPI_API_BASE_URL` : URL de base API Genapi (`.../actes`) -- `GENAPI_TENANT_ID` : Tenant Genapi (header `tenantId`) -- `GENAPI_CLIENT_ID` : Client ID OAuth Genapi -- `GENAPI_CLIENT_SECRET` : Client secret OAuth Genapi (sensible) -- `GENAPI_SCOPE` : Scopes OAuth Genapi (ex: `sas:read sas:write`) - -### Backend endpoint for Genapi push - -- `POST /api/v1/notary/documents/:uid/push-inot` - -Execution flow: - -1. Validate document status (`VALIDATED` required). -2. Resolve dossier in Genapi by folder `numero` using `/api/v1/Dossiers/Search`. -3. Create an eDocument in Genapi (`/api/v1/Clients/{idParent}/Edocument` or `/api/v1/Partenaires/{idParent}/Edocument`). -4. Upload binary payload to `/api/v1/Documents/{id}/Data`. - -Unavailable response: - -- If Genapi endpoint is unreachable, backend returns `GENAPI_API_UNAVAILABLE`. -- Frontend displays a dedicated message for this case ("API Genapi indisponible"). - -Backend mutualization: - -- Shared helper for same auth protocol integrations: `TenantOAuthHttpClient` -- Scope: external backend calls using OAuth client-credentials + `tenantId` header. -- Frontend unavailable error mappings are centralized in `front/Utils/errorMessages/constants/externalServiceErrorPatterns.ts`. -- External integration registry is centralized in backend: `lecoffre-back-main/src/common/utils/externalServiceErrors.ts`. -- Standardized backend unavailable codes applied to multiple integrations: - - `GENAPI_API_UNAVAILABLE` - - `ANNUAIRE_API_UNAVAILABLE` - - `ANCHORAGE_API_UNAVAILABLE` - - `IDNOT_API_UNAVAILABLE` - - `OPENID_API_UNAVAILABLE` - - `IPFS_API_UNAVAILABLE` - - `MAILCHIMP_API_UNAVAILABLE` -- Common controller mapper for integration errors: - - `lecoffre-back-main/src/common/utils/controllerExternalErrorHelper.ts` - - Shared integration->HTTP mapping table moved to: - - `lecoffre-back-main/src/common/utils/externalIntegrationHttpMappings.ts` - - Variant wrapper available: - - `handleControllerErrorWithExternalIntegration(...)` - - Domain factory utility available: - - `getExternalIntegrationMappings("mailchimp" | "idnot" | "ipfs" | "openid" | "anchorage" | "genapi" | "stripe" | "bitcoin_signet" | "idnot_annuaire" | "all")` - - Subset mapping utilities available: - - `EXTERNAL_INTEGRATION_HTTP_MAPPINGS_MAILCHIMP_ONLY` - - `EXTERNAL_INTEGRATION_HTTP_MAPPINGS_IDNOT_ONLY` - - `EXTERNAL_INTEGRATION_HTTP_MAPPINGS_IPFS_ONLY` - - `EXTERNAL_INTEGRATION_HTTP_MAPPINGS_OPENID_ONLY` - - `EXTERNAL_INTEGRATION_HTTP_MAPPINGS_ANCHORAGE_ONLY` - - `EXTERNAL_INTEGRATION_HTTP_MAPPINGS_GENAPI_ONLY` - - `EXTERNAL_INTEGRATION_HTTP_MAPPINGS_STRIPE_ONLY` - - `EXTERNAL_INTEGRATION_HTTP_MAPPINGS_BITCOIN_SIGNET_ONLY` - - `EXTERNAL_INTEGRATION_HTTP_MAPPINGS_IDNOT_ANNUAIRE` - - Mapping response messages are now derived from `buildExternalServiceUnavailableCode(...)` to keep integration error messages homogeneous. - - Already wired in: - - `notary/DocumentsController` - - `idnot/UserController` - - `idnot/OfficeController` - - `notary/MailchimpController` - - `public/IncidentReportController` - - `notary/FolderThirdPartiesController` (resend-code flow) - - `super-admin/HealthTestsController` - - `admin/SyncController` - - `public/ThirdPartyAuthController` (`request-code`) - - `notary/FolderSharingController` (`searchNotaries`) - - `notary/DocumentAnchorsController` (`anchorDocument`, `anchorFolderDocuments`, `verifyPendingAnchors`) - - `notary/DocumentsController` (`push-inot`, `resend-request`, `resend-request-batch`) - - `notary/FolderThirdPartiesController` (`resend-code` with `mailchimp` domain mapping) - - `idnot/UserController` - - `idnot/OfficeController` - - Helper-level fallback utility available for non-controller flows: - - `handleErrorWithExternalIntegrationFallback(...)` - - Service-level propagation utility available for business layers without HTTP response object: - - `toExternalServiceUnavailableError(...)` - - `isExternalServiceUnavailableError(...)` - - `buildExternalOperationName(integration, operation)` (`integration.operation`) - - `wrapExternalCall(integration, operation, fn)` - - `wrapExternalCallWithLog(integration, operation, fn, logError)` - - `createExternalIntegrationCallLogger(integrationLabel, context, level?, extraMetadata?)` - - `buildStandardExternalLogMetadata({ targetEmail?, subscriptionId?, requestPath?, resourceUid? })` - - `mergeExternalLogMetadata(...metadataItems)` - - `buildStripeSubscriptionMetadata({ requestPath, subscriptionType?, frequency?, subscriptionId? })` - - `buildIdNotRequestMetadata({ targetEmail?, requestPath })` - - `buildMailchimpRequestMetadata({ targetEmail, requestPath })` - - `buildOpenIdRequestMetadata({ requestPath, resourceUid? })` - - `buildIpfsReadMetadata({ requestPath, resourceUid? })` - - `buildIpfsWriteMetadata({ requestPath, resourceUid? })` - - `buildOpenIdTokenMetadata({ requestPath, resourceUid? })` - - `buildOpenIdJwksMetadata({ requestPath, resourceUid? })` - - `buildOpenIdUserInfoMetadata({ requestPath, resourceUid? })` - - `buildOpenIdLogoutMetadata({ requestPath, resourceUid? })` - - `buildIpfsPinMetadata({ requestPath, resourceUid? })` - - `buildIpfsUnpinMetadata({ requestPath, resourceUid? })` - - `buildIpfsGatewayReadMetadata({ requestPath, resourceUid? })` - - `buildAnchorageRequestMetadata({ requestPath, resourceUid? })` - - `buildGenapiRequestMetadata({ requestPath, resourceUid? })` - - `createIpfsGatewayReadLogger(context, requestPath)` - - `createIpfsGatewayReadLoggerFromPath(context, pathOrUrl)` - - `createIpfsPinLogger(context, requestPath, resourceUid?)` - - `createIpfsPinLoggerFromFileName(context, fileName, requestPath?)` - - `createIpfsUnpinLogger(context, requestPath, resourceUid?)` - - `createIpfsUnpinLoggerFromHash(context, hashToUnpin)` - - `createOpenIdUserInfoLogger(context, requestPath, resourceUid?)` - - `createOpenIdLogoutLogger(context, requestPath, resourceUid?)` - - `createOpenIdTokenLogger(context, requestPath, resourceUid?)` - - `createOpenIdJwksLogger(context, requestPath, resourceUid?)` - - `createOpenIdUserInfoLoggerFromPath(context, pathOrUrl, resourceUid?)` - - `createOpenIdLogoutLoggerFromPath(context, pathOrUrl, resourceUid?)` - - `createOpenIdTokenLoggerFromPath(context, pathOrUrl, resourceUid?)` - - `createOpenIdJwksLoggerFromPath(context, pathOrUrl, resourceUid?)` - - `createIpfsExternalCallLogger(context)` - - `createMailchimpRequestLoggerFromPath(context, pathOrUrl, targetEmail)` - - `createIdNotRequestLoggerFromPath(context, pathOrUrl, targetEmail?, level?)` - - `createStripeSubscriptionLogger(context, { requestPath, subscriptionType?, frequency?, subscriptionId?, level? })` - - `createStripeSubscriptionLoggerFromPath(context, { pathOrUrl, subscriptionType?, frequency?, subscriptionId?, level? })` - - `createAnchorageRequestLogger(context, requestPath, resourceUid?, level?)` - - `createAnchorageRequestLoggerFromPath(context, pathOrUrl, resourceUid?, level?)` - - `createGenapiRequestLogger(context, requestPath, resourceUid?, level?)` - - `createGenapiRequestLoggerFromPath(context, pathOrUrl, resourceUid?, level?)` - - `createMailchimpExternalCallLogger(context, extraMetadata?)` - - `createIdNotExternalCallLogger(context, level?, extraMetadata?)` - - `createStripeExternalCallLogger(context, level?, extraMetadata?)` - - `createAnchorageExternalCallLogger(context, level?, extraMetadata?)` - - `createGenapiExternalCallLogger(context, level?, extraMetadata?)` - - `lecoffre-back-main/src/common/utils/externalServiceErrorPropagation.ts` - - `lecoffre-back-main/src/common/utils/externalServiceCallLoggers.ts` - - Helper-level fallback already wired in: - - `notary/helpers/FolderThirdPartiesResendCodeHelper.ts` - - `public/helpers/ThirdPartyAuthHelper.ts` - - `notary/helpers/FolderSharingResendHelper.ts` - - Service-level propagation already wired in: - - `services/notary/DocumentsService/DocumentsService.ts` - - `services/notary/OfficesService/OfficesRibService.ts` - - `services/common/FileProcessingService/helpers/FileProcessingStepHelper.ts` - - `services/common/BitcoinSignetService/BitcoinSignetService.ts` - - `services/common/MailchimpService/MailchimpService.ts` - - `services/notary/OfficesService/helpers/OfficesRibProcessingHelper.ts` - - `services/common/FileProcessingService/helpers/FileProcessingViewHelper.ts` - - `wrapExternalCall(...)` usage extended to secondary flows where failure is explicitly external: - - IPFS read/view/download helpers - - Mailchimp list subscription API call - - `wrapExternalCallWithLog(...)` usage extended where log+rethrow was duplicated: - - Bitcoin RPC anchorage flows - - Secondary Mailchimp resend flows - - Secondary IPFS/watermark processing flows - - Secondary IPFS view/download/watermark read helper flows - - Secondary RIB IPFS low-level download flow - - Mailchimp list subscription flow (`addToMailchimpList`) - - Annuaire/IdNot secondary email-search API flow (`searchByEmail`) - - Stripe checkout-session creation flow (`createCheckoutSession`) - - Standardized metadata keys used in external log payloads: - - `targetEmail` - - `subscriptionId` - - `requestPath` - - `resourceUid` - - Inline callback metadata objects replaced by dedicated metadata builders on active IDNOT/MAILCHIMP/STRIPE wrappers. - - Existing IPFS gateway-read wrappers aligned with `buildIpfsGatewayReadMetadata(...)`. - - Existing IPFS upload wrappers aligned with `buildIpfsPinMetadata(...)`. - - Remaining OPENID/IPFS wrapper logger lambdas replaced by dedicated logger wrappers. - - Existing IPFS unpin flow migrated to wrapper + dedicated unpin logger (`IpfsService.unpinFile`). - - Existing IPFS gateway-read logger usage migrated to `createIpfsGatewayReadLoggerFromPath(...)` for URL/path normalization. - - Existing IPFS unpin logger usage migrated to `createIpfsUnpinLoggerFromHash(...)` for hash-only caller contract. - - Existing IPFS pin logger usage migrated to `createIpfsPinLoggerFromFileName(...)` for default pin endpoint encapsulation. - - OPENID userinfo/logout flows are now available in `OpenIdService` with dedicated logger wrappers. - - Existing OPENID logger usage migrated to `fromPath` wrappers for normalized endpoint metadata. - - `OpenIdService` implements `common/system/OpenIdInterface.ts` (`getOpenIdConfig`, `getSigningKey`, `verifyIdToken`, `getUserInfo`, `notifyLogout`). - - Existing OPENID calls aligned with dedicated metadata builders for discovery/token and JWKS endpoints. - - Existing Mailchimp and Annuaire/IdNot flows aligned with `fromPath` wrappers for endpoint normalization: - - `services/common/MailchimpService/MailchimpService.ts` (`addToMailchimpList`) - - `services/common/IdNotDirectoryService/IdNotDirectoryService.ts` (`searchByEmail`) - - Existing Stripe checkout-session flow aligned with `createStripeSubscriptionLoggerFromPath(...)` for endpoint normalization: - - `services/common/StripeService/StripeService.ts` (`createCheckoutSession`) - - Existing Genapi HTTP paths aligned with `createGenapiRequestLoggerFromPath(...)`: - - `services/common/GenapiService/GenapiService.ts` (`findDossierIdByFolderNumber`, `createEdocument`, `uploadDocumentData`) - - Existing Anchorage verify endpoint aligned with `createAnchorageRequestLoggerFromPath(...)`: - - `services/common/BitcoinSignetService/helpers/BitcoinSignetApiHelper.ts` (`verifyHashViaApi`) - -### Endpoints API - -**Offices** : `GET /api/pp/v2/entites?type=office&page={page}&size={size}&key={IDNOT_API_KEY}` -**Personnes** : `GET /api/pp/v2/personnes?page={page}&size={size}&key={IDNOT_API_KEY}` -**Rattachements** : `GET /api/pp/v2/personnes/{idnot}/rattachements?page={page}&size=100&key={IDNOT_API_KEY}` - -**Notes** : - -- Auth : la clé API se passe en query (puis fallback header x-api-key) -- Pagination : utiliser page>=1 (ou 0 selon endpoint) et size=100 -- Erreurs : 403 accessDenied → essayer fallback /entites sans type et filtrer côté client - -### Tables - -Les tables historiques `annu_*` ne sont plus utilisées par l’application. - -### Synchronisation - -Les synchronisations batch (IdNot/Annuaire) ne sont plus exécutées via des scripts dédiés. La recherche et la résolution de confrères reposent sur des **appels API** (IdNot/Annuaire) au moment de la demande, complétés par la base du site (users/contacts/offices) et les partages (`folder_sharing`). - -### Troubleshooting - -**Erreur : Missing ANNUAIRE_API_LOGIN or ANNUAIRE_API_PASSWORD** - -- Solution : Injecter les clés dans `system_configuration` via le mécanisme standard d’injection (fichier `.secrets//env-full--for-bdd-injection.txt` puis déploiement). - -**Erreur : HTTP 401 Unauthorized** - -- Cause : Login ou mot de passe incorrect -- Solution : Vérifier les credentials en base et mettre à jour si nécessaire - -**Erreur : HTTP 403 Forbidden** - -- Cause : Le compte n'a pas accès à l'API Annuaire -- Solution : Contacter le support ID.NOT pour vérifier les droits du compte - -### Recherche confrères (appel direct API annuaire) - -La recherche de notaires pour inviter un confrère au partage de dossier utilise **uniquement l’API Annuaire** (pas de base du site ni de confrères déjà invités). - -**Endpoint exposé** : `GET /api/v1/notary/search/notaries?q={query}` - -- **Authentification** : session notaire (authHandler, permissionContextInjector). -- **Paramètre** : `q` (string, min. 2 caractères) – nom, prénom, email ou nom d’office. -- **Réponse** : `{ results: Array<...>, annuaireConfigured: boolean, message?: string }`. `results` : tableau d’objets `{ idNot, email, firstName, lastName, officeName, crpcen, isPrimary, displayName, hasActiveLicense?, phone? }`. Si l’annuaire n’est pas configuré : **503 Service Unavailable** avec le même format (`annuaireConfigured: false`, `message` explicatif, `results: []`). Pas de 200 avec résultats vides. - -**Comportement** : - -- Si `ANNUAIRE_API_BASE_URL` et `IDNOT_API_KEY` sont renseignés en base (`system_configuration`) : appel API Annuaire V2 avec **pagination complète** (pages de 50 jusqu’à épuisement) : - - **PB lookup** : `GET {ANNUAIRE_API_BASE_URL}/api/v2/directory/lookup?nomNotaire={query}&page=&size=50&key=...` (toutes les pages). - - **Fallback PP** : `GET {ANNUAIRE_API_BASE_URL}/api/pp/v2/personnes?prenom=...&nomUsuel=...&page=&size=50&key=...` (toutes les pages). -- Si l’annuaire n’est pas configuré : réponse `{ results: [], annuaireConfigured: false, message }` ; le front affiche le message d’alerte (pas de fallback legacy IdNot). - -**Fichiers** : - -- `lecoffre-back-main/src/services/common/IdNotDirectoryService/IdNotDirectoryService.ts` : `searchNotaries()` retourne `SearchNotariesResponse` ; `getAnnuaireCredentials()`. -- `lecoffre-back-main/src/services/common/IdNotDirectoryService/helpers/IdNotDirectoryApiSearchHelper.ts` : API Annuaire V2 (lookup + PP, pagination complète). -- `lecoffre-back-main/src/app/api/notary/helpers/FolderSharingSearchHelper.ts` : renvoie `{ results, annuaireConfigured, message? }`. - -**Configuration** : `ANNUAIRE_API_BASE_URL`, `IDNOT_API_KEY` en base (`system_configuration`). Obligatoires pour que la recherche renvoie des résultats. - -### Notaire invité : email et IdNot en base (folder_sharing) - -Après la création d'un partage, **seul l'email en base** associé au notaire invité est utilisé pour les envois (invitations, relances, documents). Aucune relecture de l'email côté IdNot. - -- **Un seul champ email** : `invited_notary_email` = email de préférence pour les envois. À l'invitation il est en général pré-rempli depuis IdNot (annuaire) ; il peut être modifié avant enregistrement. **L'email IdNot et l'email de préférence sont donc le même champ en base** pour le notaire invité. -- **Accès aux dossiers invités** : croisement sur l'**IdNot** (`invited_notary_idnot`). Quand l'utilisateur connecté a un IdNot, seuls les partages dont `invited_notary_idnot` correspond sont pris en compte. L'email en base n'est pas utilisé pour ce croisement. -- **Profils avec IdNot** : à l'invitation, le partage enregistre `invited_notary_idnot`. Les notaires invités (y compris sans seat) ont en base un identifiant IdNot : sur chaque partage (`folder_sharing.invited_notary_idnot`), et après première connexion via IdNot sur le profil utilisateur (`users.idNot`). - -Voir aussi : `docs/ARCHITECTURE.md` § Partage Inter-Études, `docs/README.md` (consolidation opérationnelle). - -### Architecture - -Les synchronisations batch historiques (et les tables `annu_*` associées) ne sont plus utilisées par l’application. Les recherches de confrères et les résolutions (ex: par email) s’effectuent via des **appels API** (IdNot / Annuaire) au moment de la demande, avec complément de la base du site et des partages (`folder_sharing`). - -#### Déploiement - -Déploiement standard du backend via `deploy/scripts/build-and-deploy.sh `. Aucune migration spécifique de tables `annu_*` n’est requise. Les appels API Annuaire/IdNot nécessitent que les clés correspondantes soient présentes dans `system_configuration` (injection via `.secrets//env-full--for-bdd-injection.txt` puis déploiement). - -### Sécurité - -**Authentification Basic Auth** : - -```http -Authorization: Basic -``` - -**Stockage des credentials** : - -- Login : Stocké en clair dans `system_configuration` (`is_sensitive: false`) -- Password : Marqué comme sensible dans `system_configuration` (`is_sensitive: true`) -- Affichage : Le mot de passe est masqué (`***`) dans l'interface super-admin - -**Logs** : Les logs ne contiennent **jamais** les credentials. Seuls les URLs (sans query params) et les codes HTTP sont loggés. - -### Tests et Vérification - -Les tests de connectivité et les vérifications se font via les parcours applicatifs qui déclenchent des appels API (IdNot / Annuaire), ou via les cartes de tests *live* de la page super-admin santé. - -### Troubleshooting API Annuaire - -**Erreur : Missing ANNUAIRE_API_LOGIN or ANNUAIRE_API_PASSWORD** : - -**Cause** : Les credentials ne sont pas en base de données. - -**Solution** : - -```bash -deploy/scripts/build-and-deploy.sh test -``` - -**Erreur : HTTP 401 Unauthorized** : - -**Cause** : Login ou mot de passe incorrect. - -**Solution** : - -1. Vérifier les credentials en base : - -```sql -SELECT key, value FROM system_configuration WHERE key LIKE 'ANNUAIRE_API_%'; -``` - -2. Mettre à jour si nécessaire via l'interface super-admin ou le script. - -**Erreur : HTTP 403 Forbidden** : - -**Cause** : Le compte n'a pas accès à l'API Annuaire. - -**Solution** : Contacter le support ID.NOT pour vérifier les droits du compte. - -**Aucune donnée synchronisée** : - -**Cause** : Les pages retournent 0 résultats ou l'API retourne un format inattendu. - -**Solution** : - -1. Vérifier les logs backend (service API) pour les erreurs IdNot/Annuaire -2. Regarder les "sample" logs (premier élément de chaque type) -3. Adapter le parsing si nécessaire dans les helpers de recherche IdNot/Annuaire - ---- - -## Rappels Automatiques Documents - -**Statut** : ✅ 100% | **Version** : 2.0.0 - -### Architecture Backend - -**Service** : `DocumentRemindersService` (~350 lignes) -**Fichier** : `lecoffre-back-main/src/services/notary/DocumentRemindersService/DocumentRemindersService.ts` - -**Repository** : `DocumentRemindersConfigRepository` - -### Endpoints - -**6 endpoints** (config CRUD + trigger manuel) : - -- `GET /api/v1/notary/document-types/:documentTypeUid/reminders/config` : Récupérer configuration pour un type de document -- `GET /api/v1/notary/offices/:officeUid/reminders/config` : Liste toutes les configurations d'un office -- `POST /api/v1/notary/document-types/:documentTypeUid/reminders/config` : Créer configuration -- `PUT /api/v1/notary/document-types/:documentTypeUid/reminders/config` : Modifier configuration -- `DELETE /api/v1/notary/document-types/:documentTypeUid/reminders/config` : Supprimer configuration -- `POST /api/v1/notary/reminders/send` : Déclencher manuellement (⚠️ DÉSACTIVÉ - relances automatiques désactivées) - -**Cron** : `sendDocumentReminders()` quotidien 9h (⚠️ DÉSACTIVÉ - relances automatiques totalement désactivées, voir `CronService.ts` ligne 144-168) - -### Configuration Base de Données - -**Table** : `document_reminders_config` - -**Champs** : - -- `document_type_uid` : Type de document concerné -- `office_uid` : Office concerné -- `reminder_intervals` : JSON array d'intervalles en jours (ex: `[7, 14, 21]`) -- `reminder_message` : Message personnalisé optionnel -- `auto_send` : Envoi automatique activé (défaut: true) -- `is_active` : Configuration active/inactive - -### Frontend - -**Pages** : - -- `/reminders/config.tsx` : Liste + toggle actif/inactif -- `/folders/[uid]/documents-reminder-history` : Historique rappels - -**API** : `DocumentReminders` (~130 lignes) - -**Support destinataires** : - -- Clients : Relance via `depositor.uid` -- Tiers : Relance via `third_party_depositor.uid` (v2.0.0) -- Notaires invités : Relance via `shared_to_office.uid` (v2.0.1) - -### Fonctionnalités - -- **Intervalles configurables** : Ex: [7, 14, 21] jours après demande -- **Message personnalisé** : Optionnel par type document + office -- **Configuration par type document + office** : Granularité fine -- **Activation/désactivation** : Via champ `isActive` dans la configuration (pas d'endpoint toggle dédié) -- **Trigger manuel** : ⚠️ DÉSACTIVÉ - L'endpoint `POST /api/v1/notary/reminders/send` est désactivé. Utiliser `/api/v1/notary/customers/:uid/send_reminder` pour les relances manuelles -- **Évite doublons** : 1 rappel/jour maximum par document -- **Support notaires invités** : Relance possible pour les confrères via partage inter-études (v2.0.1) - -### Configuration Système - -**Variable** : `CRON_SEND_REMINDERS_ENABLED` (dans `system_configuration`) -**Gestion** : Désactivable via `/super-admin/system-config` - ---- - -## Opérations Batch - -**Statut** : ✅ 100% | **Version** : 2.0.0 - -### Certificat Unique Dossier - -**Service** : `FolderAnchorCertificateService` -**Fonctionnalité** : PDF unique pour dossier complet - -**Inclut** : - -- Liste tous documents ancrés -- Merkle root dossier -- Hash ZIP complet -- Informations blockchain - -### Envoi Documents par Destinataire - -**Backend** : - -- **Endpoint** : `POST /api/v1/notary/documents_notary/send` -- **Body** : `recipientUid`, `folderUid`, `files` (FormData) -- **Middleware** : `multer().array("files", 20)` (20 fichiers max) -- **Logique** : Un destinataire par appel ; le frontend boucle sur les destinataires sélectionnés et agrège les résultats - -**Note** : Route exclue du middleware multer global (voir [README.md](./README.md#consolidation-operationnelle-ex-operationsmd)) - -### Filtres Avancés - -**Backend** : - -- **Service** : `FolderSearchService` -- **Controller** : `FolderSearchController` -- **Endpoint** : `POST /api/v1/notary/folders/search` - -**Filtres disponibles** : - -- Numéro dossier -- Nom dossier -- Type acte -- Office -- Statut (LIVE, ARCHIVED, DELETED) -- Date création -- Collaborateurs - -### Téléchargement Multiple - -**Backend** : - -- **Service** : `ZipService` -- **Endpoint** : `POST /api/v1/notary/files/download-multiple` - -**Fonctionnalités** : - -- Sélection multiple fichiers -- Conversion automatique en PDF si nécessaire -- Ajout filigrane "lecoffre.io" (préfixe "acpl_") -- Ajout métadonnées d'ancrage dans les PDF -- Inclut certificats d'ancrage si disponibles -- Utilise versions filigrannées existantes - -**Fichier** : `lecoffre-back-main/src/services/common/ZipService/ZipService.ts` - -### Certificat Agrégé Documents - -**Statut** : ✅ 100% | **Version** : 2.1.0 - -**Service** : `AggregatedCertificateService` -**Fichier** : `lecoffre-back-main/src/services/notary/AggregatedCertificateService/AggregatedCertificateService.ts` - -**Fonctionnalités** : - -- Génération d'un certificat PDF unique pour plusieurs documents sélectionnés -- Support des Documents (documents validés) et DocumentsNotary (documents envoyés) -- Ancrage du certificat sur la blockchain Bitcoin Signet -- Upload du certificat sur IPFS -- Inclusion automatique dans les ZIP téléchargés - -**Endpoints** : - -- `POST /api/v1/notary/files/download-aggregated-certificate` : Certificat agrégé pour Documents -- `POST /api/v1/notary/files-notary/download-aggregated-certificate` : Certificat agrégé pour DocumentsNotary - -**Limite** : 100 documents maximum par certificat - ---- - -### Mémo API Annuaire (Qualification) - -**Endpoints** : - -- ID.not Auth: `https://qual-connexion.idnot.fr` (authorize/token) -- Annuaire base: `https://qual-api.notaires.fr/annuaire` -- Recherche personnes (si supporté): `GET {base}/persons?q={query}` avec header `x-api-key` -- Entités (offices): `GET /api/pp/v2/entites?type=office&page={page}&size={size}&key={IDNOT_API_KEY}` -- Fallback offices: `GET /api/pp/v2/entites?key={IDNOT_API_KEY}` → filtrer `typeEntite.name==office` -- Personnes: `GET /api/pp/v2/personnes?page={page}&size={size}&key={IDNOT_API_KEY}` -- Rattachements: `GET /api/pp/v2/personnes/{idnot}/rattachements?page={page}&size=100&key={IDNOT_API_KEY}` - -**Notes** : - -- Auth : la clé API se passe en query (puis fallback header x-api-key) -- Pagination : utiliser page>=1 (ou 0 selon endpoint) et size=100 -- Erreurs : 403 accessDenied → essayer fallback /entites sans type et filtrer côté client -- Sécurité : ne publier aucune valeur de clé - ---- - -### Exemple de Client pour l'API d'Ancrage - -Un exemple complet de client Node.js pour tester l'API d'ancrage est disponible dans le code source. Le client permet de : - -- Vérifier l'état de l'API (`/health`) -- Ancrer un document (`POST /api/anchor/document`) -- Vérifier un ancrage (`POST /api/anchor/verify`) - -**Fonctionnement synchrone** : L'API retourne directement le `txid` dans la réponse (HTTP 200 OK), pas besoin de polling ou de status endpoint. - -**Points importants** : - -- L'API retourne directement le `txid` dans la réponse (fonctionnement synchrone) -- Le code HTTP est **200 OK** (pas 202 Accepted) -- Toutes les informations sont disponibles immédiatement (confirmations, block_height) - -Voir le code source dans `lecoffre-anchor-api/` pour l'exemple complet de client. - -### Mise à jour de l'URL de l'API d'ancrage - -**Date** : 2026-01-27 - -**URL mise à jour** : L'URL de l'API d'ancrage a été changée de `https://prod.signet.4nkweb.com` vers `https://anchorage.certificator.4nkweb.com`. - -**Fichiers à mettre à jour** : - -**1. Fichiers `.secrets//env-full--for-bdd-injection.txt`** : - -Ces fichiers sont utilisés pour injecter les variables dans la base de données via `setSettings`. - -**Emplacements** : - -- `.secrets/test/env-full-test-for-bdd-injection.txt` -- `.secrets/pprod/env-full-pprod-for-bdd-injection.txt` -- `.secrets/prod/env-full-prod-for-bdd-injection.txt` - -**Variable à mettre à jour** : - -```bash -ANCHORE_API_URL=https://anchorage.certificator.4nkweb.com -``` - -**Note** : Ces fichiers ne sont pas versionnés (dans `.gitignore`) et doivent être mis à jour manuellement sur chaque machine de développement. - -**2. Base de données (via `system_configuration`)** : - -Si les fichiers `env-full-*-for-bdd-injection.txt` ont déjà été injectés, la valeur dans la base de données doit être mise à jour : - -**Requête SQL** : - -```sql -UPDATE system_configuration -SET value = 'https://anchorage.certificator.4nkweb.com' -WHERE key = 'ANCHORE_API_URL' - AND category = 'INTEGRATION_BITCOIN'; -``` - -**Ou via le script `setSettings`** : - -```bash -# Après avoir mis à jour le fichier env-full--for-bdd-injection.txt -bash deploy/scripts_v2/remote/set-settings.sh -``` - -**Vérification** : - -```bash -# Depuis le backend -cd lecoffre-back-main -npm run anchorage:test-anchor-api -``` - -Le script doit afficher : - -```text -✅ [TestAnchorAPI] Variables récupérées depuis la BDD - URL: https://anchorage.certificator.4nkweb.com -``` - -**Problème 502 Bad Gateway** : Si vous rencontrez des erreurs **502 Bad Gateway** : - -- Vérifier l'état du service : `systemctl --user status anchor-api.service` -- Vérifier que le service écoute sur le port 3004 : `ss -tlnp | grep 3004` -- Tester depuis le backend : `curl -s http://localhost:3004/health` -- Configuration Nginx : Route `anchorage.certificator.4nkweb.com` → `192.168.1.103:3004` - -**Configuration actuelle** : - -- **Service** : `anchor-api.service` (user service) -- **Port** : `3004` (configuré dans `.env` et systemd) -- **Nginx** : Route `anchorage.certificator.4nkweb.com` → `192.168.1.103:3004` -- **Bitcoin RPC** : `localhost:18443` (Signet) - ---- - -**Dernière mise à jour** : 2026-01-28 (consolidation API.md, API_ANNUAIRE_IMPLEMENTATION.md, API_MEMO.md, ANCHOR_API_ANALYSIS.md, ANCHOR_API_EXAMPLE_CLIENT.md, UPDATE_ANCHOR_API_URL.md) - -## Correctif accès invités notaires (2026-03-02) - -**Problème observé** : - -- Un notaire invité pouvait voir des dossiers partagés (`GUEST_FOLDERS`) mais être déconnecté à l'ouverture d'un dossier lors de `GET /v1/notary/documents`. - -**Cause racine** : - -- Des chemins helper notaire appelaient encore `FolderSharingService.hasAccess(folderUid, officeUid, email)` sans propagation de `idNot`, malgré la centralisation d'identité déjà engagée. - -**Correctif appliqué** : - -- Propagation de `idNot` dans les appels de contrôle d'accès des helpers notaire concernés. -- Extension du même alignement à des helpers customer/middleware/service où la vérification restait email-only. -- Réutilisation de l'extraction d'identité centralisée `buildUserIdentityContext` pour éviter les divergences de signature d'appel. - -**Effet attendu** : - -- Les contrôles d'accès invités notaires utilisent de manière cohérente l'identité `idNot` sur le flux `GUEST_FOLDERS`, sans régression sur les flux non-notaire (où `userIdNot` reste optionnel). +Règle : tout message destiné à l'utilisateur (ex. corps 403) en français et fonctionnel ; constantes et libellés centralisés dans ces modules (ou un module dédié documenté ici). diff --git a/projects/lecoffreio/docs/ARCHITECTURE.md b/projects/lecoffreio/docs/ARCHITECTURE.md deleted file mode 100644 index 17647b2..0000000 --- a/projects/lecoffreio/docs/ARCHITECTURE.md +++ /dev/null @@ -1,1789 +0,0 @@ -# Architecture LeCoffre.io - -**Dernière mise à jour** : 2026-01-28 -**Version** : 2.1.0 - -**Référence unique (checks de déploiement)** : [`docs/DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique`](./DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique) - -Ce document décrit l'architecture complète de LeCoffre.io : infrastructure physique, architecture logicielle, configuration des serveurs, types d'utilisateurs, rôles et permissions, gestion multi-office, et ancrage blockchain. - ---- - -## 📋 Table des Matières - -1. [Architecture Physique](#architecture-physique) -2. [Architecture Logicielle (Monorepo)](#architecture-logicielle-monorepo) -3. [Configuration Initiale des Serveurs](#configuration-initiale-des-serveurs) -4. [Environnements](#environnements) -5. [Services Externes](#services-externes) -6. [Types d'Utilisateurs](#types-dutilisateurs) -7. [Architecture Rôles & Règles](#architecture-rôles--règles) -8. [Matrice des Permissions](#matrice-des-permissions) -9. [JWT Multi-Office](#jwt-multi-office) -10. [Évolution planifiée - Documents unifiés](#évolution-planifiée---documents-unifiés) -11. [Partage Inter-Études](#partage-inter-études) -12. [Gestion Multi-Office](#gestion-multi-office) -13. [Ancrage Blockchain](#ancrage-blockchain) - ---- - -## Architecture Physique - -### Infrastructure Serveurs - -LeCoffre.io est déployé sur une infrastructure **privée** (réseau `192.168.1.x`) avec un **proxy** public en point d'entrée unique. - -#### VM Environnements (Application) - -| Environnement | IP | Domaine | Répertoire | -|---------------|-----|---------|------------| -| **test** | `192.168.1.101` | `test.lecoffreio.4nkweb.com` | `/srv/4NK/test.lecoffreio.4nkweb.com` | -| **pprod** | `192.168.1.102` | `pprod.lecoffreio.4nkweb.com` | `/srv/4NK/pprod.lecoffreio.4nkweb.com` | -| **prod** | `192.168.1.103` | `prod.lecoffreio.4nkweb.com` | `/srv/4NK/prod.lecoffreio.4nkweb.com` | - -**Proxy (point d'entrée)** : - -- IP privée : `192.168.1.100` -- Accès externe : `4nk.myftp.biz` -- Rôle : terminaison SSL + routage Nginx vers les **ports applicatifs** des serveurs `101/102/103`. - -**Services déployés sur chaque serveur environnement (host-native, systemd)** : - -- PostgreSQL (service système) -- ClamAV (service système) -- Backend (systemd) -- Cron (systemd) -- Frontend (systemd) -- Routeur HTTP local (Node) sur le port "service" (systemd) - -**Important :** - -- Il n'y a **pas de Nginx** ni Certbot sur `101/102/103`. -- Les déploiements "v2" sont gérés par `deploy/scripts_v2/` (orchestrés depuis le proxy). - -#### VM Git (Repository) - -**Serveur Git** : `git.4nkweb.com` -**Repository** : `git@git.4nkweb.com:4nk/lecoffreio.git` - -**Rôle** : - -- Stockage du code source (monorepo) -- Versioning Git -- Synchronisation entre machines locales et serveurs de déploiement - -**Workflow** : - -1. Développement local → commit/push vers Git -2. Script de déploiement → pull depuis Git sur VM environnement -3. Build et déploiement sur VM environnement - -#### VM Base de Données - -**PostgreSQL externe** : - -- **Host** : Variable selon environnement (`DATABASE_HOST` dans `.env.`) -- **Port** : **5432** (standard PostgreSQL en mode host-native) -- **Utilisateurs** : `lecoffre-user-{env}` (un par environnement) -- **Bases** : `bdd-{env}` (une par environnement) - -**Accès** : - -- Depuis VM environnements sur l'hôte -- Depuis machines locales via SSH tunnel (optionnel) - -#### VM API Ancrage Bitcoin (Séparée) - -**Serveur** : `dev3.4nkweb.com` -**Host SSH** : `@31.33.24.235` -**Répertoire** : `/home//dev/lecoffre-anchor-api` -**Port** : `3002` -**Health** : `http://31.33.24.235:3002/health` - -**Rôle** : - -- API d'ancrage blockchain Bitcoin Signet -- Service séparé pour isolation et scalabilité -- Appelée par le backend principal pour les ancrages - -### Réseau et Communication - -```text -┌─────────────────────────────────────────────────────────────┐ -│ Machine Dev (externe) │ -│ │ -│ ┌──────────────┐ │ -│ │ Git Client │ ────SSH───> Proxy (4nk.myftp.biz) │ -│ └──────────────┘ │ -│ │ -│ ┌──────────────┐ │ -│ │ Deploy Script│ ────SSH───> Proxy (orchestrateur) │ -│ └──────────────┘ │ -└─────────────────────────────────────────────────────────────┘ - │ - │ SSH (réseau privé) - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ Serveurs Environnements (101/102/103) │ -│ │ -│ ┌──────────────┐ │ -│ │ Git Pull │ <───SSH─── Git (git.4nkweb.com) │ -│ └──────────────┘ │ -│ │ -│ ┌──────────────┐ │ -│ │ systemd │ - backend / cron / frontend / router │ -│ └──────────────┘ │ -│ │ │ -│ │ PostgreSQL (port 5432) │ -│ ▼ │ -│ ┌──────────────┐ │ -│ │ PostgreSQL │ (VM Base de Données externe) │ -│ └──────────────┘ │ -└─────────────────────────────────────────────────────────────┘ -``` - -### Persistance des Données - -| Type | Emplacement | Persistance | -|------|-------------|-------------| -| **Code source** | VM Git (git.4nkweb.com) | ✅ Git repository | -| **Code déployé** | VM Environnement (`/home/debian/sites/...`) | ✅ Host filesystem | -| **Base de données** | VM PostgreSQL externe | ✅ PostgreSQL | -| **Certificats SSL** | VM Environnement (`deploy/nginx/certbot/`) | ✅ Host filesystem (bind mount) | -| **Configuration Nginx** | VM Environnement (`deploy/nginx/nginx-*.conf`) | ✅ Host filesystem (bind mount) | -| **Backups BDD** | VM Environnement (`deploy/backups/`) | ✅ Host filesystem | -| **Backups certificats** | VM Environnement (`deploy/certificats/`) | ✅ Host filesystem | -| **Logs** | VM Environnement (`logs/`) | ✅ Host filesystem (collectés par Promtail) | -| **Build backend/frontend** | VM Environnement | ⚠️ Rebuild à chaque déploiement | -| **node_modules** | VM Environnement (dans le build) | ❌ Non persistant (rebuild) | - ---- - -## Architecture Logicielle (Monorepo) - -### Structure du Monorepo - -LeCoffre.io est organisé en **monorepo** contenant 4 sous-projets principaux : - -```text -lecoffreio/ # Repository global (ce repo) -│ -├── lecoffre-back-main/ # Backend API (Express + TypeScript) -│ ├── src/ # Code source TypeScript -│ │ ├── app/ # Controllers & Middlewares -│ │ │ ├── api/ # Routes API (notary, admin, customer, public) -│ │ │ └── middleware/ # Middlewares (auth, rate limiting, CORS) -│ │ ├── services/ # Business Logic -│ │ │ ├── admin/ # Services admin -│ │ │ ├── common/ # Services communs (IdNot, Annuaire, Stripe, Bitcoin) -│ │ │ ├── customer/ # Services clients -│ │ │ ├── notary/ # Services notaires -│ │ │ └── super-admin/ # Services super-admin -│ │ ├── common/ # Utilities & Repositories -│ │ │ ├── databases/ # Prisma schema & migrations -│ │ │ ├── dtos/ # DTOs validation -│ │ │ ├── emails/ # Services email -│ │ │ ├── repositories/ # Couche d'accès données -│ │ │ └── utils/ # Helpers centralisés -│ │ └── entries/ # Entry points -│ │ └── App.ts # Point d'entrée API -│ ├── dist/ # Build JavaScript (généré) -│ ├── prisma/ # Migrations base de données -│ └── package.json # Dependencies backend -│ -├── lecoffre-front-main/ # Frontend Web (Next.js + React) -│ ├── src/ -│ │ ├── pages/ # Pages Next.js (routes) -│ │ │ ├── folders/ # Routes dossiers -│ │ │ ├── admin/ # Routes admin -│ │ │ ├── subscription/ # Routes abonnements -│ │ │ └── ... -│ │ ├── front/ # Components React -│ │ │ ├── Components/ # UI Components -│ │ │ │ ├── DesignSystem/ # Design system (Header, Footer, etc.) -│ │ │ │ ├── Layouts/ # Layouts (Folder, Subscription, etc.) -│ │ │ │ └── Elements/ # Éléments UI (Buttons, Forms, etc.) -│ │ │ ├── Api/ # API Services (59 services) -│ │ │ ├── Hooks/ # Custom React Hooks -│ │ │ ├── Services/ # Frontend Services -│ │ │ ├── Stores/ # State management (MobX) -│ │ │ └── Utils/ # Utilitaires -│ │ └── proxy.ts # Next.js Proxy (auth) -│ ├── public/ # Assets statiques -│ └── package.json # Dependencies frontend -│ -├── lecoffre-ressources-dev/ # Resources TypeScript partagées -│ ├── src/ -│ │ ├── Admin/ # Types Admin (22 types) -│ │ ├── Notary/ # Types Notary (29 types) -│ │ ├── Customer/ # Types Customer (15 types) -│ │ └── SuperAdmin/ # Types SuperAdmin (20 types) -│ └── package.json # Dependencies resources -│ -├── lecoffre-anchor-api/ # API d'ancrage Bitcoin (serveur séparé) -│ ├── src/ -│ │ ├── config/ # Configuration logger -│ │ ├── controllers/ # Controllers API ancrage -│ │ ├── services/ # Bitcoin + Queue services -│ │ └── types/ # Types ancrage -│ └── package.json # Dependencies anchor API -│ -├── docs/ # Documentation technique -├── todoFix/ # Corrections et fixes documentés -├── IA_agents/ # Règles pour IA -├── deploy/ # Déploiement et outils -│ ├── scripts/ # Scripts de déploiement -│ ├── nginx/ # Configuration Nginx -│ ├── monitoring/ # Configuration Grafana/Loki -│ └── scripts_v2/ # Scripts déploiement host-native -├── logs/ # Logs de déploiement -├── VERSION # Version actuelle -├── CHANGELOG.md # Historique des versions -└── README.md # Documentation principale -``` - -### Rôles des Sous-Projets - -| Sous-Projet | Rôle | Technologies | Port | -|-------------|------|--------------|------| -| **lecoffre-back-main** | API REST, authentification, business logic | Express, TypeScript, Prisma, PostgreSQL | 3001 | -| **lecoffre-front-main** | Interface web responsive, SSR | Next.js 14, React 18, TypeScript, SCSS | 3000 | -| **lecoffre-ressources-dev** | Types TypeScript partagés | TypeScript | - | -| **lecoffre-anchor-api** | API d'ancrage blockchain Bitcoin Signet | Express, TypeScript, Bitcoin RPC | 3002 | - -### Dépendances Inter-Projets - -```text -lecoffre-back-main - └── lecoffre-ressources-dev (types partagés) - -lecoffre-front-main - └── lecoffre-ressources-dev (types partagés) - -lecoffre-anchor-api - └── (indépendant) -``` - -**Build order** : - -1. `lecoffre-ressources-dev` : Compilation TypeScript → 87 fichiers `.js` -2. `lecoffre-back-main` : Utilise les types compilés -3. `lecoffre-front-main` : Utilise les types compilés -4. `lecoffre-anchor-api` : Build indépendant (serveur séparé) - -### Communication Inter-Services - -#### Backend ↔ Frontend - -- **Protocole** : HTTP/HTTPS -- **Format** : JSON (REST API) -- **Authentification** : JWT (Bearer token) -- **Endpoints** : `/api/v1/{notary|admin|customer|public}/...` - -#### Backend ↔ Anchor API - -- **Protocole** : HTTP -- **Format** : JSON -- **Authentification** : API Key (`ANCHORE_API_KEY`) -- **Endpoint** : `http://:3002/anchor` - -#### Backend ↔ Services Externes - -- **PostgreSQL** : Prisma ORM (port 5432 en host-native) -- **IdNot** : OAuth2 (HTTPS) -- **API Annuaire** : Basic Auth (HTTPS) -- **Stripe** : API Key (HTTPS) -- **Pinata (IPFS)** : API Key (HTTPS) -- **Mailchimp** : API Key (HTTPS) -- **OVH SMS** : API Key (HTTPS) -- **ClamAV** : TCP (port 3310) - ---- - -## Configuration Initiale des Serveurs - -### Vue d'ensemble - -Le script `setup-all-servers.sh` permet de configurer automatiquement toutes les machines distantes avec : - -- Configuration de `/etc/hosts` pour la résolution de noms -- Configuration sudo NOPASSWD pour l'utilisateur `ncantu` -- Installation des outils système (git, curl, wget) -- Installation et configuration des services (Node, PostgreSQL, etc.) - -**Serveurs configurés** : - -- `test` (192.168.1.101) : Environnement de test -- `pprod` (192.168.1.102) : Pré-production -- `prod` (192.168.1.103) : Production - -**Point d'entrée** : Proxy (4nk.myftp.biz) via SSH ProxyJump - -### Prérequis - -**Sur la machine locale** : - -- Accès SSH au proxy (4nk.myftp.biz) -- Clé SSH configurée pour l'utilisateur `ncantu` -- Bash disponible (Git Bash sur Windows) -- Mot de passe sudo des serveurs distants : `picnic1280` - -**Sur les serveurs distants** : - -- Accès SSH depuis le proxy -- Utilisateur `ncantu` avec droits sudo -- Connexion Internet pour télécharger les paquets - -### Script de configuration - -**Fichier** : `deploy/scripts/backup/setup-all-servers.sh` - -**Description** : Script bash qui se connecte à chaque serveur via SSH (ProxyJump) et exécute un script de configuration distant. - -### Fonctionnalités - -#### 1. Configuration de `/etc/hosts` - -Ajoute les entrées suivantes dans `/etc/hosts` de chaque serveur : - -```text -192.168.1.100 proxy -192.168.1.101 test -192.168.1.102 pprod -192.168.1.103 prod -192.168.1.104 services -``` - -**Avantages** : - -- Résolution de noms locale sans dépendre du DNS -- Accès rapide entre serveurs -- Indépendance du réseau externe - -#### 2. Configuration sudo NOPASSWD - -Configure l'utilisateur `ncantu` pour utiliser sudo sans mot de passe : - -```text -ncantu ALL=(ALL) NOPASSWD: ALL -``` - -**Fichier créé** : `/etc/sudoers.d/ncantu-nopasswd` - -**Sécurité** : Cette configuration permet à l'utilisateur `ncantu` d'exécuter toutes les commandes sudo sans mot de passe. À utiliser uniquement sur des serveurs d'infrastructure avec accès SSH sécurisé. - -#### 3. Installation des outils système - -Installe les outils suivants via `apt-get` (Debian/Ubuntu) ou `yum` (CentOS/RHEL) : - -- **git** : Gestion de versions -- **curl** : Téléchargement de fichiers -- **wget** : Téléchargement de fichiers (alternative) - -#### 4. Prérequis serveur - -Le script vérifie et installe les prérequis selon la distribution (Debian/Ubuntu) : git, curl, wget, Node.js, PostgreSQL, etc. Voir [DEPLOYMENT.md](./DEPLOYMENT.md) et section "Configuration Initiale des Serveurs" ci-dessus. - -### Utilisation - -**Exécution du script** : - -```bash -cd d:\code\lecoffreio -bash deploy/scripts/backup/setup-all-servers.sh -``` - -**Configuration** : - -Le script contient les variables suivantes (modifiables si nécessaire) : - -```bash -PROXY_HOST="ncantu@4nk.myftp.biz" -SUDO_PASSWORD="picnic1280" - -SERVERS=( - "192.168.1.101:test" - "192.168.1.102:pprod" - "192.168.1.103:prod" -) -``` - -### Architecture SSH - -Le script utilise SSH ProxyJump pour se connecter aux serveurs backend via le proxy : - -```text -Client Local → Proxy (4nk.myftp.biz) → Serveur Backend (192.168.1.10x) -``` - -**Commande SSH** : - -```bash -ssh -J ncantu@4nk.myftp.biz ncantu@192.168.1.101 -``` - -### Vérifications post-configuration - -**Vérification manuelle** : - -```bash -# Se connecter au serveur -ssh -J ncantu@4nk.myftp.biz ncantu@192.168.1.101 - -# Vérifier /etc/hosts -cat /etc/hosts | grep "192.168.1.10" - -# Vérifier sudo NOPASSWD -sudo -n true && echo "OK" || echo "KO" - -# Vérifier les outils -which git curl wget node -node --version -``` - -**Notes importantes** : - -- ⚠️ Le script configure sudo NOPASSWD pour **toutes** les commandes -- ⚠️ À utiliser uniquement sur des serveurs d'infrastructure avec accès SSH sécurisé -- ⚠️ Le mot de passe sudo est codé en dur dans le script (à modifier si nécessaire) -- Le script est idempotent : peut être exécuté plusieurs fois sans problème - ---- - -## Environnements - -### Environnements Disponibles - -| Environnement | Usage | Domaine | Base de Données | -|---------------|-------|---------|-----------------| -| **test** | Tests et développement | `test-lecoffreio.4nkweb.com` | `bdd-test` | -| **pprod** | Pré-production | `pprod-lecoffreio.4nkweb.com` | `bdd-pprod` | -| **prod** | Production | `prod-lecoffreio.4nkweb.com` | `bdd-prod` | -| **demo** | Démonstration | `demo-lecoffreio.4nkweb.com` | `bdd-demo` | - -### Configuration par Environnement - -Chaque environnement a sa propre configuration (legacy vs scripts_v2) : - -```bash -# scripts_v2 (host-native, recommandé) -DEPLOY_SSH_USER=ncantu -DEPLOY_SSH_KEY=~/.ssh/id_ed25519 -DEPLOY_SSH_PROXY_HOST=4nk.myftp.biz - -# Base de données (souvent locale au host env) -DATABASE_HOST=127.0.0.1 -DATABASE_PORT=5432 -DATABASE_USERNAME=lecoffre-user- -DATABASE_PASSWORD=*** -DATABASE_NAME=bdd- -``` - ---- - -## Services Externes - -### Services Utilisés - -| Service | Usage | Configuration | -|---------|-------|---------------| -| **PostgreSQL** | Base de données principale | Port 5432 (host-native) | -| **Bitcoin Signet** | Ancrage blockchain | RPC (port 38332) | -| **IPFS Pinata** | Stockage décentralisé fichiers | API Key | -| **ClamAV** | Scan antivirus uploads | Service système (TCP 3310) | -| **Mailchimp** | Emails transactionnels | API Key | -| **OVH SMS** | Notifications SMS | API Key | -| **Stripe** | Gestion paiements | API Key | -| **IdNot** | OAuth notarial | OAuth2 | -| **API Annuaire** | Synchronisation offices/personnes | Basic Auth | - -### Configuration Dynamique - -Les configurations sensibles sont stockées dans la base de données (`system_configuration`) plutôt que dans les fichiers `.env` : - -- **109 configurations** disponibles -- **Injection** : Via `npm run config:import-env -- --env ` -- **Lecture** : Via `BackendVariables` (backend) -- **Sécurité** : Masquage des valeurs sensibles dans les logs - -Voir [DATABASE_COMPLETE.md](./DATABASE_COMPLETE.md#configuration-systeme-dynamique) pour plus de détails sur `system_configuration`. - ---- - -## Types d'Utilisateurs - -Le système LeCoffre gère **4 types d'utilisateurs** avec des mécanismes d'authentification et d'autorisation différents. - -### 1. Notaires authentifiés IdNot (avec abonnement) - -**Caractéristiques** : - -- Authentification via **IdNot OAuth** -- Stockés dans table `users` -- Possèdent un `office_role_uid` (lié à leur rôle dans l'office) -- **Abonnement actif** requis pour accès complet -- Accès via JWT contenant : `userId`, `office_Id`, `role` (global), `rules` (role + office_role), `isSuperAdmin` (boolean, si `users.is_super_admin === true`) - -**Rôles possibles** : - -- `role` global : `admin`, `super-admin`, `notary`, `default` -- `office_role` : `Notaire`, `Collaborateur` (lié à l'office) - -**Règles d'accès** : - -- JWT contient **toutes les règles** combinées (`role.rules` + `office_role.rules`) -- Accès complet selon règles assignées -- Exemple : `POST rib`, `GET folders`, `DELETE documents`, etc. - -**Création** : - -1. Connexion IdNot OAuth -2. Appel API IdNot `/api/pp/v2/rattachements/{profile_idn}` -3. Création automatique de l'`office` si n'existe pas -4. Duplication `office_roles` depuis Office Template (idNot "0000") -5. Assignment automatique `office_role` selon `typeLien.name` IdNot : - - `NOTAIRE_TITULAIRE`, `NOTAIRE_ASSOCIE`, `NOTAIRE_SALARIE` → "Notaire" - - `COLLABORATEUR`, `SUPPLEANT`, `ADMINISTRATEUR`, `CURATEUR` → "Collaborateur" - -**À la connexion (mise à jour)** : les données contact (nom, prénom, téléphones, civilité) sont synchronisées depuis IdNot ; **l’email en base n’est pas mis à jour** s’il existe déjà (les notaires conservent l’email stocké). Implémentation : `IdNotService.updateContactFromRattachement` (condition `!normalizedCurrentEmail`) et `AffiliationSyncContactDataExtractionHelper.buildContactUpdateDataFromComparison` (condition `newEmail && !currentEmail`). - -### 2. Notaires visiteurs (authentifiés IdNot, SANS abonnement) - -**Caractéristiques** : - -- Identiques aux notaires authentifiés IdNot mais **sans abonnement actif** -- `hasActiveSubscription === false` dans le payload JWT -- **Nouveau flag JWT** : `isVisitor = true` -- Le rôle d'office renvoyé au frontend est forcé à `Visiteur` -- L'UI affiche un badge `Visiteur` (OfficeSelector) au lieu de `(invité)` - -**Règles d'accès** : - -- Filtrage serveur → seuls les endpoints `GET *` et le parcours d'abonnement restent autorisés : - - `GET /api/v1/admin/subscriptions` - - `GET /api/v1/admin/subscription-plans` - - `POST /api/v1/admin/stripe` - - `PUT /api/v1/admin/subscriptions` - - `GET /api/v1/admin/stripe/:uid/client-portal` -- **Toutes les autres règles (POST/PUT/DELETE dossiers, documents, etc.) sont retirées du JWT** -- Les contrôles frontend reposent donc uniquement sur `jwt.rules` (plus de bouton "Créer un dossier") - -**Effets frontend** : - -- Badge `Visiteur` dans le sélecteur d'étude (`OfficeSelector`) -- Absence du CTA "Créer un dossier" et des actions de modification -- Parcours abonnement toujours visible (composants conditionnés sur `jwt.rules`) -- Lorsqu'un notaire invité ouvre un dossier partagé, l'interface monte automatiquement le template client (`DefaultCustomerDashboard`) pour éviter les états parasites issus des composants notaire (onglets, CTA, hooks permissions) et garantir une lecture seule stricte. -- La liste complète des dossiers invités et la vue détail utilisent maintenant la même expérience que les clients (sections "Documents envoyés / reçus" basées sur `DocumentTables`), sans onglets notaire ni actions d'édition. - -**But** : - -- Permettre à un notaire sans abonnement actif de **naviguer en lecture seule** et de souscrire -- Faciliter l'onboarding multi-office - -### 3. Clients (Customers) - -**Caractéristiques** : - -- Authentification via **email + password** ou **code 2FA** -- Stockés dans table `customers` (**PAS** dans `users`) -- **Pas de `office_role_uid`** (n'existent pas dans `users`) -- JWT différent : `ICustomerJwtPayload` avec `customerId` et `email` - -**Règles d'accès** : - -- Endpoints dédiés préfixés `/api/v1/customer/*` -- Authentification via `customerAuthHandler` (différent de `authHandler`) -- Accès limité à : - - Leurs propres dossiers - - Leurs propres documents - - Upload de documents demandés - -**Création** : - -1. Notaire crée un client via `POST /api/v1/notary/customers` -2. Client reçoit email avec code 2FA -3. Client se connecte via `/customer-login` -4. Définit son mot de passe (première connexion) - -### 4. Tiers (Third Parties) - -**Caractéristiques** : - -- **Quasiment identiques aux clients** -- Authentification via **email + code 2FA** (pas de mot de passe permanent) -- Stockés dans table `folder_third_parties` -- Liés à un dossier spécifique - -**Règles d'accès** : - -- Endpoints dédiés : `/api/v1/third-party/*` -- Authentification via code 2FA à usage unique -- Accès limité à : - - Le dossier auquel ils sont rattachés - - Upload de documents demandés par le notaire - -**Création** : - -1. Notaire ajoute un tiers via `POST /api/v1/notary/folders/:folderUid/third-parties` -2. Tiers reçoit email avec code 2FA -3. Tiers se connecte via `/third-party/login` -4. Accès temporaire au dossier - -**Accès aux dossiers** : - -- Les tiers n'ont **pas d'`activeOfficeUid`** (contrairement aux notaires) -- L'accès au dossier est vérifié via le `folderUid` dans le JWT -- Les contrôleurs doivent gérer ce cas spécial : vérifier `folderUid` au lieu de `activeOfficeUid` pour les tiers -- Exemple : `FolderThirdPartiesController.verifyFolderAccess()` vérifie `permissionContext.folderUid` pour les tiers - -**Différence avec clients** : - -- Clients : liés à l'office, multiples dossiers possibles -- Tiers : liés à UN dossier spécifique, accès temporaire - -### Résumé des Types d'Utilisateurs - -| Type | Table | Auth | office_role_uid | Abonnement | Accès | -|------|-------|------|-----------------|------------|-------| -| **Notaire avec abo** | `users` | IdNot OAuth | ✅ | ✅ Requis | Complet selon rules | -| **Notaire invité** | `users` | IdNot OAuth | ✅ | ❌ Non | Limité + Gestion abo | -| **Client** | `customers` | Email/2FA | ❌ | N/A | Ses dossiers seulement | -| **Tiers** | `folder_third_parties` | 2FA | ❌ | N/A | 1 dossier seulement | -| **Template users** | `users` (Office 0000) | N/A | ❌ | N/A | Jamais connectés | - ---- - -## Architecture Rôles & Règles - -### Hiérarchie - -```text -┌─────────────────────────────────────────────────────────────┐ -│ GLOBAL │ -├─────────────────────────────────────────────────────────────┤ -│ roles (table) │ -│ ├─ admin │ -│ ├─ super-admin │ -│ ├─ notary │ -│ └─ default │ -│ │ -│ _RolesHasRules (liaison) │ -│ ├─ role.uid → rules.uid │ -│ └─ Ex: super-admin → [ALL RULES] │ -└─────────────────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────────────────┐ -│ PAR OFFICE │ -├─────────────────────────────────────────────────────────────┤ -│ office_roles (table) │ -│ ├─ Notaire (office A) │ -│ ├─ Collaborateur (office A) │ -│ ├─ Notaire (office B) │ -│ └─ Collaborateur (office B) │ -│ │ -│ _OfficeRolesHasRules (liaison) │ -│ ├─ office_role.uid → rules.uid │ -│ └─ Ex: Notaire (office A) → [POST rib, GET folders, ...] │ -└─────────────────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────────────────┐ -│ UTILISATEUR │ -├─────────────────────────────────────────────────────────────┤ -│ users.roles_uid → role (global) │ -│ users.office_role_uid → office_role (spécifique office) │ -│ │ -│ JWT contains: │ -│ rules = role.rules + office_role.rules (MERGED) │ -│ hasActiveSubscription (boolean) │ -│ isVisitor (boolean) │ -│ isSuperAdmin (boolean, si users.is_super_admin === true) │ -└─────────────────────────────────────────────────────────────┘ -``` - -### Office Template (idNot "0000") - -**Rôle** : - -- Office de **référence** contenant les données par défaut -- Utilisé pour **dupliquer** `office_roles`, `deed_types`, `document_types` vers nouveaux offices - -**Contenu** : - -- `office_roles` : `Notaire`, `Collaborateur` avec leurs règles -- `deed_types` : Types d'actes par défaut -- `document_types` : Types de documents par défaut - -**Users dans Office Template** : - -- 4 users de test avec `idNot` invalides (ex: `rflrefrjf`) -- **N'ont PAS** de `office_role_uid` (c'est **NORMAL**) -- Ne se connectent **jamais** à l'application -- Servent uniquement de données de référence - -### Flux d'autorisation - -#### Notaires (avec/sans abonnement) - -```text -Request → authHandler → ActiveOfficeInjector → ruleHandler → Controller - ↓ ↓ ↓ - JWT verify office_uid inject check rule - req.user req.activeOfficeUid req.user.rules.includes(requiredRule) -``` - -**Bypass `ruleHandler`** : - -- **Super-admin** : Accès à toutes les routes si `isSuperAdmin === true` (champ séparé dans la table `users.is_super_admin`) **OU** `role === "super-admin"` (rétrocompatibilité) -- Les middlewares (`roleHandler`, `ruleHandler`) et les controllers super-admin vérifient les deux conditions -- Endpoints `/admin/subscriptions`, `/admin/stripe`, `/admin/subscription-plans` → **pas de `ruleHandler`** - -#### Clients - -```text -Request → customerAuthHandler → Customer endpoints only - ↓ - JWT verify (ICustomerJwtPayload) - req.body.customer -``` - -#### Tiers - -```text -Request → thirdPartyAuthHandler → Third Party endpoints only - ↓ - 2FA code verify - req.body.thirdParty -``` - -### Règles spéciales - -#### Endpoints SANS `ruleHandler` - -**Abonnements (accessibles à TOUS les notaires authentifiés)** : - -- `GET /api/v1/admin/subscriptions` -- `GET /api/v1/admin/subscription-plans` -- `POST /api/v1/admin/stripe` (création checkout) -- `GET /api/v1/admin/stripe/:uid/client-portal` - -**Raison** : Permettre aux notaires invités (sans abonnement) de s'abonner. - -#### Endpoints sensibles AVEC `ruleHandler` - -**RIB (données bancaires)** : - -- `GET /api/v1/notary/rib` → règle `GET rib` -- `POST /api/v1/notary/rib` → règle `POST rib` -- `DELETE /api/v1/notary/rib` → règle `DELETE rib` - -**Assignées par défaut à** : - -- `office_role` "Notaire" et "Collaborateur" (selon configuration Office Template) - -**Téléchargement de fichiers** : - -- `POST /api/v1/notary/files/download-multiple` → règle `POST files` - -**Assignées par défaut à** : - -- `office_role` "Notaire" et "Collaborateur" (héritage des règles documents/fichiers) - -### Statut Super-Admin - -#### Double mécanisme de vérification - -Le système utilise **deux mécanismes** pour identifier un super-admin : - -1. **Champ `is_super_admin`** (table `users`) : Statut séparé du rôle, permet de promouvoir un utilisateur en super-admin sans changer son rôle global -2. **Rôle `super-admin`** (table `roles`) : Rôle global classique (rétrocompatibilité) - -**Vérification dans le code** : - -- Les middlewares (`roleHandler`, `ruleHandler`) vérifient : `isSuperAdmin === true || role === "super-admin"` -- Les controllers super-admin (ex: `HealthTestsController`, `SiteTextsController`) utilisent la même logique -- Le JWT contient `isSuperAdmin: true` si `users.is_super_admin === true` - -**Bypass super-admin** : - -- **`ruleHandler`** : Les super-admins bypassent la vérification des règles de permissions (accès à toutes les routes) -- **Validation d'accès aux dossiers** : Les super-admins sont **soumis aux mêmes règles** que les notaires et collaborateurs : - - Vérification de l'appartenance au dossier (stakeholder) - - Vérification des partages de dossier (folder_sharing) - - Validation de l'office propriétaire -- **Raison** : Le statut super-admin ne joue pas sur les fonctions de validation d'accès aux dossiers. Un super-admin doit être explicitement stakeholder ou avoir accès via `folder_sharing` pour accéder à un dossier. -- **Correction appliquée (décembre 2025)** : Le bypass super-admin dans `folderAccessHelpers.ensureFolderAccess()` a été supprimé. Les super-admins passent maintenant par les mêmes validations que les autres utilisateurs. - -**Avantages** : - -- Permet d'avoir un super-admin avec un rôle `notary` ou `admin` tout en conservant les permissions super-admin -- Flexibilité pour la gestion des accès sans modifier la structure des rôles -- Sécurité renforcée : les super-admins ne peuvent pas contourner les validations d'accès aux dossiers - -#### Page /offices et synchronisation IdNot - -Sur la page super-admin `/offices`, les actions liées aux synchronisations historiques IdNot/Annuaire ne sont plus exposées. Les recherches de confrères et les résolutions par email utilisent des **appels API** (IdNot/Annuaire) au moment de la demande, en complément de la base du site (users/contacts/offices) et des partages (`folder_sharing`). - -#### Promotion en super-admin - -La promotion se fait via : - -- Script : `lecoffre-back-main/src/scripts/promote-super-admin.ts` -- Configuration : Variable `SUPERADMIN_EMAILS` dans la BDD (déploiement avec `--promoteSuperAdmins`) - -### Cas particuliers - -#### Multer et authHandler - -**Problème** : `multer().single()` écrase `req.body` lors du parsing de `multipart/form-data`. - -**Solution** : - -- `authHandler` stocke le user dans **2 emplacements** : - - `req.body.user` (standard) - - `req.user` (backup, non écrasé par multer) -- `ActiveOfficeInjector` vérifie les 2 emplacements et restaure `req.body.user` si manquant - -**Token d'authentification** : le JWT est lu depuis l'en-tête `Authorization` ou, pour les flux SSE uniquement (EventSource sans en-têtes personnalisés), depuis le paramètre de requête `token`. L'URL est nettoyée avant d'être loggée (paramètre `token` masqué). Voir `docs/CODE_STANDARDS.md` (section Sécurité du Code). - -#### Multi-office - -- Un user peut avoir plusieurs `user_office_affiliations` -- Une seule affiliation est `is_primary = true` -- JWT généré avec l'office PRIMARY -- `ActiveOfficeInjector` injecte `req.body.activeOfficeUid` -- Les routes utilisant `activeOfficeInjector` doivent utiliser `req.body.activeOfficeUid` au lieu de `req.body.user.office_Id` pour supporter les super-admins et le multi-office - -**Important - Ordre des routes Express** : Les routes spécifiques (ex: `/folders/blockchain`, `/folders/deleted`) doivent être déclarées **avant** les routes paramétrées (ex: `/folders/:uid`) pour éviter les conflits de routage. Express matche les routes dans l'ordre de déclaration, donc `/folders/:uid` intercepterait `/folders/blockchain` si déclarée en premier. - -**Exemple** : - -```typescript -// ✅ Correct : route spécifique avant route paramétrée -@Get("/v1/notary/folders/blockchain", [authHandler, activeOfficeInjector, ruleHandler]) -@Get("/v1/notary/folders/deleted", [authHandler, ruleHandler]) -@Get("/v1/notary/folders/:uid", [authHandler, ruleHandler, folderHandler]) - -// ❌ Incorrect : route paramétrée avant route spécifique -@Get("/v1/notary/folders/:uid", [authHandler, ruleHandler, folderHandler]) -@Get("/v1/notary/folders/blockchain", [authHandler, activeOfficeInjector, ruleHandler]) // Ne sera jamais atteinte -``` - -### Base de données - Tables clés - -```sql --- Utilisateurs notaires -users { - uid, idNot, roles_uid (global), office_role_uid (spécifique), - office_uid (deprecated, use affiliations), contact_uid, - is_super_admin (boolean, statut super-admin séparé du rôle) -} - --- Affiliations multi-office -user_office_affiliations { - uid, user_uid, office_uid, is_primary, role_in_office (string), - status, idnot_sync -} - --- Clients -customers { - uid, contact_uid, office_uid, status, password, deleted_at -} - --- Tiers -folder_third_parties { - uid, folder_uid, added_by_uid, role_type, role_description, - first_name, last_name, email, phone, auth_method, email_verified -} - --- Rôles globaux -roles { - uid, name (admin, super-admin, notary, default) -} - --- Rôles par office -office_roles { - uid, name (Notaire, Collaborateur), office_uid -} - --- Règles -rules { - uid, name (ex: "POST rib", "GET folders") -} - --- Liaisons -_RolesHasRules { A: role.uid, B: rule.uid } -_OfficeRolesHasRules { A: office_role.uid, B: rule.uid } -``` - ---- - -## Matrice des Permissions - -### Vue d'Ensemble - -Le système de permissions de LeCoffre.io utilise une **matrice de permissions** stockée en base de données (`role_permissions_matrix`) qui détermine les accès pour chaque combinaison : - -- **Ressource** (ex: `documents`, `folders`, `rib`) -- **Action** (ex: `read`, `create`, `update`, `delete`) -- **Rôle** (ex: `super-admin`, `admin`, `notary`, `collaborator`, `guest_notary`, `client`, `third_party`) -- **Scope** (ex: `GLOBAL`, `OFFICE`, `ASSIGNMENT`) -- **Scoped Entity** (optionnel, UID d'une entité spécifique comme un office) - -### Architecture de la Matrice - -#### Table `role_permissions_matrix` - -**Structure** : - -```sql -- uid (PK) -- resource (string) : nom de la ressource (ex: "documents", "folders") -- action (string) : action (ex: "read", "create", "update", "delete") -- role (string) : rôle utilisateur (ex: "super-admin", "notary") -- allowed (boolean) : true = autorisé, false = refusé -- scope (string) : "GLOBAL", "OFFICE", "ASSIGNMENT" -- scoped_entity (string | null) : UID d'une entité spécifique (ex: office_uid) -- pages (JSON) : pages frontend associées -- updated_by (string | null) : UID de l'utilisateur qui a modifié -- created_at, updated_at -``` - -#### Types de Scope - -**GLOBAL** : - -- Accès à toutes les ressources, tous offices confondus -- Utilisé pour `super-admin` et `admin` (dans certains cas) - -**OFFICE** : - -- Accès limité à l'office actif de l'utilisateur -- Utilisé pour `notary` et `collaborator` -- Vérifié via `activeOfficeInjector` middleware - -**ASSIGNMENT** : - -- Accès limité aux ressources assignées à l'utilisateur -- Utilisé pour `guest_notary` (dossiers partagés), `client`, `third_party` -- Vérifié dans les controllers via filtres sur `depositor_uid`, `third_party_depositor_uid`, `shared_to_office_uid` - -### Flux de Vérification des Permissions - -#### Middleware `ruleHandler` - -**Ordre d'exécution** : - -```text -Request → authHandler → activeOfficeInjector → ruleHandler → Controller -``` - -**Étapes dans `ruleHandler`** : - -1. **Extraction du rôle utilisateur** : - - `resolveUserRole(user)` détermine le rôle effectif - - Priorité : `isSuperAdmin` → `role` global → `officeRole` → `userType` - -2. **Résolution de la ressource et action** : - - `resolveResource(req, service)` : extrait la ressource depuis le path - - `resolveAction(req, resource)` : détermine l'action (read/create/update/delete) - -3. **Consultation de la matrice** : - - `permissionsService.getDecision({ resource, action, role, scoped_entity })` - - Retourne : `"allow"`, `"deny"`, ou `"pass"` - -4. **Décision finale** : - - **Super-admin** : bypass automatique (ligne 99-112) - - **"deny"** : refusé (avec exceptions pour certaines ressources) - - **"allow"** : autorisé - - **"pass"** : pas d'entrée dans la matrice → fallback sur règles JWT - -#### Service `RolePermissionsMatrixService` - -**Méthode `getDecision`** : - -1. **Construction de la clé** : - - Format : `resource::action::role::scoped_entity` - - Exemple : `documents::read::notary::-` (sans scope) - - Exemple : `documents::read::notary::office-uid-123` (avec scope) - -2. **Recherche dans le cache** : - - Cache en mémoire (Map) chargé depuis la BDD au démarrage - - Recherche exacte de la clé - -3. **Retour** : - - `"allow"` si `allowed === true` - - `"deny"` si `allowed === false` - - `"pass"` si aucune entrée trouvée - -### Permissions par Type d'Utilisateur - -#### Super-Admin - -- **Bypass automatique** dans `ruleHandler` (ligne 99-112) -- Accès à **toutes les ressources** avec scope `GLOBAL` -- Endpoints `/api/v1/super-admin/*` accessibles -- Peut modifier la matrice des permissions - -#### Admin (Office) - -- Scope généralement `OFFICE` (limité à son office actif) -- Peut gérer les utilisateurs de son office -- Accès aux ressources de son office -- Matrice : alignement des permissions `admin` sur `collaborator` pour les ressources notariales (migration `20260305120000_backfill_admin_permissions_from_collaborator` + script `ensure-role-permissions-matrix-folders.sql`). - -#### Notary - -- Scope `OFFICE` : accès aux ressources de son office actif -- Peut créer/modifier/supprimer dossiers, documents, RIB -- Accès complet aux fonctionnalités notariales - -#### Collaborator - -- Scope `OFFICE` : accès aux ressources de son office actif -- **Mêmes droits que Notaire** pour documents, files, folder_customer, folder_third_parties, folder_sharing, document_reminder, document_request (read, create, update, delete selon ressource). Pour **folders** : archive autorisé, **delete non** (les collaborateurs peuvent archiver mais pas supprimer un dossier). -- Matrice : script `ensure-role-permissions-matrix-folders` et migration `20260204120000_align_collaborator_notary_folders_members`. Après déploiement : `run-ensure-role-permissions-matrix-folders.sh` (test | pprod | prod). - -#### Guest Notary (Notaire Invité) - -- Scope `ASSIGNMENT` : accès uniquement aux dossiers partagés -- Peut lire/créer/modifier documents dans les dossiers partagés -- Accès limité aux ressources assignées via `shared_to_office_uid` - -#### Client - -- Scope `ASSIGNMENT` : accès uniquement à ses propres dossiers et documents -- Endpoints `/api/v1/customer/*` -- Peut lire ses documents, uploader des documents demandés - -#### Third Party (Tiers) - -- Scope `ASSIGNMENT` : accès uniquement au dossier auquel il est rattaché -- Endpoints `/api/v1/third-party/*` -- Peut lire ses documents, uploader des documents demandés -- **Consultation (Mark as Viewed)** : La route `PUT .../mark-as-viewed` est exclue du blocage de mise à jour des documents `SENT`. Cela permet au tiers de consulter un document (passant de SENT à DOWNLOADED) sans erreur 403 ni déconnexion. - -### Points d'Attention - -#### Super-Admin Bypass - -Le super-admin **bypass automatiquement** la matrice des permissions. Cela signifie : - -- ✅ Accès à toutes les routes -- ⚠️ Les controllers doivent quand même vérifier la logique métier -- ⚠️ Les filtres de scope (OFFICE, ASSIGNMENT) ne s'appliquent pas automatiquement - -#### Fallback sur Règles JWT - -Si la matrice retourne `"pass"` (pas d'entrée), le système fait un **fallback sur les règles JWT** : - -- Vérifie si `user.rules` contient la règle requise (ex: `"POST documents"`) -- Si oui → autorisé -- Si non → refusé - -#### Scoped Entity - -Le paramètre `scoped_entity` permet de définir des permissions spécifiques pour une entité (ex: un office particulier) : - -- Exemple : `documents::read::notary::office-uid-123` -- Si présent, prioritaire sur l'entrée sans scope -- Utilisé pour des permissions granulaires - -### Modifications de la Matrice - -#### Via Interface Super-Admin - -- Endpoint : `PUT /api/v1/super-admin/role-permissions-matrix` -- Permet de modifier les permissions pour chaque combinaison rôle/ressource/action -- Met à jour le cache automatiquement - -#### Via Migrations - -- Les migrations peuvent créer/modifier des entrées dans `role_permissions_matrix` -- Exemple : `prisma/migrations/20251107_role_permissions_matrix/migration.sql` - -### Pilotage dynamique (onglet Super Admin "Matrice des droits") - -L'onglet `Super Admin > Matrice des droits` (`/super-admin/role-permissions`) permet désormais de piloter en temps réel la matrice. - -**Interface** : - -- Tableau double entrée (ressource × action × rôle) avec cases à cocher synchronisées avec la BDD. -- Colonne informative "Pages concernées" (liste des écrans impactés). -- Badges de portée (`Global`, `Étude active`, `Affectation`, `Personnel`) pour chaque rôle afin de rappeler le niveau de permission. -- Barre de recherche full-text (ressource, action, page, rôle, scope). -- Résumé des modifications en attente + bouton `Réinitialiser` (revenir aux valeurs chargées) + bouton `Enregistrer`. -- Feedbacks : loader centralisé, toasts de confirmation, surface d'erreur explicite. - -**API & persistance** : - -- `GET /api/v1/super-admin/role-permissions` → retourne la matrice structurée (ressource → action → [pages, rôles]). -- `PUT /api/v1/super-admin/role-permissions` → accepte une liste d'updates `{ resource, action, role, allowed, scope?, pages? }`. -- Table SQL `role_permissions_matrix` : clé unique `(resource, scoped_entity, action, role)`, colonnes `allowed`, `scope`, `pages`, `updated_by`, `updated_at`. -- Service backend : cache mémoire (chargé au démarrage, invalidé après update) + logging (`SafeLogger.debug`). - -**Middleware** : - -- `RulesHandler` interroge la matrice (décision `allow|deny|pass`) avant le fallback sur les règles JWT historiques. -- Résolution automatique du trio `{resource, action, role}` à partir du chemin, du verbe HTTP et du JWT (`guest_notary`, `client`, `third_party`, etc.). -- Journalisation standardisée des décisions (`resource`, `action`, `role`, `decision`, `userId`). - -#### Exceptions et fallbacks dans RulesHandler - -Le `RulesHandler` implémente des **fallbacks spécifiques** pour certaines ressources/actions lorsque la matrice retourne `pass` (pas de règle explicite) ou `deny` (refus explicite) : - -**1. `folder_third_parties` + action `resend`** : - -- Si décision = `pass` et aucun JWT rules : autoriser par défaut pour les rôles `notary`, `collaborator`, `admin`, `super-admin`. -- Permet le renvoi de code de vérification aux tiers sans nécessiter une règle explicite dans la matrice. - -**2. `folder_sharing` + action `delete` (révocation de partage)** : - -- **Si décision = `pass`** et aucun JWT rules : autoriser par défaut pour les rôles `notary`, `collaborator`, `admin`, `super-admin`. -- **Si décision = `deny`** mais que l'utilisateur a des permissions sur les dossiers (`POST folders`, `DELETE folders`, ou `PUT folders`) : autoriser pour les rôles `notary`, `collaborator`, `admin`, `super-admin`. -- **Sécurité** : Le contrôleur `FolderSharingController.revokeShare()` vérifie de toute façon que l'utilisateur est membre de l'office propriétaire du dossier (`folder.office_uid === activeOffice.uid`). Cette double vérification garantit que seuls les membres de l'office propriétaire peuvent révoquer un partage, même si la matrice de permissions ne contient pas de règle explicite. - -**Raison des fallbacks** : - -- Permettre aux membres de l'office propriétaire d'un dossier de gérer les partages même si la matrice de permissions n'a pas été configurée explicitement pour `folder_sharing` delete. -- Le contrôleur effectue toujours une vérification finale basée sur la propriété du dossier, garantissant la sécurité. - -La matrice devient ainsi la source de vérité opérationnelle. Les sections précédentes restent la documentation fonctionnelle de référence ; l'interface Super Admin permet de l'appliquer sans redéploiement. - ---- - -## JWT Multi-Office et Calcul Dynamique des Règles - -### Principe - -Lorsqu'un utilisateur a plusieurs affiliations (`user_office_affiliations`), le JWT doit refléter : - -- L'**office actif** (affiliation avec `is_primary = true`) -- Les **règles du rôle** dans cet office actif - -### Implémentation dans AuthService - -Le processus de calcul des règles est plus complexe que la simple addition des règles globales et office. Voici le processus complet : - -```typescript -// AuthService.getUserJwtPayload() - -// 1. Déterminer l'office actif via determineActiveOffice() -// - Gère les affiliations PRIMARY -// - Gère les dossiers partagés (GUEST_FOLDERS) si pas d'abonnement actif -// - Retourne { officeId, activeAffiliation, isGuestFolders } -const { officeId, activeAffiliation, isGuestFolders } = await this.determineActiveOffice(user); - -// 2. Normaliser le nom du rôle office -// - Utilise normalizeIdNotOfficeRoleName() pour standardiser les noms de rôles IdNot -// - Gère les variations de noms (accents, casse, underscores) -const rawAffiliationRoleName = activeAffiliation?.role_in_office ?? null; -const normalizedAffiliationRoleName = normalizeIdNotOfficeRoleName(rawAffiliationRoleName); - -// 3. Calculer les règles dans l'ordre suivant : -const rules = new Set(); - -// 3.1. Ajouter les règles du rôle global -user.role.rules.forEach((rule) => { - if (rule?.name) { - rules.add(rule.name); - } -}); - -// 3.2. Ajouter les règles super-admin si applicable -// - Vérifie is_super_admin OU rôle = "super_admin" -// - Ajoute toutes les règles super-admin via addSuperAdminRules() -await this.addSuperAdminRules(rules, isSuperAdmin); - -// 3.3. Ajouter les règles office ou guest notary -if (isGuestFolders) { - // Cas "Dossiers invités" : ajoute les règles de notaire invité - this.addGuestNotaryRules(rules); -} else if (activeAffiliation && normalizedAffiliationRoleName && officeId) { - // Cas normal : charge le rôle office et ajoute ses règles - await this.addOfficeRoleRules(rules, officeId, normalizedAffiliationRoleName, user); -} else if (user.office_role) { - // Fallback : utilise le rôle office de l'utilisateur - user.office_role.rules.forEach((rule) => { - if (rule?.name) { - rules.add(rule.name); - } - }); -} - -// 4. Filtrer les règles pour les visiteurs (si pas d'abonnement actif) -if (!hasActiveSubscription && !isGuestFolders) { - uniqueRules = this.filterVisitorRules(uniqueRules); -} - -// 5. JWT contient : userId, office_Id, role, officeRole, rules, isVisitor, isGuestFolders -``` - -**Points importants** : - -- **GUEST_FOLDERS** : Si l'utilisateur n'a pas d'abonnement actif mais a des dossiers partagés, `officeId = "GUEST_FOLDERS"` et les règles de notaire invité sont appliquées. -- **Normalisation des rôles** : Les noms de rôles IdNot sont normalisés pour correspondre à la matrice de permissions. -- **Règles super-admin** : Ajoutées séparément après les règles globales, pas fusionnées. -- **Filtrage visiteurs** : Les règles sont filtrées si l'utilisateur est un visiteur (pas d'abonnement actif et pas GUEST_FOLDERS). - -### Switch d'Office - -Lors du switch d'office via `PUT /api/v1/notary/users/:userUid/offices/active` : - -1. ✅ Modification de l'affiliation PRIMARY en BDD -2. ✅ Régénération du JWT avec : - - Nouvel `office_Id` (l'office cible) - - Nouvelles `rules` (rôle dans l'office cible) -3. ✅ Frontend met à jour le JWT en localStorage -4. ✅ Rechargement automatique des dossiers/permissions - -### Vérification Frontend Réactive - -Le frontend vérifie désormais les règles via `PermissionContext`/`useRolePermissions()` : - -```typescript -import { useRolePermissions } from "@Front/Stores/RolePermissionsStore"; -import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule"; - -const { can, loading: permissionsLoading } = useRolePermissions(); - -const canCreateFolder = can(AppRuleNames.officeFolders, AppRuleActions.create); - -// Exemple : gérer le clic sur "Créer un dossier" -const handleCreateFolderClick = useCallback(() => { - if (permissionsLoading) { - return; - } - if (canCreateFolder) { - router.push("/folders/create"); - } else { - subscriptionModal.open(); - } -}, [canCreateFolder, permissionsLoading, router, subscriptionModal]); -``` - -### Cohérence Matrice ↔ JWT et fallback frontend - -#### Source de vérité et arbitrage - -- La matrice `role_permissions_matrix` (pilotée dans l'onglet Super Admin) est la cible de gouvernance. -- Le middleware `RulesHandler` doit prioriser la matrice; en son absence pour un couple (ressource, action, rôle), la décision retombe sur les `rules` incluses dans le JWT. - -#### Fallback côté frontend (non super‑admin) - -- Par défaut, seuls les super‑admins chargent la matrice côté front. -- Pour les autres rôles, `RolePermissionsStore.can(resource, action)` s'appuie sur `jwt.rules` avec: - - mapping actions → méthodes HTTP: `create→POST`, `read→GET`, `update→PUT|PATCH`, `delete→DELETE`, `add→POST|PUT`, `remove→DELETE`, `share→POST`, `remind→POST`, `resend→POST` - - normalisation singulier/pluriel de ressource (ex. `folder_customer` ⇄ `folder_customers`) - - heuristiques: - - clients dossier: `add` accepte `POST/PUT folder_customers` ou `PUT folders` - - tiers dossier: `add` accepte `POST folder_third_parties` ou `PUT folders` - - partage dossier: `create` accepte `POST folder_sharings` ou `POST folders` - -#### Recommandations d'alignement - -- Garder cohérents: seed des `rules` et affectations (`roles`, `office_roles`), calcul `AuthService.getUserJwtPayload()`, et configuration de la matrice. - -### Licence (hasActiveSubscription) et comportement d'accès - -#### Calcul côté serveur - -- `SubscriptionsService.isUserSubscribed(user.uid, officeId)` pilote `hasActiveSubscription`. -- En échec de vérification: journalisation `warn` et fallback `hasActiveSubscription=false`. -- Si `false` → `isVisitor=true`, `officeRole="Visiteur"`, `rules` réduites: lecture + parcours abonnement. - -#### Affichages et contrôles UI - -- L'UI masque/désactive les actions d'écriture si la règle correspondante manque dans `jwt.rules`. -- Carte « Gestionnaires du projet »: « Licence active » s'affiche si `subscription.status === "active"` et `subscription.office.uid === folder.office.uid`. - ---- - -## Gestion des Fichiers - -### Nommage des Fichiers (Préfixe Dépositaire) - -**Objectif** : Identifier l'origine et le contexte de chaque fichier via un préfixe standardisé. - -**Format** : `folderNumber.[Prefix.]FirstName.LastName.TypeDocument.aplc.ext` -*Exception RIB* : `RIB.[Prefix.]officeName.aplc.ext` - -**Préfixes (Type de dépositaire)** : - -- **N** : Notaire gestionnaire -- **C** : Client -- **T** : Tiers -- **NI** : Notaire invité (office partagé) - -**Composants** : - -1. **Numéro de dossier** (ou "RIB") : Toujours en premier. -2. **Préfixe** : `N`, `C`, `T` ou `NI`. -3. **Prénom** : Formaté et inséré dans le nom. -4. **Nom** : Formaté en majuscules, espaces remplacés par tirets (ex. `DUPONT-MOREAU`). -5. **Type de document** : Nom du type de document. -6. **Suffixe** : `.aplc` (ancrage), sans index de version. - -**Implémentation** : - -- **FileNamingService** : Centralise la génération avec `generateDisplayName`. -- **DepositaryPrefixHelper** : Gère l'ajout/retrait du préfixe. -- **FileNamingParsingHelper** : Parse le format pour extraction. - ---- - -## Statuts des Documents et Flux - -### Distinction Sémantique - -- **DEPOSITED** : Document envoyé par un déposant (Client, Tiers, Notaire Invité) vers le Notaire en charge. - - *Note* : Un document envoyé par un Notaire Invité au Notaire en charge est DEPOSITED. -- **SENT** : Document envoyé par le Notaire en charge vers un destinataire (Client, Tiers, Notaire Invité). - - *Note* : Un document envoyé par le Notaire en charge au Notaire Invité est SENT. -- **DOWNLOADED** : Document SENT qui a été consulté/téléchargé par le destinataire. -- **ASKED** : Document demandé par le Notaire en charge. -- **VALIDATED/REFUSED** : Statuts de validation par le Notaire en charge. - -### Règles de Visibilité et Actions - -- **"Documents à envoyer" (Front)** : Affiche uniquement `ASKED`, `REFUSED`, `VALIDATED`. - - Exclut `DEPOSITED`, `SENT`, `DOWNLOADED`. -- **Blocage API Customer** : - - **Modification/Suppression** : Interdite si statut est `SENT` (403). Autorisé si `DOWNLOADED`. - - **Passage en SENT** : Interdit via PUT (réservé au `send`). - ---- - -## Évolution planifiée - Documents unifiés - -**Statut** : 📋 Planifié | **Version** : cible - -### Objectif - -Unifier le flux d'envoi de documents et les métadonnées d'émetteur pour tous les types de destinataires et de déposants. - -### Décisions validées - -#### 1. Tables (court terme) - -- Conserver `documents` et `documents_notary` pour des raisons historiques/techniques. -- Unifier le flux d'envoi côté API. -- Fusion des tables prévue à terme. - -#### 2. Destinataire (recipient) - -Type union unique pour tous les envois : - -```typescript -{ - type: 'customer' | 'third_party' | 'office' | 'invited_notary'; - uid: string; -} -``` - -Types de destinataires possibles : - -- `customer` : client du dossier -- `third_party` : tiers du dossier -- `office` : notaire admin de l'office -- `invited_notary` : notaire invité du dossier - -#### 3. Émetteur (emitter) - -- **Champ** : `emitter` (équivalent anglais de « émetteur »). -- **Structure** : `{ uid, first_name, last_name, office_uid?, office_name? }`. -- **Règle** : celui qui envoie le document ; absent pour documents ASKED (personne n'a encore envoyé). -- **Sources** : - - - Client dépose : `depositor.contact` - - Tiers dépose : `third_party_depositor` - - Notaire envoie (DocumentsNotary) : `depositor` (User) - - Notaire envoie (Documents shared_to_office) : notaire du `folder.office` - - RIB : `rib_anchor_proof_data.depositor` - -#### 4. Documents du notaire invité - -Les documents créés par le notaire invité sont traités comme ceux des tiers ou des clients : même modèle, pas de cas particulier. Le notaire invité est un membre du dossier comme les autres ; il ne joue pas le rôle de notaire gestionnaire dans ce dossier. - -**Implémentation** : Concept de membre unifié (`FolderMember`) : - -- **Base** : `BaseFolderMember` (uid, display_name, email) — champs communs hérités -- **Types dérivés** : `CustomerMember`, `ThirdPartyMember`, `InvitedNotaryMember` (extends BaseFolderMember + type) -- **Recipient** : `BaseRecipient` (uid), `TypedRecipient` (extends BaseRecipient + type) -- **API** : GET folder retourne `members` (tableau unifié clients + tiers + notaires invités) -- **Backend** : `buildFolderMembers()` dans `folderMemberHelpers.ts` -- **Frontend** : `folder.members` disponible ; `buildRecipientsFromMembers()` pour le flux d'envoi - -#### 5. Espaces et onglets membres (dérivation depuis cas parent member) - -Les espaces (client/tiers/notaires invités) et les onglets des membres des dossiers dérivent d'un cas parent « member ». - -**Espaces (FolderInformationUserContext)** : - -- Type de base : `BaseMemberContextType` = `"customer" | "third_party" | "invited_notary"` -- Constantes : `MEMBER_CONTEXT_THIRD_PARTY`, `MEMBER_CONTEXT_INVITED_NOTARY` -- `FolderInformationUserContext` = `"notary" | MEMBER_CONTEXT_THIRD_PARTY | MEMBER_CONTEXT_INVITED_NOTARY` - -**Onglets membres (ClientView)** : - -- Type de base : `BaseMemberTabValue` avec `tabType: BaseMemberTabType` -- `BaseMemberTabType` = `"customer" | "third_party" | "shared_notary"` -- Constantes : `TAB_TYPE_CUSTOMER`, `TAB_TYPE_THIRD_PARTY`, `TAB_TYPE_SHARED_NOTARY` -- Types dérivés : `CustomerTabValue`, `ThirdPartyTabValue`, `SharedNotaryTabValue` (extends BaseMemberTabValue) - -**Fichiers** : `MemberTypes.ts`, `folderInformationUserContext.ts`, `ClientView/types.ts` - -#### 6. Flux d'envoi unifié - -Une seule fonction d'envoi API avec un destinataire de type union. Pas de distinction par type de destinataire : envoi au bon membre en tant que notaire gestionnaire. - -**Implémentation** : L'API `POST /v1/notary/documents_notary/send` accepte désormais : - -- **Format unifié** : `recipients: { type: 'customer' | 'third_party' | 'office' | 'invited_notary'; uid: string }[]` -- **Format legacy** : `recipientUid` ou `recipientUids` (résolution automatique du type) - -Le service `DocumentBatchService` route vers Documents (third_party, office/invited_notary) ou DocumentsNotary (customer) selon le type. - -#### 7. Orchestrateurs de sortie API (plain) - -Les réponses API documents sont normalisées via des orchestrateurs de post-hydratation pour garantir une sortie `plain` cohérente entre endpoints liste, mono-entité, transformation et suppression. - -**DocumentsNotary** : - -- `#Common/utils/documentsNotaryPostHydrationHelper` -- `buildDocumentsNotaryPlainWithRelations` : enrichit la sortie (`emitter`) et réinjecte `files` si nécessaire -- `hydrateDocumentsNotaryToPlainWithRelations` : pipeline liste -- `hydrateSingleDocumentNotaryToPlainWithRelations` : pipeline mono-entité (get by uid, mark-as-viewed, suppression, transformations) -- `logDocumentsNotaryPlainFilesGuard` : garde-fou de cohérence `files` - -**Documents (non-notary)** : - -- `#Common/utils/documentsPostHydrationHelper` -- `buildDocumentsPlainWithRelations` : pipeline liste -- `buildSingleDocumentPlainWithRelations` : pipeline mono-entité (get by uid, création, mark-as-viewed, suppression) -- `logDocumentsPlainFilesGuard` : garde-fou de cohérence des fichiers téléchargeables - -**Règle d'architecture** : - -- Les controllers/helpers ne construisent plus la sortie JSON avec un `instanceToPlain` local suivi de merges manuels. -- Toute sortie document doit passer par l'orchestrateur correspondant (`DocumentsNotary` ou `Documents`). - -### Références pour implémentation - -- `DocumentsEnrichmentHelper` : enrichir les réponses avec `emitter` au lieu de limiter à `{ uid }`. -- `DocumentsNotary` : inclure `depositor` (avec `contact`) dans les requêtes et exposer `emitter`. -- RIB : exposer `emitter` depuis `rib_anchor_proof_data.depositor` (POST/GET office, GET office/:uid). - ---- - -## Partage Inter-Études - -**Statut** : ✅ 100% | **Version** : 2.0.0 - -### Distinction importante : Partage avec une personne spécifique - -**Concept fondamental** : Le partage se fait avec **une personne spécifique** (notaire invité) qui reçoit un email et se connecte ensuite. - -**Sémantique du partage** : - -- **Partage = Invitation d'une personne spécifique** (IdNot + email de préférence pour les envois, `invited_notary_email`) -- **Documents = Demandés à l'office de cette personne** (`shared_to_office_uid`) -- **Accès aux dossiers invités = Croisement sur l'id IdNot** (`invited_notary_idnot`) ; **email en base** = envois uniquement - -**Workflow** : - -1. Le notaire principal partage un dossier avec un confrère (sélection dans l'annuaire + email de préférence pour les envois, stocké en `invited_notary_email`) -2. Un email d'invitation est envoyé au confrère à l'adresse `invited_notary_email` (après création, seul cet email en base est utilisé pour tous les envois) -3. Le confrère se connecte et accède aux dossiers partagés via "Dossiers invités" (GUEST_FOLDERS) -4. Les documents demandés sont créés avec `shared_to_office_uid` (l'office du confrère) -5. L'accès aux dossiers invités est déterminé par l'IdNot ; les envois d'email utilisent `invited_notary_email` en base - -**Gestion de l'affichage et de la sélection des dossiers** : - -- **`is_primary` pour GUEST_FOLDERS** : Si un utilisateur n'a pas d'office avec licence active mais a des dossiers partagés, "Dossiers invités" est ajouté avec `is_primary: true` pour permettre la sélection automatique -- **Stabilisation de la liste** : Les hooks frontend (`useInvitedCustomerFolders`) utilisent des refs pour stabiliser la liste des dossiers et éviter les re-rendus lors de la navigation -- **Badge "Invité"** : Affiché uniquement pour les dossiers partagés en contexte non-GUEST_FOLDERS (évite la redondance) -- **Mise à jour IdNot** : Les utilisateurs invités sont mis à jour via l'API IdNot même s'ils n'ont pas de `officeMembership.idNot` (recherche par rattachements) - -**Contrainte du schéma** : `@@unique([folder_uid, shared_to_office_uid])` - -- Un dossier ne peut être partagé qu'**une seule fois avec un office donné** -- Cette contrainte garantit l'isolation des documents par notaire invité -- Si plusieurs notaires du même office doivent être invités, cela nécessiterait des partages distincts (non supporté actuellement) - -**Distinction avec un partage d'office complet** : -Le système **ne supporte pas** le partage avec un office entier où tous les membres auraient accès. Le partage est toujours avec une **personne spécifique** identifiée par son IdNot et son email de préférence pour les envois. L'office (`shared_to_office_uid`) sert uniquement à : - -- Identifier l'office du notaire invité pour la création des documents -- Permettre l'affichage dans l'interface "Dossiers invités" - -L'accès aux dossiers invités est déterminé par l'**IdNot** du notaire connecté (`invited_notary_idnot`). L'email en base (`invited_notary_email`) sert uniquement aux envois (invitations, documents). - -### Architecture Backend - -**Service** : `FolderSharingService` (~250 lignes) -**Fichier** : `lecoffre-back-main/src/services/notary/FolderSharingService/FolderSharingService.ts` - -**Controller** : `FolderSharingController` (~280 lignes) -**Fichier** : `lecoffre-back-main/src/app/api/notary/FolderSharingController.ts` - -**Endpoints** : - -- `POST /api/v1/notary/folders/:uid/share` : Partager dossier avec confrère - - **Paramètres** : `invitedNotaryEmail` (requis, email de préférence pour les envois), `invitedNotaryIdnot` (optionnel), `invitedNotaryFirstName`, `invitedNotaryLastName` (optionnels). CRPCEN optionnel. - - **Note** : L'email stocké (`invited_notary_email`) est utilisé pour tous les envois après création. L'IdNot (`invited_notary_idnot`) sert au croisement login / dossiers invités. -- `GET /api/v1/notary/folders/shared` : Liste dossiers reçus -- `GET /api/v1/notary/folders/:uid/shares` : Liste partages d'un dossier -- `POST /api/v1/notary/shares/:uid/revoke` : Révoquer partage - -### Base de Données - -**Table** : `folder_sharing` - -**Champs** : - -- `folder_uid` : Dossier partagé -- `shared_from_office_uid` : Office qui partage -- `shared_to_office_uid` : Office qui reçoit (office du notaire invité) -- `invited_notary_email` : Email de préférence pour les envois (invitations, documents). Seul champ email pour le notaire invité ; par défaut pré-rempli depuis IdNot à l'invitation, modifiable avant enregistrement. **Après création, seul ce champ est utilisé pour tous les envois** (aucune relecture IdNot). -- `invited_notary_idnot` : Identifiant IdNot du notaire invité. Utilisé pour le **croisement login / dossiers invités** : l'accès aux dossiers partagés est déterminé par cet IdNot (quand l'utilisateur connecté a un IdNot). -- `invited_notary_first_name` : Prénom du notaire invité -- `invited_notary_last_name` : Nom du notaire invité -- `shared_by_user_uid` : Utilisateur qui a partagé -- `share_role` : Rôle accordé (lecture seule) -- `status` : ACTIVE, REVOKED -- `expires_at` : Date expiration optionnelle -- `can_view_other_members_documents` : Permission pour voir les documents des autres membres - -**Email et IdNot du notaire invité** : - -- **Un seul champ email en base** : `invited_notary_email` = email de préférence (et par défaut email IdNot à l'invitation). Il n'y a pas de champ séparé « email IdNot » ; l'email IdNot et l'email de préférence sont donc le même champ persisté. -- **Accès aux dossiers invités** : croisement sur `invited_notary_idnot` (voir `buildGuestNotaryShareWhere`). L'email en base n'est pas utilisé pour l'accès. -- **Notaires invités sans seat** : à l'invitation, le partage enregistre `invited_notary_idnot`. Lors de la première connexion via IdNot, un profil `User` est créé ou mis à jour avec `users.idNot`. Les notaires invités ont donc en base un identifiant IdNot (sur le partage, et sur le User après connexion). - -**Contrainte unique** : `@@unique([folder_uid, shared_to_office_uid])` - -- Empêche de partager plusieurs fois le même dossier avec le même office -- Garantit l'isolation des documents par notaire invité - -### Frontend - -**Pages** : - -- `/folders/shared` : Dossiers reçus -- **Modal** : `ShareFolderModal` (partager avec confrère) - -**API** : `FolderSharingApi` (4 méthodes) - -**Fonctionnalités** : - -- Onglets notaires invités créés dans `ClientView` -- Badge "Lecture seule" pour notaires invités -- Query inclut `folder_sharings` avec relations -- **Demande de documents** : Possibilité de demander des documents aux confrères invités (v2.0.1) -- **Relance de documents** : Possibilité de relancer les confrères pour les documents en attente (v2.0.1) -- **Invitation simplifiée** : Seul l'email est requis pour inviter un confrère. Le CRPCEN est automatiquement récupéré depuis IdNot lors de la connexion (v2.0.1) - -### Email - -**Destinataire notaire invité** : Tous les envois (invitation partage, relance, documents) utilisent **uniquement** l'email en base `invited_notary_email`. Aucune relecture de l'email côté IdNot après création du partage. - -**Templates** : - -- `FOLDER_SHARING_INVITATION` : Invitation partage dossier -- `DOCUMENT_REQUEST` : Demande de document (utilisé aussi pour les confrères) -- Documentation : `docs/MAILCHIMP_TEMPLATE_FOLDER_SHARING_INVITATION.md` - -### Demande et Relance de Documents aux Confrères (v2.0.1) - -**Fonctionnalités** : - -- **Demander un document** : Bouton disponible sur l'onglet du confrère invité -- **Relancer un confrère** : Bouton disponible pour les documents en statut ASKED -- **Affichage des documents** : Table des documents avec filtrage par `shared_to_office_uid` - -**Frontend** : - -- `ClientView` : Gestion des onglets notaires invités avec boutons -- `AskDocuments` : Support du format `shared-office:uid` dans l'URL -- `DocumentTables` : Affichage des documents avec flag `isSharedNotary` -- `EmailReminder` : Support des relances pour les confrères - -**Backend** : - -- Utilise le champ `shared_to_office_uid` existant dans la table `documents` -- Les documents sont demandés à l'office du notaire invité, mais l'accès est contrôlé par l'email -- `EmailBuilder.sendDocumentEmails()` : Gère déjà les emails pour les confrères -- `DocumentsController.resendRequest()` : Fonctionne pour tous les types de documents - -**Important** : Les documents avec `shared_to_office_uid` sont techniquement visibles par tous les membres de cet office, mais la contrainte unique `@@unique([folder_uid, shared_to_office_uid])` limite l'exposition car un dossier ne peut être partagé qu'une seule fois avec un office donné. L'accès individuel est contrôlé via `invited_notary_email` dans le contexte `GUEST_FOLDERS`. - -**Documentation détaillée** : voir section « Partage Inter-Études » ci-dessus. Déploiement : exécuter les migrations Prisma puis `deploy/scripts/build-and-deploy.sh`. Analyse : vérifier en base `invited_notary_idnot` sur folder_sharing ; créer un partage depuis l’UI ; connexion notaire invité (IdNot) → dossiers partagés accessibles. - ---- - -### Accès et Téléchargement pour Notaire Invité - -**Problème résolu (v2.0.2)** : Les notaires invités ne pouvaient pas voir/télécharger les documents envoyés par le notaire gestionnaire (table `documents_notary`). - -**Architecture d'accès** : - -1. **Vue Document** : Permission `canViewDocuments` étendue si `isGuestNotary` (rôle ou contexte GUEST_FOLDERS). -2. **API Customer** : - - **GET documents_notary** : Support du filtre `where: { AND: [...] }` pour extraire `folderUid`. - - **Download** : `FileNotaryAccessGuestNotaryHelper` vérifie désormais dans `documents_notary` (en plus de `documents`) et valide l'accès via `folder_sharing`. -3. **Fallback Téléchargement** : Si le document est dans la table `documents` (envoyé à l'invité), le front utilise l'API Customer (`/customer/files/download/:uid`) au lieu de Notary, car l'API Customer valide correctement l'accès via `folder_sharing`. - ---- - -## Gestion Multi-Office - -**Statut** : ✅ 100% | **Version** : 2.0.0 - -### Architecture Backend (Multi-Office) - -**Service** : `UserOfficeAffiliationsService` (~250 lignes) -**Fichier** : `lecoffre-back-main/src/services/notary/UserOfficeAffiliationsService/UserOfficeAffiliationsService.ts` - -**Middleware** : `ActiveOfficeInjector` (injection activeOfficeUid dans req.body) -**Fichier** : `lecoffre-back-main/src/app/middlewares/ActiveOfficeInjector.ts` - -**Endpoints** : - -- `GET /api/v1/notary/users/:uid/offices` : Liste offices utilisateur -- `POST /api/v1/notary/users/:uid/offices/active` : Changer office actif -- `POST /api/v1/notary/users/:uid/offices/sync` : Synchroniser depuis IdNot - -### Base de Données (Multi-Office) - -**Table** : `user_office_affiliations` - -**Champs** : - -- `user_uid` : Utilisateur -- `office_uid` : Office -- `role` : Rôle dans cet office -- `is_primary` : Office principal (booléen) - -**Unique** : `(user_uid, office_uid)` - -### Frontend (Multi-Office) - -**Store** : `ActiveOfficeStore.ts` (MobX, 150 lignes) -**Composant** : `OfficeSelector` (dropdown header, masqué si 1 seul office) -**API** : `UserOffices` (4 méthodes) - -### Fonctionnalités - -- **Sync automatique** : Depuis IdNot API Annuaire -- **Changement office** : Sans déconnexion (reload page) -- **Filtrage automatique** : Par office actif -- **Méthode `setPrimary()`** : Atomique (transaction) -- **Compatible** : Tous les TODOs existants - -### Middleware Injection - -**Transparente** : Ajoute `activeOfficeUid` dans tous les endpoints notary -**Alias** : `officeUid` pour compatibilité code existant - -### Switch d'Office : Régénération JWT et Permissions - -**Flux attendu** : - -1. `PUT /api/v1/notary/users/:userUid/offices/active` (auth requis, pas de `ruleHandler`) -2. `UserOfficeAffiliationsService.setActiveOffice(...)` vérifie l'affiliation `ACTIVE` et bascule `is_primary` -3. `AuthService.getUserJwtPayload(...)` régénère le JWT avec: - - nouvel `office_Id` - - `rules` recalculées (rôle d'office + règles globales) - - filtrage licence (`hasActiveSubscription`, `isVisitor`) pour le nouvel office - -4. Front: remplace les tokens, vérifie le cookie, et relit les droits (journaux: `office_Id`, `rulesCount`) - -**Points de contrôle** : - -- Le backend doit renvoyer de nouveaux tokens après chaque bascule. -- Le front doit vérifier la mise à jour effective du cookie `leCoffreAccessToken` puis conditionner l'UI. - ---- - -## Ancrage Blockchain - -LeCoffre.io utilise l'ancrage blockchain Bitcoin Signet pour garantir l'intégrité et la traçabilité des documents. - -### Évolution des Versions - -| Version | Blockchain | Périmètre | Déclenchement | Filigrane | Fallback | -|---------|-----------|-----------|---------------|-----------|----------| -| **V1** | Tezos | Dossiers uniquement | Manuel | ❌ Non | ⚠️ Lecture seule | -| **V2** | Bitcoin Signet | Docs + Dossiers | Auto | ❌ Non | - | -| **V3** | Bitcoin Signet | Docs + Dossiers | Auto | ✅ Oui | ✅ Tezos masqué | - -### Nouveautés V3 - -1. **Filigrane automatique** : "lecoffre.io" sur tous documents -2. **Double version** : Original + filigrané (conservés en BDD) -3. **ZIP enrichi** : Originaux + filigranés + certificats + preuves JSON -4. **Preuves structurées** : JSON standardisé pour vérification externe -5. **Ancrage dossier** : Hash du ZIP complet (pas seulement Merkle tree) -6. **Fallback Tezos** : Accès masqué aux anciens ancrages (lecture seule) - -### Principes Fondamentaux - -- **Ancrage immédiat** : L'ancrage est déclenché automatiquement lors de la validation d'un document -- **Hash filigrané** : Seule la version filigranée est ancrée sur blockchain -- **Double hash** : Hash original et hash filigrané sont stockés pour vérification -- **Preuve on-chain** : Le `tx_id` (transaction ID Bitcoin) est la seule preuve fiable d'ancrage - -### Services Backend - -- **WatermarkService** : Conversion documents → PDF (hors tableurs : Excel/ODS exclus), ajout filigrane "lecoffre.io" -- **DocumentAnchorsService** : Ancrage automatique des documents validés -- **OfficeFolderAnchorsService** : Ancrage des dossiers complets (ZIP) -- **BitcoinSignetService** : Ancrage hash sur Bitcoin Signet via API externe -- **ProofDataService** : Génération JSON de preuve d'ancrage - -### Processus d'Ancrage - -**Séquence de traitement (Upload)** : - -1. Calcul hash original (fichier original) -2. Filigrane (sur fichier original non chiffré) -3. Calcul hash filigrané et ancrage blockchain (hash filigrané, génération txid) -4. Chiffrement du document filigrané -5. Upload IPFS du document chiffré -6. Stockage métadonnées fichier (hash original + hash filigrané) -7. Stockage certificat BDD (document_anchor avec hash filigrané + hash original + txid) - -**Ancrage automatique** : Déclenché lors de la validation d'un document via `PUT /api/v1/notary/documents/:uid` (asynchrone, non bloquant). - -### Certificats et ZIP - -**Structure ZIP dossier complet** : - -```text -dossier_12345678/ -├── _LISEZ-MOI.txt -├── 01_originaux/ -├── 02_filigranes/ -├── 03_certificats/ -└── 04_preuves/ -``` - -**Certificats PDF** : Générés automatiquement pour les documents validés et ancrés, contenant les hashs complets, le lien de transaction, et toutes les métadonnées d'ancrage. - -**Documentation complète** : Voir `docs/ANCRAGE_COMPLETE.md` pour les détails complets de l'architecture V3, des processus d'ancrage, des certificats, et du troubleshooting. - ---- - -## Références - -- **Déploiement** : [DEPLOYMENT.md](./DEPLOYMENT.md) -- **Monitoring** : [DEPLOYMENT.md](./DEPLOYMENT.md#monitoring) -- **Ancrage** : [ANCRAGE_COMPLETE.md](./ANCRAGE_COMPLETE.md) -- **Base de données** : [DATABASE_COMPLETE.md](./DATABASE_COMPLETE.md) -- **Consolidation opérationnelle** : [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) -- **Correctifs et dépannage** : [FRONTEND.md](./FRONTEND.md#8-consolidation-evolutions-et-correctifs), [DEPLOYMENT.md](./DEPLOYMENT.md#maintenance-et-troubleshooting), [ANCRAGE_COMPLETE.md](./ANCRAGE_COMPLETE.md) - ---- - -**Dernière mise à jour** : 2026-01-28 diff --git a/projects/lecoffreio/docs/CODE_STANDARDS.md b/projects/lecoffreio/docs/CODE_STANDARDS.md deleted file mode 100644 index 3a3ed0d..0000000 --- a/projects/lecoffreio/docs/CODE_STANDARDS.md +++ /dev/null @@ -1,527 +0,0 @@ -# Code Standards - -**Version** : 2.0.0 -**Dernière mise à jour** : 2026-01-28 -**Périmètre** : Monorepo LeCoffre.io (`lecoffre-front-main`, `lecoffre-back-main`, `lecoffre-ressources-dev`) - -**Référence unique (checks de déploiement)** : [`docs/DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique`](./DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique) - ---- - -## 📋 Table des Matières - -1. [Qualité du Code](#qualité-du-code) -2. [Sécurité du Code](#sécurité-du-code) -3. [Patterns et Bonnes Pratiques](#patterns-et-bonnes-pratiques) -4. [Règles métier - Documents](#règles-métier---documents) -5. [Documentation Fonctionnelle](#documentation-fonctionnelle) - ---- - -## Qualité du Code - -### Principes Généraux - -- **Single Source of Truth** : toute règle doit être appliquée automatiquement (ESLint/TS/CI) et documentée ici. -- **Pas de dette cachée** : lorsqu'une règle est impossible à satisfaire (legacy), consigner l'exception dans [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) ou un plan d'action. -- **Symétrie Front/Back/Ressources** : mêmes seuils par défaut (longueur fichiers/fonctions, style TypeScript). - -### Règles Automatisées - -| Catégorie | Règle | Projet | Statut | -|-----------|-------|--------|--------| -| TypeScript | `@typescript-eslint/no-explicit-any` → `warn` | front/back/ressources | ✅ ESLint | -| TypeScript | `@typescript-eslint/no-unused-vars` + ignore `_` | front/back/ressources | ✅ ESLint | -| Hooks React | `react-hooks/rules-of-hooks` `error` | front | ✅ ESLint | -| Hooks React | `react-hooks/exhaustive-deps` `warn` | front | ✅ ESLint | -| Logs | `no-console` `warn` (front), `off` (back/ressources) | front/back/ressources | ✅ ESLint | -| Taille fichier | `max-lines` 250 lignes (blancs/commentaires exclus) | front/back/ressources | ✅ ESLint (warn front, error back) | -| Taille fonction | `max-lines-per-function` 40 lignes | front/back/ressources | ✅ ESLint (warn front, error back) | -| Paramètres | `max-params` 4 | front/back/ressources | ✅ ESLint | -| Profondeur | `max-depth` 4 | front/back/ressources | ✅ ESLint | -| Complexité | `complexity` 10 | front/back/ressources | ✅ ESLint | -| Callbacks | `max-nested-callbacks` 3 | front/back/ressources | ✅ ESLint | -| Markdown | MD032, MD033, MD040 | docs, user_stories, etc. | ✅ markdownlint (`npm run lint:markdown`) | -| Typecheck | `tsc --noEmit` | front/back/ressources | ✅ Scripts `npm run typecheck` (front) / `npx tsc --noEmit` (back & ressources) | -| Build | `npm run build` (tsc ou Next build) | front/back/ressources | ✅ Pipelines | -| Exports inutilisés | `ts-prune` | front/back/ressources | ✅ Scripts `npm run find-deadcode` (ressources/front/back/racine) | - -> **Note** : Les règles `max-lines` et `max-lines-per-function` génèrent des warnings ESLint. Si un fichier legacy dépasse, documenter la dette (cf. §4). - -### Bonnes Pratiques Spécifiques - -#### Frontend (Next.js/React) - -- Préférer les hooks composables (`useX`) plutôt que des composants "god object". -- Découper `pages/` en segments logiques (garder < 300 lignes par page). -- `DocumentTables`, `FolderInformation`, `ClientView` : surveiller particulièrement la taille (historique de dépassement). -- Pas de mutation directe du DOM/Réf hors `useRef`. -- Logger via `LoggerService` uniquement (pas de `console` brute). -- **Logging dans React** : Ne jamais logger dans le corps du composant ou dans le JSX. Toujours utiliser `useEffect` pour les logs de debug/info afin d'éviter les boucles de logs et les re-rendus infinis. -- **Gestion des dépendances useEffect** : Stabiliser les dépendances avec `useMemo` et `useCallback` pour éviter les re-rendus en boucle. Ne dépendre que des valeurs qui doivent réellement déclencher l'effet. -- **Pattern Controller/Vue** : toute page ou composant complexe doit suivre le triptyque - 1. **Hook contrôleur** (`useFeatureController`) pour les états, appels API, calculs dérivés. - 2. **Sous-composants présentateurs** pour découper l'UI (sections, cartes, listes). - 3. **Helpers mutualisés** (utils/services) lorsque les mêmes opérations sont utilisées par plusieurs écrans. Fonctions cible : < 40 lignes (voir `max-lines-per-function`). -- **SuperAdmin Health** : séparer systématiquement les widgets critiques en hooks spécialisés (`useHealthStatusTracker`, `useEndpointsHealthTester`, etc.) afin de conserver < 200 lignes par bloc et d'orchestrer la page via un contrôleur unique. -- **Refactors en cours** : les écrans `FolderInformation`, `ClientDashboard`, `DocumentTables`, `AddClientToFolder`, `DocumentsReminderHistory`, `AskDocuments`, `CreateCustomerNote`, `VerifyDocument`, `ThirdPartyLogin`, `AdminBlockchain` ont déjà adopté ce pattern. Toute nouvelle évolution doit s'aligner sur cette architecture et documenter les changements dans [README.md](./README.md#consolidation-operationnelle-ex-operationsmd). -- **Règle documentaire** : chaque refactor structurant (nouveau hook ou découpe majeure) doit être : - - Référencé dans [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) (problème, impacts, root cause, correctifs). - - Documenté dans la consolidation documentaire pour suivre les efforts qualité. - -#### Backend (Node/Express/TypeDI) - -- Un service par responsabilité ; pas de "multi-purpose services". -- Routes courtes : privilégier middleware/service -> controller. -- Toujours typer les réponses (DTO communs dans `lecoffre-ressources-dev`). -- Logger via Winston centralisé (`@Common/LoggerService`). -- Pour les wrappers CJS/ESM (`src/common/resources/*.ts`), toujours normaliser les constructeurs hydratables avec un helper d'unwrap (`default.default` -> `default` -> module) avant exposition de `HydratableClass`. - -#### Ressources TypeScript - -- Ne jamais importer du code applicatif (seulement types/pure helpers). -- Garder les DTO < 200 lignes (si plus : split + ré-export). - -### Gestion des Dépassements - -- **Détection** : ESLint `max-lines` / `max-lines-per-function` déclenche un `warn`. -- **Action** : - 1. Découper immédiatement si faisable (extractions composant/service). - 2. Sinon, ouvrir un ticket et consigner dans [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) ou en ticket : - - fichier/fonction impacté(e) - - taille actuelle + cible - - plan de refactor + échéance -- **Exceptions temporaires** : Ajouter un commentaire `// TODO(MAX_LINES)` avec justificatif + lien vers ticket. - -- Aucun override ESLint n'est autorisé ; toute fonction > 40 lignes doit être refactorée (warn front, error back). -- Tout fichier > 250 lignes déclenche un avertissement (front) ou erreur (back) : planifier un découpage. -- Documenter chaque dette résiduelle dans [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) (taille actuelle, cible, plan d'action, échéance) et ajouter un commentaire `// TODO(MAX_LINES)` dans le fichier concerné. -- Les modules textuels (CGU, Privacy, etc.) doivent être synchronisés avec la base via `deploy/scripts/build-and-deploy-local-seedSiteTexts.sh` après modification. -- Toute fonction dépréciée doit être supprimée ou migrée vers l'implémentation actuelle avant clôture du ticket. - -### Process de Validation - -1. `npm run lint` (front) / `npm run lint` (back) / `npm run lint` (ressources) -2. `npm run lint:markdown` (racine, pour MD032/MD033/MD040) -3. `npm run typecheck` (front) / `npx tsc --noEmit` (back & ressources) -4. `npm run build` (tous) -5. Vérifications manuelles spécifiques (ex: Next build warnings si variables manquantes) - -### Clôture corrections/évolutions - -À la fin de toute correction ou évolution, répondre systématiquement aux questions suivantes et implémenter les améliorations identifiées : - -- Modifications similaires à produire ailleurs dans le code ? -- Optimisations, mutualisations ou centralisations possibles ? -- Fichiers corrigés exempts d'erreurs de lint ? -- Vérification des types du projet OK ? -- Projet compile ? - -Si ces réponses ne sont pas fournies : les produire si pertinent et implémenter les améliorations identifiées (mutualisation, parties impactées). - -> La CI doit échouer si un warning "qualité" (taille, hooks, unused) est promu en `error`. Actuellement en `warn` pour migration progressive. - -### To-do Qualité - -| Item | Statut | Commentaire | -|------|--------|-------------| -| Suivi dettes `max-lines` existantes | 🔄 | Règles actives (250/40). Documenter toute exception dans [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) + `// TODO(MAX_LINES)` dans le fichier ; prioriser fichiers > 250 lignes et fonctions > 40 lignes. | - -### Références Qualité - -- `eslint.config.mjs` (front/back/ressources) -- [FRONTEND.md](./FRONTEND.md), [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) pour dettes détaillées -- CI / Scripts `deploy/scripts/*` -- `FILES_TOO_MANY_LINES.md` : Liste des fichiers avec plus de 250 lignes (générée automatiquement) -- `FUNCTIONS_TOO_MANY_LINES.md` : Liste des fonctions avec plus de 40 lignes (générée automatiquement) -- `parse-lint-results-improved.js` : Script pour générer les listes de fichiers et fonctions dépassant les limites - ---- - -## Sécurité du Code - -### Principes de Base - -1. **Least Privilege** : seules les routes/serveurs nécessaires sont exposés. -2. **Defense in Depth** : validations au frontend, à l'API et au niveau ORM. -3. **Zero Trust** : jamais de données "sûres" par défaut (même celles provenant d'intégrations tierces IdNot/Annuaire). -4. **No Secrets in Code** : toute donnée sensible réside dans la base de configuration (`system_configuration`) ou les services secrets (aucun `.env` permanent). - -### Backend (Node/Express/TypeDI) - -#### Authentification & Permissions - -- **JWT** : généré côté backend via IdNot OAuth (Notaires) / login client / tiers. - - Champs obligatoires : `userId`, `office_Id`, `role`, `rules`, `hasActiveSubscription`, `isGuest`. - - Expiration : Access 1h, Refresh 7j (cf. `docs/ARCHITECTURE.md`). -- **Middlewares** : - - `authHandler` : vérifie JWT (en-tête `Authorization` ou paramètre de requête `token` pour SSE uniquement, EventSource ne supportant pas les en-têtes), place l'utilisateur sur `req.user` et `req.body.user`. L'URL est nettoyée avant log (paramètre `token` supprimé). Voir `docs/ARCHITECTURE.md` et `docs/API.md`. - - `activeOfficeInjector` : injecte `req.activeOfficeUid`. - - `ruleHandler` : s'assure que l'utilisateur dispose de la règle (`resource/action`). -- **Multi-office** : `FolderInformation` & API utilisent `folder_office_uid` + `folder_sharings`. Aucun fallback vers `office_uid` direct. - -#### Validation des Entrées - -- **Class-validator** : tous les DTOs exposés doivent étendre `BaseDto` + décorateurs `@IsString`, `@IsUUID`, etc. -- **Paramètre de requête `q`** : utiliser `parseQueryParam(req.query["q"], "GET /...")` (voir `#Common/utils/jsonHelpers.ts`) pour tout parsing de `req.query["q"]`. Ne jamais utiliser `JSON.parse(req.query["q"])` directement (risque de crash et de prototype pollution). -- **Prisma** : aucun `where` construit à la main sans sanity-check (Obligation : `deleted_at: null` pour toutes les entités soft-delete). -- **Payload externe (IdNot, API Annuaire)** : vérifier les champs obligatoires avant insertion. Les erreurs (ex: payload incomplet) doivent être loggées, pas silencées. - -#### Stockage Sensible - -- **RIB** : AES-256-GCM (clé en base). Process et variables sensibles : voir [DATABASE_COMPLETE.md](./DATABASE_COMPLETE.md#configuration-systeme-dynamique). -- **Fichiers** : Pinata + S3. Seuls les hash + métadonnées sont gardés. -- **Secrets** : jamais dans le code. Toute nouvelle config doit transiter par `system_configuration`. - -#### Logging & Audits - -- **Winston** standard (level info/warn/error) + Sentry (optionnel). -- Aucune donnée sensible (RIB, tokens OAuth, OTP) dans les logs. -- Les actions critiques (ancrages, partages, login IdNot) doivent être loggées avec `folder_uid`, `userId`, `office_uid`. - -#### Protection API - -- **Rate limiting** (cf. `docs/DEPLOYMENT.md`) : 4 niveaux (public/strict/auth/global). -- **Validation d'accès** : les routes tierces (`/third-party/*`) doivent vérifier `folder_uid` et `third_party_uid`. Aucun accès ne doit être accordé sans double filtre (folder + user). -- **Ancrage** : l'état `tx_id` est la seule preuve (pas de `status = ANCHORED` sans TX). - -#### Antivirus Uploads - -- `AntivirusService` (Node) contacte `clamav` via INSTREAM (TCP 3310) avant toute transformation. -- Configuration dynamique (`system_configuration`) : - - `CLAMAV_HOST`, `CLAMAV_PORT` (obligatoires) - - `CLAMAV_SCAN_ENABLED` (toggle exceptionnel), `CLAMAV_CONNECTION_TIMEOUT_MS`, `CLAMAV_READ_TIMEOUT_MS`, `CLAMAV_MAX_FILE_SIZE_MB` -- Blocage strict : - - Malware détecté → `400` `ErrorMessages.FILE_INFECTED` (aucun fallback). - - Timeout / indispo ClamAV → `500` via `handleInternalError` (upload interrompu). -- Tous les services de fichiers (`FilesService`, `FilesNotaryService`, copies buffer) appellent `AntivirusService` avant hash/filigrane/ancrage. - -### Frontend (Next.js/React) - -#### Données & Permissions - -- **Stores** (MobX) : ne jamais stocker de secrets (uniquement les règles/role/office). -- **ActiveOfficeStore** : toujours utiliser `activeOfficeStore.activeOffice?.office_uid` plutôt que des `ids` en clair. -- **Invités** : `DefaultCustomerDashboard` devient la seule vue (pas de fallback notaire). Tout bouton/action doit vérifier `rolePermissionsStore.can(...)`. - -#### API Calls - -- Passer par `BaseApiService` (ajoute `Authorization` + logger). -- Jamais de fetch direct (ex: `fetch("/api/v1/...")`). -- Gestion d'erreurs utilisateur : `ToasterService`, jamais de `alert()` brut (sauf fallback tiers). -- Build : si les variables `NEXT_PUBLIC_*` manquent, le build doit logguer l'avertissement (déjà en place). - -#### XSS / DOM - -- Utiliser systématiquement `dangerouslySetInnerHTML` **uniquement** si la donnée est whitelistée & sanitized (ex: templates Markdown validés). -- Form inputs : valider côté frontend (`emailRegex`, `file size`, etc.) avant de poster. -- `document.cookie` n'est jamais manipulé en direct (uniquement via `AuthService`). - -### Ressources TypeScript - -- DTOs & enums ne doivent contenir aucune logique. -- Toute évolution de type doit être propagée dans le backend + frontend (contrôle croisé `npm run typecheck`). -- Les types `never` ou `any` sont interdits dans les exports publics. Si usage interne, commenter le TODO. - -### Workflows Sécurité - -| Vérification | Script / Étape | -|--------------|----------------| -| Lint (règles de taille, hooks, types) | `npm run lint` | -| TypeScript (front) | `npm run typecheck` | -| TypeScript (back/ressources) | `npx tsc --noEmit` | -| Build | `npm run build` | -| Hooks secrets (IdNot/Annuaire) | `docs/API.md`, `docs/ID.NOT ...` | -| Audit RIB | [DATABASE_COMPLETE.md](./DATABASE_COMPLETE.md#variables-sensibles) | - -### Checklist Pull Requests - -1. `npm run lint` + `npm run typecheck` sur les modules modifiés. -2. Vérifier que les logs ne divulguent aucune data sensible. -3. Ajouter/Mettre à jour la doc (ce document pour la dette, section Sécurité du Code et [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) si nouvelles surfaces). -4. Vérifier que toute nouvelle route tierce utilise `folder.uid` + `third_party.uid` (pas d'identifiant libre). -5. Pour les parcours invités : s'assurer que `DefaultCustomerDashboard` est utilisé (pas de fallback notaire). - -### Dettes & TODO Sécurité - -| Sujet | Statut | Action | -|-------|--------|--------| -| Inventaire `console.log` backend | 🔄 | Passer sur Winston (phase 2). | - -### Références Sécurité - -- [ARCHITECTURE.md](./ARCHITECTURE.md) -- [DATABASE_COMPLETE.md](./DATABASE_COMPLETE.md) (configuration système, variables sensibles, ClamAV) -- `docs/API.md` : rapport d'audit applicatif (logging tokens, parsing `q`, config sensible, token en URL SSE) -- [ANCRAGE_COMPLETE.md](./ANCRAGE_COMPLETE.md) -- [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) pour les incidents précis - ---- - -## Patterns et Bonnes Pratiques - -Ce document décrit les patterns réutilisables et les bonnes pratiques à suivre dans le projet LeCoffre.io. - -> **Note** : Le contenu détaillé des patterns est consolidé dans ce document (sections Patterns et Bonnes Pratiques ci-dessous). - -### Patterns Principaux - -#### Gestion d'Erreurs (Backend) - -**Modules** : - -- `lecoffre-back-main/src/common/utils/errorMessages.ts` : Messages d'erreur centralisés -- `lecoffre-back-main/src/common/utils/errorLoggers.ts` : Patterns de logging standardisés -- `lecoffre-back-main/src/common/utils/errorHandlers.ts` : Gestion HTTP centralisée -- `lecoffre-back-main/src/common/system/controller-pattern/BaseController.ts` : Toutes les méthodes `http*` délèguent désormais aux helpers ci-dessus (log + message homogènes) - -**Fonctions principales** : - -- `handleInternalError(response, error, req?, operation?)` : Gère et répond à une erreur interne -- `handleValidationError(response, error, req?, operation?)` : Gère et répond à une erreur de validation -- `handleNotFoundError(response, message?, req?, operation?)` : Gère et répond à une erreur 404 -- `handleForbiddenError(response, message?, req?, operation?)` : Gère et répond à une erreur 403 -- `handleBadRequestError(response, message?, req?, operation?)` : Gère et répond à une erreur 400 -- `logError(error, context?, operation?)` : Log une erreur avec format standardisé - -#### Helpers Utilisateurs (Backend) - -**Module** : `lecoffre-back-main/src/common/utils/userHelpers.ts` - -**Fonctions principales** : - -- `isSuperAdminUser(user, role?)` : Vérifie si un utilisateur est super-admin -- `extractUserData(bodyUser, authUser, permissionContext)` : Extrait et normalise les données utilisateur -- `getStringFromRecord(record, key)` : Extrait une valeur string d'un record -- `getBooleanFromRecord(record, key)` : Extrait une valeur boolean d'un record - -#### Helpers proof_data (Backend) - -**Module** : `lecoffre-back-main/src/common/utils/proofDataHelpers.ts` - -Helpers pour la validation et l'extraction de la structure proof_data (V3.0.0). - -**Fonctions principales** : - -- `getAntivirusStatusFromProofData(proofData)` : Extrait `antivirus_scan_at` de `proof_data.document` -- `isProofDataComplete(proofData)` : Vérifie si proof_data a la structure V3.0.0 complète (version, timestamp, document, anchor, verification) - -**Utilisé par** : `bilan-documents-pprod.ts`, `reprocess-all-documents-complete.ts`, `reprocess-incomplete-documents-from-ipfs.ts` - -#### Hook API Client (Frontend) - -**Module** : `lecoffre-front-main/src/front/Hooks/useApiClient.ts` - -Ce hook centralise la gestion des appels API frontend avec gestion standardisée des tokens, headers, et erreurs. - -**Usage** : - -```typescript -import { useApiClient } from "@Front/Hooks/useApiClient"; - -const apiClient = useApiClient(); -const data = await apiClient.get("/api/v1/notary/folders"); -``` - -#### Pattern Controller/Vue (Frontend) - -**Structure** : - -1. **Hook contrôleur** (`useFeatureController`) : Gère les états, appels API, calculs dérivés -2. **Sous-composants présentateurs** : Découpent l'UI en sections logiques -3. **Hooks spécialisés** : Extraient la logique métier complexe - -**Règles** : - -- Fonction principale : Doit rester < 40 lignes (orchestration uniquement, aligné sur `max-lines-per-function`) -- Hooks contrôleurs : Peuvent dépasser 40 lignes si la logique métier est complexe (documenter en TODO(MAX_LINES) si > 40) -- Sous-composants : Doivent rester < 40 lignes chacun - -#### DTOs et Validation Centralisée (Backend) - -**Module** : `lecoffre-back-main/src/common/dtos/` - -Les DTOs (Data Transfer Objects) fournissent une validation forte des entrées API avec `class-validator` et `class-transformer`. - -**Helper de Validation** : - -```typescript -import { validateRequestBody } from "@Common/utils/validationHelpers"; -import { CreateDocumentDTO } from "@Common/dtos"; - -const dto = await validateRequestBody( - CreateDocumentDTO, - req.body, - req, - response, - "POST /notary/documents" -); -``` - -### Patterns Réutilisables - -Pour une documentation complète de tous les patterns disponibles, consulter : - -- **Backend** : Helpers Prisma, SQL, UID extraction, Request helpers, Repository helpers, Date helpers, Array helpers, Type guards -- **Frontend** : Hooks (useDataLoader, useModal, useForm, useAsyncOperation, useSearchWithDebounce), Helpers (formatters, validators, error classifiers, retry helpers), Composants génériques (ConfirmModal, UpdateFormLayout, DocumentTableSection) - -#### Helpers Documents (Frontend) - -**Modules** : `lecoffre-front-main/src/front/Utils/` - -- `documentNameHelpers.ts` : `hasDocumentName`, `getDocumentNameWithTypePriority` (priorité : document_type.name > display_name > file_name > fallback) -- `documentTypeHelpers.ts` : `getDocumentTypeNameLower`, `getDocumentTypeName`, `isDocumentNotaireType`, `isDocumentTypeAutresDocuments`, `DOCUMENT_NOTAIRE_TYPE`, `DOCUMENT_NOTAIRE_LABEL`, `AUTRES_DOCUMENTS_TYPE` -- `statusHelpers.ts` : `statusUpper` (normalisation casse des statuts document) - -### Notes Importantes - -- **Toujours utiliser les modules centralisés** : Éviter de dupliquer la logique, utiliser les modules existants. -- **Migration progressive** : Migrer les fichiers au fur et à mesure, pas besoin de tout migrer d'un coup. -- **Tests** : Les modules centralisés sont plus faciles à tester unitairement. -- **Documentation** : Mettre à jour ce document si de nouveaux patterns sont ajoutés. -- **Logging React** : Ne jamais logger dans le corps du composant ou dans le JSX, toujours utiliser `useEffect`. -- **DTOs** : Utiliser les DTOs pour valider toutes les entrées API avec des messages d'erreur clairs. -- **Hydratation** : Utiliser les helpers d'hydratation pour une conversion cohérente des entités. -- **Services Façade** : Préférer les façades pour la simplicité, accéder aux services spécialisés pour les cas avancés. -- **Pattern Controller/Vue** : Appliquer systématiquement pour les composants > 40 lignes afin de respecter `max-lines-per-function`. -- **Request Helpers** : Utiliser les helpers `requestHelpers.ts` pour extraire les IDs depuis les requêtes au lieu de dupliquer la logique. -- **RequestWithPermissionContext** : Le type de base `Request & { permissionContext?: PermissionContext }` est exporté depuis `#Common/utils/requestHelpers`. L'importer pour typer les requêtes avec contexte de permission ; étendre localement si une route a besoin de champs supplémentaires (ex. `body.user`, `user`), par ex. `type LocalReq = RequestWithPermissionContext & { body?: { user?: ... } }`. - -#### Orchestrateurs de sortie documents (Backend) - -Pour les réponses API documents, utiliser exclusivement les orchestrateurs de post-hydratation : - -- `documentsNotaryPostHydrationHelper` pour `DocumentsNotary` -- `documentsPostHydrationHelper` pour `Documents` - -Règles : - -1. Ne pas reconstruire localement une sortie `plain` avec `instanceToPlain` + merge manuel dans les controllers/helpers. -2. Utiliser les fonctions `build*PlainWithRelations` / `hydrate*ToPlainWithRelations` pour les listes et mono-entités. -3. Conserver les garde-fous `log*FilesGuard` pour détecter les incohérences de `files`. -4. Toute nouvelle route documents doit réutiliser ces orchestrateurs. -5. Pour `Documents` côté `admin` / `super-admin`, les flux `update`, `refuse` et `delete` doivent retourner un `plain` construit via `documentsPostHydrationHelper` (pas de `hydrateEntity` retourné directement). -6. Pour les suppressions `Documents` (`notary`, `admin`, `super-admin`), utiliser le helper commun `buildDeletedDocumentPlainResponse` pour réinjecter `document_type` sans duplication locale. -7. Pour les récupérations mono-entité `Documents` destinées à une réponse API, utiliser `fetchDocumentWithResponseInclude` afin d’unifier `include` (`document_type` + include demandé) et éviter la duplication de `getByUid(..., { document_type: true })`. -8. Pour transformer une entité `Documents` récupérée en payload API mono-entité, utiliser `buildPlainFromFetchedDocument` pour centraliser `hydrateEntity + buildSingleDocumentPlainWithRelations`. -9. Pour les flux mono-entité `update/refuse/restore/delete`, utiliser `buildPlainResponseWithOptionalSourceDocumentType` pour unifier la réinjection optionnelle de `document_type` (source explicite si présente, sinon entité courante). -10. Pour `DocumentsNotary` mono-entité, utiliser `buildDocumentNotaryPlainResponseWithOptionalSource` pour homogénéiser le rendu `plain` (source optionnelle + réinjection fichiers). -11. Pour les contrôleurs `Documents` mono-entité (`admin/super-admin/notary`), privilégier `fetchDocumentPlainOrHandleNotFound` quand le flux suit le pattern `fetch + not found + build plain`. -12. Pour les contrôleurs `DocumentsNotary` mono-entité, privilégier `fetchDocumentNotaryPlainOrHandleNotFound` avec `withDocumentNotaryResponseInclude` afin d’imposer l’include de réponse et standardiser le `not found`. -13. Pour les validations `not found + deleted` sur `Documents`, réutiliser `createValidateDocumentNotDeletedCallback` plutôt que redéfinir des closures locales. -14. Pour les règles d’accès `DocumentsNotary` en mono-entité, utiliser `createDocumentNotaryAccessValidator` en callback réutilisable afin d’éviter la duplication `verify...Access` entre contrôleurs. -15. Pour la visibilité/état `DocumentsNotary` (statut métier, présence de fichiers visibles), utiliser `createDocumentNotaryVisibilityStateValidator` et l’injecter dans `fetchDocumentNotaryPlainOrHandleNotFound`. -16. Préférer le preset `createDocumentsNotarySentOrDownloadedVisibilityValidator` pour les routes de consultation/téléchargement `DocumentsNotary` (customer/notary) afin d’éviter la répétition des `allowedStatuses`. -17. Pour les endpoints de téléchargement `DocumentsNotary`, utiliser le preset `createDocumentsNotaryDownloadVisibilityValidator` (`requireAtLeastOneNonArchivedFile: true`) afin de mutualiser la règle “document téléchargeable” (statut + au moins un fichier non archivé). -18. Pour les flux fallback `Documents` (routes `DocumentsNotary` qui basculent sur la table `Documents`) et les contrôles transverses, utiliser les helpers communs `isDocumentSentOrDownloadedStatus` / `validateDocumentsSentOrDownloadedStatus` (`common/utils/documentsVisibilityStateHelpers.ts`) afin d’éviter les checks locaux divergents. -19. Pour les usages métier où `VALIDATED`, `SENT` et `DOWNLOADED` sont traités comme “document validé/exploitable” (naming/ZIP/export), utiliser `isDocumentValidatedOrSentOrDownloadedStatus` afin d’éviter les tableaux de statuts locaux. - ---- - -## Règles métier - Documents - -### Évolution planifiée (Documents unifiés) - -Voir [ARCHITECTURE.md - Évolution planifiée - Documents unifiés](./ARCHITECTURE.md#évolution-planifiée---documents-unifiés) pour : - -- Flux d'envoi unifié avec destinataire de type union (`customer` | `third_party` | `office` | `invited_notary`). -- Métadonnée `emitter` (uid, first_name, last_name, office_uid?, office_name?) sur toutes les réponses. -- Constante `DOCUMENT_DISPLAY_BASE_INCLUDE` (`#Common/constants/DocumentsIncludeConstants`) : inclure `emitter` avec `contact` et `folder` avec `stakeholders` dans tous les contextes où les documents sont affichés. -- Documents du notaire invité traités comme tiers/clients. - -### Statuts des documents - -Les états des documents sont gérés par membre et par document. Un statut sur le document d'un membre n'affecte en aucun cas un autre membre. - -- Chaque document (Documents ou DocumentsNotary) est lié à un destinataire unique (customer, tiers, confrère). -- Les mises à jour de statut (SENT, DOWNLOADED, VALIDATED, etc.) s'appliquent à un document identifié par son `uid`. -- Aucune mise à jour groupée ou propagation de statut entre documents de membres différents. - -### Tableau « Documents envoyés » - -Dans les espaces clients, tiers et notaires invités (onglets participants du dossier), le tableau des documents envoyés par le notaire liste des **documents** (une ligne = un document), et non des fichiers. - -- Chaque ligne du tableau correspond à une entrée `documents_notary` ou `documents`. -- L'affichage peut utiliser le nom du fichier PDF (ex. `document.pdf`) pour identifier visuellement le document, mais l'entité représentée reste le document. -- Un document peut contenir plusieurs fichiers fusionnés en un seul PDF ; la ligne représente le document, pas chaque fichier individuellement. - -### Upload : fusion et page de métadonnées - -Tous les uploads (1 ou plusieurs fichiers) sont fusionnés en un seul PDF avec page de métadonnées (hash des fichiers originaux). FileMergeService, MergedFileCreationHelper, FileMergeHelpers (buildFilesMetadataForMerge, buildMergedMulterFile). Pas de shortcut pour 1 fichier : toujours fusion. - -### Validation d'accès document (deux entrées, convergence) - -Deux chemins coexistent pour la validation d'accès aux documents ; l'objectif est de les faire converger vers une seule source de règles. - -1. **Service central** : `DocumentAccessValidationService` (`#Services/common/DocumentAccessValidationService`) — valide l'accès selon le type (guest_notary, invited_notary, thirdparty, customer, notary). Utilisé par la couche API via `createDocumentNotaryAccessValidator` (voir `documentsNotaryValidatorsHelpers.ts`) : les helpers (DocumentsNotaryGetOneByUidHelper, DocumentsNotaryMarkAsViewedHelper, DocumentsNotaryCaseHandlers) fournissent un callback `validateAccess` qui peut s'appuyer sur ce service. -2. **Middlewares** : `DocumentAccessValidationHelper` (OfficeMembershipHandlers), `FileHandlerAccessHelpers.validateDocumentAccess` / `validateFileAccess`, `DocumentHandlerAccessByUidHelper` — chargent le document, vérifient l'accès au dossier via `ensureFolderAccess` et appliquent des règles métier similaires. - -**Convergence prévue** : faire appeler par les middlewares le `DocumentAccessValidationService.validateAccess` en construisant un `DocumentAccessContext` à partir du contexte requête, afin d'éviter la double maintenance et les écarts de comportement. En attendant, toute modification des règles d'accès doit être répercutée dans les deux chemins ou documentée comme temporaire. - ---- - -## Documentation Fonctionnelle - -Cette section décrit de façon fonctionnelle chaque fichier du projet, expliquant son intérêt dans le projet et ce qu'il fait de façon résumée. - -> **Note** : Le contenu détaillé fichier par fichier est consolidé dans ce document (section Documentation Fonctionnelle ci-dessous). - -### Synthèse Optimisations Fiabilité & Simplicité (déc. 2025) - -| Axe | État | Détails | -| --- | --- | --- | -| Gestion d'erreurs | **Terminé** | `errorHandlers.ts`, `errorLoggers.ts`, `errorMessages.ts` et depuis 2025-12, toutes les méthodes `http*` du `BaseController` délèguent à ces helpers (logs Winston homogènes, messages centralisés). | -| Résolution permissions/rôles | **Terminé** | Découpage de `RulesHandler` en `decisionHandlers`, `ruleMatcher`, `resourceResolver`, `roleResolver` + helpers `userHelpers.ts`, `roleNormalizer.ts`, `uidExtractors.ts`. | -| API client frontend | **Terminé** | Hook `useApiClient` + helpers `errorClassifiers`, `retryHelpers`, `formValidators`, `formatters` pour supprimer les appels `fetch` dupliqués. | -| Duplication helpers JWT / extraction utilisateur | **✅ Terminé** | Helpers centralisés dans `userHelpers.ts` (`getStringFromRecord`, `getBooleanFromRecord`, `getStringArrayFromRecord`, `extractUserData`) - tous les fichiers migrés. | -| Validation formulaires frontend | **✅ Terminé** | Validations centralisées dans `front/Utils/formValidators.ts` (`validateEmail`, `validatePassword`, `validateRequired`, etc.) - regex inline remplacées. | -| Formatage prix / données | **✅ Terminé** | Fonctions centralisées dans `front/Utils/formatters.ts` (`formatPrice`, `formatDate`, `formatMonthString`, `truncateHash`, etc.) - tous les fichiers utilisent les helpers centralisés. | -| Tests & documentation | **En cours** | Chaque lot migré doit être consigné dans [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) (root cause, impacts) et dans ce document (CODE_STANDARDS.md). | - -### Structure du Projet - -#### Backend (lecoffre-back-main/src/) - -- **Entries** : Points d'entrée (`App.ts`, `Cron.ts`) -- **App** : Configuration Express, middlewares, routes -- **App/api/customer** : Contrôleurs et helpers customer ; constantes statuts dans `api/customer/constants/documentStatusConstants.ts` (`DOCUMENTS_TO_SEND_STATUSES`, `DOCUMENT_STATUSES_RECEIVED_FROM_NOTARY`) pour alignement sémantique avec le front et réutilisation (ex. filtre « documents à envoyer »). `DocumentsQueryHelper` et requêtes customer peuvent s'y référer (voir JSDoc). -- **Common** : Utilitaires partagés (error handlers, helpers, DTOs, emails) -- **Services** : Services métier (Files, Documents, Folders, Users, etc.) - -#### Frontend (lecoffre-front-main/src/) - -- **Pages** : Pages Next.js (routes) -- **Front** : Composants, hooks, stores, API clients, utils - -#### Ressources (lecoffre-ressources-dev/src/) - -- **Admin** : Types et DTOs pour l'administration -- **Customer** : Types et DTOs pour les clients -- **Notary** : Types et DTOs pour les notaires -- **SuperAdmin** : Types et DTOs pour le super-admin -- **MemberTypes** : Types centralisés pour `userType` (JWT), `role` (role_permissions_matrix) et `UserContext` (frontend). Constantes : `USER_TYPE_THIRDPARTY`, `USER_TYPE_CUSTOMER`, `ROLE_CLIENT`, `ROLE_NOTARY`, `ROLE_GUEST_NOTARY`, `USER_CONTEXT_CUSTOMER`, `USER_CONTEXT_NOTARY`, `USER_CONTEXT_INVITED_NOTARY`, `USER_CONTEXT_THIRD_PARTY`, `THIRD_PARTY_UI_VARIANT`, `ROLES`. Helpers : `isRole`, `isThirdParty`, `isCustomer`, `isGuestNotary`, `isNotaryRole`. - -### Documentation Complète - -La documentation détaillée fichier par fichier a été archivée (voir historique Git). Les patterns et la structure sont dans les sections ci-dessus. README des sous-projets : `lecoffre-back-main`, `lecoffre-front-main`, `lecoffre-ressources-dev`. - ---- - -## Sécurité Applicative - -Résumé : **Chiffrement** (clé maître `FILE_ENCRYPTION_MASTER_KEY`, AES-256-GCM, tous les fichiers) ; **2FA tiers** (request-code/verify-code, codes 6 chiffres, JWT `userType: 'thirdparty'`) ; **Déconnexion** (table `revoked_tokens`, `POST /api/v1/auth/logout`) ; **ClamAV** (AntivirusService, daemon clamd, config dans `system_configuration`). Détail : [DATABASE_COMPLETE.md](./DATABASE_COMPLETE.md) et [SCRIPTS.md](./SCRIPTS.md). - ---- - -## Références Générales - -- `eslint.config.mjs` (front/back/ressources) -- `docs/ARCHITECTURE.md` : Architecture générale du système -- `docs/DEPLOYMENT.md` : Guide de déploiement -- `docs/API.md` : Documentation des APIs externes -- [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) : Documentation des problèmes et correctifs -- [ARCHITECTURE.md](./ARCHITECTURE.md) et [FRONTEND.md](./FRONTEND.md) : Documentation des évolutions - ---- - -**Dernière mise à jour** : 2026-02-27 diff --git a/projects/lecoffreio/docs/DEPLOYMENT.md b/projects/lecoffreio/docs/DEPLOYMENT.md deleted file mode 100644 index 5a1c37c..0000000 --- a/projects/lecoffreio/docs/DEPLOYMENT.md +++ /dev/null @@ -1,2361 +0,0 @@ -# Déploiement LeCoffre.io - Guide Complet - -**Dernière mise à jour** : 2026-03-10 -**Version** : 2.0.1 - -Ce document consolide toute la documentation relative au déploiement (scripts, variables, logging, Nginx, SSL). - ---- - -## 📋 Table des Matières - -1. [Vue d'ensemble](#vue-densemble) -2. [Prérequis](#prérequis) -3. [Configuration](#configuration) -4. [Variables d'environnement](#variables-denvironnement) -5. [Déploiement principal](#déploiement-principal) -6. [Cartographie des checks de déploiement (source unique)](#cartographie-des-checks-de-déploiement-source-unique) -7. [Logs de déploiement](#logs-de-déploiement) -8. [Scripts et commandes](#scripts-et-commandes) -9. [Maintenance et troubleshooting](#maintenance-et-troubleshooting) -10. [API d'ancrage Bitcoin séparée](#api-dancrage-bitcoin-séparée) -11. [Sécurité](#sécurité) -12. [Monitoring](#monitoring) -13. [Disaster Recovery](#disaster-recovery) - ---- - -## Vue d'ensemble - -LeCoffre.io utilise un système de déploiement automatisé via SSH orchestré depuis le **proxy** (point d’entrée unique). - -Le mode de déploiement recommandé est **`deploy/scripts_v2/`** (services systemd sur les serveurs d’environnements) : - -- Synchronisation Git -- Installation des dépendances système sur l’hôte (PostgreSQL, ClamAV, Chromium, LibreOffice, Java…) -- Build backend/frontend -- Services `systemd` (backend/cron/frontend/router) -- Migrations Prisma, injection de configuration (`system_configuration`), scripts métier (Stripe, ré-ancrage…) - -**Environnements disponibles** : - -- `test` : Environnement de test -- `pprod` : Pré-production -- `prod` : Production -- `demo` : Démonstration - ---- - -## Prérequis - -### Sur la machine locale (déploiement) - -- **Git** : Pour le versioning et la synchronisation -- **Node.js 25+** : Pour la compilation TypeScript -- **npm** : Gestionnaire de paquets -- **rsync** : Synchronisation des fichiers -- **SSH avec clé configurée** : Accès au serveur distant - -### Sur la machine distante (cible) - -- **Accès SSH avec clé** -- **PostgreSQL** (service système, port 5432) -- **ClamAV** (service système, port 3310) -- **Chromium** (binaire `/usr/bin/chromium`) -- **LibreOffice + Java** -- **Node.js 25+ et npm** (pour build et exécution). Mise à jour si inférieur : `bash deploy/scripts_v2/run-update-node-25-on-servers.sh` (test, pprod, prod). - -### Configuration SSH - -```bash -# Configurer la clé SSH -ssh-copy-id -i ~/.ssh/id_ed25519 ncantu@4nk.myftp.biz - -# Tester la connexion -ssh ncantu@4nk.myftp.biz "echo OK" -``` - ---- - -## Configuration - -### 1. Fichier `.env.` - -Créer `.secrets/test/.env.test`, `.secrets/pprod/.env.pprod`, ou `.secrets/prod/.env.prod` avec : - -```bash -# Database -# Variables construites dynamiquement depuis ENV : -# DATABASE_HOST=127.0.0.1 -# DATABASE_PORT=5432 -# DATABASE_USERNAME=lecoffre-user-${ENV} -# DATABASE_NAME=bdd-${ENV} -# Seule variable requise : -DATABASE_PASSWORD=*** - -# Deployment (scripts_v2) -# Les déploiements sont exécutés sur le proxy (4nk.myftp.biz / 192.168.1.100). -# Les environnements sont sur le réseau privé : -# - test : 192.168.1.101 → test.lecoffreio.4nkweb.com -# - pprod : 192.168.1.102 → pprod.lecoffreio.4nkweb.com -# - prod : 192.168.1.103 → prod.lecoffreio.4nkweb.com -DEPLOY_SSH_USER=ncantu -DEPLOY_SSH_KEY=~/.ssh/id_ed25519 - -# Frontend (sera injecté en base lors du premier déploiement) -NEXT_PUBLIC_SPLASH_MESSAGE=Environnement de test - -# Grafana (monitoring) -GRAFANA_ADMIN_USER=grafana-admin -GRAFANA_ADMIN_PASSWORD=change_me -``` - -### 1bis. Git (scripts_v2) - -`deploy/scripts_v2/deploy.sh` effectue automatiquement, **en local**, avant toute synchronisation côté cible : - -- `npm run lint:fix` sur `lecoffre-ressources-dev`, `lecoffre-back-main`, `lecoffre-front-main` (non bloquant) (le déploiement échoue si lint:fix retourne une erreur non corrigeable) -- `git add -A` -- `git commit` (message au format imposé par le dépôt) -- `git push origin $DEPLOY_GIT_BRANCH` - -Le script est **strict** : - -- il exige `DEPLOY_GIT_BRANCH` (dans `.secrets//.env.` ou exporté) -- la branche locale courante doit être exactement `$DEPLOY_GIT_BRANCH` -- la cible est synchronisée **uniquement** sur `origin/$DEPLOY_GIT_BRANCH` (pas d’autre ref) - -### 1ter. Migrations requises (scripts_v2) - -En mode `scripts_v2`, certaines actions **exigent** que les migrations Prisma soient exécutées dans la même commande (mode strict) : - -- `--resetDatabase` ⇒ **doit** être accompagné de `--migrateResolveDatabase` -- `--resetDatabaseFromSchemaExport` ⇒ **doit** être accompagné de `--migrateResolveDatabase` (voir ci-dessous) -- `--setSettings`, `--seedSiteTexts`, `--importStripe`, `--reanchorAll`, `--promoteSuperAdmins`, `--activateSubscriptions` ⇒ **doivent** être accompagnés de `--migrateResolveDatabase` - -**Variables d'automatisation (stockées en BDD via `system_configuration`) :** - -- `SUPERADMIN_EMAILS` : Liste d'emails (séparés par des virgules) promus automatiquement en super-admin lors du déploiement avec `--promoteSuperAdmins` -- `AUTO_ACTIVATE_SUBSCRIPTIONS_EMAILS` : Liste d'emails (séparés par des virgules) dont tous les abonnements seront activés/créés automatiquement lors du déploiement avec `--activateSubscriptions` - -#### Baseline DB v2 depuis un export de schéma (sans rejouer l'historique des migrations) - -Quand les dumps disponibles sont **uniquement en v1** (et/ou que l'historique des migrations pose problème), `scripts_v2` permet de reconstruire une base **v2 vide** directement depuis un export SQL **schema-only** : - -- Le script applique un export SQL **schema-only** sur une base vide : - - `deploy/schema-v2.sql` si présent (prioritaire) - - sinon `deploy/scripts_v2/schema-v2-initial.sql` (export de référence v2 fourni dans le repo) -- Puis exécute `prisma migrate resolve --applied` sur **toutes** les migrations présentes dans le repo (sans les exécuter) pour que Prisma considère la base comme "à jour" -- Ensuite, `--migrateResolveDatabase` peut s'exécuter (il ne doit appliquer que des migrations futures, au-delà de la baseline) - -**Fichier requis :** - -- `deploy/schema-v2.sql` (optionnel) : export du schéma cible (schema-only) compatible `psql -f` - - il peut contenir des directives `psql` (ex: `\restrict` / `\unrestrict`) : `scripts_v2` les filtre lors de l'application - - si besoin, utiliser `deploy/schema-v2.sql.example` comme guide - -**Commande type :** - -```bash -bash deploy/scripts_v2/deploy.sh test \ - --resetDatabaseFromSchemaExport \ - --migrateResolveDatabase \ - --deploy \ - --skipCheckLint -``` - -#### Import v1 → v2 : Premier import (full) vs Imports incrémentaux (merge) - -**Premier import (full, destructif) :** - -Le flag `--importV1Data` effectue un import complet depuis un dump v1 : - -- Applique la compatibilité layer v1→v2 (enums + colonnes manquantes) -- **TRUNCATE** toutes les tables (sauf `_prisma_migrations` et `system_configuration`) -- Restaure les **TABLE DATA** du dump v1 via `pg_restore --data-only` - -```bash -bash deploy/scripts_v2/deploy.sh test \ - --resetDatabaseFromSchemaExport \ - --migrateResolveDatabase \ - --importV1Data \ - --deploy \ - --skipCheckLint -``` - -**Imports incrémentaux (merge, non-destructif) :** - -Le flag `--importV1DataMerge` effectue un merge des données v1 dans la v2 existante : - -- Applique la compatibilité layer v1→v2 (enums + colonnes manquantes) -- Restaure le dump v1 dans une **DB temporaire** -- Effectue des **INSERT ... ON CONFLICT DO UPDATE** par table (upsert) -- **Préserve** les données v2 existantes -- Intègre les nouvelles données v1 - -```bash -# Script wrapper (recommandé) -bash deploy/scripts_v2/merge-v1-data-into-v2.sh test - -# Ou directement -bash deploy/scripts_v2/deploy.sh test \ - --importV1DataMerge \ - --skipCheckLint -``` - -**Workflow recommandé :** - -1. **Premier import** : `install-or-reset-env-from-schema-export.sh ` (baseline + import full) -2. **Imports suivants** : `merge-v1-data-into-v2.sh ` (merge incrémental) - -**Prérequis pour les imports :** - -- Le dump v1 doit être présent localement : `.secrets//bdd.` -- Format attendu : custom-format `pg_dump` (PGDMP) - -**Détails techniques (merge) :** - -- Les tables sans PK/UK sont **ignorées** (risque de doublons) -- Les colonnes communes entre v1 et v2 sont synchronisées -- Les colonnes v1 absentes en v2 sont ignorées (après compat layer) -- Les colonnes v2 absentes en v1 sont préservées (non modifiées) - -### 2. Base de données - -La base PostgreSQL doit être créée et accessible. Le script : - -- ✅ Applique les migrations Prisma automatiquement -- ✅ Synchronise les données (IdNot, Annuaire, Stripe) -- ✅ Valide les identifiants PostgreSQL avant déploiement -- ❌ Ne crée PAS la base (à faire manuellement) - -**Validation des identifiants PostgreSQL** : -Le script valide automatiquement les identifiants (`DATABASE_USERNAME` / `DATABASE_PASSWORD`) avant de démarrer le déploiement. En cas d'échec de connexion, le script affiche un message d'erreur explicite et s'arrête. - -**Gestion des migrations Prisma** : - -- Le schéma Prisma est dans `lecoffre-back-main/prisma/schema.prisma` -- Les migrations sont dans `prisma/migrations/` -- Le script crée automatiquement un lien symbolique `src/common/databases/migrations` → `prisma/migrations` pour que Prisma trouve les migrations -- Les migrations sont appliquées via `npm run migrate` (qui utilise `prisma migrate deploy`) -- Détection automatique des migrations baselinées (marquées comme appliquées sans être exécutées) et réinitialisation si nécessaire - -### 3. Configuration ClamAV (system_configuration via import-env-to-db) - -Les variables suivantes ne vont pas dans `.env.` mais dans `.secrets//env-full--for-bdd-injection.txt` (puis importées via `config:import-env`) : - -```bash -CLAMAV_HOST=127.0.0.1 -CLAMAV_PORT=3310 -CLAMAV_SCAN_ENABLED=true -CLAMAV_CONNECTION_TIMEOUT_MS=8000 -CLAMAV_READ_TIMEOUT_MS=150000 -CLAMAV_MAX_FILE_SIZE_MB=120 -``` - -> Sans ces clés, le backend refuse désormais les uploads (aucun fallback). - ---- - -## Variables d'environnement - -### Vue d'ensemble - -Le script `build-and-deploy.sh` lit ses configurations de déploiement depuis les fichiers `.env.` au lieu d'avoir des valeurs hardcodées. - -### Variables requises - -Chaque fichier `.env.test`, `.env.pprod`, `.env.prod` doit contenir : - -#### Variables de déploiement SSH - -```bash -# Host SSH de la machine cible -DEPLOY_SSH_HOST= - -# Utilisateur SSH (optionnel, défaut: debian) -DEPLOY_SSH_USER=debian - -# Clé SSH (optionnel, défaut: ~/.ssh/id_rsa) -DEPLOY_SSH_KEY=~/.ssh/id_rsa - -# Répertoire de déploiement sur la machine distante -DEPLOY_REMOTE_PATH=/home/debian/sites/test-lecoffreio.4nkweb.com - -# Domaine pour Nginx et SSL -DEPLOY_DOMAIN=test-lecoffreio.4nkweb.com -``` - -### Configuration par environnement - -#### Test - -```bash -DEPLOY_SSH_HOST= -DEPLOY_SSH_USER=debian -DEPLOY_REMOTE_PATH=/home/debian/sites/test-lecoffreio.4nkweb.com -DEPLOY_DOMAIN=test-lecoffreio.4nkweb.com -``` - -#### Pré-production - -```bash -DEPLOY_SSH_HOST= -DEPLOY_SSH_USER=debian -DEPLOY_REMOTE_PATH=/home/debian/sites/pprod-lecoffreio.4nkweb.com -DEPLOY_DOMAIN=pprod-lecoffreio.4nkweb.com -``` - -#### Production - -```bash -DEPLOY_SSH_HOST= -DEPLOY_SSH_USER=debian -DEPLOY_REMOTE_PATH=/home/debian/sites/prod-lecoffreio.4nkweb.com -DEPLOY_DOMAIN=prod-lecoffreio.4nkweb.com -``` - -### Fonctionnement - -1. Le script `build-and-deploy.sh` vérifie que `.env.` existe -2. Il source le fichier avec `source .env.` -3. Il mappe les variables `DEPLOY_*` vers les variables internes du script -4. Il valide que toutes les variables requises sont définies -5. Il bloque si `DEPLOY_SSH_HOST=TBD` (environnement non configuré) - -### Avantages - -- ✅ **Pas de valeurs hardcodées** dans le script -- ✅ **Configuration centralisée** dans les fichiers `.env` -- ✅ **Cohérence** avec le reste de l'architecture (config en BDD) -- ✅ **Facilité de maintenance** : modifier `.env.*` au lieu du script -- ✅ **Sécurité** : les `.env.*` sont en `.gitignore` -- ✅ **Validation** : le script vérifie que toutes les variables sont présentes - ---- - -## Déploiement principal - -### Commande simple - -```bash -./deploy/scripts/build-and-deploy.sh test # Environnement test -./deploy/scripts/build-and-deploy.sh pprod # Environnement pprod -./deploy/scripts/build-and-deploy.sh prod # Environnement prod -``` - -### Options de déploiement - -```bash -# Déploiement complet avec toutes les étapes -./deploy/scripts/build-and-deploy.sh test --deploy --cert --resetDatabase --setSettings --syncIdNot --syncDirectory - -# Opérations post-déploiement (conteneurs déjà actifs) -./deploy/scripts/build-and-deploy.sh pprod --syncIdNot --syncDirectory --reanchorAll - -# Import Stripe et réancrage -./deploy/scripts/build-and-deploy.sh test --importStripe --reanchorAll - -# Promotion super-admins depuis liste config -./deploy/scripts/build-and-deploy.sh pprod --promoteSuperAdmins - -# Activation abonnements depuis liste config -./deploy/scripts/build-and-deploy.sh pprod --activateSubscriptions -``` - -**Flags disponibles :** - -- `--deploy` : Build et déploiement complet (redémarre les services systemd) -- `--resetDatabase` : ⚠️ DESTRUCTIF : Recréer la base et importer depuis `.secrets//bdd.` - ➜ **Important** : relancer ensuite `npm run config:import-env -- --env ` sur le serveur cible puis reseeder `role_permissions_matrix` (cf. [DATABASE_RESET_GUIDE](DATABASE_RESET_GUIDE.md)) - -- `--migrateResolveDatabase` : Appliquer les migrations Prisma manquantes (prisma migrate deploy) - - ✅ **Vérifications systématisées** : Vérifie et ajoute automatiquement les colonnes manquantes (`proof_data`, `confirmations`, `anchor_job_id`) dans les tables d'ancrage (`document_anchors`, `document_notary_anchors`) - - ✅ **8 points de vérification** : Les colonnes sont vérifiées à 8 endroits différents pour garantir leur présence - - ✅ **Gestion des erreurs** : Retry automatique et messages d'erreur explicites si l'ajout de colonnes échoue - - 📖 Voir [ANCRAGE_COMPLETE.md](./ANCRAGE_COMPLETE.md) pour les corrections détaillées sur les colonnes d'ancrage -- `--setSettings` : Injection configurations depuis fichier env-full-* -- `--seedSiteTexts` : Exécuter le seed des textes du site (`node dist/scripts/seed-site-texts.js`) -- `--syncIdNot` : Synchronisation utilisateurs IdNot OAuth -- `--syncDirectory` : Synchronisation API Annuaire -- `--cert` : ⚠️ DESTRUCTIF : Ecrase les certificats existant, activer la gestion des certificats Let's Encrypt (sinon aucune action certificat) -- `--reanchorAll` : ⚠️ DESTRUCTIF : Supprimer toutes les ancres et réancré tous les documents (tous statuts), documents notaires et RIB - - ✅ **Vérification préalable** : Vérifie l'existence des tables avant d'ajouter les colonnes manquantes - - ✅ **Vérification finale** : Bloque le réancrage si les colonnes indispensables (`proof_data`, `confirmations`) manquent - - ✅ **Ordre garanti** : S'exécute toujours après `--migrateResolveDatabase` pour garantir la présence des colonnes - - 📖 Voir [ANCRAGE_COMPLETE.md](./ANCRAGE_COMPLETE.md) pour les corrections détaillées sur les colonnes d'ancrage -- `--promoteSuperAdmins` : Promotion automatique des super-admins configurés en BDD (clé SUPERADMIN_EMAILS) -- `--activateSubscriptions` : Activation automatique des abonnements pour les emails configurés en BDD (clé AUTO_ACTIVATE_SUBSCRIPTIONS_EMAILS) -- `--importStripe` : Synchronisation plans et abonnements Stripe -- `--getToken` : Génère un couple access/refresh token pour le premier super-admin disponible (promotion automatique de `laurence@notaires.fr` si aucun super-admin actif) -- `--checkLint` : Exécuter les lints/TypeScript/compilation checks avant le build (activé par défaut) -- `--skipCheckLint` : Désactiver les vérifications pré-build (lint, TypeScript, compilation) -- `--skipBuildChecks` : Désactiver uniquement les build checks (lint et typecheck toujours exécutés) -- `--skipDiskCleanup` : Ignorer le nettoyage de l'espace disque (peut accélérer le déploiement) -- `--skipBackupsRecovery` : Ignorer la récupération des sauvegardes sur la machine locale -- `--aggressiveCleanup` : ⚠️ Nettoyage agressif de l'espace disque -- `--showLogs` : Afficher les logs des services (backend/frontend) après le déploiement -- `--showLogsService ` : Service spécifique pour --showLogs (backend, frontend, ou all, défaut: backend) -- `--showLogsTail ` : Nombre de lignes de logs à afficher (défaut: 200) -- `--showLogsSince ` : Filtrer les logs depuis (ex: 1h, 10m, 2024-12-02T10:00:00) - - -#### Mise à jour des secrets *site texts* (pprod / prod) - -Les fichiers `.secrets//seed-site-texts-.ts` et `.secrets//env-full--for-bdd-injection.txt` ne doivent pas être versionnés dans le dépôt applicatif cible. La source de vérité est le dépôt utilisé pour déclencher le déploiement. - -- **Seed** : copier le contenu du seed de test vers le seed de l’environnement cible, puis adapter uniquement les mentions d’environnement (ex. retirer `[ENVIRONNEMENT DE TEST]` en prod). -- **Env-full** : aligner la ligne `SITE_TEXTS_KEYS_INDEX=...` avec celle de test (même liste et même ordre) pour garantir que les clés seedées sont bien indexées. - -Après mise à jour des secrets, un déploiement standard (`deploy/scripts_v2/deploy.sh`) synchronise les fichiers `.secrets//` vers la cible et exécute le seed si `--seedSiteTexts` est actif. - -**Exemples d'usage :** - -```bash -# Déploiement complet environnement from scratch -./deploy/scripts/build-and-deploy.sh test --deploy --cert --resetDatabase --setSettings --syncIdNot --syncDirectory --importStripe --reanchorAll - -# Synchronisation quotidienne sans interruption -./deploy/scripts/build-and-deploy.sh pprod --syncIdNot --syncDirectory --importStripe - -# Réancrage tous dossiers existants (maintenance - DESTRUCTIF) -./deploy/scripts/build-and-deploy.sh test --reanchorAll - -# Mise à jour code avec redéploiement -./deploy/scripts/build-and-deploy.sh pprod --deploy - -# Génération d'un token super-admin sans redéployer -./deploy/scripts/build-and-deploy.sh pprod --getToken - -# Déploiement avec affichage des logs backend -./deploy/scripts/build-and-deploy.sh pprod --deploy --showLogs --showLogsService backend --showLogsTail 500 --showLogsSince 1h -``` - -> ℹ️ Pour les tests manuels, il est possible de récupérer un jeton en local via `npm run token:get -- --json`. - -### Processus complet - -Le script exécute automatiquement : - -#### Phase 1 : Build local - -1. ✅ Compilation TypeScript de `lecoffre-ressources-dev` -2. ✅ Vérification du build (87 fichiers .js) - -#### Phase 2 : Synchronisation - -1. ✅ Test connexion SSH -2. ✅ Création répertoire distant si nécessaire -3. ✅ Rsync de tous les fichiers (excluant node_modules, .git, logs...) -4. ✅ Transfert des fichiers `.env.` - -#### Phase 3 : Build distant - -1. ✅ Backup base de données (pg_dump) -2. ✅ Build backend/frontend -3. ✅ Chargement variables `.env.` - -#### Phase 4 : Certificats SSL - -1. ✅ Vérification si certificats existent -2. ✅ Génération certificat auto-signé temporaire si absent -3. ✅ Démarrage services (systemd) -4. ✅ Obtention certificat Let's Encrypt réel -5. ✅ Rechargement Nginx avec certificat - -#### Phase 5 : Déploiement - -1. ✅ Arrêt services existants (systemd) -2. ✅ Démarrage services (systemd) -3. ✅ Attente démarrage (10s) -4. ✅ Vérification statut (systemctl) -5. ✅ Cleanup builds obsolètes - -#### Phase 6 : Synchronisation données (si flags activés) - -1. ✅ `--setSettings` : Injection configurations depuis `env-full--for-bdd-injection.txt` - - Commande utilisée côté serveur : `npm run config:import-env -- --env ` (dans le répertoire backend) - - Doit être rejouée manuellement après `--resetDatabase` - - ✅ Depuis 2025-12-02, le transfert des fichiers sensibles sous Windows passe par un répertoire de staging pour `Compress-Archive`, ce qui garantit que les chemins relatifs (`deploy/...`) sont bien conservés lors de l'extraction distante. -2. ✅ `--syncIdNot` : Synchronisation utilisateurs IdNot OAuth -3. ✅ `--syncDirectory` : Synchronisation offices/personnes API Annuaire -4. ✅ `--importStripe` : Synchronisation plans et abonnements Stripe -5. ✅ `--reanchorAll` : ⚠️ DESTRUCTIF : Supprimer toutes les ancres et réancrer tous les documents et dossiers -6. ✅ `--promoteSuperAdmins` : Promotion super-admins depuis liste emails -7. ✅ `--getToken` : Génération d'un access/refresh token super-admin (trace `logs/token-superadmin-*.log`) -8. ✅ Logs sauvegardés dans `logs/` - -#### Phase 7 : Scripts de fix (automatique) - -1. ✅ **Exécution automatique** : Tous les scripts `.sh` dans `deploy/fix/` sont exécutés automatiquement après le déploiement -2. ✅ **Ordre alphabétique** : Les scripts sont exécutés dans l'ordre alphabétique -3. ✅ **Déplacement automatique** : Après exécution réussie, chaque script est déplacé dans `deploy/fixDone/` -4. ✅ **Arrêt en cas d'erreur** : Si un script échoue, le processus s'arrête et le script reste dans `deploy/fix/` -5. ✅ **Contexte complet** : Chaque script reçoit `ENV` et `DOMAIN` comme paramètres et a accès à toutes les variables d'environnement nécessaires - -#### Phase 8 : Vérifications finales - -1. ✅ Statut services systemd -2. ✅ Health check backend (`/api/v1/public/health`) -3. ✅ Logs accessibles - -### Première fois (from scratch) - -#### Option 1 : Script automatique - -```bash -# Déployer de A à Z (inclut SSL, migrations, sync) -./deploy/scripts/build-and-deploy.sh test -``` - -Le script détecte automatiquement que c'est un premier déploiement et : - -- Génère les certificats SSL -- Applique toutes les migrations Prisma -- Synchronise les données - -#### Option 2 : Script init (legacy) - -```bash -# Alternative : script init manuel (moins maintenu) -./deploy/scripts/init-environment.sh test admin@4nkweb.com -``` - ---- - -## Cartographie des checks de déploiement (source unique) - -Cette section fait foi pour distinguer ce qui est exécuté automatiquement pendant un déploiement standard (`deploy/scripts_v2/deploy.sh`) et ce qui reste manuel. - -### Snippet de renvoi standard (gabarit interne) - -Position attendue dans chaque document concerné : en tête du fichier, juste après le titre et les métadonnées éventuelles. - -Règle unique : aucun bloc local de checks de déploiement dans les autres documents. Conserver uniquement le snippet de renvoi, avec éventuellement une phrase de contexte métier non-check. - -Depuis un document situé dans `docs/` : - -```md -**Référence unique (checks de déploiement)** : [`docs/DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique`](./DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique) -``` - -Depuis un document situé hors `docs/` : - -```md -**Référence unique (checks de déploiement)** : [`docs/DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique`](../docs/DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique) -``` - -### Checks automatiques (pipeline de déploiement) - -| Check | Déclenchement | Script source | Statut | -|-------|---------------|---------------|--------| -| Vérification API d'ancrage (`health` + test d'ancrage) | Systématique dans `deploy.sh` | `deploy/scripts_v2/deploy.sh` (`test_anchor_api_blocking`) | Automatique | -| Migrations Prisma (`prisma generate` + `prisma migrate deploy`) | Systématique dans `deploy.sh` | `deploy/scripts_v2/remote/migrate-resolve-database.sh` | Automatique | -| Build backend/frontend sur cible | Systématique en déploiement normal/import | `deploy/scripts_v2/remote/deploy-app.sh` | Automatique | -| Vérification post-déploiement (health + systemd + logs récents) | Systématique après déploiement/import | `deploy/scripts_v2/_lib/git-flow.sh` (`verify_deployment_success`) | Automatique | -| Lint/typecheck/build locaux | Option `--checkLint` | `deploy/scripts_v2/deploy.sh` | Optionnel | - -### Scripts manuels (hors pipeline automatique) - -| Script | Usage | Statut | -|--------|-------|--------| -| `deploy/scripts_v2/run-verify-db-connection.sh` | Vérification ciblée connectivité DB | Manuel | -| `deploy/scripts_v2/run-verify-invited-received-documents.sh` | Vérification SQL ciblée documents invités | Manuel | -| `deploy/scripts_v2/run-check-role-permissions-matrix.sh` | Vérification droits/rôles | Manuel | -| `deploy/scripts_v2/run-check-logs-and-db-invite-notaire-587.sh` | Diagnostic ciblé logs + DB | Manuel | -| `deploy/scripts_v2/run-check-user-offices-logs.sh` | Diagnostic ciblé logs `UserOffices` (offices vides / sync IdNot) | Manuel | -| `deploy/scripts_v2/run-copy-connectdb-if-missing.sh` | Remédiation ciblée fichier connectDB | Manuel | -| `deploy/scripts_v2/run-set-settings.sh` | Exécution manuelle set-settings | Manuel | - -### Scripts orphelins nettoyés - -- Supprimés car non appelés par le pipeline et non référencés comme étapes obligatoires : - - `lecoffre-back-main/test-audit.js` - - `lecoffre-back-main/test-audit.js.map` - - `lecoffre-back-main/scripts/test-guest-folders-query.ts` - - `lecoffre-back-main/scripts/test-guest-folders-complete.ts` - ---- - -## Architecture (scripts_v2 - host-native) - -### Vue d'ensemble - -Dans l’infrastructure proxy `192.168.1.100`, le SSL et le routage sont gérés **uniquement sur le proxy**. -Les serveurs d’environnements (`192.168.1.101/102/103`) exposent uniquement des **ports applicatifs HTTP**. - -Le déploiement est orchestré par `deploy/scripts_v2/deploy.sh`. - -En fin d'exécution, `deploy/scripts_v2/deploy.sh` affiche l'heure de fin au format `HH:MM` pour faciliter la lecture chronologique des logs de déploiement. - -### Structure des fichiers sur un serveur d’environnement - -```tree -/srv/4NK/test.lecoffreio.4nkweb.com/ -├── deploy/ # Scripts + fichiers de déploiement versionnés -├── deploy/scripts_v2/ # Scripts v2 (host-native) -├── lecoffre-back-main/ # Backend -├── lecoffre-front-main/ # Frontend -└── lecoffre-ressources-dev/ # Ressources partagées -``` - -### Services système (systemd) - -- `lecoffreio-backend@` -- `lecoffreio-cron@` -- `lecoffreio-frontend@` -- `lecoffreio-router@` (port service, ex: 3009) - ---- - -## Logs de déploiement - -### Script principal `build-and-deploy.sh` - -Le script principal **ne produit pas automatiquement** de logs. Il faut rediriger la sortie manuellement : - -```bash -# Exemple recommandé -./deploy/scripts/build-and-deploy.sh pprod --deploy > logs/build-and-deploy-pprod.log 2>&1 -``` - -**Format recommandé** : `logs/build-and-deploy-.log` - -**Avantages de la redirection manuelle** : - -- Contrôle total sur le nom du fichier -- Peut inclure des informations supplémentaires (date, numéro de déploiement) -- Permet de gérer plusieurs logs simultanés - -**Exemples d'utilisation** : - -```bash -# Log simple -./deploy/scripts/build-and-deploy.sh pprod --deploy > logs/build-and-deploy-pprod.log 2>&1 - -# Log avec timestamp -./deploy/scripts/build-and-deploy.sh pprod --deploy > logs/build-and-deploy-pprod-$(date +%Y%m%d_%H%M%S).log 2>&1 - -# Log avec affichage en temps réel (tee) -./deploy/scripts/build-and-deploy.sh pprod --deploy 2>&1 | tee logs/build-and-deploy-pprod.log -``` - -### Copie automatique vers serveur distant - -Le script `build-and-deploy.sh` copie automatiquement les logs de déploiement vers le serveur distant après l'exécution (succès ou échec) pour que Promtail puisse les collecter. - -**Fonctionnement** : - -- Vérifie si le fichier de log local existe -- Crée le répertoire `logs/` sur le serveur distant s'il n'existe pas -- Copie le log avec un nom horodaté (`build-and-deploy--YYYYMMDD-HHMMSS.log`) -- Le nom correspond au pattern Promtail : `build-and-deploy-*.log` -- La copie est non-bloquante (ne fait pas échouer le déploiement si elle échoue) - -Voir [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) pour la consolidation et [ANCRAGE_COMPLETE.md](./ANCRAGE_COMPLETE.md) pour le dépannage ancrage. - -### Répertoire des logs - -#### Structure - -```text -logs/ -├── build-and-deploy-pprod.log # Log principal (manuel) -├── build-and-deploy-pprod-20250102_143022.log # Log horodaté (copié automatiquement) -├── sync-idnot-20251124_143022.log # Sync IdNot (automatique) -├── sync-directory-20251124_143100.log # Sync Directory (automatique) -├── import-stripe-20251124_144000.log # Import Stripe (automatique) -├── reanchor-all-20251124_150000.log # Réancrage complet (automatique) -├── token-superadmin-20251124_151000.log # Token super-admin (automatique) -└── activate-subscriptions-20251124_152000.log # Activation abonnements (automatique) -``` - -#### Création automatique - -Le répertoire `logs/` est créé automatiquement par : - -- `build-and-deploy.sh` (ligne 451) -- `run_sync_idnot()` (ligne 853) -- `run_sync_directory()` (ligne 887) - -### Gestion des logs volumineux - -#### Compression automatique - -Les logs de synchronisation sont automatiquement compressés si >5MB : - -- Réduction à 2000 dernières lignes -- Compression en `.gz` -- Affichage des 20 dernières lignes après compression - -#### Nettoyage - -Les logs ne sont **pas nettoyés automatiquement**. Il est recommandé de : - -- Archiver les anciens logs -- Supprimer les logs >30 jours -- Surveiller l'espace disque - -**Script de nettoyage recommandé** : - -```bash -# Supprimer les logs >30 jours -find logs/ -name "*.log" -mtime +30 -delete -find logs/ -name "*.log.gz" -mtime +90 -delete -``` - ---- - -## Scripts et commandes - -### Mise à jour Node.js 25 sur test, pprod, prod - -Si la version de Node sur les serveurs est inférieure à 25, exécuter (depuis la machine locale, les scripts font le SSH via le proxy) : - -```bash -bash deploy/scripts_v2/run-update-node-25-on-servers.sh -``` - -Ce script copie et exécute `deploy/scripts_v2/remote/update-node-25.sh` sur chaque hôte (test, pprod, prod). Idempotent : ne modifie rien si Node >= 25. Utilise NodeSource `setup_25.x` avec repli sur `setup_current.x` si 25 n’est pas disponible. - -### Scripts de déploiement - -#### `deploy/scripts_v2/install-or-reset-env.sh` (scripts_v2) - -**Bootstrap / reset complet en 1 commande (DESTRUCTIF)** pour `test` et `pprod`. - -Ce wrapper exécute un cycle complet : - -- setup host (`--setupHost`) -- reset DB depuis `.secrets//bdd.` (`--resetDatabase`) -- migrations Prisma (`--migrateResolveDatabase`) -- build+restart (`--deploy`) -- import settings en BDD (`--setSettings`) -- scripts métier (`--seedSiteTexts`, `--importStripe`, `--reanchorAll`, `--promoteSuperAdmins`, `--activateSubscriptions`) - -```bash -# Usage -bash deploy/scripts_v2/install-or-reset-env.sh test -``` - -#### `deploy/scripts_v2/install-or-reset-env-from-schema-export.sh` (scripts_v2) - -**Bootstrap / reset complet en 1 commande (DESTRUCTIF)** pour `test` et `pprod`, basé sur une **baseline schema-only** (utile quand les dumps disponibles sont uniquement en v1). - -Ce script enchaîne : - -- `--resetDatabaseFromSchemaExport` (création DB v2 vide depuis export SQL schema-only) -- `--importV1Data` (import des TABLE DATA v1 dans la DB v2) - - `scripts_v2` prépare automatiquement une **compat layer v1→v2** (types/colonnes manquants) en restaurant le schéma v1 dans une DB temporaire puis en appliquant le diff sur la DB v2 - - `scripts_v2` n'importe pas `_prisma_migrations` (baseline Prisma gérée à part) ni `system_configuration` (géré via `--setSettings`) -- `--migrateResolveDatabase` (baseline Prisma via `migrate resolve --applied` + migrations futures) -- `--deploy` + scripts post-déploiement (`--setSettings`, `--seedSiteTexts`, `--importStripe`, `--reanchorAll`, `--promoteSuperAdmins`, `--activateSubscriptions`) - -```bash -# Usage -bash deploy/scripts_v2/install-or-reset-env-from-schema-export.sh test -``` - -⚠️ Pour `prod`, ce wrapper refuse d'exécuter (risque de reset DB). Utiliser `deploy/scripts_v2/deploy.sh` et valider les flags explicitement. - -#### `deploy/scripts_v2/merge-v1-data-into-v2.sh` (scripts_v2) - -**Merge incrémental v1 → v2 (NON-DESTRUCTIF)** pour `test`, `pprod` et `prod`. - -Ce script effectue un merge des données v1 dans la v2 existante, puis exécute les scripts post-déploiement : - -**Étapes du merge :** - -1. Applique la compatibilité layer v1→v2 (enums + colonnes manquantes) -2. Restaure le dump v1 dans une DB temporaire -3. Effectue des upserts (INSERT ... ON CONFLICT DO UPDATE) par table -4. **Préserve** les données v2 existantes -5. Intègre les nouvelles données v1 - -**Scripts post-déploiement systématisés :** - -Après le merge, le script exécute automatiquement : - -- `--migrateResolveDatabase` : Applique les migrations Prisma (requis, ajouté automatiquement) -- `--setSettings` : Import des configurations depuis `env-full--for-bdd-injection.txt` -- `--seedSiteTexts` : Initialise les textes du site -- `--importStripe` : Importe les abonnements Stripe -- `--reanchorAll` : Réancrage complet des documents (DESTRUCTIF pour les ancres existantes) -- `--promoteSuperAdmins` : Promotion des super-admins (via `SUPERADMIN_EMAILS` en DB) -- `--activateSubscriptions` : Activation des abonnements (via `AUTO_ACTIVATE_SUBSCRIPTIONS_EMAILS` en DB) - -**Note :** Ces scripts sont systématisés pour garantir la cohérence des environnements après chaque merge v1→v2. - -```bash -# Usage -bash deploy/scripts_v2/merge-v1-data-into-v2.sh test -``` - -**Workflow recommandé :** - -1. **Premier import** : `install-or-reset-env-from-schema-export.sh ` (baseline + import full + scripts post-déploiement) -2. **Imports suivants** : `merge-v1-data-into-v2.sh ` (merge incrémental + scripts post-déploiement) - -**Prérequis :** - -- Le dump v1 doit être présent localement : `.secrets//bdd.` -- Format attendu : custom-format `pg_dump` (PGDMP) - -**Détails techniques (merge) :** - -- Les tables sans PK/UK sont **ignorées** (risque de doublons) -- Les colonnes communes entre v1 et v2 sont synchronisées -- Les colonnes v1 absentes en v2 sont ignorées (après compat layer) -- Les colonnes v2 absentes en v1 sont préservées (non modifiées) -- Les lignes avec NULL dans des colonnes NOT NULL sont automatiquement filtrées - -#### `build-and-deploy.sh` - -**Script principal de déploiement complet** - -Déploie l'application de A à Z sur un environnement distant via SSH. - -```bash -# Usage -./deploy/scripts/build-and-deploy.sh [options] - -# Exemples -./deploy/scripts/build-and-deploy.sh test --deploy --cert --resetDatabase --setSettings -./deploy/scripts/build-and-deploy.sh pprod --syncIdNot --syncDirectory -./deploy/scripts/build-and-deploy.sh prod --deploy --importStripe --reanchorAll -``` - -#### `init-environment.sh` (legacy) - -**Initialisation d'un nouvel environnement** - -Alternative pour la première fois (moins maintenu que `build-and-deploy.sh`). - -```bash -# Usage -./deploy/scripts/init-environment.sh - -# Exemple -./deploy/scripts/init-environment.sh pprod nicolas.cantu@pm.me -``` - -**Note :** `build-and-deploy.sh` est maintenant autonome et **recommandé** pour tous les cas. - -#### `bump-version.sh` - -**Mise à jour de version** - -```bash -# Usage -./deploy/scripts/bump-version.sh "" - -# Exemple -./deploy/scripts/bump-version.sh 2.1.0 "Nouveaux exports" -``` - -### Scripts de maintenance - -#### `cleanup-remote-disk-aggressive.sh` - -**Nettoyage agressif de l'espace disque** - -```bash -# Usage -bash deploy/scripts/cleanup-remote-disk-aggressive.sh -``` - -**Actions effectuées** : - -1. Arrêt des services systemd (backend, frontend, cron, router) -2. Nettoyage agressif (builds, caches, journaux) -3. Nettoyage des anciens builds -4. Nettoyage des caches système (apt, journaux, logs) -5. Nettoyage des caches npm et node_modules -6. Nettoyage des fichiers temporaires (/tmp, /var/tmp, builds, logs) -7. Nettoyage Git agressif (reflog, packs volumineux) -8. Affichage de l'état du disque avant et après - -**Précautions** : - -- La base de données PostgreSQL n'est pas modifiée -- Les node_modules nécessaires pour le build sont préservés -- Le script affiche l'état du disque avant et après le nettoyage - -#### `repair-remote-git.sh` - -**Réparation du repository Git corrompu** - -```bash -# Usage -bash deploy/scripts/repair-remote-git.sh -``` - -**Cas d'utilisation** : - -- Erreur `BUG: diff-lib.c:633: run_diff_index must be passed exactly one tree` -- Erreur `fatal: unresolved deltas left after unpacking` -- Erreur `fatal: unpack-objects failed` -- Erreur `invalid sha1 pointer` - -**Actions effectuées** : - -1. Sauvegarde des fichiers sensibles (.env, nginx) -2. Nettoyage des fichiers Git verrouillés -3. Tentative de réparation avec `git fsck` et `git gc` -4. Si le repository est trop corrompu : recréation complète -5. Restauration des fichiers sensibles -6. Vérification de l'état final - -### Scripts de fix automatiques - -#### Vue d'ensemble - -Les scripts de fix sont des scripts bash optionnels placés dans `deploy/fix/` qui sont exécutés automatiquement après chaque déploiement. Ils permettent d'appliquer des correctifs ponctuels ou des mises à jour de configuration sans modifier le code principal. - -#### Méthode de déploiement - -1. **Placement** : Créer un script `.sh` dans `deploy/fix/` -2. **Exécution automatique** : Le script `build-and-deploy-local-fix.sh` est appelé automatiquement par `build-and-deploy-local.sh` après le déploiement -3. **Ordre** : Les scripts sont exécutés dans l'ordre alphabétique -4. **Déplacement** : Après exécution réussie, le script est automatiquement déplacé dans `deploy/fixDone/` - -#### Structure obligatoire - -```bash -#!/bin/bash - -# Description du script -# Usage: Ce script est exécuté automatiquement par build-and-deploy-local-fix.sh - -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -DEPLOY_SCRIPTS_DIR="$(cd "$SCRIPT_DIR/../scripts" && pwd)" -source "$DEPLOY_SCRIPTS_DIR/build-and-deploy-local-common.sh" - -ENV="$1" -DOMAIN="$2" - -if [ -z "$ENV" ]; then - error "ENV manquant" - exit 1 -fi - -if [ -z "$DOMAIN" ]; then - error "DOMAIN manquant" - exit 1 -fi - -init_deploy_context "$ENV" "$DOMAIN" -validate_database_credentials - -# Votre code ici -``` - -#### Règles importantes - -1. **Paramètres** : Le script doit accepter `ENV` et `DOMAIN` comme paramètres -2. **Initialisation** : Utiliser `init_deploy_context` pour charger les variables d'environnement -3. **Vérifications** : Vérifier l'état des services systemd et les credentials BDD avant d'exécuter -4. **Code de retour** : Retourner `0` en cas de succès, `1` en cas d'échec -5. **Logging** : Utiliser les fonctions `info`, `success`, `error`, `warning` du script commun - -### Scripts utilitaires backend - -#### Promotion des super-administrateurs - -La promotion/dé-promotion se fait via les scripts npm du backend, exécutés **sur le serveur cible** : - -```bash -# Sur le serveur, dans le répertoire backend -npm run promote:superadmin -- --email user@notaires.fr -npm run promote:superadmin -- --uid 123e4567-e89b-12d3-a456-426614174000 -``` - -- Le script applique automatiquement les contraintes (rechargement configuration, connexion BDD via Prisma). -- L'utilisateur doit se reconnecter pour régénérer ses jetons après promotion. - -#### Générer un token super-admin - -```bash -npm run token:get -- --json -``` - -Ce script promeut automatiquement un super-admin si aucun n'est présent, puis génère un couple access/refresh token. - -#### Activation des abonnements utilisateur - -L'activation automatique passe par `npm run activate:subscriptions` (backend) : - -```bash -npm run activate:subscriptions -- user@notaires.fr -``` - -- Recherche par email, UID ou IdNot. -- Crée un abonnement UNLIMITED si nécessaire et active ceux existants. -- Fournit un résumé pour chaque office. - -Dans les pipelines de déploiement (`--activateSubscriptions`), cette commande est appelée automatiquement pour chaque email configuré dans `AUTO_ACTIVATE_SUBSCRIPTIONS_EMAILS`. - ---- - -## Maintenance et troubleshooting - -### Vérifications post-déploiement - -#### Logs - -```bash -# Depuis la machine distante -ssh debian@ -cd /home/debian/sites/test-lecoffreio.4nkweb.com - -# Logs temps réel -journalctl -u lecoffreio-backend@ -f -journalctl -u lecoffreio-frontend@ -f -journalctl -u lecoffreio-cron@ -f - -# Logs synchronisation -tail -100 logs/sync-all-*.log | grep -E "ok|error|✅|❌" -``` - -#### Tests - -```bash -# Health check -curl https://test-lecoffreio.4nkweb.com/api/v1/public/health | jq - -# Page d'accueil -curl -I https://test-lecoffreio.4nkweb.com/ - -# Config publique -curl https://test-lecoffreio.4nkweb.com/api/v1/public/config | jq -``` - -- Interface Super Admin `https://test-lecoffreio.4nkweb.com/super-admin/health` - - Permet de lancer des tests *live* (ancrage Signet, SMS OVH, e-mail Mailchimp, upload Pinata, lectures ID.NOT). - - Chaque carte affiche le résultat détaillé : lien mempool, URL IPFS, modales JSON IdNot, confirmation "Message envoyé". - - La section "Résumé base de données" reprend les volumes principaux (ex: `offices`). - -#### Base de données - -```bash -# Compter les enregistrements -psql -h $DATABASE_HOST -p $DATABASE_PORT -U $DATABASE_USERNAME -d $DATABASE_NAME \ - -c "SELECT 'offices' as table, COUNT(*) FROM offices UNION SELECT 'users', COUNT(*) FROM users;" -``` - -### Maintenance - -#### Re-déploiement - -```bash -# Simple -./deploy/scripts/build-and-deploy.sh test - -# Le script : -# - Détecte que SSL existe déjà → ne régénère pas -# - Applique nouvelles migrations uniquement -# - Re-synchronise les données -``` - -#### Renouvellement SSL - -Les certificats sont renouvelés automatiquement par Certbot (toutes les 12h). - -Pour forcer le renouvellement : - -```bash -ssh debian@ -cd /home/debian/sites/test-lecoffreio.4nkweb.com -# Renouvellement certificats (sur le proxy ou serveur où Certbot est installé) -certbot renew -# Rechargement Nginx (sur le proxy) -nginx -s reload -``` - -#### Rollback - -```bash -# Restaurer backup BDD -ssh debian@ -cd /home/debian/sites/test-lecoffreio.4nkweb.com -psql -h ... -U ... -d bdd.test < backups/bdd-test_YYYYMMDD_HHMMSS.sql - -# Re-déployer version précédente -git checkout -./deploy/scripts/build-and-deploy.sh test -``` - -### Troubleshooting - -#### Erreur : `Variable DEPLOY_SSH_HOST manquante` - -→ Vérifier que `.env.` contient toutes les variables `DEPLOY_*` - -#### Erreur : `Impossible de se connecter` - -→ Vérifier clé SSH : `ssh debian@` - -#### Erreur : Certificat Let's Encrypt échoue - -→ Vérifier DNS : `host test-lecoffreio.4nkweb.com` -→ Vérifier ports : `nc -zv 80 443` - -#### Backend en crash loop - -→ Vérifier logs : `ssh ... 'journalctl -u lecoffreio-backend@ -n 200'` -→ Vérifier BDD accessible : tester avec psql -→ Vérifier migrations Prisma : marquer comme appliquées si nécessaire - -#### Erreur : Connexion PostgreSQL impossible - -→ Vérifier que `DATABASE_USERNAME` et `DATABASE_PASSWORD` dans `deploy/.env.` sont corrects -→ Vérifier que l'utilisateur PostgreSQL existe : `psql -U postgres -c "\du"` -→ Vérifier que l'utilisateur a les permissions sur la base : `psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE bdd-test TO lecoffre-user-test;"` - -#### Erreur : No migration found in prisma/migrations - -→ Vérifier que le répertoire `prisma/migrations` existe dans le conteneur -→ Vérifier que le lien symbolique `src/common/databases/migrations` → `prisma/migrations` est créé -→ Le script crée automatiquement ce lien lors du déploiement avec `--migrateResolveDatabase` - -#### Erreur : Disque plein (Out of diskspace) - -→ Utiliser le script de nettoyage : `bash deploy/scripts/cleanup-remote-disk-aggressive.sh ` -→ Vérifier l'espace disque : `ssh ... 'df -h'` -→ Le script libère généralement 15-20GB d'espace - -#### Erreur : Repository Git corrompu - -→ Utiliser le script de réparation : `bash deploy/scripts/repair-remote-git.sh ` -→ Erreurs courantes : `unresolved deltas`, `invalid sha1 pointer`, `unpack-objects failed` -→ Le script sauvegarde les fichiers sensibles avant réparation - -#### Synchronisation échoue - -→ Vérifier credentials IdNot dans `system_configuration` -→ Vérifier les logs applicatifs (API IdNot / Annuaire) côté backend - -#### Erreur : `curl_error` variable sans liaison (deploy.sh) - -**Symptômes :** `./deploy/scripts_v2/deploy.sh: ligne 281: curl_error : variable sans liaison` lorsque l'API d'ancrage échoue et que le fichier d'erreur curl est vide. - -**Root cause :** Avec `set -u`, toute référence à une variable non définie provoque une erreur. `curl_error` n'est défini que si le fichier d'erreur curl contient des données ; sinon la variable est utilisée sans exister. - -**Correctif :** Initialiser `local curl_error=""` avant usage. Fichier : `deploy/scripts_v2/deploy.sh`. - -### Vérifications et debugging - -#### Vérifier les certificats sur le host - -```bash -# Sur le serveur distant -ssh debian@ - -cd /home/debian/sites/test-lecoffreio.4nkweb.com - -# Lister certificats -sudo ls -la deploy/nginx/certbot/conf-test/live/test-lecoffreio.4nkweb.com/ - -# Vérifier validité -sudo openssl x509 -in deploy/nginx/certbot/conf-test/live/test-lecoffreio.4nkweb.com/fullchain.pem -noout -dates -``` - -#### Vérifier la configuration Nginx - -```bash -# Tester configuration (sur le host) -cat deploy/nginx/nginx-test.conf - -# Tester configuration Nginx -nginx -t - -# Recharger Nginx -nginx -s reload -``` - -#### Vérifier les montages et la configuration Nginx - -```bash -# Voir les volumes montés -# Vérifier la configuration Nginx sur le proxy -nginx -T - -# Exemple de sortie : -# /home/debian/sites/test-lecoffreio.4nkweb.com/deploy/nginx/nginx-test.conf -> /etc/nginx/nginx.conf -# /home/debian/sites/test-lecoffreio.4nkweb.com/deploy/nginx/certbot/conf-test -> /etc/letsencrypt -# /home/debian/sites/test-lecoffreio.4nkweb.com/deploy/nginx/certbot/www-test -> /var/www/certbot -``` - -#### Vérifier les logs - -```bash -# Logs Nginx (sur le proxy) -journalctl -u nginx -n 50 - -# Logs backend (sur le serveur env) -journalctl -u lecoffreio-backend@ -n 50 - -# Certbot : selon l'installation (cron ou service systemd) -``` - ---- - -## API d'ancrage Bitcoin séparée - -L'API d'ancrage Bitcoin est déployée sur un serveur séparé. - -**Production** : `anchorage.certificator.4nkweb.com` (HTTPS) - -### Configuration - -```bash -cp env.bitcoin.api.deploy.example .env.bitcoin.api.deploy -nano .env.bitcoin.api.deploy # Configurer serveur Bitcoin -``` - -**Variables requises** : - -```bash -REMOTE_HOST=@ -REMOTE_DIR=~/dev/lecoffre-anchor-api -LOCAL_DIR=~/dev/lecoffre-anchor-api -PM2_APP_NAME=lecoffre-anchor-api -SSH_KEY=$HOME/.ssh/id_ed25519_4nk - -# Production -ANCHORE_API_URL=https://anchorage.certificator.4nkweb.com -ANCHORE_API_KEY= -``` - -### Déploiement - -```bash -./deploy-anchor-api.sh -``` - -### Infrastructure - -- **Serveur Production** : `anchorage.certificator.4nkweb.com` (HTTPS) -- **Répertoire** : `/home//dev/lecoffre-anchor-api` -- **Health** : `https://anchorage.certificator.4nkweb.com/health` - ---- - -## Sécurité - -### Certificats SSL - -- ✅ Certificats SSL Let's Encrypt valides (renouvellement auto toutes les 12h) -- ✅ Certificats en lecture seule pour Nginx -- ✅ Permissions strictes sur les clés privées (600) - -### Configuration - -- ✅ Fichiers `.env.*` en `.gitignore` -- ✅ Certificats SSL Let's Encrypt valides -- ✅ Configurations sensibles en BDD (masquées) -- ✅ CORS strictement configuré -- ✅ Rate limiting actif -- ✅ Pas de ports exposés (sauf 80/443 via Nginx) - -### Permissions des certificats - -```bash -# Les certificats doivent appartenir à root (générés par Certbot en root) -sudo chown -R root:root deploy/nginx/certbot/conf-test/ - -# Permissions recommandées -sudo chmod 755 deploy/nginx/certbot/conf-test/live/test-lecoffreio.4nkweb.com/ -sudo chmod 644 deploy/nginx/certbot/conf-test/live/test-lecoffreio.4nkweb.com/fullchain.pem -sudo chmod 600 deploy/nginx/certbot/conf-test/live/test-lecoffreio.4nkweb.com/privkey.pem -``` - -### Lecture seule - -Les certificats et configurations Nginx sont configurés en **lecture seule** sur le système de fichiers. - -**Avantages** : - -- Nginx ne peut pas modifier les certificats -- Séparation des privilèges - ---- - -## Architecture récapitulative - -```text -┌─────────────────────────────────────────────────────────────┐ -│ HOST (Serveur distant) │ -│ /home/debian/sites/test-lecoffreio.4nkweb.com/ │ -│ │ -│ ┌─────────────────┐ ┌──────────────────┐ │ -│ │ deploy/nginx/ │ │ deploy/nginx/ │ │ -│ │ nginx-test.conf │ │ certbot/ │ │ -│ │ │ │ ├─ conf-test/ │ │ -│ │ (Bind mount) │ │ │ └─ live/... │ │ -│ └────────┬────────┘ │ └─ www-test/ │ │ -│ │ │ (Bind mounts) │ │ -│ │ └────────┬──────────┘ │ -│ │ │ │ -│ ┌────────▼────────────────────▼──────────────────────┐ │ -│ │ Services (systemd / Nginx) │ │ -│ │ │ │ -│ │ ┌──────────────┐ ┌──────────────┐ │ │ -│ │ │ Nginx │ │ Certbot │ │ │ -│ │ │ (nginx:alpine│ │(certbot/ │ │ │ -│ │ │ ports 80,443│ │ certbot) │ │ │ -│ │ └──────┬───────┘ └──────────────┘ │ │ -│ │ │ │ │ -│ │ ┌──────▼───────┐ ┌──────────────┐ │ │ -│ │ │ Frontend │ │ Backend │ │ │ -│ │ │ (Next.js) │ │ (Express) │ │ │ -│ │ │ port 3000 │ │ port 3001 │ │ │ -│ │ └──────────────┘ └──────────────┘ │ │ -│ │ │ │ -│ │ ┌──────────────┐ │ │ -│ │ │ Cron │ │ │ -│ │ │ (Tâches) │ │ │ -│ │ └──────────────┘ │ │ -│ │ │ │ -│ │ ┌──────────────┐ ┌──────────────┐ │ │ -│ │ │ Loki │ │ Promtail │ │ │ -│ │ │ (port 3100) │ │ (collecte) │ │ │ -│ │ └──────┬───────┘ └──────┬───────┘ │ │ -│ │ │ │ │ │ -│ │ ┌──────▼──────────────────▼───────┐ │ │ -│ │ │ Grafana │ │ │ -│ │ │ (port 3000 loopback) │ │ │ -│ │ └──────────────────────────────────┘ │ │ -│ └──────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ PostgreSQL (Base de données externe) │ │ -│ │ : │ │ -│ └─────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ -``` - ---- - -## Commandes utiles - -### Déploiement complet avec certificats - -```bash -# Depuis la machine de développement -cd /home/debian/legacy/lecoffreio -./deploy/scripts/build-and-deploy.sh test --deploy --cert -``` - -### Renouveler les certificats manuellement - -```bash -# Sur le serveur distant -cd /home/debian/sites/test-lecoffreio.4nkweb.com - -# Forcer renouvellement (sur le proxy ou serveur où Certbot est installé) -certbot renew --force-renewal - -# Recharger Nginx (sur le proxy) -nginx -s reload -``` - -### Vérifier l'état des services - -```bash -# Statut des services (sur le serveur env) -systemctl status 'lecoffreio-*@*' - -# Logs en temps réel -journalctl -u lecoffreio-backend@ -f - -# Health check backend -curl https://test-lecoffreio.4nkweb.com/health -``` - -### Logs après déploiement (depuis machine distante) - -```bash -ssh ncantu@ 'journalctl -u lecoffreio-backend@ -f' - -# Statut services -ssh ncantu@ 'systemctl status "lecoffreio-*@*"' - -# Logs synchronisation -ssh debian@ 'cd /home/debian/sites/test-lecoffreio.4nkweb.com && tail -100 logs/sync-all-*.log' - -# Health check -curl https://test-lecoffreio.4nkweb.com/api/v1/public/health | jq -``` - ---- - -## Monitoring - -### Architecture - -#### Objectif - -- Centraliser les logs applicatifs (`logs/*.log`) via Promtail → Loki -- Offrir aux équipes support un Grafana local accounts (pas de SSO) -- Faciliter l'analyse racine via requêtes Loki (filtre `job`, `level`, `message`) - -#### Services déployés - -| Service | Instance | Port | Volume de données | -|---------|----------|------|-------------------| -| **Loki** | `lecoffre_loki_${ENV}` | 3100 (interne) | `/var/lib/lecoffre/loki-${ENV}` | -| **Promtail** | `lecoffre_promtail_${ENV}` | - | `/var/lib/lecoffre/promtail-${ENV}` | -| **Grafana** | `lecoffre_grafana_${ENV}` | 3000 (loopback) | `/var/lib/lecoffre/grafana-${ENV}` | - -#### Réseau - -Les services de monitoring communiquent sur l'hôte (localhost ou réseau interne). - -#### Flux de données - -```text -Backend/Cron → Fichiers logs → Promtail → Loki → Grafana -``` - -#### Infrastructure - -- **Promtail** : surveille `logs/` (bind `../logs`) via jobs `application-logs` et `sync-logs` -- **Loki** : stocke les logs sur `/var/lib/lecoffre/loki-` (retention 14 jours par défaut) -- **Grafana** : interface UI, accessible via `/monitor` (reverse proxy) ou tunnel SSH (`127.0.0.1:3000`) - -### Configuration Grafana - -#### Accès - -**Via HTTPS (recommandé)** : - -1. URL : `https:///monitor/` -2. Authentification : Identifiants configurés dans `.env.` - - User : `GRAFANA_ADMIN_USER` - - Password : `GRAFANA_ADMIN_PASSWORD` - -**Via tunnel SSH (fallback)** : - -```bash -ssh -L 3000:127.0.0.1:3000 debian@ -``` - -Puis ouvrir `http://localhost:3000` dans le navigateur. - -#### Variables d'environnement - -```yaml -GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-grafana} -GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-grafana-pass} -GF_SECURITY_ALLOW_EMBEDDING: "false" -GF_SECURITY_DISABLE_GRAVATAR: "true" -GF_USERS_ALLOW_SIGN_UP: "false" -GF_SERVER_DOMAIN: ${DEPLOY_DOMAIN} -GF_SERVER_ROOT_URL: "https://${DEPLOY_DOMAIN}/monitor/" -GF_SERVER_SERVE_FROM_SUB_PATH: "true" -``` - -#### Provisioning automatique - -**Datasources** : - -**Emplacement** : `deploy/monitoring/grafana/provisioning/datasources/loki.yml` - -La datasource Loki est automatiquement configurée avec : - -- **URL** : `http://loki:3100` -- **Type** : `loki` -- **Accès** : `proxy` -- **Par défaut** : Oui -- **Max lines** : 1000 -- **Derived fields** : Support pour les traceID - -**Dashboards** : - -**Emplacement** : `deploy/monitoring/grafana/provisioning/dashboards/dashboards.yml` - -Le provisioning des dashboards est configuré pour : - -- Charger automatiquement les dashboards depuis `/etc/grafana/provisioning/dashboards` -- Mettre à jour toutes les 10 secondes -- Permettre les mises à jour via l'UI -- Organiser les dashboards par structure de dossiers - -#### Reverse Proxy Nginx - -**Fichier** : `deploy/nginx/includes/monitor.conf` - -Nginx configure un reverse proxy pour Grafana : - -- **Path** : `/monitor/` -- **Backend** : `http://grafana:3000` -- **Headers** : Préservation des headers X-Forwarded-* pour HTTPS -- **Timeout** : 300s pour les requêtes longues - -### Configuration Loki - -#### Fichier de configuration - -**Emplacement** : `deploy/monitoring/loki-config.yml` - -#### Paramètres clés - -- **Port HTTP** : 3100 (interne uniquement) -- **Stockage** : Filesystem (`/var/lib/loki`) -- **Rétention** : 336h (14 jours) -- **Schéma** : v12 avec boltdb-shipper -- **Compression** : Activée pour les réponses - -#### Rétention des données - -```yaml -table_manager: - retention_deletes_enabled: true - retention_period: 336h # 14 jours - poll_interval: 5m -``` - -Les données sont automatiquement supprimées après 14 jours. - -### Configuration Promtail - -#### Fichier de configuration - -**Emplacement** : `deploy/monitoring/promtail-config.yml` - -#### Jobs de collecte - -Promtail collecte les logs depuis `/var/log/lecoffre` (monté depuis `../logs` sur l'hôte) : - -1. **application-logs** : Tous les fichiers `*.log` à la racine -2. **sync-logs** : Fichiers `sync-*.log` -3. **winston-logs** : Fichiers Winston avec pattern de date : - - `error-YYYY-MM-DD.log` - - `combined-YYYY-MM-DD.log` - - `login-YYYY-MM-DD.log` -4. **deployment-logs** : Fichiers `build-and-deploy-*.log` - -#### Labels - -- `job` : Type de log (`application-logs`, `sync-logs`, `deployment-logs`) -- `env` : Environnement (`test`, `pprod`, `prod`, `demo`) - ajouté via `-client.external-labels` - -#### Positions - -Les positions de lecture sont stockées dans `/var/promtail/positions.yaml` pour éviter de relire les fichiers depuis le début. - -### Dashboards - -#### Dashboard Vue d'ensemble - -**Fichier** : `deploy/monitoring/grafana/provisioning/dashboards/lecoffre-overview.json` - -**Panneaux** : - -- Logs applicatifs récents + Erreurs récentes -- Statistiques (1h) : Erreurs, Warnings, Connexions, Requêtes lentes -- Graphiques timeseries : Erreurs/heure, Warnings/heure -- Logs spécialisés : Connexion [LOGIN_MARKER], Requêtes Prisma lentes, Erreurs Prisma -- Logs de synchronisation + Logs de déploiement - -#### Dashboards spécialisés - -Les dashboards suivants sont disponibles : - -1. **LeCoffre.io - Backend** : Logs backend, erreurs, connexions, requêtes Prisma lentes -2. **LeCoffre.io - Frontend** : Logs frontend Next.js, erreurs, builds -3. **LeCoffre.io - Base de données** : Requêtes Prisma lentes, erreurs Prisma, erreurs connexion PostgreSQL -4. **LeCoffre.io - ClamAV Antivirus** : Scans antivirus, malwares détectés -5. **LeCoffre.io - IdNot** : Logs de connexion, synchronisation des offices -6. **LeCoffre.io - OVH/SMS** : Logs SMS, codes 2FA -7. **LeCoffre.io - Stripe** : Webhooks Stripe, abonnements -8. **LeCoffre.io - Mailchimp** : Emails envoyés, erreurs d'envoi -9. **LeCoffre.io - Bitcoin Signet** : Ancrages réussis/échoués, vérifications blockchain -10. **LeCoffre.io - Espace Tiers** : Ajouts de tiers, documents consultés -11. **LeCoffre.io - Espace Client** : Documents consultés, fichiers uploadés -12. **LeCoffre.io - Dossiers Invités** : Partages de dossiers, accès notaires invités -13. **LeCoffre.io - Espace Notaire** : Créations de dossiers, documents, ancrages - -### Requêtes LogQL - -#### Requêtes courantes - -- `{job="application-logs"} |= "ERROR"` : Toutes les erreurs applicatives -- `{job="sync-logs"} |= "sync-directory"` : Logs de synchronisation -- `{job="application-logs"} |= "[Antivirus]"` : Tous les scans ClamAV -- `{job="application-logs"} |= "[Antivirus] Malware detected"` : Malwares détectés - -#### Logs applicatifs généraux - -```logql -{job="application-logs"} -``` - -#### Erreurs uniquement - -```logql -{job="application-logs"} |~ "(ERROR|❌|error)" -``` - -#### Warnings - -```logql -{job="application-logs"} |~ "(WARN|⚠️|warn)" -``` - -#### Logs ClamAV - -```logql -{job="application-logs"} |= "[Antivirus]" -``` - -#### Malwares détectés - -```logql -{job="application-logs"} |= "[Antivirus] Malware detected" -``` - -#### Logs de synchronisation - -```logql -{job="sync-logs"} -``` - -#### Logs de déploiement - -```logql -{job="deployment-logs"} -``` - -#### Métriques temporelles - -**Nombre d'erreurs par heure** : - -```logql -count_over_time({job="application-logs"} |= "ERROR" [1h]) -``` - -**Nombre de scans ClamAV par heure** : - -```logql -count_over_time({job="application-logs"} |= "[Antivirus] Scan started" [1h]) -``` - -#### Patterns de logs utilisés - -- `[LOGIN_MARKER]` : Logs de connexion utilisateur -- `[Prisma Slow Query]` : Requêtes Prisma lentes (> 1 seconde) -- `[Prisma Error]` : Erreurs Prisma -- `[Antivirus]` : Tous les logs ClamAV -- `ERROR`, `❌`, `error` : Erreurs générales -- `WARN`, `⚠️`, `warn` : Warnings -- `✅` : Succès - -### Troubleshooting Monitoring - -#### Problème "No Data" dans Grafana - -**1. Vérifier que les fichiers de logs existent** : - -```bash -# Sur le serveur distant -ls -la /home/debian/sites/-lecoffreio.4nkweb.com/logs/ -``` - -**Résultat attendu** : - -- `backend-YYYY-MM-DD.log` (logs backend) -- `cron-YYYY-MM-DD.log` (logs cron) -- Autres fichiers de logs (Winston, déploiement, etc.) - -**2. Vérifier que Promtail peut lire les fichiers** : - -```bash -# Vérifier les logs Promtail (selon déploiement : systemd ou service) -journalctl -u promtail -n 100 - -# Vérifier les positions (chemin selon installation) -cat /var/lib/lecoffre/promtail-/positions.yaml - -# Vérifier que les fichiers de logs existent -ls -la logs/ -``` - -**3. Vérifier la connexion Promtail → Loki** : - -```bash -# Vérifier que Loki est accessible (selon déploiement) -curl -s http://127.0.0.1:3100/ready - -# Vérifier les logs Promtail pour les erreurs de connexion -journalctl -u promtail -n 100 | grep -i error -``` - -**4. Vérifier que Loki reçoit les logs** : - -```bash -# Vérifier les logs Loki (selon déploiement) -journalctl -u loki -n 100 - -# Tester l'API Loki (labels disponibles) -curl -s http://127.0.0.1:3100/loki/api/v1/labels - -# Tester une requête LogQL -curl -s "http://127.0.0.1:3100/loki/api/v1/query_range?query={job=\"application-logs\"}&limit=10" -``` - -**5. Vérifier les labels dans Grafana** : - -Dans Grafana, aller dans **Explore** (icône boussole) : - -1. Sélectionner la datasource **Loki** -2. Tester la requête : `{job="application-logs"}` -3. Vérifier que des logs apparaissent - -**6. Vérifier la plage de temps** : - -Les logs doivent avoir des timestamps récents. Si les logs sont trop anciens, ils peuvent ne pas apparaître dans la plage de temps sélectionnée dans Grafana. - -**Solution** : Ajuster la plage de temps dans Grafana (par défaut : `now-6h` à `now`) - -#### Causes courantes et solutions - -**Cause 1 : Fichiers de logs non créés** : - -**Symptômes** : - -- `ls -la logs/` ne montre pas de fichiers `backend-*.log` ou `cron-*.log` -- Les conteneurs backend/cron démarrent mais n'écrivent pas de logs - -**Solutions** : - -1. Vérifier que les scripts wrapper sont exécutables -2. Vérifier les logs backend : `journalctl -u lecoffreio-backend@ -n 100` -3. Vérifier le répertoire des logs : `ls -la logs/` - -**Cause 2 : Promtail ne peut pas lire les fichiers** : - -**Symptômes** : - -- Les fichiers existent mais Promtail ne les lit pas -- Erreurs `permission denied` dans les logs Promtail - -**Solutions** : - -1. Vérifier les permissions des fichiers : `ls -la logs/` -2. Vérifier que Promtail a accès au répertoire des logs (configuration Promtail) -3. Vérifier les permissions du répertoire logs/ - -**Cause 3 : Promtail ne peut pas se connecter à Loki** : - -**Symptômes** : - -- Erreurs `connection refused` dans les logs Promtail -- Loki ne reçoit pas de logs - -**Solutions** : - -1. Vérifier que Loki est démarré : `systemctl status loki` ou selon déploiement -2. Vérifier que Promtail et Loki communiquent (même hôte ou URL configurée) -3. Vérifier l'URL dans `promtail-config.yml` : `http://127.0.0.1:3100/loki/api/v1/push` - -**Cause 4 : Labels incorrects** : - -**Symptômes** : - -- Loki reçoit des logs mais les dashboards affichent "No data" -- Les requêtes LogQL ne retournent rien - -**Solutions** : - -1. Vérifier les labels disponibles : `curl http://localhost:3100/loki/api/v1/labels` -2. Vérifier que les labels dans `promtail-config.yml` correspondent aux requêtes dans les dashboards -3. Vérifier que le label `job` est bien défini dans tous les `scrape_configs` - -**Cause 5 : Configuration Promtail obsolète** : - -**Symptôme** : Pattern trop large (`*.log`) créant des doublons - -**Solution** : Supprimer le `scrape_config` avec pattern trop large et conserver uniquement les configurations spécifiques (backend-*.log, cron-*.log, etc.) - -Voir [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) et logs backend pour le dépannage Grafana/Loki. - -### Persistance des données Monitoring - -#### Persistance des données - -Toutes les données sont persistées sur l'hôte dans `/var/lib/lecoffre/` : - -- **Loki** : `/var/lib/lecoffre/loki-${ENV}/` - Index et chunks de logs -- **Promtail** : `/var/lib/lecoffre/promtail-${ENV}/` - Positions de lecture -- **Grafana** : `/var/lib/lecoffre/grafana-${ENV}/` - Dashboards, datasources, utilisateurs - -#### Survie aux redéploiements - -Les répertoires de données sont créés avant le démarrage des services et survivent aux redéploiements. - -### Sécurité Monitoring - -#### Authentification Grafana - -- Authentification locale uniquement -- Pas d'inscription publique (`GF_USERS_ALLOW_SIGN_UP: "false"`) -- Pas d'embedding externe (`GF_SECURITY_ALLOW_EMBEDDING: "false"`) - -#### Accès réseau - -- **Loki** : Accessible sur le réseau local (port 3100) -- **Promtail** : Pas de port exposé (collecte uniquement) -- **Grafana** : Port 3000 exposé uniquement en loopback (`127.0.0.1:3000`) - -#### Accès externe - -L'accès externe se fait uniquement via HTTPS via Nginx reverse proxy sur `/monitor/`. - -### Vérification de l'état Monitoring - -#### Vérifier que les services sont en cours d'exécution - -```bash -systemctl status loki promtail grafana-server -``` - -#### Vérifier les logs des services - -```bash -# Loki -journalctl -u loki -n 100 - -# Promtail -journalctl -u promtail -n 100 - -# Grafana -journalctl -u grafana-server -n 100 -``` - -#### Vérifier la connectivité Loki - -```bash -curl -s http://127.0.0.1:3100/ready -``` - -#### Vérifier les fichiers de logs collectés - -```bash -ls -la logs/ -``` - -#### Vérifier les positions Promtail - -```bash -cat /var/lib/lecoffre/promtail-/positions.yaml -``` - ---- - -## Disaster Recovery - -### Vue d'Ensemble - -Le système de disaster recovery permet de : - -- **Sauvegarder quotidiennement** l'état complet de la production -- **Conserver 7 versions** de sauvegardes sur une VM Gandi -- **Récupérer automatiquement** en cas d'incident : installation complète, bascule DNS, certificats SSL, relance des services - -**RTO (Recovery Time Objective)** : 3 heures -**RPO (Recovery Point Objective)** : 24 heures (sauvegarde quotidienne) - -### Architecture - -#### Infrastructure - -```text -Serveur Prod (192.168.1.103) - │ - │ [backup-daily.sh - quotidien 2h00] - │ - ▼ -VM Gandi (92.243.24.12) - │ - │ /var/lecoffreio/prod/ - │ ├── backup-20250109-020000.tar.gz - │ ├── backup-20250108-020000.tar.gz - │ ├── ... (7 versions) - │ └── latest -> backup-20250109-020000.tar.gz - │ - ▼ -[En cas d'incident] - │ - │ [disaster-recovery.sh] - │ - ▼ -Serveur de Récupération (RECOVERY_TARGET_HOST) - │ - │ [Bascule DNS via API Gandi] - │ - ▼ -prod.lecoffreio.4nkweb.com → Nouvelle IP -``` - -#### Composants - -1. **`backup-daily.sh`** : Script de sauvegarde quotidienne automatique -2. **`disaster-recovery.sh`** : Script de récupération automatisée complète -3. **`gandi-dns-api.sh`** : Script d'interaction avec l'API Gandi pour la bascule DNS - -### Configuration Initiale - -#### 1. Créer le fichier de configuration - -```bash -cp .secrets/.env.backup.example .secrets/.env.backup -``` - -#### 2. Configurer les variables - -Éditez `.secrets/.env.backup` : - -```bash -# VM Gandi (Sauvegarde) -BACKUP_GANDI_HOST=92.243.24.12 -BACKUP_GANDI_USER=debian -BACKUP_GANDI_SSH_KEY=~/.ssh/id_ed25519 -BACKUP_GANDI_PATH=/var/lecoffreio - -# API Gandi (Bascule DNS) -GANDI_API_KEY=your-gandi-api-key-here -GANDI_DOMAIN=prod.lecoffreio.4nkweb.com - -# Configuration de sauvegarde -BACKUP_RETENTION_DAYS=7 -BACKUP_SCHEDULE_HOUR=2 -BACKUP_SCHEDULE_MINUTE=0 -BACKUP_ENV=prod - -# Récupération (optionnel - pour disaster-recovery.sh) -RECOVERY_TARGET_HOST=92.243.24.12 -RECOVERY_TARGET_USER=debian -RECOVERY_TARGET_SSH_KEY=~/.ssh/id_ed25519 -``` - -#### 3. Configurer l'accès SSH - -Assurez-vous que les clés SSH sont configurées : - -```bash -# Test connexion VM Gandi -ssh -i ~/.ssh/id_ed25519 debian@92.243.24.12 "echo 'OK'" - -# Test connexion serveur de récupération (si différent) -ssh -i ~/.ssh/id_ed25519 debian@$RECOVERY_TARGET_HOST "echo 'OK'" -``` - -#### 4. Préparer la VM Gandi - -Sur la VM Gandi, créer la structure de répertoires : - -```bash -ssh debian@92.243.24.12 -sudo mkdir -p /var/lecoffreio/prod -sudo chown debian:debian /var/lecoffreio/prod -``` - -#### 5. Vérifier l'API Gandi - -Tester l'accès à l'API Gandi : - -```bash -curl -H "Authorization: Apikey $GANDI_API_KEY" \ - https://id.gandi.net/tokeninfo -``` - -### Sauvegarde Quotidienne - -#### Exécution Manuelle - -```bash -./deploy/scripts/backup/backup-daily.sh -``` - -#### Configuration Cron (Automatique) - -Sur le serveur prod, ajouter dans le crontab : - -```bash -# Sauvegarde quotidienne à 2h00 -0 2 * * * /path/to/lecoffreio/deploy/scripts/backup/backup-daily.sh >> /path/to/logs/backup-daily.log 2>&1 -``` - -#### Contenu des Sauvegardes - -Chaque sauvegarde contient : - -- **`database.dump`** : Dump PostgreSQL au format custom (pg_dump -Fc) -- **`.env.prod`** : Fichier de configuration de l'environnement -- **`nginx-prod.conf`** : Configuration Nginx -- **`certbot-conf-prod/`** : Certificats SSL (live, archive, accounts, renewal) -- **`backup-metadata.json`** : Métadonnées (timestamp, Git commit, environnement) - -#### Structure sur VM Gandi - -```text -/var/lecoffreio/prod/ -├── backup-20250109-020000.tar.gz -├── backup-20250108-020000.tar.gz -├── backup-20250107-020000.tar.gz -├── backup-20250106-020000.tar.gz -├── backup-20250105-020000.tar.gz -├── backup-20250104-020000.tar.gz -├── backup-20250103-020000.tar.gz -└── latest -> backup-20250109-020000.tar.gz -``` - -#### Rotation Automatique - -Le script conserve automatiquement les 7 dernières sauvegardes. Les sauvegardes plus anciennes sont supprimées automatiquement. - -### Récupération d'Urgence - -#### Exécution - -```bash -# Utiliser la dernière sauvegarde (latest) -./deploy/scripts/backup/disaster-recovery.sh - -# Utiliser une sauvegarde spécifique -./deploy/scripts/backup/disaster-recovery.sh backup-20250108-020000.tar.gz -``` - -#### Processus Automatisé - -Le script `disaster-recovery.sh` effectue automatiquement : - -1. **Récupération de la sauvegarde** depuis la VM Gandi -2. **Extraction de l'archive** et vérification du contenu -3. **Préparation du serveur de récupération** (vérification prérequis) -4. **Clone/update du repository Git** sur le serveur de récupération -5. **Restauration des fichiers de configuration** (.env, nginx, certificats) -6. **Upload du dump de base de données** -7. **Bascule DNS** via API Gandi (prod.lecoffreio.4nkweb.com → IP serveur de récupération) -8. **Attente de la propagation DNS** (vérification toutes les 30 secondes) -9. **Restauration de la base de données** sur le serveur de récupération -10. **Mise à jour des certificats SSL** (renouvellement si nécessaire) -11. **Build et déploiement** (build, démarrage services systemd) -12. **Vérification de la santé** des services (backend, nginx) -13. **Tests de connectivité** HTTP/HTTPS - -#### Durée Estimée - -- Récupération sauvegarde : ~5-10 minutes -- Bascule DNS : ~5-10 minutes (propagation) -- Restauration BDD : ~10-30 minutes (selon taille) -- Build : ~15-30 minutes -- **Total** : ~45-80 minutes (objectif RTO : 3 heures) - -### Gestion DNS via API Gandi - -#### Script Gandi DNS API - -Le script `gandi-dns-api.sh` permet de gérer les enregistrements DNS : - -```bash -# Récupérer l'enregistrement actuel -./deploy/scripts/backup/gandi-dns-api.sh get prod.lecoffreio.4nkweb.com - -# Mettre à jour vers une nouvelle IP -./deploy/scripts/backup/gandi-dns-api.sh update prod.lecoffreio.4nkweb.com 92.243.24.12 - -# Supprimer un enregistrement (rarement nécessaire) -./deploy/scripts/backup/gandi-dns-api.sh delete prod.lecoffreio.4nkweb.com -``` - -#### Format de l'API - -L'API Gandi utilise le format : - -- **URL** : `https://api.gandi.net/api/v5/domains/{domain}/records/{name}/{type}` -- **Authorization** : `Apikey ${GANDI_API_KEY}` -- **Domain** : `lecoffreio.4nkweb.com` (domaine de base) -- **Name** : `prod` (sous-domaine) -- **Type** : `A` (enregistrement IPv4) - -### Monitoring et Maintenance Disaster Recovery - -#### Vérification Quotidienne des Sauvegardes - -```bash -# Lister les sauvegardes sur VM Gandi -ssh debian@92.243.24.12 "ls -lh /var/lecoffreio/prod/" - -# Vérifier la dernière sauvegarde -ssh debian@92.243.24.12 "ls -lh /var/lecoffreio/prod/latest" -``` - -#### Logs - -Les scripts génèrent des logs dans `logs/` : - -```bash -# Logs de sauvegarde -ls -lh logs/backup-daily-*.log - -# Logs de récupération -ls -lh logs/disaster-recovery-*.log -``` - -#### Vérification de l'Espace Disque - -Sur la VM Gandi : - -```bash -# Espace disque utilisé -ssh debian@92.243.24.12 "df -h /var/lecoffreio" - -# Taille des sauvegardes -ssh debian@92.243.24.12 "du -sh /var/lecoffreio/prod/*" -``` - -#### Tests de Récupération - -Il est recommandé de tester la récupération **trimestriellement** : - -1. Utiliser un serveur de test (ou VM temporaire) -2. Exécuter `disaster-recovery.sh` avec une sauvegarde récente -3. Vérifier que tous les services fonctionnent -4. Documenter les problèmes rencontrés - -### Dépannage Disaster Recovery - -#### Problème : Sauvegarde échoue - -**Symptômes** : - -- Le script `backup-daily.sh` échoue -- Pas de nouvelle sauvegarde dans `/var/lecoffreio/prod/` - -**Diagnostic** : - -```bash -# Vérifier les logs -tail -100 logs/backup-daily-*.log - -# Vérifier la connexion SSH au serveur prod -ssh -i ~/.ssh/id_ed25519 debian@ "echo 'OK'" - -# Vérifier la connexion SSH à la VM Gandi -ssh -i ~/.ssh/id_ed25519 debian@92.243.24.12 "echo 'OK'" - -# Vérifier l'espace disque sur VM Gandi -ssh debian@92.243.24.12 "df -h /var" -``` - -**Solutions** : - -- Vérifier les clés SSH -- Vérifier l'espace disque disponible -- Vérifier que PostgreSQL est accessible - -#### Problème : Récupération échoue - -**Symptômes** : - -- Le script `disaster-recovery.sh` échoue à une étape -- Services non démarrés après récupération - -**Diagnostic** : - -```bash -# Vérifier les logs -tail -100 logs/disaster-recovery-*.log - -# Vérifier l'état des services sur le serveur de récupération -ssh ncantu@$RECOVERY_TARGET_HOST "systemctl status 'lecoffreio-*@*'" - -# Vérifier les logs -ssh ncantu@$RECOVERY_TARGET_HOST "journalctl -u lecoffreio-backend@ -n 100" -``` - -**Solutions** : - -- Vérifier les prérequis (Git, Node, services systemd) sur le serveur de récupération -- Vérifier l'espace disque disponible -- Vérifier que le dump de base de données est valide -- Vérifier la configuration DNS - -#### Problème : DNS non propagé - -**Symptômes** : - -- Le script indique que le DNS n'est pas propagé -- Le site n'est pas accessible via HTTPS - -**Diagnostic** : - -```bash -# Vérifier l'enregistrement DNS actuel -./deploy/scripts/backup/gandi-dns-api.sh get prod.lecoffreio.4nkweb.com - -# Vérifier la résolution DNS -dig prod.lecoffreio.4nkweb.com @8.8.8.8 - -# Vérifier depuis différents serveurs DNS -nslookup prod.lecoffreio.4nkweb.com 8.8.8.8 -nslookup prod.lecoffreio.4nkweb.com 1.1.1.1 -``` - -**Solutions** : - -- Attendre la propagation DNS (TTL peut être jusqu'à 600 secondes) -- Vérifier que l'enregistrement DNS a bien été mis à jour via l'API -- Vérifier les permissions de l'API Gandi - -#### Problème : Certificats SSL invalides - -**Symptômes** : - -- Erreur de certificat SSL lors de l'accès HTTPS -- Certificats expirés - -**Solutions** : - -```bash -# Sur le serveur de récupération, régénérer les certificats -ssh debian@$RECOVERY_TARGET_HOST -cd /home/debian/sites/lecoffreio.4nkweb.com -bash deploy/scripts/build-and-deploy-local-cert.sh prod lecoffreio.4nkweb.com -``` - -### Procédures Avancées Disaster Recovery - -#### Restaurer une Sauvegarde Spécifique - -```bash -# Lister les sauvegardes disponibles -ssh debian@92.243.24.12 "ls -lh /var/lecoffreio/prod/backup-*.tar.gz" - -# Restaurer une sauvegarde spécifique -./deploy/scripts/backup/disaster-recovery.sh backup-20250108-020000.tar.gz -``` - -#### Restaurer uniquement la Base de Données - -Si vous voulez restaurer uniquement la base de données (sans tout le reste) : - -```bash -# Télécharger le backup -scp debian@92.243.24.12:/var/lecoffreio/prod/backup-XXXXXX.tar.gz /tmp/ - -# Extraire le dump -tar xzf /tmp/backup-XXXXXX.tar.gz -C /tmp/extracted - -# Utiliser le script de reset avec le dump -./deploy/scripts/reset-and-import-database.sh prod /tmp/extracted/database.dump -``` - -#### Vérifier l'Intégrité d'une Sauvegarde - -```bash -# Télécharger le backup -scp debian@92.243.24.12:/var/lecoffreio/prod/backup-XXXXXX.tar.gz /tmp/ - -# Extraire et vérifier -tar xzf /tmp/backup-XXXXXX.tar.gz -C /tmp/extracted -ls -lh /tmp/extracted/ - -# Vérifier le dump PostgreSQL -file /tmp/extracted/database.dump - -# Vérifier les métadonnées -cat /tmp/extracted/backup-metadata.json -``` - -### Sécurité Disaster Recovery - -#### Protection des Clés API - -- Les clés API Gandi sont stockées dans `.env.backup` (fichier non versionné) -- Ne jamais commiter `.env.backup` dans Git -- Limiter les permissions : `chmod 600 .secrets/.env.backup` - -#### Chiffrement (Optionnel) - -Si vous souhaitez chiffrer les sauvegardes : - -```bash -# Chiffrer avant transfert -gpg --symmetric --cipher-algo AES256 backup.tar.gz - -# Déchiffrer lors de la récupération -gpg --decrypt backup.tar.gz.gpg > backup.tar.gz -``` - -### Bonnes Pratiques Disaster Recovery - -1. **Tests Réguliers** : Tester la récupération trimestriellement -2. **Monitoring** : Surveiller les logs de sauvegarde quotidienne -3. **Espace Disque** : Surveiller l'espace disque sur la VM Gandi -4. **Documentation** : Documenter toute modification du processus -5. **Validation** : Valider manuellement après chaque récupération - -### Informations Validées - -1. **Infrastructure VM Gandi** : - - Stockage : 250GB - - Machine cible : ssh debian@92.243.24.12 - -2. **Stratégie de sauvegarde** : - - Environnement : prod uniquement - - Conservation : 7 versions - -3. **API Gandi** : - - Clé API : Fournie (stockée dans `.env.backup`) - - Permissions : Admin DNS - - Domaine : prod.lecoffreio.4nkweb.com - -4. **Récupération** : - - RTO (Recovery Time Objective) : 3 heures - - RPO (Recovery Point Objective) : 24 heures (sauvegarde quotidienne) - -5. **Sécurité** : - - Gestion des clés : Fichiers `.env.backup` - - Chiffrement : Non requis (infrastructure privée) - ---- - -## Références - -### Documentation - -- **Architecture** : [ARCHITECTURE.md](./ARCHITECTURE.md) -- **Reset base de données** : `docs/DATABASE_RESET_GUIDE.md` -- **Scripts backend** : [SCRIPTS.md](./SCRIPTS.md) -- **Monitoring** : Voir section "Monitoring" ci-dessus -- **Disaster Recovery** : Voir section "Disaster Recovery" ci-dessus - -### Fichiers de configuration - -- **Configuration systemd** : `deploy/scripts_v2/systemd/*.service` -- **Configuration Nginx** : `deploy/nginx/nginx-*.conf` -- **Script de déploiement** : `deploy/scripts/build-and-deploy.sh` -- **Fichiers d'injection BDD** : `.secrets//env-full--for-bdd-injection.txt` - -### Documentation externe - -- **Documentation Let's Encrypt** : -- **Documentation Nginx** : - ---- - -## Git Workflow et Versioning - -Voir `deploy/README.md` pour les scripts. Format des commits : **Motivations**, **Root causes**, **Correctifs**, **Evolutions**, **Pages affectées**. Hooks : `.git/hooks/commit-msg`, `.git/hooks/pre-push`. Template : `.gitmessage`. Bump version : `./deploy/scripts/bump-version.sh ""` ; met à jour `VERSION`, `package.json` (backend + frontend), génère le template splash. - -**Auteur des commits :** L'auteur doit être **4NK** ou **Nicolas Cantu** uniquement. Ne jamais ajouter de ligne `Co-authored-by: Cursor` ni aucune ligne Co-authored-by qui ferait apparaître un auteur autre que 4NK ou Nicolas Cantu. - ---- - -**Dernière mise à jour** : 2026-01-29 (consolidation DEPLOYMENT, MONITORING, DISASTER_RECOVERY, GIT_WORKFLOW, VERSIONING) diff --git a/projects/lecoffreio/docs/FRONTEND.md b/projects/lecoffreio/docs/FRONTEND.md deleted file mode 100644 index 981b494..0000000 --- a/projects/lecoffreio/docs/FRONTEND.md +++ /dev/null @@ -1,1376 +0,0 @@ -# Documentation Frontend - LeCoffre.io - -**Dernière mise à jour** : 2026-02-25 - -Ce document regroupe toute la documentation frontend : routes, pages, UI/UX, et gestion des collaborateurs. - ---- - -## 📋 Table des Matières - -1. [Routes et Navigation](#1-routes-et-navigation) -2. [Pages et Composants](#2-pages-et-composants) -3. [Corrections UI/UX](#3-corrections-uiux) -4. [Agrégation des Collaborateurs](#4-agrégation-des-collaborateurs) -5. [Build et Tests](#5-build-et-tests) -6. [Authentification IdNot et Logging](#6-authentification-idnot-et-logging) -7. [Interface Espace Notaire - Onglets et Tableaux](#7-interface-espace-notaire---onglets-et-tableaux) -8. [Consolidation évolutions et correctifs](#8-consolidation-évolutions-et-correctifs) - ---- - -## 1. Routes et Navigation - -### Routes de Dossiers - -#### Routes Actives - -- `/folders` - Liste des dossiers actifs -- `/folders/[folderUid]` - Détail d'un dossier actif -- `/folders/[folderUid]/add/clients` - Ajouter un client -- `/folders/[folderUid]/add/third-party` - Ajouter un tiers -- `/folders/[folderUid]/edit/informations` - Modifier les informations -- `/folders/[folderUid]/edit/clients/[customerUid]` - Modifier un client -- `/folders/[folderUid]/edit/collaborators` - Modifier les collaborateurs -- `/folders/[folderUid]/documents-reminder-history` - Historique des rappels -- `/folders/[folderUid]/send-documents` - Envoyer des documents -- `/folders/[folderUid]/documents/[documentUid]` - Détail d'un document - -#### Routes Archivées - -- `/folders/archived` - Liste des dossiers archivés -- `/folders/archived/[folderUid]` - Détail d'un dossier archivé - -#### Routes Supprimées - -- `/folders/deleted` - Liste des dossiers supprimés (soft delete) - -### Détection des Pages de Détail - -Le composant `FolderInformation` détecte automatiquement le type de route via la prop `isArchived`. - -**Regex de détection** : - -```typescript -/\/folders\/(archived\/)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i -``` - -**Format accepté** : - -- `/folders/[uuid]` - Dossier actif -- `/folders/archived/[uuid]` - Dossier archivé - -**Format rejeté** : - -- `/folders` - Liste des dossiers -- `/folders/archived` - Liste des dossiers archivés - -**Fichiers concernés** : - -- `lecoffre-front-main/src/front/Components/Layouts/Folder/FolderInformation/index.tsx` -- `lecoffre-front-main/src/pages/folders/[folderUid]/index.tsx` -- `lecoffre-front-main/src/pages/folders/archived/[folderUid]/index.tsx` - ---- - -## 2. Pages et Composants - -### Pages Identifiées pour Migration Collaborateurs - -#### UpdateFolderCollaborators ⚠️ PRIORITÉ HAUTE - -**Fichier** : `lecoffre-front-main/src/front/Components/Layouts/Folder/UpdateFolderCollaborators/index.tsx` - -**Action requise** : Utiliser l'endpoint `/api/v1/notary/users/aggregated` pour afficher tous les collaborateurs (avec et sans compte). - -**Page concernée** : `/folders/[folderUid]/collaborators` - -#### SubscriptionManageCollaborators ⚠️ PRIORITÉ HAUTE - -**Fichier** : `lecoffre-front-main/src/front/Components/Layouts/Subscription/Manage/SubscriptionManageCollaborators/index.tsx` - -**Action requise** : Utiliser l'endpoint agrégé et filtrer pour ne garder que ceux avec compte (car seuls les utilisateurs avec compte peuvent avoir un siège). - -**Page concernée** : `/subscription/manage-collaborators` - -#### DefaultCollaboratorDashboard ⚠️ PRIORITÉ MOYENNE - -**Fichier** : `lecoffre-front-main/src/front/Components/LayoutTemplates/DefaultCollaboratorDashboard/index.tsx` - -**Action requise** : Utiliser l'endpoint agrégé et filtrer pour ne garder que ceux avec compte, ou afficher deux sections : "Avec compte" et "À inviter". - -**Page concernée** : `/collaborators` (dashboard) - -#### CreateFolder - -**Fichier** : `lecoffre-front-main/src/front/Components/Layouts/Folder/CreateFolder/index.tsx` - -**État** : Depuis novembre 2025, le formulaire de création ne gère plus l'ajout de collaborateurs, de confrères ou de tiers. Les stakeholders initiaux se limitent automatiquement au créateur. - ---- - -## 3. Corrections UI/UX - -### Boutons de Civilité Invisibles - -**Date** : 2025-01-27 -**Composant** : `AddClientToFolder` -**Page** : `/folders/[folderUid]/add/clients` - -#### Problème - -Les 3 boutons de sélection de civilité (Monsieur, Madame, Autre) apparaissaient vides/invisibles dans le formulaire "Créer un nouveau client". - -#### Root Cause - -Le CSS de la classe `.civility-option` ne définissait pas de couleur de texte par défaut. Seule la classe `.civility-option-active` (bouton sélectionné) avait une couleur définie. - -#### Solution - -Ajout d'une couleur de texte par défaut (`var(--color-neutral-700)`) et d'un fond blanc pour garantir la visibilité du texte. - -**Fichiers modifiés** : - -- `lecoffre-front-main/src/front/Components/Layouts/Folder/AddClientToFolder/classes.module.scss` - -### Migration Sass : @import vers @use - -**Date** : 2025-01-30 -**Version** : 2.0.1+ - -#### Objectif - -Éliminer les warnings de dépréciation Sass concernant l'utilisation de `@import` qui seront supprimés dans Dart Sass 3.0.0. Migrer tous les fichiers SCSS vers le système moderne `@use`/`@forward` de Sass. - -#### Modifications - -**Changement de syntaxe :** - -- **Avant** : `@import "@Themes/constants.scss";` -- **Après** : `@use "@Themes/constants.scss" as *;` - -**Fichiers migrés** : 138 fichiers SCSS dans `lecoffre-front-main/src/` - -**Compatibilité** : L'utilisation de `as *` permet de conserver l'accès global aux variables Sass (`$screen-m`, `$screen-s`, `$color-*`) et aux variables CSS (`var(--...)`). - -**Documentation complète** : Voir conventions Sass du projet (ARCHITECTURE, OPTIMIZATION_AXES). - -### Bouton supprimer, parties rattachées, page add/clients (2026-01-28) - -**Pages** : `/folders/[folderUid]/add/clients`, fiche dossier. - -**Problèmes** : (1) Clic sur supprimer (X) à côté d'un client ne retire pas l'item ; (2) après validation, aucune partie sur la fiche dossier ; (3) après création dossier, add/clients reste sur « Dossier chargé mais données invalides » (GET folder sans `uid`). - -**Root causes** : Clés React en index (`GenericExistingItemsList`) → réutilisation de nœuds ; `preventDefault`/`stopPropagation` sur IconButton ; options avec `id` vide exclus du payload ; `instanceToPlain` omet `uid` sur GET `/notary/folders/:uid`. - -**Correctifs** : `getExistingItemKey` / `getPendingItemKey` stables (ex. `id`, `email`) ; `onClick` sans `preventDefault`/`stopPropagation` ; filtrage des clients `uid` non vide et collecte UIDs stricte dans `finalizePayloadHelpers` ; `OfficeFoldersGetOneByUidHydrationHelper` : forcer `plainFolder.uid = folderUid` après `instanceToPlain`. Textes retirés : « Rien n'est envoyé… », « Associez un ou plusieurs clients… ». - -**Fichiers** : `AddClientToFolder` (sections, `GenericExistingItemsList`, `CustomersList`), `useCustomersManager`, `finalizePayloadHelpers`, `OfficeFoldersGetOneByUidHydrationHelper`. - -### Invitation confrères – validation avant envoi email (2026-03-12) - -**Composant** : `AddClientToFolder` (onglet « Confrères ») -**Page** : `/folders/[folderUid]/add/clients` - -**Problème** : Lors de l’ajout d’un confrère par email, le partage et l’email d’invitation étaient créés immédiatement au clic sur « Ajouter ». Si l’utilisateur cliquait ensuite « Annuler », le confrère restait invité et avait déjà reçu l’email. - -**Root cause** : Le flux confrères appelait `shareFolder` directement au clic « Ajouter », contrairement aux clients et tiers qui sont mis en attente puis persistés uniquement au clic « Valider ». - -**Correctifs** : Alignement sur le flux clients/tiers. Les confrères ajoutés sont stockés dans `pendingConfrereShares` ; l’API `shareFolder` et l’envoi d’email sont exécutés dans `addPendingConfrereShares` lors du `finalizeChanges`. Les confrères en attente sont affichés avec « Sera invité lors de la validation » et peuvent être retirés via `removePendingConfrere`. - -**Fichiers** : `useConfreresManager`, `finalizeOperationsHelpers`, `useFinalizeHandler`, `ExistingConfreresList`, `ConfreresSection`, `SearchConfrereSection`, `StakeholdersContent`, `types`. - -### Personne en charge du dossier – ContactBox et écran collaborateurs (2026-03-12) - -**Composant** : `ClientDashboard/ContactBox`, page « Modifier les collaborateurs » -**Pages** : Espace client/tiers (contact de l’office), `/folders/[folderUid]/edit/collaborators` - -**Problème** : Dans l’espace client/tiers, les contacts de l’office affichés (email, téléphone) ne correspondaient pas à la personne en charge du dossier : le composant choisissait le contact de façon arbitraire parmi les stakeholders (priorité au rôle « Notaire », sinon premier de la liste). Aucune notion de « personne en charge » ou « interlocuteur principal » n’existait dans le modèle. - -**Root cause** : Pas de champ dédié dans le modèle de données ; ContactBox utilisait uniquement `stakeholders` (ordre non garanti). - -**Évolution** : Ajout de `main_contact_uid` sur le dossier (optionnel, FK vers `users`). La personne en charge est obligatoirement parmi les collaborateurs du dossier (stakeholders). Par défaut = créateur du dossier. Elle est choisie/modifiée dans l’écran « Modifier les collaborateurs » via un champ « Personne en charge » (liste = collaborateurs effectifs : tout l’office ou sélection). ContactBox affiche désormais : `main_contact` puis `created_by` puis notaire puis premier stakeholder. - -**Fichiers** : Prisma `office_folders.main_contact_uid` + relation `main_contact` ; ressources Notary `OfficeFolder` ; backend DTO, `FolderHandler` (validation), `OfficeFoldersUpdateBuilder`, `OfficeFoldersRepository` (create/update), `OfficeFoldersGetOneByUidHydrationHelper` ; frontend `folderQueryBuilders`, `useFolderAndCustomer`, `ClientDashboard/ContactBox`, `UpdateFolderCollaborators` (loader, controller, vue avec SelectField), `commonUiI18nPart1` (mainContactLabel). Migration : `20260312120000_add_main_contact_uid_to_office_folders`. - -### Autocomplétion Confrères – clic suggestion non sélectionné (2026-02-22) - -**Composant** : `AddClientToFolder` (onglet « Confrères ») -**Page** : `/folders/[folderUid]/add/clients` - -**Problème** : Dans « Notaire (recherche annuaire) », cliquer une suggestion ferme la liste sans sélectionner le notaire. - -**Root cause** : La fermeture des suggestions était pilotée par `onBlur` sur le wrapper : le `blur` se déclenchait aussi lors d’un passage de focus interne (champ → suggestion), et la fermeture temporisée rendait l’événement de sélection non fiable (clic) et cassait le parcours clavier. - -**Correctifs** : `SearchConfrereSection` ferme les suggestions uniquement lorsque le focus sort réellement du wrapper (`relatedTarget` non contenu), et ferme aussi l’affichage des résultats côté hook via `setShowSuggestionsFromSearch(false)`. - -**Fichiers** : - -- `lecoffre-front-main/src/front/Components/Layouts/Folder/AddClientToFolder/sections/ConfreresSection/SearchConfrereSection.tsx` -- Correctif autocomplétion Confrères : voir [README.md](./README.md#consolidation-operationnelle-ex-operationsmd) (clic suggestion non sélectionné). - -### Création dossier – membres rattachés n'apparaissent pas (2026-01-28) - -**Problème** : Après création d'un dossier avec collaborateurs, la section « Gestionnaire du projet » est vide sur add/clients et fiche dossier. - -**Root cause** : Les requêtes GET folder ne demandaient ni `stakeholders` ni `created_by`. - -**Correctifs** : Dans `buildFolderQueryParams` (FolderInformation / `useFolderDataFetch`) et dans le loader add/clients (`useFolderDataInitialization`), ajouter `created_by` et `stakeholders` avec includes `contact`, `office_role`, `seats` / `subscription` / `office`. - -### Création dossier – reste sur « Chargement du dossier… » (2026-01-28) - -**Problème** : Après création et redirection vers `/folders/:uid/add/clients`, la page reste bloquée sur chargement. - -**Causes** : (1) GET dossier sans timeout ; (2) `folderUid` absent au premier rendu (`router.query` pas encore à jour après `router.push`). - -**Correctifs** : Timeout 30 s sur le chargement (Promise.race) ; si timeout → toast, redirection `/folders`. Fallback `folderUid` : extraction par regex sur `router.asPath` (`/\/folders\/([^/]+)\/add\/clients/`) quand `query["folderUid"]` vide. Nettoyage des logs debug. - -**Fichiers** : `useFolderDataInitialization`, `useManageFolderStakeholdersController`, `createFolderSubmitHelpers`. - -### Dossier – membres absents sur la page détail (2026-01-28) - -**Problème** : Dossier créé avec client/tiers/notaire invité, mais page détail affiche « Ajouter des clients » (NoClientView) au lieu des onglets participants. - -**Root cause** : `instanceToPlain` peut omettre `customers` et `folder_sharings` ; `doesFolderHaveClient` reste faux. - -**Correctifs** : Dans `OfficeFoldersGetOneByUidHydrationHelper`, après `instanceToPlain`, forcer `customers` et `folder_sharings` dans le plain lorsqu'ils sont présents sur l'entité brute mais absents ou vides dans le plain (même logique que `uid`). - -**Correctif frontend (2026-03-09)** : voir `docs/fixKnowledge/2026-03-09-folder-detail-participants-hidden-without-customers.md`. - -### Création dossier – validation failure (2026-01-27 / 2026-01-28) - -**Problème** : « Dossier chargé mais données invalides », `uid` manquant en POST ou GET. - -**Root causes** : POST retournait une instance de classe au lieu d'un plain ; `instanceToPlain` pouvait omettre `uid`. GET idem. - -**Correctifs backend** : `OfficeFoldersCreationHelper` : `instanceToPlain` puis **toujours** `plainFolder.uid = officeFolderEntity.uid`. `OfficeFoldersGetOneByUidHydrationHelper` : forcer `uid` (et `customers` / `folder_sharings` / `notes` / `folder_number` si omis). Stripe 404 : `StripeController` + `StripeService.isStripeNotFoundError()` pour ne pas traiter 404 comme erreur utilisateur. - -**Correctifs frontend** : Logs debug dans `useFolderDataInitialization` et `createFolderSubmitHelpers` ; délai 2000 ms avant redirection. Vérifier en déploiement que le correctif `plainFolder.uid = officeFolderEntity.uid` est bien présent dans le build. - -### Notes membres (notaires / collaborateurs) non affichées dans les onglets (2026-01-29) - -**Problème** : Les notes n'apparaissent pas dans les onglets clients / notaires invités / tiers, mais sont visibles dans le formulaire de modification. - -**Root cause** : `instanceToPlain` peut omettre le tableau `notes` ; seul le forçage de `customers` et `folder_sharings` existait. - -**Correctifs** : Dans `OfficeFoldersGetOneByUidHydrationHelper`, forcer `plainFolder.notes = rawNotes` lorsque l'entité brute a des notes mais le plain les omet. Étendre `FilteredEntityForPostProcess` avec `notes`. Log « notes forcés (instanceToPlain les avait omis) ». - -### Supprimer un dossier – modale ne s'affiche pas / champ confirmation (2026-01-28) - -**Problème** : Clic « Supprimer le dossier » (menu ⋮) : modale semble ne pas s'ouvrir ou menu se ré-affiche par-dessus. Champ de confirmation : label « Confirmation » alors qu'il faut saisir le numéro de dossier → bouton grisé. Si `folder_number` absent, rien en gras. - -**Root causes** : (1) Clic sur l'item déclenche `onClose` puis `onClick` ; l'événement remonte au root du Menu → `toggle` ré-ouvre le menu. (2) Items sans `link` avaient `key={undefined}` → clés dupliquées. (3) Label et `expectedValue` du champ de confirmation peu clairs. (4) `folder_number` parfois omis par `instanceToPlain`. - -**Correctifs** : `MenuItem` : après `onClose` / `onClick` / navigation, appeler `e.stopPropagation()` si le clic est traité. Menu : clé stable `item.link ?? \`${item.text}-${index}\``. DeleteModal : label « Numéro du dossier (répétez ci‑dessus) », `expectedValue` = numéro, `errorMessage` explicite ; si numéro vide, pas de `requireConfirmation`. ConfirmModal : si `expectedValue` vide, afficher « La valeur à saisir n'est pas disponible. Actualisez la page puis réessayez. » et garder le bouton désactivé. Backend : forcer `folder_number` dans `OfficeFoldersGetOneByUidHydrationHelper` quand omis. - -**Fichiers** : `MenuItem`, `Menu`, `DeleteModal`, `ConfirmModal`, `OfficeFoldersGetOneByUidHydrationHelper`. - -### Stripe 404 – bannière abonnement (2026-01-29) - -**Problème** : `GET /api/v1/admin/stripe/:id` en 404 (abonnement absent chez Stripe) → Toast « L'élément demandé n'existe pas », warn « Unable to load subscription banner » à chaque chargement. - -**Root cause** : Aucune distinction 404 (ressource absente, cas acceptable) vs erreur technique ; `onError` systématique. - -**Correctifs** : `StripeFactory.getStripeSubscriptionByUid` : en catch, si `http_status === 404`, ne pas appeler `onError`, puis rejeter. `Header` / `useSubscriptionLoader` : en catch, si 404, retourner sans logger. - -**Fichiers** : `StripeFactory`, `Header/index.tsx`. - -### Demander les documents – configuration du type de document non chargée - -**Page** : « Demander des documents ». - -**Problème** : Aucun type de document à sélectionner ; configuration deed / deed_type non chargée ou non exploitée. - -**Root causes** : Backend : `instanceToPlain` peut omettre `deed` (et `deed.document_types`) ; aucun forçage de `deed`. Front : utilisation uniquement de `deed.document_types`, sans `deed_type.document_types` ni fallback lorsque toutes les sources sont vides. - -**Correctifs** : Backend (`OfficeFoldersGetOneByUidHydrationHelper`) : forcer `deed` (et `document_types`) dans la réponse lorsqu’ils sont présents sur l’entité brute mais omis par `instanceToPlain`. Front (`useAskDocumentsFolderData`) : inclure `deed_type` avec `document_types` dans le `q` du fetch ; priorité `deed_type.document_types` → `deed.document_types` → types déduits des documents existants ; si tout vide, fallback `DocumentTypes.getInstance().get({})` ; log lorsque le fallback est utilisé. - -**Fichiers** : `OfficeFoldersGetOneByUidHydrationHelper`, `useAskDocumentsFolderData`. - ---- - -## 4. Agrégation des Collaborateurs - -### Vue d'Ensemble - -La fonctionnalité d'agrégation des collaborateurs retourne les **comptes utilisateurs existants** d'un office (créés lors de la première connexion OAuth IdNot). - -### API Endpoint - -#### `GET /api/v1/notary/users/aggregated` - -**Authentification** : Requise (JWT) -**Permissions** : `authHandler`, `ruleHandler` - -**Description** : Retourne les collaborateurs de l'office disposant d'un compte utilisateur. - -**Réponse** : - -```json -[ - { - "uid": "uuid-user-1", - "idNot": "123456", - "contact": { - "uid": "uuid-contact-1", - "email": "notaire@example.com", - "first_name": "Jean", - "last_name": "Dupont", - "phone": "+33123456789", - "civility": "M." - }, - "_metadata": { - "source": "users", - "has_user_account": true - } - } -] -``` - ---- - -## 11. Gestion des Documents - -### Visibilité et Affichage - -#### Documents Déposés (DEPOSITED) - -- **Pour le déposant** (Client, Tiers, Notaire Invité) : - - Le fichier **n'est pas affiché** (pas d'icône "œil", pas de téléchargement). - - Seule la ligne avec la date de dépôt est visible. - - Le module de dépôt n'est plus éditable pour empêcher la modification. - - Accès direct URL : Message "En attente de validation..." sans contenu. -- **Pour le notaire en charge** : - - Accès complet (visualisation, validation, refus). - -#### Dépôt côté déposant (Client / Tiers / Notaire invité) - -- La section **Documents à envoyer** utilise le même module d'ajout que **Documents supplémentaires (facultatif)** : ouverture via **Ajouter un document**, gestion de la sélection locale (ajout/retrait), puis envoi explicite via **Déposer le document**. -- Le dépôt n'est plus déclenché au glisser-déposer : le glisser-déposer alimente uniquement la sélection locale dans la modale. -- Après validation du dépôt, le rafraîchissement garde le comportement de ligne existant dans le tableau (mise à jour de statut/date selon le document). -- Les libellés de dépôt sont centralisés par contexte (`generic`, `modal`, `otherDocuments`, `rib`) dans `DesignSystem/helpers/fileUploadUiI18n.ts`, et les CTA communs de modales (`Annuler`, `Confirmer`) sont centralisés dans `DesignSystem/helpers/commonUiI18n.ts`. -- Les libellés d'actions métier de modales/layouts (`Valider`, `Supprimer`, `Enregistrer`, `Créer`, `Ajouter`, `Continuer`, `Quitter`) sont regroupés dans les namespaces dédiés de `DesignSystem/helpers/commonUiI18n.ts` et réutilisés dans les modales existantes. -- Les libellés métier composés sont regroupés par domaine dans `DesignSystem/helpers/commonUiI18n.ts` (`folder`, `subscription`, `member`, `role`) pour éviter les chaînes actionnelles locales dans les vues et menus. -- Les CTA et labels de tests SuperAdmin sont aussi centralisés dans `commonUiI18n.ts` via les namespaces `health` et `adminTools`, y compris les libellés de titres secondaires d'actions (boutons détails, liens utilitaires, titres de consultation). -- Les textes descriptifs non actionnels sont centralisés dans les namespaces `healthMessages`, `folderMessages`, `demoMessages`, `subscriptionMessages`, `userMessages`, `usersMessages` et `documentMessages` de `commonUiI18n.ts` (cartes de test SuperAdmin, modales d'archive/suppression dossier, sections de démonstration du DesignSystem, écrans abonnement, écrans utilisateurs et viewer documentaire). -- Les placeholders et libellés de formulaires métier longs sont centralisés dans `formMessages` (`commonUiI18n.ts`) pour les flux de préparation de dossier (tiers, confrères) et d'envoi documentaire. -- Les messages dynamiques sont composés par des helpers dédiés dans `DesignSystem/helpers/commonUiMessageBuilders.ts` pour éviter la concaténation dans les composants (`votes` SuperAdmin, message de dépôt en attente dans le viewer). -- Les écrans dossier hors scope upload initial (`AddThirdParty`, `AskDocuments`, `Folder` landing, sections NoClient, modales de suppression client/document) sont alignés sur `folderMessages`/`formMessages` et réutilisent les helpers de composition pour les messages conditionnels de permissions/chargement. -- Les messages "chargement dossier / droits insuffisants" passent par un helper dédié à objet de configuration (`buildFolderPermissionOrLoadingMessage`) et les modales de suppression mutualisent leurs feedbacks succès/erreur via des helpers dédiés. -- Les flux de suppression utilisent des payload helpers dédiés par domaine : `buildDocumentDeleteModalPayload` (documents), `buildCustomerDeleteModalPayload` (client de dossier), `buildFolderDeleteModalPayload` (suppression dossier soft/hard), `buildThirdPartyDeleteModalPayload` (tiers du dossier), `buildRibDeleteModalPayload` (RIB) et `buildDeedTypeDeleteModalPayload` (type d'acte), avec centralisation de `title/message/confirmLabel` et des messages d'erreur. -- La confirmation de suppression avec saisie du numéro de dossier est factorisée via `buildFolderDeleteRequireConfirmationPayload` pour éviter la duplication `label/placeholder/errorMessage` dans les modales. -- Les anciens `alert()` des flux de téléchargement documents/certificats et création de dossier (helpers `FolderInformation`/`InvitedView`/`CreateFolder`) sont migrés vers `ToasterService` avec messages centralisés dans `commonUiI18n.ts` et composition d'erreur agrégée via `buildAggregatedCertificateErrorMessage`. -- Le flux de désarchivage dossier n'utilise plus `confirm()`: il passe désormais par une `ConfirmModal` dédiée pilotée via `useFolderModals` (`unarchiveModal`) et payload `buildUnarchiveFolderConfirmPayload`. -- Les erreurs de téléchargement ZIP/certificat sont unifiées via le helper global `showDownloadErrorToast` (`Utils/documentDownloadHelpers.ts`) et réutilisées dans `Folder` et `ClientDashboard`. -- Les flux `SuperAdmin/SiteTexts` (create/update/publish/archive/load) n'utilisent plus `alert()/confirm()` : feedback via `ToasterService` + modale de confirmation d'archivage. -- Les toasts de succès de téléchargement sont aussi centralisés via `showDownloadSuccessToast` dans `Utils/documentDownloadHelpers.ts` et réutilisés par les flux `Folder`, `ClientDashboard`, `singleFileDownloadHelper` et `DocumentDownloadService`. -- Les toasts de succès métier hors téléchargement (create/update/publish/archive) sont harmonisés via `showBusinessSuccessToast` (`Utils/businessOperationToastHelpers.ts`) avec payload centralisé (`buildBusinessSuccessToastPayload`). -- Les erreurs métier hors téléchargement sont harmonisées via `showBusinessErrorToast` (`Utils/businessOperationToastHelpers.ts`) avec payload builders par domaine (`collaboratorMessages`, `clientDashboardMessages`, `folderMessages`) pour éviter les descriptions codées en dur. -- Les warnings métier hors téléchargement sont harmonisés via `showBusinessWarningToast` (`Utils/businessOperationToastHelpers.ts`) avec payload builders de domaine (`subscriptionMessages`, `folderMessages`) pour homogénéiser les flux d’information/validation. -- Les flux `Folder/AskDocuments`, `Folder/SendDocuments` et `Folder/AddClientToFolder` utilisent désormais les mêmes helpers `showBusinessErrorToast`/`showBusinessWarningToast` et des builders dédiés `commonUiMessageBuilders.ts` pour supprimer les `ToasterService.getInstance().warn/error` métier locaux. -- Les builders `folderMessages` sont regroupés par sous-domaines (`parameterDocuments`, `askDocuments`, `sendDocuments`, `stakeholders`) via `DesignSystem/helpers/folderToastMessageBuilders.ts` et exposés par `commonUiMessageBuilders.ts`. -- Les builders non-folder sont aussi extraits par domaine pour limiter la croissance de `commonUiMessageBuilders.ts` : - - `DesignSystem/helpers/clientDashboardMessageBuilders.ts` - - `DesignSystem/helpers/clientDashboardToastMessageBuilders.ts` - - `DesignSystem/helpers/collaboratorToastMessageBuilders.ts` - - `DesignSystem/helpers/createCustomerNoteToastMessageBuilders.ts` - - `DesignSystem/helpers/siteTextsMessageBuilders.ts` - - `DesignSystem/helpers/siteTextsToastMessageBuilders.ts` - - `DesignSystem/helpers/subscriptionToastMessageBuilders.ts` - - `DesignSystem/helpers/deedTypesToastMessageBuilders.ts` - - `DesignSystem/helpers/folderInformationToastMessageBuilders.ts` - - `DesignSystem/helpers/folderViewDocumentsToastMessageBuilders.ts` - - `DesignSystem/helpers/toastPayloadBuilderHelpers.ts` : helper générique partagé pour construire les payloads error/warning/success (évite la répétition dans chaque `*ToastMessageBuilders`). - - `DesignSystem/helpers/toastMessageBuilders/index.ts` centralise le pattern de re-export des builders `*ToastMessageBuilders.ts`. - - `commonUiMessageBuilders.ts` conserve un re-export de compatibilité. -- Les écrans `ClientDashboard/ViewDocumentSent`, `ClientDashboard/ViewDocumentsNotary` et `Folder/ViewDocuments` n’utilisent plus de `ToasterService.getInstance().error` inline pour les erreurs métier de chargement/validation. -- Les layouts métier ciblés `Folder`, `ClientDashboard` et `DeedTypes` n’utilisent plus de `ToasterService.getInstance().warn/error` direct : les flux sont alignés sur `showBusinessErrorToast` / `showBusinessWarningToast` avec payloads centralisés. -- Les succès métier peuvent aussi être injectés via payload (`title` + `description`) dans `showBusinessSuccessToast`, ce qui permet d’aligner `Folder/ViewDocuments` et `DeedTypes` sans libellés inline dans les hooks. -- Les flux `subscription-plans` n'utilisent plus `alert()/confirm()` : confirmations via `ConfirmModal` + payload helpers (`buildSubscriptionPlanSetDefaultConfirmPayload`, `buildSubscriptionPlanDeleteConfirmPayload`) et feedback centralisé. -- Les derniers usages résiduels de `alert()/confirm()` côté front ont été retirés, y compris dans la section de démonstration `ButtonIconMenuSection` et le fallback technique `ToasterHelper`. - -#### Documents Envoyés (SENT) - -- **Tableau "Documents envoyés"** : La colonne "Type de document" est renommée **"Nom du document"**. -- **Notaire Invité** : - - Les documents de type "Document notaire" (envoyés par le notaire gestionnaire) sont **exclus** de la section "Documents à envoyer". - - Ils apparaissent uniquement dans "Documents reçus". - - L'encart de notification ("X documents reçus") est aligné avec le nombre réel de documents affichés (excluant les doublons techniques). - -#### Visualisation (Viewer) - -- **Sélection du fichier** : En cas de multiples fichiers (ex: refus puis re-dépôt), le viewer affiche par défaut le **plus récent** (tri par `created_at` desc), et non le premier de la liste (souvent l'ancien refusé). -- **Batch** : L'affichage d'un document issu d'un batch (ex: pièce d'identité) ne montre que les fichiers de **ce document spécifique** (`docUid`), sans fusionner avec les autres documents du même batch. - -### Téléchargement - -#### Logique ZIP vs Fichier Unique - -- **Alignement** : La logique de téléchargement utilise le même dédoublonnage que l'affichage (`getUniqueFiles` dans `fileHelpers.ts`). -- **Comportement** : - - Si **1 seul fichier unique** (après dédoublonnage par nom) : Téléchargement direct du fichier. - - Si **> 1 fichiers uniques** : Téléchargement d'un ZIP. - - Évite de créer un ZIP inutile pour un document qui n'a qu'une seule version pertinente visible. - -### Refactoring & Helpers - -- **`fileHelpers.ts`** : Centralise la logique de dédoublonnage (`getUniqueFiles`, `getFileDisplayName`) partagée entre l'affichage (`tableColumnHelpers`) et le téléchargement (`documentDownloadHelpers`). -- **`documentFilterHelpers.ts`** : Centralise les statuts (`DOCUMENTS_TO_SEND_STATUSES`, etc.) pour garantir la cohérence entre les vues Client, Tiers et Invité. - ---- - -## 5. Build et Tests - -- `npm run build` exécute désormais `npm run typecheck` puis `node ./scripts/run-next-build.cjs`. Ce script Node applique les variables d'environnement requises (`TURBOPACK_ROOT`, `NEXT_TELEMETRY_DISABLED`) avant d'invoquer `next build` via `next/dist/bin/next`. Cette approche garantit un comportement identique sous Windows (où les affectations `VAR=value` n'étaient pas interprétées par `cmd.exe`) et sous Linux/macOS. -- Le fichier `lecoffre-front-main/scripts/run-next-build.cjs` est responsable du lancement de `next build`. Il doit être conservé à jour si des arguments supplémentaires sont nécessaires. - -### Convention de typage JSX (React 19) - -- Les composants fonctionnels utilisent `React.JSX.Element` (ou `React.JSX.Element | null` pour un rendu conditionnel). -- Les composants de classe utilisent `render(): React.ReactNode`. -- Les props de contenu (`children`, `title`, `text`, `icon`, `footer`, etc.) utilisent `React.ReactNode`. -- Le shim global `declare namespace JSX` n'est plus utilisé dans `custom.d.ts`. -- Les usages `JSX.Element` sont interdits dans le frontend pour éviter les incompatibilités de typage entre runtime JSX et React 19. - -### Stratégie couche API (useApiClient vs BaseApiService) - -- **useApiClient** (hook React) : cible pour tout appel API depuis un composant ou un hook. Gère le token, les erreurs et le base URL. Privilégier pour les nouveaux usages dans l’UI. -- **BaseApiService / getInstance()** (services sous `LeCoffreApi/`) : utilisé par les factories (Folders, Documents, etc.) pour les appels hors composants (loaders, services partagés). Conservé pour compatibilité et contexte non-React. -- **Déduplication** : réutiliser les builders de paramètres de requête (`@Front/Utils/folderQueryBuilders`, `documentQueryBuilders` dans ClientDashboard) pour éviter des `q` / `include` divergents entre écrans. -- **Règle** : nouveau code dans un composant ou hook → préférer `useApiClient` ; code dans un service/factory existant → garder BaseApiService tant que la migration n’est pas planifiée. - -**Appliqué (dossiers notary)** : les appels API dossiers notary passent par `useApiClient` via le hook `useNotaryFoldersApi` et les helpers `@Front/Utils/notaryFoldersApiEndpoints` et `@Front/Utils/folderQueryBuilders`. Migrés : liste et détail (`useFolderListData`, `useFolderDataFetch`), mise à jour / suppression / création (`UpdateFolderMetadata`, `DeleteModal`, `DeleteFolderModal`, `createFolderSubmitHelpers`, `finalizePayloadHelpers`), chargement dossier (`useFolderDataInitialization`, `useAskDocumentsFolderData`), archive / unarchive / restore (`ArchiveModal`, `folderInformationControllerCallbacksHelpers` onUnarchive, `ArchiveAlertWarning`), collaborateurs et participants (`useFolderCollaboratorsLoader`, `useUpdateFolderCollaboratorsController`, `useParticipantDeletions`), envoi documents et invités (`dataLoaders` SendDocuments, `useInvitedCustomerFolders`), validation numéro dossier (`useFolderNumberValidation`), redirection auto (`useFolderAutoRedirect`), documents reçus invité (`useInvitedReceivedDocumentsController`), page corbeille (`pages/folders/deleted` : liste supprimés + restauration), en-tête client dossier (`UserFolderHeader`, refactor classe → FC). **Appliqué (dossiers customer)** : les appels API dossiers customer (clients, tiers, notaires invités) passent par `useApiClient` via le hook `useCustomerFoldersApi` et les helpers `@Front/Utils/customerFoldersApiEndpoints`. Migrés : liste et détail (`useCustomerFolders`, `useOfficeChangeRedirect`, `folderLoaders` SelectFolder), chargement dossier et client (`useFolderAndCustomer` ClientDashboard et ReceivedDocuments, `useClientAccountLoader`), notes (`useNoteLoaderUnified`), CreateCustomerNote (`loadFolderWithIncludes`, `useFolderAndCustomerLoader`, `loadAndProcessFolder`). - -### Utilisation Frontend - -#### Ancien Code - -```typescript -const userQuery: IGetUsersParams = { - include: { - contact: { - select: { - first_name: true, - last_name: true, - }, - }, - }, -}; - -const availableCollaborators = await Users.getInstance().get(userQuery); -``` - -#### Nouveau Code - -```typescript -// Utiliser le nouvel endpoint aggregated -const response = await axios.get('/api/v1/notary/users/aggregated', { - headers: { Authorization: `Bearer ${token}` } -}); - -const availableCollaborators = response.data; - -// Les collaborateurs sont déjà au bon format -const selectOptions = availableCollaborators.map(collaborator => ({ - label: `${collaborator.contact?.first_name} ${collaborator.contact?.last_name}`, - id: collaborator.uid, - // Optionnel : afficher une indication visuelle pour les collaborateurs sans compte - badge: collaborator._metadata?.has_user_account ? null : 'Pas de compte' -})); -``` - -### Points d'Attention - -#### UID des Collaborateurs sans Compte - -Les collaborateurs sans compte ont un `uid` au format `sync:{idnot}`. - -**Important** : Ne jamais utiliser cet UID pour créer des enregistrements en base. - -```typescript -// ❌ MAUVAIS -await createStakeholder({ - user_uid: collaborator.uid, // Si c'est "sync:123", ça va échouer - folder_uid: folderUid, -}); - -// ✅ BON -if (collaborator._metadata?.has_user_account) { - await createStakeholder({ - user_uid: collaborator.uid, - folder_uid: folderUid, - }); -} else { - // Créer le compte d'abord OU afficher un message - alert('Ce collaborateur doit d\'abord créer un compte'); -} -``` - -#### Gestion des Sièges d'Abonnement - -Les collaborateurs sans compte **ne peuvent pas** avoir de siège d'abonnement. - -Filtrer les collaborateurs pour n'afficher que ceux avec compte dans la gestion des sièges. - -### Migration Progressive - -L'ancien endpoint `/api/v1/notary/users` reste disponible pour assurer la compatibilité. - -La migration peut se faire progressivement composant par composant : - -1. `UpdateFolderCollaborators` -2. `SubscriptionManageCollaborators` -3. `CreateFolder` -4. Autres composants utilisant la sélection de collaborateurs - -### Service API Frontend Recommandé - -**Fichier** : `lecoffre-front-main/src/front/Api/CollaboratorsService.ts` - -```typescript -import { apiClient } from './ApiClient'; - -export class CollaboratorsService { - static getInstance(): CollaboratorsService { - // Singleton pattern - } - - async getAggregated(): Promise { - const response = await apiClient.get('/api/v1/notary/users/aggregated'); - return response.data; - } - - async getWithAccounts(): Promise { - const all = await this.getAggregated(); - return all.filter(c => c._metadata.has_user_account === true); - } - - async getWithoutAccounts(): Promise { - const all = await this.getAggregated(); - return all.filter(c => c._metadata.has_user_account === false); - } -} -``` - ---- - -## 5. Architecture Technique - -### Wrapper jwt-decode - -**Date** : 2025-11-24 -**Version** : 2.0.1+ -**Dernière mise à jour** : 2025-11-24 - -#### Problème - -Deux problèmes principaux ont été rencontrés avec `jwt-decode` v4 : - -1. **Turbopack (bundler Next.js 16)** : Détecte statiquement que `jwt-decode` v4 n'a pas d'export par défaut dans le build ESM, causant l'erreur : - - ```text - Export default doesn't exist in target module - The export default was not found in module [project]/node_modules/jwt-decode/build/esm/index.js - ``` - -2. **Environnement de build** : En build de production, TypeScript résout `jwt-decode` comme un module CJS (`/repo/lecoffre-front-main/node_modules/jwt-decode/build/cjs/index`), et détecte statiquement que la propriété `default` n'existe pas, causant l'erreur : - - ```text - Property 'default' does not exist on type 'typeof import("/repo/lecoffre-front-main/node_modules/jwt-decode/build/cjs/index")' - ``` - -#### Solution - -Un wrapper a été créé pour gérer les différences entre ESM et CJS, avec des assertions de type explicites pour garantir la sécurité TypeScript : - -**Fichier** : `lecoffre-front-main/src/front/Utils/jwtDecodeWrapper.ts` - -```typescript -import * as jwtDecodeModule from "jwt-decode"; - -// Type pour gérer les différentes formes d'export de jwt-decode -type JwtDecodeFunction = (token: string) => unknown; - -// Type pour le module jwt-decode avec toutes ses formes possibles -type JwtDecodeModuleType = JwtDecodeFunction | { default?: JwtDecodeFunction; jwtDecode?: JwtDecodeFunction } | Record; - -// Type assertion pour gérer les différences entre ESM et CJS -const jwtDecodeModuleAny = jwtDecodeModule as unknown as JwtDecodeModuleType; - -// Vérification à l'exécution pour déterminer comment accéder à la fonction -// Utilisation de if/else explicites pour une meilleure inférence de type -let jwtDecodeFunction: JwtDecodeFunction; - -if (typeof jwtDecodeModuleAny === "function") { - jwtDecodeFunction = jwtDecodeModuleAny as JwtDecodeFunction; -} else { - const moduleObj = jwtDecodeModuleAny as Record; - const defaultExport = moduleObj["default"]; - const namedExport = moduleObj["jwtDecode"]; - if (typeof defaultExport === "function") { - jwtDecodeFunction = defaultExport as JwtDecodeFunction; - } else if (typeof namedExport === "function") { - jwtDecodeFunction = namedExport as JwtDecodeFunction; - } else { - // Fallback: essayer d'utiliser le module directement - jwtDecodeFunction = jwtDecodeModuleAny as JwtDecodeFunction; - } -} - -export const jwtDecode = jwtDecodeFunction as (token: string) => T; -``` - -**Points clés de l'implémentation :** - -- Utilisation d'assertions de type explicites (`as JwtDecodeFunction`) pour garantir la sécurité TypeScript -- Vérification à l'exécution (runtime) avec `typeof` avant les assertions pour garantir la sécurité -- Utilisation de `if/else` explicites pour une meilleure inférence de type TypeScript -- Support de plusieurs formats d'export : - - Fonction directe (CJS) - - Export default (ESM) - - Export nommé `jwtDecode` - - Fallback sur le module entier - -#### Utilisation - -**Avant** : - -```typescript -import jwtDecode from "jwt-decode"; -const decoded = jwtDecode(token); -``` - -**Après** : - -```typescript -import { jwtDecode } from "@Front/Utils/jwtDecodeWrapper"; -const decoded = jwtDecode(token); -``` - -#### Fichiers Modifiés - -- `lecoffre-front-main/src/front/Api/BaseApiService.ts` -- `lecoffre-front-main/src/front/Services/TokenRefreshService/TokenRefreshService.ts` -- `lecoffre-front-main/src/proxy.ts` -- `lecoffre-front-main/src/front/Stores/ActiveOfficeStore.ts` - -#### Avantages - -- ✅ Compatible avec Turbopack (Next.js 16) -- ✅ Gère automatiquement les différences ESM/CJS -- ✅ Fonctionne en build de production (résolution CJS) -- ✅ Évite les erreurs TypeScript statiques grâce à `as any` -- ✅ Vérification à l'exécution pour déterminer le format d'export -- ✅ Type-safe avec TypeScript (casting final) - ---- - -## 6. Authentification IdNot et Logging - -### Logging des Données API IdNot au Login - -**Date** : 2025-01-XX -**Version** : 2.0.1+ - -#### Vue d'Ensemble - -Des logs détaillés ont été ajoutés dans la console du navigateur pour tracer les données API reçues d'IdNot lors du processus d'authentification, permettant de diagnostiquer les problèmes d'authentification et de suivre le flux complet. - -#### Logs Disponibles - -**Tous les environnements** : - -- Logs de la présence des tokens (hasAccessToken, hasRefreshToken) sans jamais logger les valeurs -- Logs du JWT décodé avec informations utilisateur (userId, officeId, role, règles) -- Logs des erreurs avec contexte détaillé - -#### Fichiers Concernés - -- `lecoffre-front-main/src/front/Api/Auth/IdNot/index.ts` : Service d'authentification IdNot avec logs -- `lecoffre-front-main/src/front/Components/Layouts/LoginCallback/hooks/helpers/idNotAuthHelpers.ts` : Échange code → tokens (sans log des valeurs de tokens) -- `lecoffre-front-main/src/front/Components/Layouts/LoginCallback/index.tsx` : Callback de login avec logs détaillés - -#### Exemples de Logs - -```text -[Front] 🔄 [IdNot] Exchanging authorization code for JWT tokens -[Front] ✅ [LoginCallback] IdNot tokens received, connecting user { hasAccessToken: true, hasRefreshToken: true } -[Front] ✅ [LoginCallback] JWT decoded successfully { userId: "...", officeId: "...", role: "admin", hasRules: true, rulesCount: 15 } -``` - -#### Sécurité - -- ✅ Tokens (accessToken, refreshToken) jamais loggés, quel que soit l'environnement (risque de fuite via agrégation de logs) -- ✅ Seuls les 10 premiers caractères du code d'autorisation sont loggés -- ✅ Uniquement des indicateurs booléens (hasAccessToken, hasRefreshToken) sont loggés après réception des tokens - -**Documentation complète** : Voir ARCHITECTURE (Types d’utilisateurs, connexion IdNot) et logs IdNot au login. - -### Nettoyage silencieux des sessions expirées sur les pages publiques - -- Les appels publics (`BasePublic` → `SiteTextsApi`, `IncidentReportApi`) n'imposent plus de redirection vers `https://qual-connexion.idnot.fr/` quand un `leCoffreAccessToken` expiré est présent mais que le refresh token a disparu. -- `BaseApiService.checkJwtToken()` détecte toujours le flag IdNot, mais `BasePublic` surcharge désormais `shouldEnforceIdNotLogoutOnExpiredSession()` pour renvoyer `false`. Le SDK se contente alors de supprimer les cookies locaux au lieu de forcer une navigation. -- Toutes les API privées continuent de déclencher la redirection Id.Not dès que l'utilisateur tente d'appeler un endpoint protégé avec un token expiré ou invalide. -- Cette séparation évite la "redirection instantanée" depuis `/` tout en conservant la synchronisation stricte des sessions sur les pages authentifiées. - -### Rafraîchissement automatique des sessions (2025-12) - -- Nouveau service `SessionRefreshService` démarré depuis `_app.tsx` -- Lit en continu le refresh token (`leCoffreRefreshToken`) pour calculer sa date d'expiration (`exp`) -- Programme un rafraîchissement 2 minutes avant l'expiration (fallback toutes les 10 minutes) -- S'appuie sur `TokenRefreshService` (qui reçoit désormais un refresh token *rotated* à chaque appel) -- Ré-écoute les événements `UserStore` / `CustomerStore` (`onConnect` / `onDisconnect`) pour relancer ou arrêter les timers quand un utilisateur change d'état -- Empêche les expirations silencieuses malgré une activité continue dans l'UI, tout en journalisant les succès / échecs côté front - ---- - -## 7. Interface Espace Notaire - Onglets et Tableaux - -### Refonte des onglets membres et tableaux documents - -**Date** : 2025-01-XX -**Version** : 2.0.1+ -**Page** : `/folders/[folderUid]` - Vue ClientView - -#### Vue d'Ensemble - -Refonte complète de l'interface des onglets des membres et des tableaux de documents dans l'espace notaire pour améliorer la clarté et la conformité des documents. - -#### Modifications des Onglets Membres - -**Composants modifiés** : - -- `ClientView/ThirdPartyBox/index.tsx` -- `ClientView/ClientBox/index.tsx` -- `ClientView/SharedNotaryBox/index.tsx` - -**Changements** : - -1. **ThirdPartyBox** : - - ❌ Suppression du bouton "Renvoyer l'invitation" - - ❌ Suppression du bouton "Supprimer" - - ✅ Bouton unique "Modifier la note" (remplace le menu) - -2. **ClientBox** : - - ✅ Texte du bouton de note changé en "Modifier la note" (au lieu de "Rajouter/modifier une note") - -3. **SharedNotaryBox** : - - ✅ Texte du bouton de note changé en "Modifier la note" - - ✅ Menu conservé avec option "Modifier les informations" (si disponible) - -#### Modifications des Tableaux de Documents - -**Composants modifiés** : - -- `ClientView/DocumentTables/index.tsx` -- `ClientView/DocumentTables/useDocumentTablesController.ts` -- `ClientView/DocumentTables/hooks/useDocumentRows.tsx` -- `ClientView/DocumentTables/helpers/tableHeaders.ts` -- `ClientView/DocumentTables/helpers/documentConformity.ts` -- `ClientView/DocumentTables/components/DocumentTablesHeader.tsx` -- `ClientView/DocumentTables/components/ValidatedDocumentsSection.tsx` - -**Nouvelle fonctionnalité : Vérification de conformité** - -Un nouveau helper `documentConformity.ts` vérifie la conformité des documents : - -- **Lien IPFS** : Présence de `watermarked_s3_key` ou `file_path` dans les fichiers -- **Ancrage** : Présence de `document_anchor` -- **Données d'ancrage** : Présence de `document_anchor.proof_data` - -Si l'un de ces éléments manque, le statut du document devient **"NON CONFORME"**. - -**⚠️ Exception importante : Documents demandés (ASKED)** - -Les documents avec le statut `ASKED` (demandés mais pas encore déposés) ne sont **jamais vérifiés pour la conformité** car : - -- Ils n'ont pas encore de fichier déposé -- Ils ne peuvent donc pas avoir de filigrane, d'ancrage ou de métadonnées -- La vérification de conformité ne s'applique qu'aux documents déposés (`DEPOSITED`) - -Les documents ASKED affichent toujours le statut "DEMANDE" et jamais "NON CONFORME". - -**Nouveaux tableaux** : - -1. **"Documents demandés"** (sans nombre entre parenthèses) : - - **Statuts** : DEMANDES, A VALIDER, NON CONFORME - - **Colonnes** : Type de document, Statut, Déposé le, Action (voir) - - **Note** : Pas de colonne "Nom du document" car les documents ASKED n'ont pas encore de fichiers déposés - - **Note** : Pas de case à cocher pour les documents ASKED et DEPOSITED - - **Note** : Vérification de conformité uniquement pour les documents DEPOSITED (les documents ASKED affichent toujours "DEMANDE") - - **Fichier** : `useAskedDocumentsRows()` - Combine ASKED et DEPOSITED avec vérification conformité uniquement pour DEPOSITED - - **DEPOSITED (À VALIDER)** : L'icône « voir » (œil) est affichée pour le notaire afin de visualiser et valider le document. Pour le déposant (client/tiers/notaire invité), la ligne avec la date de dépôt reste visible mais le fichier n'est pas affiché ni modifiable sur la page de visualisation. - -2. **"Documents validés"** (sans nombre entre parenthèses) : - - **Statuts** : VALIDE, TELECHARGE, NON CONFORME - - **Colonnes** : case à cocher, Nom du document, Type de document, Déposé le, Action (télécharger, voir) - - **Boutons** : Télécharger ZIP et Télécharger Certificat (selon les coches) - - **Fichier** : `useValidatedDocumentsRows()` - Combine VALIDATED et DOWNLOADED avec vérification conformité - -3. **"Documents envoyés"** (sans nombre entre parenthèses) : - - **Statuts** : ENVOYE, NON CONFORME - - **Colonnes** : case à cocher, Nom du document, Statut, Déposé le, Action (télécharger, supprimer) - - **Boutons** : Télécharger ZIP et Télécharger Certificat (selon les coches) - - **Fichier** : `useSentDocumentsRows()` - Filtre SENT avec vérification conformité - -**Homogénéisation Documents envoyés (2026-03)** : Le notaire envoie de la même façon aux clients, tiers et notaires invités (emails différents selon le type). Affichage centralisé via `getSentDocumentsForTab()` : clients=DocumentsNotary, tiers=Documents(third_party_depositor), invités=Documents(shared_to_office). Envoi unifié via POST /notary/documents_notary/send (un appel par destinataire). - -**Modifications techniques** : - -- Suppression du compteur de progression dans `DocumentTablesHeader` -- Suppression des nombres entre parenthèses dans les titres des sections -- Ajout de cases à cocher pour la sélection multiple dans les tableaux "Documents validés" et "Documents envoyés" -- **Homogénéisation récupération données** : `useDocumentTablesData` orchestre Documents et DocumentsNotary avec paramètres centralisés par onglet -- **Centralisation onglets (2026-03)** : `useDocumentRowsData` extrait dans `hooks/useDocumentRowsData.ts` ; `filterDocumentsForTabMember` mutualise Asked/Refused ; `filterAndDeduplicateDocumentsByStatus` mutualise Validated (ClientView, InvitedView, ClientDashboard) ; `buildParticipantTab` factory pour participant tabs ; `filterOutDeletedDocuments` mutualise le filtrage des documents soft-deleted ; `hasDocumentWithDownloadableFile`/`filterDocumentsWithDownloadableFile` mutualise le filtrage documents avec fichier ; `deduplicateDocuments` déplacé vers module partagé ; `getDocumentNameFromFirstFile` mutualise l'extraction du nom ; `buildValidatedDocumentRow` factorise les 3 contextes (ClientView, InvitedView, ClientDashboard) ; renommage `documentFilterTabMemberHelpers` et `documentFilterToSendHelpers` pour clarifier les deux fichiers documentFilterHelpers -- **Sélections indépendantes** : `selectedValidatedDocuments` et `selectedSentDocuments` pour éviter que la sélection dans une section n'affecte l'autre -- Intégration de la vérification de conformité dans les hooks de génération de lignes (uniquement pour les documents avec fichiers) -- Support des documents `DocumentNotary` avec statut DOWNLOADED dans "Documents validés" -- **Colonnes conditionnelles** : Pas de colonne "Nom du document" pour "Documents demandés" (documents ASKED sans fichiers) -- **Cases à cocher conditionnelles** : Pas de case à cocher pour les documents ASKED et DEPOSITED dans "Documents demandés" - -**Fichiers créés** : - -- `ClientView/DocumentTables/helpers/documentConformity.ts` : Fonctions de vérification de conformité - -**Fichiers modifiés** : - -- `ClientView/DocumentTables/index.tsx` : Réorganisation des sections, ajout des boutons pour documents envoyés, sélections indépendantes -- `ClientView/DocumentTables/useDocumentTablesController.ts` : Ajout de deux sélections séparées (`selectedValidatedDocuments`, `selectedSentDocuments`) -- `ClientView/DocumentTables/hooks/useDocumentRows.tsx` : Refonte complète des hooks pour les nouveaux tableaux avec colonnes conditionnelles -- `ClientView/DocumentTables/hooks/useDocumentRowsData.ts` : Orchestration des lignes (Asked, Validated, Sent, Refused) et calcul progression -- `ClientView/DocumentTables/helpers/documentFilterHelpers.ts` : `filterDocumentsForTabMember` pour Asked/Refused -- `ClientView/DocumentTables/helpers/documentFilterStatusHelpers.ts` : `filterAndDeduplicateDocumentsByStatus` pour Validated -- `ClientView/DocumentTables/helpers/documentFilterDeletionHelpers.ts` : `filterOutDeletedDocuments` pour documents soft-deleted (documentsDataLoader, receivedDocumentsLoaderHelpers) -- `ClientView/DocumentTables/helpers/documentFilterFilesHelpers.ts` : `hasDocumentWithDownloadableFile`, `filterDocumentsWithDownloadableFile` -- `ClientView/DocumentTables/helpers/documentDeduplicationHelpers.ts` : `deduplicateDocuments` -- `ClientView/DocumentTables/helpers/documentFilterTabMemberHelpers.ts` : `filterDocumentsForTabMember`, `filterDocumentByActiveMember` (ex-documentFilterHelpers) -- `ClientDashboard/helpers/documentFilterToSendHelpers.ts` : `filterDocumentsToSend`, constantes statuts (ex-documentFilterHelpers) -- `ClientView/helpers/participantTabsHelpers.tsx` : `buildParticipantTab` factory -- `ClientView/DocumentTables/helpers/tableHeaders.ts` : Mise à jour des en-têtes de tableaux -- `ClientView/DocumentTables/components/DocumentTablesHeader.tsx` : Suppression du compteur -- `ClientView/DocumentTables/components/ValidatedDocumentsSection.tsx` : Suppression du nombre dans le titre - ---- - -## 8. Consolidation évolutions et correctifs - -Cette section consolide les éléments issus des documents de travail intégrés dans la documentation frontend pérenne. - -### Upload, contexte utilisateur et tables - -- Le module d'upload “Ajouter un document” est unifié entre documents à envoyer et documents supplémentaires avec sélection locale avant envoi explicite. -- Helpers clés : `Utils/documentUploadByRoleHelper.ts`, `Utils/userContextResolver.ts`, `Hooks/useResolvedUserContext.ts`, `DesignSystem/DepositOtherDocument`. -- Le routage des uploads par rôle est centralisé (`customer`, `third_party`, `invited_notary`) et les contrats de dépôt sont typés. -- La résolution de contexte utilisateur (dashboard, vues document, sélection de dossier) est mutualisée via des helpers/hook dédiés. -- Les wrappers legacy ont été alignés sur des composants partagés pour réduire les branches spécifiques par rôle. -- Les textes et colonnes des tableaux de documents reçus sont construits via des builders mutualisés. -- L'identité des onglets membres (`customer`/`third_party`/`shared notary`) est unifiée avec helpers dédiés pour navigation et sélection. - -### Téléchargements et nommage - -- Le téléchargement des documents demandés côté notaire est aligné sur un flux PDF unique (et non ZIP par défaut). -- Le dédoublonnage des noms de fichiers est renforcé (normalisation Unicode + normalisation extension `.pdf`) pour stabiliser la décision ZIP/PDF. -- Le nommage ZIP multi-téléchargement suit le format dossier/tableau (`..(validé).zip`). -- Les fichiers validés téléchargés (tableau Documents validés, page visualisation, côté tiers/clients/notaire invité) ont le suffixe ` (validé)` avant l’extension (ex. `document (validé).pdf`). S’applique aux statuts VALIDATED, SENT, DOWNLOADED. - -### Correctifs de fiabilité frontend - -- Migration des signatures JSX vers les conventions React 19 (`React.JSX.Element`, `React.ReactNode`) et suppression de la dépendance au shim global JSX. -- Nettoyage des `console.log` oubliés au profit de `LoggerService`. -- Suppression du code mort ID360 (routes, composants, API front inutilisés). - -### Upload : fusion systématique et page de métadonnées - -Tous les uploads (1 ou plusieurs fichiers) sont fusionnés et incluent la page de métadonnées (hash des fichiers originaux) dans le PDF filigrané. FileUploadHelper, ThirdPartyUploadService, GuestNotaryDocumentService, RIB, POST /notary/documents_notary : toujours fusion via FileMergeService. Module Deposit unifié : DepositContent partagé, configs par scope (documents, rib, sendDocuments). - -### Retour sur l'onglet membre après modification - -Après validation ou annulation lors de la modification des informations d'un membre (client, tiers, notaire invité), le backwardPath inclut les paramètres d'onglet (customerUid, thirdPartyUid, shareUid) pour que useParticipantTabs sélectionne le bon onglet. Fichiers : participantTabNavigationHelpers.appendQueryParam, useUpdateClientController, useUpdateSharedNotaryController, updateThirdPartyFormHelpers. - -### Restriction téléchargement/visualisation tableaux notaires - -Dans les onglets clients/tiers/notaires invités du dossier (vue notaire propriétaire) : documents DEPOSITED (À VALIDER) affichent l'icône œil pour que le notaire puisse visualiser et valider ; documents REFUSED n'affichent pas d'icône œil. Documents VALIDATED et SENT conservent téléchargement et visualisation. Page ViewDocuments : showDownload limité aux documents VALIDATED, SENT, DOWNLOADED. Espaces clients, tiers, notaires invités non impactés. - -### Centralisation aperçu fichiers (file preview) - -Helpers centralisés dans `@Front/Utils/filePreviewHelpers.ts` : FilePreviewState, createFilePreviewState, revokeFileUrl, loadFilePreviewDocumentNotary, loadFilePreviewDocument. ViewDocumentsNotary et ViewDocumentSent utilisent ces helpers. Variables SCSS : $document-preview-min-height, $document-file-container-min-height dans @Themes/constants.scss. - -## 8. Opérations Batch Frontend - -### Envoi Batch Documents - -**Frontend** : - -- **Page** : `/folders/[folderUid]/send-documents` -- **Composant** : `SendDocuments` -- **Fonctionnalités** : Sélection multiple + progression - -### Filtres Avancés - -**Frontend** : - -- **Page** : `/folders/select` (sélection avec filtres) -- **Autocomplétion** : Recherche via appels API (IdNot/Annuaire) et dans `third_parties` - -### Certificat Agrégé Documents - -**Frontend** : - -- Bouton "Télécharger Certificat" dans les tableaux de documents -- Disponible pour les documents validés et les documents envoyés -- Vérification que tous les documents sélectionnés sont ancrés - -**ZIP** : - -- Les ZIP téléchargés incluent automatiquement le certificat agrégé si des documents ancrés sont présents -- Support séparé pour Documents et DocumentsNotary (deux certificats si nécessaire) - ---- - -## 9. Envoi de Documents par Notaires Invités - -**Statut** : ✅ 100% | **Version** : 2.0.1 - -### Vue d'Ensemble - -Les modifications permettent aux notaires invités de créer des documents avec `shared_to_office_uid` pointant vers l'office du notaire propriétaire (au lieu de leur propre office). Ces documents ont le statut `DEPOSITED` et sont visibles par le notaire propriétaire. - -### ClientView - DocumentTables (Notaire propriétaire) - -**Impact** : ✅ Géré - -- Les documents créés par les notaires invités avec `shared_to_office_uid` pointant vers l'office du notaire propriétaire apparaissent maintenant dans les onglets des membres du dossier -- Modification dans `useDocumentTablesController.ts` : ajout de la condition `shared_to_office: { uid: folderOfficeUid }` dans la clause OR - -### InvitedView (Notaire invité) - -**Impact** : ✅ Géré - -- Nouvelle section "Documents à envoyer" pour les documents ASKED -- Nouvelle section "Documents supplémentaires" pour créer des documents "Autres documents" -- Les documents créés apparaissent dans la section appropriée - -### Points de Vigilance - -#### Distinction entre documents envoyés PAR et envoyés AU notaire invité - -Les documents avec `shared_to_office_uid` peuvent être : - -- **Envoyés PAR le notaire propriétaire AU notaire invité** : `shared_to_office_uid` = office invité, statut SENT, pas de depositor -- **Envoyés PAR le notaire invité AU notaire propriétaire** : `shared_to_office_uid` = office propriétaire, statut DEPOSITED, pas de depositor - -**Impact** : La logique de filtrage doit distinguer ces deux cas selon le contexte (InvitedView vs ClientView) - -#### Documents "Autres documents" créés par les notaires invités - -Les documents créés via "Documents supplémentaires" ont : - -- Type : "Autres documents" -- Statut : DEPOSITED -- `shared_to_office_uid` : office du notaire propriétaire - -**Impact** : Ces documents doivent être visibles par le notaire propriétaire dans les onglets des membres, mais pas dans les onglets des notaires invités - -### Emails et Notifications - -**Comportement** : ✅ Pas d'impact (comportement attendu) - -- `sendDocumentEmails()` ne gère que les statuts ASKED, REFUSED (VALIDATED désactivé) -- **Documents DEPOSITED** : Les documents créés par les notaires invités pour le notaire propriétaire ont le statut DEPOSITED et **ne génèrent pas d'email** -- **Raison** : Le notaire gestionnaire du dossier verra le document dans son interface sans avoir besoin d'une notification par email - ---- - -## 10. Mutualisation des Espaces Client / Tiers / Dossiers Invités - -**Statut** : ✅ COMPLÉTÉ | **Version** : 2.0.0 - -### Vue d'Ensemble - -Réduction de la duplication de code entre les trois espaces utilisateurs tout en préservant leurs spécificités fonctionnelles. - -### Résultats - -- **5 composants/services/helpers génériques** créés -- **7 fichiers** refactorisés -- **~1190 lignes** de code dupliqué → **~980 lignes** mutualisées -- **~82% de réduction** du code dupliqué -- **0 régression** : tous les comportements préservés - -### Mutualisations Implémentées - -#### Phase 1 : Composants UI - -**1. GenericDocumentsToSendSection** - -- **Fichier** : `components/GenericDocumentsToSendSection.tsx` -- **Réduction** : ~290 lignes → ~15 lignes par wrapper -- **Utilisation** : ClientDashboard, InvitedView -- **Spécificités** : Exclusion "Document notaire", gestion refusedDocuments, styles personnalisés - -**2. GenericReceivedDocumentsPage** - -- **Fichier** : `components/GenericReceivedDocumentsPage.tsx` -- **Réduction** : ~300 lignes → ~60 lignes par wrapper -- **Utilisation** : ClientDashboard, InvitedView -- **Spécificités** : Colonnes configurables, tracking personnalisé, routes flexibles - -**2.b. ReceivedDocumentsNotification** - -- **Fichier** : `ClientDashboard/components/ReceivedDocumentsNotification.tsx` -- **Utilisation** : ClientDashboardHeader, InvitedViewContent -- **Spécificités** : même composant, seul le texte métier (votre notaire / confrère) change selon l'espace - -**2.c. Legacy ReceivedDocuments wrapper alignment** - -- **Fichier** : `ClientDashboard/ReceivedDocumentsSection.tsx` -- **Utilisation** : wrapper legacy aligné sur les helpers partagés du centre -- **Spécificités** : calcul des documents reçus/counters délégué à `ClientDashboard/helpers/receivedDocumentsCenterHelpers.ts` - -**2.d. Received labels builder** - -- **Fichiers** : - - `DesignSystem/helpers/commonUiI18n.ts` - - `DesignSystem/helpers/commonUiMessageBuilders.ts` -- **Utilisation** : `ClientDashboardHeader`, `InvitedViewContent`, `ReceivedDocumentsSection` -- **Spécificités** : textes "documents envoyés par ..." centralisés via builders selon le contexte (`notary` / `peerNotary`) - -**2.e. ReceivedDocumentsTextContext** - -- **Fichier** : `DesignSystem/helpers/commonUiMessageBuilders.ts` -- **Type** : `ReceivedDocumentsTextContext` -- **Spécificités** : - - contexte explicite (`sender`) pour la source métier, - - stratégie de grammaire (`pluralStrategy`) pour singular/plural (`strict`/`inclusive`), - - réutilisé pour notifications, titres de pages "Documents reçus" et noms de ZIP. - -**2.f. Table Presets Builder** - -- **Fichier** : `Layouts/helpers/tablePresetsBuilder.ts` -- **Utilisation** : - - Wrappers reçus: `ClientDashboard/ReceivedDocuments/index.tsx`, `InvitedView/ReceivedDocuments/index.tsx` - - Tables dossier: `DocumentTables/helpers/tableHeaders.ts`, `DocumentsReminderHistory/index.tsx` - - Sections non-dossier: `Layouts/DesignSystem/sections/TableSection.tsx`, `Layouts/SuperAdmin/SiteTexts/constants.ts` -- **Spécificités** : - - source unique des intitulés partagés `Nom`, `Type de document`, `Statut`, `Envoyé le`, `Action` - - presets de colonnes prêts à l’emploi : `buildReceivedDocumentsColumnTitles()`, `buildDefaultReceivedDocumentsColumns()`, `buildDetailedReceivedDocumentsColumns()`, `buildDocumentTypeStatusActionHeaders()`, `buildDocumentTypeStatusSentAtActionHeaders()`, `buildNameStatusSentAtActionHeaders()`, `buildDocumentTypeStatutHeaders()`, `buildStatusActionHeaders()`. - -#### Phase 2 : Services - -**3. DocumentDownloadService** - -- **Fichier** : `Services/DocumentDownloadService/DocumentDownloadService.ts` -- **Réduction** : ~400 lignes dupliquées → service centralisé (~350 lignes) -- **Utilisation** : ClientDashboard, InvitedView, tous les téléchargements -- **Fonctionnalités** : - - Téléchargement individuel et groupé - - Marquage comme "vu" selon contexte - - Gestion d'erreurs et toasts uniforme - - Support de tous les types de documents - - Dépôt multi-fichiers (ClientDashboard) : envoi batch unique par document pour client, tiers et notaire invité afin de déclencher la fusion backend en un PDF unique - - Dispatch d’upload unifié par rôle via `Utils/documentUploadByRoleHelper.ts` (réutilisé par ClientDashboard et DepositOtherDocument) - - Un dépôt multi-fichiers sur un document produit un unique PDF fusionné actif côté backend (les anciens fichiers actifs du document sont archivés) - - Les formulaires HTML génériques utilisent un helper d’extraction dédié `DesignSystem/Form/helpers/formValueExtractionHelpers.ts` (source unique pour la collecte des valeurs `FormData`) - - La couche legacy `BaseApiServiceRequests` est supprimée : le socle API transite désormais via `BaseApiServiceFactory` et ses helpers partagés - - Les query params complexes (`q`, objets sérialisés) sont centralisés via `Api/helpers/queryParamHelpers.ts` et réutilisés dans les builders d’endpoints dossiers notary/customer - - Les payloads fichiers/téléchargements (download multiple, aggregated certificate, send documents) sont centralisés via `Api/LeCoffreApi/helpers/fileRequestPayloadHelpers.ts` - - Les payloads JSON métier récurrents (hors fichiers) sont centralisés via `Api/LeCoffreApi/helpers/businessRequestPayloadHelpers.ts` et réutilisés par les modules Documents, Customers, FolderSharing et DocumentReminders - - L’harmonisation est étendue aux modules SuperAdmin (`SiteTexts`, `Health`, `Folders`) avec réutilisation des builders partagés `buildEmptyPayload`, `buildPayloadOrEmpty` et `buildStatusUpdatePayload` - - Les builders CRUD typés par domaine (`documents`, `folders`, `users`) sont appliqués aux factories `Admin/SuperAdmin` restantes (`DocumentsFactory`, `UsersFactory`) pour homogénéiser les payloads create/update - - Les builders CRUD typés sont étendus aux domaines `deeds`, `deedTypes`, `documentTypes`, `roles`, `customers` et appliqués uniformément dans les factories/helpers `Admin`, `Notary`, `SuperAdmin` - - Les domaines restants `officeRoles`, `stripe`, `files`, `notifications`, `health`, `liveVotes` sont aussi alignés via des builders dédiés dans `Api/LeCoffreApi/helpers/businessRequestPayloadHelpers.ts` (suppression des payloads inline restants dans leurs factories/helpers) - - Une convention transversale de suppression par identifiant est ajoutée via `buildDeletePayloadByUid(...)`, puis déclinée en wrappers métier (`buildLiveVotesDeletePayload(...)`) pour compléter la convention CRUD (`create/update/archive/restore/delete`) - - La vérification des helpers UI/API récemment touchés est exécutée (lint + typecheck), sans régression de signature détectée - -**3.b. Office RIB consultation hook** - -- **Fichier** : `ClientDashboard/hooks/useOfficeRibDownload.ts` -- **Utilisation** : `ClientDashboard/ContactBox`, `Folder/FolderInformation/InvitedView/InvitedViewContent` -- **Spécificités** : chargement et téléchargement RIB unifiés pour client, tiers et notaire invité - -#### Phase 3 : Helpers - -**4. documentFilterHelpers** - -- **Fichier** : `ClientDashboard/helpers/documentFilterHelpers.ts` -- **Constantes (source unique)** : - - `DOCUMENTS_TO_SEND_STATUSES` : ASKED, REFUSED, VALIDATED (« Documents à envoyer » ; DEPOSITED, SENT, DOWNLOADED exclus). - - `DOCUMENT_STATUSES_RECEIVED_FROM_NOTARY` : SENT, DOWNLOADED (table documents). - - `DOCUMENT_NOTARY_STATUSES_RECEIVED_FROM_NOTARY` : SENT, DOWNLOADED (table documents_notary). -- **Fonction exportée** : - - `statusUpper(s)` : normalise le statut en majuscules (comparaisons API). -- **Fonctions** : - - `filterDocumentsToSend` : Filtrage « Documents à envoyer » (utilise DOCUMENTS_TO_SEND_STATUSES). - - `filterValidThirdPartyDocuments` : Filtrage documents tiers valides par customerUid. - - `deduplicateDocuments` : Suppression doublons par uid. -- **Autres filtres (autres fichiers)** : `filterDocuments` dans `DocumentTables/helpers/documentTablesDataHelpers.ts` ; `filterReceivedDocuments` dans `InvitedView/ReceivedDocuments/helpers/receivedDocumentsFilterHelpers.ts` et `InvitedView/helpers/filterReceivedDocuments.ts`. -- **Visualisation documents envoyés (tiers)** : en « Documents reçus », le clic œil ouvre la page de visualisation du Document (table documents) pour les tiers ; route `ClientDashboard.ViewDocument` → `/client-dashboard/[folderUid]/document/[documentUid]` ; composant `ViewDocumentSent` (Documents.getByUid, Files.download, DocumentViewer, markAsViewed). Les clients restent sur ViewDocumentsNotary (DocumentNotary). - -**5. documentQueryBuilders** - -- **Fichier** : `ClientDashboard/helpers/documentQueryBuilders.ts` -- **Builders** : - - `buildCustomerDocumentsQuery` : Requêtes ClientDashboard (client et tiers) ; utilise `DOCUMENTS_TO_SEND_STATUSES` pour « Documents à envoyer ». - - `buildDocumentsInclude` : Construction clauses include (files, document_anchor, document_history, document_type, depositor/third_party_depositor, folder). - -**6. Validated documents shared wrapper** - -- **Fichier** : `ClientDashboard/components/ValidatedDocumentsListWrapper.tsx` -- **Utilisation** : `ClientDashboard/components/ValidatedDocumentsSection.tsx`, `Folder/FolderInformation/InvitedView/components/InvitedDocumentsSection.tsx` -- **Spécificités** : source unique de filtrage `VALIDATED` + normalisation `uid` avant rendu `ValidatedDocumentsListSection` - -**7. Received counters source unique** - -- **Fichier** : `ClientDashboard/helpers/receivedDocumentsCenterHelpers.ts` -- **Utilisation** : `ClientDashboard/ReceivedDocumentsSection.tsx` et `ClientDashboard/useClientDashboardDocuments.ts` -- **Spécificités** : total + unread calculés via le même helper pour client et tiers - -**8. Received ZIP naming builder** - -- **Fichiers** : - - `DesignSystem/helpers/commonUiMessageBuilders.ts` - - `Layouts/Folder/FolderInformation/ClientView/DocumentTables/helpers/zipDownloadHelpers.ts` - - `Services/DocumentDownloadService/DocumentDownloadService.ts` -- **Spécificités** : nommage ZIP centralisé et aligné au contexte (`notary` / `peerNotary`) au lieu de chaînes inline. - -**9. Extended contextual document labels** - -- **Fichier** : `DesignSystem/helpers/clientDashboardMessageBuilders.ts` -- **Builders** : - - `buildAdditionalDocumentsDescription(...)` - - `buildAskDocumentsPageTitle(...)` - - `buildReceivedDocumentsTransferLabel(...)` -- **Utilisation** : - - `ClientDashboard/AdditionalDocumentsSection.tsx` - - `InvitedView/components/InvitedAdditionalDocumentsSection.tsx` - - `Folder/AskDocuments/hooks/useAskDocumentsPermissions.ts` - - `DocumentTables/helpers/documentTablesDownloadHelpers.ts` - -**10. Design identique partie centrale (client / tiers / notaires invités)** - -- **Objectif** : Aligner la présentation et l’ordre des sections dans les espaces client, tiers et notaires invités. -- **Ordre unifié** : Documents envoyés par le notaire (encart) → RIB → Documents à envoyer (incl. refusés) → Documents validés → Documents supplémentaires (facultatif). -- **Fichiers modifiés** : - - `ClientDashboard/index.tsx` : ValidatedDocumentsSection affiché pour client et tiers (plus seulement tiers). - - `ClientDashboard/DocumentsToSendSection.tsx` : `excludeValidated={true}` pour client et tiers (documents validés dans section dédiée). - - `InvitedView/components/InvitedViewContent.tsx` : ordre des sections (ReceivedDocumentsNotification avant RIB). - - `InvitedView/classes.module.scss` : classes `.documents`, `.content`, `.rib-actions`, `.deposit-documents` alignées sur ClientDashboard. - - `InvitedView/components/InvitedDocumentsToSendSection.tsx` : `wrapperClassName`/`contentClassName` identiques à ClientDashboard. - - `InvitedView/components/InvitedAdditionalDocumentsSection.tsx` : `showWrapper={false}`, typo par défaut pour cohérence. -- **Documents validés** : même présentation (ValidatedDocumentsListSection) pour client, tiers et notaires invités. - -### Bénéfices Obtenus - -#### Maintenabilité - -- Logique centralisée : modifications futures plus faciles -- Moins de duplication : moins de risques d'incohérences -- Code plus lisible : structure claire et organisée - -#### Cohérence - -- Comportement uniforme entre les espaces -- Gestion d'erreurs standardisée -- Logging et toasts cohérents - -#### Flexibilité - -- Configuration via props/options -- Spécificités préservées par contexte -- Extensibilité facilitée - ---- - -## 📚 Références - -- [ARCHITECTURE.md](./ARCHITECTURE.md) - Architecture rôles et permissions -- [API.md](./API.md) - API Annuaire -- Évolutions espace client : voir section « documentFilterHelpers » ci-dessus (visualisation documents envoyés tiers). - ---- - -**Dernière mise à jour** : 2026-02-25 - ---- - -## 7. Interface Espace Notaire - Onglets et Tableaux - -### Refonte des onglets membres et tableaux documents - -**Date** : 2025-01-XX -**Version** : 2.0.1+ -**Page** : `/folders/[folderUid]` - Vue ClientView - -#### Vue d'Ensemble - -Refonte complète de l'interface des onglets des membres et des tableaux de documents dans l'espace notaire pour améliorer la clarté et la conformité des documents. - -#### Modifications des Onglets Membres - -**Composants modifiés** : - -- `ClientView/ThirdPartyBox/index.tsx` -- `ClientView/ClientBox/index.tsx` -- `ClientView/SharedNotaryBox/index.tsx` - -**Changements** : - -1. **ThirdPartyBox** : - - ❌ Suppression du bouton "Renvoyer l'invitation" - - ❌ Suppression du bouton "Supprimer" - - ✅ Bouton unique "Modifier la note" (remplace le menu) - -2. **ClientBox** : - - ✅ Texte du bouton de note changé en "Modifier la note" (au lieu de "Rajouter/modifier une note") - -3. **SharedNotaryBox** : - - ✅ Texte du bouton de note changé en "Modifier la note" - - ✅ Menu conservé avec option "Modifier les informations" (si disponible) - -#### Modifications des Tableaux de Documents - -**Composants modifiés** : - -- `ClientView/DocumentTables/index.tsx` -- `ClientView/DocumentTables/hooks/useDocumentRows.tsx` -- `ClientView/DocumentTables/helpers/tableHeaders.ts` -- `ClientView/DocumentTables/helpers/documentConformity.ts` -- `ClientView/DocumentTables/components/DocumentTablesHeader.tsx` -- `ClientView/DocumentTables/components/ValidatedDocumentsSection.tsx` - -**Nouvelle fonctionnalité : Vérification de conformité** - -Un nouveau helper `documentConformity.ts` vérifie la conformité des documents : - -- **Lien IPFS** : Présence de `watermarked_s3_key` ou `file_path` dans les fichiers -- **Ancrage** : Présence de `document_anchor` -- **Données d'ancrage** : Présence de `document_anchor.proof_data` - -Si l'un de ces éléments manque, le statut du document devient **"NON CONFORME"**. - -**⚠️ Exception importante : Documents demandés (ASKED)** - -Les documents avec le statut `ASKED` (demandés mais pas encore déposés) sont **toujours considérés comme conformes** car : - -- Ils n'ont pas encore de fichier déposé -- Ils ne peuvent donc pas avoir de filigrane, d'ancrage ou de métadonnées -- La vérification de conformité ne s'applique qu'aux documents déposés (`DEPOSITED`) - -La fonction `isDocumentConform()` retourne automatiquement `true` pour tous les documents en statut `ASKED`, garantissant qu'ils s'affichent toujours avec le statut "DEMANDE" et jamais "NON CONFORME". - -**Nouveaux tableaux** : - -1. **"Documents demandés"** (sans nombre entre parenthèses) : - - **Statuts** : DEMANDES, A VALIDER, NON CONFORME - - **Colonnes** : Type de document, Statut, Déposé le, Action (voir) - - **Fichier** : `useAskedDocumentsRows()` - Combine ASKED et DEPOSITED avec vérification conformité - - **Note** : Pas de colonne "Nom du document" car les documents ASKED n'ont pas encore de fichiers déposés - - **Note** : Pas de case à cocher pour les documents ASKED et DEPOSITED - - **Note** : Les documents `ASKED` ne sont jamais vérifiés pour la conformité et affichent toujours "DEMANDE" - -2. **"Documents validés"** (sans nombre entre parenthèses) : - - **Statuts** : VALIDE, TELECHARGE, NON CONFORME - - **Colonnes** : case à cocher, Nom du document, Type de document, Déposé le, Action (télécharger, voir) - - **Boutons** : Télécharger ZIP et Télécharger Certificat (selon les coches) - - **Fichier** : `useValidatedDocumentsRows()` - Combine VALIDATED et DOWNLOADED avec vérification conformité - -3. **"Documents envoyés"** (sans nombre entre parenthèses) : - - **Statuts** : ENVOYE, NON CONFORME - - **Colonnes** : case à cocher, Nom du document, Statut, Déposé le, Action (télécharger, supprimer) - - **Boutons** : Télécharger ZIP et Télécharger Certificat (selon les coches) - - **Fichier** : `useSentDocumentsRows()` - Filtre SENT avec vérification conformité - -**Homogénéisation Documents envoyés (2026-03)** : Le notaire envoie de la même façon aux clients, tiers et notaires invités (emails différents selon le type). Affichage centralisé via `getSentDocumentsForTab()` : clients=DocumentsNotary, tiers=Documents(third_party_depositor), invités=Documents(shared_to_office). Envoi unifié via POST /notary/documents_notary/send (un appel par destinataire). - -**Modifications techniques** : - -- Suppression du compteur de progression dans `DocumentTablesHeader` -- Suppression des nombres entre parenthèses dans les titres des sections -- Ajout de cases à cocher pour la sélection multiple dans tous les tableaux -- Intégration de la vérification de conformité dans tous les hooks de génération de lignes -- Support des documents `DocumentNotary` avec statut DOWNLOADED dans "Documents validés" - -**Fichiers créés** : - -- `ClientView/DocumentTables/helpers/documentConformity.ts` : Fonctions de vérification de conformité - -**Fichiers modifiés** : - -- `ClientView/DocumentTables/index.tsx` : Réorganisation des sections et ajout des boutons pour documents envoyés -- `ClientView/DocumentTables/hooks/useDocumentRows.tsx` : Refonte complète des hooks pour les nouveaux tableaux -- `ClientView/DocumentTables/helpers/tableHeaders.ts` : Mise à jour des en-têtes de tableaux -- `ClientView/DocumentTables/components/DocumentTablesHeader.tsx` : Suppression du compteur -- `ClientView/DocumentTables/components/ValidatedDocumentsSection.tsx` : Suppression du nombre dans le titre - -### Unification identité onglets membres (2026-02-26) - -**Page** : `/folders/[folderUid]` (vue notaire gestionnaire, `ClientView`) - -**Objectif** : unifier l’identité des onglets membres (`customer`, `third_party`, `shared_notary`) et centraliser la résolution d’identité pour la sélection d’onglet, la synchronisation URL et la validation des onglets. - -**Root cause** : l’onglet `shared_notary` utilisait selon les zones soit l’identité de partage (`sharing.uid`), soit l’UID d’office invité (`shared_to_office.uid`). Cette divergence pouvait créer des collisions d’identité d’onglets et des comportements non homogènes. - -**Correctifs** : - -- Centralisation de l’identité membre dans un helper dédié : - - `FolderInformation/ClientView/helpers/participantTabIdentityHelpers.ts` - - `getParticipantTabMemberUid(...)` pour l’identité de sélection/navigation. - - `getSharedNotaryOfficeUid(...)` pour le contexte de fetch des documents. - -- Centralisation de la construction des paramètres de navigation d’onglet : - - `FolderInformation/ClientView/helpers/participantTabNavigationHelpers.ts` - - `buildParticipantTabNavigationParams(...)` pour construire `tabNavigationParams`. - - `buildParticipantTabQueryPatch(...)` pour la mise à jour `router.query` sans logique locale dupliquée. - - `buildParticipantTabLogKey(...)` pour la construction unique des clés de logs par onglet (`client-*`, `thirdparty-*`, `shared-*`). - - `normalizeParticipantTabNavigationParams(...)` pour produire un objet stable mutualisé (plus de normalizer local dans les hooks de data loading). - - s’appuie sur `Utils/participantTabNavigationHelpers.ts` pour mutualiser extraction query, patch query, sérialisation URL et log key. - -- Alignement de la sélection et du query param `shareUid` : - - `FolderInformation/ClientView/helpers/participantTabsSelectionHelpers.ts` - - `shareUid` est désormais écrit avec l’identité membre (`sharing.uid`/`id`). - - compatibilité maintenue pour anciens liens `shareUid=` en lecture. - - sur les futurs flux de navigation d’onglets membres, utiliser systématiquement `extractParticipantTabNavigationParamsFromQuery(...)` + `appendParticipantTabQueryToPath(...)` (interdiction de mapping local `customerUid/thirdPartyUid/shareUid`). - - flux migrés : `ClientDashboard/ViewDocumentsNotary/helpers/navigationHelpers.ts`, `Folder/ViewDocuments/helpers/viewDocumentsBackUrlHelpers.ts`, `Folder/AskDocuments/hooks/helpers/askDocumentsNavigationHelpers.ts`. - -- Alignement de la validation d’onglets : - - `FolderInformation/ClientView/helpers/participantTabsValidationHelpers.tsx` - - détection UID manquant/dupliqué basée sur le helper d’identité central. - -- Alignement du rendu des tableaux documents : - - `FolderInformation/ClientView/components/ParticipantDocumentTables.tsx` - - clé de rendu shared notary basée sur l’identité membre. - - `tabNavigationParams.shareUid` basé sur l’identité membre. - - contexte de fetch conservé sur l’UID d’office invité (`shared_to_office.uid`). - - les modules `DocumentTables/*` réutilisent désormais le type partagé `ParticipantTabNavigationParams` au lieu de signatures inline. - -### Retour sur l'onglet membre après modification (2026-02-27) - -**Objectif** : Après validation ou annulation lors de la modification des informations d'un membre (client, tiers, notaire invité), revenir sur l'onglet du membre concerné. - -**Correctifs** : - -- `backwardPath` des pages d'édition (UpdateClient, UpdateThirdParty, UpdateSharedNotary) inclut désormais les paramètres de requête d'onglet (`customerUid`, `thirdPartyUid`, `shareUid`) via `appendParticipantTabQueryToPath`. -- Ajout de `appendQueryParam(path, key, value)` dans `Utils/participantTabNavigationHelpers.ts` pour gérer correctement l'ajout de `refresh` lorsque l'URL contient déjà des paramètres. -- Documentation : section « Retour sur l'onglet membre après modification » dans ce document. - ---- - -### Correctifs flux invité notaire et ZIP validés (2026-03-02) - -**Contexte invité notaire (téléchargement document validé)** : - -- Le flux invité notaire pouvait appeler l'API notaire (`/v1/notary/files/download`) au lieu du chemin client/guest, ce qui provoquait un 401/403 et une déconnexion côté front. -- Le paramétrage est aligné pour traiter l'invité notaire comme un flux client/tiers sur le téléchargement de document validé : - - `InvitedView/documentTableHelpers.tsx` : `downloadValidatedDocument(..., { isCustomerOrThirdParty: true })` - - `InvitedView/InvitedDocumentsSection.tsx` : `ValidatedDocumentsListWrapper` avec `isCustomerOrThirdParty={true}` - - `ClientView/DocumentTables/hooks/useValidatedDocumentsRows.ts` : `resolvedContext.isInvitedNotary` inclus dans `isCustomerOrThirdParty` - -**Contexte ZIP documents validés** : - -- Le suffixe ` (validé)` pouvait être dupliqué dans certains noms de fichiers ZIP (ex: `... (validé).aplc (validé).pdf`). -- La normalisation du suffixe est centralisée et mutualisée : - - réutilisation de `addValidatedSuffixToFileName()` depuis `src/front/Utils/zipValidatedSuffixHelper.ts` - - suppression de la logique locale redondante dans `src/front/Utils/zipDownloadHelpers/zipStructureHelpers.ts` -- Règle unique : ne pas réappliquer un suffixe déjà normalisé (`... (validé).aplc.pdf`). - ---- diff --git a/projects/lecoffreio/docs/OPERATIONS.md b/projects/lecoffreio/docs/OPERATIONS.md deleted file mode 100644 index 1a35822..0000000 --- a/projects/lecoffreio/docs/OPERATIONS.md +++ /dev/null @@ -1,1354 +0,0 @@ -# Opérations et Maintenance - -## Correctifs et dépannage documentés - -### Ancrage notarié (prod) : échec déchiffrement ou filigrane PDF (catalog.Pages, Invalid object ref) - -**Problème :** En prod, les logs remontent des erreurs lors de l’ancrage de documents notariés : -- `Unsupported state or unable to authenticate data` (Decipheriv.final) puis `Fichier corrompu ou modifié (vérification d'intégrité échouée)` ; -- `[WatermarkService] Erreur lors de l'ajout du filigrane: _this.catalog.Pages is not a function` ; -- `Trying to parse invalid object`, `Invalid object ref: 74 0 R`, `Buffer commence par %PDF mais n'est pas un PDF valide`. - -**Cause :** Deux cas distincts (éventuellement sur deux documents ou deux fichiers différents) : -1. **Échec déchiffrement** : La clé d’office ou la clé globale ne permet pas de déchiffrer le fichier (GCM auth tag invalide). Causes possibles : fichier chiffré avec une autre clé (migration, rotation), payload corrompu ou tronqué, clé d’office incorrecte en BDD. -2. **PDF invalide après déchiffrement** : Le buffer déchiffré commence par `%PDF` mais la structure du PDF est invalide ou non supportée par pdf-lib (référence d’objet cassée, catalog/Pages absent). Causes possibles : PDF corrompu, généré par un outil exotique, ou contenu déchiffré avec une mauvaise clé (garbage qui commence par %PDF). - -**Root cause :** Pour (1) : mauvaise association clé/fichier ou donnée chiffrée corrompue. Pour (2) : format PDF non standard ou contenu réellement corrompu après un déchiffrement réussi. - -**Correction :** -- Enrichissement des erreurs avec l’UID du fichier (`File : ...`) dans `FilesNotaryService` (déchiffrement et fallback clé globale) pour faciliter le diagnostic en logs. -- Message d’erreur explicite lors de l’échec du filigrane si la structure PDF est invalide (catalog.Pages, Invalid object ref) : indication « PDF invalide ou structure non supportée ; possible fichier corrompu ou déchiffré avec mauvaise clé ». -- Dans `DocumentAnchoringWatermarkHelper`, encapsulation de l’erreur de téléchargement/déchiffrement avec le `fileUid` dans le message (`Failed to download or decrypt file : ...`). - -**Modifications :** -- `lecoffre-back-main/src/services/common/FilesNotaryService/FilesNotaryService.ts` : en cas d’échec déchiffrement (direct ou après fallback), lever une erreur avec préfixe `File ${uid}:`. -- `lecoffre-back-main/src/services/common/WatermarkService/helpers/WatermarkPdfProcessorHelper.ts` : dans le catch de `addWatermark`, si le message contient `catalog`, `Pages is not a function` ou `Invalid object ref`, ajouter un hint dans le message et le log. -- `lecoffre-back-main/src/services/common/WatermarkService/helpers/WatermarkBufferProcessorHelper.ts` : dans `validatePdfBuffer`, même hint lorsque `PDFDocument.load` échoue avec une de ces chaînes. -- `lecoffre-back-main/src/services/notary/DocumentAnchorsService/helpers/DocumentAnchoringWatermarkHelper.ts` : `.catch` sur `filesService.download(fileUid)` pour renvoyer une erreur incluant `fileUid`. - -**Impact :** Les logs en prod contiennent l’UID du fichier en erreur et un message plus explicite pour les PDF invalides. Comportement fonctionnel inchangé : l’ancrage échoue toujours pour ces cas ; le diagnostic est amélioré. - -**Modalités de déploiement :** Déploiement standard backend (build-and-deploy). - -**Modalités d’analyse :** Consulter les logs backend (notary, ancrage) ; en cas d’erreur d’ancrage, le message doit contenir `File :` ou `Failed to download or decrypt file :` et, pour les PDF invalides, le hint sur structure non supportée / mauvaise clé. Vérifier en BDD que la clé d’office du document (`office_encryption_key`) et la clé du fichier (`files_notary.key`) sont cohérentes pour le fichier concerné. - -### Email de préférence pour les envois non renseigné à l’ajout d’un confrère (annuaire) - -**Problème :** Lors de l’ajout d’un confrère depuis l’annuaire (recherche notaire), le champ « Email de préférence pour les envois » restait vide au lieu d’être pré-rempli avec l’email du notaire sélectionné. - -**Cause :** La recherche utilise l’API Annuaire V2 : d’abord l’endpoint PB lookup (`/api/v2/directory/lookup`), puis en fallback l’endpoint PP personnes (`/api/pp/v2/personnes`). Les résultats issus du lookup sont construits à partir de `personnes` et de `rattachements`. Pour les entrées construites uniquement à partir de `personnes`, la fonction `buildResultFromPersonne` fixait l’email à une chaîne vide et ne lisait pas d’éventuels champs `email`/`mail` renvoyés par l’API. De plus, lorsque le lookup renvoyait des résultats, l’appel PP (qui fournit les emails via `mailRattachement`/`email`/`mail`) n’était jamais effectué, donc aucun enrichissement n’était possible pour ces cas. - -**Root cause :** (1) Non-utilisation des champs email/mail sur l’objet personne dans la réponse lookup. (2) Absence d’enrichissement des résultats lookup par un appel PP pour compléter les emails manquants. - -**Correction :** -- Dans `buildResultFromPersonne`, extraction de l’email depuis l’objet personne via `pickFirst(..., "email", "mail", "courriel")` au lieu d’une chaîne vide ; types `AnnuaireLookupPerson` et paramètre de `buildResultFromPersonne` étendus avec `mail?` et `email?`. -- Après le PB lookup, si des résultats existent, appel d’une nouvelle méthode `enrichLookupResultsWithPpEmails` : un appel unique à l’endpoint PP (première page) avec la même requête (prenom, nomUsuel), construction d’une map idNot → email, puis mise à jour des résultats du lookup qui ont un idNot mais un email vide. - -**Modifications :** -- `lecoffre-back-main/src/services/common/IdNotDirectoryService/helpers/IdNotDirectoryApiSearchHelper.ts` : types `AnnuaireLookupPerson` et paramètre de `buildResultFromPersonne` avec `mail?`, `email?` ; `buildResultFromPersonne` utilise `pickFirst` pour l’email et l’inclut dans le `displayName` de fallback ; nouvelle méthode privée `enrichLookupResultsWithPpEmails` ; après le lookup réussi, appel à cette méthode avant de retourner. - -**Impact :** Lorsqu’un confrère est choisi dans l’annuaire, l’email de préférence pour les envois est pré-rempli lorsque l’API (lookup ou PP) fournit un email. Comportement inchangé pour les résultats déjà fournis par les rattachements ou par le fallback PP seul. - -**Modalités de déploiement :** Déploiement standard backend (build-and-deploy). - -**Modalités d’analyse :** Depuis l’interface d’ajout de confrères à un dossier, effectuer une recherche par nom de notaire, sélectionner un résultat : le champ « Email de préférence pour les envois » doit être renseigné lorsque l’annuaire retourne un email pour ce notaire. Vérifier en logs backend les messages d’enrichissement (`Enrichissement email lookup par PP`) si des résultats lookup sont complétés par PP. - -### Erreur en mode « Dossiers invités » sur la page /folders/shared (prod) - -**Contexte lint backend (2026-03-12) :** Après corrections fix-lint (variables inutilisées, non-null assertions, extraction `scheduleV1ToV2SyncIfEnabled`), il reste environ 334 erreurs ESLint (max-lines, max-params, complexity). Plan : refactor par lots ; pas de désactivation de règles. Détail dans cette section si besoin. - -**Problème :** Un notaire invité (ex. gwendal@lecoffre.io) en mode « Dossiers invités » obtient une erreur en accédant à la page des dossiers partagés (réponse à une invitation d'un office, ex. DIADEYS). La page affiche « Impossible de charger les dossiers partagés » ou un écran d'erreur. - -**Cause :** La page `/folders/shared` (`shared.tsx`) charge la liste via `GET /notary/folders/shared`. Le backend rejette cette requête lorsque l’office actif est `GUEST_FOLDERS` (400 : « Active office is required to list shared folders »). Pour un notaire invité, l’office actif est justement `GUEST_FOLDERS`. - -**Root cause :** `GET /notary/folders/shared` est conçu pour lister les dossiers partagés **avec un office réel**. En mode invité, la liste des dossiers accessibles doit provenir de `GET /notary/folders` (filtré par email/idNot via le JWT), pas de `GET /notary/folders/shared`. - -**Correction :** Dans `lecoffre-front-main/src/pages/folders/shared.tsx`, lorsque l’utilisateur est en `GUEST_FOLDERS` (permissionContext ou activeOfficeStore), ne plus appeler `getSharedFolders()`. Afficher `DefaultNotaryDashboard` sans fournir de liste externe : le tableau de bord charge alors la liste via `GET /notary/folders` (même flux que la page principale des dossiers), ce qui fonctionne pour les notaires invités. - -**Modifications :** -- Fichier : `lecoffre-front-main/src/pages/folders/shared.tsx` - - Import de `useRolePermissions` et `activeOfficeStore`. - - Détection de `GUEST_FOLDERS` via `permissionContext?.activeOfficeUid` ou `activeOfficeStore.activeOffice?.office_uid`. - - Si `GUEST_FOLDERS` : rendu de `DefaultNotaryDashboard` sans prop `folders` (chargement interne via GET /notary/folders). - - Sinon : comportement inchangé (chargement via `getSharedFolders()`, mapping en `OfficeFolderExtended`). - -**Impact :** Les notaires invités peuvent accéder à la page « Dossiers partagés » et voir la liste de leurs dossiers invités sans erreur. Aucun changement pour les offices réels. - -**Modalités de déploiement :** Déploiement standard frontend (build-and-deploy). - -**Modalités d'analyse :** Se connecter avec un compte notaire invité (ou un compte ayant des dossiers partagés et sélectionner « Dossiers invités »), ouvrir la page /folders/shared : la liste des dossiers doit s’afficher sans erreur. - -### Évolution – Personne en charge du dossier (main_contact_uid) – 2026-03-12 - -**Objectif :** Afficher dans l’espace client/tiers la personne en charge du dossier (email, téléphone) et permettre de la désigner/modifier dans l’écran « Modifier les collaborateurs ». - -**Modifications :** Nouveau champ `main_contact_uid` sur `office_folders` (nullable, FK `users`). Création dossier : `main_contact_uid` = créateur. PUT dossier : accepte `main_contact_uid` (validé parmi les stakeholders). GET dossier : retourne `main_contact`. Front : ContactBox utilise main_contact puis created_by puis notaire puis premier stakeholder ; écran collaborateurs : liste « Personne en charge », envoi de `main_contact_uid` à l’enregistrement. - -**Modalités de déploiement :** Appliquer la migration Prisma `20260312120000_add_main_contact_uid_to_office_folders`. Puis déploiement standard backend + frontend. - -**Modalités d’analyse :** En espace client/tiers, vérifier que le contact affiché correspond à la personne en charge si définie. Sur « Modifier les collaborateurs », choisir une personne en charge, enregistrer, vérifier en espace client que le contact affiché est bien celui choisi. - -### Migration V1 → V2 : Phase 4 (réancrage) non bloquante - -**Problème :** -Lors de l'import V1 → V2, la Phase 4 (réancrage des documents et RIB) pouvait faire échouer toute la migration (exit 1) lorsque des documents V1 étaient invalides (fichier non-PDF, corrompu, format tableur, PDF chiffré). La Phase 5 (vérification du statut) n'était alors jamais exécutée et le log restait sans clôture explicite. - -**Cause :** -Le script `import-v1.sh` utilise `set -e`. Dès que `ssh_run` (Phase 4) renvoyait le code de sortie non nul de `reanchor-all.sh`, le script quittait immédiatement sans exécuter `phase4_rc=$?` ni la Phase 5. - -**Cause additionnelle (backend) :** -Le script backend `reanchor-all-complete.ts` utilisait `files.key IS NOT NULL` comme critère V1 (et `IS NULL` comme critère V2). Or des fichiers V2 peuvent avoir un `key` non nul (compatibilité API), ce qui élargissait la suppression/réancrage au-delà du périmètre V1. - -**Correction :** -En cas d'échec de la Phase 4, le script affiche un avertissement et continue vers la Phase 5 au lieu de quitter. Les erreurs sur documents individuels (PDF invalide, format non supporté) restent loggées dans le fichier de log complet (`./logs/import-v1--.log`). - -Côté backend, le périmètre V1 est désormais détecté via le format de clé V1 (UUID). Le traitement Phase 4 est idempotent pour les documents : aucune ancre existante n'est supprimée et un document déjà présent avec une entrée d'ancre en base (même incomplète) n'est pas retenté. - -**Modifications :** - -- Fichier : `deploy/scripts_v2/import-v1.sh` - - Désactivation temporaire de `set -e` autour de l'appel Phase 4 : `set +e` avant `ssh_run`, `set -e` après capture de `phase4_rc=$?`, afin que les échecs partiels (documents ignorés/skip) ne fassent pas quitter le script. - - Capture du code de sortie de `ssh_run` pour la Phase 4 (`phase4_rc=$?`). - - Si `phase4_rc -ne 0` : `warning "Phase 4 a rencontré des erreurs (documents invalides ou réancrage partiel), continuation vers Phase 5..."` sans `exit 1`. - - Si `phase4_rc -eq 0` : `success "Phase 4 terminée avec succès"` comme avant. - -**Impact :** -La migration complète (phases 0 à 5) se termine même lorsque des documents ne peuvent pas être réancrés. Le log complet et la Phase 5 permettent d'identifier les fichiers non migrés ou en erreur. - -**Modalités de déploiement :** -Aucune (script d'import uniquement). Déploiement standard si le script est versionné. - -**Modalités d'analyse :** -Relancer une migration test ; en cas d'erreurs Phase 4, vérifier que le message d'avertissement s'affiche et que la Phase 5 est bien exécutée. Consulter `./logs/import-v1--.log` pour le détail des erreurs document par document. - -Depuis le backend, les erreurs de conversion PDF affichent aussi une `signatureHex` et un type détecté (ex: zip/7z/png/jpeg/gzip) pour distinguer un faux PDF (mimetype incorrect) d'un contenu corrompu/déchiffré avec une mauvaise clé (cas V1). - -**Détails (backend) :** -Backend : analyseDecryptionError, périmètre V1 par format de clé UUID, skip documents invalides. - - -### Phase 5 (vérification migration) : erreur « backend.env ligne 7 : 7 : Aucun fichier ou dossier » - -**Problème :** En prod, le déploiement échouait en Phase 5 (vérification du statut de migration) avec code de sortie 1 et le message : `/etc/lecoffreio/prod.lecoffreio.4nkweb.com/backend.env: ligne 7: 7: Aucun fichier ou dossier de ce type`. La Phase 5 n'était pas exécutée. - -**Cause :** Le script `check-v1-migration-status.sh` est exécuté sur le serveur (via SSH) et chargeait `backend.env` avec `source "$BACKEND_ENV_FILE"`. Toute ligne du fichier qui n'est pas une assignation valide (ex. une ligne contenant uniquement « 7 » ou une valeur mal formée) est alors interprétée par le shell comme une commande ; le shell tente d'exécuter « 7 » comme nom de commande ou de fichier, d'où l'erreur. - -**Cause racine :** Le fichier `backend.env` sur l'hôte prod contenait à la ligne 7 une entrée invalide (valeur seule, numéro, ou ligne exécutable). Les fichiers `.env` de prod ne sont pas modifiables depuis le dépôt. - -**Correction :** Dans `deploy/scripts_v2/check-v1-migration-status.sh`, remplacer `source "$BACKEND_ENV_FILE"` par un chargement qui n'exporte que les lignes au format `VAR=value` (regex `^[A-Za-z_][A-Za-z0-9_]*=`), en ignorant les commentaires et les lignes vides ou non assignation. Ainsi, une ligne 7 invalide dans `backend.env` n'est plus exécutée et ne fait plus échouer la Phase 5. Par ailleurs, la Phase 5 utilise uniquement `APP_ROOT/.secrets/${ENV}/.env.${ENV}.connectDB` (source de vérité). Après migration, backend.env ne doit plus exister (sauvegarde puis suppression) ; la seule source est connectDB. - -**Modifications :** - -- Fichier : `deploy/scripts_v2/check-v1-migration-status.sh` - - Remplacement de `source "$BACKEND_ENV_FILE"` par une boucle `while IFS= read -r line` qui ne traite que les lignes correspondant à une assignation valide et les exporte. - - Utilisation exclusive de `APP_ROOT/.secrets/${ENV}/.env.${ENV}.connectDB`. backend.env ne doit plus exister après migration (sauvegarde puis suppression). - -**Impact :** La Phase 5 s'exécute même si `backend.env` contient des lignes invalides. Aucune modification des fichiers `.env` prod depuis le dépôt. - -**Modalités de déploiement :** Déploiement standard (scripts_v2). Aucune action requise sur l'hôte prod pour les `.env`. - -**Modalités d'analyse :** Relancer le déploiement prod ; la Phase 5 doit se terminer sans erreur « ligne 7 ». Optionnel : sur l'hôte prod, corriger manuellement la ligne 7 de `backend.env` pour supprimer la valeur invalide. - - -### Dossier sans client : aucun membre affiché après ajout d'un tiers (notaire) - -**Problème :** En tant que notaire, lorsqu'on crée un dossier sans client puis qu'on ajoute un tiers, aucun membre n'apparaît dans le dossier (vue détail avec onglets Clients / Notaires invités / Tiers). - -**Cause :** Le critère « le dossier a des participants » (`doesFolderHaveParticipants`) ne prenait en compte que : présence d'au moins un client, ou d'un partage (notaire invité), ou de **documents déposés par des tiers**. Un dossier avec uniquement des tiers associés (sans document déposé encore) n'était pas considéré comme ayant des participants, donc la vue détail n'était pas rendue. - -**Cause racine :** Dans `useFolderData.ts`, `doesFolderHaveParticipants` utilisait `hasThirdPartyDocuments` (documents des tiers) et non la liste des **tiers associés au dossier** (`folder.third_parties`). L'API détail folder inclut déjà `third_parties: true` ; la donnée était disponible mais non utilisée pour ce calcul. - -**Correction :** Inclure la présence de tiers associés au dossier (`folder.third_parties`) dans le calcul de `doesFolderHaveParticipants`, en plus des clients, des folder_sharings et des documents des tiers. Ainsi un dossier sans client mais avec au moins un tiers affiche bien la vue détail et les onglets (dont « Tiers invités »). - -**Modifications :** - -- Fichier : `lecoffre-front-main/src/front/Components/Layouts/Folder/FolderInformation/hooks/useFolderData.ts` - - Calcul de `hasThirdParties` via `safeArrayLength(folderWithRelations?.third_parties) > 0`. - - Retour de `doesFolderHaveParticipants` étendu à `hasThirdParties ||` (en plus de hasCustomers, hasFolderSharings, hasThirdPartyDocuments). - - Typage du folder étendu pour inclure `third_parties` dans le cast (aligné sur `folder_sharings`). - -**Impact :** Les dossiers créés sans client puis complétés par l'ajout d'un ou plusieurs tiers affichent correctement la section participants (Clients / Notaires invités / Tiers invités). Aucun impact sur les dossiers avec client(s) ou avec documents tiers existants. - -**Modalités de déploiement :** Déploiement standard frontend (build-and-deploy). - -**Modalités d'analyse :** En environnement notaire : créer un dossier sans client, ajouter un tiers depuis « Gérer les acteurs » ou la page dédiée ; rouvrir le dossier et vérifier que la vue détail s'affiche avec au moins l'onglet « Tiers invités » et le tiers listé. - -**Complément (backend, 2026-03-11) :** En test et production, l’ajout seul d’un tiers ne faisait toujours pas apparaître les onglets membres. **Cause :** La réponse GET /notary/folders/:uid est construite via `instanceToPlain` (class-transformer) puis `forceRelationsAndDeedIntoPlain` ; `third_parties` n’était pas inclus dans les relations forcées, donc absente de la réponse JSON. **Correction :** Dans `OfficeFoldersGetOneByUidHydrationHelper.ts`, ajout de `forceRelationIntoPlain` pour `third_parties` (comme pour `folder_sharings`), afin que la réponse contienne toujours `third_parties` lorsque l’include le demande. Après déploiement backend, les onglets membres s’affichent pour un dossier avec uniquement des tiers. - - -### Recherche confrères : peu de résultats malgré noms/prénoms dans l'annuaire - -**Problème :** À la saisie dans l’ajout d’un confrère, le filtre ne remonte que quelques comptes alors que les notaires ont bien leurs noms et prénoms dans l’annuaire / IdNot. - -**Cause :** Lorsque l’API Annuaire V2 n’est pas configurée (`ANNUAIRE_API_BASE_URL` / `IDNOT_API_KEY` absents de `system_configuration`), le backend utilise le **legacy IdNot**. Le fallback après `GET /persons?q=...` appelait `GET .../api/pp/v2/personnes?page=1&size=100` **sans paramètre de recherche** : seules les 100 premières personnes de l’API étaient récupérées, puis filtrées en mémoire par la requête. La recherche ne portait donc que sur un sous-ensemble fixe, d’où très peu de résultats. - -**Root cause :** Dans `IdNotDirectoryApiSearchHelper.searchInLegacyIdNot`, le fallback vers `api/pp/v2/personnes` ne passait pas la requête (prenom / nomUsuel) à l’API. - -**Correction :** Passer la requête à l’API comme pour l’Annuaire V2 : découper la chaîne en `prenom` et `nomUsuel` et appeler `api/pp/v2/personnes?prenom=...&nomUsuel=...&page=0&size=100`. La recherche est ainsi effectuée côté API (ou au moins sur un jeu pertinent) au lieu des 100 premières lignes. - -**Modifications :** -- Fichier : `lecoffre-back-main/src/services/common/IdNotDirectoryService/helpers/IdNotDirectoryApiSearchHelper.ts` — fallback legacy : construction de l’URL avec `prenom` et `nomUsuel` dérivés de la requête, `page=0`, `size=100`. - -**Impact :** La recherche confrère en mode legacy IdNot renvoie davantage de résultats cohérents avec la saisie. Pour couvrir **tous les notaires de France**, configurer `ANNUAIRE_API_BASE_URL` et `IDNOT_API_KEY` dans `system_configuration` afin d’utiliser l’API Annuaire V2. - -**Modalités de déploiement :** Déploiement standard backend. - -**Modalités d'analyse :** Vérifier en logs `[IdNotDirectoryService] Étape 2 (API v2): X résultat(s) après filtre sur Y personne(s)` ; en cas d’API ne supportant pas prenom/nomUsuel, adapter ou conserver un fallback sans paramètres. - -**Évolution (2026-03-12) – Annuaire seul, pagination complète, alerte si non configuré :** La recherche confrères ne s’appuie plus sur la base du site (users/contacts/offices) ni sur les confrères déjà invités (folder_sharing). Source unique : API Annuaire (ANNUAIRE_API_BASE_URL + IDNOT_API_KEY). Pagination complète (lookup puis PP personnes, pages de 50 jusqu’à épuisement). Si l’annuaire n’est pas configuré : le backend renvoie `{ results: [], annuaireConfigured: false, message }` ; le front affiche un message d’alerte, sans fallback. Réponse API : `{ results, annuaireConfigured, message? }`. Plus de fallback legacy IdNot lorsque l’annuaire n’est pas configuré. Fichiers modifiés : IdNotDirectoryService, IdNotDirectoryApiSearchHelper, FolderSharingSearchHelper (back) ; SearchNotaries (types), useNotarySearch, SearchConfrereSection, ShareFolderModal (front). - -**Évolution (2026-03-12) – Invitation confrères : validation avant envoi email :** Les confrères ajoutés via « Ajouter » sur la page participants du dossier sont désormais mis en attente locale (comme les tiers). L'appel API `shareFolder` et l'envoi de l'email d'invitation ne sont effectués qu'au clic sur « Valider ». Si l'utilisateur clique « Annuler », aucun partage n'est créé et aucun email n'est envoyé. Les confrères en attente sont affichés avec « Sera invité lors de la validation » et peuvent être retirés avant validation. Fichiers modifiés : useConfreresManager, finalizeOperationsHelpers, useFinalizeHandler, ExistingConfreresList, ConfreresSection, SearchConfrereSection, StakeholdersContent, types. - -**Recherche confrères vide en test (ou tout env) :** L’annuaire **doit absolument être configuré** pour que la recherche confrères fonctionne. Si **ANNUAIRE_API_BASE_URL** ou **IDNOT_API_KEY** sont absents ou vides dans la table `system_configuration`, l’API `GET /api/v1/notary/search/notaries?q=...` renvoie **503 Service Unavailable** avec un corps `{ results: [], annuaireConfigured: false, message }` (pas de 200 avec résultats vides). Ces clés sont injectées depuis `.secrets//env-full--for-bdd-injection.txt` lors de l’étape **setSettings** du déploiement. Vérifier que ce fichier contient les lignes `ANNUAIRE_API_BASE_URL=...` et `IDNOT_API_KEY=...` (noms exacts), puis relancer setSettings sur la cible. Les logs backend indiquent les clés manquantes : `[IdNotDirectoryService] Annuaire non configuré: clé(s) manquante(s) dans system_configuration: ...`. - -**« Aucun notaire trouvé » alors que des notaires existent :** Si l’annuaire est configuré (pas de 503) mais la recherche renvoie 0 résultat pour des noms connus : (1) Consulter les logs backend sur la cible (service lecoffreio-backend@) : `🔍 [IdNotDirectoryService] API Annuaire V2 lookup...` indique l’URL appelée (sans la clé) ; `✅ ... lookup page 0: X entité(s), Y notaire(s) retenus` indique si l’API retourne des entités et combien passent le filtre (champs requis idNot, firstName, lastName). (2) Si « X entité(s), 0 notaire(s) retenus » : la réponse API ne contient pas les champs requis (uid/idnot, prenom, nomUsuel) sur les personnes ; le log `⚠️ Incomplete directory result ignored` et `⚠️ Lookup a retourné N entité(s) mais 0 notaire retenu` confirment ce cas. Adapter le parsing dans `IdNotDirectoryApiSearchHelper` (noms de champs selon la doc API Annuaire V2) ou vérifier le format de réponse de l’environnement (qual vs prod). (3) Si une erreur HTTP (4xx/5xx) apparaît : le log `⚠️ API Annuaire V2 lookup page 0 erreur: ... status=...` donne le code et éventuellement les clés du corps de réponse ; vérifier l’URL de base (ANNUAIRE_API_BASE_URL sans /api à la fin), la clé API et les droits du compte. (4) Fallback PP personnes : si le lookup échoue ou retourne 0 entité, le backend appelle `api/pp/v2/personnes?prenom=...&nomUsuel=...` ; vérifier en logs `API Annuaire V2 PP page 0: ... résultat(s) après filtre`. - - -### Secrets (.secrets) et copie vers les cibles - -Les répertoires `.secrets//` ne sont **pas dans git** (voir `.gitignore`). Ils doivent exister en local avant tout déploiement. - -Lors du déploiement (`deploy/scripts_v2/deploy.sh`), le script `sync-secrets-to-target.sh` copie les fichiers suivants vers la cible (test, pprod ou prod) dans `APP_ROOT/.secrets//` : - -- `.env.` (connexion SSH / config déploiement) -- `.env..connectDB` (credentials BDD V2 et V1) -- `env-full--for-bdd-injection.txt` (injection BDD / NEXT_PUBLIC_*) - -La base V1 est toujours distante : `DATABASE_V1_HOST` dans `.env..connectDB` ne doit **pas** être `localhost` ni `127.0.0.1`. En cas de valeur locale, `sync-secrets-to-target.sh` refuse la synchronisation (exit 1) et affiche une erreur. Le script est dans deploy/scripts_v2/remote/ ; PROJECT_ROOT doit être la racine du dépôt (SCRIPT_DIR/../../..) pour que le contrôle s'applique au bon fichier .secrets//.env..connectDB. - -backend.env n'existe plus sur les cibles : il est sauvegardé dans .secrets//backup/ puis supprimé à chaque sync ; la seule source pour la BDD est .env..connectDB. - -#### Phase 5 (check-v1-migration-status) : quel fichier est utilisé ? - -La Phase 5 s'exécute **sur la cible** (via SSH, heredoc dans `check-v1-migration-status.sh`). Elle lit **uniquement le fichier sur la cible** : `APP_ROOT/.secrets//.env..connectDB` (ex. `/srv/4NK/test.lecoffreio.4nkweb.com/.secrets/test/.env.test.connectDB`). Aucune lecture du fichier local pendant Phase 5. - -**Copie vers la cible au déploiement :** - -1. **deploy.sh** appelle **sync-secrets-to-target.sh** : le fichier **local** `.secrets//.env..connectDB` est copié vers la cible `APP_ROOT/.secrets//`. Si le fichier local est **absent**, aucune copie n'est faite et la cible garde son fichier existant. -2. **import-v1.sh** au début recopie le connectDB local vers la cible (`deploy/` puis `cp` vers `.secrets//`). -3. **import-v1.sh** juste avant Phase 5 recopie à nouveau le connectDB local vers la cible `.secrets//`, afin que Phase 5 utilise exactement le même contenu que la machine locale (évite dérive si un script intermédiaire ou une action manuelle a modifié le fichier sur la cible). - -Si le fichier local est absent, sync-secrets et import-v1 ne copient rien ; Phase 5 lit alors l’ancien fichier déjà présent sur la cible (éventuellement incorrect). En cas d’erreur « password authentication failed » en Phase 5 alors que `run-verify-db-connection.sh` réussit plus tard : vérifier que le fichier **local** `.secrets//.env..connectDB` existe et contient le bon mot de passe, puis relancer le déploiement (la copie avant Phase 5 recalera la cible sur le fichier local). - -**Pourquoi Phase 5 a pu échouer alors que run-verify-db-connection réussissait :** Phase 5 chargeait le fichier avec une boucle manuelle dans un heredoc (`while read` + `export "$trimmed"`), alors que run-verify-db-connection utilise `_lib.sh` et `load_env_file_export`. Un mot de passe avec caractères spéciaux pouvait être tronqué ou modifié par la boucle. Depuis la correction, Phase 5 utilise uniquement la même logique (source `_lib.sh` puis `load_env_file_export`) ; pas de fallback — si `_lib.sh` est absent sur la cible, Phase 5 sort en erreur (exit 2) avec un message explicite pour comprendre la cause. - -**Correction du parsing dans `load_env_file_export` (_lib.sh) :** L’ancienne ligne `export "${key}=${value}"` provoquait l’expansion par le shell de `${value}` : un mot de passe contenant `$` ou `` ` `` était modifié (ex. `my$ecr3t` → `my` si la variable `ecr3t` est vide). La correction : exporter la valeur sans expansion en l’enveloppant en guillemets simples et en échappant les `'` dans la valeur (`escaped="${value//\'/\'\\\'\'}"` puis `export "${key}='${escaped}'"`). Ainsi les mots de passe avec `$`, `` ` ``, guillemets ou retours à la ligne sont transmis correctement à `psql`. Si le fichier `.env..connectDB` contient `ENV='test'` ou `ENV="test"`, la valeur doit être délivrée sans les guillemets pour que `apply-connectdb.sh` puisse comparer `ENV` à l’argument `env` (ex. `test`) ; une comparaison avec guillemets inclus provoquait « ENV mismatch: connectDB ENV='test' but requested env=test ». La suppression des guillemets en début et fin de valeur est faite par expansion de paramètre (premier et dernier caractère) dans `load_env_file_export`, de façon portable (sans regex contenant des guillemets). - -### Échanges V1 / V2 : règles et flux - -Règles invariantes : **toute écriture en base cible V2 uniquement** ; **V1 strictement en lecture** lorsqu'elle est utilisée. - -#### Règle globale : écriture uniquement en V2 - -- L'application backend (repositories, services) utilise une seule connexion Prisma vers la base **V2** (DATABASE_*). Aucun code applicatif n'écrit en V1. -- Les scripts deploy qui modifient la base utilisent `.env..connectDB` avec `load_connectdb_v2` pour les écritures ; les écritures (INSERT, UPDATE, DELETE, TRUNCATE) vont toujours vers la base V2 (locale à la cible). - -#### Sync au login (utilisateur qui se connecte) - -- **Contexte** : à la connexion d'un utilisateur, une synchronisation optionnelle (V1 vers V2) peut être déclenchée pour ses bureaux (offices). -- **V1** : utilisée **uniquement en lecture** (SELECT) pour récupérer les données des offices de l'utilisateur et les lignes à fusionner. Fichiers : `V1ToV2LoginSyncService`, `V1ToV2MergeHelper` ; connexion via `getV1DatabaseConfig` (config lue depuis system_configuration / connectDB). -- **V2** : toutes les écritures (upsert) sont faites via Prisma (`this.database.getClient()`, `prisma.$executeRawUnsafe`) vers la base V2 uniquement. L'ancrage après sync (`V1ToV2LoginSyncAnchoringHelper`) écrit aussi uniquement en V2. - -- **Portée (scope)** : le sync ne concerne **que l'utilisateur connecté** (notaire ou notaire invité). Il est déclenché uniquement depuis `finalizeUserLoginFlow` avec `ensuredPayload.userId` (utilisateur authentifié). Les offices sont dérivés de V1 via `user_office_affiliations WHERE user_uid = $1` ; toutes les tables mergées et l'ancrage sont filtrés par ces `officeUids`. Aucun autre utilisateur n'est concerné. - -| Élément | Lecture | Écriture | -|--------|--------|----------| -| Base V1 (Scaleway distante) | Oui (SELECT, scoped by user offices) | Non | -| Base V2 (locale cible) | Oui | Oui (upsert, ancrage) | - -#### Import au déploiement (import-v1-data-direct-into-v2.sh) - -- **V1** : **lecture seule**. Connexion `psql_v1` / `pg_dump` vers DATABASE_V1_* ; uniquement SELECT et COPY TO STDOUT (export). Aucun INSERT/UPDATE/DELETE sur V1. -- **V2** : **seule cible d'écriture**. `psql_admin` = `psql -d "${DATABASE_NAME}"` (base V2) pour TRUNCATE, INSERT, UPDATE, ANALYZE. Les bases temporaires (tmp_db, TEMP_DB) sont créées sur l'hôte local (même machine que V2), pas sur V1. - -En-tête du script : `# CRITICAL: V1 is READ-ONLY.` et `# All writes target V2 only. Never modify V1.` - -#### Autres scripts et fichiers - -| Fichier / zone | Usage V1 | Usage V2 | -|----------------|----------|----------| -| `verify-db-connection.sh` | Lecture (SELECT 1) via .env..connectDB DATABASE_V1_* | Lecture (SELECT 1) via DATABASE_* | -| `run-verify-v1-connection-local.sh` | Lecture (SELECT 1) depuis .secrets//.env..connectDB | — | -| `read-v1-contacts-emails.sh` | Lecture seule (structure, effectifs, stats emails) | — | -| `recover-files-from-v1.ts` | Lecture (v1Prisma / --v1-database-url) pour importer documents/fichiers manquants | Écriture (prisma.create/update) vers V2 uniquement | -| Scripts ensure-* / promote-* / seed-* (connectDB) | — | Lecture/écriture via load_connectdb_v2 ou connectDB V2 | - -#### backend.env - -- **Plus utilisé** : après backup dans `.secrets//backup/`, le fichier `backend.env` est supprimé sur chaque cible à chaque exécution de `sync-secrets-to-target.sh`. La seule source pour la BDD est `.env..connectDB` (blocs DATABASE_* et DATABASE_V1_*). - -#### systemd et .secrets/ - -- Les unités systemd (backend, frontend, router) n'utilisent plus `/etc/lecoffreio//backend.env`, `frontend.env` ni `router.env`. Elles chargent uniquement les fichiers de `APP_ROOT/.secrets//` via des drop-ins par instance : `.env.` pour tous les services, et `.env..connectDB` en plus pour le backend. Ces fichiers ne sont pas modifiés par le déploiement. Le fichier `.env.` doit contenir notamment : pour le backend `ENV`, `NODE_ENV`, `APP_PORT` ; pour le frontend `PORT` ; pour le router `ROUTER_LISTEN_HOST`, `ROUTER_LISTEN_PORT`, `BACKEND_UPSTREAM`, `FRONTEND_UPSTREAM`. - - -### Phase 5 (vérification migration) : échec authentification PostgreSQL - -**Problème :** Déploiement prod en échec en Phase 5 (code 2) : authentification PostgreSQL échouée pour le rôle (ex. `lecoffre-user-prod`) sur 127.0.0.1:5432. - -**Cause :** Phase 5 charge uniquement `.secrets//.env..connectDB` sur la cible. L'auth échoue si ce fichier est absent, ou si `DATABASE_PASSWORD` ne correspond pas au rôle PostgreSQL, ou si une variable est vide/mal parsée. - -**Cause racine :** Fichier connectDB absent ou non synchronisé avec les identifiants réels de la cible ; ou pas de contrôle/test de connexion avant les requêtes. - -**Correction :** La Phase 5 utilise uniquement `.secrets//.env..connectDB`. Après chargement : vérification des variables DATABASE_* ; sinon exit 2. Puis test de connexion psql ; en cas d'échec, message explicite et exit 2. Assurer que .secrets//.env..connectDB est rempli et synchronisé vers la cible (backend.env ne doit plus exister après migration). - -**À faire :** S'assurer que `.secrets//.env..connectDB` est rempli (V2 et V1 si import) et synchronisé vers la cible. Après migration, backend.env ne doit plus exister (sauvegarde puis suppression). En cas d'échec auth : aligner le mot de passe du rôle PostgreSQL avec DATABASE_PASSWORD du fichier connectDB. - -**Modalités d'analyse :** Exécuter `bash deploy/scripts_v2/check-v1-migration-status.sh prod` (ou test/pprod) ; en cas d'échec, le message indique le fichier chargé. Tester la connexion : `bash deploy/scripts_v2/run-verify-db-connection.sh prod`. - - - -### Réancrage Phase 4 : erreur « Format tableur non supporté » traitée en skip - -**Problème :** Lors du réancrage (Phase 4), si le déchiffrement échoue (GCM) et que le validateur de format renvoie « Format tableur non supporté » (ou autre message de rejet de format), le document n'était pas considéré comme à ignorer et la phase pouvait échouer. - -**Correction :** Extension de `analyzeDecryptionError()` pour reconnaître tous les messages de rejet de format (`FileFormatValidator.getRejectionMessage()`) et marquer le document en skip. Rebuild backend puis relancer Phase 4 / déploiement si besoin. - - - -### Réancrage Phase 4 (prod) : « PDF conversion failed » / « PDF invalide » traités en skip - -**Problème :** En prod, la Phase 4 (réancrage) s'arrêtait (exit 1) lorsque des documents notary provoquaient une erreur de déchiffrement (GCM) puis « PDF conversion failed: generated PDF is invalid » ou « PDF invalide après conversion ». Ces messages n'étaient pas reconnus comme skip, donc le document allait en échec et le script quittait avec code 1. - -**Correction :** Extension de `analyzeDecryptionError()` pour reconnaître « pdf conversion failed », « pdf invalide », « generated pdf is invalid », « failed to parse pdf » et marquer le document en skip. Rebuild backend puis redéployer / relancer Phase 4. - -Complément : si `anchorDocumentForReanchor` renvoyait bien `skipped: true`, `processDocumentForReanchor` ne propageait pas `anchorResult.skipped`/`reason` et le document était encore compté en failed (« Erreur inconnue »). Correctif : dans `processDocumentForReanchor`, traiter en premier `anchorResult.skipped && anchorResult.reason` et retourner `{ success: false, skipped: true, reason }` ; fallback `error: anchorResult.error ?? "Erreur inconnue"` (fichier `ReanchorHelpers.ts`). - -### Réancrage Phase 4 : propagation de skipped/reason (processDocumentForReanchor) - -**Problème :** Même après reconnaissance des libellés PDF invalide / conversion échouée, le script affichait « Erreur document notary … : Erreur inconnue » et quittait en exit 1. - -**Cause :** `processDocumentForReanchor` ne testait que `anchorResult.error` ; lorsque `anchorDocumentForReanchor` retournait `{ success: false, skipped: true, reason }` (sans `error`), le retour générique était `{ success: false, skipped: false, error: undefined }`. - -**Correction :** Dans `ReanchorHelpers.ts`, si `!anchorResult.success` et `anchorResult.skipped && anchorResult.reason`, retourner immédiatement `{ success: false, skipped: true, reason: anchorResult.reason }` ; sinon utiliser `error: anchorResult.error ?? "Erreur inconnue"`. - -### Réancrage Phase 4 (prod) : « V1 decrypt produced empty buffer » traité en skip - -**Problème :** En prod, la Phase 4 (réancrage) pouvait encore quitter avec exit 1 lorsque l'erreur était « [FilesNotaryService] V1 decrypt produced empty buffer for <uid> ». Ce message n'était pas reconnu par `analyzeDecryptionError`, donc le document était compté en failed. - -**Cause :** La fonction `analyzeDecryptionError` (ReanchorHelpers.ts) ne reconnaissait que « buffer is empty » et « buffer vide » dans le bloc « buffer vide », pas « empty buffer » ni « v1 decrypt produced empty buffer ». - -**Correction :** Dans le bloc « buffer vide » de `analyzeDecryptionError`, ajouter les conditions `normalizedMessage.includes("empty buffer")` et `normalizedMessage.includes("v1 decrypt produced empty buffer")` pour traiter ces erreurs en skip (fichier expiré / buffer vide). - -### Migration V1 → V2 : `import-v1-direct` (MERGE) — collisions d’unicité sur `(name, office_uid)` - -**Problème :** -Le mode `MERGE` peut échouer sur des tables dont l’unicité métier est portée par `(name, office_uid)` (ex. `deed_types`, `document_types`) lorsque des lignes V1 utilisent un `uid` différent d’une ligne déjà existante en V2 pour le même couple `(name, office_uid)`. - -**Cause :** -Upsert basé sur `uid` (PK) alors que certaines tables ont aussi une contrainte unique sur `(name, office_uid)`. - -**Correction :** -Avant l’UPSERT, appliquer un mécanisme générique piloté par allowlist : - -Optionnel : limiter l’import aux éléments postérieurs à une date (facile à modifier/enlever) via `deploy/import-v1-last-ok.txt` (fichier gitignoré). - -- Construire un mapping `uid_v1 → uid_v2` depuis la clé métier (ex. `(name, office_uid)`) et l’exporter en CSV dans `/tmp` -- Réécrire ensuite les clés étrangères vers ces tables via discovery des FK (`pg_constraint`) pendant le run - -Détails : allowlist par table (deed_types, document_types, users idnot, office_folders folder_number), voir scripts import-v1-direct. - -### [import-v1-direct] MERGE customers : FK `customers_contact_uid_fkey` - -**Problème :** Lors du MERGE, l'étape customers échoue car des lignes V1 ont un `contact_uid` absent de la table `contacts`. - -**Correction :** Ajout de la table `contacts` à la liste des tables sans filtre date, pour que tous les contacts V1 soient mergés avant les customers. Les customers sont tous insérés (pas de filtre). Vérification en lecture seule en V1 : `SELECT uid, created_at, updated_at FROM contacts WHERE uid = ''`. - - - -### Suppression des synchronisations IdNot / Annuaire / Stripe (tables `sync_*` / `annu_*`) - -**Problème :** -Les parcours de recherche / résolution de confrères (ex: par email) pouvaient dépendre de tables de synchronisation (`sync_*`, `annu_*`) et de scripts/endpoints de sync dédiés. - -**Cause :** -Présence d’un mécanisme de synchronisation batch (IdNot/Annuaire/Stripe) avec tables dédiées et endpoints/scripts associés. - -**Cause racine :** -Le besoin “référentiel complet” a été implémenté par réplication locale (tables sync) au lieu d’un usage systématique des appels API au moment de la demande. - -**Correction :** - -- Suppression des points d’entrée / scripts / endpoints de sync et retrait des modèles Prisma `sync_*` / `annu_*` / `sync_stripe_subscriptions`. -- Les recherches utilisent désormais des **appels API** (IdNot/Annuaire) au moment de la demande, avec complément base du site et `folder_sharing`. - -**Impact :** - -- Plus d’usage applicatif des tables de synchronisation. -- Les réponses reposent sur la base réelle (users/contacts/offices) et les APIs externes au moment du besoin. - -**Modalités de déploiement :** -Déploiement standard backend. - -**Modalités d'analyse :** - -- Vérifier la recherche confrères (partage de dossier) et la résolution par email (logs backend, réponses API). - - - - -### Super-admin : chargement matrice de droits à la demande - -**Problème :** -Le frontend chargeait systématiquement `GET /api/v1/super-admin/role-permissions` dès qu’un utilisateur était identifié comme `super-admin`, y compris sur des pages non liées (ex: `/folders`). En cas d’aléa réseau, cela générait des erreurs `Failed to fetch` et du bruit console, sans valeur fonctionnelle. - -**Cause :** -Le refresh global des permissions déclenchait un chargement complet de matrice dès `permissionContext.globalRole === "super-admin"`. - -**Cause racine :** -Le chargement de la matrice était implémenté comme un effet global plutôt que comme un besoin local à l’écran d’administration de matrice, ce qui augmente la surface réseau et les points de défaillance. - -**Correction :** - -- Le refresh global ne charge plus la matrice complète pour les super-admins. -- La matrice complète est chargée uniquement sur la page `/super-admin/role-permissions` (à la demande). -- Suppression des imports dynamiques (`import()`/`require()`) sur les modules RolePermissions via extraction des types. - -**Impact :** - -- Plus d’erreur réseau sur `GET /super-admin/role-permissions` lors de navigation standard. -- Réduction des dépendances dynamiques côté front sur ce module. - -**Modalités de déploiement :** -Déploiement standard frontend. - -**Modalités d’analyse :** - -- Se connecter en super-admin et naviguer vers `/folders` puis vérifier l’absence d’appel `GET /api/v1/super-admin/role-permissions`. -- Naviguer vers `/super-admin/role-permissions` et vérifier que l’appel est déclenché (si la page existe). - - - -### Refactoring Linting Frontend (Transition ESLint 250/40) - -**Objectif :** -Réduire la dette technique et améliorer la maintenabilité du code frontend en respectant les règles ESLint strictes (max 40 lignes par fonction, max 250 lignes par fichier, max 4 paramètres). - -**Modifications :** - -- **BaseApiService :** Refactoring pour utiliser `RequestOptions` et `ProcessResponseOptions` afin de réduire le nombre de paramètres. -- **Factories :** Découpage des factories API (Auth, Admin, Notary, Customer) en extrayant les méthodes dans des helpers dédiés (`*MethodsHelpers.ts`). -- **Helpers :** Création de nombreux helpers pour alléger les fichiers principaux. -- **Imports :** Utilisation d'alias `@Front/Api/types` pour simplifier les imports. - -**Impact :** - -- Code plus modulaire et plus facile à tester. -- Réduction significative des erreurs de lint `max-lines-per-function`, `max-params` et `complexity`. -- Pas de changement fonctionnel (refactoring pur). - -**Modalités de déploiement :** -Déploiement standard du frontend. - -**Modalités d'analyse :** -Vérifier que l'application fonctionne correctement (navigation, appels API). `npm run lint` doit rapporter moins d'erreurs. - -### Correction de la visibilité des dossiers archivés pour les clients - -**Problème :** -Les clients pouvaient voir les dossiers archivés dans leur liste de dossiers, ce qui n'est pas le comportement attendu. - -**Cause :** -Le filtre `status: "LIVE"` était manquant dans la configuration de la requête Prisma pour les clients dans `OfficeFoldersController`. - -**Correction :** -Ajout explicite du filtre `status: "LIVE"` et `deleted_at: null` dans les méthodes de configuration de requête pour les clients et les tiers. - -**Modifications :** - -- Fichier : `lecoffre-back-main/src/app/api/customer/OfficeFoldersController.ts` - - Ajout de `status: "LIVE"` dans `configureThirdPartyQuery` et `configureCustomerQuery`. - - Ajout de la vérification du statut dans `getOneByUid`. - -**Impact :** -Les clients et tiers ne verront plus les dossiers archivés ou supprimés dans leur interface. - -**Modalités de déploiement :** -Déploiement standard du backend. - -**Modalités d'analyse :** -Vérifier que les dossiers ayant le statut `ARCHIVED` ou `DELETED` n'apparaissent plus dans la liste des dossiers côté client. - -### Correction de la génération de ZIP avec fichiers multiples (collision de noms) - -**Problème :** -Lors de la sélection de plusieurs documents pour créer un ZIP, si les noms de fichiers générés étaient identiques (ex: même type de document pour le même dossier), un seul fichier était présent dans le ZIP final car ils s'écrasaient mutuellement. - -**Cause :** -Absence de gestion des collisions de noms de fichiers dans `ZipService.ts`. Le service ajoutait les fichiers à l'archive avec le nom généré par `FileNamingService`, sans vérifier si ce nom était déjà utilisé dans l'archive en cours de construction. - -**Correction :** -Ajout d'une logique pour détecter les noms de fichiers déjà utilisés dans l'archive et ajouter un suffixe incrémental `(n)` pour les rendre uniques. -La logique a été factorisée dans `FileNamingService.getUniqueFileName`. - -**Modifications :** - -- Fichier : `lecoffre-back-main/src/services/common/ZipService/ZipService.ts` - - Utilisation de `FileNamingService.getUniqueFileName` pour garantir l'unicité des noms dans l'archive. -- Fichier : `lecoffre-back-main/src/services/common/FileNamingService/FileNamingService.ts` - - Ajout de la méthode `getUniqueFileName` pour gérer la déduplication avec suffixe `(n)`. - -**Impact :** -Les ZIPs générés contiendront désormais tous les fichiers sélectionnés, même s'ils ont le même nom de base. - -**Modalités de déploiement :** -Déploiement standard du backend. - -**Modalités d'analyse :** -Vérifier que les ZIPs contenant plusieurs fichiers avec le même nom (ex: deux "Pièce d'identité" dans le même dossier) contiennent bien tous les fichiers avec des noms distincts. - -### Correction des permissions V2 pour le rôle `admin` (matrice des droits) - -**Problème :** -Un utilisateur d’étude avec `global_role = admin` pouvait se retrouver avec une matrice V2 très restreinte côté UI (pages/actions manquantes sur documents/fichiers/partages/rappels/demandes), tandis que les collègues résolus en `collaborator` avaient le périmètre complet. - -**Cause :** -La V2 filtre la matrice de permissions sur le rôle résolu. Pour un utilisateur `admin`, la résolution conserve `admin`, donc l’UI dépend directement des entrées `admin` présentes dans `role_permissions_matrix`. - -**Cause racine :** -Les scripts/migrations existants assuraient la complétude de `collaborator` sur les ressources notariales, mais pouvaient laisser `admin` incomplet (notamment via certains scripts d’ensure). - -**Correction :** -Backfill idempotent (insert-only) des entrées `admin` à partir des entrées `collaborator` pour les ressources notariales : - -- Script d’ensure : `deploy/scripts_v2/remote/sql/ensure-role-permissions-matrix-folders.sql` (clonage `collaborator` → `admin` avec `ON CONFLICT DO NOTHING`). -- Migration Prisma : `20260305120000_backfill_admin_permissions_from_collaborator`. - -- Ensure au login : lors de la génération du JWT pour un utilisateur `admin`, backfill idempotent `collaborator` → `admin` si la matrice est incomplète (avec reload du cache). - -**Modalités de déploiement :** -Déploiement standard du backend (migrations via `prisma migrate deploy`). En cas de base restaurée / matrice incomplète, exécuter le script d’ensure habituel. - -- Depuis `deploy/scripts_v2/deploy.sh` : exécution systématique de `deploy/scripts_v2/remote/ensure-role-permissions-matrix-folders.sh` après migrations. - -- Diagnostics (logs) : - - `deploy/scripts_v2/run-check-user-offices-logs.sh ` : journalctl backend centré sur `UserOffices` et sync offices (utile quand le front loggue `OfficeSelector hidden: no offices available`). - - Les scripts de diagnostic `scripts_v2/remote/*` utilisent `print_journalctl_matches` / `count_journalctl_matches` (définis dans `deploy/scripts_v2/remote/_lib.sh`) pour rester stables avec `set -euo pipefail` même quand il n’y a aucun match. - -**Modalités d'analyse :** -Contrôler que `role_permissions_matrix` contient des entrées `admin` sur `documents`, `files`, `folder_customer`, `folder_third_parties`, `folder_sharing`, `document_reminder`, `document_request`, et que `GET /api/v1/notary/my-permissions` pour un utilisateur `admin` renvoie ces ressources/pages. - - -### Correction : offices sans `office_roles` + Annuaire (champs requis) - -**Problème :** - -- Certains offices peuvent exister sans aucune entrée dans `office_roles` (absence de `Notaire` / `Collaborateur`), ce qui peut dégrader le calcul des règles au login. -- Les résultats Annuaire/IdNot peuvent contenir des personnes incomplètes (sans `idNot`, `firstName`, `lastName`) qui ne doivent pas être proposées comme ajoutables. - -**Cause :** - -- Incohérences de données historiques / import / migrations. -- Qualité variable des données source Annuaire. - -**Correction :** - -- Vérification/correction au login : ensure idempotent des rôles d’office par défaut (`Notaire`, `Collaborateur`) et liaison aux règles attendues. -- Vérification/correction systématique au déploiement : exécution d’un ensure SQL idempotent via `deploy/scripts_v2/deploy.sh`. -- Filtrage Annuaire : exclusion + log des résultats incomplets (champs requis). - -**Modifications :** - -- `lecoffre-back-main/src/services/common/IdNotService/IdNotRoleService.ts` -- `lecoffre-back-main/src/services/common/AuthService/helpers/JwtPayloadGenerationHelper.ts` -- `lecoffre-back-main/src/services/common/IdNotDirectoryService/helpers/DirectoryRequiredFieldsHelper.ts` -- `deploy/scripts_v2/remote/sql/ensure-default-office-roles.sql` -- `deploy/scripts_v2/deploy.sh` - - Au démarrage : `git add -A` + commit + push local (si changements) AVANT toute action distante. - - Sur la machine cible : reset/checkout du ref via `git checkout -B /` pendant le sync. - -**Modalités de déploiement :** - -- Déploiement standard V2. -- En cas de base restaurée : `deploy/scripts_v2/run-ensure-default-office-roles.sh `. - -**Modalités d'analyse :** - - - -### Vérification migration i18n / site texts (backend, frontend, ressources) - -**Objectif :** -Vérifier que les textes utilisateur sont migrés en i18n (site texts) et configurés dans `.secrets//seed-site-texts-.ts` et `.secrets//env-full--for-bdd-injection.txt` pour chaque environnement. - -**État constaté :** - -- **Frontend :** Les écrans Login, MyAccount, et la page Office (informations) utilisent `useSiteTextContent` avec des clés déclarées dans le seed. Les clés utilisées (65 à 80 selon les écrans) sont présentes dans le seed et dans `SITE_TEXTS_KEYS_INDEX` de l’env-full. Le boot (page `/_app`) précharge désormais aussi le scope `components` (en plus de `login`) via `lecoffre-front-main/src/front/Stores/siteTextsBootConfig.ts`, pour éviter les logs `Missing site text` quand une clé `components.*` est en base mais pas chargée. Plusieurs écrans (dossiers, formulaires tiers, super-admin, etc.) contiennent encore des libellés en dur ; une migration progressive peut étendre le scope `components` ou ajouter des scopes métier et les clés correspondantes au seed puis à l’env-full. -- **Backend :** Les messages d’erreur et de validation sont centralisés dans `lecoffre-back-main/src/common/utils/errorMessages.ts`. Les réponses API (erreurs 4xx/5xx) utilisent ces constantes. Quelques messages de succès sont en dur dans les contrôleurs (ex. `IncidentReportController`, `FolderSharingResendHelper`, `FolderThirdPartiesResendCodeHelper`, `DocumentsGuestNotaryHelper`). Aucune résolution de clés site texts côté backend pour les réponses API n’est en place ; une évolution ultérieure pourrait exposer des clés i18n côté API si besoin. -- **Ressources :** Le package `le-coffre-resources` fournit des types (Notary, SuperAdmin, etc.) ; pas de chaînes utilisateur à migrer. - -**Modifications effectuées :** - -- **MyAccount / Mon compte :** Utilisation des clés `myAccount.office.idNot.label` et `myAccount.office.idNot.placeholder` déjà présentes dans le seed ; passage de ces libellés depuis le parent vers `MyAccountOfficeSection` (plus de texte en dur pour l’identifiant ID.NOT). -- **Page Office (informations) :** Ajout de 11 clés dans le seed (scope `office`) : `office.labels.crpcen`, `office.labels.denomination`, `office.labels.address`, `office.titles.administrators`, `office.titles.collaborators`, `office.userLabels.lastName`, `office.userLabels.firstName`, `office.userLabels.email`, `office.userLabels.phone`, `office.userLabels.role`, `office.header.active`. Mise à jour de `.secrets/test/seed-site-texts-test.ts` et de `.secrets/test/env-full-test-for-bdd-injection.txt` (SITE_TEXTS_KEYS_INDEX). Page `offices/[officeUid]` enveloppée dans `SiteTextsProvider` avec `scopes={["office"]}` ; composant `OfficeInformations` migré pour utiliser `useSiteTextContent` et passer les libellés à la vue (correction orthographique « Addresse » → « Adresse » via la clé). - -**Modalités de déploiement :** - -- Exécuter le seed site texts après déploiement si les nouvelles clés doivent être présentes en base : `npm run site-texts:seed` (backend) en s’assurant que le fichier seed et l’env-full sont bien déployés (voir `deploy/scripts_v2`). -- Pour pprod/prod : reproduire les entrées du seed test dans les dépôts concernés (`.secrets//seed-site-texts-.ts` et `env-full--for-bdd-injection.txt`) puis exécuter le seed sur l’environnement cible. - -**Modalités d’analyse :** - -- Vérifier que la page Mon compte affiche bien les libellés ID.NOT depuis le seed. -- Vérifier que la page Office (super-admin) affiche les libellés CRPCEN, Dénomination, Adresse, Administrateurs, Collaborateurs, Nom, Prénom, E-mail, Téléphone, Rôle depuis le seed (scope `office`). -- Vérifier que `SITE_TEXTS_KEYS_INDEX` dans l’env-full contient toutes les clés du seed (ordre identique recommandé). -- Vérifier au login que `officeRole` est résoluble et que les `rules` associées sont chargées. -- Vérifier que les résultats Annuaire incomplets sont ignorés et loggués (missing fields + `idNotPrefix`). - -## Évolutions documentées -### Centralisation vérification accès invité notaire - -**Date** : 2026-03-02 - -#### Objectif - -Centraliser la logique « vérifier accès invité notary » (email invité + folder + hasAccess via folder_sharing) dans un helper générique, utilisé par le téléchargement batch (FilesBatchDownloadGuestNotaryHelper) et le téléchargement single fichier notary (FilesNotaryDownloadGuestNotaryHelper). La logique batch ne sert plus qu’au regroupement d’emails ou à la vérification des relances, pas pour l’upload de documents. - -#### Impacts - -- Un seul point de vérité pour : résolution de l’email invité (getEffectiveGuestNotaryEmail), appel folderSharingService.hasAccess(folderUid, "GUEST_FOLDERS", email, idNot), et réponse 404 en cas d’échec. - -- Réduction de la duplication entre FilesBatchDownloadGuestNotaryHelper et FilesNotaryDownloadGuestNotaryHelper. - -- Signature commune : verifyGuestNotaryAccess({ permissionContext, folderUid, req, response, operationLabel, logContext, contextId }) → { hasAccess: true, folderUid } | { hasAccess: false }. - -#### Modifications - -- **Création** : `lecoffre-back-main/src/app/api/common/helpers/GuestNotaryAccessVerificationHelper.ts` — exporte `verifyGuestNotaryAccess` et types associés. - -- **Refactor** : `FilesBatchDownloadGuestNotaryHelper.ts` — résolution du folderUid (existant), puis appel au helper commun ; suppression de la duplication email + hasAccess. - -- **Refactor** : `FilesNotaryDownloadGuestNotaryHelper.ts` — après verifyDocumentAndFolder, appel au helper commun ; suppression de la fonction locale verifyGuestNotaryAccess (renommée en checkGuestNotaryAccess qui délègue au helper commun). Suppression du paramètre userEmail de handleGuestNotaryFileFromFiles (l’email est résolu dans le helper commun via permissionContext). - -- **Appelant** : `FilesNotaryDownloadHelper.ts` — appel à handleGuestNotaryFileFromFiles sans userEmail ; extraction simplifiée (isGuestNotary uniquement si besoin). - -#### Modalités de déploiement - -Déploiement standard (build-and-deploy.sh). Aucune migration ni variable d’environnement. - -#### Modalités d’analyse - -- Téléchargement single fichier notary en notaire invité : accès autorisé uniquement si l’invité a accès au dossier via folder_sharing (GUEST_FOLDERS). - -- Téléchargement batch en notaire invité : même règle ; le folderUid est résolu à partir du premier fichier du batch. - -### Remédiation : Sièges pour affiliations actives - -**Date** : 2026-03-04 -**Auteur** : Équipe 4NK -**Statut** : Implémenté - -#### Objectif - -Assurer que tout utilisateur avec une affiliation **ACTIVE** à un office disposant d’un abonnement **ACTIVE** possède bien un siège (licence) pour cet office. Cette vérification est effectuée : - -1. **À la connexion (login)** : De manière automatique et non bloquante. - -2. **Via script batch** : Pour remédiation de masse. - -#### Modifications - -##### 1. Backend - Services - -* **`UserOfficeAffiliationsService`** : Ajout de la méthode `ensureSeatsForUserActiveAffiliations(userUid)`. - * Récupère les affiliations actives. - * Pour chaque affiliation, vérifie/crée le siège via `SubscriptionsService`. - -* **`SubscriptionsService`** : Modification de `isUserSubscribed`. - * Pour les abonnements **UNLIMITED**, si le siège n'existe pas, il est créé automatiquement à la volée. - * Pour les abonnements **STANDARD**, le comportement reste une simple vérification (retourne `false` si pas de siège). - -##### 2. Backend - Flux de connexion - -* **`UserControllerLoginFlowAuthHelper`** : - * Appel de `ensureSeatsForUserActiveAffiliations` après la synchronisation IdNot. - * Exécuté dans un `try/catch` pour ne pas bloquer le login en cas d'erreur technique. - -##### 3. Scripts - -* **Analyse SQL** : `scripts/analyze-seats-vs-affiliations.sql` - * Liste les utilisateurs avec affiliation ACTIVE mais sans siège dans un office avec abonnement ACTIVE. - -* **Remédiation Batch** : `src/scripts/ensure-seats-for-active-affiliations.ts` - * Parcourt tous les utilisateurs et lance la vérification `ensureSeatsForUserActiveAffiliations`. - -#### Modalités de déploiement - -1. **Analyse préalable (Obligatoire)** : - Exécuter le script SQL pour identifier les écarts avant correction. - ```bash - psql "$DATABASE_URL" -f scripts/analyze-seats-vs-affiliations.sql - ``` - -2. **Exécution du script de remédiation** : - ```bash - # Dry-run (simulation) - npm run ensure:seats -- --dry-run - - # Exécution réelle - npm run ensure:seats - ``` - -3. **Analyse post-remédiation (Obligatoire)** : - Ré-exécuter le script SQL pour confirmer la résolution des écarts. - ```bash - psql "$DATABASE_URL" -f scripts/analyze-seats-vs-affiliations.sql - ``` - -#### Modalités d'analyse - -* Vérifier les logs avec `[ensureSeats]` ou `[SubscriptionsService]`. - -* Utiliser le script SQL d'analyse pour vérifier qu'il n'y a plus d'écarts. - -## Correctifs additionnels documentés -### Correctifs espace notaire invité (retour, téléchargement, note dossier) - -**Date** : 2026-03-02 - -#### Problèmes - -1. **Lien retour après visualisation d’un document envoyé** - Depuis la page « Documents envoyés par le notaire », clic sur un document puis sur Retour renvoyait vers la home (/folders) au lieu de la liste des documents envoyés. - -2. **Téléchargement de fichier avec accent** - Télécharger un fichier (filigrane + métadonnées) dont le nom contient des accents pouvait mal s’afficher ou mal se nommer (Content-Disposition sans guillemets autour du filename). - -3. **Note à la création du dossier** - La note saisie à la création du dossier ne s’affichait pas sur la page « Préparer le dossier » (add/clients) après redirection ; elle n’apparaissait qu’après modification ultérieure sur la fiche dossier. - -#### Cause / root cause - -1. **Retour** - L’URL de retour dans ViewDocuments (Folder) est calculée via `useReceivedDocumentBackUrl`. Pour le notaire invité, elle dépend de `document.shared_to_office` et du statut SENT/DOWNLOADED. Lorsque le document est chargé via l’API DocumentsNotary (customer), `shared_to_office` n’est pas inclus dans la réponse, donc la condition échouait et on retombait sur l’URL par défaut (FolderInformation ou vide → home). Root cause : pas de fallback quand l’utilisateur est notaire invité avec un folderUid connu. - -2. **Téléchargement** - Dans `FilesNotaryDownloadHelper`, le header Content-Disposition utilisait `filename=${encodeURIComponent(...)}` sans guillemets. La RFC 2183 recommande une valeur entre guillemets pour les noms avec caractères spéciaux/accents. De plus, le nom utilisé était uniquement `file_name` au lieu de privilégier `display_name` quand disponible. - -3. **Note** - La note (description) est bien envoyée à la création et persistée en base. La page add/clients charge le dossier via GET /notary/folders/:uid mais ne mettait dans `folderMetadata` que `number` et `name`, pas `description`, et n’affichait nulle part la note sur « Préparer le dossier ». - -#### Correctifs - -1. **Retour (viewDocumentsBackUrlHelpers.ts)** - - Ajout d’un fallback : si l’utilisateur est notaire invité et que `folderUid` est défini, l’URL de retour est toujours `InvitedReceivedDocuments` (liste des documents envoyés), même lorsque le document n’a pas encore `shared_to_office` dans la réponse (ex. chargement via DocumentsNotary API). - -2. **Téléchargement (FilesNotaryDownloadHelper.ts)** - - Utilisation de `display_name` si présent, sinon `file_name`. - - Content-Disposition : `filename="${encodeURIComponent(fileName)}"` (guillemets autour de la valeur). - -3. **Note (AddClientToFolder)** - - `FolderMetadata` étendu avec `description?: string | null`. - - Dans `folderPageDataLoaderHelpers`, passage de `description` depuis le dossier chargé à `setFolderMetadata`. - - Sur la page add/clients, affichage d’un bloc « Note du dossier » sous le sous-titre lorsque `folderMetadata.description` est renseigné (style `.folder-note` dans `classes.module.scss`). - -#### Modifications - -- `lecoffre-front-main/src/front/Components/Layouts/Folder/ViewDocuments/helpers/viewDocumentsBackUrlHelpers.ts` - -- `lecoffre-back-main/src/app/api/notary/helpers/FilesNotaryDownloadHelper.ts` - -- `lecoffre-front-main/src/front/Components/Layouts/Folder/AddClientToFolder/types.ts` - -- `lecoffre-front-main/src/front/Components/Layouts/Folder/AddClientToFolder/hooks/helpers/folderPageDataLoaderHelpers.ts` - -- `lecoffre-front-main/src/front/Components/Layouts/Folder/AddClientToFolder/index.tsx` - -- `lecoffre-front-main/src/front/Components/Layouts/Folder/AddClientToFolder/classes.module.scss` - -#### Environnements - -Les correctifs sont côté code (front + back) et s’appliquent à tous les environnements (test, pprod, prod) après déploiement. Aucune configuration spécifique par environnement. - -#### Modalités de déploiement - -Déploiement standard (build-and-deploy.sh). Aucune migration ni variable d’environnement. - -#### Modalités d’analyse - -- **Retour** : se connecter en notaire invité, ouvrir un dossier partagé, aller dans « Documents envoyés par le notaire », ouvrir un document, cliquer sur Retour → la page doit être la liste des documents envoyés, pas la home. - -- **Téléchargement** : en notaire invité (ou notaire), télécharger un document dont le fichier a un nom avec accents (et si possible filigrane) → le nom du fichier téléchargé doit être correct et lisible. - -- **Note** : créer un dossier avec une note « Note de dossier » renseignée, valider → après redirection sur « Préparer le dossier », la note doit apparaître sous le sous-titre. - -### Problème : Note de dossier non sauvegardée à la création - -#### Description du problème -Lors de la création d'un dossier, la note saisie dans le champ "Note de dossier" n'était pas sauvegardée. Le champ apparaissait vide après la création. - -#### Cause -Le champ `description` (qui contient la note) dans la classe `OfficeFolder` (ressource Notary) n'avait pas de décorateur de validation pour le groupe `createFolder`. -Lors de la validation de l'entité avec `class-validator` et l'option `whitelist: true`, les propriétés sans décorateur de validation actif pour le groupe donné sont supprimées de l'objet. -Comme `description` n'avait que `@IsOptional({ groups: ["updateFolder"] })`, il était ignoré et supprimé lors de la validation avec le groupe `createFolder`. - -#### Correction -Ajout du groupe `createFolder` au décorateur `@IsOptional` pour les champs `description`, `archived_description` et `status` dans `lecoffre-ressources-dev/src/Notary/OfficeFolder.ts`. - -```typescript - @Expose() - @IsOptional({ groups: ["createFolder", "updateFolder"] }) - public override description: string | null = null; - - @Expose() - @IsOptional({ groups: ["createFolder", "updateFolder"] }) - public override archived_description: string | null = null; - - @Expose() - @IsOptional({ groups: ["createFolder", "updateFolder"] }) - public override status: EFolderStatus | string = EFolderStatus.LIVE; -``` - -#### Impacts - -- La note de dossier est maintenant correctement sauvegardée à la création. - -- `archived_description` et `status` sont également correctement traités s'ils sont fournis à la création (bien que `status` ait une valeur par défaut). - -#### Modalités de déploiement - -- Recompiler `lecoffre-ressources-dev`. - -- Redéployer le backend pour prendre en compte la nouvelle version de la ressource. - -### Correction V2 : Injection de dépendance circulaire dans OfficesService (ESM) - -**Date :** 04/03/2026 -**Auteur :** Équipe 4NK - -#### Problème -Malgré le fix précédent (lazy loading via getter), une erreur persistait lors de l'exécution sur l'environnement de test (ESM) : -```text -TypeError: this.officesRibService.uploadEncryptedRibFromFiles is not a function -``` -Cela indique que `OfficesRibService` est toujours injecté comme un objet vide `{}` à cause de la dépendance circulaire, même avec le getter, car l'import statique `import OfficesRibService from "./OfficesRibService"` est évalué au chargement du module et résolu en `{}` si le cycle n'est pas résolu. - -#### Analyse - -- **Cause racine :** Dépendance circulaire persistante. Dans un environnement ESM, si un module est importé avant d'être complètement évalué (cycle), l'import par défaut peut être un objet vide ou incomplet. - -- **Échec du fix précédent :** Le getter utilisait `Container.get(OfficesRibService)`, mais `OfficesRibService` (le symbole importé) était déjà `{}`. - -#### Solution -Utilisation de l'import dynamique `await import(...)` à l'intérieur des méthodes pour garantir que le module est complètement chargé avant utilisation. - -##### Modifications - -1. Remplacement des imports statiques par `import type`. - -2. Remplacement des getters synchrones par des méthodes privées asynchrones `getOfficesRibService()` et `getOfficesAnchorService()`. - -3. Utilisation de `await import("./OfficesRibService")` pour récupérer la classe au runtime. - -4. Mise à jour de toutes les méthodes appelantes pour être `async` (elles l'étaient déjà) et utiliser `await this.getOfficesRibService()`. - -```typescript -// Avant -import OfficesRibService from "./OfficesRibService"; -// ... -private get officesRibService(): OfficesRibService { - return Container.get(OfficesRibService); -} - -// Après -import type OfficesRibService from "./OfficesRibService"; -// ... -private async getOfficesRibService(): Promise { - const { default: OfficesRibServiceClass } = await import("./OfficesRibService"); - return Container.get(OfficesRibServiceClass); -} -``` - -#### Vérification - -- Le code compile. - -- L'import dynamique casse le cycle de dépendance au niveau du chargement des modules. - -### Correction : Injection de dépendance circulaire dans OfficesService - -**Date :** 04/03/2026 -**Auteur :** Équipe 4NK - -#### Problème -Le démarrage du backend échouait avec l'erreur suivante : -```text -Error: OfficesRibService injected but missing uploadEncryptedRibFromFiles method. Injected type: Object -``` -Cela indiquait que `OfficesRibService` était injecté comme un objet vide `{}` au lieu de l'instance de la classe, symptôme classique d'une dépendance circulaire mal gérée par le système d'injection de dépendances (TypeDI) lors de l'initialisation. - -#### Analyse - -- **Cause racine :** Dépendance circulaire entre `OfficesService` et `OfficesRibService` (probablement via `OfficesRibProcessingHelper` ou d'autres dépendances partagées). - -- **Impact :** Le service `OfficesService` tentait d'accéder à `OfficesRibService` dans son constructeur alors que ce dernier n'était pas encore totalement initialisé. - -#### Solution -Mise en place du **Lazy Loading** pour les services problématiques dans `OfficesService`. - -##### Modifications - -1. Suppression de `officesRibService` et `officesAnchorService` du constructeur de `OfficesService`. - -2. Utilisation de getters pour récupérer ces services via `Container.get()` uniquement au moment de leur utilisation. - -```typescript -// Avant -constructor(private officesRibService: OfficesRibService) { ... } - -// Après -private get officesRibService(): OfficesRibService { - return Container.get(OfficesRibService); -} -``` - -#### Vérification - -- Le code compile et les linters passent. - -- Cette approche est déjà utilisée dans `WatermarkService` pour résoudre des problèmes similaires. - -### Correction : Extension manquante dans l'import dynamique de OfficesService - -**Date :** 2026-03-04 -**Auteur :** Équipe 4NK - -#### Problème - -Une erreur 500 survenait lors du téléchargement du RIB : -`Cannot find module '.../services/notary/OfficesService/OfficesService.js'` - -Cette erreur indiquait que Node.js n'arrivait pas à charger le module `OfficesService`, probablement à cause d'une erreur d'import interne. - -#### Cause racine - -Le fichier `OfficesService.ts` utilisait des imports dynamiques relatifs (`await import("./OfficesRibService")`) sans spécifier l'extension `.js`. -Dans un environnement Node.js ESM (configuré avec `"type": "module"`), les imports relatifs doivent obligatoirement inclure l'extension du fichier. - -#### Correctif - -Ajout de l'extension `.js` aux imports dynamiques dans `src/services/notary/OfficesService/OfficesService.ts` : - -```typescript -// Avant -const { default: OfficesRibServiceClass } = await import("./OfficesRibService"); -const { default: OfficesAnchorServiceClass } = await import("./OfficesAnchorService"); - -// Après -const { default: OfficesRibServiceClass } = await import("./OfficesRibService.js"); -const { default: OfficesAnchorServiceClass } = await import("./OfficesAnchorService.js"); -``` - -#### Impact - -- `src/services/notary/OfficesService/OfficesService.ts` - -#### Vérification - -Le déploiement est nécessaire pour vérifier la correction. -L'erreur de téléchargement du RIB devrait disparaître. - -#### Autre correction incluse - -Ajout de la clé de traduction manquante `components.dropdown.defaultAriaLabel` dans `.secrets/test/seed-site-texts-test.ts` pour corriger l'erreur frontend : -`[Front] 🚫 [SiteTextsStore] Missing site text {textKey: 'components.dropdown.defaultAriaLabel', locale: 'fr-FR'}` - -### Fix: Erreur téléchargement ZIP "files-notary" (Notaire invité) - -#### Problème -Lorsqu'un notaire invité tente de télécharger plusieurs documents (ZIP) depuis la vue "Documents envoyés par le notaire", une erreur survient et l'utilisateur est parfois déconnecté. -Le frontend appelle l'endpoint `POST /api/v1/notary/files-notary/download-multiple`, mais celui-ci n'était pas implémenté dans le contrôleur `FilesNotaryController`. - -#### Cause Racine -L'endpoint `downloadMultiple` manquait dans `FilesNotaryController`. Bien que le helper `FilesBatchDownloadHelper` existe, il était câblé uniquement pour les fichiers standards (`FilesService`) et utilisé seulement par `FilesController`. - -#### Correctifs - -1. **Modification de `FilesBatchDownloadHelper` et `FilesBatchDownloadZipHelper`** : - * Ajout du paramètre `isNotaryFiles` (booléen, défaut `false`) pour indiquer si le téléchargement concerne des fichiers notaire (`FilesNotaryService`) ou standards. - * Propagation de ce paramètre jusqu'à `ZipService.createZipFromFiles` qui supportait déjà cette option. - -2. **Mise à jour de `FilesNotaryController`** : - * Injection de `ZipService`. - * Instanciation de `FilesBatchDownloadHelper`. - * Ajout de la méthode `downloadMultiple` exposant la route `POST /api/v1/notary/files-notary/download-multiple` et appelant le helper avec `isNotaryFiles: true`. - -#### Impacts - -- `lecoffre-back-main/src/app/api/notary/helpers/FilesBatchDownloadHelper.ts` - -- `lecoffre-back-main/src/app/api/notary/helpers/FilesBatchDownloadZipHelper.ts` - -- `lecoffre-back-main/src/app/api/notary/FilesNotaryController.ts` - -#### Modalités de déploiement -Déploiement standard du backend. - -#### Modalités d'analyse -Vérifier le téléchargement multiple (ZIP) en tant que notaire invité sur des documents partagés par le notaire principal. - -### Correction de l'avertissement de préchargement de police non utilisé et migration next/font - -**Date :** 04/03/2026 -**Auteur :** Équipe 4NK - -#### Problème -L'application affichait un avertissement dans la console du navigateur : -`La ressource à l’adresse « ... » préchargée avec le préchargement de lien n’a pas été utilisée après quelques secondes.` -De plus, certaines parties de l'application utilisaient encore des définitions de polices en dur ("Poppins", "Inter") au lieu d'utiliser le système optimisé de Next.js (`next/font`). - -#### Cause -L'application utilise `next/font/google` pour charger les polices "Inter" et "Poppins" de manière optimisée. `next/font` génère des noms de classes et des variables CSS uniques pour isoler les polices. -Cependant, plusieurs fichiers SCSS référençaient les noms de polices en dur ("Poppins", "Inter") au lieu d'utiliser les variables CSS injectées par `next/font` (`var(--font-poppins)`, `var(--font-inter)`). -En conséquence : - -1. Le navigateur téléchargeait la police préchargée par `next/font`. - -2. Le CSS demandait une autre police (système ou téléchargée séparément), rendant le préchargement inutile et causant un double téléchargement. - -3. Risque de Cumulative Layout Shift (CLS) accru. - -#### Correctifs - -1. **Variables globales (`variables.scss`) :** - - Remplacement de `font-family: "Poppins", ...` par `font-family: var(--font-poppins), ...`. - -2. **Styles globaux (`index.scss`) :** - - Remplacement de `font-family: "Inter", ...` par `font-family: var(--font-inter), ...`. - -3. **Composants spécifiques :** - - Remplacement des fallbacks `Poppins` par `sans-serif` dans les fichiers suivants pour utiliser correctement la variable CSS héritée ou définie : - - `src/front/Components/Elements/NumberPicker/classes.module.scss` - - `src/front/Components/DesignSystem/SearchBar/classes.module.scss` - - `src/front/Components/DesignSystem/Form/TextField/classes.module.scss` - - `src/front/Components/DesignSystem/Form/TextareaField/classes.module.scss` - - `src/front/Components/DesignSystem/AutocompleteMultiSelect/ChipContainer/classes.module.scss` - - `src/front/Components/DesignSystem/Footer/classes.module.scss` - -#### Impacts - -- Disparition de l'avertissement dans la console. - -- Meilleure performance de chargement des polices (pas de double téléchargement). - -- Élimination du Layout Shift (CLS) lié aux polices. - -- Cohérence totale de la gestion des polices via `next/font`. - -#### Modalités de déploiement -Déploiement standard de l'application frontend `lecoffreio`. - -#### Modalités d'analyse -Vérifier dans la console du navigateur (Chrome/Firefox) que l'avertissement concernant le préchargement des polices n'apparaît plus lors du chargement de l'application. -Vérifier visuellement que les polices s'affichent correctement (Inter pour le corps, Poppins pour les titres/textes spécifiques). - -### Correction de l'affichage des polices et support des Portals - -#### Problème -Les polices (Inter et Poppins) chargées via `next/font` ne s'affichaient pas correctement : - -1. Avertissement "Ressource préchargée non utilisée". - -2. Polices non appliquées sur les éléments rendus via React Portals (modales, tooltips, etc.). - -3. Polices non appliquées sur le `body` (fallback vers Serif). - -#### Cause - -1. `next/font` génère des noms de classes et de variables CSS uniques (hashés). - -2. Pour que Next.js inclue les définitions `@font-face` dans le build, les classes générées (`inter.variable`, etc.) doivent être utilisées dans le rendu. - -3. Les Portals rendent leur contenu en dehors de l'arbre React principal (souvent directement dans `body`), donc ils n'héritent pas des variables CSS définies sur un conteneur racine (`#__next > div`). - -4. Le fichier `index.scss` ne définissait pas explicitement `font-family` sur le `body`, laissant le navigateur utiliser la police par défaut (Serif) si aucune autre règle ne s'appliquait. - -5. `variables.scss` utilisait `Poppins` comme police de texte par défaut au lieu de `Inter`. - -#### Solution - -##### 1. Chargement des polices (`src/pages/_app.tsx`) - -- Configuration de `Inter` et `Poppins` avec `next/font/google` et l'option `variable`. - -- Application des classes générées (`inter.variable`, `poppins.variable`) sur un `div` conteneur entourant l'application. Cela force Next.js à inclure les polices dans le build. - -- Injection des variables CSS au niveau global (`:root`) via ` - - {/* ... */} - -); - -return ( -
- {/* ... providers ... */} - {getLayout(page)} -
-); -``` - -##### 2. Application des styles (`src/front/index.scss`) - -- Ajout explicite de la famille de police sur le `body` pour assurer l'héritage par défaut. - -```scss -// src/front/index.scss -body { - // ... - font-family: var(--font-inter), sans-serif; -} -``` - -##### 3. Variables SCSS (`src/front/Themes/variables.scss`) - -- Mise à jour de `--font-text-family` pour utiliser `Inter` (via `var(--font-inter)`) au lieu de `Poppins`. - -```scss -// src/front/Themes/variables.scss ---font-text-family: var(--font-inter), sans-serif; -``` - -#### Vérification - -- Vérifier que les requêtes réseau pour les fichiers `.woff2` sont bien effectuées (Code 200). - -- Vérifier que les variables CSS `--font-inter` et `--font-poppins` sont définies sur `:root` et sur le conteneur principal. - -- Vérifier que le texte du corps utilise bien `Inter` et les titres `Poppins`. - -### Suppression des logs info et debug - -**Date :** 04/03/2026 -**Auteur :** Équipe 4NK - -#### Problème -Les logs de niveau `info` et `debug` étaient trop verbeux et pouvaient potentiellement exposer des informations sensibles ou encombrer les sorties de logs en production. - -#### Impacts - -- Réduction du volume de logs. - -- Amélioration de la sécurité (moins de risque de fuite d'informations). - -- Meilleure lisibilité des logs (focus sur `warn` et `error`). - -#### Cause -Configuration par défaut des wrappers de logs qui laissait passer tous les niveaux. - -#### Root Cause -Absence de filtrage ou de désactivation explicite des niveaux `info` et `debug` dans les utilitaires de logging centralisés. - -#### Corrections - -- Désactivation des méthodes `log`, `info` et `debug` dans `lecoffre-back-main/src/common/helpers/LogSanitizer.ts`. - -- Désactivation des méthodes `info` et `debug` dans `lecoffre-front-main/src/front/Services/LoggerService/LoggerServiceClass.ts`. - -- Correction d'une erreur de type (variable inutilisée) dans `lecoffre-front-main/src/front/Api/BaseApiServiceFactory.ts` détectée lors du typecheck. - -#### Modifications - -- `lecoffre-back-main/src/common/helpers/LogSanitizer.ts` : Commentaire du corps des fonctions `log`, `info`, `debug`. - -- `lecoffre-front-main/src/front/Services/LoggerService/LoggerServiceClass.ts` : Commentaire du corps des fonctions `info`, `debug`. - -- `lecoffre-front-main/src/front/Api/BaseApiServiceFactory.ts` : Suppression de la variable `token` inutilisée. - -#### Modalités de déploiement -Déploiement standard via le script de déploiement. Aucune action spécifique requise. - -#### Modalités d'analyse -Vérifier que les logs `info` et `debug` n'apparaissent plus dans les sorties de l'application, mais que les `warn` et `error` sont toujours présents. - -### Fix: Erreur "this.officesRibService.uploadEncryptedRibFromFiles is not a function" - -#### Problème -Lors de l'upload d'un RIB par un notaire, une erreur 500 survenait avec le message `technicalMessage: 'this.officesRibService.uploadEncryptedRibFromFiles is not a function'`. - -#### Cause Racine -Le service `OfficesRibService` était enregistré deux fois dans le conteneur d'injection de dépendances (TypeDI) : - -1. Via le décorateur `@Service()` sur la classe. - -2. Via `Container.set()` avec une factory à la fin du fichier. - -Cette double déclaration créait une ambiguïté. TypeDI injectait probablement une instance créée via le constructeur par défaut (à cause de `@Service()`) au lieu d'utiliser la factory. Comme le constructeur attend une interface `OfficesRibServiceDeps` que TypeDI ne peut pas résoudre automatiquement, l'instance injectée était probablement mal initialisée ou vide. - -#### Correctifs - -1. Suppression du décorateur `@Service()` dans `OfficesRibService.ts` pour forcer l'utilisation de la factory définie par `Container.set()`. - -2. Suppression du décorateur `@Service()` dans `OfficesAnchorService.ts` (même pattern identifié). - -3. Ajout de vérifications de sécurité dans le constructeur de `OfficesService.ts` pour lever une erreur explicite si les services injectés sont invalides ou incomplets. - -#### Impacts - -- `lecoffre-back-main/src/services/notary/OfficesService/OfficesRibService.ts` - -- `lecoffre-back-main/src/services/notary/OfficesService/OfficesAnchorService.ts` - -- `lecoffre-back-main/src/services/notary/OfficesService/OfficesService.ts` - -#### Modalités de déploiement -Déploiement standard du backend. - -#### Modalités d'analyse -Vérifier les logs lors de l'upload de RIB. L'erreur ne doit plus apparaître. -Si une erreur d'injection survient, elle sera désormais explicite grâce aux checks ajoutés dans `OfficesService`. - -### Correction de l'erreur "Response body is already used" - -**Date :** 04/03/2026 -**Auteur :** Équipe 4NK - -#### Problème -L'application frontend rencontrait une erreur critique lors du chargement des textes (`SiteTextsProvider`) ou d'autres appels API : -`Failed to execute 'clone' on 'Response': Response body is already used` -Cette erreur provoquait potentiellement un crash ou un comportement instable, empêchant notamment le chargement correct des polices (si lié au rendu bloqué). - -#### Cause -Dans `BaseApiServiceResponseHandler.handleErrorResponse` (et potentiellement `processResponse`), la méthode `response.clone()` était appelée sans vérifier si le corps de la réponse avait déjà été consommé (`response.bodyUsed`). -Si une requête échouait (ex: 404, 500) et que le corps avait été lu précédemment (par exemple par `response.text()` dans `processResponse` avant de détecter une erreur JSON, ou par une autre logique), l'appel à `clone()` levait une exception non gérée correctement à ce niveau. - -#### Correctifs - -1. **Sécurisation de `handleErrorResponse` (`BaseApiServiceResponseHandler.ts`) :** - - Ajout d'une vérification `if (!response.bodyUsed)` avant de tenter `response.clone()`. - - Gestion du cas où le corps est déjà utilisé : on ne tente pas de le relire, on renvoie un message d'erreur générique ("Response body was already consumed") ou on utilise le texte extrait précédemment si disponible (via `responseCopy`). - -2. **Sécurisation de `processResponse` (`responseHelpers.ts`) :** - - Ajout d'une vérification similaire avant `response.clone()` pour le parsing JSON. - - Passage explicite de `responseClone` (nullable) à `parseJsonResponse`. - -#### Impacts - -- Disparition de l'erreur "Response body is already used". - -- Meilleure résilience de l'application face aux erreurs API. - -- Les erreurs API réelles (404, 500) seront désormais correctement loggées et traitées au lieu d'être masquées par un crash technique. - -#### Modalités de déploiement -Déploiement standard de l'application frontend `lecoffreio`. - -#### Modalités d'analyse - -- Vérifier dans la console que l'erreur "Response body is already used" n'apparaît plus. - -- Si des erreurs API surviennent (ex: `SiteTextsApi`), vérifier qu'elles sont loggées avec leur statut et message corrects. - -### Correction du chargement des textes du site et de l'erreur response.clone() - -**Date :** 04/03/2026 -**Auteur :** Équipe 4NK - -#### Problème -L'application frontend rencontrait une erreur critique au démarrage : `Failed to execute 'clone' on 'Response': Response body is already used`. -Cette erreur empêchait le chargement des textes du site (`SiteTexts`), ce qui pouvait entraîner un affichage dégradé (polices non chargées, textes manquants). - -#### Impacts - -- Échec du chargement des `SiteTexts`. - -- Affichage potentiellement cassé de l'application. - -- Logs d'erreurs critiques dans la console. - -#### Cause -Dans `responseHelpers.ts`, la méthode `response.text()` était appelée pour lire le corps de la réponse, consommant ainsi le flux. Ensuite, `response.clone()` était appelé pour passer une copie à `parseJsonResponse`, ce qui échouait car le corps était déjà consommé. - -#### Root Cause -Mauvaise séquence d'opérations : tentative de clonage d'une réponse après consommation de son corps. - -#### Corrections - -- Modification de `lecoffre-front-main/src/front/Api/helpers/responseHelpers.ts` pour cloner la réponse (`response.clone()`) **avant** de consommer le corps avec `response.text()`. - -#### Modifications - -- `lecoffre-front-main/src/front/Api/helpers/responseHelpers.ts` : Déplacement de l'appel à `response.clone()` avant le bloc `try/catch` de lecture du texte. - -#### Modalités de déploiement -Déploiement standard via le script de déploiement. - -#### Modalités d'analyse -Vérifier dans les logs du navigateur que l'erreur `Failed to execute 'clone' on 'Response'` a disparu et que les appels à `/api/v1/public/site-texts` réussissent. Vérifier que l'application s'affiche correctement avec les bonnes polices. - -### Stockage des e-mails clients (V1 et V2) et lecture seule base V1 - -**Où sont stockés les e-mails des clients :** - -- Table **`contacts`** : colonne **`email`** (type `character varying(255)`, NOT NULL). Les clients (customers) sont liés via `customers.contact_uid` → `contacts.uid`. L’email client est donc **`contacts.email`**. -- Les utilisateurs (users) ont aussi un lien `users.contact_uid` → `contacts.uid` et peuvent avoir un champ email propre sur `users`. - -**Vérification en lecture seule sur la base V1 (ex. depuis prod) :** - -- Ne pas se connecter en SSH depuis l’automatisation (règle projet). Exécuter le script **sur** la cible (après connexion manuelle ou via un job que vous lancez). -- Sur le serveur (ex. prod) : depuis `$APP_ROOT` (ex. `/srv/4NK/prod.lecoffreio.4nkweb.com`), lancer : - - `bash deploy/scripts_v2/remote/read-v1-contacts-emails.sh prod "$APP_ROOT"` -- Le script lit la config V1 dans `.secrets//.env..connectDB` (DATABASE_V1_*), se connecte en **lecture seule** à la base V1 et affiche : structure de la table `contacts`, effectifs contacts/customers, statistiques sur les longueurs d’email (aucun email en clair). -- Prérequis sur la cible : `psql`, et le fichier `.env..connectDB` présent dans `.secrets//`. - -## Lien avec Gitea et traitement des issues par un agent - -Le dépôt est hébergé sur Gitea à l’URL **https://git.4nkweb.com/4nk/lecoffre_ng** (VM distante, distincte du serveur « services » 192.168.1.104). Les issues sont visibles sur **https://git.4nkweb.com/4nk/lecoffre_ng/issues**. - -### Comment le projet « branche » sur les issues - -- **Convention actuelle** : le README décrit une branche feature (`feature/nouvelle-fonctionnalite`), push direct (pas de pull request). Il n’existe pas de convention formelle « une branche par issue » ou « nom de branche = numéro d’issue » dans la doc. -- **Lien possible** : pour associer une branche à une issue, on peut utiliser par exemple `issue/` ou `feature/issue--titre-court`. Le numéro d’issue permet de retrouver le ticket dans Gitea et, si besoin, de le commenter ou de le fermer depuis un agent. - -### Arbitrage des pull requests en cours - -- **Workflow** : le code est intégré par **push direct** sur les branches (test, pprod, prod). Les pull requests ouvertes sur Gitea peuvent être **fermées sans merger** : le code pertinent est déjà sur les branches via push direct. Pour nettoyer : sur Gitea (https://git.4nkweb.com/4nk/lecoffre_ng/pulls), fermer chaque PR ouverte en indiquant que le workflow est désormais push direct (pas de merge via PR). - -### Traitement des tickets (issues) depuis un agent - -L’accès **Git** (clone, push) utilise **SSH** (`git@git.4nkweb.com:4nk/lecoffre_ng.git`) ; la clé SSH de la machine donne les droits nécessaires pour pousser des branches. - -L’**API Gitea** (lister les issues, lire une issue, ajouter un commentaire, etc.) est en **HTTPS** et nécessite un **token d’accès** (Personal Access Token), pas la clé SSH. - -**Prérequis sur la machine qui lance l’agent :** - -1. **Token Gitea** : créer un token dans Gitea (Settings → Applications → Generate New Token) avec les scopes nécessaires (ex. `read:issue`, `write:issue` pour commenter). Stocker le token dans une variable d’environnement (ex. `GITEA_TOKEN`) ou dans un fichier non versionné (ex. `.secrets/gitea-token`), jamais dans le dépôt. -2. **Accès réseau** : la machine doit pouvoir joindre `https://git.4nkweb.com` (le Gitea est sur une VM distante). - -**Lister les issues ouvertes (exemple curl) :** - -```bash -curl -s -H "Accept: application/json" \ - -H "Authorization: token ${GITEA_TOKEN}" \ - "https://git.4nkweb.com/api/v1/repos/4nk/lecoffre_ng/issues?state=open&page=1&limit=50" -``` - -**Récupérer une issue par numéro :** - -```bash -curl -s -H "Accept: application/json" \ - -H "Authorization: token ${GITEA_TOKEN}" \ - "https://git.4nkweb.com/api/v1/repos/4nk/lecoffre_ng/issues/" -``` - -**Workflow type pour un agent :** - -1. Récupérer la liste des issues ouvertes (ou filtrer par label/milestone si besoin). -2. Pour chaque issue (ou une issue ciblée) : - - Créer une branche dédiée (ex. `issue/123` ou `feature/issue-123-titre`) depuis la branche de travail (ex. `test`). - - Lancer l’agent adapté selon le type de ticket : **/fix** pour une anomalie, **/evol** pour une évolution (voir `.cursor/agents/fix.md`, `.cursor/agents/evol.md`), en utilisant le titre et la description de l’issue comme consigne. - - Après correction/évolution : commit, push (`/push-by-script`), **ne jamais créer de pull request**. Optionnellement commenter l'issue via l’API Gitea (POST `/repos/4nk/lecoffre_ng/issues//comments`). - -**Référence API Gitea :** (tag « issue » pour les endpoints issues). - -**Scripts et agent dédiés :** le dossier **`gitea-issues/`** à la racine du dépôt contient les scripts (list-open-issues.sh, get-issue.sh, print-issue-prompt.sh, create-branch-for-issue.sh, comment-issue.sh) et l'agent **/gitea-issues-process** (`.cursor/agents/gitea-issues-process.md`) orchestre le traitement des tickets en s'appuyant uniquement sur ces scripts. Voir `gitea-issues/README.md`. diff --git a/projects/lecoffreio/docs/README.md b/projects/lecoffreio/docs/README.md index 82353dc..022460e 100644 --- a/projects/lecoffreio/docs/README.md +++ b/projects/lecoffreio/docs/README.md @@ -1,34 +1,24 @@ -# Documentation technique LeCoffre.io +# Documentation LeCoffre.io (index) -La documentation technique permanente est hébergée sur le **wiki** du dépôt. +**Projet** : lecoffreio +**Wiki** : https://git.4nkweb.com/4nk/lecoffre_ng/wiki +**Correspondance fichier → page wiki** : nom du fichier sans `.md`, `_` → `-`, title-case par segment (ex. `README.md` → Home, `OPERATIONS.md` → Operations). -- **Page d'accueil du wiki** : [Home](https://git.4nkweb.com/4nk/lecoffre_ng/wiki/Home) -- **Index complet du wiki** : https://git.4nkweb.com/4nk/lecoffre_ng/wiki +## Pages disponibles (docs/ racine) -## Correspondance anciens fichiers docs/ → pages wiki +| Fichier | Page wiki | Description | +|---------|-----------|-------------| +| README.md | Home | Index et correspondance (ce fichier). | +| API.md | Api | APIs externes (IdNot, Annuaire, Ancrage, agent IA notaire), contrats et évolution. | +| Operations.md | Operations | Retours prod, parcours, vérifications, scripts d'analyse, modalités d'analyse. | +| Frontend.md | Frontend | Toasters, messages 403, sources de textes front, paramétrage, parcours UI. | +| Code-Standards.md | Code-Standards | Lint, refactors, rôles et droits, conventions. | +| Deployment.md | Deployment | Env, seeds, site-texts, vérifications boot, déploiement. | +| Architecture.md | Architecture | Vue d'ensemble technique et intégrations. | +| compliance-annuaire-idnot-specs.md | Compliance-Annuaire-Idnot-Specs | Conformité API Annuaire V2 et ID.NOT (lookup, PP). | -| Ancien fichier | Page wiki | -|----------------|-----------| -| OPERATIONS.md | [Operations](https://git.4nkweb.com/4nk/lecoffre_ng/wiki/Operations) | -| DEPLOYMENT.md | [Deployment](https://git.4nkweb.com/4nk/lecoffre_ng/wiki/Deployment) | -| API.md | [Api](https://git.4nkweb.com/4nk/lecoffre_ng/wiki/Api) | -| ARCHITECTURE.md | [Architecture](https://git.4nkweb.com/4nk/lecoffre_ng/wiki/Architecture) | -| CODE_STANDARDS.md | [Code-Standards](https://git.4nkweb.com/4nk/lecoffre_ng/wiki/Code-Standards) | -| FRONTEND.md | [Frontend](https://git.4nkweb.com/4nk/lecoffre_ng/wiki/Frontend) | -| SCRIPTS.md | [Scripts](https://git.4nkweb.com/4nk/lecoffre_ng/wiki/Scripts) | -| MIGRATION.md | [Migration](https://git.4nkweb.com/4nk/lecoffre_ng/wiki/Migration) | -| README.md | [Readme](https://git.4nkweb.com/4nk/lecoffre_ng/wiki/Readme) | -| Autres | Voir l’index du wiki | +Contenu des anciens dossiers `docs/features/` et `docs/fixKnowledge/` : ventilé dans les pages ci-dessus (pas de page dédiée FEATURES ni FIXKNOWLEDGE). -Pour mettre à jour le wiki après modification locale : exécuter `./gitea-issues/wiki-migrate-docs.sh` (voir `gitea-issues/README.md`). +## Mise à jour du wiki -## Autres emplacements (hors wiki) - -- **../user_stories/** : User stories, tests, accessibilité -- **../todoFix/** : Tâches en cours -- **../CHANGELOG.md** : Historique des versions -- **../deploy/README.md** : Scripts de déploiement - ---- - -**Propriétaire** : Équipe 4NK | **Projet** : LeCoffre.io +Depuis la racine du dépôt qui contient `docs/` : exécuter le script de migration (si disponible depuis ia_dev) pour pousser `docs/*.md` vers le wiki. Correspondance détaillée : `projects/ia_dev/docs/GITEA_ISSUES_SCRIPTS_AGENTS.md` (section Migration docs/ → wiki). Ne pas committer `docs/` (hors versionnement).