Files
hermes-mcp/src/db.ts
2026-05-11 10:39:24 -04:00

125 lines
3.5 KiB
TypeScript

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<void> {
const [rows] = await db.execute<mysql.RowDataPacket[]>(
`
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<void> {
// 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');
}