Files
siop2/packages/shared/scripts/generate-openapi.ts
pr-daaif 8adb561b63 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>
2026-07-15 21:55:04 +01:00

85 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Règle d'or (ADR-001) : Zod → OpenAPI → clients typés.
* Génère docs/openapi.json (COMMITTÉE) depuis src/contract.ts.
* Usage : pnpm --filter @siop/shared contract (raccourci racine : pnpm contract)
* La CI (ci-contract, R0.12) échoue si le fichier committé diffère de la génération.
*/
import { mkdirSync, writeFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { z } from 'zod';
import { API_CONTRACT } from '../src/contract';
const OUT = resolve(__dirname, '../../../docs/openapi.json');
const schemas: Record<string, unknown> = {};
function register(name: string, schema: z.ZodType): { $ref: string } {
if (!(name in schemas)) {
schemas[name] = z.toJSONSchema(schema, { target: 'draft-2020-12' });
}
return { $ref: `#/components/schemas/${name}` };
}
const paths: Record<string, Record<string, unknown>> = {};
for (const op of API_CONTRACT) {
const responses: Record<string, unknown> = {};
for (const [status, res] of Object.entries(op.responses)) {
responses[status] = {
description: res.description,
...(res.schema && res.name
? { content: { 'application/json': { schema: register(res.name, res.schema) } } }
: {}),
};
}
paths[op.path] = {
...(paths[op.path] ?? {}),
[op.method]: {
operationId: op.operationId,
summary: op.summary,
tags: op.tags,
...(op.isPublic ? {} : { security: [{ bearerAuth: [] }] }),
...(op.demoOnly
? {
'x-demo-only': true,
description:
'ADR-002 : cette route est absente (404) quand DEMO_MODE nest pas actif.',
}
: {}),
...(op.request
? {
requestBody: {
required: true,
content: {
'application/json': { schema: register(op.request.name, op.request.schema) },
},
},
}
: {}),
responses,
},
};
}
const doc = {
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,
components: {
schemas,
securitySchemes: {
bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
},
},
};
mkdirSync(dirname(OUT), { recursive: true });
writeFileSync(OUT, JSON.stringify(doc, null, 2) + '\n');
console.log(`OpenAPI générée : ${OUT} (${API_CONTRACT.length} opérations)`);