const fs = require("node:fs"); const path = require("node:path"); const { dataDir, defaultUser, defaultSystemConfig } = require("./config.cjs"); const { addYears, nowIso, passwordHash } = require("./security.cjs"); const dbPath = path.join(dataDir, "users.json"); let memoryDb = null; function createInitialDb() { const expiresAt = addYears(new Date(), 100).toISOString(); const user = { id: 1, username: defaultUser.username, email: defaultUser.email, password: passwordHash(defaultUser.password), role: "admin", status: "active", points: 999999, is_super_admin: 1, owner_admin_id: null, invite_code: "LOCALADMIN", 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, }; return { nextUserId: 2, nextSubscriptionId: 2, nextUserLogId: 1, nextGenerationLogId: 1, users: [user], subscriptions: [ { id: 1, user_id: 1, subscription_type: "local", expires_at: expiresAt, trial_started_at: nowIso(), created_at: nowIso(), updated_at: nowIso(), }, ], generationLogs: [], userLogs: [], devices: [], systemConfigs: { ...defaultSystemConfig }, }; } function normalizeSystemConfigs(value) { if (Array.isArray(value)) { return value.reduce((configs, item) => { if (item?.config_key) configs[item.config_key] = String(item.config_value ?? ""); return configs; }, { ...defaultSystemConfig }); } return { ...defaultSystemConfig, ...(value || {}) }; } function normalizeDb(db) { const initial = createInitialDb(); const normalized = { ...initial, ...(db || {}) }; normalized.users = Array.isArray(normalized.users) ? normalized.users : initial.users; normalized.subscriptions = Array.isArray(normalized.subscriptions) ? normalized.subscriptions : initial.subscriptions; normalized.generationLogs = Array.isArray(normalized.generationLogs) ? normalized.generationLogs : []; normalized.userLogs = Array.isArray(normalized.userLogs) ? normalized.userLogs : []; normalized.devices = Array.isArray(normalized.devices) ? normalized.devices : []; normalized.systemConfigs = normalizeSystemConfigs(normalized.systemConfigs); for (const user of normalized.users) { user.role = user.role || "user"; user.status = user.status || "active"; user.points = Number(user.points || 0); user.max_devices = Number(user.max_devices || 1); user.is_super_admin = Number(user.is_super_admin || 0); user.cloud_digital_human_disabled = Number(user.cloud_digital_human_disabled || 0); user.created_at = user.created_at || nowIso(); user.updated_at = user.updated_at || nowIso(); } normalized.nextUserId = Math.max( Number(normalized.nextUserId || 1), ...normalized.users.map(user => Number(user.id || 0) + 1), ); normalized.nextSubscriptionId = Math.max( Number(normalized.nextSubscriptionId || 1), ...normalized.subscriptions.map(item => Number(item.id || 0) + 1), ); normalized.nextUserLogId = Math.max( Number(normalized.nextUserLogId || 1), ...normalized.userLogs.map(item => Number(item.id || 0) + 1), ); normalized.nextGenerationLogId = Math.max( Number(normalized.nextGenerationLogId || 1), ...normalized.generationLogs.map(item => Number(item.id || 0) + 1), ); return normalized; } function ensureDb() { if (memoryDb) return; try { fs.mkdirSync(dataDir, { recursive: true }); if (!fs.existsSync(dbPath)) { fs.writeFileSync(dbPath, JSON.stringify(createInitialDb(), null, 2), "utf8"); } } catch (error) { memoryDb = createInitialDb(); console.warn(`[local-auth] cannot write ${dbPath}; using in-memory database: ${error.message}`); } } function loadDb() { ensureDb(); if (memoryDb) return memoryDb; return normalizeDb(JSON.parse(fs.readFileSync(dbPath, "utf8"))); } function saveDb(db) { const normalized = normalizeDb(db); if (memoryDb) { memoryDb = normalized; return; } fs.writeFileSync(dbPath, JSON.stringify(normalized, null, 2), "utf8"); } function getSubscription(db, userId) { const existing = db.subscriptions.find(item => Number(item.user_id) === Number(userId)); if (existing) return existing; const subscription = { id: db.nextSubscriptionId++, user_id: Number(userId), subscription_type: "local", expires_at: addYears(new Date(), 100).toISOString(), trial_started_at: nowIso(), created_at: nowIso(), updated_at: nowIso(), }; db.subscriptions.push(subscription); return subscription; } function addUserLog(db, userId, action, message, extra = {}) { db.userLogs.push({ id: db.nextUserLogId++, user_id: Number(userId || 0) || null, action, message, device_fingerprint: extra.device_fingerprint || null, ip_address: extra.ip_address || "127.0.0.1", created_at: nowIso(), }); } function addGenerationLog(db, userId, type, detail, extra = {}) { db.generationLogs.push({ id: db.nextGenerationLogId++, user_id: Number(userId || 0) || null, type: type || "unknown", detail: typeof detail === "string" ? detail : JSON.stringify(detail || {}), device_fingerprint: extra.device_fingerprint || null, ip_address: extra.ip_address || "127.0.0.1", created_at: nowIso(), }); } module.exports = { dbPath, ensureDb, loadDb, saveDb, getSubscription, addUserLog, addGenerationLog, };