chore: sincroniza projeto para gitea

This commit is contained in:
Tiago
2025-11-29 21:31:52 -03:00
parent 33d8645eb4
commit 7e7a0f8867
129 changed files with 24999 additions and 6757 deletions

View File

@@ -1,13 +0,0 @@
{
"web": {
"client_id": "SEU_CLIENT_ID_AQUI.apps.googleusercontent.com",
"project_id": "seu-projeto-id",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "SUA_CLIENT_SECRET_AQUI",
"redirect_uris": [
"http://localhost:5000/auth/google/callback"
]
}
}

View File

@@ -1,295 +0,0 @@
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;

View File

@@ -1,384 +0,0 @@
const { google } = require('googleapis');
const fs = require('fs');
const path = require('path');
class GoogleSheetsService {
constructor() {
this.auth = null;
this.sheets = null;
this.drive = null;
}
// Inicializar autenticação OAuth 2.0
async initializeAuth(credentialsData = null) {
try {
let credentials;
if (credentialsData) {
// Usar credenciais fornecidas via parâmetro
credentials = credentialsData;
} else {
// Tentar carregar do arquivo (fallback)
const credentialsPath = path.join(__dirname, 'google-credentials.json');
if (!fs.existsSync(credentialsPath)) {
throw new Error('Credenciais do Google não configuradas. Configure na página de Configurações.');
}
credentials = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'));
}
const { client_id, client_secret, redirect_uris } = credentials.web || credentials;
this.auth = new google.auth.OAuth2(
client_id,
client_secret,
redirect_uris[0]
);
this.sheets = google.sheets({ version: 'v4', auth: this.auth });
this.drive = google.drive({ version: 'v3', auth: this.auth });
return true;
} catch (error) {
console.error('Erro ao inicializar autenticação Google:', error.message);
return false;
}
}
// Gerar URL de autorização
getAuthUrl() {
const scopes = [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive.file'
];
return this.auth.generateAuthUrl({
access_type: 'offline',
scope: scopes,
prompt: 'consent'
});
}
// Processar código de autorização
async handleAuthCallback(code) {
try {
const { tokens } = await this.auth.getToken(code);
this.auth.setCredentials(tokens);
// Salvar tokens para uso futuro
const tokensPath = path.join(__dirname, 'google-tokens.json');
fs.writeFileSync(tokensPath, JSON.stringify(tokens, null, 2));
return tokens;
} catch (error) {
console.error('Erro ao processar callback de autorização:', error);
throw error;
}
}
// Carregar tokens salvos
async loadSavedTokens() {
try {
const tokensPath = path.join(__dirname, 'google-tokens.json');
if (fs.existsSync(tokensPath)) {
const tokens = JSON.parse(fs.readFileSync(tokensPath, 'utf8'));
this.auth.setCredentials(tokens);
return true;
}
return false;
} catch (error) {
console.error('Erro ao carregar tokens salvos:', error);
return false;
}
}
// Criar ou atualizar planilha persistente
async createOrUpdatePersistentSpreadsheet(spreadsheetId = null, title = 'Liberi Kids - Sistema de Estoque') {
try {
if (spreadsheetId) {
// Tentar verificar se planilha existente ainda existe
try {
const existingSheet = await this.sheets.spreadsheets.get({
spreadsheetId: spreadsheetId
});
console.log('Planilha existente encontrada, usando a mesma...');
return {
spreadsheetId,
url: `https://docs.google.com/spreadsheets/d/${spreadsheetId}/edit`,
exists: true
};
} catch (error) {
console.log('Planilha não encontrada ou inacessível, criando nova...');
}
}
// Criar nova planilha
const spreadsheet = await this.sheets.spreadsheets.create({
resource: {
properties: {
title: title
},
sheets: [
{
properties: {
sheetId: 0,
title: 'Produtos',
gridProperties: {
rowCount: 1000,
columnCount: 20
}
}
},
{
properties: {
sheetId: 1,
title: 'Vendas',
gridProperties: {
rowCount: 1000,
columnCount: 15
}
}
}
]
}
});
const newSpreadsheetId = spreadsheet.data.spreadsheetId;
// Aguardar um pouco para garantir que a planilha foi criada
await new Promise(resolve => setTimeout(resolve, 2000));
// Configurar cabeçalhos
await this.setupHeaders(newSpreadsheetId);
return {
spreadsheetId: newSpreadsheetId,
url: `https://docs.google.com/spreadsheets/d/${newSpreadsheetId}/edit`
};
} catch (error) {
console.error('Erro ao criar planilha:', error);
throw error;
}
}
// Configurar cabeçalhos das abas
async setupHeaders(spreadsheetId) {
try {
const produtosHeaders = [
'ID da Roupa',
'Nome do Produto',
'Fornecedor',
'Tamanho',
'Estação',
'Gênero',
'Valor da Compra',
'Valor da Venda',
'Data da Compra',
'Data da Venda',
'Estoque Atual',
'Marca'
];
const vendasHeaders = [
'ID da Venda',
'Cliente',
'Data da Venda',
'Tipo de Pagamento',
'Valor Total',
'Desconto',
'Valor Final',
'Status',
'Observações',
'Produtos Vendidos'
];
// Configurar cabeçalhos da aba Produtos
await this.sheets.spreadsheets.values.update({
spreadsheetId,
range: 'Produtos!A1:L1',
valueInputOption: 'USER_ENTERED',
resource: {
values: [produtosHeaders]
}
});
// Configurar cabeçalhos da aba Vendas
await this.sheets.spreadsheets.values.update({
spreadsheetId,
range: 'Vendas!A1:J1',
valueInputOption: 'USER_ENTERED',
resource: {
values: [vendasHeaders]
}
});
// Formatar cabeçalhos (opcional, pode falhar sem quebrar)
try {
const requests = [
{
repeatCell: {
range: {
sheetId: 0,
startRowIndex: 0,
endRowIndex: 1,
startColumnIndex: 0,
endColumnIndex: produtosHeaders.length
},
cell: {
userEnteredFormat: {
backgroundColor: { red: 0.2, green: 0.6, blue: 1.0 },
textFormat: { bold: true, foregroundColor: { red: 1, green: 1, blue: 1 } }
}
},
fields: 'userEnteredFormat'
}
},
{
repeatCell: {
range: {
sheetId: 1,
startRowIndex: 0,
endRowIndex: 1,
startColumnIndex: 0,
endColumnIndex: vendasHeaders.length
},
cell: {
userEnteredFormat: {
backgroundColor: { red: 0.2, green: 0.8, blue: 0.2 },
textFormat: { bold: true, foregroundColor: { red: 1, green: 1, blue: 1 } }
}
},
fields: 'userEnteredFormat'
}
}
];
await this.sheets.spreadsheets.batchUpdate({
spreadsheetId,
resource: { requests }
});
} catch (formatError) {
console.log('Aviso: Não foi possível formatar cabeçalhos, mas planilha foi criada:', formatError.message);
}
} catch (error) {
console.error('Erro ao configurar cabeçalhos:', error);
throw error;
}
}
// Exportar dados de produtos
async exportProdutos(spreadsheetId, produtos) {
try {
const values = produtos.map(produto => [
produto.id || '',
produto.nome || '',
produto.fornecedor_nome || '',
produto.tamanho || '',
produto.estacao || '',
produto.genero || '',
produto.valor_compra || 0,
produto.valor_revenda || 0,
produto.created_at ? new Date(produto.created_at).toLocaleDateString('pt-BR') : '',
'', // Data da venda será preenchida quando houver venda
produto.quantidade_total || 0,
produto.marca || ''
]);
await this.sheets.spreadsheets.values.update({
spreadsheetId,
range: 'Produtos!A2:L' + (values.length + 1),
valueInputOption: 'USER_ENTERED',
resource: { values }
});
return { success: true, rowsUpdated: values.length };
} catch (error) {
console.error('Erro ao exportar produtos:', error);
throw error;
}
}
// Exportar dados de vendas
async exportVendas(spreadsheetId, vendas) {
try {
const values = vendas.map(venda => [
venda.id || '',
venda.cliente_nome || 'Cliente não informado',
venda.data_venda ? new Date(venda.data_venda).toLocaleDateString('pt-BR') : '',
venda.tipo_pagamento || '',
venda.valor_total || 0,
venda.desconto || 0,
venda.valor_final || 0,
venda.status || 'Concluída',
venda.observacoes || '',
venda.produtos ? venda.produtos.map(p => `${p.nome} (${p.quantidade}x)`).join(', ') : ''
]);
await this.sheets.spreadsheets.values.update({
spreadsheetId,
range: 'Vendas!A2:J' + (values.length + 1),
valueInputOption: 'USER_ENTERED',
resource: { values }
});
return { success: true, rowsUpdated: values.length };
} catch (error) {
console.error('Erro ao exportar vendas:', error);
throw error;
}
}
// Verificar se está autenticado
isAuthenticated() {
return this.auth && this.auth.credentials && this.auth.credentials.access_token;
}
// Verificar se o token está próximo do vencimento
isTokenExpiringSoon() {
if (!this.auth || !this.auth.credentials || !this.auth.credentials.expiry_date) {
return false;
}
const now = new Date().getTime();
const expiry = this.auth.credentials.expiry_date;
const timeUntilExpiry = expiry - now;
// Considera que está expirando se faltam menos de 5 minutos
return timeUntilExpiry < 5 * 60 * 1000;
}
// Renovar token automaticamente se necessário
async refreshTokenIfNeeded() {
if (this.isTokenExpiringSoon()) {
try {
const { credentials } = await this.auth.refreshAccessToken();
this.auth.setCredentials(credentials);
// Salvar tokens atualizados
const tokensPath = path.join(__dirname, 'google-tokens.json');
fs.writeFileSync(tokensPath, JSON.stringify(credentials, null, 2));
console.log('Token Google renovado automaticamente');
return true;
} catch (error) {
console.error('Erro ao renovar token Google:', error);
return false;
}
}
return true;
}
// Resetar o serviço (limpar autenticação)
reset() {
this.auth = null;
this.sheets = null;
this.drive = null;
console.log('Serviço Google Sheets resetado');
}
}
module.exports = new GoogleSheetsService();

