// services/api/src/uploads/uploads.controller.ts —— /uploads 端点(Task 18 起; // Task 19 增补 listUploads/renewUploadCredential/completeUpload)。方法名与契约 // operationId 一一对应。控制器级 SessionGuard:端点全需认证。职责:透传认证 // 用户 + 查询参数规整(parseLimit/parseCursor 复用相册控制器导出,同契约 bounds)。 // 注意 DTO 必须值导入(非 import type):@Body 参数类型经 emitDecoratorMetadata // 进入设计期元数据,import type 会令参数在管线中被置为 undefined(Task 18 实测)。 import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Query, Req, UseGuards, } from '@nestjs/common'; import { parseCursor, parseLimit, userOf, } from '../albums/albums.controller'; import { AuthenticatedRequest, SessionGuard } from '../auth/session.guard'; import { UploadsService } from './uploads.service'; import type { UploadCompleteResponse, UploadInitResponse, UploadSessionListResponse, } from './uploads.service'; import { UploadInitRequestDto } from './dto'; @Controller('uploads') @UseGuards(SessionGuard) export class UploadsController { constructor(private readonly uploadsService: UploadsService) {} /** 契约 201(幂等重复调用返回原会话,同 201)。 */ @Post() initUpload( @Body() dto: UploadInitRequestDto, @Req() req: AuthenticatedRequest, ): Promise { return this.uploadsService.init(userOf(req), dto); } /** 上传历史:当前用户自己的会话,新在前,(createdAt,id) 键集游标。 */ @Get() listUploads( @Query('cursor') cursor: string | undefined, @Query('limit') limit: string | undefined, @Req() req: AuthenticatedRequest, ): Promise { return this.uploadsService.list( userOf(req), parseCursor(cursor), parseLimit(limit), ); } /** 续签:仅 pending 会话;返回结构同 initUpload(credential 为新值)。 */ @Post(':uploadId/renew') @HttpCode(HttpStatus.OK) renewUploadCredential( @Param('uploadId') uploadId: string, @Req() req: AuthenticatedRequest, ): Promise { return this.uploadsService.renew(userOf(req), uploadId); } /** 完成核验:幂等(重复调用返回相同结果,不重复建 job)。 */ @Post(':uploadId/complete') @HttpCode(HttpStatus.OK) completeUpload( @Param('uploadId') uploadId: string, @Req() req: AuthenticatedRequest, ): Promise { return this.uploadsService.complete(userOf(req), uploadId); } }