Files
crearte-monorepo/docs/plans/2026-10-01-p8b2-account-catalog.md
T

10 KiB
Raw Blame History

P8 第二批(③账号注销 + ④目录导出 + P7 文案尾)Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development. Steps use checkbox (- [ ]) syntax for tracking.

Goal: crearte-server feat/p8b2-account-catalog(0.15.0:迁移 0011、AccountService 注销内核、DELETE /api/auth/account、CLI user delete、CLI catalog export)与 crearte feat/p8b2-account-ui(0.21.0:AccountView 危险区注销、auth client deleteAccount+错误码、AdminUsersView 错误中文化)。spec:docs/specs/2026-10-01-p8b2-deletion-catalog-design.md(§1–§5 全量为审查依据,不只任务书)。

Architecture: 注销=单事务墓碑内核(users 改写 + works 下架 + submissions 收口),对象删除 tx 外 best-effort;导出=复用 handler.Games 映射的薄 Render 方法,cmd 落盘;前端只加第四段与错误映射。P3 重落是另一批(§历史重放,见 spec §4),本计划不含。

Tech Stack: Go 1.24(gin/pgx/stdlib;禁新增依赖)、Vue3+vitest+playwright、Postgres 17(gated 集成)。

Global Constraints

  • 工作目录:crearte-monorepo/crearte-server/src、crearte-monorepo/crearte/src。两内层仓从 master(server 921443a / crearte 70ba10c)切上述分支;master 禁直接提交;合回 --no-ff。
  • Go 命令一律容器化(AGENTS.md):docker run --rm -v /root/.openclaw/workspace/coder/crearte-monorepo/crearte-server:/work -v crearte_gomod:/go/pkg/mod -v crearte_gocache:/gocache -e GOCACHE=/gocache -e GOPROXY=https://goproxy.cn,direct -e GOSUMDB=sum.golang.google.cn -w /work/src golang:1.24-alpine sh -c "<cmd>"。冷跑用 background+poll,勿掐。
  • 集成测试 gated:TEST_DATABASE_URL 缺失即 t.Skip(既有惯例);gated 测试 TRUNCATE 共享表 → 全量集成必须 -p 1 -count=1。
  • 迁移只增不改:0011_user_deletion.sql;userColumns 追加 deleted_at,pg+memory 双实现同步(memory parity 注释照旧风格)。
  • CHANGELOG:server docs/CHANGELOG.md 顶版 0.14.0 之上追加 ## [0.15.0] - 2026-10-01;crearte 0.20.0 之上追加 ## [0.21.0] - 2026-10-01;英文行+中文行连续,条目间空行;沿用两仓既有文风(去模板腔)。
  • 完成定义:Task S/F 各自容器四连+新测试绿;控制者验收(Task V)六腿 + 冒烟全中;双审 PASS 后合主。

Task S: server — 注销链 + catalog export

Files:

  • Create: src/internal/repository/migrations/0011_user_deletion.sql
  • Modify: src/internal/model/user.go(+DeletedAt *time.Time)、src/internal/repository/user.go(userColumns/scanUser/MarkDeleted/ListBySubmitter 无关——users repo 加 MarkDeleted(ctx,id,Email,Username,PasswordHash);List/CountAdmins 过滤 deleted;memory 实现同步)
  • Modify: src/internal/repository/work.go + memory_content.go(+DelistByOwnerTx/计数;接口名 DelistPublishedByOwner(ctx, ownerID string) (int, error))
  • Modify: src/internal/repository/submission.go(+ListAllBySubmitter? 否——ListBySubmitter 已够;MarkReviewed 已够;无新法——仅确认签名可用)
  • Create: src/internal/service/account.go + account_test.go
  • Create: src/internal/handler/account.go + account_test.go
  • Create: src/internal/api/account_postgres_test.go(gated 集成:全链断言)
  • Modify: src/internal/api/router.go(RouteDeleteAccount+Deps.Account)、src/cmd/serve.go(接线 AccountService/handler)、src/cmd/user.go(user delete)、Create src/cmd/catalog.go(catalog export)、src/cmd/main.go 或 root 注册
  • Modify: src/internal/handler/games.go(导出 RenderIndex(ctx) (any, error) / RenderDetail(ctx, id string) (any, error) 薄封装)
  • Modify: docs/CHANGELOG.md(0.15.0)