View File

@@ -1,7 +0,0 @@
{
"access_token": "ya29.a0AQQ_BDTf90ixT9y_x6M_zK1qmBhM88FscZ9xmiUDv0acKlSDkge5lxiUEYIi3yxFFiwxv3Az9P-PnV1wWnfEscTcht6z5uvp3LT0uzS3uFWndQ3HWN3X-YVxnvpyBD7P3gCc6YGHz0zHeN1QtyXTAIM9iW0hwLf8R5d88b_EI2DftLLZ7F68jo_MOQwN9pwUkmb6DWlaaCgYKAVoSARcSFQHGX2Milg4J_31KZx9930lNX7B8Hg0207",
"scope": "https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/spreadsheets",
"token_type": "Bearer",
"expiry_date": 1759955985106,
"refresh_token": "1//0hZ-EhpsmoFVGCgYIARAAGBESNwF-L9IrIlcr7zua7Q-wDP3Eg6-hje3BoKCDYdaMVWJMqa3VGDx57VgI0O40iu5hYtsbIsg_bTc"
}

View File

@@ -57,6 +57,39 @@ class MercadoPagoService {
}
}
async createPixPayment(paymentData) {
try {
console.log('🏦 Criando PIX Payment:', paymentData);
console.log('🔑 Access Token:', process.env.MERCADOPAGO_ACCESS_TOKEN ? 'Configurado' : 'NÃO CONFIGURADO');
if (!this.payment) {
throw new Error('MercadoPago não configurado. Verifique o MERCADOPAGO_ACCESS_TOKEN no .env');
}
const payment_data = {
transaction_amount: parseFloat(paymentData.transaction_amount),
description: paymentData.description,
payment_method_id: paymentData.payment_method_id || 'pix',
payer: paymentData.payer,
date_of_expiration: new Date(Date.now() + 30 * 60 * 1000).toISOString() // 30 minutos
};
if (paymentData.external_reference) {
payment_data.external_reference = paymentData.external_reference.toString();
}
console.log('📤 Enviando dados para Mercado Pago:', JSON.stringify(payment_data, null, 2));
const payment = await this.payment.create({ body: payment_data });
console.log('✅ PIX criado com sucesso:', payment.id);
return payment;
} catch (error) {
console.error('❌ Erro ao criar PIX:', error);
throw error;
}
}
async consultarPagamento(payment_id) {
try {
const payment = await this.payment.get({ id: payment_id });

View File

@@ -1,7 +1,18 @@
const { createClient } = require('@supabase/supabase-js');
require('dotenv').config();
const supabaseUrl = 'https://xyqmlesqdqybiyjofysb.supabase.co';
const supabaseKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh5cW1sZXNxZHF5Yml5am9meXNiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTk2NjEzMzcsImV4cCI6MjA3NTIzNzMzN30.uXPONkstd_xXbzX1ZwlB9gK05zjwQL0Ymj94_3NnOGE';
const supabaseUrl = process.env.SUPABASE_URL;
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
const anonKey = process.env.SUPABASE_ANON_KEY;
const supabaseKey = serviceRoleKey || anonKey;
if (!supabaseUrl) {
throw new Error('Variável SUPABASE_URL não configurada. Defina-a no arquivo .env.');
}
if (!supabaseKey) {
throw new Error('Defina SUPABASE_SERVICE_ROLE_KEY (recomendado) ou SUPABASE_ANON_KEY no arquivo .env.');
}
const supabase = createClient(supabaseUrl, supabaseKey);