import mysql from 'mysql2/promise'; const host = process.env.MYSQL_HOST || '127.0.0.1'; const port = parseInt(process.env.MYSQL_PORT || '3306', 10); const user = process.env.MYSQL_USER || 'root'; const password = process.env.MYSQL_PASSWORD || ''; let pool: mysql.Pool | null = null; async function ensureColumn( db: mysql.PoolConnection, tableName: string, columnName: string, definition: string ): Promise { const [rows] = await db.execute( ` SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? `, [tableName, columnName] ); if (Array.isArray(rows) && rows.length > 0) { return; } await db.execute(`ALTER TABLE \`${tableName}\` ADD COLUMN \`${columnName}\` ${definition}`); } export function getPool(): mysql.Pool { if (!pool) { throw new Error('Database pool not initialized. Call initDatabase() first.'); } return pool; } export function isPoolReady(): boolean { return pool !== null; } export async function initDatabase(): Promise { // Create database if it doesn't exist const tmpConn = await mysql.createConnection({ host, port, user, password }); await tmpConn.execute('CREATE DATABASE IF NOT EXISTS hermes_oauth'); await tmpConn.end(); pool = mysql.createPool({ host, port, user, password, database: 'hermes_oauth', waitForConnections: true, connectionLimit: 10, queueLimit: 0, timezone: 'Z', }); const db = await pool.getConnection(); try { await db.execute(` CREATE TABLE IF NOT EXISTS oauth_clients ( client_id VARCHAR(255) PRIMARY KEY, client_secret VARCHAR(255) NOT NULL, client_name VARCHAR(255), redirect_urls JSON, grant_types JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_used TIMESTAMP NULL, is_static BOOLEAN DEFAULT FALSE, INDEX idx_last_used (last_used) ) `); await db.execute(` CREATE TABLE IF NOT EXISTS oauth_auth_codes ( code VARCHAR(255) PRIMARY KEY, client_id VARCHAR(255), redirect_uri TEXT, scope TEXT NULL, code_challenge TEXT NULL, code_challenge_method VARCHAR(20) NULL, expires_at TIMESTAMP, used BOOLEAN DEFAULT FALSE, INDEX idx_expires (expires_at) ) `); await ensureColumn(db, 'oauth_auth_codes', 'scope', 'TEXT NULL'); await ensureColumn(db, 'oauth_auth_codes', 'code_challenge', 'TEXT NULL'); await ensureColumn(db, 'oauth_auth_codes', 'code_challenge_method', 'VARCHAR(20) NULL'); await db.execute(` CREATE TABLE IF NOT EXISTS oauth_tokens ( token VARCHAR(255) PRIMARY KEY, client_id VARCHAR(255), token_type ENUM('access', 'refresh') DEFAULT 'access', expires_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_expires (expires_at) ) `); await db.execute(` CREATE TABLE IF NOT EXISTS customers ( id VARCHAR(255) PRIMARY KEY, api_key VARCHAR(255) NOT NULL UNIQUE, plan ENUM('free', 'starter', 'growth', 'enterprise') DEFAULT 'free', active BOOLEAN DEFAULT TRUE, email VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_api_key (api_key) ) `); } finally { db.release(); } console.log('[db] MySQL connected and schema initialized'); }