feat(#132): photo à la création enfant staff (multipart + bools).
Transform "true"/"false" pour class-validator ; multipart staff
avec FileInterceptor('photo') inchangé côté stockage.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
26e9738ec7
commit
f7ac628445
@ -1,4 +1,5 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { Transform } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsDateString,
|
IsDateString,
|
||||||
@ -12,6 +13,17 @@ import {
|
|||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { GenreType, StatutEnfantType } from 'src/entities/children.entity';
|
import { GenreType, StatutEnfantType } from 'src/entities/children.entity';
|
||||||
|
|
||||||
|
/** Multipart envoie des strings ("true"/"false") — JSON envoie déjà des booleans. */
|
||||||
|
function toBoolean({ value }: { value: unknown }): boolean | unknown {
|
||||||
|
if (typeof value === 'boolean') return value;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const v = value.trim().toLowerCase();
|
||||||
|
if (v === 'true' || v === '1') return true;
|
||||||
|
if (v === 'false' || v === '0' || v === '') return false;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
export class CreateEnfantsDto {
|
export class CreateEnfantsDto {
|
||||||
@ApiProperty({ enum: StatutEnfantType, example: StatutEnfantType.SANS_GARDE })
|
@ApiProperty({ enum: StatutEnfantType, example: StatutEnfantType.SANS_GARDE })
|
||||||
@IsEnum(StatutEnfantType)
|
@IsEnum(StatutEnfantType)
|
||||||
@ -53,6 +65,7 @@ export class CreateEnfantsDto {
|
|||||||
photo_url?: string;
|
photo_url?: string;
|
||||||
|
|
||||||
@ApiProperty({ default: false })
|
@ApiProperty({ default: false })
|
||||||
|
@Transform(toBoolean)
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
consent_photo: boolean;
|
consent_photo: boolean;
|
||||||
|
|
||||||
@ -62,6 +75,7 @@ export class CreateEnfantsDto {
|
|||||||
consent_photo_at?: string;
|
consent_photo_at?: string;
|
||||||
|
|
||||||
@ApiProperty({ default: false })
|
@ApiProperty({ default: false })
|
||||||
|
@Transform(toBoolean)
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
is_multiple: boolean;
|
is_multiple: boolean;
|
||||||
|
|
||||||
|
|||||||
@ -58,8 +58,8 @@ const photoMulterOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multer uniquement si Content-Type multipart (parcours parent).
|
* Multer uniquement si Content-Type multipart (parent ou staff + photo).
|
||||||
* Les appels staff JSON (#132) passent sans interceptor fichier.
|
* JSON sans photo (#132) passe sans interceptor fichier.
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class OptionalEnfantPhotoInterceptor implements NestInterceptor {
|
class OptionalEnfantPhotoInterceptor implements NestInterceptor {
|
||||||
@ -96,10 +96,15 @@ export class EnfantsController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: 'Créer un enfant',
|
summary: 'Créer un enfant',
|
||||||
description:
|
description:
|
||||||
'PARENT : multipart éventuel, rattache au compte connecté. Staff : JSON + parent_user_id (foyer). Ticket #132.',
|
'PARENT : multipart éventuel, rattache au compte connecté. ' +
|
||||||
|
'Staff : parent_user_id obligatoire ; JSON sans photo OK ; avec photo → multipart (champ fichier `photo`, max 5 Mo). Ticket #132.',
|
||||||
})
|
})
|
||||||
@ApiConsumes('application/json', 'multipart/form-data')
|
@ApiConsumes('application/json', 'multipart/form-data')
|
||||||
@ApiBody({ type: CreateEnfantsDto })
|
@ApiBody({
|
||||||
|
description:
|
||||||
|
'Champs métier (+ parent_user_id côté staff). Fichier optionnel `photo` en multipart.',
|
||||||
|
type: CreateEnfantsDto,
|
||||||
|
})
|
||||||
@UseInterceptors(OptionalEnfantPhotoInterceptor)
|
@UseInterceptors(OptionalEnfantPhotoInterceptor)
|
||||||
create(
|
create(
|
||||||
@Body() dto: CreateEnfantsDto,
|
@Body() dto: CreateEnfantsDto,
|
||||||
|
|||||||
@ -37,7 +37,8 @@ export class EnfantsService {
|
|||||||
/**
|
/**
|
||||||
* Création d'un enfant.
|
* Création d'un enfant.
|
||||||
* - PARENT : rattache au parent connecté (multipart photo optionnel).
|
* - PARENT : rattache au parent connecté (multipart photo optionnel).
|
||||||
* - Staff : JSON + `parent_user_id` obligatoire (foyer existant). Ticket #132.
|
* - Staff : `parent_user_id` obligatoire ; JSON sans photo OK ;
|
||||||
|
* avec photo → multipart (même stockage `/uploads/photos/...`). Ticket #132.
|
||||||
*/
|
*/
|
||||||
async create(
|
async create(
|
||||||
dto: CreateEnfantsDto,
|
dto: CreateEnfantsDto,
|
||||||
@ -67,7 +68,7 @@ export class EnfantsService {
|
|||||||
});
|
});
|
||||||
if (exist) throw new ConflictException('Cet enfant existe déjà');
|
if (exist) throw new ConflictException('Cet enfant existe déjà');
|
||||||
|
|
||||||
// Gestion de la photo uploadée (parcours parent multipart)
|
// Gestion de la photo uploadée (multipart parent ou staff)
|
||||||
let photoUrl = dto.photo_url;
|
let photoUrl = dto.photo_url;
|
||||||
let consentAt: Date | undefined;
|
let consentAt: Date | undefined;
|
||||||
if (photoFile) {
|
if (photoFile) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user