# 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 4 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) Le bridge **ne stocke pas d'etat metier** (Phase 2). Source of truth = Baserow. Le bridge est stateless avec cache Redis. ## 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
endpoints REST + webhooks] Middleware[Middleware
auth, logging, rate-limit, error] Services[Services layer
PersonneService, ProjetService, etc.] Adapters[Adapters layer
BaserowClient, DocmostClient, RedisCache] Domain[Domain layer
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": , "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_` | | **JWT court** (Phase 3+) | User authentifie via Docmost SSO | `Authorization: Bearer ` | | **Webhook signature** | Verification webhook Baserow | `X-Baserow-Signature: ` | ### 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 Health & metrics | Method | Path | Scope | Description | |--------|------|-------|-------------| | GET | `/api/health` | (none) | Healthcheck (200 si OK, 503 si degraded) | | GET | `/api/ready` | (none) | Readiness (Baserow + 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:` | 5 min | JSON full Personne (avec rollups calcules) | | `bridge:projet:` | 5 min | JSON full Projet | | `bridge:formation:` | 10 min | JSON Formation (change moins souvent) | | `bridge:webhook:event:` | 24h | Idempotence webhook | | `bridge:rate-limit::` | 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 { 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 { "heures_realisees": 3.5, "comment": "Cours JS du 2026-05-07 OK" } ``` 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 { "personne_id": 5, "heures_attribuees": 50, "date_debut": "2026-09-01" } ``` 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}` ## 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 (semaine 11-12) - [ ] Routes /personne/:id, /projet/:id en page Docmost-style - [ ] Endpoint /rapports/* PDF generation - [ ] Stabilisation, fix bugs, doc utilisateur ## 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. 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:*) |