Files
photo-live/services/api/src/albums/albums.controller.ts
T
浅念 8e674a902e feat(albums): soft-delete albums (admin+owner, hidden & irreversible, both app entries)
契约 deleteAlbum(DELETE /albums/{albumId} → 204 幂等)+ status enum 'deleted':
- 服务端:softDelete(canView 404→canManage 403 防枚举;乐观锁 claim +
  outbox album.updated priority=high + audit album.delete);列表/详情/更新/
  成员/资产/分类/水印/照片/展示位绑定全部 deleted→404;公开端 cn
  (status!=='active')与 ov(isAlbumVisibleOv)天然屏蔽;ov 副本经
  album.updated 载荷 status 直投自动下线
- Admin:列表表格操作「删除」+ 详情页头「删除相册」(确认含不可恢复说明)
- App:操作面板「删除相册」(红字、二次确认、成功退回列表并刷新)
- 验证:API 56/56、App 397/397(analyze 0)、admin typecheck/lint/test/build 绿

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 10:13:08 +08:00

102 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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<AlbumResponse> {
return this.albumsService.create(userOf(req).id, dto);
}
@Get()
listAlbums(
@Query('cursor') cursor: string | undefined,
@Query('limit') limit: string | undefined,
@Req() req: AuthenticatedRequest,
): Promise<AlbumListResponse> {
return this.albumsService.list(
userOf(req),
parseCursor(cursor),
parseLimit(limit),
);
}
@Get(':albumId')
getAlbum(
@Param('albumId') albumId: string,
@Req() req: AuthenticatedRequest,
): Promise<AlbumResponse> {
return this.albumsService.get(userOf(req), albumId);
}
@Delete(':albumId')
@HttpCode(HttpStatus.NO_CONTENT)
deleteAlbum(
@Param('albumId') albumId: string,
@Req() req: AuthenticatedRequest,
): Promise<void> {
return this.albumsService.softDelete(userOf(req), albumId);
}
@Patch(':albumId')
updateAlbum(
@Param('albumId') albumId: string,
@Body() dto: AlbumUpdateRequestDto,
@Req() req: AuthenticatedRequest,
): Promise<AlbumResponse> {
return this.albumsService.update(userOf(req), albumId, dto);
}
}