355 lines
15 KiB
JavaScript
355 lines
15 KiB
JavaScript
const { publicUser, requireAdmin } = require("./auth.cjs");
|
|
const { loadDb, saveDb, getSubscription, addUserLog } = require("./db.cjs");
|
|
const { sendJson } = require("./http-utils.cjs");
|
|
const { nowIso, addHours, passwordHash } = require("./security.cjs");
|
|
const { systemConfigDescriptions } = require("./config.cjs");
|
|
|
|
function isValidSubscription(subscription) {
|
|
return subscription && new Date(subscription.expires_at).getTime() > Date.now();
|
|
}
|
|
|
|
function userRow(db, user) {
|
|
const subscription = getSubscription(db, user.id);
|
|
const owner = db.users.find(item => Number(item.id) === Number(user.owner_admin_id));
|
|
const deviceCount = db.devices.filter(item => Number(item.user_id) === Number(user.id)).length || (user.device_fingerprint ? 1 : 0);
|
|
|
|
return {
|
|
...publicUser(user),
|
|
avatar: user.avatar || null,
|
|
device_fingerprint: user.device_fingerprint || "",
|
|
current_token: null,
|
|
subscription_type: subscription.subscription_type,
|
|
expires_at: subscription.expires_at,
|
|
owner_admin_name: owner?.username || null,
|
|
device_count: deviceCount,
|
|
};
|
|
}
|
|
|
|
function configRows(db) {
|
|
return Object.entries(db.systemConfigs).map(([config_key, config_value], index) => ({
|
|
id: index + 1,
|
|
config_key,
|
|
config_value: String(config_value ?? ""),
|
|
description: systemConfigDescriptions[config_key] || "",
|
|
updated_at: nowIso(),
|
|
updated_by: "local-admin",
|
|
}));
|
|
}
|
|
|
|
function logRows(db, logs) {
|
|
return logs
|
|
.slice()
|
|
.sort((left, right) => new Date(right.created_at) - new Date(left.created_at))
|
|
.map(log => ({
|
|
...log,
|
|
username: db.users.find(user => Number(user.id) === Number(log.user_id))?.username || null,
|
|
}));
|
|
}
|
|
|
|
function generationStats(db, userId) {
|
|
const logs = userId
|
|
? db.generationLogs.filter(log => Number(log.user_id) === Number(userId))
|
|
: db.generationLogs;
|
|
const stats = { script: 0, voice: 0, video: 0, subtitle: 0, total: logs.length };
|
|
for (const log of logs) {
|
|
if (Object.prototype.hasOwnProperty.call(stats, log.type)) stats[log.type] += 1;
|
|
}
|
|
return stats;
|
|
}
|
|
|
|
function updateUserRole(user, role, inviteCode) {
|
|
user.role = role === "admin" ? "admin" : "user";
|
|
user.is_super_admin = user.is_super_admin || 0;
|
|
if (inviteCode !== undefined) user.invite_code = inviteCode || null;
|
|
user.updated_at = nowIso();
|
|
}
|
|
|
|
async function handleAdminApi(req, res, pathname, body, requestUrl) {
|
|
if (!pathname.startsWith("/api/admin")) return false;
|
|
|
|
const db = loadDb();
|
|
const admin = requireAdmin(db, req);
|
|
if (!admin) {
|
|
sendJson(res, 401, { success: false, message: "需要管理员登录" });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "GET" && pathname === "/api/admin/stats") {
|
|
const activeUsers = db.users.filter(user => user.status === "active").length;
|
|
const adminCount = db.users.filter(user => user.role === "admin" || Number(user.is_super_admin) === 1).length;
|
|
const validSubscriptions = db.subscriptions.filter(isValidSubscription).length;
|
|
sendJson(res, 200, {
|
|
success: true,
|
|
stats: {
|
|
total_users: db.users.length,
|
|
active_users: activeUsers,
|
|
admin_count: adminCount,
|
|
valid_subscriptions: validSubscriptions,
|
|
expired_subscriptions: db.subscriptions.length - validSubscriptions,
|
|
total_devices: db.devices.length,
|
|
},
|
|
});
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "GET" && pathname === "/api/admin/users") {
|
|
const search = String(requestUrl.searchParams.get("search") || "").trim().toLowerCase();
|
|
const status = String(requestUrl.searchParams.get("status") || "").trim();
|
|
const subscription = String(requestUrl.searchParams.get("subscription") || "").trim();
|
|
|
|
let users = db.users;
|
|
if (search) {
|
|
users = users.filter(user =>
|
|
String(user.username || "").toLowerCase().includes(search) ||
|
|
String(user.email || "").toLowerCase().includes(search),
|
|
);
|
|
}
|
|
if (status) users = users.filter(user => user.status === status);
|
|
if (subscription === "valid") users = users.filter(user => isValidSubscription(getSubscription(db, user.id)));
|
|
if (subscription === "expired") users = users.filter(user => !isValidSubscription(getSubscription(db, user.id)));
|
|
|
|
saveDb(db);
|
|
sendJson(res, 200, { success: true, users: users.map(user => userRow(db, user)) });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "GET" && pathname === "/api/admin/admins") {
|
|
const admins = db.users
|
|
.filter(user => user.role === "admin" || Number(user.is_super_admin) === 1)
|
|
.map(user => ({
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
invite_code: user.invite_code || null,
|
|
is_super_admin: Number(user.is_super_admin || 0),
|
|
status: user.status,
|
|
owned_user_count: db.users.filter(item => Number(item.owner_admin_id) === Number(user.id)).length,
|
|
}));
|
|
sendJson(res, 200, { success: true, admins });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "POST" && pathname === "/api/admin/add-time") {
|
|
const user = db.users.find(item => Number(item.id) === Number(body.user_id));
|
|
if (!user) {
|
|
sendJson(res, 200, { success: false, message: "用户不存在" });
|
|
return true;
|
|
}
|
|
const subscription = getSubscription(db, user.id);
|
|
const base = new Date(subscription.expires_at).getTime() > Date.now() ? new Date(subscription.expires_at) : new Date();
|
|
subscription.expires_at = addHours(base, Number(body.hours || 0)).toISOString();
|
|
subscription.updated_at = nowIso();
|
|
addUserLog(db, user.id, "add_time", body.reason || `管理员增加 ${body.hours || 0} 小时`);
|
|
saveDb(db);
|
|
sendJson(res, 200, { success: true, subscription });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "POST" && pathname === "/api/admin/user/status") {
|
|
const user = db.users.find(item => Number(item.id) === Number(body.user_id));
|
|
if (!user) {
|
|
sendJson(res, 200, { success: false, message: "用户不存在" });
|
|
return true;
|
|
}
|
|
user.status = body.status === "disabled" ? "disabled" : "active";
|
|
user.updated_at = nowIso();
|
|
addUserLog(db, user.id, "status", `管理员修改状态为 ${user.status}`);
|
|
saveDb(db);
|
|
sendJson(res, 200, { success: true, user: userRow(db, user) });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "POST" && pathname === "/api/admin/device/unbind") {
|
|
const user = db.users.find(item => Number(item.id) === Number(body.user_id));
|
|
if (!user) {
|
|
sendJson(res, 200, { success: false, message: "用户不存在" });
|
|
return true;
|
|
}
|
|
user.device_fingerprint = "";
|
|
user.last_login_device = null;
|
|
db.devices = db.devices.filter(item => Number(item.user_id) !== Number(user.id));
|
|
addUserLog(db, user.id, "device_unbind", "管理员解绑设备");
|
|
saveDb(db);
|
|
sendJson(res, 200, { success: true });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "POST" && pathname === "/api/admin/user/reset-password") {
|
|
const user = db.users.find(item => Number(item.id) === Number(body.user_id));
|
|
if (!user || !body.new_password) {
|
|
sendJson(res, 200, { success: false, message: "用户或新密码无效" });
|
|
return true;
|
|
}
|
|
user.password = passwordHash(body.new_password);
|
|
user.updated_at = nowIso();
|
|
addUserLog(db, user.id, "reset_password", "管理员重置密码");
|
|
saveDb(db);
|
|
sendJson(res, 200, { success: true });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "POST" && pathname === "/api/admin/create-admin") {
|
|
const email = String(body.email || "").trim();
|
|
const username = String(body.username || "").trim();
|
|
const password = String(body.password || "");
|
|
if (!email || !username || !password) {
|
|
sendJson(res, 200, { success: false, message: "管理员账号信息不完整" });
|
|
return true;
|
|
}
|
|
if (db.users.some(user => user.email === email || user.username === username)) {
|
|
sendJson(res, 200, { success: false, message: "账号已存在" });
|
|
return true;
|
|
}
|
|
const user = {
|
|
id: db.nextUserId++,
|
|
username,
|
|
email,
|
|
password: passwordHash(password),
|
|
role: "admin",
|
|
status: "active",
|
|
points: 999999,
|
|
is_super_admin: 0,
|
|
owner_admin_id: admin.id,
|
|
invite_code: body.invite_code || null,
|
|
max_devices: 99,
|
|
device_fingerprint: "",
|
|
created_at: nowIso(),
|
|
updated_at: nowIso(),
|
|
last_login_at: null,
|
|
last_login_device: null,
|
|
current_token: "",
|
|
cloud_digital_human_disabled: 0,
|
|
};
|
|
db.users.push(user);
|
|
getSubscription(db, user.id);
|
|
addUserLog(db, user.id, "create_admin", `由 ${admin.username} 创建管理员`);
|
|
saveDb(db);
|
|
sendJson(res, 200, { success: true, admin: userRow(db, user) });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "POST" && pathname === "/api/admin/points/modify") {
|
|
const user = db.users.find(item => Number(item.id) === Number(body.user_id));
|
|
if (!user) {
|
|
sendJson(res, 200, { success: false, message: "用户不存在" });
|
|
return true;
|
|
}
|
|
user.points = Number(user.points || 0) + Number(body.points_change || 0);
|
|
user.updated_at = nowIso();
|
|
addUserLog(db, user.id, "points_modify", body.reason || `积分变更 ${body.points_change || 0}`);
|
|
saveDb(db);
|
|
sendJson(res, 200, { success: true, points: user.points });
|
|
return true;
|
|
}
|
|
|
|
const userLogMatch = pathname.match(/^\/api\/admin\/logs\/user\/(\d+)$/);
|
|
if (req.method === "GET" && userLogMatch) {
|
|
const logs = db.userLogs.filter(log => Number(log.user_id) === Number(userLogMatch[1]));
|
|
sendJson(res, 200, { success: true, logs: logRows(db, logs) });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "GET" && pathname === "/api/admin/logs/all") {
|
|
const limit = Number(requestUrl.searchParams.get("limit") || 100);
|
|
sendJson(res, 200, { success: true, logs: logRows(db, db.userLogs).slice(0, limit), page: 1, limit });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "POST" && pathname === "/api/admin/user/assign-owner") {
|
|
const user = db.users.find(item => Number(item.id) === Number(body.user_id));
|
|
if (!user) {
|
|
sendJson(res, 200, { success: false, message: "用户不存在" });
|
|
return true;
|
|
}
|
|
user.owner_admin_id = body.owner_admin_id ? Number(body.owner_admin_id) : null;
|
|
user.updated_at = nowIso();
|
|
addUserLog(db, user.id, "assign_owner", `管理员归属变更为 ${user.owner_admin_id || "无"}`);
|
|
saveDb(db);
|
|
sendJson(res, 200, { success: true, user: userRow(db, user) });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "PUT" && pathname === "/api/admin/admin/invite-code") {
|
|
const user = db.users.find(item => Number(item.id) === Number(body.admin_id));
|
|
if (!user) {
|
|
sendJson(res, 200, { success: false, message: "管理员不存在" });
|
|
return true;
|
|
}
|
|
user.invite_code = body.invite_code || null;
|
|
user.updated_at = nowIso();
|
|
addUserLog(db, user.id, "invite_code", "管理员邀请码变更");
|
|
saveDb(db);
|
|
sendJson(res, 200, { success: true, admin: userRow(db, user) });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "POST" && pathname === "/api/admin/user/role") {
|
|
const user = db.users.find(item => Number(item.id) === Number(body.user_id));
|
|
if (!user) {
|
|
sendJson(res, 200, { success: false, message: "用户不存在" });
|
|
return true;
|
|
}
|
|
updateUserRole(user, body.role, body.invite_code);
|
|
addUserLog(db, user.id, "role", `角色变更为 ${user.role}`);
|
|
saveDb(db);
|
|
sendJson(res, 200, { success: true, user: userRow(db, user) });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "GET" && pathname === "/api/admin/generation-stats") {
|
|
sendJson(res, 200, {
|
|
success: true,
|
|
stats: generationStats(db, requestUrl.searchParams.get("userId")),
|
|
});
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "GET" && pathname === "/api/admin/generation-logs") {
|
|
const userId = requestUrl.searchParams.get("userId");
|
|
const limit = Number(requestUrl.searchParams.get("limit") || 100);
|
|
const logs = userId ? db.generationLogs.filter(log => Number(log.user_id) === Number(userId)) : db.generationLogs;
|
|
const rows = logRows(db, logs).slice(0, limit);
|
|
sendJson(res, 200, { success: true, logs: rows, total: logs.length });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "GET" && pathname === "/api/admin/system/config") {
|
|
sendJson(res, 200, { success: true, configs: configRows(db) });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "PUT" && pathname === "/api/admin/system/config") {
|
|
const updates = Array.isArray(body.configs)
|
|
? body.configs.reduce((acc, item) => ({ ...acc, [item.config_key]: item.config_value }), {})
|
|
: body.configs || body;
|
|
for (const [key, value] of Object.entries(updates || {})) {
|
|
if (key in db.systemConfigs) db.systemConfigs[key] = String(value ?? "");
|
|
}
|
|
addUserLog(db, admin.id, "system_config", "管理员更新系统配置");
|
|
saveDb(db);
|
|
sendJson(res, 200, { success: true, configs: configRows(db) });
|
|
return true;
|
|
}
|
|
|
|
if (req.method === "PUT" && pathname === "/api/admin/user/cloud-digital-human") {
|
|
const user = db.users.find(item => Number(item.id) === Number(body.user_id));
|
|
if (!user) {
|
|
sendJson(res, 200, { success: false, message: "用户不存在" });
|
|
return true;
|
|
}
|
|
user.cloud_digital_human_disabled = body.disabled ? 1 : 0;
|
|
user.updated_at = nowIso();
|
|
addUserLog(db, user.id, "cloud_digital_human", `云数字人状态 ${user.cloud_digital_human_disabled ? "禁用" : "启用"}`);
|
|
saveDb(db);
|
|
sendJson(res, 200, { success: true, user: userRow(db, user) });
|
|
return true;
|
|
}
|
|
|
|
sendJson(res, 404, { success: false, message: `Admin endpoint not found: ${req.method} ${pathname}` });
|
|
return true;
|
|
}
|
|
|
|
module.exports = {
|
|
handleAdminApi,
|
|
};
|