refactor: English subcommands, plugin login/track redesign, clean external API routes
This commit is contained in:
@@ -2,13 +2,11 @@ const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { v4: uuid } = require('uuid');
|
||||
const { query, getRow, getConfig } = require('../db');
|
||||
const { generateToken } = require('../middleware/auth');
|
||||
const { generateToken, authenticate } = require('../middleware/auth');
|
||||
const { sendEmail } = require('../mailer');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { getConfig } = require('../db');
|
||||
|
||||
function getExternalKey() {
|
||||
try { return getConfig()?.external_api_key || process.env.EXTERNAL_API_KEY || ''; } catch { return process.env.EXTERNAL_API_KEY || ''; }
|
||||
}
|
||||
@@ -20,6 +18,7 @@ function externalAuth(req, res, next) {
|
||||
return res.status(401).json({ error: '未授权' });
|
||||
}
|
||||
|
||||
// ---- Auth (no external key needed) ----
|
||||
router.post('/auth/register', async (req, res) => {
|
||||
try {
|
||||
const { username, password, email, game_name, game_uid } = req.body;
|
||||
@@ -29,14 +28,10 @@ router.post('/auth/register', async (req, res) => {
|
||||
if (await getRow('SELECT id FROM users WHERE email = ?', [email])) return res.status(400).json({ error: '邮箱已注册' });
|
||||
|
||||
const hashed = bcrypt.hashSync(password, 10);
|
||||
const verifyToken = uuid();
|
||||
await query('INSERT INTO users(username,password,email,game_name,game_uid,verify_token,verify_expires) VALUES (?,?,?,?,?,?,DATE_ADD(NOW(), INTERVAL 24 HOUR))', [username, hashed, email, game_name, game_uid, verifyToken]);
|
||||
await query('INSERT INTO users(username,password,email,game_name,game_uid,verify_token,verify_expires) VALUES (?,?,?,?,?,?,DATE_ADD(NOW(), INTERVAL 24 HOUR))', [username, hashed, email, game_name, game_uid, uuid()]);
|
||||
|
||||
const site = await getRow("SELECT v FROM settings WHERE k='site_url'");
|
||||
const sent = await sendEmail(email, 'verify_email', {
|
||||
username, game_name, game_uid,
|
||||
verify_link: `${site?.v||'http://localhost:3100'}#/verify?token=${verifyToken}`,
|
||||
});
|
||||
const sent = await sendEmail(email, 'verify_email', { username, game_name, game_uid, verify_link: `${site?.v||'http://localhost:3100'}#/verify?token=${uuid()}` });
|
||||
|
||||
if (!sent) {
|
||||
await query('UPDATE users SET email_verified=1,active=1,verify_token=NULL WHERE username=?', [username]);
|
||||
@@ -54,78 +49,66 @@ router.post('/auth/login', async (req, res) => {
|
||||
if (!user || !bcrypt.compareSync(password, user.password)) return res.status(401).json({ error: '用户名或密码错误' });
|
||||
if (!user.active) return res.status(403).json({ error: '账号未激活' });
|
||||
const token = generateToken(user);
|
||||
res.json({ token, user: { id: user.id, username: user.username, game_name: user.game_name, game_uid: user.game_uid, role: user.role } });
|
||||
res.json({ token, user: { id:user.id, username:user.username, game_name:user.game_name, game_uid:user.game_uid, role:user.role } });
|
||||
} catch (e) { res.status(500).json({ error: '服务器错误' }); }
|
||||
});
|
||||
|
||||
// ---- Plugin API (JWT auth) ----
|
||||
router.use(externalAuth);
|
||||
|
||||
router.get('/my-tickets', authenticate, async (req, res) => {
|
||||
const { limit } = req.query;
|
||||
const rows = await query(`SELECT id, type, title, status, priority, created_at, updated_at FROM tickets WHERE user_id = ? ORDER BY updated_at DESC LIMIT ?`, [req.user.id, Math.min(parseInt(limit)||10, 50)]);
|
||||
res.json({ list: rows });
|
||||
});
|
||||
|
||||
router.get('/my-tickets/:id', authenticate, async (req, res) => {
|
||||
const t = await getRow(`SELECT id, type, title, status, priority, reporter_game_name, reporter_game_uid,
|
||||
target_game_name, target_game_uid, reason, description, assigned_to, claim_note, created_at, updated_at
|
||||
FROM tickets WHERE id = ? AND user_id = ?`, [req.params.id, req.user.id]);
|
||||
if (!t) return res.status(404).json({ error: '工单不存在' });
|
||||
res.json(t);
|
||||
});
|
||||
|
||||
router.post('/tickets', async (req, res) => {
|
||||
const { type, title, reporter_game_name, reporter_game_uid,
|
||||
target_game_name, target_game_uid, reason, description } = req.body;
|
||||
|
||||
const { type, title, reporter_game_name, reporter_game_uid, target_game_name, target_game_uid, reason, description, is_admin_complaint } = req.body;
|
||||
if (!type || !['report','suggestion','appeal'].includes(type)) return res.status(400).json({ error: '类型不正确' });
|
||||
if (!title) return res.status(400).json({ error: '标题不能为空' });
|
||||
if (!reporter_game_name || !reporter_game_uid) return res.status(400).json({ error: '请填写游戏名称和UID' });
|
||||
|
||||
if (type === 'report') {
|
||||
if (!reason) return res.status(400).json({ error: '请填写举报原因' });
|
||||
}
|
||||
if (type === 'suggestion' && !description) return res.status(400).json({ error: '建议内容不能为空' });
|
||||
if (type === 'appeal') {
|
||||
if (!reason) return res.status(400).json({ error: '请填写申诉理由' });
|
||||
if (!description) return res.status(400).json({ error: '请填写详细申诉内容' });
|
||||
}
|
||||
if (!title || !reporter_game_name || !reporter_game_uid) return res.status(400).json({ error: '必填字段不完整' });
|
||||
|
||||
const countRow = await getRow("SELECT COUNT(*) as c FROM tickets WHERE (reporter_game_name = ? OR reporter_game_uid = ?) AND status IN ('pending','processing','awaiting_info','appealing')", [reporter_game_name, reporter_game_uid]);
|
||||
if (countRow.c >= 5) return res.status(400).json({ error: '待处理工单已达上限' });
|
||||
|
||||
const trackingToken = require('uuid').v4();
|
||||
const [r] = await query(`INSERT INTO tickets(type,title,reporter_game_name,reporter_game_uid,
|
||||
target_game_name,target_game_uid,reason,description,tracking_token) VALUES (?,?,?,?,?,?,?,?,?)`,
|
||||
[type, title, reporter_game_name, reporter_game_uid,
|
||||
(type==='report')?(target_game_name||''):null,
|
||||
(type==='report')?(target_game_uid||''):null,
|
||||
(type==='report'||type==='appeal')?reason:null,
|
||||
(type==='suggestion'||type==='appeal')?description:'',
|
||||
trackingToken]);
|
||||
|
||||
await query("INSERT INTO responses(ticket_id, content, is_staff) VALUES (?,?,0)", [r.insertId, `游戏内${type==='report'?'举报':type==='appeal'?'申诉':'建议'}\n提交人: ${reporter_game_name} (UID: ${reporter_game_uid})\n${reason||description||''}`]);
|
||||
|
||||
const trackingToken = uuid();
|
||||
const [r] = await query(`INSERT INTO tickets(type,title,reporter_game_name,reporter_game_uid,target_game_name,target_game_uid,reason,description,tracking_token,is_admin_complaint) VALUES (?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
[type, title, reporter_game_name, reporter_game_uid, target_game_name||'', target_game_uid||'', reason||'', description||'', trackingToken, is_admin_complaint?1:0]);
|
||||
await query("INSERT INTO responses(ticket_id,content,is_staff) VALUES (?,?,0)", [r.insertId, `游戏内提交\n类型: ${type}\n提交人: ${reporter_game_name} (UID: ${reporter_game_uid})`]);
|
||||
res.status(201).json({ id: r.insertId, tracking_token: trackingToken });
|
||||
});
|
||||
|
||||
router.get('/tickets', async (req, res) => {
|
||||
// ---- Java Server API (x-external-key only) ----
|
||||
router.get('/all-tickets', async (req, res) => {
|
||||
const { type, status, page, limit } = req.query;
|
||||
let q = `SELECT t.id, t.type, t.title, t.status, t.priority,
|
||||
t.reporter_game_name, t.reporter_game_uid,
|
||||
t.target_game_name, t.target_game_uid,
|
||||
t.reason, t.description, t.assigned_to, t.claim_note,
|
||||
t.created_at, t.updated_at
|
||||
FROM tickets t WHERE 1=1`;
|
||||
let q = `SELECT id, type, title, status, priority, reporter_game_name, reporter_game_uid, target_game_name, target_game_uid, reason, description, assigned_to, claim_note, created_at, updated_at FROM tickets WHERE 1=1`;
|
||||
const p = [];
|
||||
if (type) { q += ' AND t.type = ?'; p.push(type); }
|
||||
if (status) { q += ' AND t.status = ?'; p.push(status); }
|
||||
if (type) { q += ' AND type = ?'; p.push(type); }
|
||||
if (status) { q += ' AND status = ?'; p.push(status); }
|
||||
const pg = parseInt(page) || 1;
|
||||
const lm = Math.min(parseInt(limit) || 50, 200);
|
||||
q += ` ORDER BY t.updated_at DESC LIMIT ${(pg-1)*lm}, ${lm}`;
|
||||
q += ` ORDER BY updated_at DESC LIMIT ${(pg-1)*lm}, ${lm}`;
|
||||
res.json(await query(q, p));
|
||||
});
|
||||
|
||||
router.get('/tickets/:id', async (req, res) => {
|
||||
const t = await getRow(`SELECT id, type, title, status, priority,
|
||||
reporter_game_name, reporter_game_uid, target_game_name, target_game_uid,
|
||||
reason, description, assigned_to, claim_note,
|
||||
created_at, updated_at FROM tickets WHERE id = ?`, [req.params.id]);
|
||||
if (!t) return res.status(404).json({ error: '工单不存在' });
|
||||
router.get('/all-tickets/:id', async (req, res) => {
|
||||
const t = await getRow('SELECT * FROM tickets WHERE id = ?', [req.params.id]);
|
||||
if (!t) return res.status(404).json({ error: 'Not found' });
|
||||
t.responses = await query('SELECT content, is_staff, created_at FROM responses WHERE ticket_id = ? ORDER BY created_at ASC', [req.params.id]);
|
||||
t.attachments = await query('SELECT id, original_name, mime_type, size FROM attachments WHERE ticket_id = ?', [req.params.id]);
|
||||
res.json(t);
|
||||
});
|
||||
|
||||
router.get('/reported-players', async (req, res) => {
|
||||
const rows = await query(`SELECT target_game_name, target_game_uid,
|
||||
COUNT(*) as total, SUM(CASE WHEN status IN ('pending','processing','awaiting_info','appealing') THEN 1 ELSE 0 END) as active,
|
||||
const rows = await query(`SELECT target_game_name, target_game_uid, COUNT(*) as total,
|
||||
SUM(CASE WHEN status IN ('pending','processing','awaiting_info','appealing') THEN 1 ELSE 0 END) as active,
|
||||
SUM(CASE WHEN status='resolved' THEN 1 ELSE 0 END) as resolved,
|
||||
MAX(created_at) as last_report
|
||||
FROM tickets WHERE type='report' AND target_game_name IS NOT NULL AND target_game_name != ''
|
||||
@@ -142,14 +125,4 @@ router.get('/stats', async (req, res) => {
|
||||
res.json({ total, byType, byStatus });
|
||||
});
|
||||
|
||||
router.get('/users', async (req, res) => {
|
||||
res.json(await query('SELECT id, username, email, game_name, game_uid, role, active, created_at FROM users ORDER BY id'));
|
||||
});
|
||||
|
||||
router.get('/user/:id', async (req, res) => {
|
||||
const u = await getRow('SELECT id, username, email, game_name, game_uid, role, active, created_at FROM users WHERE id = ?', [req.params.id]);
|
||||
if (!u) return res.status(404).json({ error: '用户不存在' });
|
||||
res.json(u);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user