diff --git a/docs/03-architecture/adr/ADR-001-stack.md b/docs/03-architecture/adr/ADR-001-stack.md new file mode 100644 index 0000000..28874a5 --- /dev/null +++ b/docs/03-architecture/adr/ADR-001-stack.md @@ -0,0 +1,31 @@ +# ADR-001 — Stack technique + +- **Statut** : acceptée (R0, 15 juillet 2026) +- **Contexte** : la V2 repart de zéro (code) mais pas de nulle part : la v1 a éprouvé une + stack pendant 8 sprints (~400 tests). Atlas CMMS, notre référence fonctionnelle, est en + Java Spring Boot + React MUI — cloner sa stack n'a aucun intérêt pédagogique ni pratique + pour une équipe TypeScript. + +## Décision + +| Couche | Choix | Justification | +| --- | --- | --- | +| Monorepo | **pnpm workspaces + Turborepo** | Un dépôt, plusieurs apps, caches de tâches ; éprouvé v1 | +| API | **NestJS (Node 24) + Prisma + PostgreSQL 18** | Architecture modulaire enseignable ; Prisma = modèle lisible par les étudiants | +| Extensions BDD | **pgvector** (RAG, R5) + **PostGIS** (carte, R1) | Un seul moteur de données pour tout | +| Cache/files | **Redis** (BullMQ pour les jobs, R2) | Génération mensuelle du préventif | +| Fichiers | **MinIO** derrière l'interface `FileStorage` | S3-compatible auto-hébergé ; l'interface interdit tout couplage (règle lint) | +| Contrat | **Zod → OpenAPI → clients typés** (« règle d'or ») | Le front ne peut pas dériver du back ; CI bloquante | +| Web | **React 19 + Vite + TanStack Query/Table + Tailwind v4** | Éprouvé v1 | +| UI kit | **shadcn/ui** thématisé par `docs/02-design/tokens.css` | Composants possédés (copiés dans le repo), accessibles (Radix), thémables par tokens — le contraire du « DaisyUI brut » de la v1. Introduit en R0 (primitives) et enrichi par release | +| Mobile | **Expo (Expo Go)** — R4 | Démo par QR sans build natif ; leçons v1 consignées | +| IA | **FastAPI (Python 3.13, uv)** — R5 | Écosystème IA Python ; n'écrit jamais en base métier | +| Déploiement | **Docker + Dokploy** (PaaS auto-hébergé) | Choix du référent ; portable (tout est compose) | + +## Conséquences + +- Un seul langage principal (TypeScript) du mobile à l'API — onboarding étudiant simplifié. +- La v1 sert de **référence de lecture** (décisions validées : machine à états OT, matrice + de permissions en base, stock dérivé des mouvements, synchro à verrou optimiste) ; tout + code V2 est réécrit et re-testé. +- Versions épinglées ; toute montée de version = tâche dédiée (risque n°5 du registre). diff --git a/docs/03-architecture/adr/ADR-002-demo-login.md b/docs/03-architecture/adr/ADR-002-demo-login.md new file mode 100644 index 0000000..7c9f22b --- /dev/null +++ b/docs/03-architecture/adr/ADR-002-demo-login.md @@ -0,0 +1,34 @@ +# ADR-002 — Sélecteur de compte de démonstration (`DEMO_MODE`) + +- **Statut** : acceptée (R0, 15 juillet 2026) +- **Contexte** : demande explicite du référent après la v1 — tester avec plusieurs rôles + obligeait à se déconnecter/reconnecter sans cesse, en local comme sur l'instance en ligne. + C'est aussi un besoin de **démonstration commerciale** (montrer chaque persona en 1 clic). + +## Décision + +1. Une variable d'environnement **`DEMO_MODE`** (`true`/absent) contrôle le mode démo, + côté API **et** côté web (`VITE_DEMO_MODE` injectée au build ou servie par `/auth/demo-accounts`). +2. Quand `DEMO_MODE=true` : + - `GET /auth/demo-accounts` (**publique**) liste les comptes de démonstration seedés + (id, nom, rôle, initiales — jamais de secret) ; + - `POST /auth/demo-login { userId }` (**publique**) émet un JWT pour ce compte **sans + mot de passe** — uniquement pour les comptes marqués `isDemo=true` en base ; + - le web affiche la liste sur l'écran de connexion **et** un sélecteur dans la topbar + (bascule instantanée, marquage visuel « DÉMO » safran permanent). +3. Quand `DEMO_MODE` est absent/faux : **les deux routes n'existent pas** (404 — le module + n'est pas enregistré), le web n'affiche rien. Test e2e dédié qui vérifie le 404. + +## Sécurité + +- Le danger réel est un `DEMO_MODE=true` oublié en production. Parades : (a) le module + refuse de démarrer si `DEMO_MODE=true` **et** `NODE_ENV=production` sauf si + `DEMO_MODE_I_KNOW=true` (double verrou explicite pour l'instance de démo publique) ; + (b) `isDemo` en base — un compte réel ne peut jamais être emprunté ; (c) bannière + « DÉMO » permanente dans l'UI. +- Critère d'acceptation (exigences) : passer d'Administrateur à Technicien en **< 3 s**. + +## Conséquences + +Le seed crée 7 comptes `isDemo` (un par rôle). Les environnements : dev local +(`DEMO_MODE=true`), démo en ligne (`true` + double verrou), production client (absent). diff --git a/docs/03-architecture/architecture.md b/docs/03-architecture/architecture.md new file mode 100644 index 0000000..2beec54 --- /dev/null +++ b/docs/03-architecture/architecture.md @@ -0,0 +1,71 @@ +# Architecture — SIOP V2 + +> **Rôle de ce document (playbook)** : la vue d'ensemble en 3 niveaux (méthode C4 +> simplifiée : contexte → conteneurs → composants). Les décisions structurantes sont +> tracées en ADR (`adr/`). Mis à jour à chaque release. + +## Niveau 1 — Contexte + +``` + Gardien (Karim) ──scan QR──▶ ┌─────────────────┐ ◀──navigateur── Bureau (Salma, Nadia, direction) + │ │ + Technicien (Ahmed) ─mobile─▶ │ SIOP V2 │ ──emails──▶ demandeurs / alertes stock + │ │ + Notices, historiques ──────▶ └─────────────────┘ + (ingestion RAG, R5) +``` + +## Niveau 2 — Conteneurs + +``` +┌───────────────────────────── Dokploy (Docker) ─────────────────────────────┐ +│ │ +│ apps/web ── nginx ──/api──▶ apps/api (NestJS) ──▶ PostgreSQL 18 │ +│ (React 19, statique) │ auth JWT, permissions, métier │ (+ pgvector, PostGIS) +│ │──▶ Redis (BullMQ : jobs préventif, R2) │ +│ apps/mobile (Expo, R4) ────▶│──▶ MinIO (FileStorage : photos, documents) │ +│ /sync │ │ +│ apps/ai (FastAPI, R5) ◀─────┘ (lecture métier via API ; écrit seulement │ +│ RAG, embeddings les embeddings pgvector) │ +└────────────────────────────────────────────────────────────────────────────┘ +``` + +Règles de dépendance : **toutes les écritures métier passent par l'API NestJS** ; le web et +le mobile ne parlent qu'au contrat OpenAPI ; MinIO n'est accédé qu'à travers `FileStorage`. + +## Niveau 3 — Composants de l'API (état R0) + +``` +apps/api/src +├── auth/ JWT (access), guard global « fermé par défaut », @Public(), +│ démo-login (module conditionnel DEMO_MODE — ADR-002) +├── permissions/ matrice rôles × objets × droits (en base, cache 60 s), +│ @RequirePermission(objet, droit) + PermissionsGuard +├── users/ profils (R0 : lecture du profil courant ; gestion complète R1) +├── health/ /health (base, Redis, MinIO) — public +├── prisma/ PrismaService (client généré) +└── common/ zodToOpenApi, ZodValidationPipe, filtres d'erreurs +``` + +Chaque release ajoute ses modules (R1 : locations/assets/categories/teams ; R2 : +work-orders/requests/preventive ; …) — le document est enrichi à chaque clôture. + +## La « règle d'or » du contrat + +``` +packages/shared (Zod) ──zodToOpenApi──▶ docs/openapi.json (committée) + │ openapi-typescript + ▼ + clients typés web / mobile (générés) +``` + +Toute modification d'API régénère spec + clients **dans le même commit** ; la CI +(`ci-contract`) échoue sur le moindre diff. Le front ne peut structurellement pas dériver. + +## Environnements + +| Env | `DEMO_MODE` | Données | Où | +| --- | --- | --- | --- | +| dev local | `true` | seed démo | Docker local (`pnpm infra:up`) | +| démo en ligne | `true` (+ double verrou) | seed démo | Dokploy | +| production client | absent | réelles | Dokploy | diff --git a/docs/03-architecture/modele-donnees.md b/docs/03-architecture/modele-donnees.md new file mode 100644 index 0000000..a5a7157 --- /dev/null +++ b/docs/03-architecture/modele-donnees.md @@ -0,0 +1,69 @@ +# Modèle de données — SIOP V2 + +> **Rôle de ce document (playbook)** : le modèle est introduit **par release** (on ne +> modélise pas ce qu'on ne construit pas). Chaque section explique les invariants et OÙ +> ils vivent (base / service). La v1 sert de référence éprouvée pour les entités à venir. + +## R0 — Identité & permissions + +```prisma +model Role { + id String @id @default(uuid()) @db.Uuid + name String @unique // Administrateur, Dispatcher, Technicien, + users User[] // Technicien limité, Gestionnaire, + permissions Permission[] // Demandeur, Vue seule +} + +model Permission { // matrice rôles × objets × droits — EN BASE, jamais dans le JWT + id String @id @default(uuid()) @db.Uuid + roleId String @db.Uuid + role Role @relation(...) + objectCategory String // enum applicatif : WORK_ORDERS, ASSETS, … + canView Boolean @default(false) + canViewOther Boolean @default(false) // « voir autre » : au-delà de ses propres objets + canCreate Boolean @default(false) + canEdit Boolean @default(false) + canDelete Boolean @default(false) + @@unique([roleId, objectCategory]) +} + +model User { + id String @id @default(uuid()) @db.Uuid + email String @unique + displayName String + passwordHash String? // null tant que le compte n'est pas activé (R1) + roleId String @db.Uuid + role Role @relation(...) + isActive Boolean @default(true) + isDemo Boolean @default(false) // seul un compte isDemo est empruntable (ADR-002) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} +``` + +**Invariants R0** : + +| Invariant | Où il vit | +| --- | --- | +| Un rôle par utilisateur ; la matrice décide de tout accès | `PermissionsGuard` (relit la base, cache 60 s) | +| Le JWT ne porte jamais de droits (seulement l'identité + roleId) | conception auth | +| `demo-login` refuse tout compte `isDemo=false` | service auth (+ test e2e) | +| Matrice complète : chaque rôle a une ligne par catégorie d'objet | seed idempotent (+ test) | + +## À venir (référence v1 éprouvée, sera réintroduit release par release) + +- **R1** : `Location` (hiérarchie + colonne PostGIS), `Asset` + organes (hiérarchie 2 niveaux, + CHECK « un organe n'a pas d'emplacement propre »), `Category`, `Team`. +- **R2** : `WorkOrder` (machine à états stricte, priorité « personne bloquée »), `Request` + (lien 1-1 vers OT), `InterventionReport` (bilan codé 6 champs → `ReferenceValue`), + `TaskTemplate`/`PreventivePlan`/`ChecklistItem` (périodicité calendrier), `Meter`. +- **R3** : `Part`/`StockMovement` (stock **dérivé des mouvements**), `PurchaseOrder`, + `Partner`, `LaborTime` (taux figé), `Document`. +- **R4** : `WorkOrder.version` (verrou optimiste de la synchro mobile). +- **R5** : tables d'embeddings pgvector (côté service IA). + +## Journal des migrations + +| # | Migration | Contenu | +| --- | --- | --- | +| 1 | `r0_identity` | Role, Permission, User (+ index & uniques ci-dessus) |