101 lines
3.1 KiB
TypeScript
101 lines
3.1 KiB
TypeScript
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),
|
|
};
|
|
}
|