// apps/box-mgnt-api/src/mgnt-backend/feature/video-media/video-media.controller.ts import { Controller, Get, Param, Query, Patch, Body, Post, Delete, Req, BadRequestException, } from '@nestjs/common'; import type { FastifyRequest } from 'fastify'; import { ApiTags, ApiOperation, ApiParam, ApiConsumes, ApiBody, ApiResponse, ApiOkResponse, ApiNotFoundResponse, ApiBadRequestResponse, } from '@nestjs/swagger'; import { VideoMediaService } from './video-media.service'; import { VideoMediaListQueryDto, UpdateVideoMediaManageDto, UpdateVideoMediaStatusDto, BatchUpdateVideoMediaStatusDto, VideoMediaListItemDto, VideoMediaDetailDto, UpdateVideoMediaCoverResponseDto, } from './video-media.dto'; @ApiTags('视频管理 (Video Media Management)') @Controller('video-media') export class VideoMediaController { constructor(private readonly videoMediaService: VideoMediaService) {} /** * 列表查询 * POST /video-media/list */ @ApiOperation({ summary: '获取视频媒体列表', description: '分页查询视频列表,支持关键词搜索和上下架状态过滤', }) @ApiOkResponse({ description: '返回视频媒体分页列表', schema: { type: 'object', properties: { data: { type: 'array', items: { $ref: '#/components/schemas/VideoMediaListItemDto' }, }, total: { type: 'number', example: 100 }, page: { type: 'number', example: 1 }, pageSize: { type: 'number', example: 20 }, }, }, }) @ApiBadRequestResponse({ description: '请求参数验证失败', }) @Post('list') async findAll(@Body() dto: VideoMediaListQueryDto) { return this.videoMediaService.findAll(dto); } /** * 详情(管理弹窗) * GET /video-media/:id */ @ApiOperation({ summary: '获取视频媒体详情', description: '获取单个视频媒体的完整详细信息,包括基础属性、管理信息和反范式化数据', }) @ApiParam({ name: 'id', type: String, description: '视频媒体 MongoDB ID', example: '507f1f77bcf86cd799439011', }) @ApiOkResponse({ description: '返回视频媒体详情', type: VideoMediaDetailDto, }) @ApiNotFoundResponse({ description: '视频媒体不存在', }) @Get(':id') async findOne(@Param('id') id: string) { return this.videoMediaService.findOne(id); } /** * 管理弹窗保存(标题 / 分类 / 标签 / 上下架) * PATCH /video-media/:id/manage */ @ApiOperation({ summary: '更新视频媒体管理信息', description: '更新视频的标题、分类、标签、上下架状态等管理级别信息', }) @ApiParam({ name: 'id', type: String, description: '视频媒体 MongoDB ID', example: '507f1f77bcf86cd799439011', }) @ApiBody({ type: UpdateVideoMediaManageDto, description: '更新的管理信息', }) @ApiOkResponse({ description: '更新成功', type: VideoMediaDetailDto, }) @ApiNotFoundResponse({ description: '视频媒体不存在', }) @ApiBadRequestResponse({ description: '请求参数验证失败', }) @Patch(':id/manage') async updateManage( @Param('id') id: string, @Body() dto: UpdateVideoMediaManageDto, ) { return this.videoMediaService.updateManage(id, dto); } /** * 单个上/下架 * PATCH /video-media/:id/status */ @ApiOperation({ summary: '更新视频媒体上下架状态', description: '对单个视频进行上架(1)或下架(0)操作', }) @ApiParam({ name: 'id', type: String, description: '视频媒体 MongoDB ID', example: '507f1f77bcf86cd799439011', }) @ApiBody({ type: UpdateVideoMediaStatusDto, description: '上下架状态信息', }) @ApiOkResponse({ description: '状态更新成功', type: VideoMediaDetailDto, }) @ApiNotFoundResponse({ description: '视频媒体不存在', }) @ApiBadRequestResponse({ description: '请求参数验证失败', }) @Patch(':id/status') async updateStatus( @Param('id') id: string, @Body() dto: UpdateVideoMediaStatusDto, ) { return this.videoMediaService.updateStatus(id, dto); } /** * 批量上/下架 * POST /video-media/batch/status */ @ApiOperation({ summary: '批量更新视频媒体上下架状态', description: '对多个视频进行批量上架或下架操作', }) @ApiBody({ type: BatchUpdateVideoMediaStatusDto, description: '批量更新信息,包含 ID 列表和目标状态', }) @ApiOkResponse({ description: '批量更新成功', schema: { type: 'object', properties: { success: { type: 'number', example: 10 }, failed: { type: 'number', example: 0 }, }, }, }) @ApiBadRequestResponse({ description: '请求参数验证失败', }) @Post('batch/status') async batchUpdateStatus(@Body() dto: BatchUpdateVideoMediaStatusDto) { return this.videoMediaService.batchUpdateStatus(dto); } /** * 封面上传: * - 前端通过 multipart/form-data 上传文件 * - 这里示例使用 FileInterceptor;实际中你会上传到 S3,得到一个 URL / key * POST /video-media/:id/cover */ @ApiOperation({ summary: '上传视频封面', description: '为指定视频上传或更新自定义封面图片,支持上传至 S3 存储', }) @ApiParam({ name: 'id', type: String, description: '视频媒体 MongoDB ID', example: '507f1f77bcf86cd799439011', }) @ApiConsumes('multipart/form-data') @ApiBody({ description: '封面图片文件上传', schema: { type: 'object', properties: { file: { type: 'string', format: 'binary', description: '图片文件(支持 JPG、PNG 等常见格式)', }, }, required: ['file'], }, }) @ApiOkResponse({ description: '封面上传成功', type: UpdateVideoMediaCoverResponseDto, }) @ApiNotFoundResponse({ description: '视频媒体不存在', }) @ApiBadRequestResponse({ description: '文件格式或大小不符合要求', }) @Post(':id/cover') async updateCover(@Param('id') id: string, @Req() req: FastifyRequest) { const reqAny = req as any; const bodyFile = reqAny.body?.file; let mpFile = Array.isArray(bodyFile) ? bodyFile[0] : bodyFile; if (!mpFile && reqAny.isMultipart?.()) { mpFile = await reqAny.file(); } if (!mpFile) { throw new BadRequestException('No file uploaded'); } return this.videoMediaService.updateCover(id, mpFile); } // TODO: 删除视频媒体 @ApiOperation({ summary: '删除视频媒体', description: '根据 ID 删除指定的视频媒体', }) @ApiParam({ name: 'id', type: String, description: '视频媒体 MongoDB ID', example: '507f1f77bcf86cd799439011', }) @ApiOkResponse({ description: '删除成功', schema: { type: 'object', properties: { id: { type: 'string', example: '507f1f77bcf86cd799439011' }, }, }, }) @ApiNotFoundResponse({ description: '视频媒体不存在', }) @Delete(':id') async delete(@Param('id') id: string) { return this.videoMediaService.delete(id); } }