diff --git a/back/src/modules/apis/dto/create-api-entry.dto.ts b/back/src/modules/apis/dto/create-api-entry.dto.ts index 9a61d0a..4b5654b 100644 --- a/back/src/modules/apis/dto/create-api-entry.dto.ts +++ b/back/src/modules/apis/dto/create-api-entry.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsNotEmpty, IsIn, IsOptional, IsUUID, IsInt, Min, IsEmail } from 'class-validator'; +import { IsString, IsNotEmpty, IsIn, IsOptional, IsUUID, IsInt, Min, IsEmail, MaxLength } from 'class-validator'; import { Type } from 'class-transformer'; import { ApiType, ApiConvention, API_CONVENTIONS, API_TYPES } from '@datacat/shared'; @@ -8,10 +8,12 @@ export class CreateApiEntryDto { @IsString() @IsNotEmpty() + @MaxLength(255) name!: string; @IsString() @IsNotEmpty() + @MaxLength(255) provider!: string; @IsInt() @@ -46,23 +48,29 @@ export class CreateApiEntryDto { @IsString() @IsNotEmpty() + @MaxLength(255) contactFunctionalName!: string; @IsString() @IsNotEmpty() + @MaxLength(255) contactFunctionalEntity!: string; @IsEmail() + @MaxLength(255) contactFunctionalEmail!: string; @IsString() @IsNotEmpty() + @MaxLength(255) contactTechnicalName!: string; @IsString() @IsNotEmpty() + @MaxLength(255) contactTechnicalEntity!: string; @IsEmail() + @MaxLength(255) contactTechnicalEmail!: string; } diff --git a/back/src/modules/uploads/dto/create-api-upload.dto.ts b/back/src/modules/uploads/dto/create-api-upload.dto.ts new file mode 100644 index 0000000..e2a2e79 --- /dev/null +++ b/back/src/modules/uploads/dto/create-api-upload.dto.ts @@ -0,0 +1,96 @@ +import { Type } from 'class-transformer'; +import { + IsEmail, + IsIn, + IsInt, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, + MaxLength, + Min, + ValidateIf, +} from 'class-validator'; +import { ApiConvention, ApiType, API_CONVENTIONS, API_TYPES } from '@datacat/shared'; + +/** + * Corps du multipart POST /uploads. Les valeurs arrivent en `string` (form-data) + * et sont transformées par le ValidationPipe global (transform:true). + * + * Strictness alignée sur le comportement existant pour ne pas casser le front : + * seul `name` est réellement requis ; provider/contacts peuvent être vides ; les + * emails ne sont validés que s'ils sont renseignés (@ValidateIf). + */ +export class CreateApiUploadDto { + @IsIn([...API_CONVENTIONS]) + @IsOptional() + convention?: ApiConvention; + + @IsString() + @IsNotEmpty() + @MaxLength(255) + name!: string; + + @IsString() + @IsOptional() + @MaxLength(255) + provider?: string; + + @Type(() => Number) + @IsInt() + @Min(0) + @IsOptional() + versionMajor?: number; + + @Type(() => Number) + @IsInt() + @Min(0) + @IsOptional() + versionMinor?: number; + + @Type(() => Number) + @IsInt() + @Min(0) + @IsOptional() + versionPatch?: number; + + @IsString() + @IsOptional() + description?: string; + + @IsIn([...API_TYPES]) + @IsOptional() + type?: ApiType; + + @IsUUID() + @IsOptional() + categoryId?: string; + + @IsString() + @IsOptional() + @MaxLength(255) + contactFunctionalName?: string; + + @IsString() + @IsOptional() + @MaxLength(255) + contactFunctionalEntity?: string; + + @ValidateIf((o: CreateApiUploadDto) => !!o.contactFunctionalEmail) + @IsEmail() + contactFunctionalEmail?: string; + + @IsString() + @IsOptional() + @MaxLength(255) + contactTechnicalName?: string; + + @IsString() + @IsOptional() + @MaxLength(255) + contactTechnicalEntity?: string; + + @ValidateIf((o: CreateApiUploadDto) => !!o.contactTechnicalEmail) + @IsEmail() + contactTechnicalEmail?: string; +} diff --git a/back/src/modules/uploads/uploads.controller.ts b/back/src/modules/uploads/uploads.controller.ts index 2d18ec9..9891710 100644 --- a/back/src/modules/uploads/uploads.controller.ts +++ b/back/src/modules/uploads/uploads.controller.ts @@ -28,6 +28,7 @@ const execEnv = { import { ApisService } from '../apis/apis.service'; import { DocsGenerationService } from '../docs-generation/docs-generation.service'; import { ApiType } from '@datacat/shared'; +import { CreateApiUploadDto } from './dto/create-api-upload.dto'; @Controller('uploads') export class UploadsController { @@ -176,28 +177,8 @@ export class UploadsController { ) async upload( @UploadedFiles() files: Express.Multer.File[], - @Body() - body: { - convention: string; - name: string; - provider: string; - versionMajor: string; - versionMinor: string; - versionPatch: string; - description?: string; - type?: ApiType; - categoryId?: string; - contactFunctionalName: string; - contactFunctionalEntity: string; - contactFunctionalEmail: string; - contactTechnicalName: string; - contactTechnicalEntity: string; - contactTechnicalEmail: string; - }, + @Body() body: CreateApiUploadDto, ) { - if (!body.name) throw new BadRequestException('name is required'); - if (!body.convention) throw new BadRequestException('convention is required'); - /* Crée le répertoire si absent (mkdir recursive est idempotent) */ const uploadDir = path.join(os.tmpdir(), 'datacat-uploads'); await fsPromises.mkdir(uploadDir, { recursive: true }); @@ -232,22 +213,22 @@ export class UploadsController { } const entry = await this.apisService.create({ - convention: (body.convention as 'CONSULTER' | 'ENREGISTRER' | 'ETRE_NOTIFIE') ?? 'CONSULTER', + convention: body.convention ?? 'CONSULTER', name: body.name, - provider: body.provider, - versionMajor: parseInt(body.versionMajor, 10) || 0, - versionMinor: parseInt(body.versionMinor, 10) || 0, - versionPatch: parseInt(body.versionPatch, 10) || 0, + provider: body.provider ?? '', + versionMajor: body.versionMajor ?? 0, + versionMinor: body.versionMinor ?? 0, + versionPatch: body.versionPatch ?? 0, description: body.description, type: apiType, yamlContent, categoryId: body.categoryId || undefined, - contactFunctionalName: body.contactFunctionalName, - contactFunctionalEntity: body.contactFunctionalEntity, - contactFunctionalEmail: body.contactFunctionalEmail, - contactTechnicalName: body.contactTechnicalName, - contactTechnicalEntity: body.contactTechnicalEntity, - contactTechnicalEmail: body.contactTechnicalEmail, + contactFunctionalName: body.contactFunctionalName ?? '', + contactFunctionalEntity: body.contactFunctionalEntity ?? '', + contactFunctionalEmail: body.contactFunctionalEmail ?? '', + contactTechnicalName: body.contactTechnicalName ?? '', + contactTechnicalEntity: body.contactTechnicalEntity ?? '', + contactTechnicalEmail: body.contactTechnicalEmail ?? '', }); /* Fire-and-forget : génération asynchrone (seulement si YAML fourni) */