feat(api): members and categories management
成员(/v1/albums/{albumId}/members):owner/管理员邀请已开通同事为协作者
(重复 409 MEMBER_CONFLICT、owner 目标 400、未开通/停用账号 400)、成员列表
((addedAt,id) DESC 键集分页)、移除协作者(幂等 204;成员行是唯一权限来源,
删除行即失去可见性 A02;owner 不可移除 403)。分类(/albums/{albumId}/categories
与 /categories/{categoryId}):创建/部分更新/删除,translations 全量替换,
相册详情按 sortOrder 升序带出;404 统一 ALBUM_NOT_FOUND(分类不存在与相册
不可见同码)。审计 album.member.add/remove、album.category.add/update/remove
(resourceId=albumId,metadata 记目标 id/字段)。共用错误/P2002 判定抽至
support.ts(修复 meta.target 为 DB 约束名时的误判)。
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
// services/api/src/albums/members.controller.ts —— /albums/{albumId}/members
|
||||
// 端点(Task 17)。方法名与契约 operationId 一一对应(addAlbumMember/
|
||||
// listAlbumMembers/removeAlbumMember)。控制器级 SessionGuard:三端点全需
|
||||
// 认证。控制器只做认证用户透传 + 查询参数规整(复用 albums.controller 的
|
||||
// parseLimit/parseCursor,同一契约 bounds/游标语义);权限与 HTTP 映射由
|
||||
// MembersService 抛 ApiException(404 ALBUM_NOT_FOUND / 403 AUTH_FORBIDDEN /
|
||||
// 409 MEMBER_CONFLICT)。
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { AuthenticatedRequest, SessionGuard } from '../auth/session.guard';
|
||||
import { parseCursor, parseLimit, userOf } from './albums.controller';
|
||||
import { MemberAddRequestDto } from './dto';
|
||||
import {
|
||||
type MemberListResponse,
|
||||
type MemberResponse,
|
||||
MembersService,
|
||||
} from './members.service';
|
||||
|
||||
@Controller('albums/:albumId/members')
|
||||
@UseGuards(SessionGuard)
|
||||
export class MembersController {
|
||||
constructor(private readonly membersService: MembersService) {}
|
||||
|
||||
@Post()
|
||||
addAlbumMember(
|
||||
@Param('albumId') albumId: string,
|
||||
@Body() dto: MemberAddRequestDto,
|
||||
@Req() req: AuthenticatedRequest,
|
||||
): Promise<MemberResponse> {
|
||||
return this.membersService.add(userOf(req), albumId, dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
listAlbumMembers(
|
||||
@Param('albumId') albumId: string,
|
||||
@Query('cursor') cursor: string | undefined,
|
||||
@Query('limit') limit: string | undefined,
|
||||
@Req() req: AuthenticatedRequest,
|
||||
): Promise<MemberListResponse> {
|
||||
return this.membersService.list(
|
||||
userOf(req),
|
||||
albumId,
|
||||
parseCursor(cursor),
|
||||
parseLimit(limit),
|
||||
);
|
||||
}
|
||||
|
||||
@Delete(':userId')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
removeAlbumMember(
|
||||
@Param('albumId') albumId: string,
|
||||
@Param('userId') userId: string,
|
||||
@Req() req: AuthenticatedRequest,
|
||||
): Promise<void> {
|
||||
return this.membersService.remove(userOf(req), albumId, userId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user