feat(account): self-service deletion with a 7-day cooling-off period

Request signs out everywhere and freezes paid routes (423 + DB triggers);
signing in within 7 days shows the pending gate with 撤销注销. A periodic
idempotent worker purges personal content afterwards and keeps finance
rows against a tombstoned identity. Read-only admin list.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
Jesse_Chen
2026-09-30 09:42:11 +08:00
co-authored by Claude Opus 5.5
parent 3c8123d912
commit 96adbce3a9
30 changed files with 1480 additions and 2 deletions
@@ -0,0 +1,44 @@
"use client";
import { Descriptions, type TableColumnsType } from "antd";
import { formatAdminDate, ResourceTable } from "@/components/admin/resource-table";
type DeletionRecord = {
id: string;
userId: string;
status: string;
requestedAt: string;
scheduledFor: string;
cancelledAt: string | null;
completedAt: string | null;
attemptCount: number;
errorCode: string | null;
};
const STATUS_LABELS: Record<string, string> = { pending: "冷静期中", cancelled: "已撤销", completed: "已删除" };
const columns: TableColumnsType<DeletionRecord> = [
{ title: "状态", dataIndex: "status", sorter: true, render: (value: string) => STATUS_LABELS[value] ?? value },
{ title: "用户 ID", dataIndex: "userId" },
{ title: "申请时间", dataIndex: "requestedAt", sorter: true, render: formatAdminDate },
{ title: "计划删除", dataIndex: "scheduledFor", sorter: true, render: formatAdminDate },
{ title: "撤销时间", dataIndex: "cancelledAt", render: (value: string | null) => (value ? formatAdminDate(value) : "—") },
{ title: "完成时间", dataIndex: "completedAt", render: (value: string | null) => (value ? formatAdminDate(value) : "—") },
{ title: "失败次数", dataIndex: "attemptCount" },
{ title: "最近错误", dataIndex: "errorCode", render: (value: string | null) => value ?? "—" },
];
export default function AccountDeletionsPage() {
return <ResourceTable<DeletionRecord>
resource="account-deletions"
title="注销申请(只读)"
columns={columns}
statusOptions={[
{ label: "冷静期中", value: "pending" },
{ label: "已撤销", value: "cancelled" },
{ label: "已删除", value: "completed" },
]}
extra={<Descriptions size="small" items={[{ key: "policy", label: "说明", children: "7 天冷静期后自动删除个人内容;订单与点数流水保留但不再关联身份。本表不存邮箱与昵称。" }]} />}
/>;
}
@@ -104,6 +104,7 @@ export function AdminApp({ children }: { children: ReactNode }) {
{ name: "administrators", list: "/admin/administrators", meta: { label: "管理员", icon: <SafetyCertificateOutlined /> } },
{ name: "roles", list: "/admin/roles", meta: { label: "角色权限", icon: <TeamOutlined /> } },
{ name: "customers", list: "/admin/customers", meta: { label: "用户资料", icon: <UserOutlined /> } },
{ name: "account-deletions", list: "/admin/account-deletions", meta: { label: "注销申请", icon: <UserOutlined /> } },
{ name: "products", list: "/admin/products", meta: { label: "商品权益", icon: <ShoppingOutlined /> } },
{ name: "subscriptions", list: "/admin/subscriptions", meta: { label: "订阅", icon: <CreditCardOutlined /> } },
{ name: "orders", list: "/admin/orders", meta: { label: "订单", icon: <DatabaseOutlined /> } },