mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
docs(r0): architecture — ADR-001 stack, ADR-002 démo-login, C4, modèle R0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
31
docs/03-architecture/adr/ADR-001-stack.md
Normal file
31
docs/03-architecture/adr/ADR-001-stack.md
Normal file
@@ -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).
|
||||
34
docs/03-architecture/adr/ADR-002-demo-login.md
Normal file
34
docs/03-architecture/adr/ADR-002-demo-login.md
Normal file
@@ -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).
|
||||
71
docs/03-architecture/architecture.md
Normal file
71
docs/03-architecture/architecture.md
Normal file
@@ -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 |
|
||||
69
docs/03-architecture/modele-donnees.md
Normal file
69
docs/03-architecture/modele-donnees.md
Normal file
@@ -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) |
|
||||
Reference in New Issue
Block a user