From 355f08e9275f31aa2fcf920bb9b69e94d8631a58 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 21 Aug 2025 08:53:55 +0300 Subject: [PATCH] PM-1504 - weight sum validator --- src/dto/scorecard.dto.ts | 6 +++++ src/shared/validators/customValidators.ts | 31 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/dto/scorecard.dto.ts b/src/dto/scorecard.dto.ts index e111a04..e52f737 100644 --- a/src/dto/scorecard.dto.ts +++ b/src/dto/scorecard.dto.ts @@ -16,6 +16,7 @@ import { import { IsGreaterThan, IsSmallerThan, + WeightSum, } from 'src/shared/validators/customValidators'; export enum ScorecardStatus { @@ -148,6 +149,7 @@ export class ScorecardSectionRequestDto extends ScorecardSectionBaseDto { @IsArray() @ValidateNested({ each: true }) @Type(() => ScorecardQuestionRequestDto) + @WeightSum() questions: ScorecardQuestionRequestDto[]; } @@ -193,6 +195,7 @@ export class ScorecardGroupRequestDto extends ScorecardGroupBaseDto { @IsArray() @ValidateNested({ each: true }) @Type(() => ScorecardSectionRequestDto) + @WeightSum() sections: ScorecardSectionRequestDto[]; } @@ -231,6 +234,7 @@ export class ScorecardBaseDto { @ApiProperty({ description: 'The challenge type', example: 'Code' }) @IsString() + @IsNotEmpty() challengeType: string; @ApiProperty({ @@ -238,6 +242,7 @@ export class ScorecardBaseDto { example: 'Sample Scorecard', }) @IsString() + @IsNotEmpty() name: string; @ApiProperty({ description: 'The version of the scorecard', example: '1.0' }) @@ -280,6 +285,7 @@ export class ScorecardRequestDto extends ScorecardBaseWithGroupsDto { @IsArray() @ValidateNested({ each: true }) // validate each item in the array @Type(() => ScorecardGroupRequestDto) + @WeightSum() scorecardGroups: ScorecardGroupRequestDto[]; } diff --git a/src/shared/validators/customValidators.ts b/src/shared/validators/customValidators.ts index fa012d0..7d9a47b 100644 --- a/src/shared/validators/customValidators.ts +++ b/src/shared/validators/customValidators.ts @@ -1,4 +1,5 @@ import { + isArray, registerDecorator, ValidationOptions, ValidationArguments, @@ -85,3 +86,33 @@ export function IsSmallerThan( }, ); } + +export function WeightSum(sum = 100, validationOptions?: ValidationOptions) { + return function (object: object, propertyName: string) { + registerDecorator({ + name: 'WeightSum', + target: object.constructor, + propertyName: propertyName, + constraints: [sum], + options: validationOptions, + validator: { + validate(value: { weight: number }[], args: ValidationArguments) { + const [totalSum = 100] = args.constraints as [number]; + + if (!isArray(value)) return false; + + const sum = value.reduce((acc, item) => { + const weight = typeof item?.weight === 'number' ? item.weight : 0; + return acc + weight; + }, 0); + return sum === totalSum; + }, + + defaultMessage(args: ValidationArguments) { + const [totalSum = 100] = args.constraints as [number]; + return `The sum of all weights must be exactly ${totalSum}`; + }, + }, + }); + }; +}