const { google } = require('googleapis'); const fs = require('fs'); const path = require('path'); class GoogleDriveService { constructor() { this.auth = null; this.drive = null; this.credentialsPath = path.join(__dirname, 'google-credentials.json'); this.tokensPath = path.join(__dirname, 'google-tokens.json'); } /** * Inicializa a autenticação com credenciais salvas no Supabase ou arquivo */ async initializeAuth(credentials = null) { try { let creds = credentials; // Se não foram passadas credenciais, tenta carregar do arquivo if (!creds && fs.existsSync(this.credentialsPath)) { creds = JSON.parse(fs.readFileSync(this.credentialsPath, 'utf8')); } if (!creds) { throw new Error('Credenciais do Google não encontradas'); } // Configurar OAuth2 com scopes this.auth = new google.auth.OAuth2( creds.client_id, creds.client_secret, creds.redirect_uris[0] ); // Definir scopes para Google Drive this.scopes = [ 'https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive.readonly' ]; // Carregar tokens salvos se existirem if (fs.existsSync(this.tokensPath)) { const tokens = JSON.parse(fs.readFileSync(this.tokensPath, 'utf8')); this.auth.setCredentials(tokens); } // Inicializar Google Drive API this.drive = google.drive({ version: 'v3', auth: this.auth }); return true; } catch (error) { console.error('Erro ao inicializar Google Drive:', error.message); return false; } } /** * Gera URL de autenticação para o usuário */ getAuthUrl() { if (!this.auth) { throw new Error('Autenticação não inicializada'); } return this.auth.generateAuthUrl({ access_type: 'offline', scope: this.scopes, prompt: 'consent' }); } /** * Processa o código de autorização retornado pelo Google */ async handleAuthCallback(code) { try { const { tokens } = await this.auth.getToken(code); this.auth.setCredentials(tokens); // Salvar tokens para uso futuro fs.writeFileSync(this.tokensPath, JSON.stringify(tokens, null, 2)); console.log('✅ Autenticação Google Drive realizada com sucesso'); return tokens; } catch (error) { console.error('❌ Erro na autenticação Google Drive:', error.message); throw error; } } /** * Verifica se o usuário está autenticado */ isAuthenticated() { return this.auth && this.auth.credentials && this.auth.credentials.access_token; } /** * Cria uma pasta no Google Drive (se não existir) */ async createFolder(folderName, parentFolderId = null) { try { // Verificar se a pasta já existe const query = `name='${folderName}' and mimeType='application/vnd.google-apps.folder' and trashed=false`; const existingFolders = await this.drive.files.list({ q: parentFolderId ? `${query} and '${parentFolderId}' in parents` : query, fields: 'files(id, name)' }); if (existingFolders.data.files.length > 0) { return existingFolders.data.files[0].id; } // Criar nova pasta const folderMetadata = { name: folderName, mimeType: 'application/vnd.google-apps.folder', parents: parentFolderId ? [parentFolderId] : undefined }; const folder = await this.drive.files.create({ resource: folderMetadata, fields: 'id' }); console.log(`📁 Pasta '${folderName}' criada no Google Drive: ${folder.data.id}`); return folder.data.id; } catch (error) { console.error('Erro ao criar pasta no Google Drive:', error.message); throw error; } } /** * Faz upload de um arquivo para o Google Drive */ async uploadFile(filePath, fileName, folderId = null, mimeType = 'image/jpeg') { try { if (!this.isAuthenticated()) { throw new Error('Usuário não autenticado no Google Drive'); } const fileMetadata = { name: fileName, parents: folderId ? [folderId] : undefined }; const media = { mimeType: mimeType, body: fs.createReadStream(filePath) }; const file = await this.drive.files.create({ resource: fileMetadata, media: media, fields: 'id, name, webViewLink, webContentLink' }); // Tornar o arquivo público para visualização await this.drive.permissions.create({ fileId: file.data.id, resource: { role: 'reader', type: 'anyone' } }); console.log(`📤 Arquivo '${fileName}' enviado para Google Drive: ${file.data.id}`); return { id: file.data.id, name: file.data.name, webViewLink: file.data.webViewLink, webContentLink: file.data.webContentLink, publicUrl: `https://drive.google.com/uc?id=${file.data.id}` }; } catch (error) { console.error('Erro ao fazer upload para Google Drive:', error.message); throw error; } } /** * Faz upload de múltiplos arquivos */ async uploadMultipleFiles(files, folderId = null) { const uploadPromises = files.map(file => this.uploadFile(file.path, file.name, folderId, file.mimeType) ); try { const results = await Promise.all(uploadPromises); console.log(`📤 ${results.length} arquivos enviados para Google Drive`); return results; } catch (error) { console.error('Erro ao fazer upload múltiplo:', error.message); throw error; } } /** * Deleta um arquivo do Google Drive */ async deleteFile(fileId) { try { await this.drive.files.delete({ fileId: fileId }); console.log(`🗑️ Arquivo deletado do Google Drive: ${fileId}`); return true; } catch (error) { console.error('Erro ao deletar arquivo do Google Drive:', error.message); throw error; } } /** * Lista arquivos de uma pasta */ async listFiles(folderId = null, pageSize = 10) { try { const query = folderId ? `'${folderId}' in parents and trashed=false` : 'trashed=false'; const response = await this.drive.files.list({ q: query, pageSize: pageSize, fields: 'files(id, name, mimeType, createdTime, size, webViewLink)' }); return response.data.files; } catch (error) { console.error('Erro ao listar arquivos do Google Drive:', error.message); throw error; } } /** * Verifica se o token está próximo do vencimento */ isTokenExpiringSoon() { if (!this.auth.credentials || !this.auth.credentials.expiry_date) { return false; } const expiryTime = new Date(this.auth.credentials.expiry_date); const now = new Date(); const fiveMinutesFromNow = new Date(now.getTime() + 5 * 60 * 1000); return expiryTime <= fiveMinutesFromNow; } /** * Renova o token se necessário */ async refreshTokenIfNeeded() { if (this.isTokenExpiringSoon()) { try { const { credentials } = await this.auth.refreshAccessToken(); this.auth.setCredentials(credentials); // Salvar tokens atualizados fs.writeFileSync(this.tokensPath, JSON.stringify(credentials, null, 2)); console.log('🔄 Token Google Drive renovado automaticamente'); } catch (error) { console.error('Erro ao renovar token Google Drive:', error.message); throw error; } } } /** * Obtém informações sobre o espaço de armazenamento */ async getStorageInfo() { try { const response = await this.drive.about.get({ fields: 'storageQuota' }); const quota = response.data.storageQuota; return { limit: parseInt(quota.limit), usage: parseInt(quota.usage), usageInDrive: parseInt(quota.usageInDrive), usageInDriveTrash: parseInt(quota.usageInDriveTrash) }; } catch (error) { console.error('Erro ao obter informações de armazenamento:', error.message); throw error; } } } module.exports = GoogleDriveService;