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:
@@ -1,5 +1,7 @@
|
||||
// services/api/src/albums/dto.ts —— 相册端点入参 DTO(Task 15;Task 16 增补更新版)。
|
||||
// 校验约束对齐契约 AlbumCreateRequest / AlbumUpdateRequest / AlbumTranslation:
|
||||
// services/api/src/albums/dto.ts —— 相册端点入参 DTO(Task 15;Task 16 增补更新版;
|
||||
// Task 17 增补成员/分类)。校验约束对齐契约
|
||||
// AlbumCreateRequest / AlbumUpdateRequest / MemberAddRequest /
|
||||
// CategoryCreateRequest / CategoryUpdateRequest / AlbumTranslation:
|
||||
// · translations 至少 1 条(minItems: 1),locale 限 P1a 双语枚举;
|
||||
// · defaultLocale 必须有对应翻译条目(跨字段校验,契约 required 语义);
|
||||
// · endAt 可空(契约 [string,'null']),给值时须晚于 startAt(跨字段校验);
|
||||
@@ -13,11 +15,14 @@ import {
|
||||
ArrayMinSize,
|
||||
IsBoolean,
|
||||
IsIn,
|
||||
IsInt,
|
||||
IsISO8601,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
Max,
|
||||
Min,
|
||||
Validate,
|
||||
ValidateNested,
|
||||
ValidatorConstraint,
|
||||
@@ -222,3 +227,70 @@ export class AlbumUpdateRequestDto {
|
||||
@IsBoolean()
|
||||
originalDownloadEnabled?: boolean;
|
||||
}
|
||||
|
||||
// ── 成员(Task 17)──
|
||||
|
||||
/**
|
||||
* 契约 MemberAddRequest:userId 必填(已开通账号);role 缺省 collaborator,
|
||||
* 枚举仅 collaborator——owner 由创建流程产生,经本端点加 owner 属 P4a 管理员
|
||||
* 操作,role='owner' 在 DTO 层即 400。
|
||||
*/
|
||||
export class MemberAddRequestDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(64) // users.id VarChar(64)
|
||||
userId!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn(['collaborator'])
|
||||
role?: 'collaborator';
|
||||
}
|
||||
|
||||
// ── 分类(Task 17)──
|
||||
|
||||
/** 契约 CategoryTranslation:name minLength 1;列宽 VarChar(128)。 */
|
||||
export class CategoryTranslationDto {
|
||||
@IsIn(ALBUM_LOCALES)
|
||||
locale!: AlbumLocale;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(128)
|
||||
name!: string;
|
||||
}
|
||||
|
||||
/** sortOrder:契约 integer 无界;Int 列物理边界内收为 400(而非溢出 500)。 */
|
||||
const SORT_ORDER_MIN = -2_147_483_648;
|
||||
const SORT_ORDER_MAX = 2_147_483_647;
|
||||
|
||||
export class CategoryCreateRequestDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(SORT_ORDER_MIN)
|
||||
@Max(SORT_ORDER_MAX)
|
||||
sortOrder?: number;
|
||||
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CategoryTranslationDto)
|
||||
translations!: CategoryTranslationDto[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 契约 CategoryUpdateRequest:全字段可选 + minProperties:1。空体拒绝在服务层
|
||||
* (同 AlbumUpdateRequestDto:plainToInstance 后声明属性以 undefined 成键,
|
||||
* Object.keys 不可判空,以「任一字段已赋值」为判据)。
|
||||
*/
|
||||
export class CategoryUpdateRequestDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(SORT_ORDER_MIN)
|
||||
@Max(SORT_ORDER_MAX)
|
||||
sortOrder?: number;
|
||||
|
||||
@IsOptional()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CategoryTranslationDto)
|
||||
translations?: CategoryTranslationDto[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user