import { Pool, type PoolClient, type QueryResult, type QueryResultRow } from "pg"; import { readFileSync, existsSync, writeFileSync } from "fs"; import { resolve } from "path"; interface DbConfig { host: string; port: number; database: string; user: string; password: string; ssl?: boolean; } const CONFIG_PATH = resolve(process.cwd(), "data", "db-config.json"); let pool: Pool | null = null; let currentConfig: DbConfig | null = null; function loadConfigFromEnv(): DbConfig { return { host: process.env.FINANCE_DB_HOST || process.env.DB_HOST || "localhost", port: parseInt(process.env.FINANCE_DB_PORT || process.env.DB_PORT || "5432", 10), database: process.env.FINANCE_DB_NAME || process.env.DB_NAME || "postgres", user: process.env.FINANCE_DB_USER || process.env.DB_USER || "postgres", password: process.env.FINANCE_DB_PASSWORD || process.env.DB_PASSWORD || "", ssl: process.env.FINANCE_DB_SSL === "true" || process.env.DB_SSL === "true", }; } function loadConfigFromFile(): DbConfig | null { if (!existsSync(CONFIG_PATH)) return null; try { const raw = readFileSync(CONFIG_PATH, "utf-8"); const parsed = JSON.parse(raw) as DbConfig; if (parsed.host && parsed.database && parsed.user) { return parsed; } return null; } catch { return null; } } function createPool(config: DbConfig): Pool { return new Pool({ host: config.host, port: config.port, database: config.database, user: config.user, password: config.password, ssl: config.ssl ? { rejectUnauthorized: false } : false, max: 10, connectionTimeoutMillis: 10000, }); } export function getPool(): Pool { if (!pool) { const fileConfig = loadConfigFromFile(); const envConfig = loadConfigFromEnv(); currentConfig = fileConfig || envConfig; if (!currentConfig) { throw new Error("Database configuration not found"); } pool = createPool(currentConfig); pool.on("error", (err) => { console.error("Unexpected finance DB pool error:", err); }); } return pool; } export async function query(sql: string, params?: any[]): Promise> { const p = getPool(); return p.query(sql, params); } // Клиент для ручных транзакций (BEGIN/COMMIT/ROLLBACK) — см. google-auth.ts (синхронизация зарплат) export async function getPoolClient(): Promise { const p = getPool(); return p.connect(); } export async function testConnection(config: DbConfig): Promise<{ ok: boolean; message: string }> { const testPool = createPool(config); try { const client = await testPool.connect(); try { await client.query("SELECT 1"); return { ok: true, message: "Подключение успешно" }; } finally { client.release(); } } catch (err: any) { return { ok: false, message: err?.message || "Ошибка подключения" }; } finally { await testPool.end(); } } export function setConfig(config: DbConfig): void { if (pool) { pool.end().catch(() => {}); } pool = null; currentConfig = config; writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8"); pool = createPool(config); pool.on("error", (err) => { console.error("Unexpected finance DB pool error:", err); }); } export function getConfig(): Omit | null { if (!currentConfig) return null; const { password: _password, ...rest } = currentConfig; return rest; } export async function isConnected(): Promise { try { await query("SELECT 1"); return true; } catch { return false; } } const CONNECTION_ERROR_CODES = new Set(["57P01", "57P02", "57P03", "08000", "08001", "08003", "08006"]); export function isConnectionError(err: unknown): boolean { if (!(err instanceof Error)) return false; const code = (err as any).code; if (code && CONNECTION_ERROR_CODES.has(code)) return true; const msg = err.message.toLowerCase(); return msg.includes("econnrefused") || msg.includes("etimedout") || msg.includes("enotfound") || msg.includes("connection terminated") || msg.includes("connection timeout") || msg.includes("закрытие подключения") || msg.includes("the database system is shutting down"); }