All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m5s
136 lines
3.7 KiB
TypeScript
136 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 = 5
|
|
|
|
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) {
|
|
// 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<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
|
|
}
|
|
}
|