Skip to main content

路径

DELETE /user/?userId={userId}&password={password}

认证

内部端点,无 JWT。 用 query 上的固定密码保护:password 不匹配 $INTERNAL_DELETE_PASSWORD 时返回 404(不是 401,避免暴露端点存在)。
这是内部硬删除入口(GDPR/CCPA right-to-erasure),通常由 account_deletion PGMQ worker 在 30 天倒计时后调用。不要暴露给前端。密码占位符 $INTERNAL_DELETE_PASSWORD,真值见 Notion Key 页。

参数

参数位置说明
passwordquery必填,不匹配 → 404
userIdquery必填,缺失 → 404

响应

{
  "message": "ok",
  "deletedUser": {
    "id": "uuid",
    "email": "user@example.com",
    "nickname": "Owen",
    "plan": "free",
    "credit": 0
  }
}
deletedUser 各字段来自删除前 best-effort 预取(读不到为 null,不阻断删除)。

删除顺序(全程 idempotent,auth user 留到最后)

先清完所有下游再删 auth user —— 任何一步失败,caller retry 都安全,不会因 auth 先删导致下游 cleanup 跑不到留孤儿。
  1. Stripe — US(STRIPE_SECRET_KEY)+ SG(STRIPE_SECRET_KEY_SG)两区域 customer 删除。resource_missing 已捕获不报错
  2. PostHog — GDPR person 删除(delete_events=true),person 不存在自然 no-op
  3. Supabase 业务表 — 显式删(不依赖 FK CASCADE 的可观察性):user_liked_feeds / user_liked_filters / feed(legacy)/ feeds_v2 / threads(按 resource_id,CASCADE 带走 thread_versionmessages)/ user_profiles(主表,最后删)
  4. cdn per-user cascadecdnClient.deleteScope:R2 prefix u/{userId}/ + legacy {userId}/feed/{userId}/ + cdn DB rows
  5. cdn media_tasksdeleteTasksByOwner 清 task 索引行
  6. avatar 兜底avatar_image_id 可能不在 u/{userId}/ 下(历史 OAuth pic / cross-owner ref),补删一刀
  7. Supabase auth user — 最后删主键。User not found / user_not_found 视为已删成功(return ok);其他错误抛 500 让 worker retry

错误

状态码含义
404password 不匹配或 userId 缺失
500auth user 删除失败(非 already-gone)
下游各步(Stripe/PostHog/Supabase 表/cdn/avatar)失败只 warn/error log,不阻断。

src/hono/user/index.tsDELETE /

相关