10 KiB
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(server921443a/ crearte70ba10c)切上述分支;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)、Createsrc/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/accountbody{"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 deleteCLI + gated 集成account_postgres_test.go:真库全链(注册→import 作品→投稿 draft+pending→注销→目录隐身/详情 404/me 401/原邮箱重注册成功/audit actor 保留可读)。 - S6 handler.Games Render 方法 +
catalog exportcmd + 单测(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 小写含数字,-不出头)。 - 不确定点回填:
MarkReviewedreviewer_id=自己(墓碑前)合法——FK 在行保留方案下恒成立;catalog export走handler.Games需ContentService已在 serve.go 构造——cmd 里自建同栈。 - 占位符扫描:无 TBD。