From 963c78c35abac6ae46e9b533fb5113952b3891de Mon Sep 17 00:00:00 2001 From: geonhee-min Date: Wed, 17 Dec 2025 09:49:42 +0900 Subject: [PATCH] =?UTF-8?q?issue=20#=20-=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EB=8D=B0=EC=BD=94=EB=A0=88=EC=9D=B4=ED=84=B0,=20class-validato?= =?UTF-8?q?r=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/scheduler/decorator/composer.ts | 12 +++++ src/scheduler/decorator/required-decorator.ts | 11 +++++ src/scheduler/decorator/valid-decorator.ts | 23 +++++++++ .../schedule/detail/detail-response.dto.ts | 48 ++++++++++++++++++- .../dto/schedule/list/list-response.dto.ts | 21 ++++++++ .../type/class-validator/MessageType.ts | 10 ++++ 6 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/scheduler/decorator/composer.ts create mode 100644 src/scheduler/decorator/required-decorator.ts create mode 100644 src/scheduler/decorator/valid-decorator.ts create mode 100644 src/scheduler/type/class-validator/MessageType.ts diff --git a/src/scheduler/decorator/composer.ts b/src/scheduler/decorator/composer.ts new file mode 100644 index 0000000..a951b4b --- /dev/null +++ b/src/scheduler/decorator/composer.ts @@ -0,0 +1,12 @@ +type Decorator = (target: Object, propertyKey: string | symbol) => void; + +export function composeDecorators(...decorators: Decorator[]): Decorator { + return ( + target: Object, + property: string | symbol + ) => { + for (const decorator of decorators) { + decorator(target, property); + } + } +} \ No newline at end of file diff --git a/src/scheduler/decorator/required-decorator.ts b/src/scheduler/decorator/required-decorator.ts new file mode 100644 index 0000000..199445d --- /dev/null +++ b/src/scheduler/decorator/required-decorator.ts @@ -0,0 +1,11 @@ +import { getMessage } from "../type/class-validator/MessageType"; +import { composeDecorators } from "./composer"; +import { IsNotEmpty } from "class-validator"; + +export function IsRequired() { + const notEmptyMessage = getMessage('REQUIRED'); + + return composeDecorators( + IsNotEmpty({ message: notEmptyMessage }) + ); +} \ No newline at end of file diff --git a/src/scheduler/decorator/valid-decorator.ts b/src/scheduler/decorator/valid-decorator.ts new file mode 100644 index 0000000..aac2399 --- /dev/null +++ b/src/scheduler/decorator/valid-decorator.ts @@ -0,0 +1,23 @@ +import { getMessage } from "../type/class-validator/MessageType"; +import { composeDecorators } from "./composer"; +import { IsIn, IsArray } from "class-validator"; + +export function IsValid(allowedValues: ReadonlyArray, isArray: boolean = false) { + const allowedValuesString = allowedValues.join(', '); + + if (isArray) { + return composeDecorators( + IsArray({ message: `TYPE: The type of $property must be array.`}), + IsIn(allowedValues, { + each: true, + message: `${getMessage('INVALID_ARRAY')} Valid values: ${allowedValuesString}` + }) + ); + } + + return composeDecorators( + IsIn(allowedValues, { + message: `${getMessage('INVALID')} Valid values: ${allowedValuesString}` + }) + ); +} \ No newline at end of file diff --git a/src/scheduler/http/dto/schedule/detail/detail-response.dto.ts b/src/scheduler/http/dto/schedule/detail/detail-response.dto.ts index ffe31c8..31e98a6 100644 --- a/src/scheduler/http/dto/schedule/detail/detail-response.dto.ts +++ b/src/scheduler/http/dto/schedule/detail/detail-response.dto.ts @@ -2,26 +2,72 @@ import type { BaseResponseDTO } from '@BaseResponseDTO'; import type { Status } from 'src/scheduler/type/schedule/ScheduleStatus'; import type { Type } from 'src/scheduler/type/schedule/ScheduleType'; import { Type as TransformType } from 'class-transformer'; +import { IsRequired } from 'src/scheduler/decorator/required-decorator'; +import { IsArray, IsBoolean, IsDate, IsOptional, IsString, ValidateIf } from 'class-validator'; +import { getMessage } from 'src/scheduler/type/class-validator/MessageType'; export class ScheduleDetail { + @IsRequired() + @IsString({ message: getMessage('TYPE')}) id!: string; + + @IsRequired() + @IsString({ message: getMessage('TYPE')}) name!: string; + + @IsRequired() + @IsString({ message: getMessage('TYPE') }) status!: Status; + + @ValidateIf(o => o.content !== undefined && o.content !== null) + @IsString({ message: getMessage('TYPE') }) content?: string; + + @IsRequired() + @IsBoolean({ message: getMessage('TYPE')}) isDeleted!: boolean; + + @IsRequired() + @IsString({ message: getMessage('TYPE') }) type!: Type; + + @IsRequired() + @IsString({ message: getMessage('TYPE') }) createdAt!: string; + + @IsRequired() + @IsString({ message: getMessage('TYPE') }) owner!: string; + + @IsRequired() + @IsString({ message: getMessage('TYPE') }) style!: string; + + @IsRequired() + @IsString({ message: getMessage('TYPE') }) startTime!: string; + + @IsRequired() + @IsString({ message: getMessage('TYPE') }) endTime!: string; + + @ValidateIf(o => o.dayList !== undefined && o.dayList !== null) + @IsString({ message: getMessage('TYPE') }) dayList?: string; - participantList?: string; + + @ValidateIf(o => o.participantList !== undefined && o.participantList !== null) + @IsArray() + @IsString({ each: true, message: getMessage('TYPE') }) + participantList?: string[]; @TransformType(() => Date) + @IsRequired() + @IsDate({ message: getMessage('TYPE') }) startDate!: Date; @TransformType(() => Date) + @IsRequired() + @IsDate({ message: getMessage('TYPE') }) endDate!: Date; } diff --git a/src/scheduler/http/dto/schedule/list/list-response.dto.ts b/src/scheduler/http/dto/schedule/list/list-response.dto.ts index 898a5d9..2169922 100644 --- a/src/scheduler/http/dto/schedule/list/list-response.dto.ts +++ b/src/scheduler/http/dto/schedule/list/list-response.dto.ts @@ -2,18 +2,39 @@ import type { BaseResponseDTO } from '@BaseResponseDTO'; import type { Status } from "src/scheduler/type/schedule/ScheduleStatus"; import type { Type } from "src/scheduler/type/schedule/ScheduleType"; import { Type as TransformType } from 'class-transformer'; +import { IsDate, IsString } from 'class-validator'; +import { IsRequired } from 'src/scheduler/decorator/required-decorator'; +import { getMessage } from 'src/scheduler/type/class-validator/MessageType'; export class ScheduleList { + @IsRequired() + @IsString({ message: getMessage('TYPE') }) name!: string; + + @IsRequired() + @IsString({ message: getMessage('TYPE')}) id!: string; + + @IsRequired() + @IsString({ message: getMessage('TYPE')}) type!: Type; + + @IsRequired() + @IsString({ message: getMessage('TYPE')}) style!: string; + + @IsRequired() + @IsString({ message: getMessage('TYPE')}) status!: Status; @TransformType(() => Date) + @IsRequired() + @IsDate() startDate!: Date; @TransformType(() => Date) + @IsRequired() + @IsDate() endDate!: Date; } diff --git a/src/scheduler/type/class-validator/MessageType.ts b/src/scheduler/type/class-validator/MessageType.ts new file mode 100644 index 0000000..0955473 --- /dev/null +++ b/src/scheduler/type/class-validator/MessageType.ts @@ -0,0 +1,10 @@ +export type MessageType = 'REQUIRED' | 'TYPE' | 'INVALID' | 'INVALID_ARRAY'; + +export const getMessage = (type: MessageType) => { + switch (type) { + case 'REQUIRED': return `REQUIRED: $property is required entity.`; + case 'TYPE': return `TYPE: The type of $property is invalid.`; + case 'INVALID': return `INVALID: The value of $property($value) is invalid.`; + case 'INVALID_ARRAY': return `INVALID_ARRAY: The $property array includes invalid value($value).` + } +} \ No newline at end of file