const sql = require('mssql'); const database = require('./database'); const CONFIG_NAME = 'jiansun_database'; const CONFIG_CACHE_TTL_MS = 30000; let pool = null; let poolPromise = null; let poolSignature = ''; let cachedConfig = null; let configPromise = null; let configExpiresAt = 0; async function loadConfig() { if (cachedConfig && Date.now() < configExpiresAt) return cachedConfig; if (configPromise) return configPromise; configPromise = database.local.query( `SELECT host, port, database_name, username, password, \`ssl\`, status FROM database_configs WHERE name = ? LIMIT 1`, [CONFIG_NAME] ).then((rows) => { const stored = rows[0]; if (stored) { cachedConfig = stored.status === 'inactive' ? { disabled: true } : { server: stored.host, port: Number(stored.port || 1433), database: stored.database_name, user: stored.username, password: stored.password, encrypt: stored.ssl === 1 || stored.ssl === true, }; } else { cachedConfig = { server: process.env.JIAN_SUN_DB_HOST, port: Number(process.env.JIAN_SUN_DB_PORT || 1433), database: process.env.JIAN_SUN_DB_NAME || 'JianSun', user: process.env.JIAN_SUN_DB_USER, password: process.env.JIAN_SUN_DB_PASSWORD, encrypt: false, }; } configExpiresAt = Date.now() + CONFIG_CACHE_TTL_MS; return cachedConfig; }).finally(() => { configPromise = null; }); return configPromise; } function createPoolConfig(config) { return { server: config.server, port: config.port, database: config.database, user: config.user, password: config.password, options: { encrypt: config.encrypt, trustServerCertificate: true, }, requestTimeout: 120000, connectionTimeout: 120000, pool: { max: 100, min: 0, idleTimeoutMillis: 120000, }, }; } async function getPool() { const config = await loadConfig(); if (config.disabled) { throw new Error('JianSun database configuration is inactive'); } if (!config.server || !config.database || !config.user || !config.password) { throw new Error('JianSun database configuration is incomplete'); } const signature = [config.server, config.port, config.database, config.user, config.password, config.encrypt].join('|'); if (pool?.connected && signature === poolSignature) return pool; if (poolPromise && signature === poolSignature) return poolPromise; if (pool) await pool.close().catch(() => {}); poolSignature = signature; poolPromise = new sql.ConnectionPool(createPoolConfig(config)).connect() .then((connectedPool) => { pool = connectedPool; return connectedPool; }) .catch((error) => { pool = null; poolSignature = ''; throw error; }) .finally(() => { poolPromise = null; }); return poolPromise; } async function query(sqlText, params = {}, options = {}) { const activePool = await getPool(); const request = activePool.request(); if (options.requestTimeout) request.timeout = Number(options.requestTimeout); Object.entries(params).forEach(([key, value]) => request.input(key, value)); const result = await request.query(sqlText); return result.recordset || []; } async function close() { if (poolPromise) await poolPromise.catch(() => {}); if (pool) await pool.close().catch(() => {}); pool = null; poolPromise = null; poolSignature = ''; cachedConfig = null; configPromise = null; configExpiresAt = 0; } module.exports = { CONFIG_NAME, query, close, };