8e674a902e
契约 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>
102 lines
3.0 KiB
TypeScript
102 lines
3.0 KiB
TypeScript
// 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);
|
||
}
|
||
}
|