|
|
@@ -12,12 +12,19 @@ import {
|
|
|
MaxLength,
|
|
|
} from 'class-validator';
|
|
|
import { Type } from 'class-transformer';
|
|
|
+import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
|
import { PageListDto } from '@box/common/dto/page-list.dto';
|
|
|
|
|
|
export class VideoMediaListQueryDto extends PageListDto {
|
|
|
/**
|
|
|
* 搜索关键词:匹配标题、文件名等(具体逻辑在 service 里实现)
|
|
|
*/
|
|
|
+ @ApiPropertyOptional({
|
|
|
+ type: String,
|
|
|
+ maxLength: 100,
|
|
|
+ description: '搜索关键词,用于匹配视频标题、文件名等',
|
|
|
+ example: '产品演示视频',
|
|
|
+ })
|
|
|
@IsOptional()
|
|
|
@IsString()
|
|
|
@MaxLength(100)
|
|
|
@@ -26,6 +33,11 @@ export class VideoMediaListQueryDto extends PageListDto {
|
|
|
/**
|
|
|
* 分类过滤:可选
|
|
|
*/
|
|
|
+ @ApiPropertyOptional({
|
|
|
+ type: String,
|
|
|
+ description: '视频分类 MongoDB ID',
|
|
|
+ example: '507f1f77bcf86cd799439011',
|
|
|
+ })
|
|
|
@IsOptional()
|
|
|
@IsMongoId()
|
|
|
categoryId?: string;
|
|
|
@@ -33,6 +45,11 @@ export class VideoMediaListQueryDto extends PageListDto {
|
|
|
/**
|
|
|
* 标签过滤(通常前端只会传一个 tagId)
|
|
|
*/
|
|
|
+ @ApiPropertyOptional({
|
|
|
+ type: String,
|
|
|
+ description: '视频标签 MongoDB ID',
|
|
|
+ example: '507f1f77bcf86cd799439012',
|
|
|
+ })
|
|
|
@IsOptional()
|
|
|
@IsMongoId()
|
|
|
tagId?: string;
|
|
|
@@ -41,6 +58,12 @@ export class VideoMediaListQueryDto extends PageListDto {
|
|
|
* 上/下架状态过滤
|
|
|
* 0 = 下架, 1 = 上架
|
|
|
*/
|
|
|
+ @ApiPropertyOptional({
|
|
|
+ type: Number,
|
|
|
+ enum: [0, 1],
|
|
|
+ description: '上下架状态过滤: 0=下架, 1=上架',
|
|
|
+ example: 1,
|
|
|
+ })
|
|
|
@IsOptional()
|
|
|
@Type(() => Number)
|
|
|
@IsInt()
|
|
|
@@ -49,6 +72,12 @@ export class VideoMediaListQueryDto extends PageListDto {
|
|
|
}
|
|
|
|
|
|
export class UpdateVideoMediaManageDto {
|
|
|
+ @ApiPropertyOptional({
|
|
|
+ type: String,
|
|
|
+ maxLength: 200,
|
|
|
+ description: '视频标题',
|
|
|
+ example: '产品演示视频 2025',
|
|
|
+ })
|
|
|
@IsOptional()
|
|
|
@IsString()
|
|
|
@MaxLength(200)
|
|
|
@@ -57,6 +86,12 @@ export class UpdateVideoMediaManageDto {
|
|
|
/**
|
|
|
* 分类 ID,可为空(取消分类)
|
|
|
*/
|
|
|
+ @ApiPropertyOptional({
|
|
|
+ type: String,
|
|
|
+ nullable: true,
|
|
|
+ description: '视频分类 MongoDB ID,设为 null 可取消分类',
|
|
|
+ example: '507f1f77bcf86cd799439011',
|
|
|
+ })
|
|
|
@IsOptional()
|
|
|
@IsMongoId()
|
|
|
categoryId?: string | null;
|
|
|
@@ -64,6 +99,12 @@ export class UpdateVideoMediaManageDto {
|
|
|
/**
|
|
|
* 标签 ID 列表,最多 5 个
|
|
|
*/
|
|
|
+ @ApiPropertyOptional({
|
|
|
+ type: [String],
|
|
|
+ maxItems: 5,
|
|
|
+ description: '视频标签 MongoDB ID 列表,最多 5 个',
|
|
|
+ example: ['507f1f77bcf86cd799439012', '507f1f77bcf86cd799439013'],
|
|
|
+ })
|
|
|
@IsOptional()
|
|
|
@IsArray()
|
|
|
@IsMongoId({ each: true })
|
|
|
@@ -73,6 +114,12 @@ export class UpdateVideoMediaManageDto {
|
|
|
* 上/下架状态,也可以在这里一起保存(可选)
|
|
|
* 0 = 下架, 1 = 上架
|
|
|
*/
|
|
|
+ @ApiPropertyOptional({
|
|
|
+ type: Number,
|
|
|
+ enum: [0, 1],
|
|
|
+ description: '上下架状态: 0=下架, 1=上架',
|
|
|
+ example: 1,
|
|
|
+ })
|
|
|
@IsOptional()
|
|
|
@Type(() => Number)
|
|
|
@IsInt()
|
|
|
@@ -81,6 +128,12 @@ export class UpdateVideoMediaManageDto {
|
|
|
}
|
|
|
|
|
|
export class UpdateVideoMediaStatusDto {
|
|
|
+ @ApiProperty({
|
|
|
+ type: Number,
|
|
|
+ enum: [0, 1],
|
|
|
+ description: '上下架状态: 0=下架, 1=上架',
|
|
|
+ example: 1,
|
|
|
+ })
|
|
|
@Type(() => Number)
|
|
|
@IsInt()
|
|
|
@IsIn([0, 1])
|
|
|
@@ -88,10 +141,21 @@ export class UpdateVideoMediaStatusDto {
|
|
|
}
|
|
|
|
|
|
export class BatchUpdateVideoMediaStatusDto {
|
|
|
+ @ApiProperty({
|
|
|
+ type: [String],
|
|
|
+ description: 'MongoDB 视频 ID 列表',
|
|
|
+ example: ['507f1f77bcf86cd799439011', '507f1f77bcf86cd799439012'],
|
|
|
+ })
|
|
|
@IsArray()
|
|
|
@IsMongoId({ each: true })
|
|
|
ids!: string[];
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: Number,
|
|
|
+ enum: [0, 1],
|
|
|
+ description: '上下架状态: 0=下架, 1=上架',
|
|
|
+ example: 1,
|
|
|
+ })
|
|
|
@Type(() => Number)
|
|
|
@IsInt()
|
|
|
@IsIn([0, 1])
|
|
|
@@ -99,32 +163,68 @@ export class BatchUpdateVideoMediaStatusDto {
|
|
|
}
|
|
|
|
|
|
export class UpdateVideoMediaCoverResponseDto {
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '视频 MongoDB ID',
|
|
|
+ example: '507f1f77bcf86cd799439011',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
id!: string;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '新的封面图片 URL 或 S3 key',
|
|
|
+ example: 'https://s3.example.com/covers/507f1f77bcf86cd799439011.jpg',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
coverImg!: string;
|
|
|
|
|
|
/**
|
|
|
* 新 editedAt 值
|
|
|
*/
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ format: 'date-time',
|
|
|
+ description: '更新后的编辑时间戳(ISO 8601 格式)',
|
|
|
+ example: '2025-12-04T10:30:00.000Z',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
editedAt!: string;
|
|
|
}
|
|
|
|
|
|
export class VideoMediaListItemDto {
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '视频 MongoDB ID',
|
|
|
+ example: '507f1f77bcf86cd799439011',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
id!: string;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '视频标题',
|
|
|
+ example: '产品演示视频',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
title!: string;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '视频文件名',
|
|
|
+ example: 'product-demo.mp4',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
filename!: string;
|
|
|
|
|
|
/**
|
|
|
* 视频时长(秒)
|
|
|
*/
|
|
|
+ @ApiProperty({
|
|
|
+ type: Number,
|
|
|
+ description: '视频时长(秒)',
|
|
|
+ example: 120,
|
|
|
+ })
|
|
|
@Type(() => Number)
|
|
|
@IsInt()
|
|
|
videoTime!: number;
|
|
|
@@ -132,18 +232,34 @@ export class VideoMediaListItemDto {
|
|
|
/**
|
|
|
* 文件大小(业务上 BigInt 存储,DTO 建议用字符串避免精度问题)
|
|
|
*/
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '文件大小(字节),以字符串形式存储避免精度问题',
|
|
|
+ example: '1048576000',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
size!: string;
|
|
|
|
|
|
/**
|
|
|
* 封面 URL / key
|
|
|
*/
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '视频封面 URL 或 S3 key',
|
|
|
+ example: 'https://s3.example.com/covers/507f1f77bcf86cd799439011.jpg',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
coverImg!: string;
|
|
|
|
|
|
/**
|
|
|
* 分类 ID,可空
|
|
|
*/
|
|
|
+ @ApiPropertyOptional({
|
|
|
+ type: String,
|
|
|
+ nullable: true,
|
|
|
+ description: '视频分类 MongoDB ID',
|
|
|
+ example: '507f1f77bcf86cd799439012',
|
|
|
+ })
|
|
|
@IsOptional()
|
|
|
@IsMongoId()
|
|
|
categoryId?: string | null;
|
|
|
@@ -151,6 +267,11 @@ export class VideoMediaListItemDto {
|
|
|
/**
|
|
|
* 当前选中的标签 ID 列表(最多 5 个)
|
|
|
*/
|
|
|
+ @ApiProperty({
|
|
|
+ type: [String],
|
|
|
+ description: '视频标签 MongoDB ID 列表',
|
|
|
+ example: ['507f1f77bcf86cd799439013', '507f1f77bcf86cd799439014'],
|
|
|
+ })
|
|
|
@IsArray()
|
|
|
@IsMongoId({ each: true })
|
|
|
tagIds!: string[];
|
|
|
@@ -158,6 +279,12 @@ export class VideoMediaListItemDto {
|
|
|
/**
|
|
|
* 上/下架状态 0 = 下架, 1 = 上架
|
|
|
*/
|
|
|
+ @ApiProperty({
|
|
|
+ type: Number,
|
|
|
+ enum: [0, 1],
|
|
|
+ description: '上下架状态: 0=下架, 1=上架',
|
|
|
+ example: 1,
|
|
|
+ })
|
|
|
@Type(() => Number)
|
|
|
@IsInt()
|
|
|
@IsIn([0, 1])
|
|
|
@@ -166,76 +293,179 @@ export class VideoMediaListItemDto {
|
|
|
/**
|
|
|
* 本地编辑时间(BigInt epoch)— 字符串返回
|
|
|
*/
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ format: 'date-time',
|
|
|
+ description: '编辑时间戳(ISO 8601 格式)',
|
|
|
+ example: '2025-12-04T10:30:00.000Z',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
editedAt!: string;
|
|
|
}
|
|
|
|
|
|
export class VideoMediaDetailDto {
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '视频 MongoDB ID',
|
|
|
+ example: '507f1f77bcf86cd799439011',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
id!: string;
|
|
|
|
|
|
// --- Provider fields (read-only for mgnt) ---
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '视频标题',
|
|
|
+ example: '产品演示视频',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
title!: string;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '视频文件名',
|
|
|
+ example: 'product-demo.mp4',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
filename!: string;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: Number,
|
|
|
+ description: '视频时长(秒)',
|
|
|
+ example: 120,
|
|
|
+ })
|
|
|
@Type(() => Number)
|
|
|
@IsInt()
|
|
|
videoTime!: number;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '文件大小(字节),以字符串形式存储避免精度问题',
|
|
|
+ example: '1048576000',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
- size!: string; // from BigInt
|
|
|
+ size!: string;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '视频封面 URL 或 S3 key',
|
|
|
+ example: 'https://s3.example.com/covers/507f1f77bcf86cd799439011.jpg',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
coverImg!: string;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '视频类型',
|
|
|
+ example: 'video/mp4',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
type!: string;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: Number,
|
|
|
+ description: '视频格式类型编码',
|
|
|
+ example: 1,
|
|
|
+ })
|
|
|
@Type(() => Number)
|
|
|
@IsInt()
|
|
|
formatType!: number;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: Number,
|
|
|
+ description: '内容类型编码',
|
|
|
+ example: 1,
|
|
|
+ })
|
|
|
@Type(() => Number)
|
|
|
@IsInt()
|
|
|
contentType!: number;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '国家/地区代码',
|
|
|
+ example: 'CN',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
country!: string;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '视频状态',
|
|
|
+ example: 'active',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
status!: string;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ description: '视频描述',
|
|
|
+ example: '这是一个产品演示视频',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
desc!: string;
|
|
|
|
|
|
// --- Local business fields ---
|
|
|
|
|
|
+ @ApiPropertyOptional({
|
|
|
+ type: String,
|
|
|
+ nullable: true,
|
|
|
+ description: '视频分类 MongoDB ID',
|
|
|
+ example: '507f1f77bcf86cd799439012',
|
|
|
+ })
|
|
|
@IsOptional()
|
|
|
@IsMongoId()
|
|
|
categoryId?: string | null;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: [String],
|
|
|
+ description: '视频标签 MongoDB ID 列表',
|
|
|
+ example: ['507f1f77bcf86cd799439013', '507f1f77bcf86cd799439014'],
|
|
|
+ })
|
|
|
@IsArray()
|
|
|
@IsMongoId({ each: true })
|
|
|
tagIds!: string[];
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: Number,
|
|
|
+ enum: [0, 1],
|
|
|
+ description: '上下架状态: 0=下架, 1=上架',
|
|
|
+ example: 1,
|
|
|
+ })
|
|
|
@Type(() => Number)
|
|
|
@IsInt()
|
|
|
@IsIn([0, 1])
|
|
|
listStatus!: number;
|
|
|
|
|
|
+ @ApiProperty({
|
|
|
+ type: String,
|
|
|
+ format: 'date-time',
|
|
|
+ description: '编辑时间戳(ISO 8601 格式)',
|
|
|
+ example: '2025-12-04T10:30:00.000Z',
|
|
|
+ })
|
|
|
@IsString()
|
|
|
editedAt!: string;
|
|
|
|
|
|
// --- Optional denormalized information for UI convenience ---
|
|
|
|
|
|
+ @ApiPropertyOptional({
|
|
|
+ type: String,
|
|
|
+ nullable: true,
|
|
|
+ description: '分类名称(反范式化字段,用于 UI 便利性)',
|
|
|
+ example: '产品介绍',
|
|
|
+ })
|
|
|
@IsOptional()
|
|
|
@IsString()
|
|
|
categoryName?: string | null;
|
|
|
|
|
|
+ @ApiPropertyOptional({
|
|
|
+ type: [Object],
|
|
|
+ description: '标签对象数组(反范式化字段,包含 id 和 name)',
|
|
|
+ example: [
|
|
|
+ { id: '507f1f77bcf86cd799439013', name: '演示' },
|
|
|
+ { id: '507f1f77bcf86cd799439014', name: '产品' },
|
|
|
+ ],
|
|
|
+ })
|
|
|
@IsOptional()
|
|
|
@IsArray()
|
|
|
tags?: { id: string; name: string }[];
|