mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 20:51:53 +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:
77
apps/api/src/documents/documents.controller.ts
Normal file
77
apps/api/src/documents/documents.controller.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
Param,
|
||||
ParseUUIDPipe,
|
||||
Post,
|
||||
Query,
|
||||
StreamableFile,
|
||||
UploadedFile,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { DOCUMENT_MAX_BYTES } from '@siop/shared';
|
||||
import {
|
||||
AuthenticatedUser,
|
||||
CurrentUser,
|
||||
} from '../auth/current-user.decorator';
|
||||
import { DocumentsService } from './documents.service';
|
||||
|
||||
@Controller('documents')
|
||||
export class DocumentsController {
|
||||
constructor(private readonly documents: DocumentsService) {}
|
||||
|
||||
/** Bibliothèque interne — authentification seule (les cartes « Documents »
|
||||
* des fiches la consomment). */
|
||||
@Get()
|
||||
list(
|
||||
@Query('assetId') assetId?: string,
|
||||
@Query('workOrderId') workOrderId?: string,
|
||||
@Query('kind') kind?: string,
|
||||
) {
|
||||
return this.documents.list({ assetId, workOrderId, kind });
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseInterceptors(
|
||||
FileInterceptor('file', { limits: { fileSize: DOCUMENT_MAX_BYTES } }),
|
||||
)
|
||||
upload(
|
||||
@UploadedFile() file: Express.Multer.File | undefined,
|
||||
@Body() body: { kind?: string; assetId?: string; workOrderId?: string },
|
||||
@CurrentUser() user: AuthenticatedUser,
|
||||
) {
|
||||
if (!file) throw new BadRequestException('Aucun fichier reçu (champ « file »)');
|
||||
return this.documents.upload(
|
||||
{
|
||||
buffer: file.buffer,
|
||||
originalName: file.originalname,
|
||||
contentType: file.mimetype,
|
||||
size: file.size,
|
||||
kind: body.kind ?? 'OTHER',
|
||||
assetId: body.assetId || undefined,
|
||||
workOrderId: body.workOrderId || undefined,
|
||||
},
|
||||
user,
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id/download')
|
||||
async download(@Param('id', ParseUUIDPipe) id: string): Promise<StreamableFile> {
|
||||
const { stream, fileName, contentType } = await this.documents.download(id);
|
||||
return new StreamableFile(stream as never, {
|
||||
type: contentType,
|
||||
disposition: `attachment; filename*=UTF-8''${encodeURIComponent(fileName)}`,
|
||||
});
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(204)
|
||||
remove(@Param('id', ParseUUIDPipe) id: string, @CurrentUser() user: AuthenticatedUser) {
|
||||
return this.documents.remove(id, user);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user