Files
MC_Report/backend/db.js
canglan 257cd14051 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)
2026-08-19 19:12:34 +08:00

430 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const mysql = require('mysql2/promise');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const CONFIG_PATH = path.join(__dirname, '..', 'data', 'config.json');
let pool = null;
let _configCache = null;
let _configMtime = 0;
function getConfig() {
try {
const stat = fs.statSync(CONFIG_PATH);
if (_configCache && stat.mtimeMs === _configMtime) return _configCache;
_configCache = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
_configMtime = stat.mtimeMs;
return _configCache;
} catch { return null; }
}
function saveConfig(cfg) {
const dir = path.dirname(CONFIG_PATH);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), 'utf-8');
}
function isInstalled() {
return fs.existsSync(CONFIG_PATH);
}
async function getPool() {
if (!pool) {
const cfg = getConfig();
if (!cfg) throw new Error('SYSTEM_NOT_INSTALLED');
pool = mysql.createPool({
host: cfg.db_host || 'localhost',
port: cfg.db_port || 3306,
user: cfg.db_user,
password: cfg.db_pass,
database: cfg.db_name,
waitForConnections: true,
connectionLimit: 10,
charset: 'utf8mb4',
timezone: '+08:00',
});
await migrateAdditions(pool);
}
return pool;
}
async function query(sql, params = []) {
const p = await getPool();
const [rows, fields] = await p.execute(sql, params);
return rows;
}
async function getRow(sql, params = []) {
const rows = await query(sql, params);
return rows[0] || null;
}
async function initSchema(connection) {
const db = connection || await getPool();
const createIfNotExists = async (tableName, sql) => {
const [rows] = await db.execute(`SELECT COUNT(*) as c FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?`, [tableName]);
if (rows[0].c > 0) return; // Table exists
await db.execute(sql);
};
await createIfNotExists('users', `CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
game_name VARCHAR(50) NOT NULL,
game_uid VARCHAR(50) NOT NULL,
role ENUM('owner','admin','player') NOT NULL DEFAULT 'player',
active TINYINT(1) NOT NULL DEFAULT 0,
email_verified TINYINT(1) NOT NULL DEFAULT 0,
source ENUM('netease','skin') NOT NULL DEFAULT 'netease',
verify_token VARCHAR(255),
verify_expires DATETIME,
reset_token VARCHAR(255),
reset_expires DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('user_identities', `CREATE TABLE user_identities (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
source ENUM('netease','skin') NOT NULL,
game_name VARCHAR(50) NOT NULL,
game_uid VARCHAR(50) NOT NULL DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_source (user_id, source),
INDEX idx_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('tickets', `CREATE TABLE tickets (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
type ENUM('report','suggestion','appeal','result_appeal') NOT NULL,
server_name VARCHAR(100) NOT NULL DEFAULT '',
title VARCHAR(100) NOT NULL,
status ENUM('pending','processing','awaiting_info','appealing','resolved','rejected','closed') NOT NULL DEFAULT 'pending',
priority ENUM('low','medium','high','urgent') NOT NULL DEFAULT 'medium',
reporter_game_name VARCHAR(50) NOT NULL,
reporter_game_uid VARCHAR(50) NOT NULL,
target_game_name VARCHAR(50),
target_game_uid VARCHAR(50),
reason VARCHAR(500),
description TEXT,
assigned_to INT,
claim_note TEXT,
claimed_at DATETIME,
tracking_token VARCHAR(255) UNIQUE,
is_admin_complaint TINYINT(1) NOT NULL DEFAULT 0,
parent_ticket_id INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_type (type),
INDEX idx_status (status),
INDEX idx_user (user_id),
INDEX idx_tracking (tracking_token),
INDEX idx_assigned (assigned_to)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('ticket_transfers', `CREATE TABLE ticket_transfers (
id INT AUTO_INCREMENT PRIMARY KEY,
ticket_id INT NOT NULL,
from_user_id INT NOT NULL,
to_user_id INT NOT NULL,
reason TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_ticket (ticket_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('attachments', `CREATE TABLE attachments (
id INT AUTO_INCREMENT PRIMARY KEY,
ticket_id INT NOT NULL,
original_name VARCHAR(255) NOT NULL,
stored_name VARCHAR(255) NOT NULL,
mime_type VARCHAR(100) NOT NULL,
size INT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_ticket (ticket_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('responses', `CREATE TABLE responses (
id INT AUTO_INCREMENT PRIMARY KEY,
ticket_id INT NOT NULL,
user_id INT,
content TEXT NOT NULL,
is_staff TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_ticket (ticket_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('email_templates', `CREATE TABLE email_templates (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
subject VARCHAR(255) NOT NULL,
body TEXT NOT NULL,
description TEXT,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('notification_configs', `CREATE TABLE notification_configs (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
type ENUM('webhook','email') NOT NULL DEFAULT 'webhook',
webhook_url TEXT,
events VARCHAR(500) NOT NULL DEFAULT 'all',
active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('settings', `CREATE TABLE settings (
k VARCHAR(100) PRIMARY KEY,
v TEXT NOT NULL,
label VARCHAR(255),
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('captchas', `CREATE TABLE captchas (
id VARCHAR(255) PRIMARY KEY,
question VARCHAR(100) NOT NULL,
answer VARCHAR(20) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('polls', `CREATE TABLE polls (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
description TEXT,
options JSON NOT NULL,
group_name VARCHAR(100) NOT NULL DEFAULT '',
server_name VARCHAR(100) NOT NULL DEFAULT '',
start_time DATETIME,
end_time DATETIME,
active TINYINT(1) NOT NULL DEFAULT 1,
created_by INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_active (active),
INDEX idx_group_server (group_name, server_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('poll_votes', `CREATE TABLE poll_votes (
id INT AUTO_INCREMENT PRIMARY KEY,
poll_id INT NOT NULL,
user_id INT NOT NULL,
option_index INT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_poll_user (poll_id, user_id),
INDEX idx_poll (poll_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('feature_items', `CREATE TABLE feature_items (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
description TEXT,
group_name VARCHAR(100) NOT NULL DEFAULT '',
server_name VARCHAR(100) NOT NULL DEFAULT '',
status ENUM('pending','planned','done') NOT NULL DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('feature_votes', `CREATE TABLE feature_votes (
id INT AUTO_INCREMENT PRIMARY KEY,
item_id INT NOT NULL,
user_id INT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_item_user (item_id, user_id),
INDEX idx_item (item_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('feature_comments', `CREATE TABLE feature_comments (
id INT AUTO_INCREMENT PRIMARY KEY,
item_id INT NOT NULL,
user_id INT NOT NULL,
content TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_item (item_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('server_groups', `CREATE TABLE server_groups (
id INT AUTO_INCREMENT PRIMARY KEY,
group_name VARCHAR(100) NOT NULL,
server_name VARCHAR(100) NOT NULL,
alias VARCHAR(100) NOT NULL DEFAULT '',
UNIQUE KEY uk_group_server (group_name, server_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('api_clients', `CREATE TABLE api_clients (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
client_id VARCHAR(64) NOT NULL UNIQUE,
secret_hash VARCHAR(255) NOT NULL,
active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_active (active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('user_servers', `CREATE TABLE user_servers (
user_id INT NOT NULL,
group_name VARCHAR(100) NOT NULL,
server_name VARCHAR(100) NOT NULL,
UNIQUE KEY uk_user_server (user_id, group_name, server_name),
INDEX idx_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('bans', `CREATE TABLE bans (
id INT AUTO_INCREMENT PRIMARY KEY,
player_name VARCHAR(50) NOT NULL,
player_uid VARCHAR(50),
server_name VARCHAR(100) NOT NULL DEFAULT '',
source VARCHAR(10) DEFAULT '',
reason TEXT,
type ENUM('ban','mute','warn','other') NOT NULL DEFAULT 'ban',
duration VARCHAR(20) DEFAULT '',
ticket_id INT,
created_by INT,
status ENUM('active','expired','appealed','lifted') NOT NULL DEFAULT 'active',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
expires_at DATETIME
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('audit_logs', `CREATE TABLE audit_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
username VARCHAR(50),
action VARCHAR(100) NOT NULL,
entity_type VARCHAR(50),
entity_id INT,
details TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('email_logs', `CREATE TABLE email_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
to_email VARCHAR(100) NOT NULL,
template_code VARCHAR(50) NOT NULL DEFAULT '',
subject VARCHAR(255) NOT NULL DEFAULT '',
status ENUM('sent','failed') NOT NULL,
error TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_email (to_email),
INDEX idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await createIfNotExists('system_logs', `CREATE TABLE system_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
level ENUM('info','warn','error') NOT NULL DEFAULT 'info',
source VARCHAR(50) NOT NULL DEFAULT '',
message TEXT NOT NULL,
details TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_level (level),
INDEX idx_source (source),
INDEX idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await seedTemplates(db);
await migrateAdditions(db);
}
async function migrateAdditions(db) {
try { await db.execute("ALTER TABLE tickets ADD COLUMN is_admin_complaint TINYINT(1) NOT NULL DEFAULT 0"); } catch {}
try { await db.execute("ALTER TABLE tickets ADD COLUMN parent_ticket_id INT"); } catch {}
try { await db.execute("ALTER TABLE tickets MODIFY type ENUM('report','suggestion','appeal','result_appeal') NOT NULL"); } catch {}
try { await db.execute("ALTER TABLE tickets MODIFY status ENUM('pending','processing','awaiting_info','appealing','resolved','rejected','closed') NOT NULL"); } catch {}
try { await db.execute("ALTER TABLE users ADD COLUMN source ENUM('netease','skin') NOT NULL DEFAULT 'netease'"); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS user_servers (user_id INT NOT NULL, group_name VARCHAR(100) NOT NULL, server_name VARCHAR(100) NOT NULL, UNIQUE KEY uk_user_server (user_id, group_name, server_name), INDEX idx_user (user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS bans (id INT AUTO_INCREMENT PRIMARY KEY, player_name VARCHAR(50) NOT NULL, player_uid VARCHAR(50), source VARCHAR(10) DEFAULT '', reason TEXT, type ENUM('ban','mute','warn','other') NOT NULL DEFAULT 'ban', duration VARCHAR(20) DEFAULT '', ticket_id INT, created_by INT, status ENUM('active','expired','appealed','lifted') NOT NULL DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute("ALTER TABLE bans ADD COLUMN source VARCHAR(10) DEFAULT ''"); } catch {}
// 多来源身份: 建表 + 从 users 主身份回填(幂等)
try {
await db.execute(`CREATE TABLE IF NOT EXISTS user_identities (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
source ENUM('netease','skin') NOT NULL,
game_name VARCHAR(50) NOT NULL,
game_uid VARCHAR(50) NOT NULL DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_source (user_id, source),
INDEX idx_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await db.execute(`INSERT IGNORE INTO user_identities (user_id, source, game_name, game_uid)
SELECT id, source, game_name, game_uid FROM users WHERE game_name != ''`);
} catch {}
// 日志表(邮件日志 + 系统日志)
try {
await db.execute(`CREATE TABLE IF NOT EXISTS email_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
to_email VARCHAR(100) NOT NULL,
template_code VARCHAR(50) NOT NULL DEFAULT '',
subject VARCHAR(255) NOT NULL DEFAULT '',
status ENUM('sent','failed') NOT NULL,
error TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_email (to_email),
INDEX idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await db.execute(`CREATE TABLE IF NOT EXISTS system_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
level ENUM('info','warn','error') NOT NULL DEFAULT 'info',
source VARCHAR(50) NOT NULL DEFAULT '',
message TEXT NOT NULL,
details TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_level (level),
INDEX idx_source (source),
INDEX idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
} catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS polls (id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, description TEXT, options JSON NOT NULL, group_name VARCHAR(100) NOT NULL DEFAULT '', server_name VARCHAR(100) NOT NULL DEFAULT '', start_time DATETIME, end_time DATETIME, active TINYINT(1) NOT NULL DEFAULT 1, created_by INT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_active (active), INDEX idx_group_server (group_name, server_name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS poll_votes (id INT AUTO_INCREMENT PRIMARY KEY, poll_id INT NOT NULL, user_id INT NOT NULL, option_index INT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_poll_user (poll_id, user_id), INDEX idx_poll (poll_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS feature_items (id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, description TEXT, group_name VARCHAR(100) NOT NULL DEFAULT '', server_name VARCHAR(100) NOT NULL DEFAULT '', status ENUM('pending','planned','done') NOT NULL DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS feature_votes (id INT AUTO_INCREMENT PRIMARY KEY, item_id INT NOT NULL, user_id INT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_item_user (item_id, user_id), INDEX idx_item (item_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS feature_comments (id INT AUTO_INCREMENT PRIMARY KEY, item_id INT NOT NULL, user_id INT NOT NULL, content TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_item (item_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS server_groups (id INT AUTO_INCREMENT PRIMARY KEY, group_name VARCHAR(100) NOT NULL, server_name VARCHAR(100) NOT NULL, UNIQUE KEY uk_group_server (group_name, server_name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute("ALTER TABLE server_groups ADD COLUMN alias VARCHAR(100) NOT NULL DEFAULT ''"); } catch {}
try { await db.execute(`CREATE TABLE IF NOT EXISTS api_clients (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, client_id VARCHAR(64) NOT NULL UNIQUE, secret_hash VARCHAR(255) NOT NULL, active TINYINT(1) NOT NULL DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_active (active)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); } catch {}
try { await db.execute("ALTER TABLE tickets ADD COLUMN server_name VARCHAR(100) NOT NULL DEFAULT ''"); } catch {}
try { await db.execute("ALTER TABLE bans ADD COLUMN server_name VARCHAR(100) NOT NULL DEFAULT ''"); } catch {}
}
async function seedTemplates(db) {
const [rows] = await db.execute('SELECT COUNT(*) as c FROM email_templates');
if (rows[0].c > 0) return;
const temps = [
['verify_email', '邮箱验证', '【{{site_name}}】请验证您的邮箱',
'<p>您好 <strong>{{username}}</strong>(游戏名: {{game_name}}UID: {{game_uid}}</p><p>感谢注册!请点击以下链接验证邮箱:</p><p><a href="{{verify_link}}">{{verify_link}}</a></p><p>链接有效期24小时。</p><p>— {{site_name}}</p>',
'变量: {{site_name}}, {{username}}, {{game_name}}, {{game_uid}}, {{verify_link}}'],
['email_code', '邮箱验证码', '【{{site_name}}】您的邮箱验证码',
'<p>您好,</p><p>您的邮箱验证码为:</p><p style="font-size:24px;font-weight:bold;letter-spacing:4px">{{code}}</p><p>验证码10分钟内有效。如非本人操作请忽略此邮件。</p><p>— {{site_name}}</p>',
'变量: {{site_name}}, {{code}}'],
['ticket_created', '工单创建通知', '【{{site_name}}】{{ticket_type}}已提交 #{{ticket_id}}',
'<p><strong>{{reporter_game_name}}</strong>UID: {{reporter_game_uid}}),您的{{ticket_type}}已提交。</p><p>工单编号: <strong>#{{ticket_id}}</strong></p><p>标题: {{ticket_title}}</p><p><a href="{{tracking_link}}">查看工单</a></p><p>— {{site_name}}</p>',
'变量: {{site_name}}, {{reporter_game_name}}, {{ticket_type}}, {{ticket_id}}, {{ticket_title}}, {{tracking_link}}'],
['ticket_updated', '工单状态更新', '【{{site_name}}】工单 #{{ticket_id}} 状态更新',
'<p>工单 <strong>#{{ticket_id}}</strong> 状态已更新为 <strong>{{ticket_status}}</strong>。</p><p>标题: {{ticket_title}}</p><p><a href="{{tracking_link}}">查看详情</a></p><p>— {{site_name}}</p>',
'变量: {{site_name}}, {{ticket_id}}, {{ticket_title}}, {{ticket_status}}, {{tracking_link}}'],
['ticket_claimed', '工单被认领', '【{{site_name}}】工单 #{{ticket_id}} 已被认领',
'<p>工单 <strong>#{{ticket_id}}</strong>{{ticket_title}})已被 <strong>{{assigned_to}}</strong> 认领。</p><p>— {{site_name}}</p>',
'变量: {{site_name}}, {{ticket_id}}, {{ticket_title}}, {{assigned_to}}'],
['ticket_transferred', '工单已转交', '【{{site_name}}】工单 #{{ticket_id}} 已转交',
'<p>工单 <strong>#{{ticket_id}}</strong>{{ticket_title}})已由 {{from_user}} 转交给 {{to_user}}。</p><p>— {{site_name}}</p>',
'变量: {{site_name}}, {{ticket_id}}, {{ticket_title}}, {{from_user}}, {{to_user}}'],
['reset_password', '密码重置', '【{{site_name}}】密码重置请求',
'<p>您好 <strong>{{username}}</strong></p><p>我们收到了您的密码重置请求。请点击以下链接设置新密码:</p><p><a href="{{reset_link}}">{{reset_link}}</a></p><p>链接1小时内有效。如非本人操作请忽略此邮件。</p><p>— {{site_name}}</p>',
'变量: {{site_name}}, {{username}}, {{reset_link}}'],
];
for (const t of temps) {
await db.execute('INSERT INTO email_templates(code, name, subject, body, description) VALUES (?,?,?,?,?)', t);
}
}
module.exports = { getPool, query, getRow, initSchema, isInstalled, getConfig, saveConfig, CONFIG_PATH };