- Add bridge/package-lock.json (npm install) — fix setup-node@v4 cache fail - Bump testcontainers 10.x -> 11.14.0 (fix high CVE undici) - Apply Biome formatter on src/index.ts + src/lib/config.ts - Update doc 19 Bridge API design : * Add 5th mission : sync bidirectionnel Docmost <-> Baserow * Add endpoints /docmost/* write + /sync/* orchestration * Add section MCP server (Phase 3+) with tools/resources/prompts * Add anti-loop strategy (X-Bridge-Origin + idempotence Redis)
692 lines
26 KiB
Markdown
692 lines
26 KiB
Markdown
# Bridge API Design
|
|
|
|
> Specification du **bridge service** : architecture, endpoints, auth, contrats, integration patterns.
|
|
> Service Node TS qui expose Baserow comme nodes Tiptap custom dans Docmost et orchestre les rollups cross-zone.
|
|
> Statut : design doc avant code Phase 2.
|
|
|
|
## 1. Mission du bridge
|
|
|
|
Le bridge est notre **seul code custom**. Il a **5 missions** :
|
|
|
|
1. **Exposer les rows Baserow comme objets typed** au reste de l'ecosysteme (typing strict, validation, cache)
|
|
2. **Recevoir les webhooks Baserow** pour invalider caches et declencher actions (notifications, recalculs cross-zone)
|
|
3. **Servir les Tiptap node-views custom** dans Docmost (mention `@formateur`, embed `[projet]`, etc.) avec donnees fraiches
|
|
4. **Orchestrer les workflows metier** que ni Docmost ni Baserow ne savent faire seuls (validation RG, notifications croisees, capacite Personne unifiee)
|
|
5. **Synchroniser bidirectionnel Docmost ↔ Baserow** — c'est la mission centrale. Le bridge ecrit dans **les deux systemes**, pas juste lecture.
|
|
|
|
### 1.1 Bidirectionnalite — patterns de sync
|
|
|
|
| Pattern | Direction | Trigger | Effet |
|
|
|---------|-----------|---------|-------|
|
|
| **Auto-creation page Docmost** | Baserow → Docmost | Webhook `row.created` sur `projet` | Cree une page Docmost "Projet X" dans la collection `[AGENCE] Projets` avec template (statut, charge, taches embed) |
|
|
| **Auto-creation collection** | Baserow → Docmost | Webhook `row.created` sur `formation` | Cree une collection Docmost `[CFA] Formation X` avec sub-pages par bloc |
|
|
| **Maj metadata page** | Baserow → Docmost | Webhook `row.updated` sur `projet`/`formation` | Update title/icon/cover de la page Docmost liee |
|
|
| **Auto-creation row Baserow** | Docmost → Baserow | Webhook page created (si template specifique) | Page "Compte-rendu reunion" cree → row dans table `comptes_rendus` |
|
|
| **Mention enrichie** | Baserow → Docmost (lecture seule) | User tape `@personne:Pierre` dans editeur | Bridge resoud nom + capacite restante en live |
|
|
| **Bouton action page** | Docmost → Baserow | User clique "Cloturer projet" sur la page Docmost | Bridge update `projet.statut = cloture` dans Baserow |
|
|
|
|
Le bridge **n'a pas d'etat metier propre** (Phase 2). Source of truth = Baserow pour la donnee structurée + Docmost pour le contenu rich. Le bridge est stateless avec cache Redis pour les jointures.
|
|
|
|
### 1.2 Strategie anti-boucle infinie
|
|
|
|
Risque : un webhook Baserow declenche un write Docmost qui declenche un webhook Docmost qui re-declenche un write Baserow → **boucle**.
|
|
|
|
Mitigations :
|
|
- Header `X-Bridge-Origin: bridge` sur les writes du bridge (les webhooks ignorent les rows ayant ce flag)
|
|
- Idempotence par event_id en Redis (TTL 24h)
|
|
- Rate limit cote bridge sur les sync auto (max 1 sync identique / 5 min)
|
|
- Logging exhaustif pour debug
|
|
|
|
## 2. Tech stack
|
|
|
|
| Composant | Choix | Justification |
|
|
|-----------|-------|---------------|
|
|
| Runtime | Node 22 LTS | Stable, ecosysteme TS mature |
|
|
| Framework HTTP | Hono | Leger, performant, TypeScript-first, edge-ready si futur |
|
|
| Validation | Zod | Schemas TS-typed, runtime validation |
|
|
| HTTP client | ofetch | Wrapper fetch avec retry, timeout, JSON typing |
|
|
| Cache | Redis 7 | Partage avec Docmost ou dedie (decision Phase 2) |
|
|
| Tests | Vitest + testcontainers | Cf doc 16 |
|
|
| Logger | Pino | Structured JSON logs, perf |
|
|
| Config | dotenv + zod | `.env` parse + valide au boot |
|
|
| Build | TypeScript native + bundling esbuild | Pas Webpack, simple |
|
|
| Deploy | Docker image multi-stage | Image < 100 Mo |
|
|
|
|
## 3. Architecture interne
|
|
|
|
```mermaid
|
|
flowchart TB
|
|
subgraph "Bridge service (Hono)"
|
|
Routes[Routes layer<br/>endpoints REST + webhooks]
|
|
Middleware[Middleware<br/>auth, logging, rate-limit, error]
|
|
Services[Services layer<br/>PersonneService, ProjetService, etc.]
|
|
Adapters[Adapters layer<br/>BaserowClient, DocmostClient, RedisCache]
|
|
Domain[Domain layer<br/>Personne, Module, Tache classes pures]
|
|
end
|
|
|
|
Routes --> Middleware
|
|
Middleware --> Services
|
|
Services --> Domain
|
|
Services --> Adapters
|
|
|
|
Adapters -->|HTTP| Baserow[(Baserow API)]
|
|
Adapters -->|HTTP| Docmost[(Docmost API)]
|
|
Adapters -->|TCP| Redis[(Redis)]
|
|
```
|
|
|
|
Layers :
|
|
- **Routes** : declaration endpoints + Zod schemas + delegation services
|
|
- **Middleware** : transverse (auth, logs, rate-limiting, error handling)
|
|
- **Services** : logique metier orchestree (Use Cases level)
|
|
- **Domain** : classes pures (Personne, Module, Attribution... cf doc 12)
|
|
- **Adapters** : isolation IO (Baserow API, Docmost API, Redis)
|
|
|
|
## 4. Conventions API REST
|
|
|
|
### 4.1 Style
|
|
|
|
- **REST-ish** : endpoints orientes resources, verbes HTTP standards (GET, POST, PUT, PATCH, DELETE)
|
|
- **JSON** uniquement (request + response)
|
|
- **Response shape standard** :
|
|
```typescript
|
|
// Success
|
|
{ "data": <payload>, "meta"?: { ... } }
|
|
|
|
// Error
|
|
{ "error": { "code": "ERR_CODE", "message": "Human readable", "details"?: {...} } }
|
|
```
|
|
|
|
### 4.2 Naming
|
|
|
|
- Plural noms : `/personnes`, `/projets`, `/attributions`
|
|
- IDs en path : `/personnes/:id`
|
|
- Sub-resources : `/projets/:id/taches`
|
|
- Actions : verb-style en POST si non-CRUD : `/attributions/:id/cloturer`
|
|
|
|
### 4.3 Versioning
|
|
|
|
- Prefix `/api/v1/` sur toutes les routes
|
|
- Breaking change → nouvelle version `/api/v2/` (en parallele pendant transition)
|
|
- Deprecations annoncees minimum 3 mois avant retrait
|
|
|
|
### 4.4 Pagination, filtre, tri (pour les list endpoints)
|
|
|
|
```
|
|
GET /api/v1/personnes?
|
|
page=1& # default 1
|
|
per_page=50& # default 50, max 200
|
|
sort=nom& # default id desc
|
|
filter[role]=formateur&
|
|
filter[statut]=actif
|
|
```
|
|
|
|
Response :
|
|
```json
|
|
{
|
|
"data": [...],
|
|
"meta": {
|
|
"page": 1,
|
|
"per_page": 50,
|
|
"total": 127,
|
|
"total_pages": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
## 5. Authentification
|
|
|
|
### 5.1 Strategies
|
|
|
|
| Type | Usage | Header |
|
|
|------|-------|--------|
|
|
| **API Token longue duree** | Service-to-service (Docmost ↔ Bridge, Cron ↔ Bridge) | `Authorization: Bearer brg_<token>` |
|
|
| **JWT court** (Phase 3+) | User authentifie via Docmost SSO | `Authorization: Bearer <jwt>` |
|
|
| **Webhook signature** | Verification webhook Baserow | `X-Baserow-Signature: <hmac-sha256>` |
|
|
|
|
### 5.2 Generation tokens
|
|
|
|
API tokens generes via CLI bridge :
|
|
```bash
|
|
npm run --prefix bridge token:create -- --name "docmost-prod" --scopes "read:* write:attributions"
|
|
# → "brg_a1b2c3d4..." stocke en .env.prod cote Docmost
|
|
```
|
|
|
|
Tokens stockes en clair dans une table `api_tokens` Postgres (Phase 3+) ou en memoire au boot via `.env` (Phase 2 simple).
|
|
|
|
### 5.3 Scopes
|
|
|
|
| Scope | Permissions |
|
|
|-------|-------------|
|
|
| `read:personnes` | GET /personnes/* |
|
|
| `read:projets` | GET /projets/* |
|
|
| `write:attributions` | POST/PATCH /attributions |
|
|
| `write:interventions` | POST/PATCH /interventions |
|
|
| `webhook:baserow` | POST /webhooks/baserow/* |
|
|
| `admin:*` | Tout (Corentin/Yan tokens) |
|
|
|
|
## 6. Endpoints REST
|
|
|
|
### 6.1 Personnes
|
|
|
|
| Method | Path | Scope | Description |
|
|
|--------|------|-------|-------------|
|
|
| GET | `/api/v1/personnes` | `read:personnes` | Liste paginee, filtrable par role/statut |
|
|
| GET | `/api/v1/personnes/:id` | `read:personnes` | Fiche detail avec heures restantes (formation + agence + total) |
|
|
| GET | `/api/v1/personnes/:id/attributions` | `read:personnes` | Attributions actives + historiques |
|
|
| GET | `/api/v1/personnes/:id/interventions` | `read:personnes` | Interventions sur taches (paginees) |
|
|
| GET | `/api/v1/personnes/:id/dashboard` | `read:personnes` | Vue 360 : capacite, attributions, interventions, projets en cours |
|
|
|
|
### 6.2 Formations / Blocs / Modules
|
|
|
|
| Method | Path | Scope | Description |
|
|
|--------|------|-------|-------------|
|
|
| GET | `/api/v1/formations` | `read:formations` | Liste paginee |
|
|
| GET | `/api/v1/formations/:id` | `read:formations` | Detail avec blocs/modules + rollups |
|
|
| GET | `/api/v1/blocs/:id` | `read:formations` | Detail bloc + modules |
|
|
| GET | `/api/v1/modules/:id` | `read:formations` | Detail module + attributions actives |
|
|
| POST | `/api/v1/modules/:id/attribuer` | `write:attributions` | Cree une attribution avec validation RG |
|
|
|
|
### 6.3 Attributions
|
|
|
|
| Method | Path | Scope | Description |
|
|
|--------|------|-------|-------------|
|
|
| GET | `/api/v1/attributions/:id` | `read:attributions` | Detail |
|
|
| PATCH | `/api/v1/attributions/:id/heures-realisees` | `write:attributions` | Saisir heures realisees (UC-13) |
|
|
| POST | `/api/v1/attributions/:id/cloturer` | `write:attributions` | Statut → realise |
|
|
| POST | `/api/v1/attributions/:id/annuler` | `write:attributions` | Statut → annule (justification requise) |
|
|
|
|
### 6.4 Clients / Projets / Taches
|
|
|
|
| Method | Path | Scope | Description |
|
|
|--------|------|-------|-------------|
|
|
| GET | `/api/v1/clients` | `read:projets` | Liste |
|
|
| GET | `/api/v1/clients/:id` | `read:projets` | Detail + projets |
|
|
| GET | `/api/v1/projets` | `read:projets` | Liste filtrable par statut/client |
|
|
| GET | `/api/v1/projets/:id` | `read:projets` | Detail + taches + heures realisees rollup |
|
|
| GET | `/api/v1/projets/:id/timeline` | `read:projets` | Vue chronologique interventions |
|
|
| GET | `/api/v1/taches/:id` | `read:projets` | Detail + interventions |
|
|
|
|
### 6.5 Interventions
|
|
|
|
| Method | Path | Scope | Description |
|
|
|--------|------|-------|-------------|
|
|
| POST | `/api/v1/interventions` | `write:interventions` | Saisir intervention (UCA-07) |
|
|
| PATCH | `/api/v1/interventions/:id` | `write:interventions` | Edit (heures, notes) |
|
|
| POST | `/api/v1/interventions/:id/annuler` | `write:interventions` | Annulation |
|
|
|
|
### 6.6 Rapports
|
|
|
|
| Method | Path | Scope | Description |
|
|
|--------|------|-------|-------------|
|
|
| GET | `/api/v1/rapports/formation/:id?format=pdf` | `read:formations` | PDF rapport formation |
|
|
| GET | `/api/v1/rapports/personne/:id?format=pdf` | `read:personnes` | PDF rapport personne (heures + attributions) |
|
|
| GET | `/api/v1/rapports/projet/:id?format=pdf` | `read:projets` | PDF rapport projet |
|
|
|
|
### 6.7 Docmost — write endpoints (sync bidirectionnel)
|
|
|
|
| Method | Path | Scope | Description |
|
|
|--------|------|-------|-------------|
|
|
| POST | `/api/v1/docmost/pages` | `write:docmost` | Cree une page Docmost (avec template, parent, collection) |
|
|
| PATCH | `/api/v1/docmost/pages/:id` | `write:docmost` | Update page (title, icon, cover, content partiel) |
|
|
| POST | `/api/v1/docmost/collections` | `write:docmost` | Cree une collection Docmost |
|
|
| POST | `/api/v1/docmost/share-links` | `write:docmost` | Genere un lien de partage (avec password/expiration) |
|
|
|
|
### 6.8 Sync bidirectionnel — endpoints orchestration
|
|
|
|
| Method | Path | Scope | Description |
|
|
|--------|------|-------|-------------|
|
|
| POST | `/api/v1/sync/projet/:id` | `admin:sync` | Force la sync d'un projet Baserow → page Docmost (utile pour reconciliation) |
|
|
| POST | `/api/v1/sync/formation/:id` | `admin:sync` | Force la sync d'une formation → collection Docmost |
|
|
| POST | `/api/v1/sync/all` | `admin:sync` | Sync complete batch (a executer manuellement, lent) |
|
|
| GET | `/api/v1/sync/status` | `admin:sync` | Statut dernier sync, queue, erreurs |
|
|
|
|
### 6.9 Health & metrics
|
|
|
|
| Method | Path | Scope | Description |
|
|
|--------|------|-------|-------------|
|
|
| GET | `/api/health` | (none) | Healthcheck (200 si OK, 503 si degraded) |
|
|
| GET | `/api/ready` | (none) | Readiness (Baserow + Docmost + Redis joignables) |
|
|
| GET | `/api/metrics` | `admin:*` | Prometheus metrics format |
|
|
|
|
## 7. Webhooks Baserow
|
|
|
|
Baserow envoie des webhooks sur les changements de rows. Bridge traite et reagit.
|
|
|
|
### 7.1 Endpoints webhook
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| POST | `/api/webhooks/baserow/attribution-changed` | Row created/updated/deleted sur table `attribution` |
|
|
| POST | `/api/webhooks/baserow/intervention-changed` | Idem `intervention` |
|
|
| POST | `/api/webhooks/baserow/module-status-changed` | Quand `module_statut` change |
|
|
| POST | `/api/webhooks/baserow/projet-status-changed` | Quand `projet_statut` change |
|
|
|
|
### 7.2 Format payload Baserow (extrait)
|
|
|
|
```json
|
|
{
|
|
"table_id": 123,
|
|
"database_id": 1,
|
|
"event_type": "rows.created",
|
|
"items": [
|
|
{ "id": 42, "field_X": "...", ... }
|
|
]
|
|
}
|
|
```
|
|
|
|
### 7.3 Verification signature
|
|
|
|
```typescript
|
|
// middleware/webhook-baserow.ts
|
|
const expected = hmacSha256(rawBody, env.BASEROW_WEBHOOK_SECRET);
|
|
const provided = req.headers['X-Baserow-Signature'];
|
|
if (!constantTimeEqual(expected, provided)) {
|
|
throw new Error('Invalid signature');
|
|
}
|
|
```
|
|
|
|
### 7.4 Actions par webhook
|
|
|
|
| Webhook | Actions |
|
|
|---------|---------|
|
|
| attribution-changed | Invalide cache Redis personne/module concernes ; si statut change vers `realise` ou `annule` → recalcul rollup module ; notif email formateur si nouvelle attribution |
|
|
| intervention-changed | Invalide cache personne/tache ; check capacite Personne, alerte si depassement |
|
|
| module-status-changed | Si tous modules d'une formation `realise` → declenche cloture formation auto (OP-07) |
|
|
| projet-status-changed | Si `livre` → notif admin pour facturation |
|
|
|
|
### 7.5 Idempotence
|
|
|
|
Chaque webhook a un `event_id`. Le bridge stocke en Redis avec TTL 24h les events deja traites :
|
|
```typescript
|
|
const seen = await redis.get(`webhook:event:${event_id}`);
|
|
if (seen) return; // skip duplicate
|
|
await redis.set(`webhook:event:${event_id}`, '1', 'EX', 86400);
|
|
```
|
|
|
|
## 8. Cache strategy
|
|
|
|
### 8.1 Cles Redis
|
|
|
|
| Cle | TTL | Contenu |
|
|
|-----|-----|---------|
|
|
| `bridge:personne:<id>` | 5 min | JSON full Personne (avec rollups calcules) |
|
|
| `bridge:projet:<id>` | 5 min | JSON full Projet |
|
|
| `bridge:formation:<id>` | 10 min | JSON Formation (change moins souvent) |
|
|
| `bridge:webhook:event:<event_id>` | 24h | Idempotence webhook |
|
|
| `bridge:rate-limit:<token>:<endpoint>` | 1 min | Rate limit counter |
|
|
|
|
### 8.2 Invalidation
|
|
|
|
- **Webhook Baserow** invalide les cles concernees
|
|
- **TTL** comme fallback (5-10 min)
|
|
- Pattern : `cache.invalidate('bridge:personne:42')` apres write
|
|
|
|
### 8.3 Cache aside pattern
|
|
|
|
```typescript
|
|
async getPersonne(id: number): Promise<Personne> {
|
|
const cached = await cache.get(`bridge:personne:${id}`);
|
|
if (cached) return Personne.fromJSON(cached);
|
|
|
|
const fresh = await baserow.fetchPersonne(id);
|
|
await cache.set(`bridge:personne:${id}`, fresh.toJSON(), 'EX', 300);
|
|
return fresh;
|
|
}
|
|
```
|
|
|
|
## 9. Rate limiting
|
|
|
|
Par token + endpoint (sliding window 1 min) :
|
|
|
|
| Endpoint | Limite |
|
|
|----------|--------|
|
|
| Read endpoints | 600 req/min |
|
|
| Write endpoints | 60 req/min |
|
|
| Webhooks | 1000 req/min |
|
|
| Rapports PDF | 10 req/min |
|
|
|
|
Reponse 429 si depasse :
|
|
```json
|
|
{ "error": { "code": "RATE_LIMITED", "message": "Too many requests", "retry_after": 30 } }
|
|
```
|
|
|
|
## 10. Error handling
|
|
|
|
### 10.1 Codes d'erreur
|
|
|
|
| Code | HTTP | Description |
|
|
|------|------|-------------|
|
|
| `AUTH_REQUIRED` | 401 | Token absent |
|
|
| `AUTH_INVALID` | 401 | Token invalide |
|
|
| `FORBIDDEN_SCOPE` | 403 | Token n'a pas le scope requis |
|
|
| `NOT_FOUND` | 404 | Ressource inexistante |
|
|
| `VALIDATION_ERROR` | 400 | Body invalide (Zod errors) |
|
|
| `RG_VIOLATION` | 422 | Regle de gestion violee (ex RG-01 depassement heures module) |
|
|
| `CONFLICT` | 409 | Etat incoherent (ex annuler une attribution deja annulee) |
|
|
| `RATE_LIMITED` | 429 | Trop de requetes |
|
|
| `BASEROW_UNAVAILABLE` | 502 | Baserow API down |
|
|
| `INTERNAL` | 500 | Bug bridge |
|
|
|
|
### 10.2 Format
|
|
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": "RG_VIOLATION",
|
|
"message": "Heures attribuees depassent la capacite du module",
|
|
"details": {
|
|
"rule": "RG-01",
|
|
"module_id": 42,
|
|
"heures_module": 30,
|
|
"heures_deja_attribuees": 28,
|
|
"heures_demandees": 5
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 11. Integration patterns Docmost
|
|
|
|
### 11.1 Tiptap node-view custom
|
|
|
|
Phase 2+ : on developpe (ou on commande a un freelance) des extensions Tiptap pour Docmost qui appellent le bridge.
|
|
|
|
Patterns :
|
|
- **Mention** `@formateur:Pierre` → render carte avec capacite restante via GET /personnes/:id (slug → resolution)
|
|
- **Embed** `[projet:projet-alpha]` → render card avec status + heures realisees
|
|
- **Database view** `[modules-a-attribuer]` → embed kanban filtré
|
|
|
|
Ces nodes appellent le bridge via fetch + cache cote client (5 min).
|
|
|
|
### 11.2 Routes pages full
|
|
|
|
Phase 2+ : le bridge sert aussi des **pages full** /personne/:id, /projet/:id, /formation/:id qui ressemblent a des pages Docmost (header layout + content).
|
|
|
|
Implementation : Hono cote backend rend HTML avec layout Docmost mimique + content custom. Le user clique sur une mention dans Docmost, ouvre la page bridge, voit le meme look.
|
|
|
|
Ou en Phase 3 : on contribue au repo Docmost upstream pour ajouter ces nodes nativement.
|
|
|
|
## 12. Sample request/response
|
|
|
|
### Saisir heures realisees
|
|
|
|
```http
|
|
PATCH /api/v1/attributions/42/heures-realisees HTTP/1.1
|
|
Host: bridge.acadenice.fr
|
|
Authorization: Bearer brg_xxxxx
|
|
Content-Type: application/json
|
|
|
|
```
|
|
|
|
Response 200 :
|
|
```json
|
|
{
|
|
"data": {
|
|
"attribution_id": 42,
|
|
"heures_attribuees": 10,
|
|
"heures_realisees": 3.5,
|
|
"statut": "en_cours",
|
|
"module": {
|
|
"module_id": 17,
|
|
"module_nom": "JS Fondamentaux",
|
|
"heures_realisees_total": 3.5
|
|
},
|
|
"personne": {
|
|
"personne_id": 5,
|
|
"nom_prenom": "Pierre Dupont",
|
|
"heures_attribuees_formation": 80,
|
|
"heures_restantes_formation": 670
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Erreur RG violation
|
|
|
|
```http
|
|
POST /api/v1/modules/17/attribuer HTTP/1.1
|
|
Authorization: Bearer brg_xxxxx
|
|
Content-Type: application/json
|
|
|
|
```
|
|
|
|
Response 422 :
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": "RG_VIOLATION",
|
|
"message": "Heures attribuees depassent la capacite du module",
|
|
"details": {
|
|
"rule": "RG-01",
|
|
"module_id": 17,
|
|
"heures_module": 30,
|
|
"heures_deja_attribuees": 0,
|
|
"heures_demandees": 50
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 13. Observabilite
|
|
|
|
### 13.1 Logs (Pino structured JSON)
|
|
|
|
Niveau `info` par defaut, `debug` en local. Format :
|
|
```json
|
|
{
|
|
"level": "info",
|
|
"time": "2026-05-07T10:23:45.123Z",
|
|
"msg": "PATCH /api/v1/attributions/42/heures-realisees",
|
|
"method": "PATCH",
|
|
"path": "/api/v1/attributions/42/heures-realisees",
|
|
"status": 200,
|
|
"duration_ms": 142,
|
|
"user_token_id": "tok_abc123",
|
|
"request_id": "req_xyz789"
|
|
}
|
|
```
|
|
|
|
Champs sensibles redactes : pas de body en logs, pas de token en clair.
|
|
|
|
### 13.2 Metrics Prometheus
|
|
|
|
Exposees sur `/api/metrics` :
|
|
- `http_requests_total{method,path,status}` counter
|
|
- `http_request_duration_seconds{method,path}` histogram
|
|
- `baserow_api_calls_total{endpoint,status}` counter
|
|
- `cache_hits_total` / `cache_misses_total`
|
|
- `webhook_events_processed_total{type,outcome}`
|
|
|
|
## 13bis. MCP server (Phase 3+)
|
|
|
|
> Idee retenue par Corentin le 2026-05-07 : exposer le bridge aussi comme **MCP server** pour permettre a Claude (et autres agents IA) d'interagir avec formation-hub.
|
|
|
|
### 13bis.1 Vision
|
|
|
|
Le bridge expose **deux protocoles** :
|
|
- **REST** classique pour Docmost / Tiptap nodes / scripts (ce qui est specifie dans les sections 4-12)
|
|
- **MCP** (Model Context Protocol) pour les agents IA — meme logique metier, transport different
|
|
|
|
Avec MCP, Claude peut :
|
|
- Querier "donne-moi la capacite restante de Pierre Dupont"
|
|
- Creer "attribue le module JS Fondamentaux a Pierre"
|
|
- Generer rapports "fais-moi un rapport de la formation Dev Fullstack"
|
|
- Detecter problemes "trouve les modules sans formateur attribue dans formation X"
|
|
|
|
Cas d'usage concret : un assistant IA pour l'admin pedagogique qui aide a equilibrer les charges, detecter les depassements, suggerer des reattributions.
|
|
|
|
### 13bis.2 Implementation
|
|
|
|
**Stack** :
|
|
- `@modelcontextprotocol/sdk` (Node) — SDK officiel Anthropic
|
|
- Le serveur MCP wrapping les services existants du bridge
|
|
- Transport : stdio (pour Claude Code) OU HTTP/SSE (pour autres clients)
|
|
|
|
**Co-localisation** :
|
|
- Meme codebase `bridge/`
|
|
- Module `bridge/src/mcp/` qui re-utilise les services REST
|
|
- Build separe : `npm run mcp:start` (binaire MCP standalone)
|
|
- Ou meme proces : Hono REST + MCP SDK en parallele
|
|
|
|
### 13bis.3 Tools MCP exposes
|
|
|
|
| Tool | Description | Inputs |
|
|
|------|-------------|--------|
|
|
| `formation_hub_get_personne` | Detail personne avec capacite | `personne_id` ou `email` |
|
|
| `formation_hub_search_personnes` | Liste filtree | `roles`, `statut`, `capacite_min` |
|
|
| `formation_hub_get_formation` | Detail formation + blocs + modules | `formation_id` |
|
|
| `formation_hub_get_projet` | Detail projet + taches + interventions | `projet_id` |
|
|
| `formation_hub_create_attribution` | Cree attribution avec validation RG | `module_id`, `personne_id`, `heures`, `date_debut` |
|
|
| `formation_hub_saisir_intervention` | Saisie intervention dev | `tache_id`, `personne_id`, `heures`, `date` |
|
|
| `formation_hub_get_capacite_unifiee` | Vue 360 capacite Personne (formation + agence) | `personne_id` |
|
|
| `formation_hub_search_modules_a_attribuer` | Modules en attente d'attribution | `formation_id?` |
|
|
| `formation_hub_generer_rapport` | Genere rapport PDF | `type` (formation/personne/projet), `id`, `format` |
|
|
| `formation_hub_simulate_attribution` | Dry-run attribution avec impact rollups (sans ecrire) | comme `create_attribution` |
|
|
|
|
### 13bis.4 Resources MCP
|
|
|
|
Resources MCP = donnees exposees en lecture (vs tools qui sont des actions) :
|
|
- `formation-hub://personnes` — liste des personnes
|
|
- `formation-hub://personnes/{id}` — detail personne
|
|
- `formation-hub://formations` — liste formations
|
|
- `formation-hub://projets/active` — projets en cours
|
|
- `formation-hub://capacity-overview` — synthese capacite globale equipe
|
|
|
|
### 13bis.5 Prompts MCP
|
|
|
|
Prompts MCP = templates Claude prets a l'emploi :
|
|
- `equilibrer-charges` : Claude analyse les attributions et propose des rebalancing
|
|
- `detecter-depassements` : Claude scan les capacites et signale les problemes
|
|
- `planifier-formation` : Claude aide a decomposer une formation en blocs/modules
|
|
- `synthese-projet` : Claude genere un rapport status pour un projet client
|
|
|
|
### 13bis.6 Auth MCP
|
|
|
|
- API token bridge (`brg_*` tokens, scope `admin:mcp`)
|
|
- Token configure dans le client MCP (`.claude/settings.json` cote Claude Code)
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"formation-hub": {
|
|
"command": "node",
|
|
"args": ["bridge/dist/mcp-server.js"],
|
|
"env": {
|
|
"BRIDGE_API_TOKEN": "brg_xxxx",
|
|
"BRIDGE_URL": "https://bridge.acadenice.fr"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 13bis.7 Roadmap MCP
|
|
|
|
| Phase | Effort | Livrable |
|
|
|-------|--------|----------|
|
|
| Phase 3.0 (apres bridge REST stable) | 1-2 semaines | MCP server avec 5 tools cles (get_personne, get_formation, capacite_unifiee, search_modules_a_attribuer, simulate_attribution) |
|
|
| Phase 3.1 | 1 semaine | Tools writes (create_attribution, saisir_intervention) |
|
|
| Phase 3.2 | 1 semaine | Resources + prompts |
|
|
| Phase 4 | continu | Integration dans workflows admin (Yan/Corentin utilisent Claude pour piloter formation-hub) |
|
|
|
|
### 13bis.8 Decisions a prendre Phase 3
|
|
|
|
- [ ] Transport MCP : stdio only (Claude Code local) ou HTTP/SSE (multi-client web) ?
|
|
- [ ] Co-localisation MCP + REST dans meme proces ou processus separes ?
|
|
- [ ] MCP expose sur quel hostname ? (probablement pas accessible depuis internet, juste localhost ou VPN interne)
|
|
- [ ] Authoring tools : Claude peut-il creer/modifier des entites ou seulement lire ? Decision selon trust ELO Claude vs human-in-the-loop pattern.
|
|
|
|
## 14. Tests
|
|
|
|
Cf doc 16 plan-de-tests :
|
|
- Unit Vitest 80% coverage minimum sur domain
|
|
- Integration tests avec testcontainers Baserow + Redis
|
|
- E2E playwright sur staging
|
|
|
|
## 15. Roadmap implementation
|
|
|
|
### Phase 2.0 — Bootstrap (semaine 1-2)
|
|
|
|
- [ ] Setup Hono + zod + ofetch + pino
|
|
- [ ] BaserowClient avec tests integration
|
|
- [ ] DocmostClient skeleton
|
|
- [ ] Healthcheck endpoint
|
|
- [ ] Auth middleware basique (API token)
|
|
- [ ] CI/CD complet (cf doc 17)
|
|
- [ ] Deploy staging
|
|
|
|
### Phase 2.1 — Read endpoints (semaine 3-4)
|
|
|
|
- [ ] GET /personnes/:id avec rollups calcules
|
|
- [ ] GET /projets/:id
|
|
- [ ] GET /formations/:id
|
|
- [ ] Cache Redis pattern cache-aside
|
|
- [ ] Tests integration sur les endpoints
|
|
|
|
### Phase 2.2 — Write endpoints + webhooks (semaine 5-7)
|
|
|
|
- [ ] POST /interventions
|
|
- [ ] PATCH /attributions/:id/heures-realisees
|
|
- [ ] Webhooks Baserow handlers
|
|
- [ ] Validation RG-01 a RG-06
|
|
- [ ] Tests integration write
|
|
|
|
### Phase 2.3 — Tiptap nodes (semaine 8-10)
|
|
|
|
- [ ] Premier node Tiptap custom (mention `@formateur`)
|
|
- [ ] Integration Docmost (fork ou plugin)
|
|
- [ ] E2E playwright
|
|
|
|
### Phase 2.4 — Pages full + rapports + sync bidirec (semaine 11-12)
|
|
|
|
- [ ] Routes /personne/:id, /projet/:id en page Docmost-style
|
|
- [ ] Endpoint /rapports/* PDF generation
|
|
- [ ] Endpoints `/docmost/pages` write (auto-creation depuis evenements Baserow)
|
|
- [ ] Endpoints `/sync/*` orchestration manuelle
|
|
- [ ] Strategie anti-boucle infinie (header `X-Bridge-Origin`, idempotence)
|
|
- [ ] Stabilisation, fix bugs, doc utilisateur
|
|
|
|
### Phase 3.0 — MCP server (apres bridge REST stable)
|
|
|
|
- [ ] Setup `@modelcontextprotocol/sdk` Node
|
|
- [ ] 5 tools cles read-only
|
|
- [ ] Auth via API token (scope `admin:mcp`)
|
|
- [ ] Documentation client (Claude Code config)
|
|
|
|
## 16. Decisions a prendre
|
|
|
|
- [ ] **Source of truth tokens** : .env (simple) vs Postgres dedie (rotation a chaud) ? Mon vote : .env Phase 2, Postgres Phase 3
|
|
- [ ] **Cache Redis partage Docmost ou dedie** ? Partage Phase 2 (simple, sa marche), dedie Phase 3 si charge ou conflits
|
|
- [ ] **PDF generation** : Puppeteer (lourd) vs PDFKit (manuel) vs service externe (gotenberg) ? Recommande PDFKit ou gotenberg self-host
|
|
- [ ] **OpenAPI 3 doc auto** : generee depuis Zod schemas ? Lib `@asteasolutions/zod-to-openapi`. A faire Phase 2.1.
|
|
- [ ] **GraphQL au lieu de REST ?** Pas pertinent pour notre scope (peu de endpoints, peu de variation queries). REST est plus simple.
|
|
- [ ] **Multi-tenant** ? Pour l'instant non — Acadenice mono-instance. Si rachat / scaling : ajouter `tenant_id` partout. Pas avant Phase 4.
|
|
|
|
## 17. Decisions techniques notables (deja prises)
|
|
|
|
- **Bidirectionnalite** : confirme par Corentin le 2026-05-07. Bridge ecrit dans Docmost ET Baserow (5 missions au lieu de 4).
|
|
- **MCP server** : retenu pour Phase 3+. Expose les memes capacites bridge via le protocol MCP pour usage par Claude / autres agents IA.
|
|
- **Pas d'etat metier dans le bridge** : reste stateless en Phase 2. Source of truth Baserow + Docmost.
|
|
- **Anti-boucle webhooks** : header `X-Bridge-Origin` + idempotence Redis.
|
|
|
|
## 18. Glossaire
|
|
|
|
| Terme | Definition |
|
|
|-------|------------|
|
|
| Bridge | Service custom qui se sert d'intermediaire entre Docmost (UI) et Baserow (data) |
|
|
| Tiptap node-view | Composant React custom integre dans editeur Tiptap pour rendre un block specifique |
|
|
| Cache aside | Pattern : check cache → if miss, fetch source + populate cache |
|
|
| Idempotence | Une requete repetee a le meme effet qu'une requete unique (anti-doublon) |
|
|
| HMAC signature | Hash crypto pour verifier l'authenticite d'un payload (webhook) |
|
|
| Sliding window rate limit | Compteur sur fenetre glissante (ex: derniere minute) |
|
|
| RG | Regle de Gestion (Merise) |
|
|
| Scope (token) | Permission specifique (read:X, write:Y, admin:*) |
|