Files
siop2/packages/shared/scripts/generate-openapi.ts
pr-daaif 266ffaaf1b feat(r1.1): socle backend du référentiel — modèle, contrat, API, seed, tests
- migration r1_referentiel : Category (EQUIPMENT/COMPONENT_TYPE), Location
  (site → zone, lat/lng + colonne PostGIS générée geography(Point,4326)
  + index GIST), Asset (statut d'équipement), AssetComponent (organe sans
  emplacement PAR CONSTRUCTION), Team, invitation sur User ; migration
  autosuffisante (CREATE EXTENSION IF NOT EXISTS postgis)
- contrat : 21 nouvelles opérations (26 total), générateur OpenAPI étendu
  aux paramètres de chemin ; spec + client web régénérés dans ce commit
- API : modules categories/locations/assets/teams + gestion des personnes
  (liste, rôles, invitation lien 7 j à usage unique, activation publique
  qui connecte directement, mise à jour rôle/équipes) — tout sous
  @RequirePermission ; invariants en service (profondeur 2, kinds,
  catégorie jamais supprimée)
- seed : parc de la maquette validée (5 sites + 8 zones, 8 appareils,
  organes A1/B2, 9 catégories, 2 équipes) — idempotent
- 36 tests verts (couverture 96 % stmts / 85 % branches) : recette
  site→zone→appareil→organes, matrice vivante, invitation→activation ;
  smoke test sur build de prod
- CI : postgres → postgis/postgis:18-3.6 (la migration R1 l'exige)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 12:27:48 +01:00

95 lines
2.8 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.pathParams?.length
? {
parameters: op.pathParams.map((name) => ({
name,
in: 'path',
required: true,
schema: { type: 'string', format: 'uuid' },
})),
}
: {}),
...(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)`);