191 lines
5.4 KiB
TypeScript
191 lines
5.4 KiB
TypeScript
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<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
|
const pool = getPool()
|
|
const result = await pool.query(sql, params)
|
|
return result.rows as T[]
|
|
}
|
|
|
|
/**
|
|
* 단일 행 조회
|
|
*/
|
|
export async function queryOne<T = any>(sql: string, params?: any[]): Promise<T | null> {
|
|
const rows = await query<T>(sql, params)
|
|
return rows[0] || null
|
|
}
|
|
|
|
/**
|
|
* INSERT/UPDATE/DELETE 실행
|
|
*/
|
|
export async function execute(sql: string, params?: any[]): Promise<number> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
if (pool) {
|
|
await pool.end()
|
|
pool = null
|
|
console.log('[DB] PostgreSQL pool closed')
|
|
}
|
|
}
|