video-media.controller.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. // apps/box-mgnt-api/src/mgnt-backend/feature/video-media/video-media.controller.ts
  2. import {
  3. Controller,
  4. Get,
  5. Param,
  6. Query,
  7. Patch,
  8. Body,
  9. Post,
  10. Delete,
  11. Req,
  12. BadRequestException,
  13. } from '@nestjs/common';
  14. import type { FastifyRequest } from 'fastify';
  15. import {
  16. ApiTags,
  17. ApiOperation,
  18. ApiParam,
  19. ApiConsumes,
  20. ApiBody,
  21. ApiResponse,
  22. ApiOkResponse,
  23. ApiNotFoundResponse,
  24. ApiBadRequestResponse,
  25. } from '@nestjs/swagger';
  26. import { VideoMediaService } from './video-media.service';
  27. import {
  28. VideoMediaListQueryDto,
  29. UpdateVideoMediaManageDto,
  30. UpdateVideoMediaStatusDto,
  31. BatchUpdateVideoMediaStatusDto,
  32. VideoMediaListItemDto,
  33. VideoMediaDetailDto,
  34. UpdateVideoMediaCoverResponseDto,
  35. } from './video-media.dto';
  36. @ApiTags('视频管理 (Video Media Management)')
  37. @Controller('video-media')
  38. export class VideoMediaController {
  39. constructor(private readonly videoMediaService: VideoMediaService) {}
  40. /**
  41. * 列表查询
  42. * POST /video-media/list
  43. */
  44. @ApiOperation({
  45. summary: '获取视频媒体列表',
  46. description: '分页查询视频列表,支持关键词搜索和上下架状态过滤',
  47. })
  48. @ApiOkResponse({
  49. description: '返回视频媒体分页列表',
  50. schema: {
  51. type: 'object',
  52. properties: {
  53. data: {
  54. type: 'array',
  55. items: { $ref: '#/components/schemas/VideoMediaListItemDto' },
  56. },
  57. total: { type: 'number', example: 100 },
  58. page: { type: 'number', example: 1 },
  59. pageSize: { type: 'number', example: 20 },
  60. },
  61. },
  62. })
  63. @ApiBadRequestResponse({
  64. description: '请求参数验证失败',
  65. })
  66. @Post('list')
  67. async findAll(@Body() dto: VideoMediaListQueryDto) {
  68. return this.videoMediaService.findAll(dto);
  69. }
  70. /**
  71. * 详情(管理弹窗)
  72. * GET /video-media/:id
  73. */
  74. @ApiOperation({
  75. summary: '获取视频媒体详情',
  76. description:
  77. '获取单个视频媒体的完整详细信息,包括基础属性、管理信息和反范式化数据',
  78. })
  79. @ApiParam({
  80. name: 'id',
  81. type: String,
  82. description: '视频媒体 MongoDB ID',
  83. example: '507f1f77bcf86cd799439011',
  84. })
  85. @ApiOkResponse({
  86. description: '返回视频媒体详情',
  87. type: VideoMediaDetailDto,
  88. })
  89. @ApiNotFoundResponse({
  90. description: '视频媒体不存在',
  91. })
  92. @Get(':id')
  93. async findOne(@Param('id') id: string) {
  94. return this.videoMediaService.findOne(id);
  95. }
  96. /**
  97. * 管理弹窗保存(标题 / 分类 / 标签 / 上下架)
  98. * PATCH /video-media/:id/manage
  99. */
  100. @ApiOperation({
  101. summary: '更新视频媒体管理信息',
  102. description: '更新视频的标题、分类、标签、上下架状态等管理级别信息',
  103. })
  104. @ApiParam({
  105. name: 'id',
  106. type: String,
  107. description: '视频媒体 MongoDB ID',
  108. example: '507f1f77bcf86cd799439011',
  109. })
  110. @ApiBody({
  111. type: UpdateVideoMediaManageDto,
  112. description: '更新的管理信息',
  113. })
  114. @ApiOkResponse({
  115. description: '更新成功',
  116. type: VideoMediaDetailDto,
  117. })
  118. @ApiNotFoundResponse({
  119. description: '视频媒体不存在',
  120. })
  121. @ApiBadRequestResponse({
  122. description: '请求参数验证失败',
  123. })
  124. @Patch(':id/manage')
  125. async updateManage(
  126. @Param('id') id: string,
  127. @Body() dto: UpdateVideoMediaManageDto,
  128. ) {
  129. return this.videoMediaService.updateManage(id, dto);
  130. }
  131. /**
  132. * 单个上/下架
  133. * PATCH /video-media/:id/status
  134. */
  135. @ApiOperation({
  136. summary: '更新视频媒体上下架状态',
  137. description: '对单个视频进行上架(1)或下架(0)操作',
  138. })
  139. @ApiParam({
  140. name: 'id',
  141. type: String,
  142. description: '视频媒体 MongoDB ID',
  143. example: '507f1f77bcf86cd799439011',
  144. })
  145. @ApiBody({
  146. type: UpdateVideoMediaStatusDto,
  147. description: '上下架状态信息',
  148. })
  149. @ApiOkResponse({
  150. description: '状态更新成功',
  151. type: VideoMediaDetailDto,
  152. })
  153. @ApiNotFoundResponse({
  154. description: '视频媒体不存在',
  155. })
  156. @ApiBadRequestResponse({
  157. description: '请求参数验证失败',
  158. })
  159. @Patch(':id/status')
  160. async updateStatus(
  161. @Param('id') id: string,
  162. @Body() dto: UpdateVideoMediaStatusDto,
  163. ) {
  164. return this.videoMediaService.updateStatus(id, dto);
  165. }
  166. /**
  167. * 批量上/下架
  168. * POST /video-media/batch/status
  169. */
  170. @ApiOperation({
  171. summary: '批量更新视频媒体上下架状态',
  172. description: '对多个视频进行批量上架或下架操作',
  173. })
  174. @ApiBody({
  175. type: BatchUpdateVideoMediaStatusDto,
  176. description: '批量更新信息,包含 ID 列表和目标状态',
  177. })
  178. @ApiOkResponse({
  179. description: '批量更新成功',
  180. schema: {
  181. type: 'object',
  182. properties: {
  183. success: { type: 'number', example: 10 },
  184. failed: { type: 'number', example: 0 },
  185. },
  186. },
  187. })
  188. @ApiBadRequestResponse({
  189. description: '请求参数验证失败',
  190. })
  191. @Post('batch/status')
  192. async batchUpdateStatus(@Body() dto: BatchUpdateVideoMediaStatusDto) {
  193. return this.videoMediaService.batchUpdateStatus(dto);
  194. }
  195. /**
  196. * 封面上传:
  197. * - 前端通过 multipart/form-data 上传文件
  198. * - 这里示例使用 FileInterceptor;实际中你会上传到 S3,得到一个 URL / key
  199. * POST /video-media/:id/cover
  200. */
  201. @ApiOperation({
  202. summary: '上传视频封面',
  203. description: '为指定视频上传或更新自定义封面图片,支持上传至 S3 存储',
  204. })
  205. @ApiParam({
  206. name: 'id',
  207. type: String,
  208. description: '视频媒体 MongoDB ID',
  209. example: '507f1f77bcf86cd799439011',
  210. })
  211. @ApiConsumes('multipart/form-data')
  212. @ApiBody({
  213. description: '封面图片文件上传',
  214. schema: {
  215. type: 'object',
  216. properties: {
  217. file: {
  218. type: 'string',
  219. format: 'binary',
  220. description: '图片文件(支持 JPG、PNG 等常见格式)',
  221. },
  222. },
  223. required: ['file'],
  224. },
  225. })
  226. @ApiOkResponse({
  227. description: '封面上传成功',
  228. type: UpdateVideoMediaCoverResponseDto,
  229. })
  230. @ApiNotFoundResponse({
  231. description: '视频媒体不存在',
  232. })
  233. @ApiBadRequestResponse({
  234. description: '文件格式或大小不符合要求',
  235. })
  236. @Post(':id/cover')
  237. async updateCover(@Param('id') id: string, @Req() req: FastifyRequest) {
  238. const reqAny = req as any;
  239. const bodyFile = reqAny.body?.file;
  240. let mpFile = Array.isArray(bodyFile) ? bodyFile[0] : bodyFile;
  241. if (!mpFile && reqAny.isMultipart?.()) {
  242. mpFile = await reqAny.file();
  243. }
  244. if (!mpFile) {
  245. throw new BadRequestException('No file uploaded');
  246. }
  247. return this.videoMediaService.updateCover(id, mpFile);
  248. }
  249. // TODO: 删除视频媒体
  250. @ApiOperation({
  251. summary: '删除视频媒体',
  252. description: '根据 ID 删除指定的视频媒体',
  253. })
  254. @ApiParam({
  255. name: 'id',
  256. type: String,
  257. description: '视频媒体 MongoDB ID',
  258. example: '507f1f77bcf86cd799439011',
  259. })
  260. @ApiOkResponse({
  261. description: '删除成功',
  262. schema: {
  263. type: 'object',
  264. properties: {
  265. id: { type: 'string', example: '507f1f77bcf86cd799439011' },
  266. },
  267. },
  268. })
  269. @ApiNotFoundResponse({
  270. description: '视频媒体不存在',
  271. })
  272. @Delete(':id')
  273. async delete(@Param('id') id: string) {
  274. return this.videoMediaService.delete(id);
  275. }
  276. }