Files
seguidorDeLotes/nuxt4/server/api/debug/import-database.post.ts
josedario87 730d2ce299
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m5s
Mejorar compatibilidad de backups SQL
Resuelve problemas de compatibilidad entre versiones de PostgreSQL
al exportar e importar backups.

Cambios en export-database.post.ts:
- Agregar flags adicionales a pg_dump para mayor portabilidad:
  --no-tablespaces: evita referencias a tablespaces
  --no-security-labels: evita security labels
  --no-synchronized-snapshots: evita snapshots sincronizados
- Esto genera SQL más compatible entre diferentes instalaciones

Cambios en import-database.post.ts:
- Limpiar SQL antes de importar, removiendo comandos incompatibles:
  * SET transaction_timeout (no existe en todas las versiones)
  * SET idle_in_transaction_session_timeout
  * SET lock_timeout
  * \unrestrict (comando no reconocido)
- Agregar -q y -v ON_ERROR_STOP=1 a psql
- Mejorar detección de errores: solo fallar en ERROR/FATAL reales
- Reportar tamaño original y limpio del archivo

Con estos cambios, los backups exportados desde cualquier versión
de PostgreSQL se pueden importar correctamente sin errores de
compatibilidad.
2025-11-22 04:30:55 -06:00

189 lines
6.4 KiB
TypeScript

