fix: lazily seed demo data whenever demo user db first opens (self-heal if startup seeding failed)
This commit is contained in:
@@ -26,12 +26,23 @@ const MIME = {
|
||||
|
||||
// 按用户惰性创建命令集(每个用户一个独立 SQLite 档案库)
|
||||
const userCommands = new Map();
|
||||
let demoUserId;
|
||||
function commandsFor(userId) {
|
||||
let c = userCommands.get(userId);
|
||||
if (!c) {
|
||||
fs.mkdirSync(USERS_DIR, { recursive: true });
|
||||
c = createAppCommands(path.join(USERS_DIR, userId + '.db'));
|
||||
userCommands.set(userId, c);
|
||||
// demo 用户:档案库首次打开时播种演示数据(demo_seed 幂等,失败不影响请求)
|
||||
try {
|
||||
if (demoUserId === undefined) {
|
||||
demoUserId = getDb().prepare('SELECT id FROM users WHERE username=?').get('demo')?.id ?? null;
|
||||
}
|
||||
if (userId === demoUserId) {
|
||||
const r = c.handleCommand('demo_seed', {});
|
||||
console.log('[footprints-sync] demo 演示数据:', JSON.stringify(r));
|
||||
}
|
||||
} catch (e) { console.log('[footprints-sync] demo 数据播种跳过:', e.message); }
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -66,7 +77,7 @@ const server = http.createServer(async (req, res) => {
|
||||
}
|
||||
|
||||
// ---- 管理(仅 is_admin 登录会话) ----
|
||||
if (route === '/admin/users' || route === '/admin/invites' || route.startsWith('/admin/invites/generate') || route.startsWith('/admin/invites/revoke')) {
|
||||
if (route === '/admin/users' || route === '/admin/invites' || route.startsWith('/admin/invites/')) {
|
||||
let auth;
|
||||
try { auth = authUser(req); }
|
||||
catch (e) { return send(e.code || 401, false, e.message); }
|
||||
@@ -86,9 +97,11 @@ const server = http.createServer(async (req, res) => {
|
||||
}
|
||||
if (req.method === 'GET' && route === '/admin/invites') {
|
||||
const invites = db.prepare(`
|
||||
SELECT i.id, i.code, i.created_at, i.used_by, i.used_at, i.valid,
|
||||
u.username AS used_by_name
|
||||
FROM invite_codes i LEFT JOIN users u ON u.id = i.used_by
|
||||
SELECT i.id, i.code, i.remark, i.created_at, i.used_by, i.used_at, i.valid,
|
||||
u.username AS used_by_name, cu.username AS created_by_name
|
||||
FROM invite_codes i
|
||||
LEFT JOIN users u ON u.id = i.used_by
|
||||
LEFT JOIN users cu ON cu.id = i.created_by
|
||||
ORDER BY i.id DESC`).all()
|
||||
.map(i => ({ ...i, valid: !!i.valid }));
|
||||
return send(200, true, { total: invites.length, invites });
|
||||
@@ -110,6 +123,39 @@ const server = http.createServer(async (req, res) => {
|
||||
const r = db.prepare('UPDATE invite_codes SET valid=0 WHERE id=?').run(Number(body.id));
|
||||
return send(200, true, { revoked: r });
|
||||
}
|
||||
if (req.method === 'POST' && route === '/admin/invites/create') {
|
||||
const body = await readJson(req);
|
||||
const code = String(body.code || '').trim().toUpperCase();
|
||||
if (!/^[A-Z0-9_-]{4,32}$/.test(code)) return send(400, false, '邀请码需为 4-32 位字母、数字、- 或 _');
|
||||
const remark = String(body.remark || '').trim().slice(0, 64) || null;
|
||||
try {
|
||||
db.prepare('INSERT INTO invite_codes (code, remark, created_by) VALUES (?,?,?)').run(code, remark, auth.user_id);
|
||||
} catch (e) {
|
||||
return send(400, false, String(e.message).includes('UNIQUE') ? ('邀请码已存在:' + code) : (e.message || String(e)));
|
||||
}
|
||||
return send(200, true, { code });
|
||||
}
|
||||
if (req.method === 'POST' && route === '/admin/invites/update') {
|
||||
const body = await readJson(req);
|
||||
const row = db.prepare('SELECT id, used_by FROM invite_codes WHERE id=?').get(Number(body.id));
|
||||
if (!row) return send(404, false, '邀请码不存在');
|
||||
const sets = []; const args = [];
|
||||
if (typeof body.remark === 'string') { sets.push('remark=?'); args.push(body.remark.trim().slice(0, 64) || null); }
|
||||
if (typeof body.valid === 'boolean') {
|
||||
if (body.valid && row.used_by) return send(400, false, '已使用的邀请码不可启用');
|
||||
sets.push('valid=?'); args.push(body.valid ? 1 : 0);
|
||||
}
|
||||
if (!sets.length) return send(400, false, '没有需要更新的字段');
|
||||
args.push(row.id);
|
||||
db.prepare('UPDATE invite_codes SET ' + sets.join(', ') + ' WHERE id=?').run(...args);
|
||||
return send(200, true, { id: row.id });
|
||||
}
|
||||
if (req.method === 'POST' && route === '/admin/invites/delete') {
|
||||
const body = await readJson(req);
|
||||
const r = db.prepare('DELETE FROM invite_codes WHERE id=?').run(Number(body.id));
|
||||
if (!r.changes) return send(404, false, '邀请码不存在');
|
||||
return send(200, true, { deleted: r.changes });
|
||||
}
|
||||
return send(404, false, 'not found');
|
||||
}
|
||||
|
||||
@@ -209,16 +255,20 @@ function readJson(req) {
|
||||
|
||||
getDb();
|
||||
|
||||
// 种子演示账号(demo / demo1234);seed 标记绕过邀请码/名额校验,失败原因打出日志
|
||||
// 种子演示账号(demo / demo1234)+ 演示数据;seed 标记绕过邀请码/名额校验
|
||||
try {
|
||||
const d = getDb();
|
||||
if (!d.prepare('SELECT 1 FROM users WHERE username=?').get('demo')) {
|
||||
const existing = d.prepare('SELECT id FROM users WHERE username=?').get('demo');
|
||||
let demoId = existing?.id;
|
||||
if (!demoId) {
|
||||
const { register } = await import('./auth.mjs');
|
||||
register({ username: 'demo', password: 'demo1234', device_name: 'seed-demo', seed: true });
|
||||
demoId = register({ username: 'demo', password: 'demo1234', device_name: 'seed-demo', seed: true }).user_id;
|
||||
console.log('[footprints-sync] demo 账号已创建 (demo / demo1234)');
|
||||
} else {
|
||||
console.log('[footprints-sync] demo 账号已存在 (demo / demo1234)');
|
||||
}
|
||||
const seeded = commandsFor(demoId).handleCommand('demo_seed', {});
|
||||
console.log('[footprints-sync] demo 演示数据:', JSON.stringify(seeded));
|
||||
} catch (e) { console.log('[footprints-sync] demo 种子跳过:', e.message); }
|
||||
|
||||
server.listen(PORT, '0.0.0.0', () => {
|
||||
|
||||
Reference in New Issue
Block a user