2026-03-16 16:52:55 +01:00

1569 lines
58 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 segment projet dans le chemin (ex. `http://192.168.1.173:3020/v1`). Le projet (et lenv) sont identifiés par le token Bearer : lAPI cherche dans tous les projets et tous les envs le fichier `projects/<id>/.secrets/<env>/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 <token>
```
Le token est de la forme **base** + **env** (ex. `nicolecoffreiotest`, `nicolecoffreiopprod`). **env** est le nom denvironnement (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/<id>/.secrets/<env>/ia_token` pour lenv 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>/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: <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: <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** : `<API_KEY>`
- **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-<env>-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-<env>-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 lapplication.
### 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 dinjection (fichier `.secrets/<env>/env-full-<env>-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 lAPI 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 doffice.
- **Réponse** : `{ results: Array<...>, annuaireConfigured: boolean, message?: string }`. `results` : tableau dobjets `{ idNot, email, firstName, lastName, officeName, crpcen, isPrimary, displayName, hasActiveLicense?, phone? }`. Si lannuaire nest 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 lannuaire nest pas configuré : réponse `{ results: [], annuaireConfigured: false, message }` ; le front affiche le message dalerte (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 lapplication. Les recherches de confrères et les résolutions (ex: par email) seffectuent 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 <env>`. Aucune migration spécifique de tables `annu_*` nest requise. Les appels API Annuaire/IdNot nécessitent que les clés correspondantes soient présentes dans `system_configuration` (injection via `.secrets/<env>/env-full-<env>-for-bdd-injection.txt` puis déploiement).
### Sécurité
**Authentification Basic Auth** :
```http
Authorization: Basic <base64(login:password)>
```
**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>/env-full-<env>-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-<env>-for-bdd-injection.txt
bash deploy/scripts_v2/remote/set-settings.sh <env> <domain> <app_root>
```
**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).