import { Type } from 'class-transformer'; import { ArrayMaxSize, IsArray, IsBoolean, IsIn, IsInt, IsOptional, IsString, Max, MaxLength, Min, MinLength, ValidateIf, } from 'class-validator'; import type { GamificationCondition, GamificationLevelDef } from '@mento/shared'; import { GAMIFICATION_EVENT_KEYS } from '@mento/shared'; const nullable = () => ValidateIf((_, v) => v !== '' && v != null); export class SaveRuleDto { @IsString() @MinLength(2) @MaxLength(120) name!: string; @IsOptional() @nullable() @IsString() @MaxLength(500) description?: string | null; @IsIn(GAMIFICATION_EVENT_KEYS) event!: (typeof GAMIFICATION_EVENT_KEYS)[number]; @Type(() => Number) @IsInt() @Min(-10000) @Max(100000) score!: number; @Type(() => Number) @IsInt() @Min(0) @Max(100000) coins!: number; @IsOptional() @IsArray() @ArrayMaxSize(10) conditions?: GamificationCondition[]; @IsOptional() @IsBoolean() isActive?: boolean; @IsOptional() @nullable() @Type(() => Number) @IsInt() @Min(1) maxPerDay?: number | null; @IsOptional() @nullable() @Type(() => Number) @IsInt() @Min(1) maxCoinsPerDay?: number | null; @IsOptional() @nullable() @Type(() => Number) @IsInt() @Min(1) maxPerUser?: number | null; @IsOptional() @nullable() @Type(() => Number) @IsInt() @Min(1) @Max(100000) cooldownMinutes?: number | null; @IsOptional() @IsBoolean() oncePerEntity?: boolean; @IsOptional() @IsBoolean() firstTimeOnly?: boolean; @IsOptional() @IsBoolean() requiresApproval?: boolean; @IsOptional() @IsArray() @ArrayMaxSize(10) @IsString({ each: true }) roles?: string[]; @IsOptional() @IsArray() @ArrayMaxSize(50) @IsString({ each: true }) teamIds?: string[]; } export class ToggleDto { @IsBoolean() isActive!: boolean; } export class AdjustDto { @IsString() userId!: string; @Type(() => Number) @IsInt() @Min(-1000000) @Max(1000000) amount!: number; @IsString() @MinLength(2) @MaxLength(500) reason!: string; } export class DecisionDto { @IsOptional() @nullable() @IsString() @MaxLength(500) note?: string | null; } export class RedeemDto { @IsString() rewardId!: string; @IsOptional() @nullable() @IsString() @MaxLength(500) note?: string | null; } export class SaveRewardDto { @IsString() @MinLength(2) @MaxLength(120) name!: string; @IsOptional() @nullable() @IsString() @MaxLength(500) description?: string | null; @Type(() => Number) @IsInt() @Min(1) @Max(10000000) cost!: number; @IsOptional() @IsString() @MaxLength(40) icon?: string; @IsOptional() @IsString() @MaxLength(20) color?: string; @IsOptional() @nullable() @Type(() => Number) @IsInt() @Min(0) stock?: number | null; @IsOptional() @IsBoolean() isActive?: boolean; @IsOptional() @IsBoolean() requiresApproval?: boolean; } export class SaveBadgeDto { @IsString() @MinLength(2) @MaxLength(120) name!: string; @IsOptional() @nullable() @IsString() @MaxLength(500) description?: string | null; @IsOptional() @IsString() @MaxLength(40) icon?: string; @IsOptional() @IsString() @MaxLength(20) color?: string; @IsIn(['event_count', 'score_total', 'routine_streak', 'single_deal_amount']) metric!: 'event_count' | 'score_total' | 'routine_streak' | 'single_deal_amount'; @IsOptional() @nullable() @IsIn(GAMIFICATION_EVENT_KEYS) event?: (typeof GAMIFICATION_EVENT_KEYS)[number] | null; @Type(() => Number) @IsInt() @Min(1) threshold!: number; @IsOptional() @Type(() => Number) @IsInt() @Min(0) @Max(1000000) coins?: number; @IsOptional() @IsBoolean() isActive?: boolean; } export class SaveBonusDto { @IsString() @MinLength(2) @MaxLength(120) name!: string; @IsOptional() @nullable() @IsString() @MaxLength(500) description?: string | null; @IsIn(['event_count_period', 'routine_streak', 'top_of_month']) kind!: 'event_count_period' | 'routine_streak' | 'top_of_month'; @IsOptional() @nullable() @IsIn(GAMIFICATION_EVENT_KEYS) event?: (typeof GAMIFICATION_EVENT_KEYS)[number] | null; @Type(() => Number) @IsInt() @Min(1) threshold!: number; @IsOptional() @IsIn(['day', 'week', 'month']) period?: 'day' | 'week' | 'month'; @IsOptional() @Type(() => Number) @IsInt() @Min(0) @Max(1000000) score?: number; @IsOptional() @Type(() => Number) @IsInt() @Min(0) @Max(1000000) coins?: number; @IsOptional() @IsBoolean() isActive?: boolean; } export class UpdateSettingsDto { @IsOptional() @IsBoolean() enabled?: boolean; @IsOptional() @IsArray() @ArrayMaxSize(20) levels?: GamificationLevelDef[]; @IsOptional() @Type(() => Number) @IsInt() @Min(1) @Max(10000000) monthlyGoal?: number; @IsOptional() @IsBoolean() showFullLeaderboard?: boolean; } export class TxQueryDto { @IsOptional() @IsString() userId?: string; @IsOptional() @IsString() kind?: string; @IsOptional() @IsString() event?: string; @IsOptional() @IsString() status?: string; @IsOptional() @Type(() => Number) @IsInt() @Min(1) page?: number; @IsOptional() @Type(() => Number) @IsInt() @Min(1) @Max(100) limit?: number; }