Simplificar configuración de PostgreSQL con cadena de conexión única
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 16s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 16s
Eliminados hacks de autenticación md5 y configuración manual de pg_hba.conf. Ahora usa NUXT_POSTGRES_URL como secret de Gitea para conexión directa.
This commit is contained in:
@@ -27,6 +27,15 @@ APP_DOMAIN=miapp.ejemplo.com
|
||||
# URL pública de la aplicación
|
||||
NUXT_PUBLIC_APP_URL=https://miapp.ejemplo.com
|
||||
|
||||
# ===========================================
|
||||
# DATABASE (PostgreSQL)
|
||||
# ===========================================
|
||||
POSTGRES_USER=seguidor
|
||||
POSTGRES_PASSWORD=seguidor_password
|
||||
POSTGRES_DB=seguidor_lotes
|
||||
POSTGRES_PORT=5432
|
||||
NUXT_POSTGRES_URL=postgres://seguidor:seguidor_password@postgres:5432/seguidor_lotes
|
||||
|
||||
# ===========================================
|
||||
# REGISTRY AUTHENTICATION (solo para CI/CD)
|
||||
# ===========================================
|
||||
|
||||
@@ -17,6 +17,8 @@ jobs:
|
||||
POSTGRES_USER: ${{ vars.POSTGRES_USER || 'seguidor' }}
|
||||
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD || 'seguidor_password' }}
|
||||
POSTGRES_DB: ${{ vars.POSTGRES_DB || 'seguidor_lotes' }}
|
||||
POSTGRES_PORT: ${{ vars.POSTGRES_PORT || '5432' }}
|
||||
NUXT_POSTGRES_URL: ${{ secrets.NUXT_POSTGRES_URL || vars.NUXT_POSTGRES_URL || '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -44,37 +46,17 @@ jobs:
|
||||
|
||||
docker compose pull
|
||||
docker compose --project-name $APP_NAME down
|
||||
|
||||
# Levantar solo Postgres para sincronizar auth antes de iniciar la app
|
||||
docker compose --project-name $APP_NAME up -d postgres
|
||||
|
||||
echo "⏳ Esperando a PostgreSQL..."
|
||||
for i in $(seq 1 20); do
|
||||
if docker exec $APP_NAME-postgres pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Asegurar autenticación md5 y que la contraseña coincide con la env (cura volúmenes viejos)
|
||||
echo "🔐 Sincronizando autenticación PostgreSQL (md5 + password)..."
|
||||
ESCAPED_PASSWORD=${POSTGRES_PASSWORD//\'/\'\"\'\"\'}
|
||||
docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" $APP_NAME-postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "ALTER SYSTEM SET password_encryption = 'md5';"
|
||||
docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" $APP_NAME-postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "ALTER ROLE \"$POSTGRES_USER\" WITH PASSWORD '${ESCAPED_PASSWORD}';"
|
||||
docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" $APP_NAME-postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "SELECT pg_reload_conf();"
|
||||
|
||||
# Ahora sí levantar la app
|
||||
docker compose --project-name $APP_NAME up -d --remove-orphans --wait
|
||||
|
||||
# Inicializar base de datos si es necesario
|
||||
echo "🗄️ Inicializando base de datos..."
|
||||
# Verificar si las tablas existen
|
||||
TABLE_EXISTS=$(docker exec $APP_NAME-postgres psql -U $POSTGRES_USER -d $POSTGRES_DB -tAc "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'lotes');")
|
||||
TABLE_EXISTS=$(docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" $APP_NAME-postgres psql -U $POSTGRES_USER -d $POSTGRES_DB -tAc "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'lotes');")
|
||||
|
||||
if [ "$TABLE_EXISTS" = "f" ]; then
|
||||
echo "📝 Ejecutando scripts de inicialización..."
|
||||
docker exec -i $APP_NAME-postgres psql -U $POSTGRES_USER -d $POSTGRES_DB < nuxt4/server/database/01_schema.sql
|
||||
docker exec -i $APP_NAME-postgres psql -U $POSTGRES_USER -d $POSTGRES_DB < nuxt4/server/database/02_seed.sql
|
||||
docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" -i $APP_NAME-postgres psql -U $POSTGRES_USER -d $POSTGRES_DB < nuxt4/server/database/01_schema.sql
|
||||
docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" -i $APP_NAME-postgres psql -U $POSTGRES_USER -d $POSTGRES_DB < nuxt4/server/database/02_seed.sql
|
||||
echo "✅ Base de datos inicializada"
|
||||
else
|
||||
echo "ℹ️ Base de datos ya inicializada"
|
||||
|
||||
12
README.md
12
README.md
@@ -89,8 +89,10 @@ UVA-001 (2086kg)
|
||||
- `APP_NAME` - `lotes`
|
||||
- `APP_DOMAIN` - `lotes.nucleoriofrio.com`
|
||||
- `NUXT_PUBLIC_APP_URL` - `https://lotes.nucleoriofrio.com`
|
||||
- `NUXT_POSTGRES_URL` - `postgres://seguidor:seguidor_password@postgres:5432/seguidor_lotes`
|
||||
- `POSTGRES_USER` - `seguidor`
|
||||
- `POSTGRES_DB` - `seguidor_lotes`
|
||||
- `POSTGRES_PORT` - `5432`
|
||||
|
||||
3. **Push al repositorio** - El workflow automáticamente:
|
||||
- Construye la imagen Docker
|
||||
@@ -122,7 +124,6 @@ UVA-001 (2086kg)
|
||||
│ │ │ ├── db.ts # Pool de PostgreSQL
|
||||
│ │ │ └── queries.ts # Funciones SQL
|
||||
│ │ └── database/
|
||||
│ │ ├── 00_configure_auth.sh # Config autenticación
|
||||
│ │ ├── 01_schema.sql # Esquema DB
|
||||
│ │ └── 02_seed.sql # Datos de ejemplo
|
||||
│ └── package.json
|
||||
@@ -293,10 +294,7 @@ cd nuxt4
|
||||
npm install
|
||||
|
||||
# Configurar PostgreSQL local
|
||||
export POSTGRES_USER=seguidor
|
||||
export POSTGRES_PASSWORD=seguidor_password
|
||||
export POSTGRES_DB=seguidor_lotes
|
||||
export POSTGRES_HOST=localhost
|
||||
export NUXT_POSTGRES_URL=postgres://seguidor:seguidor_password@localhost:5432/seguidor_lotes
|
||||
|
||||
# Ejecutar scripts SQL
|
||||
psql -U seguidor -d seguidor_lotes < server/database/01_schema.sql
|
||||
@@ -313,9 +311,7 @@ cd nuxt4
|
||||
npm install
|
||||
|
||||
# Apuntar a BD de producción
|
||||
export POSTGRES_HOST=server.interno.com
|
||||
export POSTGRES_PORT=5432
|
||||
# ... resto de variables
|
||||
export NUXT_POSTGRES_URL=postgres://seguidor:seguidor_password@server.interno.com:5432/seguidor_lotes
|
||||
|
||||
npm run dev
|
||||
```
|
||||
|
||||
@@ -9,6 +9,7 @@ services:
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
- POSTGRES_PORT=${POSTGRES_PORT:-5432}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./nuxt4/server/database:/docker-entrypoint-initdb.d:ro
|
||||
@@ -38,8 +39,7 @@ services:
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
- POSTGRES_HOST=postgres
|
||||
- POSTGRES_PORT=5432
|
||||
- NUXT_POSTGRES_URL=${NUXT_POSTGRES_URL:-postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:${POSTGRES_PORT:-5432}/${POSTGRES_DB}}
|
||||
networks:
|
||||
- principal
|
||||
- traefik-network
|
||||
|
||||
@@ -15,6 +15,7 @@ export default defineNuxtConfig({
|
||||
css: ['~/assets/css/main.css'],
|
||||
|
||||
runtimeConfig: {
|
||||
postgresUrl: process.env.NUXT_POSTGRES_URL || '',
|
||||
public: {
|
||||
authentikUrl: process.env.NUXT_PUBLIC_AUTHENTIK_URL || 'https://authentik.nucleoriofrio.com'
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Configurando autenticación de PostgreSQL..."
|
||||
|
||||
POSTGRES_USER="${POSTGRES_USER:-seguidor}"
|
||||
POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-seguidor_password}"
|
||||
POSTGRES_DB="${POSTGRES_DB:-$POSTGRES_USER}"
|
||||
|
||||
# Eliminar configuración scram-sha-256 y agregar md5
|
||||
# Esto asegura que las conexiones remotas funcionen correctamente
|
||||
sed -i '/scram-sha-256/d' "$PGDATA/pg_hba.conf"
|
||||
|
||||
# Agregar configuración para conexiones md5 si no existe
|
||||
if ! grep -q "host all all all md5" "$PGDATA/pg_hba.conf"; then
|
||||
echo "host all all all md5" >> "$PGDATA/pg_hba.conf"
|
||||
fi
|
||||
|
||||
# Forzar que las contraseñas se guarden en md5 (no scram) y
|
||||
# reescribir la contraseña para asegurar que coincide con las env vars.
|
||||
ESCAPED_PASSWORD=${POSTGRES_PASSWORD//\'/\'\'}
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
ALTER SYSTEM SET password_encryption = 'md5';
|
||||
ALTER ROLE "$POSTGRES_USER" WITH PASSWORD '${ESCAPED_PASSWORD}';
|
||||
SELECT pg_reload_conf();
|
||||
EOSQL
|
||||
|
||||
echo "✓ Configuración de autenticación aplicada (md5 + password sincronizada)"
|
||||
|
||||
# Nota: No es necesario recargar aquí porque este script corre ANTES
|
||||
# de que PostgreSQL termine su inicialización. Los cambios se aplican
|
||||
# automáticamente cuando PostgreSQL termina de iniciarse.
|
||||
@@ -6,7 +6,7 @@ Este directorio contiene los scripts SQL para inicializar y gestionar la base de
|
||||
|
||||
- [Archivos SQL](#archivos-sql)
|
||||
- [⚠️ Peculiaridades de Implementación](#️-peculiaridades-de-implementación)
|
||||
- [Autenticación PostgreSQL](#autenticación-postgresql)
|
||||
- [Conexión de la app](#conexión-de-la-app)
|
||||
- [Inicialización de la Base de Datos](#inicialización-de-la-base-de-datos)
|
||||
- [Persistencia de Datos](#persistencia-de-datos)
|
||||
- [Conexión desde Node.js](#conexión-desde-nodejs)
|
||||
@@ -19,12 +19,6 @@ Este directorio contiene los scripts SQL para inicializar y gestionar la base de
|
||||
|
||||
## Archivos SQL
|
||||
|
||||
### `00_configure_auth.sh`
|
||||
Script Bash que configura la autenticación de PostgreSQL:
|
||||
- Elimina configuración `scram-sha-256` (incompatible con driver `pg` de Node.js)
|
||||
- Configura método `md5` en `pg_hba.conf`
|
||||
- Se ejecuta automáticamente durante inicialización de PostgreSQL
|
||||
|
||||
### `01_schema.sql`
|
||||
Crea el esquema completo de la base de datos:
|
||||
- Tablas: `lotes`, `operaciones`, `operacion_lotes`
|
||||
@@ -52,51 +46,11 @@ Datos de ejemplo que representan un flujo completo de trazabilidad:
|
||||
|
||||
Esta sección documenta los desafíos técnicos encontrados y las soluciones implementadas durante el desarrollo.
|
||||
|
||||
### Autenticación PostgreSQL
|
||||
### Conexión de la app
|
||||
|
||||
#### ❌ Problema: Incompatibilidad scram-sha-256
|
||||
|
||||
**Contexto**: PostgreSQL 16 usa por defecto el método de autenticación `scram-sha-256`, pero el driver `pg` de Node.js tiene problemas de compatibilidad que resultan en:
|
||||
|
||||
```
|
||||
Error code: 28P01
|
||||
FATAL: password authentication failed for user "seguidor"
|
||||
```
|
||||
|
||||
#### ✅ Solución: Script de configuración automática
|
||||
|
||||
**Archivo**: `00_configure_auth.sh`
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Configurando autenticación de PostgreSQL..."
|
||||
|
||||
# Eliminar configuración scram-sha-256
|
||||
sed -i '/scram-sha-256/d' "$PGDATA/pg_hba.conf"
|
||||
|
||||
# Agregar configuración md5
|
||||
if ! grep -q "host all all all md5" "$PGDATA/pg_hba.conf"; then
|
||||
echo "host all all all md5" >> "$PGDATA/pg_hba.conf"
|
||||
fi
|
||||
|
||||
# Forzar md5 para almacenamiento de passwords y reescribir contraseña
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
ALTER SYSTEM SET password_encryption = 'md5';
|
||||
ALTER ROLE "$POSTGRES_USER" WITH PASSWORD '${POSTGRES_PASSWORD}';
|
||||
SELECT pg_reload_conf();
|
||||
EOSQL
|
||||
|
||||
echo "✓ Configuración de autenticación aplicada (md5)"
|
||||
```
|
||||
|
||||
**Notas importantes**:
|
||||
- El script se ejecuta **antes** de que PostgreSQL termine su inicialización
|
||||
- Los cambios en `pg_hba.conf` se aplican automáticamente al finalizar el inicio
|
||||
- No es necesario ejecutar `pg_ctl reload` manualmente
|
||||
- El método `md5` es compatible con el driver `pg` de Node.js
|
||||
- Cada deploy vuelve a aplicar `password_encryption=md5` y resetea la contraseña vía Gitea Action para curar volúmenes antiguos
|
||||
- Se usa una **cadena de conexión única** (`NUXT_POSTGRES_URL`) consumida desde `runtimeConfig.postgresUrl`.
|
||||
- Formato recomendado: `postgres://seguidor:seguidor_password@postgres:5432/seguidor_lotes`.
|
||||
- El driver `pg` soporta `scram-sha-256` (default de Postgres 16), no se toca `pg_hba.conf`.
|
||||
|
||||
---
|
||||
|
||||
@@ -106,9 +60,8 @@ echo "✓ Configuración de autenticación aplicada (md5)"
|
||||
|
||||
Los scripts se ejecutan **solo si el volumen de datos está vacío** (primera vez):
|
||||
|
||||
1. **`00_configure_auth.sh`** - Configura autenticación md5
|
||||
2. **`01_schema.sql`** - Crea estructura (tablas, funciones, vistas)
|
||||
3. **`02_seed.sql`** - Inserta datos de ejemplo
|
||||
1. **`01_schema.sql`** - Crea estructura (tablas, funciones, vistas)
|
||||
2. **`02_seed.sql`** - Inserta datos de ejemplo
|
||||
|
||||
**Orden de ejecución**: Los archivos se ejecutan en orden alfabético/numérico.
|
||||
|
||||
@@ -216,42 +169,19 @@ git commit --allow-empty -m "Trigger reinit DB" && git push
|
||||
**Configuración en `server/utils/db.ts`**:
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
user: process.env.POSTGRES_USER || 'seguidor',
|
||||
password: process.env.POSTGRES_PASSWORD || 'seguidor_password',
|
||||
database: process.env.POSTGRES_DB || 'seguidor_lotes',
|
||||
host: process.env.POSTGRES_HOST || 'postgres',
|
||||
port: parseInt(process.env.POSTGRES_PORT || '5432'),
|
||||
max: 20, // Máximo de conexiones en el pool
|
||||
idleTimeoutMillis: 30000, // 30s timeout para conexiones idle
|
||||
connectionTimeoutMillis: 10000, // 10s timeout para establecer conexión
|
||||
}
|
||||
const config = useRuntimeConfig()
|
||||
const pool = new Pool({
|
||||
connectionString: config.postgresUrl, // Ej: postgres://user:pass@host:5432/db
|
||||
max: 10,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 10000,
|
||||
})
|
||||
```
|
||||
|
||||
**Peculiaridades**:
|
||||
|
||||
1. **Timeout de 10 segundos**: Necesario porque PostgreSQL puede tardar en aplicar configuración de autenticación
|
||||
|
||||
2. **Retry Logic Automático**:
|
||||
```typescript
|
||||
// Verifica conexión inicial con 5 reintentos
|
||||
let retries = 5
|
||||
const tryConnect = async () => {
|
||||
try {
|
||||
const client = await pool!.connect()
|
||||
console.log('✅ Conexión inicial a PostgreSQL exitosa')
|
||||
client.release()
|
||||
} catch (err: any) {
|
||||
retries--
|
||||
if (retries > 0) {
|
||||
console.log(`⚠️ Conexión falló, reintentando... (${retries} intentos)`)
|
||||
setTimeout(tryConnect, 2000) // Reintentar en 2s
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Logs solo en desarrollo**:
|
||||
1. **Cadena única**: evita discrepancias entre host/puerto/usuario/contraseña.
|
||||
2. **Logs solo en desarrollo**:
|
||||
```typescript
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
pool.on('connect', () => {
|
||||
@@ -262,20 +192,7 @@ const config = {
|
||||
|
||||
#### ⚡ Race Condition: App vs PostgreSQL
|
||||
|
||||
**Problema**: Aunque docker-compose tiene `depends_on` con `service_healthy`, el healthcheck solo verifica que PostgreSQL **responde**, no que terminó de ejecutar los scripts de inicialización.
|
||||
|
||||
**Timeline Real**:
|
||||
```
|
||||
T+0s: PostgreSQL inicia
|
||||
T+2s: PostgreSQL responde a pg_isready → healthcheck ✅
|
||||
T+2s: App Nuxt inicia e intenta conectarse
|
||||
T+3s: PostgreSQL ejecuta 00_configure_auth.sh (cambia auth)
|
||||
T+4s: Primera conexión de app FALLA (todavía usa scram-sha-256)
|
||||
T+5s: Script termina, auth está configurado
|
||||
T+7s: Retry logic de app conecta exitosamente ✅
|
||||
```
|
||||
|
||||
**Solución**: El retry logic compensa esta race condition automáticamente.
|
||||
Aunque docker-compose tiene `depends_on` con `service_healthy`, el healthcheck solo verifica que PostgreSQL responde. Si necesitas máxima robustez puedes añadir un pequeño retry en la app o un `pg_isready` antes de exponer el servicio, pero con la cadena de conexión única y el pool el arranque es estable.
|
||||
|
||||
---
|
||||
|
||||
@@ -380,12 +297,11 @@ curl -X POST https://lotes.nucleoriofrio.com/api/debug/seed-database
|
||||
**Causas posibles**:
|
||||
|
||||
1. **Primera carga del sistema** (normal)
|
||||
- PostgreSQL todavía está configurando autenticación
|
||||
- Solución: Esperar ~5 segundos, el retry logic se encarga
|
||||
- Logs: `⚠️ Conexión falló, reintentando...` seguido de `✅ Conexión exitosa`
|
||||
- PostgreSQL todavía está terminando de iniciar
|
||||
- Solución: Esperar unos segundos y reintentar
|
||||
|
||||
2. **Volumen de postgres de un deploy anterior con scram-sha-256**
|
||||
- Solución: Eliminar volumen y redeploy
|
||||
2. **Volumen con contraseña diferente a la actual**
|
||||
- Solución: Eliminar volumen y redeploy para reinicializar
|
||||
```bash
|
||||
docker-compose --project-name lotes down -v
|
||||
git commit --allow-empty -m "Reinit DB" && git push
|
||||
@@ -393,7 +309,7 @@ curl -X POST https://lotes.nucleoriofrio.com/api/debug/seed-database
|
||||
|
||||
3. **Variables de entorno incorrectas**
|
||||
- Verificar en Gitea > Settings > Actions > Secrets/Variables
|
||||
- Variables requeridas: `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`
|
||||
- Variables requeridas: `NUXT_POSTGRES_URL`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`
|
||||
|
||||
### ❌ Error: "relation 'lotes' does not exist"
|
||||
|
||||
@@ -529,7 +445,6 @@ ORDER BY total DESC;
|
||||
↓
|
||||
4. Ejecuta /docker-entrypoint-initdb.d/ en orden:
|
||||
│
|
||||
├─ 00_configure_auth.sh → Cambia pg_hba.conf a md5
|
||||
├─ 01_schema.sql → Crea tablas, funciones, vistas
|
||||
└─ 02_seed.sql → Inserta 10 lotes, 7 ops, 16 rels
|
||||
↓
|
||||
@@ -600,9 +515,9 @@ docker exec -i lotes-postgres psql -U seguidor -d seguidor_lotes < backup.sql
|
||||
|
||||
| Decisión | Razón | Alternativa Descartada |
|
||||
|----------|-------|------------------------|
|
||||
| md5 en lugar de scram-sha-256 | Compatibilidad con driver pg de Node.js | Actualizar driver (requiere cambios mayores) |
|
||||
| Retry logic en pool | Compensa race condition de inicialización | depends_on custom healthcheck (complejo) |
|
||||
| Timeout de 10s | PostgreSQL puede tardar en configurar auth | 2s (insuficiente) |
|
||||
| Cadena de conexión única (`NUXT_POSTGRES_URL`) | Evita discrepancias entre host/puerto/usuario/contraseña | Variables sueltas (`POSTGRES_*`) en el runtime de la app |
|
||||
| No tocar `pg_hba.conf` (usa scram por defecto) | Menos acoplamiento a la imagen y a volúmenes existentes | Forzar md5 en init scripts |
|
||||
| Timeout de 10s en el pool | PostgreSQL puede tardar unos segundos en aceptar conexiones | Timeout bajo (más falsos negativos) |
|
||||
| DROP TABLE en reset | Workflow detecta ausencia y reinicializa | TRUNCATE (deja tablas vacías) |
|
||||
| seed ejecuta schema primero | Funciona después de borrar BD | Solo seed (falla si no hay tablas) |
|
||||
| Scripts SQL en app container | Permite endpoint de seed | Solo en postgres (no accesible desde app) |
|
||||
|
||||
@@ -1,60 +1,49 @@
|
||||
import pg from 'pg'
|
||||
import { useRuntimeConfig } from '#imports'
|
||||
|
||||
const { Pool } = pg
|
||||
|
||||
let pool: pg.Pool | null = null
|
||||
|
||||
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 variables de entorno para la configuración.
|
||||
* Usa cadena de conexión única para mantener consistencia.
|
||||
*/
|
||||
export function getPool(): pg.Pool {
|
||||
if (!pool) {
|
||||
const config = {
|
||||
user: process.env.POSTGRES_USER || 'seguidor',
|
||||
password: process.env.POSTGRES_PASSWORD || 'seguidor_password',
|
||||
database: process.env.POSTGRES_DB || 'seguidor_lotes',
|
||||
host: process.env.POSTGRES_HOST || 'postgres',
|
||||
port: parseInt(process.env.POSTGRES_PORT || '5432'),
|
||||
max: 20, // máximo de conexiones en el pool
|
||||
const connectionString = buildConnectionString()
|
||||
|
||||
pool = new Pool({
|
||||
connectionString,
|
||||
max: 10,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 10000, // Aumentado a 10s para la primera conexión
|
||||
}
|
||||
connectionTimeoutMillis: 10000,
|
||||
})
|
||||
|
||||
pool = new Pool(config)
|
||||
|
||||
// Manejo de errores del pool
|
||||
pool.on('error', (err) => {
|
||||
console.error('Error inesperado en el pool de PostgreSQL:', err)
|
||||
})
|
||||
|
||||
// Solo log en desarrollo para reducir ruido
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
pool.on('connect', () => {
|
||||
console.log('Nueva conexión establecida con PostgreSQL')
|
||||
})
|
||||
}
|
||||
|
||||
// Verificar conexión inicial con retry
|
||||
let retries = 5
|
||||
const tryConnect = async () => {
|
||||
try {
|
||||
const client = await pool!.connect()
|
||||
console.log('✅ Conexión inicial a PostgreSQL exitosa')
|
||||
client.release()
|
||||
} catch (err: any) {
|
||||
retries--
|
||||
if (retries > 0) {
|
||||
console.log(`⚠️ Conexión a PostgreSQL falló, reintentando... (${retries} intentos restantes)`)
|
||||
setTimeout(tryConnect, 2000) // Reintentar en 2 segundos
|
||||
} else {
|
||||
console.error('❌ No se pudo conectar a PostgreSQL después de varios intentos:', err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ejecutar verificación inicial en background
|
||||
tryConnect()
|
||||
}
|
||||
|
||||
return pool
|
||||
@@ -62,11 +51,6 @@ export function getPool(): pg.Pool {
|
||||
|
||||
/**
|
||||
* Ejecuta una query SQL con parámetros.
|
||||
* Wrapper seguro para evitar inyección SQL.
|
||||
*
|
||||
* @param text - Query SQL con placeholders $1, $2, etc.
|
||||
* @param params - Parámetros para la query
|
||||
* @returns Resultado de la query
|
||||
*/
|
||||
export async function query<T = any>(
|
||||
text: string,
|
||||
@@ -79,7 +63,6 @@ export async function query<T = any>(
|
||||
const result = await pool.query<T>(text, params)
|
||||
const duration = Date.now() - start
|
||||
|
||||
// Log solo en desarrollo
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log('Query ejecutada:', { text, duration: `${duration}ms`, rows: result.rowCount })
|
||||
}
|
||||
@@ -93,9 +76,6 @@ export async function query<T = any>(
|
||||
|
||||
/**
|
||||
* Obtiene un cliente del pool para ejecutar transacciones.
|
||||
* IMPORTANTE: Debes llamar a client.release() al terminar.
|
||||
*
|
||||
* @returns Cliente de PostgreSQL
|
||||
*/
|
||||
export async function getClient(): Promise<pg.PoolClient> {
|
||||
const pool = getPool()
|
||||
@@ -103,8 +83,7 @@ export async function getClient(): Promise<pg.PoolClient> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cierra el pool de conexiones.
|
||||
* Útil para tests o shutdown graceful.
|
||||
* Cierra el pool de conexiones (tests o shutdown).
|
||||
*/
|
||||
export async function closePool(): Promise<void> {
|
||||
if (pool) {
|
||||
@@ -116,9 +95,6 @@ export async function closePool(): Promise<void> {
|
||||
|
||||
/**
|
||||
* Verifica que la conexión a la base de datos esté funcionando.
|
||||
* Útil para health checks.
|
||||
*
|
||||
* @returns true si la conexión está OK, false en caso contrario
|
||||
*/
|
||||
export async function checkConnection(): Promise<boolean> {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user