关于 grove-account-deletion
面向 Grove 平台维护者的账号删除技能,按步骤清除租户及关联数据。流程强调删除前确认、检查订阅状态,并核验数据库与 R2 清理结果。
主要功能
- 01查找并确认租户身份
- 02删除租户及关联记录
- 03清理 R2 媒体对象
- 04核验删除结果
- 05检查订阅与多租户情形
适用场景
- 01移除演示或测试账号
- 02处理用户账号删除请求
- 03清理租户遗留数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ autumnsgrove/ lattice --skill grove-account-deletion安装后在 Claude Code 中输入/grove-account-deletion确认已加载。
详细介绍
Grove 账户删除
使用 wrangler CLI 命令,从 Grove 平台安全地删除租户账户及其所有关联数据。
何时启用
- 用户说类似“删除 Alice 的账户”或“Alice 不用了”
- 用户提到要移除测试账户
- 用户要求在演示或试用后清理数据
- 用户明确调用
/grove-account-deletion - 用户说“把那个账户彻底删掉”或“清除他们的数据”
流程
Identify → Verify → Snapshot → Delete Tenant → Clean Orphans → Purge R2 → Verify
步骤 1:识别租户
用户会提供用户名(子域名)、电子邮件或显示名称。查询对应租户:
# By username/subdomain (most common)
npx wrangler d1 execute grove-engine-db --remote --command="SELECT id, subdomain, display_name, email, plan, created_at FROM tenants WHERE subdomain = 'USERNAME';"
# By email (if username unknown)
npx wrangler d1 execute grove-engine-db --remote --command="SELECT id, subdomain, display_name, email, plan, created_at FROM tenants WHERE email = 'EMAIL';"
# By display name (fuzzy match)
npx wrangler d1 execute grove-engine-db --remote --command="SELECT id, subdomain, display_name, email, plan, created_at FROM tenants WHERE display_name LIKE '%NAME%';"
请在项目根目录(/Users/mini/Documents/Projects/Lattice)运行这些命令。
步骤 2:与用户确认
删除任何内容之前,向用户展示查到的信息,并征求明确确认:
Found tenant:
ID: abc-123-def
Username: alice
Name: Alice Wonderland
Email: [email protected]
Plan: seedling
Created: 2026-01-24
This will permanently delete:
- All blog posts and pages
- All media files (R2 storage)
- All sessions and settings
- Commerce data (orders, products, customers)
- Curio configurations (timeline, journey, gallery)
- Onboarding and billing records
Proceed with deletion?
绝不能跳过此确认步骤。 即使是测试账户也不例外。
步骤 3:删除前快照(可选,但建议执行)
对于非测试账户,快速统计即将删除的数据:
npx wrangler d1 execute grove-engine-db --remote --command="
SELECT
(SELECT COUNT(*) FROM posts WHERE tenant_id = 'TENANT_ID') as posts,
(SELECT COUNT(*) FROM pages WHERE tenant_id = 'TENANT_ID') as pages,
(SELECT COUNT(*) FROM media WHERE tenant_id = 'TENANT_ID') as media_files,
(SELECT COUNT(*) FROM sessions WHERE tenant_id = 'TENANT_ID') as sessions;
"
步骤 4:删除租户(级联删除会处理 29 张表)
执行此单条 DELETE 命令,会级联删除:posts、pages、media 记录、tenant_settings、sessions、products、product_variants、customers、orders、order_line_items、subscriptions、connect_accounts、platform_billing、refunds、discount_codes、post_views、timeline_curio_config、timeline_summaries、timeline_activity、timeline_ai_usage、journey_curio_config、journey_snapshots、journey_summaries、journey_jobs、gallery_curio_config、gallery_images、gallery_tags、gallery_collections、git_dashboard_config。
npx wrangler d1 execute grove-engine-db --remote --command="DELETE FROM tenants WHERE id = 'TENANT_ID';"
步骤 5:清理孤立记录
以下表不会从 tenants 级联删除,需要手动清理:
# Remove onboarding record (linked by username, not FK)
npx wrangler d1 execute grove-engine-db --remote --command="DELETE FROM user_onboarding WHERE username = 'USERNAME';"
# Remove email verification codes for that user (cascades from user_onboarding via FK, but verify)
npx wrangler d1 execute grove-engine-db --remote --command="DELETE FROM email_verification_codes WHERE user_id IN (SELECT id FROM user_onboarding WHERE username = 'USERNAME');"
清理旧版 users 表:
npx wrangler d1 execute grove-engine-db --remote --command="DELETE FROM users WHERE LOWER(email) = LOWER('USER_EMAIL') AND tenant_id = 'TENANT_ID';"
步骤 5b:清理 Heartwood 身份验证记录
重要: 如果不执行此步骤,用户重新注册时会看到“欢迎回来”消息,因为 Heartwood 中仍然存在 ba_user 记录。
# Check if user has other tenants first
npx wrangler d1 execute grove-engine-db --remote --command="SELECT id, subdomain FROM tenants WHERE email = 'USER_EMAIL';"
# If no other tenants exist, clean Heartwood auth records:
# Delete sessions
npx wrangler d1 execute groveauth --remote --command="DELETE FROM ba_session WHERE user_id IN (SELECT id FROM ba_user WHERE email = 'USER_EMAIL');"
# Delete OAuth account linkages (Google, etc.)
npx wrangler d1 execute groveauth --remote --command="DELETE FROM ba_account WHERE user_id IN (SELECT id FROM ba_user WHERE email = 'USER_EMAIL');"
# Delete verification records
npx wrangler d1 execute groveauth --remote --command="DELETE FROM ba_verification WHERE identifier = 'USER_EMAIL';"
# Delete the auth user record
npx wrangler d1 execute groveauth --remote --command="DELETE FROM ba_user WHERE email = 'USER_EMAIL';"
步骤 6:清除 R2 媒体文件(如果存在媒体文件)
R2 键的格式为 {tenant_id}/{filename}。D1 media 表中的记录已被删除(级联删除),但实际的 R2 对象仍然存在。
# List objects with tenant prefix
npx wrangler r2 object list grove-media --prefix="TENANT_ID/" --remote
# Delete each object (wrangler doesn't support bulk delete, so iterate)
# For a small number of files:
npx wrangler r2 object delete grove-media --remote "TENANT_ID/filename1.webp"
npx wrangler r2 object delete grove-media --remote "TENANT_ID/filename2.webp"
如果文件较多,请生成删除脚本:
# List all keys, then delete in a loop
npx wrangler r2 object list grove-media --prefix="TENANT_ID/" --remote 2>/dev/null | jq -r '.[] .key' | while read key; do
echo "Deleting: $key"
npx wrangler r2 object delete grove-media --remote "$key"
done
如果删除前快照显示媒体文件数量为 0,请跳过此步骤。
步骤 7:验证删除结果
确认租户已被彻底删除:
# Verify tenant deleted
npx wrangler d1 execute grove-engine-db --remote --command="SELECT COUNT(*) as remaining FROM tenants WHERE subdomain = 'USERNAME';"
# Verify cascade worked (spot-check a few tables)
npx wrangler d1 execute grove-engine-db --remote --command="
SELECT
(SELECT COUNT(*) FROM posts WHERE tenant_id = 'TENANT_ID') as posts,
(SELECT COUNT(*) FROM media WHERE tenant_id = 'TENANT_ID') as media,
(SELECT COUNT(*) FROM sessions WHERE tenant_id = 'TENANT_ID') as sessions;
"
# Verify onboarding cleaned
npx wrangler d1 execute grove-engine-db --remote --command="SELECT COUNT(*) as remaining FROM user_onboarding WHERE username = 'USERNAME';"
步骤 8:报告结果
向用户提供简洁的摘要:
Tenant "alice" (Alice Wonderland) has been fully deleted.
Cleaned up:
- Tenant record + 29 cascaded tables
- Onboarding record
- 3 R2 media files
- Email verification codes
The subdomain "alice.grove.place" is now available for reuse.
安全规则
- 删除前务必确认 - 即使用户看起来很确定,也要先向他们展示租户详情
- 绝不能仅凭 ID 删除 - 务必先解析为便于识别的信息(子域名/电子邮件),以供确认
- 检查是否有有效订阅 - 如果
platform_billing.status = 'active'或存在 LemonSqueezy 订阅,请发出警告 - 测试账户仍然是账户 - 同样的流程、同样的确认、同样的验证
- R2 中的数据无法恢复 - 删除 R2 对象后无法恢复。D1
media记录(其中保存了键)已被级联删除,因此请在无法追踪已存储内容之前完成 R2 清理
快速模式(测试账户)
对于用户明确称为“测试账户”的账户,或在当前会话中创建的账户:
- 仍需确认用户名/电子邮件
- 跳过删除前快照
- 如果未上传媒体文件,则跳过 R2 清理
- 仍需验证删除已完成
边缘情况
用户有多个租户
一个电子邮件地址可以关联多个子域名。只删除指定的租户,不要动其他租户。
未找到租户
如果子域名/电子邮件与任何租户都不匹配,请告知用户。不要猜测。
Heartwood 会话清理
Heartwood 会话存储在 KV 中,而非 D1。D1 sessions 表用于旧版/本地会话。Heartwood 会话会自然过期(TTL 为 24 小时)。除非用户说明情况紧急,否则无需手动清理。
LemonSqueezy 订阅处于有效状态
如果已设置 user_onboarding.lemonsqueezy_subscription_id,请提醒用户,他们可能需要在 LemonSqueezy 控制面板中单独取消订阅。删除 D1 数据库不会取消外部账单。
环境
所有命令均从项目根目录运行:/Users/mini/Documents/Projects/Lattice
数据库:grove-engine-db(D1)
媒体存储桶:grove-media(R2)
干净利落地删除,是一种体面的告别。不要留下任何孤儿。