From 906e0d9456dc2d40c18e82d571b4986bd2256a1c Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 9 Mar 2026 16:36:13 -0300 Subject: [PATCH] perf: optimize backup restoration with better timeouts and logging --- .../src/app/api/admin/backup/full/route.ts | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/frontend/src/app/api/admin/backup/full/route.ts b/frontend/src/app/api/admin/backup/full/route.ts index e704997..5a61d0b 100644 --- a/frontend/src/app/api/admin/backup/full/route.ts +++ b/frontend/src/app/api/admin/backup/full/route.ts @@ -98,13 +98,13 @@ export async function GET() { // POST /api/admin/backup/full - Restaurar TUDO (Dados + Mídias) a partir de um ZIP export async function POST(req: NextRequest) { + console.log('📦 Iniciando processo de restauração de backup...'); try { - // Verificar se é uma restauração de emergência (banco vazio) ou se está logado const userCount = await prisma.user.count(); const user = await authenticate(); - // Se houver usuários e não estiver logado, bloqueia if (userCount > 0 && !user) { + console.warn('⚠️ Tentativa de restauração não autorizada.'); return NextResponse.json({ error: 'Não autorizado. Use a página de emergência apenas em instalações novas.' }, { status: 401 }); } @@ -112,70 +112,66 @@ export async function POST(req: NextRequest) { const file = formData.get('file') as File; if (!file) { + console.error('❌ Arquivo não enviado no FormData'); return NextResponse.json({ error: 'Arquivo não enviado' }, { status: 400 }); } + console.log(`📂 Arquivo recebido: ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)`); + const buffer = Buffer.from(await file.arrayBuffer()); + console.log('🤐 Descompactando ZIP...'); const zip = await JSZip.loadAsync(buffer); // 1. Restaurar Dados do Banco (data.json) const dataFile = zip.file('data.json'); if (!dataFile) { + console.error('❌ data.json não encontrado dentro do ZIP'); return NextResponse.json({ error: 'Arquivo data.json não encontrado no ZIP' }, { status: 400 }); } const dataContent = await dataFile.async('string'); const data = JSON.parse(dataContent); + console.log('✅ Dados do banco lidos com sucesso.'); - // Importação atômica + // Importação atômica com timeout estendido para 120 segundos + console.log('🔄 Iniciando transação no banco de dados...'); await prisma.$transaction(async (tx) => { - // Limpar dados atuais + console.log('🧹 Limpando tabelas atuais...'); await tx.message.deleteMany(); await tx.project.deleteMany(); await tx.service.deleteMany(); await tx.pageContent.deleteMany(); await tx.user.deleteMany(); - // Restaurar Usuários - if (data.users && data.users.length > 0) { - await tx.user.createMany({ data: data.users }); - } + console.log('📝 Inserindo novos dados...'); + if (data.users?.length > 0) await tx.user.createMany({ data: data.users }); + if (data.projects?.length > 0) await tx.project.createMany({ data: data.projects }); + if (data.services?.length > 0) await tx.service.createMany({ data: data.services }); + if (data.pageContents?.length > 0) await tx.pageContent.createMany({ data: data.pageContents }); + if (data.messages?.length > 0) await tx.message.createMany({ data: data.messages }); - // Restaurar Projetos - if (data.projects && data.projects.length > 0) { - await tx.project.createMany({ data: data.projects }); - } - - // Restaurar Serviços - if (data.services && data.services.length > 0) { - await tx.service.createMany({ data: data.services }); - } - - // Restaurar Conteúdo de Páginas - if (data.pageContents && data.pageContents.length > 0) { - await tx.pageContent.createMany({ data: data.pageContents }); - } - - // Restaurar Mensagens - if (data.messages && data.messages.length > 0) { - await tx.message.createMany({ data: data.messages }); - } - - // Restaurar Configurações if (data.settings) { await tx.settings.deleteMany(); await tx.settings.create({ data: data.settings }); } + }, { + maxWait: 20000, + timeout: 120000, // 2 minutos para processar os dados }); + console.log('✅ Banco de dados restaurado.'); // 2. Restaurar Mídias no S3/MinIO + console.log('🖼️ Iniciando restauração de mídias...'); await ensureBucketExists(); const mediaFolder = zip.folder('media'); if (mediaFolder) { - const files = Object.keys(mediaFolder.files); - for (const fileName of files) { + const files = Object.keys(mediaFolder.files).filter(name => !mediaFolder.files[name].dir); + console.log(`📸 Total de arquivos de mídia encontrados: ${files.length}`); + + for (let i = 0; i < files.length; i++) { + const fileName = files[i]; const mediaFile = mediaFolder.file(fileName); - if (mediaFile && !mediaFile.dir) { + if (mediaFile) { const content = await mediaFile.async('nodebuffer'); const pureFileName = fileName.replace('media/', ''); @@ -184,13 +180,17 @@ export async function POST(req: NextRequest) { Key: pureFileName, Body: content })); + + if ((i + 1) % 10 === 0 || i === files.length - 1) { + console.log(`📤 Progresso mídias: ${i + 1}/${files.length}`); + } } } } - + console.log('✨ Restauração concluída com sucesso!'); return NextResponse.json({ message: 'Restauração completa concluída com sucesso' }); } catch (error) { - console.error('Erro na restauração completa:', error); + console.error('💥 Erro fatal na restauração:', error); return NextResponse.json({ error: 'Erro ao processar restauração', details: error instanceof Error ? error.message : String(error)