feat(api): batch hide with per-item results and owner transfer

P4a Task 2(A16/A17):

- POST /albums/{albumId}/photos/hide-batch:逐项结果(部分成功)——
  不存在/他册 → ok:false PHOTO_NOT_FOUND;非 published(unpublished/
  deleted)→ ok:false PHOTO_NOT_PUBLIC;已 hidden → ok:true 幂等 no-op
  (同值不产生第二次事件/审计/版本,P1 语义);published → 逐照片事务
  (条件更新 + outbox photo.hidden 高优先级 + audit photo.hide)。
  results 与请求同序;权限同管理照片列表(仅创建者或管理员,404 防枚举);
  单批 1..200(DTO 层 400)。
- POST /admin/albums/{albumId}/transfer:admin-only owner 移交——事务内
  条件更新 ownerId+entityVersion+1(乐观重试)+ 成员互换(目标 upsert 为
  owner,原 owner 按 keepAsCollaborator 降级 collaborator 或删行)+
  outbox album.updated(标准载荷,版本推进)+ audit album.transfer
  {from,to,kept}。目标校验同成员邀请资格谓词(active/未锁定/未停用);
  自交接 400;photos.uploaderId 不动(A17 历史上传人不变)。
- e2e(batch-ops.e2e-spec,双入口 harness):8 用例覆盖逐项结果/幂等/
  入参权限矩阵/ov 传播(真实发布链→隐藏→photos_ov=hidden+公开列表剔除)/
  keep 两分支/目标校验/交接后权限(新 owner 管理、旧 owner 保留协作者
  可上传)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-20 16:36:34 +08:00
parent 8e3e19a7d3
commit a125fcab1a
8 changed files with 1085 additions and 15 deletions
+17
View File
@@ -300,6 +300,23 @@ export class CategoryUpdateRequestDto {
translations?: CategoryTranslationDto[];
}
// ── 负责人交接(P4a Task 2)──
/**
* 契约 AlbumTransferRequest(A17):targetUserId 须为可被指定为 owner 的账号
* (存在且已开通——credentialStatus=active、!localLocked、sourceStatus≠disabled,
* 服务层对存量行校验);keepAsCollaborator=false 时原 owner 移出成员表。
*/
export class AlbumTransferRequestDto {
@IsString()
@IsNotEmpty()
@MaxLength(64) // users.id VarChar(64)
targetUserId!: string;
@IsBoolean()
keepAsCollaborator!: boolean;
}
// ── 资产(P1b Task 3)──
/** 契约 AssetKind:banner(观众页宣传图,PublicAlbum.banner 消费方);cover/logo 建链就绪,展示消费随后续阶段。 */