// services/api/src/albums/albums.controller.ts —— /albums 端点(Task 15;Task 16 // 增补 updateAlbum)。方法名与契约 operationId 一一对应(createAlbum/ // listAlbums/getAlbum/updateAlbum)。控制器级 SessionGuard:四端点全需认证 // (契约均有 bearerAuth、无 security: [] 例外)。职责仅剩: // · 透传认证用户(req.user,守卫挂载); // · 查询参数规整(limit 1..100 整数、cursor 长度)经 shared/pagination // (P1a 终审修缮③迁出本控制器),非法 → 400 VALIDATION_FAILED; // · 权限/可见性→HTTP 映射交由服务层抛 ApiException(404 ALBUM_NOT_FOUND)。 import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Query, Req, UseGuards, } from '@nestjs/common'; import { ErrorCodes } from '@photo/contracts'; import { ApiException } from '../api-exception'; import type { AuthUser } from '../auth/session.guard'; import { AuthenticatedRequest, SessionGuard } from '../auth/session.guard'; import { parseCursor, parseLimit } from '../shared/pagination'; import { type AlbumListResponse, type AlbumResponse, AlbumsService, } from './albums.service'; import { AlbumCreateRequestDto, AlbumUpdateRequestDto } from './dto'; /** * 守卫放行后提取 req.user;防御性兜底(守卫未挂载属装配错误,按未认证拒绝)。 * members/categories/photos/uploads 控制器复用同一判定,导出。 */ export function userOf(req: AuthenticatedRequest): AuthUser { if (req.user === undefined) { throw new ApiException( ErrorCodes.AUTH_TOKEN_EXPIRED, 'Session token is missing, invalid, or expired', HttpStatus.UNAUTHORIZED, ); } return req.user; } @Controller('albums') @UseGuards(SessionGuard) export class AlbumsController { constructor(private readonly albumsService: AlbumsService) {} @Post() createAlbum( @Body() dto: AlbumCreateRequestDto, @Req() req: AuthenticatedRequest, ): Promise { return this.albumsService.create(userOf(req).id, dto); } @Get() listAlbums( @Query('cursor') cursor: string | undefined, @Query('limit') limit: string | undefined, @Req() req: AuthenticatedRequest, ): Promise { return this.albumsService.list( userOf(req), parseCursor(cursor), parseLimit(limit), ); } @Get(':albumId') getAlbum( @Param('albumId') albumId: string, @Req() req: AuthenticatedRequest, ): Promise { return this.albumsService.get(userOf(req), albumId); } @Delete(':albumId') @HttpCode(HttpStatus.NO_CONTENT) deleteAlbum( @Param('albumId') albumId: string, @Req() req: AuthenticatedRequest, ): Promise { return this.albumsService.softDelete(userOf(req), albumId); } @Patch(':albumId') updateAlbum( @Param('albumId') albumId: string, @Body() dto: AlbumUpdateRequestDto, @Req() req: AuthenticatedRequest, ): Promise { return this.albumsService.update(userOf(req), albumId, dto); } }