125 lines
3.5 KiB
TypeScript
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');
|
|
}
|