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 档案库)
|
// 按用户惰性创建命令集(每个用户一个独立 SQLite 档案库)
|
||||||
const userCommands = new Map();
|
const userCommands = new Map();
|
||||||
|
let demoUserId;
|
||||||
function commandsFor(userId) {
|
function commandsFor(userId) {
|
||||||
let c = userCommands.get(userId);
|
let c = userCommands.get(userId);
|
||||||
if (!c) {
|
if (!c) {
|
||||||
fs.mkdirSync(USERS_DIR, { recursive: true });
|
fs.mkdirSync(USERS_DIR, { recursive: true });
|
||||||
c = createAppCommands(path.join(USERS_DIR, userId + '.db'));
|
c = createAppCommands(path.join(USERS_DIR, userId + '.db'));
|
||||||
userCommands.set(userId, c);
|
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;
|
return c;
|
||||||
}
|
}
|
||||||
@@ -66,7 +77,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- 管理(仅 is_admin 登录会话) ----
|
// ---- 管理(仅 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;
|
let auth;
|
||||||
try { auth = authUser(req); }
|
try { auth = authUser(req); }
|
||||||
catch (e) { return send(e.code || 401, false, e.message); }
|
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') {
|
if (req.method === 'GET' && route === '/admin/invites') {
|
||||||
const invites = db.prepare(`
|
const invites = db.prepare(`
|
||||||
SELECT i.id, i.code, i.created_at, i.used_by, i.used_at, i.valid,
|
SELECT i.id, i.code, i.remark, i.created_at, i.used_by, i.used_at, i.valid,
|
||||||
u.username AS used_by_name
|
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
|
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()
|
ORDER BY i.id DESC`).all()
|
||||||
.map(i => ({ ...i, valid: !!i.valid }));
|
.map(i => ({ ...i, valid: !!i.valid }));
|
||||||
return send(200, true, { total: invites.length, invites });
|
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));
|
const r = db.prepare('UPDATE invite_codes SET valid=0 WHERE id=?').run(Number(body.id));
|
||||||
return send(200, true, { revoked: r });
|
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');
|
return send(404, false, 'not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,16 +255,20 @@ function readJson(req) {
|
|||||||
|
|
||||||
getDb();
|
getDb();
|
||||||
|
|
||||||
// 种子演示账号(demo / demo1234);seed 标记绕过邀请码/名额校验,失败原因打出日志
|
// 种子演示账号(demo / demo1234)+ 演示数据;seed 标记绕过邀请码/名额校验
|
||||||
try {
|
try {
|
||||||
const d = getDb();
|
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');
|
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)');
|
console.log('[footprints-sync] demo 账号已创建 (demo / demo1234)');
|
||||||
} else {
|
} else {
|
||||||
console.log('[footprints-sync] demo 账号已存在 (demo / demo1234)');
|
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); }
|
} catch (e) { console.log('[footprints-sync] demo 种子跳过:', e.message); }
|
||||||
|
|
||||||
server.listen(PORT, '0.0.0.0', () => {
|
server.listen(PORT, '0.0.0.0', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user