From 9eaac0c7efe9e2825fc200bf8c6dec67881c666b Mon Sep 17 00:00:00 2001 From: ordinarthur <@arthurbarre.js@gmail.com> Date: Sat, 9 May 2026 19:13:44 +0200 Subject: [PATCH] =?UTF-8?q?docs(audit-2/3):=20aligner=20doc=20tech=20sur?= =?UTF-8?q?=20le=20code=20livr=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit cross-doc/code, batch tech : architecture, backend, frontend, dev-setup. Corrige les claims qui pouvaient induire un dev en erreur (noms de services K3s, hostnames Traefik, Tuyau, queue wrapper, seeders, env vars, polices). architecture.md - Composants : status « À écrire » → « ✅ Déployé » (apps/web, apps/api, packages/shared) ; ajout Redis Deployment K3s ; OCR = Mistral choisi ; mail = Resend (sortant) + OVH MX (entrant) validés - §7.5 Pods K3s : noms réels (rubis-api / rubis-web / rubis-landing / rubis-redis, pas de *-svc) ; pas d'IngressRoute api.rubis.pro (l'API est servie via app.rubis.pro/api/* proxifié par nginx du pod web) ; PG/MinIO en URL directe dans la ConfigMap, pas de Service ExternalName - §10 Décisions en attente : ADRs 019-024 mises à jour (tranchées / obsolètes), suppression du wording « à venir » pour les choix déjà figés dans le code backend.md - Note de cohérence en tête : pointe vers start/routes.ts comme source de vérité de la surface API (~80 routes — Stripe, Demo, AI, Microsoft SSO, admin blog, posts publics, KPIs timeseries) que cette doc n'inventorie pas exhaustivement - §1 Vue d'ensemble : Tuyau marqué « non utilisé en pratique » (présent en deps mais zéro import côté SPA), partage de types via packages/shared. OCR Mistral choisi. Mail Resend choisi. BullMQ direct (workers inline pod API). Sentry ADR-024. - §2 Stack : queue = BullMQ direct (pas @rlanz/bull-queue, qui n'est pas installé) ; type-sharing = packages/shared - §2 Dépendances : remplacé la todo-list pré-livraison par la liste réelle des packages dans apps/api/package.json - §3 Repo layout : `database/factories/` (dossier) → `factories.ts` (mono-fichier) ; `database/seeders/{default_plans,demo_data}` → inexistants, services à la place - §13.2 Jobs : ProcessOcrJob + RecomputeKpisJob retirés (n'existent pas — OCR synchrone via services/import_batch.ts, KPIs calculés on-the-fly). Liste des jobs réels : send_relance, send_checkin, send_payment_thanks - env vars : MINIO_* → S3_* (cf. .env.example + manifest k3s) ; bucket prod = rubis-prod-invoices frontend.md - Note de cohérence en tête : Tuyau pas utilisé, tokens dans packages/ui (pas inline), polices @fontsource-variable (pas Google Fonts via ) - §1 Vue d'ensemble : client API = fetch minimaliste dans apps/web/src/lib/api.ts ; périmètre livré = ~15 routes _app/ - §3 Polices : section Google Fonts → @fontsource-variable (avec note preload woff2 critique sur la landing Astro) - §4 Routes : arbo `_onboarding/` (faux) → `onboarding/` (réel, segment URL) + ajout admin.blog*, clients_.$id, insights, parametres_.abonnement, plans_.nouveau, factures_.import - §6 Tuyau : section marquée « historique, non utilisé en V1 » avec note explicative en tête - §10 env vars : VITE_API_URL=https://api.rubis.pro → vide (proxifié same-origin par nginx) + ajout VITE_USE_MOCKS, VITE_SENTRY_DSN_WEB, VITE_APP_VERSION dev-setup.md - Mailhog → Mailpit (3 occurrences) — c'est ce qui tourne dans docker-compose.dev.yml Co-Authored-By: Claude Opus 4.7 --- docs/tech/architecture.md | 61 ++++++++++++---------- docs/tech/backend.md | 107 ++++++++++++++++++++++---------------- docs/tech/dev-setup.md | 6 +-- docs/tech/frontend.md | 88 +++++++++++++++++++------------ 4 files changed, 152 insertions(+), 110 deletions(-) diff --git a/docs/tech/architecture.md b/docs/tech/architecture.md index 5f2cf8e..759ce15 100644 --- a/docs/tech/architecture.md +++ b/docs/tech/architecture.md @@ -44,14 +44,16 @@ Ce document est la source de vérité technique. Quand le code et ce fichier div | Composant | Rôle | Hosting | Status | |---|---|---|---| | `apps/landing` | Landing + blog publics (Astro 6 SSR) | Pod Node K3s (`rubis-landing:4321`) | ✅ Déployé | -| `apps/web` | SPA React (SaaS) | nginx pod K3s (build statique) | À écrire | -| `apps/api` | API REST AdonisJS — logique métier, jobs, email | Pod Node K3s (`rubis-api:3333`) | À écrire | -| `packages/shared` | Types TS, schemas Zod, constantes communes | workspace local | À écrire | -| `packages/ui` | Design system partagé (tokens Tailwind + composants TSX) | workspace local | ✅ | -| PostgreSQL | Base de données métier | LXC Proxmox existant | ✅ En place | -| MinIO | Stockage PDF + pièces jointes (S3-compat) | LXC Proxmox existant | ✅ En place | -| Provider OCR | Extraction texte des factures | Externe (HTTPS) | ADR-020 à venir | -| Provider Email | Envoi outbound (relances + check-in) | Externe (HTTPS) | ADR-021 à venir | +| `apps/web` | SPA React + TanStack Router/Query (SaaS) | nginx pod K3s (build statique + proxy `/api/*` → rubis-api) | ✅ Déployé | +| `apps/api` | API REST AdonisJS v7 — logique métier, jobs BullMQ inline, email, billing Stripe | Pod Node K3s (`rubis-api:3333`) | ✅ Déployé | +| `packages/shared` | Types TS, schemas Zod, constantes (statuts, tons, plans, etc.) | workspace local | ✅ | +| `packages/ui` | Design system partagé (tokens Tailwind v4 + composants TSX) | workspace local | ✅ | +| PostgreSQL | Base de données métier | LXC Proxmox existant (`10.10.10.3`) | ✅ En place | +| MinIO | Stockage PDF + uploads blog (S3-compat, bucket `rubis-prod-invoices`) | namespace K3s `minio` | ✅ En place | +| Redis | Backend BullMQ (queues: relances, checkins, payment-thanks) | Deployment K3s `rubis-redis` + PVC | ✅ Déployé | +| Provider OCR | Extraction texte des factures | Mistral (`OCR_PROVIDER=mistral`) | ✅ Choisi | +| Provider Email outbound | Resend (`relances@rubis.pro`, sub-domaine `send.rubis.pro`) | Externe (HTTPS) | ✅ En place | +| Provider Email inbound | OVH MX (`contact@rubis.pro`, `dev@rubis.pro`) | Externe (HTTPS/IMAP) | ✅ En place | --- @@ -487,21 +489,25 @@ SPA retry l'appel original avec nouveau token ### Pods K3s ```yaml -# Namespace: rubis -- Deployment: rubis-api # AdonisJS Node, port 3333 -- Deployment: rubis-web # nginx, sert le bundle Vite, port 80 -- Deployment: rubis-landing # déjà existant -- Service: rubis-api-svc # ClusterIP -- Service: rubis-web-svc # ClusterIP -- Service: postgres-external # ExternalName → IP du LXC postgres -- Service: minio-external # ExternalName → IP du LXC minio -- Secret: rubis-config # DB credentials, MinIO credentials, OCR API key, mail API key -- IngressRoute (Traefik) : - api.rubis.pro → rubis-api-svc:3333 - app.rubis.pro → rubis-web-svc:80 - rubis.pro → rubis-landing-svc:80 +# Namespace: rubis (cf. k3s/app/*.yml) +- Deployment: rubis-api # AdonisJS Node, port 3333 (workers BullMQ inline, pas de pod séparé) +- Deployment: rubis-web # nginx, sert le bundle Vite + proxy /api → rubis-api:3333, port 80 +- Deployment: rubis-landing # Astro 6 SSR Node, port 4321 +- Deployment: rubis-redis # Redis 7 + PVC pour BullMQ +- Service: rubis-api # NodePort 30100 → 3333 +- Service: rubis-web # NodePort 30110 → 80 +- Service: rubis-landing # NodePort 30111 → 4321 +- Service: rubis-redis # ClusterIP 6379 +- ConfigMap: rubis-api-config + rubis-landing-config + rubis-web-config +- Secret: rubis-api-secrets # DB credentials, S3 credentials, OCR/Mistral API key, Resend key, Stripe keys +- Routing Traefik (config dynamique sur la VM gateway, repo proxmox) : + rubis.pro → 10.10.10.5:30111 (rubis-landing) + app.rubis.pro/* → 10.10.10.5:30110 (rubis-web — qui proxie /api/* vers rubis-api en interne K3s) + # Note : pas de hostname `api.rubis.pro` exposé. L'API est servie via app.rubis.pro/api/*. ``` +**Ressources externes (LXC Proxmox, hors K3s)** : PostgreSQL (`PG_HOST=10.10.10.3` en clair dans `rubis-api-config`), MinIO (DNS `minio.minio.svc.cluster.local:9000` via le namespace MinIO du même cluster, bucket prod = `rubis-prod-invoices`). Pas de `Service ExternalName` créé, les URLs sont posées directement dans la ConfigMap. + ### Pipeline CI Gitea ``` @@ -554,13 +560,14 @@ healthchecks readinessProbe → service public À trancher avant fin V1, par ordre de priorité : -| # | Sujet | Échéance suggérée | +| # | Sujet | Statut | |---|---|---| -| 019 | **Domain model** (entités, relations, index) | Avant la 1ère migration | -| 020 | **Provider OCR** (Mindee, Document AI, Textract, Tesseract) | Avant l'implémentation du job ProcessOcr | -| 021 | **Provider email** (Resend, Postmark, SendGrid, AWS SES) | Avant l'implémentation des relances | -| 022 | **Pricing exact** (Free 5 factures ? Pro 19 €/mois ?) | Avant le payment flow | -| 023 | **Endpoint waitlist** (Resend / Formspree / API Adonis) | Au push de la landing en prod | +| 019 | **Domain model** (entités, relations, index) | ✅ tranché — modèles dans `apps/api/app/models/` | +| 020 | **Provider OCR** | ✅ tranché — Mistral (`OCR_PROVIDER=mistral`, cf. `.env.example` + `k3s/app/api.yml`). ADR à formaliser | +| 021 | **Provider email** | ✅ tranché — Resend pour le sortant, OVH MX pour l'entrant (cf. `docs/tech/backend.md` §12.5). ADR à formaliser | +| 022 | **Pricing exact** | ✅ tranché — Free 5 factures / Pro 19 € / Business 49 € (cf. `apps/api/app/services/billing.ts:34`) | +| 023 | **Endpoint waitlist** | ❌ obsolète — la landing pousse directement vers signup, pas de waitlist en prod | +| 024 | **Sentry pour error tracking** | ✅ tranché — voir `docs/decisions.md` ADR-024 | --- diff --git a/docs/tech/backend.md b/docs/tech/backend.md index 56f29d7..3a67bf5 100644 --- a/docs/tech/backend.md +++ b/docs/tech/backend.md @@ -1,18 +1,21 @@ # Guide d'implémentation — Backend -> Version : 0.1 · Dernière maj : 2026-05-06 -> Décisions de référence : ADR-014 (stack), ADR-015 (monorepo), ADR-016 (PG), ADR-017 (auth), ADR-018 (storage). +> Version : 0.2 · Dernière maj : 2026-05-09 +> Décisions de référence : ADR-014 (stack), ADR-015 (monorepo), ADR-016 (PG), ADR-017 (auth), ADR-018 (storage), ADR-024 (Sentry). -Ce document est le **guide pratique d'implémentation de l'API**. Il complète `architecture.md` (qui décrit le **quoi**) en expliquant le **comment** : commandes, snippets, conventions, et — surtout — le **contrat exact** que le SPA attend déjà côté front (les mocks MSW de `apps/web/src/mocks/handlers/` sont la source de vérité du contrat actuel). +> ⚠️ **Note de cohérence (audit 2026-05-09)** : ce guide a été rédigé en phase de planification. Plusieurs claims ont divergé du code livré. Pour la **liste exhaustive des routes**, lire **`apps/api/start/routes.ts`** comme source de vérité (~80 routes : auth, factures, clients, plans, billing Stripe, blog admin/public, demo, AI, uploads, KPIs). Les modèles vivants sont dans `apps/api/app/models/`, les services dans `apps/api/app/services/`. Les sections ci-dessous sont valides **dans leur esprit** mais détaillent parfois des choix pré-livraison qui ont évolué. + +Ce document est le **guide pratique d'implémentation de l'API**. Il complète `architecture.md` (qui décrit le **quoi**) en expliquant le **comment** : commandes, snippets, conventions. **À lire avant** : - `/CLAUDE.md` — contexte top-level - `/docs/produit.md` — flows utilisateur, IN/OUT V1 - `/docs/tech/architecture.md` — vue d'ensemble du système -- `/docs/tech/frontend.md` — guide d'implémentation du SPA (utile pour comprendre ce que le back doit servir) -- `/packages/shared/src/` — types et schemas Zod déjà partagés -- `/apps/web/src/mocks/handlers/` — **le contrat API tel qu'il est consommé** +- `/docs/tech/frontend.md` — guide d'implémentation du SPA +- `/packages/shared/src/` — types et schemas Zod partagés (statuts, tons, plans, billing) +- `/apps/api/start/routes.ts` — **contrat API actuel (source de vérité)** +- `/apps/api/app/services/` — logique métier (billing, posts, blog_uploads, default_plans, import_batch, etc.) --- @@ -22,12 +25,15 @@ L'API (`apps/api/`) est un **AdonisJS v7** en TypeScript, qui sert : - **JSON-only** sur `/api/v1/*` (pas de Inertia, pas de Hypermedia — le SPA est un consommateur séparé) - **Auth Bearer** stateless via `@adonisjs/auth` access_tokens (cf. ADR-017), avec un refresh token cookie httpOnly géré custom par-dessus -- **Tuyau** pour générer le client TS typé consommé par le SPA → contrat API ↔ web verrouillé par le compilateur -- **PostgreSQL** comme base relationnelle (cf. ADR-016, instance LXC Proxmox existante) -- **MinIO** pour les pièces jointes (cf. ADR-018) -- **Provider OCR** externe (à benchmarker, ADR-020) -- **Provider Email** externe (à benchmarker, ADR-021) -- **Background jobs** pour l'OCR différé, les relances programmées, les check-ins +- **Type-sharing** via `packages/shared` (Zod schemas + types TS), consommé en workspace symlink par `apps/web` et `apps/landing`. *Note historique : la doc évoquait Tuyau pour générer un client TS typé — `@tuyau/core` est dans les deps mais n'est pas utilisé en pratique côté SPA, qui consomme l'API via un client `fetch()` minimaliste dans `apps/web/src/lib/api.ts`.* +- **PostgreSQL** comme base relationnelle (cf. ADR-016, instance LXC Proxmox existante, IP `10.10.10.3`) +- **MinIO** pour les pièces jointes (cf. ADR-018, namespace K3s `minio`, bucket `rubis-prod-invoices`) +- **OCR** : Mistral (`OCR_PROVIDER=mistral`) +- **Email outbound** : Resend (sub-domaine `send.rubis.pro`) +- **Email inbound** : OVH MX (`contact@rubis.pro`) +- **Background jobs** : BullMQ (Redis), workers inline dans le pod API (pas de pod worker séparé) +- **Billing** : Stripe (checkout, customer portal, webhook `/billing/webhook`) +- **Error tracking** : Sentry (cf. ADR-024) Le scaffold initial a été créé via `pnpm create adonisjs@latest -- apps/api --kit=api --pkg=pnpm`, kit `api`. Ça nous a déjà donné : @@ -50,31 +56,33 @@ Tout le reste (org, clients, factures, plans, jobs, OCR, email) est à construir | Authz | `@adonisjs/bouncer` | Policies pour les permissions (V1 mono-user, prêt V2 multi-user) | | Validation | `@vinejs/vine` | Validateurs typés natifs Adonis, mappés sur les schemas Zod de `packages/shared` | | Mail | `@adonisjs/mail` | Templates + provider switchable (Resend / Postmark / SES) | -| Queue | `@rlanz/bull-queue` (BullMQ) | Jobs différés (OCR, envoi email, check-ins) | +| Queue | BullMQ direct + ioredis | Jobs différés (envoi relance, envoi confirmation, envoi remerciement paiement). Pas de wrapper Adonis. Code dans `app/services/queue.ts` | | Cache / queue backend | Redis | Backend de BullMQ + cache des KPIs dashboard | | Rate-limit | `@adonisjs/limiter` | 5 req/min sur `/auth/*`, 10/h sur upload | | Tests | `@japa/runner` + `@japa/api-client` | Tests d'intégration HTTP | -| Type-sharing front | `@tuyau/core` | Génère `.adonisjs/api.ts` consommé par le SPA | +| Type-sharing front | `packages/shared` (workspace) | Zod schemas + types TS (statuts, tons, plans, billing) — consommé par `apps/web` et `apps/landing` | | HTTP client | `@adonisjs/limiter` + `ky` (côté SPA) | — | | Storage | `@adonisjs/drive` (S3 driver MinIO) | Abstraction stockage PDFs | -### Dépendances déjà installées par le starter API +### Dépendances installées (cf. `apps/api/package.json`) -Voir `apps/api/package.json`. À ajouter pour V1 : +Stack actuelle (V1 livrée) : -```bash -cd apps/api -pnpm add @adonisjs/bouncer @adonisjs/mail @adonisjs/limiter @adonisjs/drive -pnpm add @rlanz/bull-queue bullmq ioredis -pnpm add @aws-sdk/client-s3 # pour MinIO via le driver S3 d'@adonisjs/drive -pnpm add resend # ou postmark / @aws-sdk/client-ses selon ADR-021 -node ace add @adonisjs/bouncer -node ace add @adonisjs/mail --providers=resend -node ace add @adonisjs/limiter -node ace add @adonisjs/drive --services=s3 -node ace add @rlanz/bull-queue +```jsonc +// apps/api/package.json (extrait) +"@adonisjs/auth", "@adonisjs/bouncer", "@adonisjs/mail", +"@adonisjs/limiter", "@adonisjs/drive", "@adonisjs/lucid", +"bullmq", "ioredis", // queues directes, pas de wrapper Adonis +"@aws-sdk/client-s3", // MinIO via driver S3 +"resend", // email outbound +"@mistralai/mistralai", // OCR +"stripe", // billing +"@anthropic-ai/sdk", // génération IA blog +"@sentry/node", // ADR-024 ``` +Pas de BullMQ direct (sans wrapper Adonis), pas de `@tuyau/server` (le `@tuyau/core` historique n'est plus utilisé côté SPA). + --- ## 3. Repo layout (apps/api) @@ -185,9 +193,11 @@ apps/api/ │ │ ├── XXXX_create_import_drafts_table.ts │ │ └── XXXX_create_activity_events_table.ts │ ├── seeders/ -│ │ ├── default_plans_seeder.ts # 4 plans pré-fournis (cf. seed.ts MSW) -│ │ └── demo_data_seeder.ts # comptes démo (dev seulement) -│ └── factories/ # Factories Lucid pour les tests +│ │ └── (pas de seeders Adonis : les 4 plans pré-fournis vivent dans +│ │ app/services/default_plans.ts et sont insérés à la création de +│ │ chaque organisation. Le mode démo est servi par +│ │ app/services/demo_simulator.ts, pas par un seeder.) +│ └── factories.ts # Factories Lucid (mono-fichier, pas de dossier — V1) ├── tests/ │ ├── functional/ # Tests HTTP via @japa/api-client │ │ ├── auth.spec.ts @@ -693,12 +703,12 @@ const driveConfig = defineConfig({ services: { s3: services.s3({ credentials: { - accessKeyId: env.get('MINIO_ACCESS_KEY'), - secretAccessKey: env.get('MINIO_SECRET_KEY'), + accessKeyId: env.get('S3_ACCESS_KEY'), + secretAccessKey: env.get('S3_SECRET_KEY'), }, - endpoint: env.get('MINIO_ENDPOINT'), // http://lxc-minio:9000 - region: 'fr-par', - bucket: 'rubis-invoices', + endpoint: env.get('S3_ENDPOINT'), // http://minio.minio.svc.cluster.local:9000 + region: env.get('S3_REGION'), + bucket: env.get('S3_BUCKET'), // rubis-prod-invoices en prod forcePathStyle: true, // requis pour MinIO visibility: 'private', }), @@ -912,7 +922,7 @@ Inbound : les deux veulent le MX `@`. Plan probable : on garde OVH pour ### 13.1 Stack -`@rlanz/bull-queue` (Adonis 7 wrapper de BullMQ) + Redis comme backend. +BullMQ direct (sans wrapper Adonis) (Adonis 7 wrapper de BullMQ) + Redis comme backend. ```ts // config/queue.ts @@ -931,12 +941,17 @@ const queueConfig = defineConfig({ ### 13.2 Jobs -| Job | Trigger | Idempotent | Retry | +Réalité V1 (cf. `apps/api/app/jobs/`) : + +| Job (fichier) | Trigger | Idempotent | Retry | |---|---|---|---| -| `ProcessOcrJob` | POST /invoices/upload | oui (status=validated/skipped → no-op) | 3× exponential backoff | -| `SendRelanceJob` | RelanceTask.sendAt | oui (status=sent → no-op) | 5× | -| `SendCheckinJob` | CheckinTask.sendAt | oui | 3× | -| `RecomputeKpisJob` | nightly cron + post-mutation | oui | 1× | +| `send_relance_job.ts` | RelanceTask.sendAt | oui (status=sent → no-op) | 5× | +| `send_checkin_job.ts` | CheckinTask.sendAt | oui | 3× | +| `send_payment_thanks_job.ts` | confirmation paiement (step 04 du flow) | oui | 3× | + +**OCR** : pas de job dédié — l'extraction est synchrone via `app/services/import_batch.ts` (appel Mistral bloquant pendant l'upload). Si le volume monte, basculer en job différé. + +**KPIs** : calculés on-the-fly à chaque GET dashboard/timeseries (cf. commentaire dans `start/routes.ts`). Pas de `RecomputeKpisJob` — pas de cache Redis pour les KPIs en V1. ### 13.3 Programmation des relances @@ -1111,11 +1126,11 @@ REDIS_HOST=lxc-redis.proxmox.local REDIS_PORT=6379 # MinIO -MINIO_ENDPOINT=http://lxc-minio.proxmox.local:9000 -MINIO_ACCESS_KEY= -MINIO_SECRET_KEY= -MINIO_INVOICES_BUCKET=rubis-invoices -MINIO_BACKUPS_BUCKET=rubis-backups +S3_ENDPOINT=http://minio.minio.svc.cluster.local:9000 +S3_ACCESS_KEY= +S3_SECRET_KEY= +S3_REGION=us-east-1 +S3_BUCKET=rubis-prod-invoices # bucket réel en prod (cf. k3s/app/api.yml) # Mail (Resend) MAIL_FROM_ADDRESS=relances@rubis.pro diff --git a/docs/tech/dev-setup.md b/docs/tech/dev-setup.md index bf683ac..5a12795 100644 --- a/docs/tech/dev-setup.md +++ b/docs/tech/dev-setup.md @@ -2,7 +2,7 @@ > Version : 0.1 · 2026-05-06 -Tout ce qu'il faut pour faire tourner Rubis en local : services backing (Postgres, Redis, MinIO, Mailhog) + l'API + le SPA. +Tout ce qu'il faut pour faire tourner Rubis en local : services backing (Postgres, Redis, MinIO, Mailpit) + l'API + le SPA. ## Prérequis @@ -68,7 +68,7 @@ Si Docker n'est pas dispo, on peut basculer sur SQLite : DB_CONNECTION=sqlite ``` -Les migrations tournent sur les deux. Pour les jobs/storage/mail, il faut quand même Redis/MinIO/Mailhog — donc Docker reste recommandé. +Les migrations tournent sur les deux. Pour les jobs/storage/mail, il faut quand même Redis/MinIO/Mailpit — donc Docker reste recommandé. ## 3. Web (apps/web) @@ -86,7 +86,7 @@ Voir `apps/api/.env.example` — c'est la source de vérité. Récap : - **PG_*** : connexion Postgres (default ports décalés docker-compose) - **REDIS_*** : BullMQ - **S3_*** : MinIO (driver S3 d'@adonisjs/drive) -- **MAIL_*** + **SMTP_*** : Mailhog en dev, Resend en prod (`MAIL_DRIVER=resend` + `RESEND_API_KEY`) +- **MAIL_*** + **SMTP_*** : Mailpit en dev, Resend en prod (`MAIL_DRIVER=resend` + `RESEND_API_KEY`) - **OCR_PROVIDER** : `mock` en dev, `mistral` quand l'API key est en place - **ACCESS_TOKEN_TTL_MINUTES** / **REFRESH_TOKEN_TTL_DAYS** : durées d'auth diff --git a/docs/tech/frontend.md b/docs/tech/frontend.md index e6c6a49..616ce2d 100644 --- a/docs/tech/frontend.md +++ b/docs/tech/frontend.md @@ -1,7 +1,12 @@ # Guide d'implémentation — Frontend -> Version : 0.1 · Dernière maj : 2026-05-05 -> Décisions de référence : ADR-014 (stack), ADR-015 (monorepo), ADR-017 (auth). +> Version : 0.2 · Dernière maj : 2026-05-09 +> Décisions de référence : ADR-014 (stack), ADR-015 (monorepo), ADR-017 (auth), ADR-024 (Sentry). + +> ⚠️ **Note de cohérence (audit 2026-05-09)** : ce guide a été rédigé en phase de planification. Plusieurs choix ont évolué : +> - **Tuyau N'EST PAS utilisé** côté SPA. Le client API vit dans `apps/web/src/lib/api.ts` (fetch + ApiError custom). Le partage de types entre API et SPA passe par `packages/shared` (Zod + types TS), pas par un client typed-RPC. Les sections §6 et §7 ci-dessous, qui décrivent l'install et l'usage de Tuyau, sont **historiques** — gardées pour mémoire mais non applicables au code livré. +> - Les **tokens** de design vivent dans `packages/ui/src/styles/{tokens,base}.css`, pas inline dans `apps/web/src/styles/app.css` comme la doc l'écrit. +> - Les **polices** sont self-hosted via `@fontsource-variable/{bricolage-grotesque,inter}`, pas via `` Google Fonts. Ce document est le **guide pratique d'implémentation du SPA**. Il complète `architecture.md` (qui décrit le **quoi**) en expliquant le **comment** : commandes exactes, snippets de config, conventions de dossier. @@ -9,16 +14,16 @@ Ce document est le **guide pratique d'implémentation du SPA**. Il complète `ar - `/CLAUDE.md` — contexte top-level - `/docs/produit.md` — flows utilisateur, IN/OUT V1 - `/docs/marque.md` — palette, typo, voix, do/don't -- `/docs/wireframes-mvp.html` — les 13 écrans MVP avec annotations +- `/docs/wireframes-mvp.html` — wireframes low-fi initiaux (13 écrans, à jour partiellement) - `/docs/tech/architecture.md` — vue d'ensemble du système --- ## 1. Vue d'ensemble -L'app web (`apps/web/`) est un SPA React 19 buildé par Vite, qui consomme l'API AdonisJS `apps/api/` via un client HTTP type-safe (Tuyau). Le routing client est géré par **TanStack Router** (file-based, type-safe), le state serveur par **TanStack Query**, le styling par **Tailwind CSS v4** avec les tokens de marque issus de `marque.md`. +L'app web (`apps/web/`) est un SPA React 19 buildé par Vite, qui consomme l'API AdonisJS `apps/api/` via un client `fetch()` minimaliste dans `apps/web/src/lib/api.ts`. Le routing client est géré par **TanStack Router** (file-based, type-safe), le state serveur par **TanStack Query**, le styling par **Tailwind CSS v4** avec les tokens de marque issus de `packages/ui` (consommé en workspace). -**Périmètre V1** : 13 écrans listés dans `wireframes-mvp.html`. Auth Bearer (cf. ADR-017) avec refresh token httpOnly cookie. Mobile responsive, pas d'app native. +**Périmètre V1 livré** : ~15 routes `_app/` (dashboard, factures, clients, plans, paramètres, abonnement, insights, admin/blog) + onboarding 3 étapes + auth (login, signup, reset-password, callbacks SSO Google/Microsoft). Auth Bearer (cf. ADR-017) avec refresh token httpOnly cookie. Mobile responsive, pas d'app native. **Hors scope V1** : SSR (pas nécessaire pour un SaaS B2B authentifié), i18n (FR uniquement), PWA offline (nice-to-have V2). @@ -163,16 +168,22 @@ body { Importé dans `main.tsx` : `import './styles/app.css'`. -### Polices Google Fonts +### Polices self-hosted via @fontsource-variable -`index.html` : +Les fonts sont bundlées au build (pas de fetch Google Fonts au runtime → pas de FOUT, pas de fuite RGPD vers `fonts.googleapis.com`). -```html - - - +```bash +pnpm add @fontsource-variable/bricolage-grotesque @fontsource-variable/inter ``` +```css +/* apps/web/src/styles/app.css (ou packages/ui/src/styles/base.css selon où tu importes) */ +@import "@fontsource-variable/bricolage-grotesque"; +@import "@fontsource-variable/inter"; +``` + +**Bonus prod** : sur la landing Astro, on preload les woff2 latin critiques dans le `` pour casser la chaîne HTML→CSS→fonts du critical path (cf. `apps/landing/src/layouts/Layout.astro`). + ### Usage typique ```tsx @@ -207,27 +218,31 @@ Référence : les 13 écrans dans `wireframes-mvp.html`. ``` apps/web/src/routes/ -├── __root.tsx # Layout global, providers, AuthGate -├── login.tsx # 1.2 Connexion -├── signup.tsx # 1.1 Inscription -├── _onboarding/ # Layout onboarding (sans sidebar) -│ ├── _onboarding.tsx -│ ├── compte.tsx # 1.3 step 1 -│ ├── entreprise.tsx # 1.3 step 2 -│ └── signature.tsx # 1.3 step 3 -└── _app/ # Layout app authentifiée - ├── _app.tsx # Layout : sidebar + topbar + tab bar mobile - ├── index.tsx # 4.1 Dashboard - ├── factures.tsx # 2.4 Liste filtrable - ├── factures.$id.tsx # 4.2 Détail facture (timeline) - ├── factures.import.$batchId.tsx # 2.2 Vérification OCR - ├── plans.tsx # 3.1 Bibliothèque - ├── plans.$slug.tsx # 3.2 Éditeur (cadence + templates) - ├── clients.tsx # liste clients - └── parametres.tsx # paramètres compte +├── __root.tsx # Layout global, providers, AuthGate +├── login.tsx, signup.tsx, reset-password.tsx, accept-invite.tsx +├── auth/ # callbacks SSO Google + Microsoft +├── onboarding.tsx # layout onboarding (segment URL /onboarding) +├── onboarding/ # 3 étapes inscription post-signup +│ ├── compte.tsx +│ ├── entreprise.tsx +│ ├── signature.tsx +│ └── index.tsx +└── _app/ # Layout app authentifiée (group route, pas de segment) + ├── _app.tsx # sidebar + topbar + tab bar mobile + ├── index.tsx # Dashboard (compteur rubis + KPIs) + ├── factures.tsx # Liste filtrable + ├── factures_.$id.tsx # Détail facture + timeline relances + ├── factures_.import.tsx # Drag-and-drop OCR (étape 1) + ├── factures_.import_.$batchId.tsx # Vérification OCR (étape 2) + ├── clients.tsx, clients_.$id.tsx + ├── plans.tsx, plans_.$slug.tsx, plans_.nouveau.tsx + ├── parametres.tsx, parametres_.abonnement.tsx + ├── insights.tsx # KPIs avancés (timeseries) + ├── admin.blog.tsx # liste posts (admin only) + └── admin.blog_.$id.tsx # éditeur post + publish workflow ``` -Les routes commençant par `_` sont des **layout routes** (n'ajoutent pas de segment URL). +Les routes en `_app/*` sont sous **layout group route** (pas de segment URL ajouté). Le suffixe `_` dans `factures_.$id` empêche l'imbrication visuelle dans `factures.tsx` (TanStack Router file-based convention). ### Configuration root @@ -405,7 +420,9 @@ const markPaidMutation = useMutation({ --- -## 6. Tuyau — client HTTP typé pour AdonisJS +## 6. Tuyau — *historique, non utilisé en V1* + +> ⚠️ Cette section décrit un choix initial qui n'a pas été retenu côté SPA livré. Le client HTTP réel est un `fetch()` minimaliste dans `apps/web/src/lib/api.ts`, et le partage de types entre l'API et le SPA passe par `packages/shared` (Zod schemas + types TS exportés en workspace). Section gardée pour mémoire et pour un éventuel pivot futur si la surface API grossit suffisamment. [Tuyau](https://github.com/Julien-R44/tuyau) est l'équivalent tRPC pour AdonisJS. Il génère un client TS qui connaît toutes les routes API, leurs payloads, et leurs réponses — depuis le code Adonis lui-même. @@ -676,14 +693,17 @@ VITE_API_URL=http://localhost:3333 VITE_PUBLIC_LANDING_URL=https://rubis.pro ``` -Production via secret K3s injecté dans le build Vite : +Production : le SPA appelle son API via le **même origin** (`/api/v1/*`), proxifié par le nginx du pod `rubis-web` vers `rubis-api:3333` côté K3s. Pas de hostname `api.rubis.pro`. Le `VITE_API_URL` peut donc être laissé vide ou `'/api/v1'` en prod. ```bash -VITE_API_URL=https://api.rubis.pro +VITE_API_URL= # vide → fetch en relatif VITE_PUBLIC_LANDING_URL=https://rubis.pro +VITE_USE_MOCKS=false +VITE_SENTRY_DSN_WEB= # ADR-024 +VITE_APP_VERSION=$GIT_SHA # tag image / sha commit ``` -Toutes les vars accessibles côté SPA **doivent être préfixées `VITE_`** (sinon Vite ne les expose pas au bundle). +Toutes les vars accessibles côté SPA **doivent être préfixées `VITE_`** (sinon Vite ne les expose pas au bundle). Liste complète : voir `apps/web/src/lib/env.ts`. ---