import pg from 'pg' import { useRuntimeConfig } from '#imports' const { Pool } = pg let pool: pg.Pool | null = null let connectionStringLogged = false function buildConnectionString(): string { const config = useRuntimeConfig() if (config.postgresUrl) { return config.postgresUrl } // Fallback para entornos locales si no se pasó NUXT_POSTGRES_URL const user = process.env.POSTGRES_USER || 'seguidor' const password = process.env.POSTGRES_PASSWORD || 'seguidor_password' const host = process.env.POSTGRES_HOST || 'postgres' const port = process.env.POSTGRES_PORT || '5432' const db = process.env.POSTGRES_DB || 'seguidor_lotes' return `postgres://${user}:${password}@${host}:${port}/${db}` } /** * Obtiene o crea el pool de conexiones a PostgreSQL. * Usa cadena de conexión única para mantener consistencia. */ export function getPool(): pg.Pool { if (!pool) { const connectionString = buildConnectionString() if (!connectionStringLogged && process.env.NODE_ENV !== 'production') { const masked = connectionString.replace(/:\/\/([^:]+):([^@]+)@/, '://$1:*****@') console.log('[db] Usando connectionString:', masked) connectionStringLogged = true } pool = new Pool({ connectionString, max: 10, idleTimeoutMillis: 30000, connectionTimeoutMillis: 10000, }) pool.on('error', (err) => { console.error('Error inesperado en el pool de PostgreSQL:', err) }) if (process.env.NODE_ENV !== 'production') { pool.on('connect', () => { console.log('Nueva conexión establecida con PostgreSQL') }) } } return pool } /** * Ejecuta una query SQL con parámetros. */ export async function query( text: string, params?: any[] ): Promise> { const start = Date.now() const maxRetries = 5 for (let attempt = 0; attempt <= maxRetries; attempt++) { const currentPool = getPool() try { const result = await currentPool.query(text, params) const duration = Date.now() - start if (process.env.NODE_ENV !== 'production') { console.log('Query ejecutada:', { text, duration: `${duration}ms`, rows: result.rowCount }) } return result } catch (error: any) { const isAuthError = error?.code === '28P01' const isConnError = ['ECONNREFUSED', 'ECONNRESET', '57P01'].includes(error?.code) const shouldRetry = attempt < maxRetries && (isAuthError || isConnError) console.error('Error ejecutando query:', { text, params, error: error?.message || error, code: error?.code, attempt }) if (shouldRetry) { // Backoff progresivo y reintento const delay = Math.min(2000, 300 * (attempt + 1)) await new Promise((res) => setTimeout(res, delay)) // Resetear pool en auth/conn error por si la contraseña/estado cambia en caliente try { await currentPool.end() } catch (_) {} pool = null continue } throw error } } } /** * Obtiene un cliente del pool para ejecutar transacciones. */ export async function getClient(): Promise { const pool = getPool() return await pool.connect() } /** * Cierra el pool de conexiones (tests o shutdown). */ export async function closePool(): Promise { if (pool) { await pool.end() pool = null console.log('Pool de PostgreSQL cerrado') } } /** * Verifica que la conexión a la base de datos esté funcionando. */ export async function checkConnection(): Promise { try { const result = await query('SELECT NOW() as now') return result.rows.length > 0 } catch (error) { console.error('Error verificando conexión a PostgreSQL:', error) return false } }