import { BadRequestException } from '@nestjs/common'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Allow } from 'class-validator'; import type { ContentStatus, UpdateContentStatusRequest, } from '@doodle/contracts'; import { WorksheetQueryDto, parseWorksheetQuery, } from '../worksheets/worksheet-query.dto'; import { contentStatus } from './content-policy'; /** 管理列表才接受 status;公开接口不复用此 DTO,避免开放草稿查询开关。 */ export class AdminWorksheetQueryDto extends WorksheetQueryDto { @Allow() @ApiPropertyOptional({ enum: ['draft', 'active', 'hidden'], description: '省略查询全部状态', }) declare status?: string; } /** 首期只提供状态写入,不开放未经白名单校验的渲染配置编辑。 */ export class UpdateContentStatusDto implements UpdateContentStatusRequest { @Allow() @ApiProperty({ enum: ['draft', 'active', 'hidden'] }) declare status: ContentStatus; } /** 资源管理列表仅支持分页、所属题型和状态,不隐式套用题型年龄/年级条件。 */ export class AdminResourceQueryDto { @Allow() @ApiPropertyOptional({ type: 'integer', default: 1, minimum: 1, maximum: 1000000, }) declare page?: string; @Allow() @ApiPropertyOptional({ type: 'integer', default: 20, minimum: 1, maximum: 100, }) declare pageSize?: string; @Allow() @ApiPropertyOptional({ maxLength: 128 }) declare worksheetId?: string; @Allow() @ApiPropertyOptional({ enum: ['draft', 'active', 'hidden'] }) declare status?: string; } /** 服务层同样检查字段白名单,防止非 HTTP 调用绕过状态参数限制。 */ export function parseAdminWorksheetQuery(input: AdminWorksheetQueryDto) { const { status, ...query } = input; return { query: parseWorksheetQuery(query), status: status === undefined ? undefined : contentStatus(status), }; } /** 严格解析资源分页和过滤;拒绝重复/未知字段,避免管理查询静默扩大范围。 */ export function parseAdminResourceQuery(input: AdminResourceQueryDto) { if ( Object.keys(input).some( (key) => !['page', 'pageSize', 'worksheetId', 'status'].includes(key), ) ) throw new BadRequestException(); const { page, pageSize } = parseWorksheetQuery({ ...(input.page === undefined ? {} : { page: input.page }), ...(input.pageSize === undefined ? {} : { pageSize: input.pageSize }), }); const worksheetId = input.worksheetId; if ( worksheetId !== undefined && (typeof worksheetId !== 'string' || !worksheetId.trim() || [...worksheetId].length > 128 || worksheetId.includes('\0')) ) throw new BadRequestException(); return { page, pageSize, worksheetId, status: input.status === undefined ? undefined : contentStatus(input.status), }; }