import Database from 'better-sqlite3'; import path from 'path'; import fs from 'fs'; import crypto from 'crypto'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // ANSI color codes for terminal output const colors = { reset: '\x1b[0m', bright: '\x1b[1m', cyan: '\x1b[36m', dim: '\x1b[2m', }; const c = { info: (text) => `${colors.cyan}${text}${colors.reset}`, bright: (text) => `${colors.bright}${text}${colors.reset}`, dim: (text) => `${colors.dim}${text}${colors.reset}`, }; // Use DATABASE_PATH environment variable if set, otherwise use default location const DB_PATH = process.env.DATABASE_PATH || path.join(__dirname, 'auth.db'); const INIT_SQL_PATH = path.join(__dirname, 'init.sql'); // Ensure database directory exists if custom path is provided if (process.env.DATABASE_PATH) { const dbDir = path.dirname(DB_PATH); try { if (!fs.existsSync(dbDir)) { fs.mkdirSync(dbDir, { recursive: true }); console.log(`Created database directory: ${dbDir}`); } } catch (error) { console.error(`Failed to create database directory ${dbDir}:`, error.message); throw error; } } // Create database connection const db = new Database(DB_PATH); // Show app installation path prominently const appInstallPath = path.join(__dirname, '../..'); console.log(''); console.log(c.dim('═'.repeat(60))); console.log(`${c.info('[INFO]')} App Installation: ${c.bright(appInstallPath)}`); console.log(`${c.info('[INFO]')} Database: ${c.dim(path.relative(appInstallPath, DB_PATH))}`); if (process.env.DATABASE_PATH) { console.log(` ${c.dim('(Using custom DATABASE_PATH from environment)')}`); } console.log(c.dim('═'.repeat(60))); console.log(''); // Initialize database with schema const initializeDatabase = async () => { try { const initSQL = fs.readFileSync(INIT_SQL_PATH, 'utf8'); db.exec(initSQL); console.log('Database initialized successfully'); } catch (error) { console.error('Error initializing database:', error.message); throw error; } }; // User database operations const userDb = { // Check if any users exist hasUsers: () => { try { const row = db.prepare('SELECT COUNT(*) as count FROM users').get(); return row.count > 0; } catch (err) { throw err; } }, // Create a new user createUser: (username, passwordHash) => { try { const stmt = db.prepare('INSERT INTO users (username, password_hash) VALUES (?, ?)'); const result = stmt.run(username, passwordHash); return { id: result.lastInsertRowid, username }; } catch (err) { throw err; } }, // Get user by username getUserByUsername: (username) => { try { const row = db.prepare('SELECT * FROM users WHERE username = ? AND is_active = 1').get(username); return row; } catch (err) { throw err; } }, // Update last login time updateLastLogin: (userId) => { try { db.prepare('UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?').run(userId); } catch (err) { throw err; } }, // Get user by ID getUserById: (userId) => { try { const row = db.prepare('SELECT id, username, created_at, last_login FROM users WHERE id = ? AND is_active = 1').get(userId); return row; } catch (err) { throw err; } }, getFirstUser: () => { try { const row = db.prepare('SELECT id, username, created_at, last_login FROM users WHERE is_active = 1 LIMIT 1').get(); return row; } catch (err) { throw err; } } }; // API Keys database operations const apiKeysDb = { // Generate a new API key generateApiKey: () => { return 'ck_' + crypto.randomBytes(32).toString('hex'); }, // Create a new API key createApiKey: (userId, keyName) => { try { const apiKey = apiKeysDb.generateApiKey(); const stmt = db.prepare('INSERT INTO api_keys (user_id, key_name, api_key) VALUES (?, ?, ?)'); const result = stmt.run(userId, keyName, apiKey); return { id: result.lastInsertRowid, keyName, apiKey }; } catch (err) { throw err; } }, // Get all API keys for a user getApiKeys: (userId) => { try { const rows = db.prepare('SELECT id, key_name, api_key, created_at, last_used, is_active FROM api_keys WHERE user_id = ? ORDER BY created_at DESC').all(userId); return rows; } catch (err) { throw err; } }, // Validate API key and get user validateApiKey: (apiKey) => { try { const row = db.prepare(` SELECT u.id, u.username, ak.id as api_key_id FROM api_keys ak JOIN users u ON ak.user_id = u.id WHERE ak.api_key = ? AND ak.is_active = 1 AND u.is_active = 1 `).get(apiKey); if (row) { // Update last_used timestamp db.prepare('UPDATE api_keys SET last_used = CURRENT_TIMESTAMP WHERE id = ?').run(row.api_key_id); } return row; } catch (err) { throw err; } }, // Delete an API key deleteApiKey: (userId, apiKeyId) => { try { const stmt = db.prepare('DELETE FROM api_keys WHERE id = ? AND user_id = ?'); const result = stmt.run(apiKeyId, userId); return result.changes > 0; } catch (err) { throw err; } }, // Toggle API key active status toggleApiKey: (userId, apiKeyId, isActive) => { try { const stmt = db.prepare('UPDATE api_keys SET is_active = ? WHERE id = ? AND user_id = ?'); const result = stmt.run(isActive ? 1 : 0, apiKeyId, userId); return result.changes > 0; } catch (err) { throw err; } } }; // User credentials database operations (for GitHub tokens, GitLab tokens, etc.) const credentialsDb = { // Create a new credential createCredential: (userId, credentialName, credentialType, credentialValue, description = null) => { try { const stmt = db.prepare('INSERT INTO user_credentials (user_id, credential_name, credential_type, credential_value, description) VALUES (?, ?, ?, ?, ?)'); const result = stmt.run(userId, credentialName, credentialType, credentialValue, description); return { id: result.lastInsertRowid, credentialName, credentialType }; } catch (err) { throw err; } }, // Get all credentials for a user, optionally filtered by type getCredentials: (userId, credentialType = null) => { try { let query = 'SELECT id, credential_name, credential_type, description, created_at, is_active FROM user_credentials WHERE user_id = ?'; const params = [userId]; if (credentialType) { query += ' AND credential_type = ?'; params.push(credentialType); } query += ' ORDER BY created_at DESC'; const rows = db.prepare(query).all(...params); return rows; } catch (err) { throw err; } }, // Get active credential value for a user by type (returns most recent active) getActiveCredential: (userId, credentialType) => { try { const row = db.prepare('SELECT credential_value FROM user_credentials WHERE user_id = ? AND credential_type = ? AND is_active = 1 ORDER BY created_at DESC LIMIT 1').get(userId, credentialType); return row?.credential_value || null; } catch (err) { throw err; } }, // Delete a credential deleteCredential: (userId, credentialId) => { try { const stmt = db.prepare('DELETE FROM user_credentials WHERE id = ? AND user_id = ?'); const result = stmt.run(credentialId, userId); return result.changes > 0; } catch (err) { throw err; } }, // Toggle credential active status toggleCredential: (userId, credentialId, isActive) => { try { const stmt = db.prepare('UPDATE user_credentials SET is_active = ? WHERE id = ? AND user_id = ?'); const result = stmt.run(isActive ? 1 : 0, credentialId, userId); return result.changes > 0; } catch (err) { throw err; } } }; // Backward compatibility - keep old names pointing to new system const githubTokensDb = { createGithubToken: (userId, tokenName, githubToken, description = null) => { return credentialsDb.createCredential(userId, tokenName, 'github_token', githubToken, description); }, getGithubTokens: (userId) => { return credentialsDb.getCredentials(userId, 'github_token'); }, getActiveGithubToken: (userId) => { return credentialsDb.getActiveCredential(userId, 'github_token'); }, deleteGithubToken: (userId, tokenId) => { return credentialsDb.deleteCredential(userId, tokenId); }, toggleGithubToken: (userId, tokenId, isActive) => { return credentialsDb.toggleCredential(userId, tokenId, isActive); } }; export { db, initializeDatabase, userDb, apiKeysDb, credentialsDb, githubTokensDb // Backward compatibility };