phase 5 prep: backfill-users.ts + deployment runbook in .memory

- scripts/backfill-users.ts: 扫 PG users 表,对每人幂等调 orchestrator POST /users
- orchestrator/package.json: 加 postgres 依赖
- .memory/project.md: Phase 3/4 完成状态,Phase 5 上线 checklist

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-19 21:17:01 +08:00
parent e7155b7001
commit 7df50e0dc3
4 changed files with 93 additions and 9 deletions

59
scripts/backfill-users.ts Normal file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bun
/**
* 给存量 LobeChat 用户补建沙箱容器(幂等,已存在就跳过)。
*
* 用法(在 VPS 上跑):
* export DATABASE_URL=postgresql://...
* export SANDBOX_BACKEND_URL=http://127.0.0.1:8700
* export SANDBOX_BACKEND_SECRET=<same as orchestrator>
* bun run scripts/backfill-users.ts
*/
import postgres from 'postgres';
const DATABASE_URL = process.env.DATABASE_URL;
const BACKEND_URL = process.env.SANDBOX_BACKEND_URL ?? 'http://127.0.0.1:8700';
const SECRET = process.env.SANDBOX_BACKEND_SECRET;
if (!DATABASE_URL) throw new Error('DATABASE_URL required');
if (!SECRET) throw new Error('SANDBOX_BACKEND_SECRET required');
const sql = postgres(DATABASE_URL, { max: 1 });
const users: { id: string; email: string | null }[] = await sql`
SELECT id, email FROM users ORDER BY created_at ASC
`;
console.log(`[backfill] ${users.length} users to check`);
let ok = 0,
skip = 0,
fail = 0;
for (const u of users) {
try {
const res = await fetch(`${BACKEND_URL}/api/v1/users`, {
body: JSON.stringify({ userId: u.id }),
headers: { 'Content-Type': 'application/json', 'X-Sandbox-Secret': SECRET },
method: 'POST',
});
const text = await res.text();
if (res.ok) {
if (text.includes('"success":true')) {
ok++;
console.log(`[backfill] ✓ ${u.id} (${u.email ?? '-'})`);
} else {
skip++;
console.log(`[backfill] ~ ${u.id}: ${text.slice(0, 100)}`);
}
} else {
fail++;
console.error(`[backfill] ✗ ${u.id}: HTTP ${res.status} ${text.slice(0, 100)}`);
}
} catch (e) {
fail++;
console.error(`[backfill] ✗ ${u.id}:`, (e as Error).message);
}
}
console.log(`[backfill] done: ok=${ok} skip=${skip} fail=${fail}`);
await sql.end();