From 33e78e70c34abd3af33ef523dc8c704625da5e9e Mon Sep 17 00:00:00 2001 From: Rafael Lopes Date: Thu, 2 Oct 2025 20:57:20 -0300 Subject: [PATCH] =?UTF-8?q?FEAT:=20Adiciona=20fun=C3=A7=C3=A3o=20de=20sani?= =?UTF-8?q?tiza=C3=A7=C3=A3o=20de=20coment=C3=A1rios=20do=20GLPI=20para=20?= =?UTF-8?q?remover=20HTML=20e=20limpar=20metadados.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/glpiCommentService.js | 34 +++++--- src/utils/commentSanitizer.js | 132 +++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 src/utils/commentSanitizer.js diff --git a/src/services/glpiCommentService.js b/src/services/glpiCommentService.js index b33824d..e7bee36 100644 --- a/src/services/glpiCommentService.js +++ b/src/services/glpiCommentService.js @@ -5,7 +5,7 @@ const TicketSyncModel = require('../models/ticketSyncModel'); const TicketUpdateModel = require('../models/ticketUpdateModel'); const { logInfo, logError } = require('../utils/logger'); const { existingCommentInServiceNow, syncCommentToServiceNow } = require('./servicenowService'); - +const { sanitizeGLPIComment } = require('../utils/commentSanitizer'); // Sincroniza comentários do GLPI para o Banco de Dados local const syncCommentsGlpi = async () => { @@ -23,12 +23,21 @@ const syncCommentsGlpi = async () => { for (const comment of comments) { + // DEBUG: Mostra antes e depois + logInfo(`🔍 Processando comentário GLPI ID: ${comment.id}`); + logInfo(`📝 CONTEÚDO ORIGINAL: ${comment.content.substring(0, 200)}...`); + + // Sanitiza o comentário + const sanitizedContent = sanitizeGLPIComment(comment); + + logInfo(`✨ CONTEÚDO SANITIZADO: ${sanitizedContent.substring(0, 200)}...`); + const existingComment = await TicketUpdateModel.getCommentSync(ticket.glpi_ticket_id, comment.id); const commentExistsInSN = await existingCommentInServiceNow( ticket.sn_ticket_id, - comment.content - ) + sanitizedContent // ✅ Usa o conteúdo sanitizado + ); if (existingComment && commentExistsInSN) { logInfo(`Comentário ID ${comment.id} já sincronizado no banco de dados e no ServiceNow`, { step: 3 }); @@ -41,7 +50,7 @@ const syncCommentsGlpi = async () => { const commentData = { ticket_sync_id: sync_id, source_system: 'glpi', - content: comment.content, + content: sanitizedContent, // ✅ Usa o conteúdo sanitizado created_at: new Date(comment.date_creation), updated_at: new Date(comment.date_mod), source_id: comment.id, @@ -59,10 +68,9 @@ const syncCommentsGlpi = async () => { if (existingComment && !commentExistsInSN) { logInfo(`Comentário ID ${comment.id} já existe no banco, mas não no ServiceNow. Sincronizando...`, { step: 3 }); // Só sincroniza no ServiceNow - const snCommentId = await syncCommentToServiceNow(ticket.sn_ticket_id, comment.content); - // Se quiser, pode atualizar o registro no banco com o id do ServiceNow aqui + const snCommentId = await syncCommentToServiceNow(ticket.sn_ticket_id, sanitizedContent); // ✅ Usa o conteúdo sanitizado logInfo(`✅ Comentário ID ${comment.id} sincronizado no ServiceNow!`, { step: 4 }); - const updateData = await TicketUpdateModel.updateDestinyId(comment.id, snCommentId); + const updateData = await TicketUpdateModel.updateDestinyId(comment.id, snCommentId); if (updateData && updateData.success) { logInfo(`✅ ID do comentário de destino atualizado no banco: ${snCommentId}`, { step: 4 }); } @@ -70,12 +78,12 @@ const syncCommentsGlpi = async () => { } // Se não existe em nenhum dos dois, sincroniza nos dois normalmente - const snCommentId = await syncCommentToServiceNow(ticket.sn_ticket_id, comment); + const snCommentId = await syncCommentToServiceNow(ticket.sn_ticket_id, sanitizedContent); // ✅ Usa o conteúdo sanitizado const sync_id = await TicketSyncModel.getIdSyncByGlpiId(ticket.glpi_ticket_id); const commentData = { ticket_sync_id: sync_id, source_system: 'glpi', - content: comment.content, + content: sanitizedContent, // ✅ Usa o conteúdo sanitizado created_at: new Date(comment.date_creation), updated_at: new Date(comment.date_mod), source_id: comment.id, @@ -89,22 +97,20 @@ const syncCommentsGlpi = async () => { } const hasError = !insertResult.success; - const snStatus = 'pending'; - const glpiStatus = hasError ? 'Error' :'updated'; + const snStatus = 'pending'; + const glpiStatus = hasError ? 'Error' : 'updated'; await TicketSyncModel.updateStatus(ticket.id, glpiStatus, snStatus); logInfo(`Status de sincronização para o ticket ID ${ticket.id} atualizado para GLPI: '${glpiStatus}', SN: '${snStatus}'.`); } } logInfo('✅ Sincronização de comentários do GLPI -> ServiceNow concluída!'); - }catch (error) { + } catch (error) { logError(error, 'GLPI Tickets'); } } - - /** * @module glpiCommentService * @description Este módulo fornece serviços para sincronizar comentários do GLPI para o banco de dados local. diff --git a/src/utils/commentSanitizer.js b/src/utils/commentSanitizer.js new file mode 100644 index 0000000..e51fa0f --- /dev/null +++ b/src/utils/commentSanitizer.js @@ -0,0 +1,132 @@ +// src/utils/commentSanitizer.js +const { logInfo, logWarning } = require('./logger'); + +/** + * Remove HTML tags e converte entidades HTML - CORRIGIDO + */ +const stripHTML = (html) => { + if (!html) return ''; + + // PRIMEIRO: converte entidades HTML para caracteres normais + let cleaned = html + .replace(/</g, '<') // < para < + .replace(/>/g, '>') // > para > + .replace(/&/g, '&') // & para & + .replace(/ /g, ' ') //   para espaço + .replace(/</g, '<') // < para < + .replace(/>/g, '>') // > para > + .replace(/&/g, '&') // & para & + .replace(/"/g, '"'); // " para " + + // DEPOIS: remove tags HTML + cleaned = cleaned + .replace(//gi, '\n') //
para quebra de linha + .replace(/

/gi, '') // Remove