Interfaces(冻结,FE/冒烟依赖):

  • DELETE /api/auth/account body {"password": string} → 204 | 400 invalid_request | 401 invalid_credentials | 409 last_admin | 410 account_deleted;Cache-Control: no-store。
  • 错误常量:service.ErrAccountDeleted;Authenticate/Login 对墓碑分别 ErrUnauthorized/ErrInvalidCredentials。
  • AccountService{users repository.UserStore; content repository.ContentStore; objects storage.ObjectStorage; now func() time.Time},方法 DeleteSelf(ctx, userID, password) (deletionReport, error)、DeleteByEmail(ctx, email) (deletionReport, error)、deletionReport{Delisted, DraftsRemoved, PendingClosed int, UploadKeys []string}。
  • CLI:user delete <email> 打印 %s: deleted (works delisted=%d, drafts removed=%d, pending closed=%d);catalog export --out DIR [--pretty] 打印 exported %d game(s) to %s。
  • 导出产物:DIR/index.json = {"schemaVersion":2,"generatedAt":RFC3339,"games":[GameSummary...]};DIR/games/<user>__<slug>.json = GameDetail(与 /api/games/:user/:slug 逐字段同形状——测试用同一 handler 输出对拍)。

Steps:

  • S1 写失败测试:account_test.go(memory):DeleteSelf 成功→墓碑字段断言(email=deleted-<hexid>@deleted.invalid、username=gone-<id前8>、display_name=已注销用户、role=user、token_version+1、deleted_at!=nil);错密码 ErrInvalidCredentials;二次注销 ErrAccountDeleted;名下 published work 隐身(ListPublished 不再含);draft 删、pending→rejected;唯一 admin ErrLastAdmin;非唯一 admin 可销。favorites/ratings 行保留(memory 聚合仍计入)。
  • S2 实现 migration 0011 + model/repo/memory 扩展(MarkDeleted 一条 UPDATE 完成全部改写;List/CountAdmins 过滤 deleted_at IS NULL)。
  • S3 实现 service/account.go 内核(顺序照 spec §3.2:护栏→内容级联 tx→墓碑 tx 内最后→tx 外删对象 best-effort);S1 测试转绿。
  • S4 Authenticate/Login 墓碑纵深(各加 DeletedAt 判断)+ handler/account.go + router Deps.Account + serve.go 接线 + account 组 api handler_test(httptest:204/401/400/409/410)。
  • S5 user delete CLI + gated 集成 account_postgres_test.go:真库全链(注册→import 作品→投稿 draft+pending→注销→目录隐身/详情 404/me 401/原邮箱重注册成功/audit actor 保留可读)。
  • S6 handler.Games Render 方法 + catalog export cmd + 单测(memory store 造 2 作品——1 virtual 1 external——导出后读回断言 schemaVersion/文件名/详情 bundle 对象/external 无 bundle;--out 建目录幂等)。
  • S7 容器四连 gofmt -l .; go vet ./...; go build ./...; go test ./... 全绿;集成腿带 TEST_DATABASE_URL(p8 同款一次性 postgres:17-alpine 容器,密码放 /tmp/p8b2dbpw)-p 1 -count=1 零 FAIL 零 skip(skip 守卫 grep 必须 0)。
  • S8 CHANGELOG 0.15.0(Added/Changed 双语)+ commit feat(account): self-service account deletion (migration 0011, DELETE /api/auth/account, user delete CLI) 与 feat(catalog): catalog export CLI producing static-fallback JSON contract 两提交(分支 feat/p8b2-account-catalog)。

