Files
WYF-koubo/server/local-auth/admin-api.cjs
T
2026-06-20 10:07:44 +08:00

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