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, };