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:
104
packages/shared/src/contract.ts
Normal file
104
packages/shared/src/contract.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { z } from 'zod';
|
||||
import {
|
||||
AuthResponseSchema,
|
||||
DemoAccountsResponseSchema,
|
||||
DemoLoginRequestSchema,
|
||||
LoginRequestSchema,
|
||||
} from './schemas/auth';
|
||||
import { MeResponseSchema } from './schemas/users';
|
||||
import { HealthResponseSchema } from './schemas/health';
|
||||
|
||||
/**
|
||||
* Contrat d'API R0 — source unique de vérité (règle d'or ADR-001).
|
||||
* `scripts/generate-openapi.ts` en dérive `docs/openapi.json` (committée) ;
|
||||
* les clients web/mobile sont générés depuis cette spec, dans le même commit.
|
||||
*/
|
||||
export interface ApiOperation {
|
||||
operationId: string;
|
||||
method: 'get' | 'post' | 'put' | 'patch' | 'delete';
|
||||
path: string;
|
||||
summary: string;
|
||||
tags: string[];
|
||||
/** Route annotée @Public() côté API (pas de JWT requis). */
|
||||
isPublic?: boolean;
|
||||
/** ADR-002 : la route N'EXISTE PAS (404) si DEMO_MODE n'est pas actif. */
|
||||
demoOnly?: boolean;
|
||||
request?: { name: string; schema: z.ZodType };
|
||||
responses: Record<
|
||||
number,
|
||||
{ description: string; name?: string; schema?: z.ZodType }
|
||||
>;
|
||||
}
|
||||
|
||||
export const API_CONTRACT: ApiOperation[] = [
|
||||
{
|
||||
operationId: 'login',
|
||||
method: 'post',
|
||||
path: '/auth/login',
|
||||
summary: 'Connexion par e-mail et mot de passe',
|
||||
tags: ['auth'],
|
||||
isPublic: true,
|
||||
request: { name: 'LoginRequest', schema: LoginRequestSchema },
|
||||
responses: {
|
||||
200: { description: 'Jeton émis', name: 'AuthResponse', schema: AuthResponseSchema },
|
||||
401: { description: 'Identifiants invalides ou compte inactif' },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'listDemoAccounts',
|
||||
method: 'get',
|
||||
path: '/auth/demo-accounts',
|
||||
summary: 'Comptes de démonstration (ADR-002 — jamais de secret)',
|
||||
tags: ['auth', 'demo'],
|
||||
isPublic: true,
|
||||
demoOnly: true,
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Comptes isDemo actifs',
|
||||
name: 'DemoAccountsResponse',
|
||||
schema: DemoAccountsResponseSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'demoLogin',
|
||||
method: 'post',
|
||||
path: '/auth/demo-login',
|
||||
summary: 'Connexion 1 clic sur un compte de démonstration (ADR-002)',
|
||||
tags: ['auth', 'demo'],
|
||||
isPublic: true,
|
||||
demoOnly: true,
|
||||
request: { name: 'DemoLoginRequest', schema: DemoLoginRequestSchema },
|
||||
responses: {
|
||||
200: { description: 'Jeton émis', name: 'AuthResponse', schema: AuthResponseSchema },
|
||||
403: { description: 'Le compte n’est pas un compte de démonstration' },
|
||||
404: { description: 'Compte inconnu' },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'getMe',
|
||||
method: 'get',
|
||||
path: '/users/me',
|
||||
summary: 'Profil courant + matrice de permissions du rôle',
|
||||
tags: ['users'],
|
||||
responses: {
|
||||
200: { description: 'Profil', name: 'MeResponse', schema: MeResponseSchema },
|
||||
401: { description: 'Non authentifié' },
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: 'getHealth',
|
||||
method: 'get',
|
||||
path: '/health',
|
||||
summary: 'État des dépendances (base, Redis, stockage)',
|
||||
tags: ['health'],
|
||||
isPublic: true,
|
||||
responses: {
|
||||
200: {
|
||||
description: 'État agrégé',
|
||||
name: 'HealthResponse',
|
||||
schema: HealthResponseSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user