# APIs Externes - Documentation Complète **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) ## 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 slug** (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. ### Authentification **Contrat obligatoire** : tous les appels à l'API agent doivent envoyer le header : ```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 : - **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) 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. --- ## Consolidation des évolutions backend récentes Cette section consolide les éléments issus des documents de travail intégrés intégrés dans la documentation API pérenne. ### Intégration Genapi / iNot - 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 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).