fix(uploads): validation YAML + logging erreurs 5xx détaillé

- Validation syntaxique js-yaml avant import (BadRequestException 400 avec message précis)
- Logger dans UploadsController : log du fichier principal détecté pour multi-upload
- AllExceptionsFilter : log complet avec stack trace pour toutes les erreurs 5xx
- @asyncapi/bundler version corrigée (0.9.0 → ^1.0.1, version inexistante)
- @types/js-yaml ajouté en devDependencies

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
z3n
2026-06-08 12:50:50 +00:00
parent b975e01e15
commit a0677df5ec
6 changed files with 11453 additions and 93 deletions

View File

@@ -1,8 +1,10 @@
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
private readonly logger = new Logger('ExceptionFilter');
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
@@ -18,6 +20,14 @@ export class AllExceptionsFilter implements ExceptionFilter {
? exception.getResponse()
: 'Internal server error';
/* Logguer toutes les erreurs 5xx avec stack trace complète */
if (status >= 500) {
this.logger.error(
`[${request.method} ${request.url}] ${String(exception)}`,
exception instanceof Error ? exception.stack : undefined,
);
}
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),

View File

@@ -5,6 +5,7 @@ import {
UploadedFiles,
Body,
BadRequestException,
Logger,
} from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
@@ -12,6 +13,7 @@ import * as fs from 'fs';
import * as fsPromises from 'fs/promises';
import * as path from 'path';
import * as os from 'os';
import * as yaml from 'js-yaml';
import { execFile } from 'child_process';
import { promisify } from 'util';
import { randomUUID } from 'crypto';
@@ -30,6 +32,8 @@ import { ApiType } from '@datacat/shared';
@Controller('uploads')
export class UploadsController {
private readonly logger = new Logger(UploadsController.name);
constructor(
private readonly apisService: ApisService,
private readonly docsService: DocsGenerationService,
@@ -78,13 +82,19 @@ export class UploadsController {
let detectedType: ApiType | null = null;
if (files.length === 1) {
/* Cas simple : un seul fichier, comportement identique à l'ancien */
/* Cas simple : un seul fichier */
yamlContent = fs.readFileSync(files[0].path, 'utf-8');
/* Validation syntaxique YAML */
this.validateYamlSyntax(yamlContent, files[0].originalname);
detectedType = this.detectTypeFromContent(yamlContent);
} else {
/* Cas multi-fichiers : détecter le fichier principal et bundler */
const { mainFile, type } = this.detectMainFile(files);
detectedType = type;
this.logger.log(`Multi-file upload: main=${mainFile.originalname}, type=${type}, total=${files.length} files`);
yamlContent = await this.bundleFiles(files, mainFile, type);
}
@@ -115,6 +125,21 @@ export class UploadsController {
return entry;
}
/** Valide la syntaxe YAML et lève une BadRequestException si invalide */
private validateYamlSyntax(content: string, filename: string): void {
try {
const parsed = yaml.load(content);
if (parsed === null || typeof parsed !== 'object') {
throw new BadRequestException(`${filename}: le fichier YAML est vide ou invalide`);
}
} catch (err) {
if (err instanceof BadRequestException) throw err;
const msg = err instanceof Error ? err.message : String(err);
this.logger.warn(`Validation YAML échouée pour ${filename}: ${msg}`);
throw new BadRequestException(`${filename}: erreur de syntaxe YAML — ${msg}`);
}
}
private detectTypeFromContent(content: string): ApiType | null {
/* Cherche la clé racine openapi: ou asyncapi: sans dépendance à js-yaml */
if (/^openapi\s*:/m.test(content)) return 'OPENAPI';