// ===== FUNÇÕES PARA UPLOAD DIRETO AO WASABI ===== /** * Função para fazer upload direto de arquivo para Wasabi * @param {File} file - Arquivo a ser enviado * @param {Function} progressCallback - Callback para atualizar progresso * @returns {Promise} - Resultado do upload com URL */ async function uploadToWasabi(file, progressCallback = null) { console.log('📤 [WASABI-UPLOAD] Iniciando upload direto para Wasabi'); console.log('📤 [WASABI-UPLOAD] Arquivo:', file.name); console.log('📤 [WASABI-UPLOAD] Tamanho:', (file.size / (1024 * 1024)).toFixed(2), 'MB'); try { // Obter URL pré-assinada do servidor console.log('🔐 [WASABI-UPLOAD] Obtendo URL pré-assinada do servidor...'); const presignedResponse = await fetch('/api/wasabi/presigned-url', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName: file.name, fileType: file.type, folder: 'transcriptions' }) }); if (!presignedResponse.ok) { const errorData = await presignedResponse.json(); throw new Error(errorData.erro || 'Erro ao obter URL pré-assinada do Wasabi'); } const presignedData = await presignedResponse.json(); console.log('✅ [WASABI-UPLOAD] URL pré-assinada obtida com sucesso'); console.log('✅ [WASABI-UPLOAD] Key:', presignedData.key); console.log('✅ [WASABI-UPLOAD] Public URL:', presignedData.publicUrl); // Fazer upload direto para Wasabi usando a URL pré-assinada console.log('📤 [WASABI-UPLOAD] Iniciando upload para Wasabi...'); return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); let heartbeatInterval = null; let lastProgressTime = Date.now(); // Heartbeat para manter conexão viva durante uploads grandes const startHeartbeat = () => { heartbeatInterval = setInterval(() => { const now = Date.now(); const timeSinceLastProgress = now - lastProgressTime; // Se não houve progresso por mais de 30 segundos, enviar heartbeat if (timeSinceLastProgress > 30000) { console.log('💓 [WASABI-UPLOAD] Enviando heartbeat...'); // Fazer uma pequena requisição para manter conexão viva fetch('/api/wasabi/presigned-url', { method: 'HEAD' }).catch(() => {}); lastProgressTime = now; } }, 15000); // Verificar a cada 15 segundos }; const stopHeartbeat = () => { if (heartbeatInterval) { clearInterval(heartbeatInterval); heartbeatInterval = null; } }; // Configurar progresso xhr.upload.addEventListener('progress', (event) => { lastProgressTime = Date.now(); if (event.lengthComputable && progressCallback) { const percentComplete = Math.round((event.loaded / event.total) * 100); console.log(`📤 [WASABI-UPLOAD] Progresso: ${percentComplete}%`); progressCallback(percentComplete); } }); // Configurar resposta xhr.addEventListener('load', () => { stopHeartbeat(); if (xhr.status === 200) { console.log('✅ [WASABI-UPLOAD] Upload concluído com sucesso!'); console.log('✅ [WASABI-UPLOAD] URL pública:', presignedData.publicUrl); console.log('✅ [WASABI-UPLOAD] Key:', presignedData.key); // Retornar dados no formato esperado resolve({ secure_url: presignedData.publicUrl, public_id: presignedData.key, original_filename: presignedData.originalName, bytes: file.size, format: file.name.split('.').pop(), resource_type: 'video' }); } else { console.error('❌ [WASABI-UPLOAD] Erro HTTP:', xhr.status, xhr.statusText); console.error('❌ [WASABI-UPLOAD] Resposta:', xhr.responseText); reject(new Error(`Erro no upload: ${xhr.status} ${xhr.statusText}`)); } }); // Configurar erro xhr.addEventListener('error', () => { stopHeartbeat(); console.error('❌ [WASABI-UPLOAD] Erro de rede durante upload'); reject(new Error('Erro de rede durante upload para Wasabi')); }); // Configurar timeout xhr.addEventListener('timeout', () => { stopHeartbeat(); console.error('❌ [WASABI-UPLOAD] Timeout durante upload'); reject(new Error('Timeout durante upload para Wasabi')); }); // Configurar e enviar requisição xhr.open('PUT', presignedData.presignedUrl); // Definir Content-Type xhr.setRequestHeader('Content-Type', file.type); // Timeout mais longo para arquivos grandes (2 horas) const timeoutDuration = Math.max(7200000, file.size / 1024 / 1024 * 3000); // 3 segundos por MB, mínimo 2 horas xhr.timeout = timeoutDuration; console.log(`⏱️ [WASABI-UPLOAD] Timeout configurado para: ${Math.round(timeoutDuration/1000)}s`); // Iniciar heartbeat para arquivos grandes (>50MB) if (file.size > 50 * 1024 * 1024) { console.log('💓 [WASABI-UPLOAD] Iniciando heartbeat para arquivo grande'); startHeartbeat(); } xhr.send(file); }); } catch (error) { console.error('❌ [WASABI-UPLOAD] Erro geral:', error); throw new Error(`Falha no upload para Wasabi: ${error.message}`); } } /** * Função para deletar arquivo do Wasabi * @param {String} key - Key do arquivo no Wasabi * @returns {Promise} */ async function deleteFromWasabi(key) { if (!key) { console.warn('🗑️ [WASABI-DELETE] Key não fornecida'); return; } try { console.log('🗑️ [WASABI-DELETE] Deletando arquivo:', key); const response = await fetch('/api/wasabi/delete', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: key }) }); if (response.ok) { console.log('✅ [WASABI-DELETE] Arquivo deletado com sucesso'); } else { console.warn('⚠️ [WASABI-DELETE] Erro ao deletar (não crítico):', response.status); } } catch (error) { console.warn('⚠️ [WASABI-DELETE] Erro ao deletar (não crítico):', error.message); // Não propagar erro de limpeza } } /** * Função para verificar se um arquivo é muito grande para Cloudinary * @param {File} file - Arquivo a ser verificado * @returns {boolean} - True se o arquivo é muito grande para Cloudinary */ function isFileTooLargeForCloudinary(file) { const CLOUDINARY_LIMIT = 100 * 1024 * 1024; // 100MB return file.size > CLOUDINARY_LIMIT; } console.log('✅ [WASABI-UPLOAD] Módulo de upload Wasabi carregado');