feat: external API for QQ bot - client id+secret auth, ticket tracking, bans, per-server data

- db: api_clients table + server_groups.alias + tickets/bans server_name (auto-migrate)
- external.js: client_id+secret auth (multi-client, bcrypt, show-once secret),
  legacy x-external-key compat, JWT pass-through
  - POST /tickets (optional server), GET /tickets/track?token= (full lifecycle)
  - GET/POST /bans, reported-players/stats/all-tickets with server filter
  - GET /servers (alias list), client CRUD (owner/admin, audit logged)
  - resolveServer: alias | 分组/子服 | server_name
- polls.js: server_groups CRUD with alias (add/edit, dup check)
- servers page: alias display + edit; new 外部API page: client mgmt + quick docs
- API.md: full external API section (auth matrix, server param syntax)
- verified: 37 checks incl. resolveServer sandbox (alias/group/fallback)
This commit is contained in:
2026-08-19 19:12:34 +08:00
parent a11df22600
commit 257cd14051
8 changed files with 393 additions and 54 deletions

View File

@@ -9,12 +9,27 @@ router.get('/groups', authenticate, async (req, res) => {
});
router.post('/groups', authenticate, requireRole('owner'), async (req, res) => {
const { group_name, server_name } = req.body;
const { group_name, server_name, alias } = req.body;
if (!group_name || !server_name) return res.status(400).json({ error: '分组名和子服名为必填' });
await query('INSERT IGNORE INTO server_groups(group_name, server_name) VALUES (?,?)', [group_name, server_name]);
if (alias) {
const dup = await getRow('SELECT id FROM server_groups WHERE alias = ?', [alias]);
if (dup) return res.status(400).json({ error: '该外部别名已被使用' });
}
await query('INSERT IGNORE INTO server_groups(group_name, server_name, alias) VALUES (?,?,?)', [group_name, server_name, alias || '']);
res.status(201).json({ message: '已添加' });
});
router.put('/groups/:id', authenticate, requireRole('owner'), async (req, res) => {
const { group_name, server_name, alias } = req.body;
if (!group_name || !server_name) return res.status(400).json({ error: '分组名和子服名为必填' });
if (alias) {
const dup = await getRow('SELECT id FROM server_groups WHERE alias = ? AND id != ?', [alias, req.params.id]);
if (dup) return res.status(400).json({ error: '该外部别名已被使用' });
}
await query('UPDATE server_groups SET group_name = ?, server_name = ?, alias = ? WHERE id = ?', [group_name, server_name, alias || '', req.params.id]);
res.json({ message: '已更新' });
});
router.delete('/groups/:id', authenticate, requireRole('owner'), async (req, res) => {
await query('DELETE FROM server_groups WHERE id = ?', [req.params.id]);
res.json({ message: '已删除' });