+ .replace(/<\/p>/gi, '\n\n') //

para duas quebras + .replace(//gi, '**') // para ** + .replace(/<\/strong>/gi, '**') // para ** + .replace(//gi, '*') // para * + .replace(/<\/em>/gi, '*') // para * + .replace(/
/gi, '> ') //
para citação + .replace(/<\/blockquote>/gi, '') //
+ .replace(/]*>/gi, '') // Remove
com qualquer atributo + .replace(/<\/div>/gi, '\n') //
para quebra + .replace(/<[^>]*>/g, '') // Remove todas outras tags HTML + .replace(/\n\s*\n\s*\n/g, '\n\n') // Remove múltiplas quebras + .replace(/^\s+|\s+$/g, '') // Remove espaços no início/fim + .trim(); + + return cleaned; +}; + +/** + * Detecta e trata imagens do GLPI + */ +const handleImages = (content) => { + const imgRegex = /]+src="([^"]+)"[^>]*>/gi; + const hasImages = imgRegex.test(content); + + if (hasImages) { + logWarning('📷 Imagem detectada no comentário GLPI'); + // Remove toda a tag de imagem mas mantém o texto around + return content.replace(/]*>.*?]*>.*?<\/a>/gi, '[IMAGEM ANEXA NO GLPI]'); + } + + return content; +}; + +/** + * Limpa metadados JSON do GLPI + */ +const cleanJSONMetadata = (content) => { + if (typeof content === 'string' && content.includes('{id=') && content.includes('content=')) { + try { + // Extrai o conteúdo do campo content= do objeto + const contentMatch = content.match(/content=([^,]+),/); + if (contentMatch && contentMatch[1]) { + let extracted = contentMatch[1].trim(); + // Remove aspas se existirem + extracted = extracted.replace(/^['"]|['"]$/g, ''); + return extracted; + } + } catch (error) { + logWarning('Não foi possível extrair conteúdo do metadado GLPI', { content }); + } + } + return content; +}; + +/** + * Função principal de sanitização + */ +const sanitizeGLPIComment = (commentObj) => { + if (!commentObj || typeof commentObj !== 'object') { + logWarning('Comentário inválido recebido', { comment: commentObj }); + return ''; + } + + // Pega o conteúdo do objeto comment + let content = commentObj.content || ''; + + // Se for string vazia, retorna vazio + if (!content) return ''; + + // Aplica as limpezas em sequência + content = cleanJSONMetadata(content.toString()); + content = handleImages(content); + content = stripHTML(content); + + // Log para debugging + if (content !== commentObj.content) { + logInfo('🔧 Comentário sanitizado', { + original: commentObj.content.substring(0, 100) + '...', + cleaned: content.substring(0, 100) + '...' + }); + } + + return content; +}; + +/** + * Esse módulo fornece funções para sanitizar comentários importados do GLPI. + * Ele remove tags HTML, trata imagens e limpa metadados JSON específicos do GLPI. + * O objetivo é garantir que os comentários sejam legíveis e seguros para exibição. + * + * Funções principais: + * - sanitizeGLPIComment: Função principal que aplica todas as sanitizações. + * - stripHTML: Remove tags HTML e converte algumas entidades para texto simples. + * - handleImages: Detecta imagens e substitui por um marcador de imagem. + * - cleanJSONMetadata: Remove metadados JSON do GLPI, extraindo apenas o conteúdo relevante. + * + * Logs são gerados para ajudar no debugging e monitoramento do processo de sanitização. + */ + +module.exports = { + sanitizeGLPIComment, + stripHTML, + handleImages, + cleanJSONMetadata +}; + + +