feat: 管理员账号体系 + 动态邀请码管理; 各端内置数据固定 uuid 对齐

This commit is contained in:
root
2026-10-16 15:24:49 +08:00
parent e0866aa690
commit dc520bfac0
7 changed files with 305 additions and 134 deletions
+49 -17
View File
@@ -5,7 +5,8 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { getDb } from './db.mjs';
import { createAppCommands } from './app-commands.mjs';
import { register, login, authUser } from './auth.mjs';
import { register, login, authUser, isUserAdmin } from './auth.mjs';
import crypto from 'node:crypto';
import { push, pull } from './sync.mjs';
import { upload, download } from './attachments.mjs';
@@ -64,21 +65,52 @@ const server = http.createServer(async (req, res) => {
return send(200, true, login(body));
}
// ---- 管理(X-Admin-Token)----
if (req.method === 'GET' && route === '/admin/users') {
const expect = process.env.ADMIN_TOKEN;
if (!expect || req.headers['x-admin-token'] !== expect) {
return send(403, false, '管理接口未启用或令牌错误');
}
// ---- 管理(仅 is_admin 登录会话) ----
if (route === '/admin/users' || route === '/admin/invites' || route.startsWith('/admin/invites/generate') || route.startsWith('/admin/invites/revoke')) {
let auth;
try { auth = authUser(req); }
catch (e) { return send(e.code || 401, false, e.message); }
if (!isUserAdmin(auth.user_id)) return send(403, false, '需要管理员账号');
const db = getDb();
const users = db.prepare(`
SELECT u.id, u.username, u.created_at,
(SELECT COUNT(*) FROM devices dv WHERE dv.user_id = u.id) AS devices,
(SELECT COUNT(*) FROM current c WHERE c.user_id = u.id AND c.op = 'upsert') AS records,
(SELECT COUNT(*) FROM attachments a WHERE a.user_id = u.id) AS attachments,
(SELECT COALESCE(SUM(size), 0) FROM attachments a WHERE a.user_id = u.id) AS attachment_bytes
FROM users u ORDER BY u.created_at`).all();
return send(200, true, { total: users.length, users });
if (req.method === 'GET' && route === '/admin/users') {
const users = db.prepare(`
SELECT u.id, u.username, u.is_admin, u.created_at,
(SELECT COUNT(*) FROM devices dv WHERE dv.user_id = u.id) AS devices,
(SELECT COUNT(*) FROM current c WHERE c.user_id = u.id AND c.op = 'upsert') AS records,
(SELECT COUNT(*) FROM attachments a WHERE a.user_id = u.id) AS attachments,
(SELECT COALESCE(SUM(size), 0) FROM attachments a WHERE a.user_id = u.id) AS attachment_bytes
FROM users u ORDER BY u.created_at`).all()
.map(u => ({ ...u, is_admin: !!u.is_admin }));
return send(200, true, { total: users.length, users });
}
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
ORDER BY i.id DESC`).all()
.map(i => ({ ...i, valid: !!i.valid }));
return send(200, true, { total: invites.length, invites });
}
if (req.method === 'POST' && route === '/admin/invites/generate') {
const body = await readJson(req);
const count = Math.min(50, Math.max(1, Number(body.count) || 1));
const codes = [];
const ins = db.prepare('INSERT INTO invite_codes (code, created_by) VALUES (?,?)');
for (let i = 0; i < count; i++) {
const code = 'FP-' + crypto.randomBytes(5).toString('hex').toUpperCase();
ins.run(code, auth.user_id);
codes.push(code);
}
return send(200, true, { codes });
}
if (req.method === 'POST' && route === '/admin/invites/revoke') {
const body = await readJson(req);
const r = db.prepare('UPDATE invite_codes SET valid=0 WHERE id=?').run(Number(body.id));
return send(200, true, { revoked: r });
}
return send(404, false, 'not found');
}
// ---- 同步与 Web API(鉴权区) ----
@@ -88,8 +120,8 @@ const server = http.createServer(async (req, res) => {
catch (e) { return send(e.code || 401, false, e.message); }
if (req.method === 'GET' && route === '/auth/me') {
const row = getDb().prepare('SELECT id, username FROM users WHERE id=?').get(auth.user_id);
return send(200, true, { user_id: auth.user_id, username: row?.username ?? '', device_id: auth.device_id });
const row = getDb().prepare('SELECT id, username, is_admin FROM users WHERE id=?').get(auth.user_id);
return send(200, true, { user_id: auth.user_id, username: row?.username ?? '', is_admin: !!row?.is_admin, device_id: auth.device_id });
}
if (req.method === 'POST' && route === '/sync/push') {