const bcrypt = require('bcryptjs'); const database = require('../config/database'); const { getRedisConfig } = require('./redisConfig'); const DEFAULT_ADMIN_PASSWORD = 'admin123'; const SETUP_COMPLETED_KEY = 'setup_completed'; const SETUP_COMPLETED_AT_KEY = 'setup_completed_at'; function normalizeBoolean(value) { return value === true || value === 1 || value === '1' || value === 'true'; } async function getSystemConfigMap(keys) { const rows = await database.local.query( `SELECT config_key, config_value FROM system_config WHERE config_key IN (${keys.map(() => '?').join(', ')})`, keys ); return rows.reduce((acc, row) => { acc[row.config_key] = row.config_value; return acc; }, {}); } async function upsertSystemConfig(key, value, type = 'string') { const existing = await database.local.query( 'SELECT id FROM system_config WHERE config_key = ? LIMIT 1', [key] ); if (existing.length > 0) { await database.local.query( 'UPDATE system_config SET config_value = ?, config_type = ? WHERE config_key = ?', [value, type, key] ); return; } await database.local.insert('system_config', { config_key: key, config_value: value, config_type: type, }); } async function isBusinessDatabaseConfigured() { const rows = await database.local.query( `SELECT host, database_name, username, status FROM database_configs WHERE is_default = 1 ORDER BY id ASC LIMIT 1` ); const row = rows[0]; return Boolean( row && row.status === 'active' && row.host && row.database_name && row.username ); } async function isAuthConfigured() { const rows = await database.local.query( `SELECT auth_base_url, dingtalk_enabled, dingtalk_app_key, dingtalk_app_secret, google_enabled, google_client_id, google_client_secret FROM auth_configs ORDER BY id ASC LIMIT 1` ); const row = rows[0]; if (!row) { return false; } const dingtalkReady = normalizeBoolean(row.dingtalk_enabled) && Boolean(row.dingtalk_app_key && row.dingtalk_app_secret); const googleReady = normalizeBoolean(row.google_enabled) && Boolean(row.google_client_id && row.google_client_secret); return Boolean((row.auth_base_url || '').trim() || dingtalkReady || googleReady); } async function isS3Configured() { const rows = await database.local.query( `SELECT bucket, access_key_id, secret_access_key FROM s3_configs WHERE is_default = 1 ORDER BY id ASC LIMIT 1` ); const row = rows[0]; return Boolean(row && row.bucket && row.access_key_id && row.secret_access_key); } async function getCurrentAdminUser(userId) { const rows = await database.local.query( `SELECT id, username, name, email, role, status, password_hash FROM users WHERE id = ? LIMIT 1`, [userId] ); return rows[0] || null; } async function isUsingDefaultPassword(user) { if (!user || !user.password_hash) { return false; } return bcrypt.compare(DEFAULT_ADMIN_PASSWORD, user.password_hash); } async function ensureSetupConfigDefaults() { const configMap = await getSystemConfigMap([SETUP_COMPLETED_KEY, SETUP_COMPLETED_AT_KEY]); if (configMap[SETUP_COMPLETED_KEY] === undefined) { const inferredCompleted = await isBusinessDatabaseConfigured(); await upsertSystemConfig(SETUP_COMPLETED_KEY, inferredCompleted ? 'true' : 'false', 'boolean'); } if (configMap[SETUP_COMPLETED_AT_KEY] === undefined) { await upsertSystemConfig(SETUP_COMPLETED_AT_KEY, '', 'string'); } } async function getSetupStatus(userId) { await ensureSetupConfigDefaults(); const [configMap, currentUser, businessDatabaseConfigured, authConfigured, s3Configured] = await Promise.all([ getSystemConfigMap([SETUP_COMPLETED_KEY, SETUP_COMPLETED_AT_KEY]), getCurrentAdminUser(userId), isBusinessDatabaseConfigured(), isAuthConfigured(), isS3Configured(), ]); const redisConfigured = (await getRedisConfig()).enabled; const currentUserUsesDefaultPassword = await isUsingDefaultPassword(currentUser); const completed = normalizeBoolean(configMap[SETUP_COMPLETED_KEY]); return { completed, shouldShowWizard: !completed, completedAt: configMap[SETUP_COMPLETED_AT_KEY] || '', currentUser: currentUser ? { id: currentUser.id, username: currentUser.username, name: currentUser.name, email: currentUser.email, role: currentUser.role, status: currentUser.status, } : null, currentUserUsesDefaultPassword, steps: { changeAdminPassword: !currentUserUsesDefaultPassword, businessDatabase: businessDatabaseConfigured, redisCache: redisConfigured, authConfig: authConfigured, objectStorage: s3Configured, }, }; } async function markSetupCompleted(userId) { const status = await getSetupStatus(userId); if (!status.steps.businessDatabase) { throw new Error('请先完成业务数据库配置'); } if (status.currentUserUsesDefaultPassword) { throw new Error('请先修改默认管理员密码'); } await upsertSystemConfig(SETUP_COMPLETED_KEY, 'true', 'boolean'); await upsertSystemConfig(SETUP_COMPLETED_AT_KEY, new Date().toISOString(), 'string'); return getSetupStatus(userId); } module.exports = { DEFAULT_ADMIN_PASSWORD, ensureSetupConfigDefaults, getSetupStatus, markSetupCompleted, };