로컬(개발)용과 운영용 설정분리

This commit is contained in:
2025-12-28 13:45:41 +09:00
parent a871ec8008
commit 716f4f8791
15 changed files with 661 additions and 368 deletions

View File

@@ -1,44 +1,109 @@
import Database from 'better-sqlite3'
import { resolve } from 'path'
import pg from 'pg'
// 싱글톤 DB 인스턴스
let db: Database.Database | null = null
const { Pool } = pg
export function getDb(): Database.Database {
if (!db) {
const dbPath = resolve(process.cwd(), 'database/osolit-monitor.db')
db = new Database(dbPath)
db.pragma('journal_mode = WAL')
// 환경 설정
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 db
return pool
}
export function initPubnetTables(): void {
const db = getDb()
/**
* 단일 쿼리 실행 (자동 커넥션 반환)
*/
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[]
}
db.exec(`
/**
* 단일 행 조회
*/
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 INTEGER PRIMARY KEY AUTOINCREMENT,
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 (datetime('now', 'localtime')),
updated_at TEXT DEFAULT (datetime('now', 'localtime'))
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')
)
`)
db.exec(`
// pubnet_logs
await pool.query(`
CREATE TABLE IF NOT EXISTS pubnet_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
id SERIAL PRIMARY KEY,
target_id INTEGER NOT NULL,
is_success INTEGER NOT NULL,
checked_at TEXT DEFAULT (datetime('now', 'localtime')),
checked_at TEXT DEFAULT to_char(NOW(), 'YYYY-MM-DD HH24:MI:SS'),
FOREIGN KEY (target_id) REFERENCES pubnet_targets(id)
)
`)
db.exec(`
// pubnet_status (싱글톤 패턴)
await pool.query(`
CREATE TABLE IF NOT EXISTS pubnet_status (
id INTEGER PRIMARY KEY CHECK (id = 1),
current_index INTEGER DEFAULT 0,
@@ -47,44 +112,51 @@ export function initPubnetTables(): void {
last_target_id INTEGER,
last_checked_at TEXT,
scheduler_running INTEGER DEFAULT 0,
updated_at TEXT DEFAULT (datetime('now', 'localtime'))
updated_at TEXT DEFAULT to_char(NOW(), 'YYYY-MM-DD HH24:MI:SS')
)
`)
const statusExists = db.prepare('SELECT COUNT(*) as cnt FROM pubnet_status').get() as { cnt: number }
if (statusExists.cnt === 0) {
db.prepare('INSERT INTO pubnet_status (id) VALUES (1)').run()
// 상태 행 존재 확인 및 생성
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')
}
export function initPrivnetTables(): void {
const db = getDb()
/**
* privnet 테이블 초기화 (PostgreSQL용)
*/
export async function initPrivnetTables(): Promise<void> {
const pool = getPool()
db.exec(`
// privnet_targets
await pool.query(`
CREATE TABLE IF NOT EXISTS privnet_targets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
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 (datetime('now', 'localtime')),
updated_at TEXT DEFAULT (datetime('now', 'localtime'))
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')
)
`)
db.exec(`
// privnet_logs
await pool.query(`
CREATE TABLE IF NOT EXISTS privnet_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
id SERIAL PRIMARY KEY,
target_id INTEGER NOT NULL,
is_success INTEGER NOT NULL,
checked_at TEXT DEFAULT (datetime('now', 'localtime')),
checked_at TEXT DEFAULT to_char(NOW(), 'YYYY-MM-DD HH24:MI:SS'),
FOREIGN KEY (target_id) REFERENCES privnet_targets(id)
)
`)
db.exec(`
// privnet_status (싱글톤 패턴)
await pool.query(`
CREATE TABLE IF NOT EXISTS privnet_status (
id INTEGER PRIMARY KEY CHECK (id = 1),
current_index INTEGER DEFAULT 0,
@@ -93,21 +165,26 @@ export function initPrivnetTables(): void {
last_target_id INTEGER,
last_checked_at TEXT,
scheduler_running INTEGER DEFAULT 0,
updated_at TEXT DEFAULT (datetime('now', 'localtime'))
updated_at TEXT DEFAULT to_char(NOW(), 'YYYY-MM-DD HH24:MI:SS')
)
`)
const statusExists = db.prepare('SELECT COUNT(*) as cnt FROM privnet_status').get() as { cnt: number }
if (statusExists.cnt === 0) {
db.prepare('INSERT INTO privnet_status (id) VALUES (1)').run()
// 상태 행 존재 확인 및 생성
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 function closeDb(): void {
if (db) {
db.close()
db = null
/**
* 연결 풀 종료
*/
export async function closeDb(): Promise<void> {
if (pool) {
await pool.end()
pool = null
console.log('[DB] PostgreSQL pool closed')
}
}