video-media.dto.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. // video-media.dto.ts
  2. import {
  3. IsInt,
  4. IsOptional,
  5. IsString,
  6. IsIn,
  7. IsArray,
  8. IsMongoId,
  9. Min,
  10. Max,
  11. MaxLength,
  12. } from 'class-validator';
  13. import { Type } from 'class-transformer';
  14. import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
  15. import { PageListDto } from '@box/common/dto/page-list.dto';
  16. export class VideoMediaListQueryDto extends PageListDto {
  17. /**
  18. * 搜索关键词:匹配标题、文件名等(具体逻辑在 service 里实现)
  19. */
  20. @ApiPropertyOptional({
  21. type: String,
  22. maxLength: 100,
  23. description: '搜索关键词,用于匹配视频标题、文件名等',
  24. example: '产品演示视频',
  25. })
  26. @IsOptional()
  27. @IsString()
  28. @MaxLength(100)
  29. keyword?: string;
  30. // search tags
  31. @ApiPropertyOptional({
  32. type: String,
  33. maxLength: 100,
  34. description: '搜索标签关键词,用于匹配视频标签',
  35. example: '演示',
  36. })
  37. @IsOptional()
  38. @IsString()
  39. @MaxLength(100)
  40. tag?: string;
  41. /**
  42. * 上/下架状态过滤
  43. * 0 = 下架, 1 = 上架
  44. */
  45. @ApiPropertyOptional({
  46. type: Number,
  47. enum: [0, 1],
  48. description: '上下架状态过滤: 0=下架, 1=上架',
  49. example: 1,
  50. })
  51. @IsOptional()
  52. @Type(() => Number)
  53. @IsInt()
  54. @IsIn([0, 1])
  55. listStatus?: number;
  56. /**
  57. * 搜索 更新时间 范围 - 开始时间(EPOCH 秒)
  58. */
  59. @ApiPropertyOptional({
  60. type: Number,
  61. description: '更新时间范围过滤的开始时间(EPOCH 毫秒)',
  62. example: 1700000000,
  63. })
  64. @IsOptional()
  65. @Type(() => Number)
  66. @IsInt()
  67. @Min(0)
  68. updatedFrom?: number;
  69. /**
  70. * 搜索 更新时间 范围 - 结束时间(EPOCH 秒)
  71. */
  72. @ApiPropertyOptional({
  73. type: Number,
  74. description: '更新时间范围过滤的结束时间(EPOCH 毫秒)',
  75. example: 1705000000,
  76. })
  77. @IsOptional()
  78. @Type(() => Number)
  79. @IsInt()
  80. @Min(0)
  81. updatedTo?: number;
  82. }
  83. export class UpdateVideoMediaManageDto {
  84. @ApiPropertyOptional({
  85. type: String,
  86. maxLength: 200,
  87. description: '视频标题',
  88. example: '产品演示视频 2025',
  89. })
  90. @IsOptional()
  91. @IsString()
  92. @MaxLength(200)
  93. title?: string;
  94. /**
  95. * 分类 ID,可为空(取消分类)
  96. */
  97. @ApiPropertyOptional({
  98. type: String,
  99. nullable: true,
  100. description: '视频分类 MongoDB ID,设为 null 可取消分类',
  101. example: '507f1f77bcf86cd799439011',
  102. })
  103. @IsOptional()
  104. @IsMongoId()
  105. categoryId?: string | null;
  106. /**
  107. * 标签 ID 列表,最多 5 个
  108. */
  109. @ApiPropertyOptional({
  110. type: [String],
  111. maxItems: 5,
  112. description: '视频标签 MongoDB ID 列表,最多 5 个',
  113. example: ['507f1f77bcf86cd799439012', '507f1f77bcf86cd799439013'],
  114. })
  115. @IsOptional()
  116. @IsArray()
  117. @IsMongoId({ each: true })
  118. tagIds?: string[];
  119. /**
  120. * 上/下架状态,也可以在这里一起保存(可选)
  121. * 0 = 下架, 1 = 上架
  122. */
  123. @ApiPropertyOptional({
  124. type: Number,
  125. enum: [0, 1],
  126. description: '上下架状态: 0=下架, 1=上架',
  127. example: 1,
  128. })
  129. @IsOptional()
  130. @Type(() => Number)
  131. @IsInt()
  132. @IsIn([0, 1])
  133. listStatus?: number;
  134. }
  135. export class UpdateVideoMediaStatusDto {
  136. @ApiProperty({
  137. type: Number,
  138. enum: [0, 1],
  139. description: '上下架状态: 0=下架, 1=上架',
  140. example: 1,
  141. })
  142. @Type(() => Number)
  143. @IsInt()
  144. @IsIn([0, 1])
  145. listStatus!: number;
  146. }
  147. export class BatchUpdateVideoMediaStatusDto {
  148. @ApiProperty({
  149. type: [String],
  150. description: 'MongoDB 视频 ID 列表',
  151. example: ['507f1f77bcf86cd799439011', '507f1f77bcf86cd799439012'],
  152. })
  153. @IsArray()
  154. @IsMongoId({ each: true })
  155. ids!: string[];
  156. @ApiProperty({
  157. type: Number,
  158. enum: [0, 1],
  159. description: '上下架状态: 0=下架, 1=上架',
  160. example: 1,
  161. })
  162. @Type(() => Number)
  163. @IsInt()
  164. @IsIn([0, 1])
  165. listStatus!: number;
  166. }
  167. export class UpdateVideoMediaCoverResponseDto {
  168. @ApiProperty({
  169. type: String,
  170. description: '视频 MongoDB ID',
  171. example: '507f1f77bcf86cd799439011',
  172. })
  173. @IsString()
  174. id!: string;
  175. @ApiProperty({
  176. type: String,
  177. description: '新的封面图片 URL 或 S3 key',
  178. example: 'https://s3.example.com/covers/507f1f77bcf86cd799439011.jpg',
  179. })
  180. @IsString()
  181. coverImg!: string;
  182. /**
  183. * 新 editedAt 值
  184. */
  185. @ApiProperty({
  186. type: Number,
  187. description: '编辑时间戳(EPOCH 毫秒)',
  188. example: 1733304600000,
  189. })
  190. @IsInt()
  191. editedAt!: number;
  192. }
  193. export class VideoMediaListItemDto {
  194. @ApiProperty({
  195. type: String,
  196. description: '视频 MongoDB ID',
  197. example: '507f1f77bcf86cd799439011',
  198. })
  199. @IsString()
  200. id!: string;
  201. @ApiProperty({
  202. type: String,
  203. description: '视频标题',
  204. example: '产品演示视频',
  205. })
  206. @IsString()
  207. title!: string;
  208. @ApiProperty({
  209. type: String,
  210. description: '视频文件名',
  211. example: 'product-demo.mp4',
  212. })
  213. @IsString()
  214. filename!: string;
  215. /**
  216. * 视频时长(秒)
  217. */
  218. @ApiProperty({
  219. type: Number,
  220. description: '视频时长(秒)',
  221. example: 120,
  222. })
  223. @Type(() => Number)
  224. @IsInt()
  225. videoTime!: number;
  226. /**
  227. * 文件大小(业务上 BigInt 存储,DTO 建议用字符串避免精度问题)
  228. */
  229. @ApiProperty({
  230. type: String,
  231. description: '文件大小(字节),以字符串形式存储避免精度问题',
  232. example: '1048576000',
  233. })
  234. @IsString()
  235. size!: string;
  236. /**
  237. * 封面 URL / key
  238. */
  239. @ApiProperty({
  240. type: String,
  241. description: '视频封面 URL 或 S3 key',
  242. example: 'https://s3.example.com/covers/507f1f77bcf86cd799439011.jpg',
  243. })
  244. @IsString()
  245. coverImg!: string;
  246. /**
  247. * 分类 ID,可空
  248. */
  249. @ApiPropertyOptional({
  250. type: String,
  251. nullable: true,
  252. description: '视频分类 MongoDB ID',
  253. example: '507f1f77bcf86cd799439012',
  254. })
  255. @IsOptional()
  256. @IsMongoId()
  257. categoryId?: string | null;
  258. /**
  259. * 当前选中的标签 ID 列表(最多 5 个)
  260. */
  261. @ApiProperty({
  262. type: [String],
  263. description: '视频标签 MongoDB ID 列表',
  264. example: ['507f1f77bcf86cd799439013', '507f1f77bcf86cd799439014'],
  265. })
  266. @IsArray()
  267. @IsMongoId({ each: true })
  268. tagIds!: string[];
  269. /**
  270. * 上/下架状态 0 = 下架, 1 = 上架
  271. */
  272. @ApiProperty({
  273. type: Number,
  274. enum: [0, 1],
  275. description: '上下架状态: 0=下架, 1=上架',
  276. example: 1,
  277. })
  278. @Type(() => Number)
  279. @IsInt()
  280. @IsIn([0, 1])
  281. listStatus!: number;
  282. /**
  283. * 编辑时间戳(EPOCH 毫秒)
  284. */
  285. @ApiProperty({
  286. type: Number,
  287. description: '编辑时间戳(EPOCH 毫秒)',
  288. example: 1733304600000,
  289. })
  290. @IsInt()
  291. editedAt!: number;
  292. }
  293. export class VideoMediaDetailDto {
  294. @ApiProperty({
  295. type: String,
  296. description: '视频 MongoDB ID',
  297. example: '507f1f77bcf86cd799439011',
  298. })
  299. @IsString()
  300. id!: string;
  301. // --- Provider fields (read-only for mgnt) ---
  302. @ApiProperty({
  303. type: String,
  304. description: '视频标题',
  305. example: '产品演示视频',
  306. })
  307. @IsString()
  308. title!: string;
  309. @ApiProperty({
  310. type: String,
  311. description: '视频文件名',
  312. example: 'product-demo.mp4',
  313. })
  314. @IsString()
  315. filename!: string;
  316. @ApiProperty({
  317. type: Number,
  318. description: '视频时长(秒)',
  319. example: 120,
  320. })
  321. @Type(() => Number)
  322. @IsInt()
  323. videoTime!: number;
  324. @ApiProperty({
  325. type: String,
  326. description: '文件大小(字节),以字符串形式存储避免精度问题',
  327. example: '1048576000',
  328. })
  329. @IsString()
  330. size!: string;
  331. @ApiProperty({
  332. type: String,
  333. description: '视频封面 URL 或 S3 key',
  334. example: 'https://s3.example.com/covers/507f1f77bcf86cd799439011.jpg',
  335. })
  336. @IsString()
  337. coverImg!: string;
  338. @ApiProperty({
  339. type: String,
  340. description: '视频类型',
  341. example: 'video/mp4',
  342. })
  343. @IsString()
  344. type!: string;
  345. @ApiProperty({
  346. type: Number,
  347. description: '视频格式类型编码',
  348. example: 1,
  349. })
  350. @Type(() => Number)
  351. @IsInt()
  352. formatType!: number;
  353. @ApiProperty({
  354. type: Number,
  355. description: '内容类型编码',
  356. example: 1,
  357. })
  358. @Type(() => Number)
  359. @IsInt()
  360. contentType!: number;
  361. @ApiProperty({
  362. type: String,
  363. description: '国家/地区代码',
  364. example: 'CN',
  365. })
  366. @IsString()
  367. country!: string;
  368. @ApiProperty({
  369. type: String,
  370. description: '视频状态',
  371. example: 'active',
  372. })
  373. @IsString()
  374. status!: string;
  375. @ApiProperty({
  376. type: String,
  377. description: '视频描述',
  378. example: '这是一个产品演示视频',
  379. })
  380. @IsString()
  381. desc!: string;
  382. // --- Local business fields ---
  383. @ApiPropertyOptional({
  384. type: String,
  385. nullable: true,
  386. description: '视频分类 MongoDB ID',
  387. example: '507f1f77bcf86cd799439012',
  388. })
  389. @IsOptional()
  390. @IsMongoId()
  391. categoryId?: string | null;
  392. @ApiProperty({
  393. type: [String],
  394. description: '视频标签 MongoDB ID 列表',
  395. example: ['507f1f77bcf86cd799439013', '507f1f77bcf86cd799439014'],
  396. })
  397. @IsArray()
  398. @IsMongoId({ each: true })
  399. tagIds!: string[];
  400. @ApiProperty({
  401. type: Number,
  402. enum: [0, 1],
  403. description: '上下架状态: 0=下架, 1=上架',
  404. example: 1,
  405. })
  406. @Type(() => Number)
  407. @IsInt()
  408. @IsIn([0, 1])
  409. listStatus!: number;
  410. @ApiProperty({
  411. type: Number,
  412. description: '编辑时间戳(EPOCH 毫秒)',
  413. example: 1733304600000,
  414. })
  415. @IsInt()
  416. editedAt!: number;
  417. // --- Optional denormalized information for UI convenience ---
  418. @ApiPropertyOptional({
  419. type: String,
  420. nullable: true,
  421. description: '分类名称(反范式化字段,用于 UI 便利性)',
  422. example: '产品介绍',
  423. })
  424. @IsOptional()
  425. @IsString()
  426. categoryName?: string | null;
  427. @ApiPropertyOptional({
  428. type: [Object],
  429. description: '标签对象数组(反范式化字段,包含 id 和 name)',
  430. example: [
  431. { id: '507f1f77bcf86cd799439013', name: '演示' },
  432. { id: '507f1f77bcf86cd799439014', name: '产品' },
  433. ],
  434. })
  435. @IsOptional()
  436. @IsArray()
  437. tags?: { id: string; name: string }[];
  438. }