From 0b2748a579684fffdd03d8b07dff5646ce09705d Mon Sep 17 00:00:00 2001 From: Ayron Santoss Date: Wed, 1 Jul 2026 16:30:10 +0000 Subject: [PATCH] fix: criar tabela MENSAGENS_CLIENTES (migracao #25) e corrigir insert com MSC_CODIGO_ID - add: migration #25 cria tabela MENSAGENS_CLIENTES + generator - fix: insert agora usa db.nextId para gerar MSC_CODIGO_ID - fix: adiciona GEN_MENSAGENS_CLIENTES ao whitelist no database.js --- scripts/migrationRunner.js | 25 +++++++++ src/config/connections.js | 12 +++++ src/controllers/chatController.js | 7 +-- src/controllers/evolutionController.js | 72 ++++++++++++++++++++++++-- src/database.js | 1 + src/public/settings.html | 50 +++++++++++++++++- src/routes/configRoutes.js | 1 + 7 files changed, 161 insertions(+), 7 deletions(-) diff --git a/scripts/migrationRunner.js b/scripts/migrationRunner.js index 9bc02ea..6922a0e 100644 --- a/scripts/migrationRunner.js +++ b/scripts/migrationRunner.js @@ -137,6 +137,31 @@ const MIGRACOES = [ 'ALTER TABLE CHATC2_CONVERSAS ADD CON_RESOLUCAO VARCHAR(4000)', ], }, + + // ---------------------------------------------------------- + // MIGRAÇÃO 25: Tabela MENSAGENS_CLIENTES + // ---------------------------------------------------------- + { + id: 25, + descricao: 'Criar tabela MENSAGENS_CLIENTES para salvar resolução do atendimento', + sql: [ + `CREATE TABLE MENSAGENS_CLIENTES ( + MSC_CODIGO_ID INTEGER NOT NULL PRIMARY KEY, + MSC_CLIENTE_ID INTEGER, + MSC_USUARIO_ID INTEGER, + MSC_DT_MENSAGEM TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + MSC_AVISO CHAR(1) DEFAULT 'N', + MSC_MENSAGEM VARCHAR(4000) + )`, + 'CREATE GENERATOR GEN_MENSAGENS_CLIENTES', + ], + async after(alias) { + try { + const r = await db.query(alias, 'SELECT COALESCE(MAX(MSC_CODIGO_ID),0)+1 AS V FROM MENSAGENS_CLIENTES'); + await db.execute(alias, 'SET GENERATOR GEN_MENSAGENS_CLIENTES TO ' + (r[0]?.V || 1)); + } catch (_) {} + }, + }, ]; // ============================================================ diff --git a/src/config/connections.js b/src/config/connections.js index 444fb97..084db9f 100644 --- a/src/config/connections.js +++ b/src/config/connections.js @@ -50,6 +50,18 @@ const databases = { pageSize: 4096, wireCrypt: 1, }, + novo: { + driver: 'firebird', + host: '177.70.23.53', + port: 3050, + database: 'sigef_web_novo', + user: process.env.DB_USER || 'SYSDBA', + password: process.env.DB_PASSWORD || 'masterkey', + encoding: process.env.DB_ENCODING || 'UTF-8', + lowercase_keys: false, + pageSize: 4096, + wireCrypt: 1, + }, }; function salvarCustomDatabases() { diff --git a/src/controllers/chatController.js b/src/controllers/chatController.js index a1528ff..426858f 100644 --- a/src/controllers/chatController.js +++ b/src/controllers/chatController.js @@ -736,10 +736,11 @@ class ChatController { // Salva resolução na tabela MENSAGENS_CLIENTES como aviso (se configurado) if (isS(fl.CFE_RESOLUCAO_SALVAR_MSC) && resolucao && c.CON_CLIENTE_ID) { try { + const mscId = await db.nextId(alias, 'GEN_MENSAGENS_CLIENTES'); await db.execute(alias, - `INSERT INTO MENSAGENS_CLIENTES (MSC_CLIENTE_ID, MSC_USUARIO_ID, MSC_DT_MENSAGEM, MSC_AVISO, MSC_MENSAGEM) - VALUES (?, ?, CURRENT_TIMESTAMP, 'S', ?)`, - [c.CON_CLIENTE_ID, c.CON_USUARIO_ID || null, resolucao]); + `INSERT INTO MENSAGENS_CLIENTES (MSC_CODIGO_ID, MSC_CLIENTE_ID, MSC_USUARIO_ID, MSC_DT_MENSAGEM, MSC_AVISO, MSC_MENSAGEM) + VALUES (?, ?, ?, CURRENT_TIMESTAMP, 'S', ?)`, + [mscId, c.CON_CLIENTE_ID, c.CON_USUARIO_ID || null, resolucao]); console.log('[Resolucao] Aviso salvo em MENSAGENS_CLIENTES para cliente', c.CON_CLIENTE_ID); } catch (mscErr) { console.error('[Resolucao] Erro ao salvar em MENSAGENS_CLIENTES:', mscErr.message); diff --git a/src/controllers/evolutionController.js b/src/controllers/evolutionController.js index 9671341..61dcf00 100644 --- a/src/controllers/evolutionController.js +++ b/src/controllers/evolutionController.js @@ -3,6 +3,31 @@ const https = require('https'); const http = require('http'); const { listAliases } = db; const TriageController = require('./triageController'); +const mediaService = require('../services/mediaService'); + +// ponytail: anti-loop — limita respostas automáticas por conversa +const triagemRateLimit = new Map(); +const TRIAGEM_MAX = 5; // max respostas auto em 60s +const TRIAGEM_WINDOW = 60000; // janela de 60 segundos + +function triagemExcedeu(conversaId) { + const agora = Date.now(); + const entry = triagemRateLimit.get(conversaId); + if (!entry || agora - entry.inicio > TRIAGEM_WINDOW) { + triagemRateLimit.set(conversaId, { count: 1, inicio: agora }); + return false; + } + entry.count++; + return entry.count > TRIAGEM_MAX; +} + +// Limpeza periódica +setInterval(() => { + const agora = Date.now(); + for (const [k, v] of triagemRateLimit) { + if (agora - v.inicio > TRIAGEM_WINDOW * 2) triagemRateLimit.delete(k); + } +}, 60000).unref(); // Cache de instancia -> alias para evitar consultas repetidas var cacheInstanciaAlias = {}; @@ -262,6 +287,27 @@ class EvolutionController { } } + /** + * Listar instâncias de uma empresa + /** + * Busca instâncias já conectadas no servidor Evolution. + * POST /api/:alias/evolution/fetch-instances + */ + static async fetchInstances(req, res) { + try { + const { url, apiKey } = req.body; + if (!url || !apiKey) return res.status(400).json({ success: false, error: 'URL e API Key obrigatórios.' }); + const result = await evolutionRequest(url, apiKey, '/instance/fetchInstances', 'GET'); + if (Array.isArray(result)) { + res.json({ success: true, data: result.map(r => ({ instanceName: r.instanceName || r.name, connectionStatus: r.connectionStatus || r.status })) }); + } else if (result && result.instance) { + res.json({ success: true, data: [{ instanceName: result.instance.instanceName || result.instance.name, connectionStatus: result.instance.connectionStatus || result.instance.status }] }); + } else { + res.json({ success: true, data: [] }); + } + } catch (err) { res.status(500).json({ success: false, error: err.message }); } + } + /** * Listar instâncias de uma empresa * GET /api/:alias/evolution/instances?empresaId= @@ -531,17 +577,30 @@ class EvolutionController { var u = new URL(fotoUrl); var lib = u.protocol === 'https:' ? https : http; lib.get(fotoUrl, { timeout: 15000 }, function(imgRes) { + // Valida Content-Type + var ct = (imgRes.headers['content-type'] || '').toLowerCase(); + if (ct && !ct.startsWith('image/')) { + console.log('[Foto] Content-Type invalido:', ct, '- recusando'); + return reject(new Error('Not an image: ' + ct)); + } var chunks = []; imgRes.on('data', function(c) { chunks.push(c); }); imgRes.on('end', function() { resolve(Buffer.concat(chunks)); }); }).on('error', reject).on('timeout', function() { this.destroy(); reject(new Error('Timeout')); }); }); - if (fotoBuffer && fotoBuffer.length > 100) { + // Valida buffer: tamanho realista (500B a 2MB) e header de imagem + if (fotoBuffer && fotoBuffer.length > 500 && fotoBuffer.length < 2 * 1024 * 1024) { + if (!mediaService.validarHeaderMidia(fotoBuffer, 'image/jpeg')) { + console.log('[Foto] Buffer nao tem header de imagem valido - recusando. Header:', fotoBuffer.slice(0, 4).toString('hex')); + return false; + } await db.execute(alias, 'UPDATE CLIENTES SET CLI_FOTO = ? WHERE CLI_CODIGO_ID = ?', [fotoBuffer.toString('base64'), clienteId]); console.log('[Foto] Foto atualizada para cliente', clienteId, '- bytes:', fotoBuffer.length); return true; + } else if (fotoBuffer) { + console.log('[Foto] Buffer com tamanho invalido:', fotoBuffer ? fotoBuffer.length : 0); } } catch(imgErr) { console.error('[Foto] Erro ao baixar imagem:', (imgErr.message || '').substring(0, 120)); @@ -1280,8 +1339,15 @@ async function processWebhook(alias, body) { var emTriagem = conversa.CON_MENU_ESTADO && (!conversa.CON_USUARIO_ID || !conversa.CON_EQUIPE_ID); if (!isNovaConversa && messageText && messageType === 'conversation' && !data.key?.fromMe && emTriagem) { - TriageController.processResponse(alias, conversa.CON_CODIGO_ID, empresaId, instanciaId, numero, messageText) - .catch(function(e) { console.error('[Triagem] Erro ao processar resposta:', e.message); }); + if (triagemExcedeu(conversa.CON_CODIGO_ID)) { + console.log('[Webhook] Loop detectado na conversa', conversa.CON_CODIGO_ID, '- triagem pausada. Aguardando atendente.'); + await db.execute(alias, + "UPDATE CHATC2_CONVERSAS SET CON_MENU_ESTADO = NULL WHERE CON_CODIGO_ID = ?", + [conversa.CON_CODIGO_ID]); + } else { + TriageController.processResponse(alias, conversa.CON_CODIGO_ID, empresaId, instanciaId, numero, messageText) + .catch(function(e) { console.error('[Triagem] Erro ao processar resposta:', e.message); }); + } } } } catch (err) { diff --git a/src/database.js b/src/database.js index 616bc07..c22bb7b 100644 --- a/src/database.js +++ b/src/database.js @@ -98,6 +98,7 @@ const GENERATORS = [ 'GEN_CHATC2_CSAT_AVALIACOES', 'GEN_CHATC2_MOTIVOS_ATENDIMENTO', 'GEN_CHATC2_AGENDAMENTOS', + 'GEN_MENSAGENS_CLIENTES', ]; async function nextId(alias, generatorName) { diff --git a/src/public/settings.html b/src/public/settings.html index 8d17246..ca8d8fe 100644 --- a/src/public/settings.html +++ b/src/public/settings.html @@ -280,6 +280,14 @@ body { background:var(--surface-3); display:flex; min-height:100vh; }
+ +