import pg from 'pg' const { Pool } = pg // 환경 설정 const isDev = process.env.NODE_ENV !== 'production' // PostgreSQL 연결 풀 (싱글톤) let pool: pg.Pool | null = null /** * PostgreSQL 연결 풀 가져오기 */ export function getPool(): pg.Pool { if (!pool) { pool = new Pool({ host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '5432'), database: process.env.DB_NAME || 'osolit_monitor', user: process.env.DB_USER || 'postgres', password: process.env.DB_PASSWORD || '', max: 10, idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, }) pool.on('error', (err) => { console.error('[DB] Unexpected pool error:', err) }) console.log(`[DB] PostgreSQL pool created (${isDev ? 'development' : 'production'})`) } return pool } /** * 단일 쿼리 실행 (자동 커넥션 반환) */ export async function query(sql: string, params?: any[]): Promise { const pool = getPool() const result = await pool.query(sql, params) return result.rows as T[] } /** * 단일 행 조회 */ export async function queryOne(sql: string, params?: any[]): Promise { const rows = await query(sql, params) return rows[0] || null } /** * INSERT/UPDATE/DELETE 실행 */ export async function execute(sql: string, params?: any[]): Promise { const pool = getPool() const result = await pool.query(sql, params) return result.rowCount || 0 } /** * 스케줄러 자동시작 여부 */ export function shouldAutoStartScheduler(): boolean { // 환경변수로 명시적 설정된 경우 const envValue = process.env.AUTO_START_SCHEDULER if (envValue !== undefined) { return envValue === 'true' } // 기본값: production=true, development=false return !isDev } /** * pubnet 테이블 초기화 (PostgreSQL용) */ export async function initPubnetTables(): Promise { const pool = getPool() // pubnet_targets await pool.query(` CREATE TABLE IF NOT EXISTS pubnet_targets ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, url TEXT NOT NULL UNIQUE, is_active INTEGER DEFAULT 1, sort_order INTEGER DEFAULT 0, created_at TEXT DEFAULT to_char(NOW(), 'YYYY-MM-DD HH24:MI:SS'), updated_at TEXT DEFAULT to_char(NOW(), 'YYYY-MM-DD HH24:MI:SS') ) `) // pubnet_logs await pool.query(` CREATE TABLE IF NOT EXISTS pubnet_logs ( id SERIAL PRIMARY KEY, target_id INTEGER NOT NULL, is_success INTEGER NOT NULL, checked_at TEXT DEFAULT to_char(NOW(), 'YYYY-MM-DD HH24:MI:SS'), FOREIGN KEY (target_id) REFERENCES pubnet_targets(id) ) `) // pubnet_status (싱글톤 패턴) await pool.query(` CREATE TABLE IF NOT EXISTS pubnet_status ( id INTEGER PRIMARY KEY CHECK (id = 1), current_index INTEGER DEFAULT 0, check_interval INTEGER DEFAULT 300000, is_healthy INTEGER DEFAULT 1, last_target_id INTEGER, last_checked_at TEXT, scheduler_running INTEGER DEFAULT 0, updated_at TEXT DEFAULT to_char(NOW(), 'YYYY-MM-DD HH24:MI:SS') ) `) // 상태 행 존재 확인 및 생성 const statusExists = await queryOne<{ cnt: number }>('SELECT COUNT(*) as cnt FROM pubnet_status') if (!statusExists || statusExists.cnt === 0) { await pool.query('INSERT INTO pubnet_status (id) VALUES (1)') } console.log('[DB] pubnet tables initialized') } /** * privnet 테이블 초기화 (PostgreSQL용) */ export async function initPrivnetTables(): Promise { const pool = getPool() // privnet_targets await pool.query(` CREATE TABLE IF NOT EXISTS privnet_targets ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, url TEXT NOT NULL UNIQUE, is_active INTEGER DEFAULT 1, sort_order INTEGER DEFAULT 0, created_at TEXT DEFAULT to_char(NOW(), 'YYYY-MM-DD HH24:MI:SS'), updated_at TEXT DEFAULT to_char(NOW(), 'YYYY-MM-DD HH24:MI:SS') ) `) // privnet_logs await pool.query(` CREATE TABLE IF NOT EXISTS privnet_logs ( id SERIAL PRIMARY KEY, target_id INTEGER NOT NULL, is_success INTEGER NOT NULL, checked_at TEXT DEFAULT to_char(NOW(), 'YYYY-MM-DD HH24:MI:SS'), FOREIGN KEY (target_id) REFERENCES privnet_targets(id) ) `) // privnet_status (싱글톤 패턴) await pool.query(` CREATE TABLE IF NOT EXISTS privnet_status ( id INTEGER PRIMARY KEY CHECK (id = 1), current_index INTEGER DEFAULT 0, check_interval INTEGER DEFAULT 300000, is_healthy INTEGER DEFAULT 1, last_target_id INTEGER, last_checked_at TEXT, scheduler_running INTEGER DEFAULT 0, updated_at TEXT DEFAULT to_char(NOW(), 'YYYY-MM-DD HH24:MI:SS') ) `) // 상태 행 존재 확인 및 생성 const statusExists = await queryOne<{ cnt: number }>('SELECT COUNT(*) as cnt FROM privnet_status') if (!statusExists || statusExists.cnt === 0) { await pool.query('INSERT INTO privnet_status (id) VALUES (1)') } console.log('[DB] privnet tables initialized') } /** * 연결 풀 종료 */ export async function closeDb(): Promise { if (pool) { await pool.end() pool = null console.log('[DB] PostgreSQL pool closed') } }