/**
* ⚠️ ⚠️ ⚠️ ENDPOINT DE DEBUG - TEMPORAL ⚠️ ⚠️ ⚠️
*
* POST /api/debug/import-database
*
* IMPORTA UN BACKUP SQL COMPLETO (reemplaza todo lo existente)
*
* ⚠️ NO ELIMINAR SIN CONSULTAR A DARIO/DRAGANEL/NUCLEO000 ⚠️
*
* Este endpoint fue creado para desarrollo y debugging.
* Antes de eliminarlo, preguntar si todavía es necesario.
*/
import { exec } from 'child_process'
import { promisify } from 'util'
import { writeFile, unlink } from 'fs/promises'
import { join } from 'path'
const execAsync = promisify(exec)
export default defineEventHandler(async (event) => {
let tempFilePath: string | null = null
try {
console.log('📥 IMPORT DATABASE - Importando backup SQL...')
// Leer el archivo del request
const formData = await readMultipartFormData(event)
if (!formData || formData.length === 0) {
throw createError({
statusCode: 400,
statusMessage: 'No se recibió ningún archivo',
data: { message: 'Debes enviar un archivo .sql en el campo "file"' },
})
}
const fileData = formData.find(item => item.name === 'file')
if (!fileData) {
throw createError({
statusCode: 400,
statusMessage: 'Campo "file" no encontrado',
data: { message: 'El archivo debe enviarse en el campo "file"' },
})
}
const originalSqlContent = fileData.data.toString('utf-8')
const originalFileSize = (originalSqlContent.length / 1024).toFixed(2)
console.log(` - Archivo recibido: ${fileData.filename || 'sin nombre'}`)
console.log(` - Tamaño original: ${originalFileSize} KB`)
if (!originalSqlContent.trim()) {
throw createError({
statusCode: 400,
statusMessage: 'Archivo vacío',
data: { message: 'El archivo SQL está vacío' },
})
}
// Limpiar el SQL de comandos problemáticos o incompatibles
console.log(' - Limpiando SQL de comandos incompatibles...')
const sqlLines = originalSqlContent.split('\n')
const cleanedLines = sqlLines.filter(line => {
const trimmed = line.trim()
// Remover líneas problemáticas conocidas
if (trimmed.startsWith('SET transaction_timeout')) return false
if (trimmed.startsWith('SET idle_in_transaction_session_timeout')) return false
if (trimmed.startsWith('SET lock_timeout')) return false
if (trimmed.startsWith('\\unrestrict')) return false
return true
})
const sqlContent = cleanedLines.join('\n')
const cleanedFileSize = (sqlContent.length / 1024).toFixed(2)
console.log(` - Tamaño después de limpieza: ${cleanedFileSize} KB`)
// Guardar el archivo temporalmente
const timestamp = Date.now()
tempFilePath = join('/tmp', `import-${timestamp}.sql`)
await writeFile(tempFilePath, sqlContent, 'utf-8')
console.log(` - Archivo guardado temporalmente en: ${tempFilePath}`)
// Obtener credenciales de la base de datos
const config = useRuntimeConfig()
let host: string
let port: string
let database: string
let user: string
let password: string
if (config.postgresUrl) {
// Parsear la URL de conexión
const url = new URL(config.postgresUrl)
host = url.hostname
port = url.port || '5432'
database = url.pathname.slice(1)
user = url.username
password = url.password
} else {
// Fallback a variables de entorno
const defaultHost = process.env.APP_NAME ? `${process.env.APP_NAME}-postgres` : 'postgres'
host = process.env.POSTGRES_HOST || defaultHost
port = process.env.POSTGRES_PORT || '5432'
database = process.env.POSTGRES_DB || 'seguidor_lotes'
user = process.env.POSTGRES_USER || 'seguidor'
password = process.env.POSTGRES_PASSWORD || 'seguidor_password'
}
console.log(` - Host: ${host}`)
console.log(` - Puerto: ${port}`)
console.log(` - Base de datos: ${database}`)
console.log(` - Usuario: ${user}`)
// Ejecutar psql para importar el backup
console.log(' - Ejecutando psql para importar el backup...')
console.log(' - ADVERTENCIA: Esto eliminará y recreará todas las tablas')
// Opciones de psql:
// -q: quiet mode (menos mensajes)
// -v ON_ERROR_STOP=1: detener en el primer error crítico
const command = `PGPASSWORD="${password}" psql -h ${host} -p ${port} -U ${user} -d ${database} -q -v ON_ERROR_STOP=1 -f ${tempFilePath}`
const { stdout, stderr } = await execAsync(command, {
maxBuffer: 50 * 1024 * 1024, // 50MB buffer
})
// psql envía algunos mensajes normales a stderr
// Solo fallar si hay errores críticos (ERROR en mayúsculas o 'error:' en minúsculas)
if (stderr) {
const hasError = stderr.includes('ERROR:') || stderr.includes('error:') || stderr.includes('FATAL:')
if (hasError) {
console.error('⚠️ Errores durante la importación:', stderr)
throw new Error(`Error ejecutando psql: ${stderr}`)
} else {
// Son solo warnings o notices, no son críticos
console.log(' - Mensajes de psql (no críticos):', stderr)
}
}
console.log('✅ Backup importado exitosamente')
return {
success: true,
message: 'Backup importado exitosamente. La base de datos ha sido restaurada.',
original_size: originalFileSize,
cleaned_size: cleanedFileSize,
filename: fileData.filename || 'sin nombre',
}
} catch (error: any) {
console.error('❌ Error importando backup:', error)
// Si el error es porque psql no está disponible
if (error.message?.includes('psql') && error.message?.includes('not found')) {
throw createError({
statusCode: 500,
statusMessage: 'psql no está disponible',
data: {
message: 'El comando psql no está disponible en el servidor. Asegúrate de que PostgreSQL client tools estén instalados.',
},
})
}
// Si el error ya es un createError, reusarlo
if (error.statusCode) {
throw error
}
throw createError({
statusCode: 500,
statusMessage: 'Error importando backup',
data: { message: error.message },
})
} finally {
// Limpiar el archivo temporal
if (tempFilePath) {
try {
await unlink(tempFilePath)
console.log(` - Archivo temporal eliminado: ${tempFilePath}`)
} catch (unlinkError) {
console.warn('⚠️ No se pudo eliminar el archivo temporal:', unlinkError)
}
}
}
})