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

79 lines
2.8 KiB
TypeScript
Raw Normal View History

// services/api/src/admin/users-lookup.controller.ts —— /admin/users 端点
// (P1b Task 4)。方法名与契约 operationId 一一对应(lookupUsersByAccount)。
// 控制器级 SessionGuard:访问策略=任何已认证工作人员(契约描述的 P1b 决策,
// 见服务文件头),认证通过即可,无角色判定。控制器只做查询参数规整
// (accountPrefix 必填 2-64 字符;limit 缺省 10、1..20——本端点独立 bounds,
// 同公开照片列表在 public.controller 本地维护 parsePublicPhotoLimit 的先例,
// 勿混用 shared/pagination 的 1..100);语义在服务层。
import {
Controller,
Get,
HttpStatus,
Query,
UseGuards,
} from '@nestjs/common';
import { ErrorCodes } from '@photo/contracts';
import { ApiException } from '../api-exception';
import { SessionGuard } from '../auth/session.guard';
import { validationFailed } from '../albums/support';
import {
UsersLookupService,
type AdminUserLookupListResponse,
} from './users-lookup.service';
/** 契约 lookupUsersByAccount.limit:缺省 10,上下界 1..20。 */
export const DEFAULT_LOOKUP_LIMIT = 10;
export const MAX_LOOKUP_LIMIT = 20;
/** accountPrefix 查询参数:必填(缺失/空串 400),长度 2..64(契约 bounds)。 */
export function parseAccountPrefix(raw: string | undefined): string {
if (raw === undefined || raw === '') {
throw validationFailed('accountPrefix 必填(2-64 字符)');
}
if (raw.length < 2 || raw.length > 64) {
throw validationFailed('accountPrefix 须为 2-64 字符');
}
return raw;
}
/** limit 查询参数:缺省 10,须为 1..20 的十进制整数(契约 bounds)。 */
export function parseLookupLimit(raw: string | undefined): number {
if (raw === undefined || raw === '') {
return DEFAULT_LOOKUP_LIMIT;
}
if (!/^\d+$/.test(raw)) {
throw new ApiException(
ErrorCodes.VALIDATION_FAILED,
'limit 必须是 1-20 的整数',
HttpStatus.BAD_REQUEST,
);
}
const limit = Number.parseInt(raw, 10);
if (limit < 1 || limit > MAX_LOOKUP_LIMIT) {
throw new ApiException(
ErrorCodes.VALIDATION_FAILED,
'limit 必须是 1-20 的整数',
HttpStatus.BAD_REQUEST,
);
}
return limit;
}
@Controller('admin/users')
@UseGuards(SessionGuard)
export class UsersLookupController {
constructor(private readonly usersLookupService: UsersLookupService) {}
/** 账号前缀查询:激活用户脱敏列表(account 升序,无游标)。 */
@Get()
lookupUsersByAccount(
@Query('accountPrefix') accountPrefix: string | undefined,
@Query('limit') limit: string | undefined,
): Promise<AdminUserLookupListResponse> {
return this.usersLookupService.lookup(
parseAccountPrefix(accountPrefix),
parseLookupLimit(limit),
);
}
}