296 lines
8.1 KiB
JavaScript
296 lines
8.1 KiB
JavaScript
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;
|