const mysql = require("mysql2/promise"); const { Client } = require("ssh2"); const { Pool } = require('pg'); const dotenv = require("dotenv"); const path = require("path"); // Helper function to setup SSH tunnel async function setupSshTunnel(sshConfig) { return new Promise((resolve, reject) => { const ssh = new Client(); ssh.on('error', (err) => { console.error('SSH connection error:', err); }); ssh.on('end', () => { console.log('SSH connection ended normally'); }); ssh.on('close', () => { console.log('SSH connection closed'); }); ssh .on("ready", () => { ssh.forwardOut( "127.0.0.1", 0, sshConfig.prodDbConfig.host, sshConfig.prodDbConfig.port, async (err, stream) => { if (err) reject(err); resolve({ ssh, stream }); } ); }) .connect(sshConfig.ssh); }); } // Helper function to setup database connections async function setupConnections(sshConfig) { const tunnel = await setupSshTunnel(sshConfig); // Setup MySQL connection for production const prodConnection = await mysql.createConnection({ ...sshConfig.prodDbConfig, stream: tunnel.stream, }); // Setup PostgreSQL connection pool for local const localPool = new Pool(sshConfig.localDbConfig); // Test the PostgreSQL connection try { const client = await localPool.connect(); await client.query('SELECT NOW()'); client.release(); console.log('PostgreSQL connection successful'); } catch (err) { console.error('PostgreSQL connection error:', err); throw err; } // Create a wrapper for the PostgreSQL pool to match MySQL interface const localConnection = { _client: null, _transactionActive: false, query: async (text, params) => { // If we're not in a transaction, use the pool directly if (!localConnection._transactionActive) { const client = await localPool.connect(); try { const result = await client.query(text, params); return [result]; } finally { client.release(); } } // If we're in a transaction, use the dedicated client if (!localConnection._client) { throw new Error('No active transaction client'); } const result = await localConnection._client.query(text, params); return [result]; }, beginTransaction: async () => { if (localConnection._transactionActive) { throw new Error('Transaction already active'); } localConnection._client = await localPool.connect(); await localConnection._client.query('BEGIN'); localConnection._transactionActive = true; }, commit: async () => { if (!localConnection._transactionActive) { throw new Error('No active transaction to commit'); } await localConnection._client.query('COMMIT'); localConnection._client.release(); localConnection._client = null; localConnection._transactionActive = false; }, rollback: async () => { if (!localConnection._transactionActive) { throw new Error('No active transaction to rollback'); } await localConnection._client.query('ROLLBACK'); localConnection._client.release(); localConnection._client = null; localConnection._transactionActive = false; }, end: async () => { if (localConnection._client) { localConnection._client.release(); localConnection._client = null; } await localPool.end(); } }; return { prodConnection, localConnection, tunnel }; } // Helper function to close connections async function closeConnections(connections) { const { ssh, prodConnection, localConnection } = connections; try { if (prodConnection) await prodConnection.end(); if (localConnection) await localConnection.end(); // Wait a bit for any pending data to be written before closing SSH await new Promise(resolve => setTimeout(resolve, 100)); if (ssh) { ssh.on('close', () => { console.log('SSH connection closed cleanly'); }); ssh.end(); } } catch (err) { console.error('Error during cleanup:', err); } } module.exports = { setupConnections, closeConnections };