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:
@@ -29,7 +29,8 @@
|
||||
"class-validator": "^0.14.0",
|
||||
"class-transformer": "^0.5.0",
|
||||
"@asyncapi/cli": "6.0.0",
|
||||
"@asyncapi/bundler": "0.9.0",
|
||||
"@asyncapi/bundler": "^1.0.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"@asyncapi/generator": "3.0.1",
|
||||
"@asyncapi/html-template": "3.5.6",
|
||||
"@redocly/cli": "latest"
|
||||
@@ -40,6 +41,7 @@
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.0.0"
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -65,7 +65,8 @@
|
||||
},
|
||||
"defaultConfiguration": "development",
|
||||
"options": {
|
||||
"proxyConfig": "proxy.conf.json"
|
||||
"proxyConfig": "proxy.conf.json",
|
||||
"allowedHosts": ["datacat.dev.chmod777.dev"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11498
pnpm-lock.yaml
generated
11498
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -116,7 +116,7 @@ if command -v asyncapi &>/dev/null; then
|
||||
ok "asyncapi CLI déjà installé ($(asyncapi --version 2>/dev/null || echo 'version inconnue'))"
|
||||
else
|
||||
info "Installation de @asyncapi/cli@6.0.0..."
|
||||
npm install -g @asyncapi/cli@6.0.0
|
||||
npm install -g @asyncapi/cli@6.0.0 --legacy-peer-deps
|
||||
ok "asyncapi CLI installé"
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user