mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
feat(r3.2): bibliothèque de documents (FileStorage réel) + analytics
- FileStorage : putObject/getObjectStream/removeObject, bucket créé au démarrage — MinIO confiné à son implémentation (règle ESLint intacte) - documents : upload multipart (PDF/JPG/PNG, 20 Mo max, rattachement appareil OU OT requis, permission d'édition sur la CIBLE), liste filtrable, téléchargement STREAMÉ par l'API (MinIO jamais exposé), suppression ; e2e : octets téléchargés identiques aux octets envoyés - analytics : GET /analytics/summary dérivé du réel — coûts/mois (mouvements + main-d'œuvre figés), pannes par organe (bilans codés), taux de préventif, durée moyenne de résolution, top équipements - générateur OpenAPI : query params, multipart, réponse binaire (71 ops) - CI : service MinIO (bitnami) sur les jobs api et e2e - test de régression du tri « Interventions récentes » rendu déterministe (positions absolues instables sous 12 suites parallèles) ; 58 tests, 6 runs complets consécutifs verts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,18 +38,26 @@ for (const op of API_CONTRACT) {
|
||||
operationId: op.operationId,
|
||||
summary: op.summary,
|
||||
tags: op.tags,
|
||||
...(op.pathParams?.length
|
||||
...(op.pathParams?.length || op.queryParams?.length
|
||||
? {
|
||||
parameters: op.pathParams.map((name) => ({
|
||||
name,
|
||||
in: 'path',
|
||||
required: true,
|
||||
// Convention : « id » / «…Id » sont des UUID, le reste est libre
|
||||
schema:
|
||||
name === 'id' || name.endsWith('Id')
|
||||
? { type: 'string', format: 'uuid' }
|
||||
: { type: 'string' },
|
||||
})),
|
||||
parameters: [
|
||||
...(op.pathParams ?? []).map((name) => ({
|
||||
name,
|
||||
in: 'path',
|
||||
required: true,
|
||||
// Convention : « id » / «…Id » sont des UUID, le reste est libre
|
||||
schema:
|
||||
name === 'id' || name.endsWith('Id')
|
||||
? { type: 'string', format: 'uuid' }
|
||||
: { type: 'string' },
|
||||
})),
|
||||
...(op.queryParams ?? []).map((q) => ({
|
||||
name: q.name,
|
||||
in: 'query',
|
||||
required: q.required ?? false,
|
||||
schema: { type: 'string' },
|
||||
})),
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
...(op.isPublic ? {} : { security: [{ bearerAuth: [] }] }),
|
||||
@@ -70,6 +78,29 @@ for (const op of API_CONTRACT) {
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(op.multipartFields
|
||||
? {
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'multipart/form-data': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: Object.fromEntries(
|
||||
Object.entries(op.multipartFields).map(([name, sorte]) => [
|
||||
name,
|
||||
sorte === 'file'
|
||||
? { type: 'string', format: 'binary' }
|
||||
: { type: 'string' },
|
||||
]),
|
||||
),
|
||||
required: ['file'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
responses,
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user