feat(api): admin account governance endpoints
P4a Task 6: GET /admin/users 双模式收口(无 accountPrefix 进入管理列表 模式:全量用户、query/status 过滤、createdAt+ID 倒序键集游标、仅 admin; 前缀模式 P1b 语义保留)+ PATCH / reset-password / activate / deactivate 治理端点(sourceStatus 只读拒绝、自我保护、会话撤销、审计、生成初始 密码)。附带修复 full-chain e2e 的 job-done 断言竞态(轮询条件补齐 photo_jobs 终态)。api e2e 214 → 227 全绿。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user