sync: add source field to user list, create, edit, and external API
This commit is contained in:
@@ -50,7 +50,7 @@ router.post('/auth/login', async (req, res) => {
|
|||||||
if (!user || !(await bcrypt.compare(password, user.password))) return res.status(401).json({ error: '用户名或密码错误' });
|
if (!user || !(await bcrypt.compare(password, user.password))) return res.status(401).json({ error: '用户名或密码错误' });
|
||||||
if (!user.active) return res.status(403).json({ error: '账号未激活' });
|
if (!user.active) return res.status(403).json({ error: '账号未激活' });
|
||||||
const token = generateToken(user);
|
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, source:user.source } });
|
||||||
} catch (e) { res.status(500).json({ error: '服务器错误' }); }
|
} catch (e) { res.status(500).json({ error: '服务器错误' }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,23 +6,23 @@ const { authenticate, requireRole } = require('../middleware/auth');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get('/', authenticate, requireRole('owner','admin'), async (req, res) => {
|
router.get('/', authenticate, requireRole('owner','admin'), async (req, res) => {
|
||||||
res.json(await query('SELECT id, username, email, game_name, game_uid, role, active, email_verified, created_at FROM users ORDER BY created_at DESC'));
|
res.json(await query('SELECT id, username, email, game_name, game_uid, source, role, active, email_verified, created_at FROM users ORDER BY created_at DESC'));
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/:id', authenticate, requireRole('owner','admin'), async (req, res) => {
|
router.get('/:id', authenticate, requireRole('owner','admin'), async (req, res) => {
|
||||||
const u = await getRow('SELECT id, username, email, game_name, game_uid, role, active, email_verified, created_at FROM users WHERE id = ?', [req.params.id]);
|
const u = await getRow('SELECT id, username, email, game_name, game_uid, source, role, active, email_verified, created_at FROM users WHERE id = ?', [req.params.id]);
|
||||||
if (!u) return res.status(404).json({ error: '用户不存在' });
|
if (!u) return res.status(404).json({ error: '用户不存在' });
|
||||||
res.json(u);
|
res.json(u);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/', authenticate, requireRole('owner','admin'), async (req, res) => {
|
router.post('/', authenticate, requireRole('owner','admin'), async (req, res) => {
|
||||||
const { username, password, email, game_name, game_uid, role } = req.body;
|
const { username, password, email, game_name, game_uid, role, source } = req.body;
|
||||||
if (!username||!password||!email||!game_name||!game_uid||!role) return res.status(400).json({ error: '所有字段必填' });
|
if (!username||!password||!email||!game_name||!game_uid||!role) return res.status(400).json({ error: '所有字段必填' });
|
||||||
if (!['owner','admin','player'].includes(role)) return res.status(400).json({ error: '无效角色' });
|
if (!['owner','admin','player'].includes(role)) return res.status(400).json({ error: '无效角色' });
|
||||||
if (password.length < 6) return res.status(400).json({ error: '密码至少6位' });
|
if (password.length < 6) return res.status(400).json({ error: '密码至少6位' });
|
||||||
if (await getRow('SELECT id FROM users WHERE username = ?', [username])) return res.status(400).json({ error: '用户名已存在' });
|
if (await getRow('SELECT id FROM users WHERE username = ?', [username])) return res.status(400).json({ error: '用户名已存在' });
|
||||||
const hashed = bcrypt.hashSync(password, 10);
|
const hashed = bcrypt.hashSync(password, 10);
|
||||||
const r = await query('INSERT INTO users(username, password, email, game_name, game_uid, role, active, email_verified) VALUES (?,?,?,?,?,?,1,1)', [username, hashed, email, game_name, game_uid, role]);
|
const r = await query('INSERT INTO users(username, password, email, game_name, game_uid, role, source, active, email_verified) VALUES (?,?,?,?,?,?,?,1,1)', [username, hashed, email, game_name, game_uid, role, source || 'netease']);
|
||||||
await query("INSERT INTO audit_logs(user_id, username, action, entity_type, entity_id, details) VALUES (?,?,?,?,?,?)", [req.user.id, req.user.username, 'create_user', 'user', r.insertId, `创建用户 ${username}`]);
|
await query("INSERT INTO audit_logs(user_id, username, action, entity_type, entity_id, details) VALUES (?,?,?,?,?,?)", [req.user.id, req.user.username, 'create_user', 'user', r.insertId, `创建用户 ${username}`]);
|
||||||
res.status(201).json({ id: r.insertId, message: '创建成功' });
|
res.status(201).json({ id: r.insertId, message: '创建成功' });
|
||||||
});
|
});
|
||||||
@@ -35,6 +35,7 @@ router.put('/:id', authenticate, requireRole('owner','admin'), async (req, res)
|
|||||||
if (req.body.email) fields.email = req.body.email;
|
if (req.body.email) fields.email = req.body.email;
|
||||||
if (req.body.game_name) fields.game_name = req.body.game_name;
|
if (req.body.game_name) fields.game_name = req.body.game_name;
|
||||||
if (req.body.game_uid) fields.game_uid = req.body.game_uid;
|
if (req.body.game_uid) fields.game_uid = req.body.game_uid;
|
||||||
|
if (req.body.source) { if (!['netease','skin'].includes(req.body.source)) return res.status(400).json({ error: '无效来源' }); fields.source = req.body.source; }
|
||||||
if (req.body.role) { if (!['owner','admin','player'].includes(req.body.role)) return res.status(400).json({ error: '无效角色' }); fields.role = req.body.role; }
|
if (req.body.role) { if (!['owner','admin','player'].includes(req.body.role)) return res.status(400).json({ error: '无效角色' }); fields.role = req.body.role; }
|
||||||
if (req.body.active !== undefined) fields.active = req.body.active ? 1 : 0;
|
if (req.body.active !== undefined) fields.active = req.body.active ? 1 : 0;
|
||||||
if (req.body.password) { if (req.body.password.length < 6) return res.status(400).json({ error: '密码至少6位' }); fields.password = bcrypt.hashSync(req.body.password, 10); }
|
if (req.body.password) { if (req.body.password.length < 6) return res.status(400).json({ error: '密码至少6位' }); fields.password = bcrypt.hashSync(req.body.password, 10); }
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const UsersPage = {
|
|||||||
|
|
||||||
async mount() {
|
async mount() {
|
||||||
document.getElementById('page-title').textContent = '用户管理';
|
document.getElementById('page-title').textContent = '用户管理';
|
||||||
document.getElementById('page-actions').innerHTML = '<button class="btn btn-p btn-sm" onclick="UsersPage.createUser()"><i class="fas fa-user-plus"></i> 添加用户</button>';
|
document.getElementById('page-actions').innerHTML = '<button class="btn btn-p btn-sm" data-action="UsersPage:createUser"><i class="fas fa-user-plus"></i> 添加用户</button>';
|
||||||
await this.load();
|
await this.load();
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -11,17 +11,19 @@ const UsersPage = {
|
|||||||
const ct = document.getElementById('page-content');
|
const ct = document.getElementById('page-content');
|
||||||
try {
|
try {
|
||||||
const users = await API.get('/users');
|
const users = await API.get('/users');
|
||||||
|
const srcLabel = { netease:'网易', skin:'皮肤站' };
|
||||||
ct.innerHTML = `
|
ct.innerHTML = `
|
||||||
<div class="card table-wrap"><table><thead><tr>
|
<div class="card table-wrap"><table><thead><tr>
|
||||||
<th>#</th><th>用户名</th><th>邮箱</th><th>游戏名</th><th>游戏UID</th><th>角色</th><th>状态</th><th>时间</th><th>操作</th>
|
<th>#</th><th>用户名</th><th>邮箱</th><th>游戏名</th><th>游戏UID</th><th>来源</th><th>角色</th><th>状态</th><th>时间</th><th>操作</th>
|
||||||
</tr></thead><tbody>${users.map(u => `
|
</tr></thead><tbody>${users.map(u => `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${u.id}</td><td>${U.esc(u.username)}</td><td>${U.esc(u.email)}</td>
|
<td>${u.id}</td><td>${U.esc(u.username)}</td><td>${U.esc(u.email)}</td>
|
||||||
<td>${U.esc(u.game_name)}</td><td>${U.esc(u.game_uid)}</td>
|
<td>${U.esc(u.game_name)}</td><td>${U.esc(u.game_uid)}</td>
|
||||||
|
<td>${srcLabel[u.source] || u.source || '网易'}</td>
|
||||||
<td>${U.badge(u.role,'role')}</td>
|
<td>${U.badge(u.role,'role')}</td>
|
||||||
<td>${u.active ? '<span class="badge bg-resolved">启用</span>' : '<span class="badge bg-rejected">禁用</span>'}</td>
|
<td>${u.active ? '<span class="badge bg-resolved">启用</span>' : '<span class="badge bg-rejected">禁用</span>'}</td>
|
||||||
<td class="ts">${U.date(u.created_at)}</td>
|
<td class="ts">${U.date(u.created_at)}</td>
|
||||||
<td><button class="btn btn-o btn-sm" onclick="UsersPage.editUser(${u.id}, '${U.escJs(u.username)}', '${U.escJs(u.email)}', '${U.escJs(u.game_name)}', '${U.escJs(u.game_uid)}', '${u.role}', ${u.active})"><i class="fas fa-edit"></i></button></td>
|
<td><button class="btn btn-o btn-sm" data-action="UsersPage:editUser:${u.id}:${U.escJs(u.username)}:${U.escJs(u.email)}:${U.escJs(u.game_name)}:${U.escJs(u.game_uid)}:${u.role}:${u.active}:${u.source||'netease'}"><i class="fas fa-edit"></i></button></td>
|
||||||
</tr>`).join('')}</tbody></table></div>
|
</tr>`).join('')}</tbody></table></div>
|
||||||
`;
|
`;
|
||||||
} catch (e) { ct.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
|
} catch (e) { ct.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
|
||||||
@@ -31,54 +33,46 @@ const UsersPage = {
|
|||||||
U.modal('添加用户', `
|
U.modal('添加用户', `
|
||||||
<form id="cu-form">
|
<form id="cu-form">
|
||||||
<div class="grid-2"><div class="form-group"><label>用户名 *</label><input id="cu-u" required></div><div class="form-group"><label>密码 *</label><input type="password" id="cu-p" required minlength="6"></div></div>
|
<div class="grid-2"><div class="form-group"><label>用户名 *</label><input id="cu-u" required></div><div class="form-group"><label>密码 *</label><input type="password" id="cu-p" required minlength="6"></div></div>
|
||||||
<div class="grid-2"><div class="form-group"><label>邮箱 *</label><input type="email" id="cu-e" required></div><div class="form-group"><label>角色 *</label><select id="cu-r"><option value="owner">服主</option><option value="admin">管理员</option><option value="player">玩家</option></select></div></div>
|
<div class="grid-2"><div class="form-group"><label>邮箱 *</label><input type="email" id="cu-e" required></div><div class="form-group"><label>来源</label><select id="cu-s"><option value="netease">网易端</option><option value="skin">皮肤站</option></select></div></div>
|
||||||
|
<div class="grid-2"><div class="form-group"><label>角色 *</label><select id="cu-r"><option value="owner">服主</option><option value="admin">管理员</option><option value="player">玩家</option></select></div></div>
|
||||||
<div class="grid-2"><div class="form-group"><label>游戏名 *</label><input id="cu-gn" required></div><div class="form-group"><label>游戏UID *</label><input id="cu-gu" required></div></div>
|
<div class="grid-2"><div class="form-group"><label>游戏名 *</label><input id="cu-gn" required></div><div class="form-group"><label>游戏UID *</label><input id="cu-gu" required></div></div>
|
||||||
<div class="modal-f"><button type="button" class="btn btn-o" onclick="App.closeModal()">取消</button><button type="submit" class="btn btn-p">创建</button></div>
|
<div class="modal-f"><button type="button" class="btn btn-o" data-action="App:closeModal">取消</button><button type="submit" class="btn btn-p">创建</button></div>
|
||||||
</form>
|
</form>
|
||||||
`);
|
`);
|
||||||
document.getElementById('cu-form').onsubmit = async e => {
|
document.getElementById('cu-form').onsubmit = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
await API.post('/users', {
|
await API.post('/users', {
|
||||||
username: document.getElementById('cu-u').value,
|
username: document.getElementById('cu-u').value, password: document.getElementById('cu-p').value,
|
||||||
password: document.getElementById('cu-p').value,
|
email: document.getElementById('cu-e').value, role: document.getElementById('cu-r').value,
|
||||||
email: document.getElementById('cu-e').value,
|
game_name: document.getElementById('cu-gn').value, game_uid: document.getElementById('cu-gu').value,
|
||||||
role: document.getElementById('cu-r').value,
|
source: document.getElementById('cu-s').value,
|
||||||
game_name: document.getElementById('cu-gn').value,
|
|
||||||
game_uid: document.getElementById('cu-gu').value,
|
|
||||||
});
|
});
|
||||||
App.closeModal();
|
App.closeModal(); this.load();
|
||||||
this.load();
|
|
||||||
} catch (ex) { alert(ex.message); }
|
} catch (ex) { alert(ex.message); }
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
editUser(id, username, email, gameName, gameUid, role, active) {
|
editUser(id, username, email, gameName, gameUid, role, active, source) {
|
||||||
U.modal('编辑用户', `
|
U.modal('编辑用户', `
|
||||||
<form id="eu-form">
|
<form id="eu-form">
|
||||||
<div class="form-group"><label>用户名</label><input value="${username}" disabled style="background:var(--g100)"></div>
|
<div class="form-group"><label>用户名</label><input value="${username}" disabled style="background:var(--g100)"></div>
|
||||||
<div class="grid-2"><div class="form-group"><label>邮箱</label><input type="email" id="eu-e" value="${email}"></div><div class="form-group"><label>角色</label><select id="eu-r">${['owner','admin','player'].map(r=>`<option value="${r}" ${r===role?'selected':''}>${U.ROLE[r]}</option>`).join('')}</select></div></div>
|
<div class="grid-2"><div class="form-group"><label>邮箱</label><input type="email" id="eu-e" value="${email}"></div><div class="form-group"><label>来源</label><select id="eu-s"><option value="netease" ${source==='netease'?'selected':''}>网易端</option><option value="skin" ${source==='skin'?'selected':''}>皮肤站</option></select></div></div>
|
||||||
|
<div class="grid-2"><div class="form-group"><label>角色</label><select id="eu-r">${['owner','admin','player'].map(r=>`<option value="${r}" ${r===role?'selected':''}>${U.ROLE[r]}</option>`).join('')}</select></div></div>
|
||||||
<div class="grid-2"><div class="form-group"><label>游戏名</label><input id="eu-gn" value="${gameName}"></div><div class="form-group"><label>游戏UID</label><input id="eu-gu" value="${gameUid}"></div></div>
|
<div class="grid-2"><div class="form-group"><label>游戏名</label><input id="eu-gn" value="${gameName}"></div><div class="form-group"><label>游戏UID</label><input id="eu-gu" value="${gameUid}"></div></div>
|
||||||
<div class="grid-2"><div class="form-group"><label>新密码(留空不变)</label><input type="password" id="eu-p"></div><div class="form-group"><label>状态</label><select id="eu-a"><option value="1" ${active?'selected':''}>启用</option><option value="0" ${!active?'selected':''}>禁用</option></select></div></div>
|
<div class="grid-2"><div class="form-group"><label>新密码(留空不变)</label><input type="password" id="eu-p"></div><div class="form-group"><label>状态</label><select id="eu-a"><option value="1" ${active?'selected':''}>启用</option><option value="0" ${!active?'selected':''}>禁用</option></select></div></div>
|
||||||
<div class="modal-f"><button type="button" class="btn btn-o" onclick="App.closeModal()">取消</button><button type="submit" class="btn btn-p">保存</button></div>
|
<div class="modal-f"><button type="button" class="btn btn-o" data-action="App:closeModal">取消</button><button type="submit" class="btn btn-p">保存</button></div>
|
||||||
</form>
|
</form>
|
||||||
`);
|
`);
|
||||||
document.getElementById('eu-form').onsubmit = async e => {
|
document.getElementById('eu-form').onsubmit = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const data = {
|
const data = {
|
||||||
email: document.getElementById('eu-e').value,
|
email: document.getElementById('eu-e').value, role: document.getElementById('eu-r').value,
|
||||||
role: document.getElementById('eu-r').value,
|
game_name: document.getElementById('eu-gn').value, game_uid: document.getElementById('eu-gu').value,
|
||||||
game_name: document.getElementById('eu-gn').value,
|
active: document.getElementById('eu-a').value === '1', source: document.getElementById('eu-s').value,
|
||||||
game_uid: document.getElementById('eu-gu').value,
|
|
||||||
active: document.getElementById('eu-a').value === '1',
|
|
||||||
};
|
};
|
||||||
const pw = document.getElementById('eu-p').value;
|
const pw = document.getElementById('eu-p').value; if (pw) data.password = pw;
|
||||||
if (pw) data.password = pw;
|
try { await API.put(`/users/${id}`, data); App.closeModal(); this.load(); } catch (ex) { alert(ex.message); }
|
||||||
try {
|
|
||||||
await API.put(`/users/${id}`, data);
|
|
||||||
App.closeModal();
|
|
||||||
this.load();
|
|
||||||
} catch (ex) { alert(ex.message); }
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user