docs(audit-2/3): aligner doc tech sur le code livré
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 <link>) - §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 <noreply@anthropic.com>
This commit is contained in:
parent
801168fc74
commit
9eaac0c7ef
@ -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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -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=<secret>
|
||||
MINIO_SECRET_KEY=<secret>
|
||||
MINIO_INVOICES_BUCKET=rubis-invoices
|
||||
MINIO_BACKUPS_BUCKET=rubis-backups
|
||||
S3_ENDPOINT=http://minio.minio.svc.cluster.local:9000
|
||||
S3_ACCESS_KEY=<secret>
|
||||
S3_SECRET_KEY=<secret>
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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 `<link>` 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
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,500;12..96,600;12..96,700;12..96,800&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
```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 `<head>` pour casser la chaîne HTML→CSS→fonts du critical path (cf. `apps/landing/src/layouts/Layout.astro`).
|
||||
|
||||
### Usage typique
|
||||
|
||||
```tsx
|
||||
@ -208,26 +219,30 @@ 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
|
||||
├── 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=<dsn> # 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`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user