/** * ⚠️ ⚠️ ⚠️ 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) } } } })