mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
feat(r0.10): apps/api — auth fermée par défaut, matrice en base, démo-login ADR-002, seed
- packages/shared : rôles/catégories, schémas Zod, contrat d'API ; pnpm contract → docs/openapi.json committée (règle d'or ADR-001) - apps/api : NestJS 11 + Prisma 6, migration r0_identity (Role/Permission/User) ; guard JWT global + @Public() ; PermissionsGuard (@RequirePermission, matrice relue en base, cache 60 s) ; FileStorage (seul import MinIO) ; /health - démo-login ADR-002 : module conditionnel DEMO_MODE (404 sinon, testé e2e), double verrou production, refus des comptes isDemo=false - seed idempotent : 7 rôles, matrice complète (70 lignes), 7 comptes démo - 19 tests Jest (unit + e2e) ; smoke test sur build de prod Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,41 @@ Trace chronologique des sessions (la plus récente en premier). Le **playbook**
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0.10 : apps/api complète (auth, matrice, démo-login, seed)
|
||||
|
||||
**Actions**
|
||||
|
||||
- `packages/shared` : vocabulaires (7 rôles, 10 catégories d'objets), schémas Zod (auth, profil, santé) et **contrat d'API** ; `pnpm contract` génère `docs/openapi.json` (committée — règle d'or ADR-001).
|
||||
- `apps/api` (NestJS 11 + Prisma 6) : migration `r0_identity` (Role/Permission/User) ; **guard JWT global fermé par défaut** (+ `@Public()` explicite) ; **PermissionsGuard** (`@RequirePermission`, matrice relue en base, cache 60 s) ; `FileStorage` (seul point d'import MinIO) ; `/health` (base, Redis, stockage).
|
||||
- **Démo-login ADR-002** : module enregistré uniquement si `DEMO_MODE=true` (sinon routes **404**), double verrou production (`DEMO_MODE_I_KNOW`), refus des comptes `isDemo=false`.
|
||||
- **Seed idempotent** : 7 rôles, matrice complète (70 lignes), 7 comptes démo (mot de passe commun `SEED_DEMO_PASSWORD` pour la connexion classique).
|
||||
- **Vérifié bout-en-bout** : 19 tests Jest verts (dont e2e démo on/off) ; smoke test sur build de prod — démo-login → `/users/me` avec matrice, 401 sans jeton, health `ok`.
|
||||
|
||||
**Décisions**
|
||||
|
||||
- `AppModule.forRoot()` (module dynamique) pour rendre l'enregistrement conditionnel du module démo **testable dans les deux états** — le e2e « routes absentes » est l'exigence n°1 de l'ADR-002.
|
||||
- Le seed n'écrase jamais une ligne de matrice existante : **la base est la source de vérité des droits**, le fichier n'est que le point de départ.
|
||||
|
||||
**Prochaine étape** : R0.11 `apps/web` — login + sélecteur de comptes démo (fidèle à maquette-web.html), coquille sidebar/topbar avec bandeau « DÉMO », page /design, client typé généré depuis `docs/openapi.json`.
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0 : maquettes VALIDÉES ; architecture + squelette
|
||||
|
||||
**Actions**
|
||||
|
||||
- **Maquettes HD validées par le référent** → le design est la loi des revues pixel.
|
||||
- 03-architecture : ADR-001 (stack), ADR-002 (démo-login `DEMO_MODE`, double verrou prod), vue C4, modèle de données R0 (Role/Permission/User, `isDemo`).
|
||||
- Racine monorepo (pnpm + turbo, Node 24) ; `infra/` : compose local (PostgreSQL 18 pgvector+PostGIS via Dockerfile dédié, Redis, MinIO).
|
||||
|
||||
**Décisions**
|
||||
|
||||
- **Convention `siop2-`** pour tous services/conteneurs Docker (référent — collisions sur le réseau partagé Dokploy en v1).
|
||||
|
||||
**Prochaine étape (reprise)** : R0.10 `apps/api` (auth JWT + matrice permissions + démo-login + seed) → R0.11 `apps/web` (login + sélecteur démo + coquille + /design) → R0.12 CI → R0.13 prépa Dokploy.
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0 : kickoff, vision, cadrage, design
|
||||
|
||||
**Actions**
|
||||
|
||||
479
docs/openapi.json
Normal file
479
docs/openapi.json
Normal file
@@ -0,0 +1,479 @@
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "SIOP V2 API",
|
||||
"version": "0.1.0",
|
||||
"description": "GMAO ascenseurs — contrat R0 (auth, démo-login ADR-002, profil, santé). Généré depuis packages/shared/src/contract.ts — NE PAS ÉDITER À LA MAIN."
|
||||
},
|
||||
"paths": {
|
||||
"/auth/login": {
|
||||
"post": {
|
||||
"operationId": "login",
|
||||
"summary": "Connexion par e-mail et mot de passe",
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Jeton émis",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AuthResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Identifiants invalides ou compte inactif"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/demo-accounts": {
|
||||
"get": {
|
||||
"operationId": "listDemoAccounts",
|
||||
"summary": "Comptes de démonstration (ADR-002 — jamais de secret)",
|
||||
"tags": [
|
||||
"auth",
|
||||
"demo"
|
||||
],
|
||||
"x-demo-only": true,
|
||||
"description": "ADR-002 : cette route est absente (404) quand DEMO_MODE n’est pas actif.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Comptes isDemo actifs",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/DemoAccountsResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/demo-login": {
|
||||
"post": {
|
||||
"operationId": "demoLogin",
|
||||
"summary": "Connexion 1 clic sur un compte de démonstration (ADR-002)",
|
||||
"tags": [
|
||||
"auth",
|
||||
"demo"
|
||||
],
|
||||
"x-demo-only": true,
|
||||
"description": "ADR-002 : cette route est absente (404) quand DEMO_MODE n’est pas actif.",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/DemoLoginRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Jeton émis",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AuthResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Le compte n’est pas un compte de démonstration"
|
||||
},
|
||||
"404": {
|
||||
"description": "Compte inconnu"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/me": {
|
||||
"get": {
|
||||
"operationId": "getMe",
|
||||
"summary": "Profil courant + matrice de permissions du rôle",
|
||||
"tags": [
|
||||
"users"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Profil",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Non authentifié"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/health": {
|
||||
"get": {
|
||||
"operationId": "getHealth",
|
||||
"summary": "État des dépendances (base, Redis, stockage)",
|
||||
"tags": [
|
||||
"health"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "État agrégé",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HealthResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"AuthResponse": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accessToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Administrateur",
|
||||
"Dispatcher",
|
||||
"Technicien",
|
||||
"Technicien limité",
|
||||
"Gestionnaire",
|
||||
"Demandeur",
|
||||
"Vue seule"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"isDemo": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"email",
|
||||
"displayName",
|
||||
"role",
|
||||
"isDemo"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accessToken",
|
||||
"user"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"LoginRequest": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"password"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"DemoAccountsResponse": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accounts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"roleName": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Administrateur",
|
||||
"Dispatcher",
|
||||
"Technicien",
|
||||
"Technicien limité",
|
||||
"Gestionnaire",
|
||||
"Demandeur",
|
||||
"Vue seule"
|
||||
]
|
||||
},
|
||||
"initials": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 3
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"displayName",
|
||||
"roleName",
|
||||
"initials"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accounts"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"DemoLoginRequest": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"userId": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"userId"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"MeResponse": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Administrateur",
|
||||
"Dispatcher",
|
||||
"Technicien",
|
||||
"Technicien limité",
|
||||
"Gestionnaire",
|
||||
"Demandeur",
|
||||
"Vue seule"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"isDemo": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"permissions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"objectCategory": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"WORK_ORDERS",
|
||||
"REQUESTS",
|
||||
"ASSETS",
|
||||
"LOCATIONS",
|
||||
"METERS",
|
||||
"PARTS",
|
||||
"PURCHASE_ORDERS",
|
||||
"PEOPLE_TEAMS",
|
||||
"ANALYTICS",
|
||||
"SETTINGS"
|
||||
]
|
||||
},
|
||||
"canView": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canViewOther": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canCreate": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canEdit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canDelete": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"objectCategory",
|
||||
"canView",
|
||||
"canViewOther",
|
||||
"canCreate",
|
||||
"canEdit",
|
||||
"canDelete"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"email",
|
||||
"displayName",
|
||||
"role",
|
||||
"isDemo",
|
||||
"permissions"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"HealthResponse": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ok",
|
||||
"degraded"
|
||||
]
|
||||
},
|
||||
"services": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"up",
|
||||
"down"
|
||||
]
|
||||
},
|
||||
"redis": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"up",
|
||||
"down"
|
||||
]
|
||||
},
|
||||
"storage": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"up",
|
||||
"down"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"database",
|
||||
"redis",
|
||||
"storage"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"services"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
"bearerAuth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user