mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
- 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>
105 lines
3.0 KiB
TypeScript
105 lines
3.0 KiB
TypeScript
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,
|
||
},
|
||
},
|
||
},
|
||
];
|