Files
photo-live/services/api/src/admin/admin-users.dto.ts
T

66 lines
2.3 KiB
TypeScript
Raw Normal View History

// services/api/src/admin/admin-users.dto.ts —— 账号治理端点入参 DTO(P4a
// Task 6)。契约 AdminUserUpdateRequest / AdminPasswordResetRequest。
// 校验语义要点:
// · 可选字段一律 @ValidateIf((_, value) => value !== undefined) 而非
// @IsOptional:后者连 null 一并跳过,显式 null 会以 null 值渗入服务层
// (Prisma 拒绝、错误形态不契约化);ValidateIf 只放行 undefined(真缺省),
// null 落到类型校验上 → 400 VALIDATION_FAILED,与「提供了非法值」同出口。
// · sourceStatus 只读(技术方案 3.1A 红线:上游状态仅由用户同步任务维护,
// 本地不越权改写):DTO 显式声明该字段 + 只读约束,出现(含 null)即
// 400。全局 ValidationPipe 是 whitelist(剥离而非拒绝)——若不声明,
// sourceStatus 会被静默剥离成「忽略」,与只读拒绝语义不符。
import {
IsBoolean,
IsIn,
IsString,
MaxLength,
MinLength,
Validate,
ValidateIf,
ValidationArguments,
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
/** 只读字段约束:仅 undefined(真缺省)放行,任何显式值(含 null)拒绝。 */
@ValidatorConstraint({ name: 'adminUserReadOnlyField', async: false })
class ReadOnlyFieldConstraint implements ValidatorConstraintInterface {
validate(value: unknown): boolean {
return value === undefined;
}
defaultMessage(args: ValidationArguments): string {
return `${args.property} 为只读字段,不能经本端点修改`;
}
}
export class AdminUserUpdateDto {
@ValidateIf((_, value) => value !== undefined)
@IsIn(['staff', 'admin'])
role?: 'staff' | 'admin';
@ValidateIf((_, value) => value !== undefined)
@IsBoolean()
localLocked?: boolean;
@ValidateIf((_, value) => value !== undefined)
@IsString()
@MinLength(1)
@MaxLength(128)
displayName?: string;
/** sourceStatus 不在可更新集(契约 AdminUserUpdateRequest 无此字段)。 */
@ValidateIf(() => true)
@Validate(ReadOnlyFieldConstraint)
declare sourceStatus?: unknown;
}
export class AdminPasswordResetDto {
/** 契约 bounds:8-128;缺省由服务端生成随机初始密码。 */
@ValidateIf((_, value) => value !== undefined)
@IsString()
@MinLength(8)
@MaxLength(128)
newPassword?: string;
}