Commit Graph

4 Commits

Author SHA1 Message Date
浅念 a125fcab1a 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>
2026-09-20 16:36:34 +08:00
浅念 65726702f3 feat(sync): cn event coverage, bootstrap and outbox sender
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-18 15:21:41 +08:00
浅念 8ba98ace63 feat(api): management photo list and account lookup
P1b Task 4: listAlbumPhotos (GET /albums/{albumId}/photos, tag
[photos]) + lookupUsersByAccount (GET /admin/users, tag [admin]).

- management list: all visibility states visible (unpublished/
  published/hidden/deleted) for owner/admin; collaborator 403,
  outsider/ghost album 404 (canView -> canManage ordering, same
  anti-enumeration as members). Items = PhotoRecord + uploader
  {userId, account, displayName} (flat-FK second query, redacted).
  Keyset (uploadedAt,id) DESC via shared/cursor; visibility filter
  validated against PhotoVisibility enum (400 otherwise), categoryId
  must belong to the album (ghost/cross-album same-code 400).
  pending_upload rows (uploadedAt NULL) excluded: the sort key is
  both the contract-required field and the keyset predicate - NULL
  can neither be shaped as PhotoRecord nor compared; in-flight
  uploads are covered by the uploads history endpoint.
- account lookup (new src/admin module): prefix search over active
  users only (credentialStatus=active, sourceStatus!=disabled,
  !localLocked - same eligibility as SessionGuard/members.add),
  account ASC, no cursor (small cap). $queryRaw LIKE prefix% with
  explicit ESCAPE '!': !/%/_/\ escaped to literal matching (Prisma
  startsWith escaping is not contractual); utf8mb4_0900_bin makes
  the prefix match case-sensitive ('AL' misses 'alice'), documented
  in contract + tests. Access policy per contract: any authenticated
  staff (deviation from plan's admin/owner), unauthenticated 401.
- e2e: photos-admin (6) + users-lookup (6) cases; api e2e 136 -> 148.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-18 08:54:44 +08:00
浅念 6d61d117d5 feat(api): photo visibility lifecycle with audit and outbox events
Serializes turbo tasks (concurrency=1): api and worker e2e suites share
one MySQL/MinIO and resetDb TRUNCATEs raced when run in parallel.

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