Files
digital-me-history/.github/workflows/purge-twins.yml
T
admin 0593ff7e62
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s
数字我 Digital Me:全量入库(大文件走 Git LFS)
2026-10-10 16:24:43 +08:00

85 lines
4.5 KiB
YAML
Executable File

name: Purge Twins (删测试分身)
# 按名字/slug 删掉指定分身(测试号留下的垃圾数据)。
#
# 为什么要走工作流:生产库在共享 ECS 上、不对外开放,本地连不上。
# 为什么不手写 deleteOne:分身的数据散在 12+ 个集合里(知识/记忆/消息/会话/分享/
# 作品/问答卡/课堂参与/排期报名/付费解锁/钱包/大师档案…)。这里【复用后端自己的
# deleteTwin()】,跟用户在页面上点"删除分身"走完全同一条级联,不留孤儿数据。
#
# 默认 dry_run=true:先只打印"会删掉谁、连带多少条数据",确认无误再关掉 dry_run 真删。
on:
workflow_dispatch:
inputs:
targets:
# 已预填老板确认过的 5 个测试分身(竹子=测试号4 的私密分身;其余 4 个是挂在广场上的垃圾)。
# ⚠ 不要往里加 夏晚/陈九/林医生/阿May/阿正/大鹏/林小满/小野/苏晴/老周 ——
# 那 10 个是 scripts/seed-masters.js 种的【演示分身】,是广场的门面,删了广场就空了。
description: '要删的分身,填 slug 或显示名,逗号分隔'
required: true
default: '竹子,1234,社交测试89tuu7ds,11-2,12'
type: string
dry_run:
description: '试运行:只打印会删什么,不真删(想先看一眼就勾上)'
required: false
default: false
type: boolean
jobs:
purge:
runs-on: ubuntu-latest
steps:
- name: Purge
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.ECS_HOST }}
username: ${{ secrets.ECS_USER }}
password: ${{ secrets.ECS_PASSWORD }}
command_timeout: 10m
envs: TARGETS,DRY_RUN
script: |
set -e
cd /var/www/digitalme-api.invoratec.cn
[ -f .env ] || { echo "❌ 生产 .env 缺失,中止"; exit 1; }
TARGETS="${{ inputs.targets }}" DRY_RUN="${{ inputs.dry_run }}" node --input-type=module -e '
import { connectDb, getDb } from "./src/db.js";
import { deleteTwin } from "./src/services/twin.js";
const dry = String(process.env.DRY_RUN) !== "false";
const names = String(process.env.TARGETS || "").split(",").map(s => s.trim()).filter(Boolean);
if (!names.length) { console.log("没填 targets,什么都不做"); process.exit(0); }
await connectDb();
const db = getDb();
// slug 或 displayName 精确匹配(不做模糊,免得误伤同名的真实用户)。
const twins = await db.collection("twins")
.find({ $or: [{ slug: { $in: names } }, { displayName: { $in: names } }] })
.toArray();
const found = new Set(twins.flatMap(t => [t.slug, t.displayName]));
for (const n of names) if (!found.has(n)) console.log(`⚠ 没找到:「${n}」(跳过)`);
if (!twins.length) { console.log("一个都没匹配上,退出"); process.exit(0); }
console.log(dry ? "\n=== 试运行(不会真删) ===\n" : "\n=== 真删 ===\n");
for (const t of twins) {
const owner = t.ownerId ? await db.collection("users").findOne({ _id: t.ownerId }) : null;
// 顺带报一下这个分身身上挂了多少数据,好确认"确实是个空壳测试号"。
const [kn, msg, mem] = await Promise.all([
db.collection("knowledge").countDocuments({ twinId: t._id }),
db.collection("messages").countDocuments({ twinId: t._id }),
db.collection("memories").countDocuments({ twinId: t._id }),
]);
console.log(`【${t.displayName}】slug=${t.slug} 公开=${!!t.isPublic}`);
console.log(` 主人: ${owner?.email || owner?.phone || "(无主/已注销)"} 建于: ${t.createdAt?.toISOString?.().slice(0, 10) || "?"}`);
console.log(` 知识 ${kn} 条 / 对话 ${msg} 条 / 记忆 ${mem} 条`);
if (dry) { console.log(" → 试运行,未删\n"); continue; }
await deleteTwin(t._id.toString(), t.ownerId);
console.log(" → 已删(级联清干净)\n");
}
console.log(dry ? "试运行结束。确认无误后,把 dry_run 关掉再跑一次即可真删。" : `完成,删除 ${twins.length} 个分身。`);
process.exit(0);
'