Files
seguidorDeLotes/nuxt4/server/utils/db.ts
josedario87 2500cb1181
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m5s
arreglelo plix
2025-11-22 01:00:30 -06:00

135 lines
3.7 KiB
TypeScript

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<T = any>(
text: string,
params?: any[]
): Promise<pg.QueryResult<T>> {
const start = Date.now()
const maxRetries = 2
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const currentPool = getPool()
try {
const result = await currentPool.query<T>(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) {
// Pequeño backoff y reintento
await new Promise((res) => setTimeout(res, 500 * (attempt + 1)))
// 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<pg.PoolClient> {
const pool = getPool()
return await pool.connect()
}
/**
* Cierra el pool de conexiones (tests o shutdown).
*/
export async function closePool(): Promise<void> {
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<boolean> {
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
}
}