Task F: crearte — 注销 UI + 错误中文化

Files:

  • Modify: src/app/auth/client.ts(deleteAccount(password) DELETE 204)、src/app/auth/types.ts(+'account_deleted' 码)、src/app/auth/errors.ts(AUTH_ERROR_MESSAGES.account_deleted='该账号已注销')
  • Modify: src/app/views/AccountView.vue(第四段危险区)+ AccountView.test.ts(新用例:按钮禁用双闸/成功 invalidate+跳首页/401 出文案)
  • Modify: src/app/views/AdminUsersView.vue(actionError 按 code 映射表 validation/not_found/internal 中文化,last_admin/未知透传)+ AdminUsersView.test.ts(+3 断言)
  • Modify: src/e2e/auth.spec.ts(注销流,按该文件既有 mock 风格)
  • Modify: docs/CHANGELOG.md(0.21.0)

Steps:

  • F1 client/errors 单测先行(client.test.ts:204 空体 resolve、401 body code 解析、410 映射 account_deleted)→ 实现转绿。
  • F2 AccountView 危险区:密码 input + 勾选 checkbox(data-testid="delete-confirm-check"、delete-confirm-password、delete-account-button、delete-account-error);双闸才 enabled;成功链 authClient.deleteAccount → session.invalidate() → router.push('/');失败 toUserMessage(e)。测试四例照 Interfaces 断言。
  • F3 AdminUsersView 文案映射(小纯函数 adminActionMessage(e) 便于单测直打)+ 用例。
  • F4 npx vitest run + npx vue-tsc --noEmit 全绿(基线 499 只增不减);npx playwright test(主套件 70+1skip 基线)与 --config playwright.noauth.config.ts(4 passed)不回归;commit 两提交(auth client+errors / UI+admin 文案)分支 feat/p8b2-account-ui。

Task V: 控制者验收(两 Task 均 report DONE 后)

  • V1 server 终态容器四连 + 空库 -p 1 -count=1 集成复跑(skip 守卫 0)。
  • V2 curl 冒烟(分支真产物静态编译 + 一次性 p8b2 postgres 容器,p8 同款流程):register→login→作品 import CLI(复用测试夹具 1 virtual)→投稿 draft+pending→错密码 401→对密码 204→旧 token me 401→原邮箱重注册 201→唯一 admin 409;catalog export --out /tmp/p8b2cat 产物三断言(index schemaVersion2、fixture__x.json 存在含 bundle、jq 形状对)。跑完即焚容器/密码。
  • V3 FE 四腿复跑:vitest/typecheck/admin-flow+主套件/noauth。
  • V4 双路独立审查(对照 spec §1–§5 + Interfaces 冻结,两路各自 fresh 会话,一路 server 一路 FE+跨端契约)。
  • V5 审查 note 分诊(可修则修后复跑受影响腿;产品级偏离回报 owner)→ 合主 --no-ff 双仓 + 删分支 + push。
  • V6 wrapper:ROADMAP P8 行改「第二批完成·③④交付(P3 另批进行中)」+ CHANGELOG 0.2.10 双语 + 索引登记本 spec/plan(已执行)→ commit push。

Self-Review 记录

  • spec D-A…D-I → S1/S3(墓碑+级联+admin 护栏)、S4(API 映射)、S6/D-H(export 契约)、F2/F3(UI)、V2(边界矩阵)。D-G 的 username 正则满足性在 S1 断言(gone- + 8hex ≤39 且首尾合法——hex 小写含数字,- 不出头)。
  • 不确定点回填:MarkReviewed reviewer_id=自己(墓碑前)合法——FK 在行保留方案下恒成立;catalog export 走 handler.Games 需 ContentService 已在 serve.go 构造——cmd 里自建同栈。
  • 占位符扫描:无 TBD。