perf: optimize backup restoration with better timeouts and logging
This commit is contained in:
@@ -98,13 +98,13 @@ export async function GET() {
|
|||||||
|
|
||||||
// POST /api/admin/backup/full - Restaurar TUDO (Dados + Mídias) a partir de um ZIP
|
// POST /api/admin/backup/full - Restaurar TUDO (Dados + Mídias) a partir de um ZIP
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
|
console.log('📦 Iniciando processo de restauração de backup...');
|
||||||
try {
|
try {
|
||||||
// Verificar se é uma restauração de emergência (banco vazio) ou se está logado
|
|
||||||
const userCount = await prisma.user.count();
|
const userCount = await prisma.user.count();
|
||||||
const user = await authenticate();
|
const user = await authenticate();
|
||||||
|
|
||||||
// Se houver usuários e não estiver logado, bloqueia
|
|
||||||
if (userCount > 0 && !user) {
|
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 });
|
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;
|
const file = formData.get('file') as File;
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
|
console.error('❌ Arquivo não enviado no FormData');
|
||||||
return NextResponse.json({ error: 'Arquivo não enviado' }, { status: 400 });
|
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());
|
const buffer = Buffer.from(await file.arrayBuffer());
|
||||||
|
console.log('🤐 Descompactando ZIP...');
|
||||||
const zip = await JSZip.loadAsync(buffer);
|
const zip = await JSZip.loadAsync(buffer);
|
||||||
|
|
||||||
// 1. Restaurar Dados do Banco (data.json)
|
// 1. Restaurar Dados do Banco (data.json)
|
||||||
const dataFile = zip.file('data.json');
|
const dataFile = zip.file('data.json');
|
||||||
if (!dataFile) {
|
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 });
|
return NextResponse.json({ error: 'Arquivo data.json não encontrado no ZIP' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataContent = await dataFile.async('string');
|
const dataContent = await dataFile.async('string');
|
||||||
const data = JSON.parse(dataContent);
|
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) => {
|
await prisma.$transaction(async (tx) => {
|
||||||
// Limpar dados atuais
|
console.log('🧹 Limpando tabelas atuais...');
|
||||||
await tx.message.deleteMany();
|
await tx.message.deleteMany();
|
||||||
await tx.project.deleteMany();
|
await tx.project.deleteMany();
|
||||||
await tx.service.deleteMany();
|
await tx.service.deleteMany();
|
||||||
await tx.pageContent.deleteMany();
|
await tx.pageContent.deleteMany();
|
||||||
await tx.user.deleteMany();
|
await tx.user.deleteMany();
|
||||||
|
|
||||||
// Restaurar Usuários
|
console.log('📝 Inserindo novos dados...');
|
||||||
if (data.users && data.users.length > 0) {
|
if (data.users?.length > 0) await tx.user.createMany({ data: data.users });
|
||||||
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) {
|
if (data.settings) {
|
||||||
await tx.settings.deleteMany();
|
await tx.settings.deleteMany();
|
||||||
await tx.settings.create({ data: data.settings });
|
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
|
// 2. Restaurar Mídias no S3/MinIO
|
||||||
|
console.log('🖼️ Iniciando restauração de mídias...');
|
||||||
await ensureBucketExists();
|
await ensureBucketExists();
|
||||||
const mediaFolder = zip.folder('media');
|
const mediaFolder = zip.folder('media');
|
||||||
if (mediaFolder) {
|
if (mediaFolder) {
|
||||||
const files = Object.keys(mediaFolder.files);
|
const files = Object.keys(mediaFolder.files).filter(name => !mediaFolder.files[name].dir);
|
||||||
for (const fileName of files) {
|
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);
|
const mediaFile = mediaFolder.file(fileName);
|
||||||
if (mediaFile && !mediaFile.dir) {
|
if (mediaFile) {
|
||||||
const content = await mediaFile.async('nodebuffer');
|
const content = await mediaFile.async('nodebuffer');
|
||||||
const pureFileName = fileName.replace('media/', '');
|
const pureFileName = fileName.replace('media/', '');
|
||||||
|
|
||||||
@@ -184,13 +180,17 @@ export async function POST(req: NextRequest) {
|
|||||||
Key: pureFileName,
|
Key: pureFileName,
|
||||||
Body: content
|
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' });
|
return NextResponse.json({ message: 'Restauração completa concluída com sucesso' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro na restauração completa:', error);
|
console.error('💥 Erro fatal na restauração:', error);
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
error: 'Erro ao processar restauração',
|
error: 'Erro ao processar restauração',
|
||||||
details: error instanceof Error ? error.message : String(error)
|
details: error instanceof Error ? error.message : String(error)
|
||||||
|
|||||||
Reference in New Issue
Block a user