From 50935d43e72f133611381b949f83c27fe5745d74 Mon Sep 17 00:00:00 2001 From: "gabriel.amancio" Date: Mon, 8 Dec 2025 15:43:20 -0300 Subject: [PATCH] =?UTF-8?q?FEAT:=20Adi=C3=A7=C3=A3o=20do=20insert=20das=20?= =?UTF-8?q?viabilidades=20no=20banco.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Model e repository alterado para o insert dos dados de viabilidade. - Refatoração dos logs. --- package-lock.json | 147 ++++++++++++++++++ package.json | 3 +- .../contratacao/contratacao.controller.js | 9 +- src/modules/contratacao/contratacao.model.js | 9 +- .../contratacao/contratacao.repository.js | 49 ++++++ .../contratacao/contratacao.service.js | 29 ++-- src/shared/apis/cepRestService.js | 8 +- src/shared/apis/geogridService.js | 6 +- src/shared/apis/googleService.js | 3 - src/shared/config/dbConfig.js | 25 +++ 10 files changed, 255 insertions(+), 33 deletions(-) create mode 100644 src/shared/config/dbConfig.js diff --git a/package-lock.json b/package-lock.json index f8cb1b8..20c6aa8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "dotenv": "^17.2.3", "express": "^5.1.0", "node-cron": "^4.2.1", + "pg": "^8.16.3", "qs": "^6.11.0", "winston": "^3.18.3", "winston-daily-rotate-file": "^5.0.0" @@ -1216,6 +1217,95 @@ "url": "https://opencollective.com/express" } }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -1229,6 +1319,45 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1552,6 +1681,15 @@ "node": ">=10" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -1759,6 +1897,15 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } } } } diff --git a/package.json b/package.json index 924f926..cfa48f8 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "dotenv": "^17.2.3", "express": "^5.1.0", "node-cron": "^4.2.1", + "pg": "^8.16.3", "qs": "^6.11.0", "winston": "^3.18.3", "winston-daily-rotate-file": "^5.0.0" @@ -32,4 +33,4 @@ "cross-env": "^10.1.0", "nodemon": "^3.1.11" } -} \ No newline at end of file +} diff --git a/src/modules/contratacao/contratacao.controller.js b/src/modules/contratacao/contratacao.controller.js index 4f53e91..195331a 100644 --- a/src/modules/contratacao/contratacao.controller.js +++ b/src/modules/contratacao/contratacao.controller.js @@ -2,16 +2,14 @@ const contratacaoService = require('./contratacao.service.js'); const logger = require('../../shared/utils/logger.js'); async function handleViabilidade(req, res) { - const { cep, numero } = req.body; - logger.info('Iniciando handleViabilidade', { cep, numero }); + const rawViabilidadeData = req.body; try { - const resultadoViabilidade = await contratacaoService.verificarViabilidade(cep, numero); - logger.info('Viabilidade verificada com sucesso', { cep, numero, resultado: resultadoViabilidade }); + const resultadoViabilidade = await contratacaoService.verificarViabilidade(rawViabilidadeData); return res.json(resultadoViabilidade); } catch (error) { - logger.error('Erro no controller ao processar viabilidade', { message: error.message, stack: error.stack, cep, numero }); + logger.error('Erro no controller ao processar viabilidade', { message: error.message, stack: error.stack, rawViabilidadeData }); const statusCode = error.statusCode || 500; return res.status(statusCode).json({ error: error.message || "Erro interno ao processar a viabilidade." }); } @@ -19,7 +17,6 @@ async function handleViabilidade(req, res) { async function handleCriarProspecto(req, res) { const prospectData = req.body; - logger.info('Iniciando handleCriarProspecto', { prospectData }); try { const resultado = await contratacaoService.criarProspecto(prospectData); diff --git a/src/modules/contratacao/contratacao.model.js b/src/modules/contratacao/contratacao.model.js index e16748c..85dfc08 100644 --- a/src/modules/contratacao/contratacao.model.js +++ b/src/modules/contratacao/contratacao.model.js @@ -1,13 +1,16 @@ // classe construtor para o modelo de viabilidade class ViabilidadeModel { - constructor(cep, numero, bairro, city, state, logradouro, naoDedicado, dedicado) { + constructor(nome, email, telefone, logradouro, numero, bairro, city, state, cep, naoDedicado, dedicado) { // Inicialização de propriedades do modelo pode ser feita aqui - this.cep = cep; + this.nome = nome; + this.email = email; + this.telefone = telefone; + this.logradouro = logradouro; this.numero = numero; this.bairro = bairro; this.cidade = city; this.estado = state; - this.logradouro = logradouro; + this.cep = cep; this.naoDedicado = naoDedicado; this.dedicado = dedicado; } diff --git a/src/modules/contratacao/contratacao.repository.js b/src/modules/contratacao/contratacao.repository.js index add8b5d..daccbc2 100644 --- a/src/modules/contratacao/contratacao.repository.js +++ b/src/modules/contratacao/contratacao.repository.js @@ -1,3 +1,52 @@ +const dbConfig = require('../../shared/config/dbConfig.js'); + + +async function insertViabilidadeData(viabilidadeData) { + + const query = ` + INSERT INTO viabilidades ( + nome, + email, + telefone, + logradouro, + numero, + bairro, + cidade, + estado, + cep, + nao_dedicado, + dedicado, + data + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, NOW()) + RETURNING *; + `; + + const values = [ + viabilidadeData.nome, + viabilidadeData.email, + viabilidadeData.telefone, + viabilidadeData.logradouro, + viabilidadeData.numero, + viabilidadeData.bairro, + viabilidadeData.cidade, + viabilidadeData.estado, + viabilidadeData.cep, + viabilidadeData.naoDedicado, + viabilidadeData.dedicado, + ]; + + try { + const res = await dbConfig.pool.query(query, values); + return res.rows[0]; + } catch (err) { + throw new Error(`Erro ao inserir dados de viabilidade: ${err.message}`); + } +} + +module.exports = { + insertViabilidadeData +}; + /* DESCRIÇÃO: Este arquivo implementa o padrão de "Repository" (Repositório) para o módulo de contratação. A camada de repositório é responsável por toda a comunicação com a fonte de dados, que geralmente é um banco de dados. diff --git a/src/modules/contratacao/contratacao.service.js b/src/modules/contratacao/contratacao.service.js index e6c45e7..5efa559 100644 --- a/src/modules/contratacao/contratacao.service.js +++ b/src/modules/contratacao/contratacao.service.js @@ -3,6 +3,7 @@ const googleService = require("../../shared/apis/googleService.js"); const cepRestService = require("../../shared/apis/cepRestService.js"); const hubsoftService = require("../../shared/apis/hubsoftService.js"); const logger = require('../../shared/utils/logger.js'); +const repository = require('./contratacao.repository.js'); const {ViabilidadeModel, ClientModelPf, ClientModelPj, ProspectModel} = require('./contratacao.model'); // utilitário para ler chaves do payload tolerante a ":" e espaços @@ -20,7 +21,7 @@ const getPayloadValue = (obj, key) => { return undefined; }; -// mapping de planos para IDs e valores no Hubsoft +// Classe de erro personalizada para o serviço class ServiceError extends Error { constructor(message, statusCode = 500) { @@ -30,15 +31,18 @@ class ServiceError extends Error { } } -async function verificarViabilidade(rawCep, rawNumero) { - logger.info('Iniciando verificação de viabilidade', { cep: rawCep, numero: rawNumero }); +// Função para verificar a viabilidade de um endereço + +async function verificarViabilidade(rawViabilidadeData) { + const rawCep = rawViabilidadeData.cep; + const rawNumero = rawViabilidadeData.numero; if (!rawCep || !rawNumero) { logger.warn('CEP ou número não fornecidos na verificação de viabilidade.'); throw new ServiceError('CEP e número são obrigatórios.', 400); } - const address = await cepRestService.getConsultaCep(rawCep, rawNumero); + const address = await cepRestService.getConsultaCep(rawCep); // FIX: Revertendo para a verificação correta da resposta do cepRestService if (!address) { logger.warn('Endereço não encontrado ou resposta inválida do serviço de CEP', { cep: rawCep, response: address }); @@ -58,7 +62,6 @@ async function verificarViabilidade(rawCep, rawNumero) { logger.info('Coordenadas obtidas com sucesso', { coords }); const viabilidade = await geogridService.consultaViabilidade(coords.lat, coords.lon); - logger.info('Resultado da consulta de viabilidade no GeoGrid', { viabilidade }); let naoDedicado = false; let dedicado = false; @@ -70,16 +73,23 @@ async function verificarViabilidade(rawCep, rawNumero) { naoDedicado = true; dedicado = true; } else if (distancia <= 1000) { - naoDedicado = true; - dedicado = false; + naoDedicado = false; + dedicado = true; } } else { logger.warn('Resposta do GeoGrid não continha dados de viabilidade.', { cep: rawCep, numero: rawNumero }); } - const viabilidadeResult = new ViabilidadeModel(cep, rawNumero, bairro, city, state, logradouro, naoDedicado, dedicado); + const viabilidadeResult = new ViabilidadeModel(rawViabilidadeData.nome, rawViabilidadeData.email, rawViabilidadeData.telefone, logradouro, rawNumero, bairro, city, state, cep, naoDedicado, dedicado); + + await repository.insertViabilidadeData(viabilidadeResult) + .then(() => { + logger.info('Dados de viabilidade inseridos no repositório com sucesso', { viabilidadeResult }); + }) + .catch((err) => { + logger.error('Erro ao inserir dados de viabilidade no repositório', { message: err.message, stack: err.stack }); + }); - logger.info('Finalizando verificação de viabilidade com sucesso', { viabilidadeResult }); return viabilidadeResult; } @@ -111,7 +121,6 @@ async function criarProspecto(rawProspectData) { if (!Number.isNaN(valorNum)) servico.valor = valorNum; } servico.id_servico = Number(servico.id_servico); - console.log(servico); if (!servico) { logger.warn('Plano não encontrado no mapeamento', { planoKey, disponíveis: Object.keys(planos) }); throw new ServiceError('Plano inválido ou não encontrado.', 400); diff --git a/src/shared/apis/cepRestService.js b/src/shared/apis/cepRestService.js index 40e1091..7a4655b 100644 --- a/src/shared/apis/cepRestService.js +++ b/src/shared/apis/cepRestService.js @@ -3,8 +3,8 @@ const logger = require('../utils/logger.js'); // Consulta o endereço completo usando a API externa de CEP -const getConsultaCep = async (rawCep, rawNumero) => { - logger.info('Iniciando consulta de CEP', { cep: rawCep, numero: rawNumero }); +const getConsultaCep = async (rawCep) => { + logger.info('Iniciando consulta de CEP'); if (!rawCep) { logger.warn('CEP não fornecido para getConsultaCep'); @@ -16,12 +16,9 @@ const getConsultaCep = async (rawCep, rawNumero) => { throw new Error("cep inválido, verifique se foram digitados apenas números"); } - // Número é opcional na consulta, mas se fornecido, deve ser uma string limpa - // const numero = rawNumero ? String(rawNumero).trim() : ""; try { const cepRestUrl = 'https://api.cep.rest/'; - logger.info('Consultando API de CEP', { url: cepRestUrl, cep }); const address = await axios.post(cepRestUrl, { cep }); if (address.data && address.data.code === 404) { @@ -39,7 +36,6 @@ const getConsultaCep = async (rawCep, rawNumero) => { } // Estrutura 2: { bairro, cep, ... } (direto em data) else if (address.data.data && address.data.data.cep) { - logger.info('CEP encontrado com estrutura direta'); return address.data.data; } else { diff --git a/src/shared/apis/geogridService.js b/src/shared/apis/geogridService.js index 7ae6adc..d361622 100644 --- a/src/shared/apis/geogridService.js +++ b/src/shared/apis/geogridService.js @@ -5,7 +5,6 @@ const logger = require('../utils/logger.js'); const consultaViabilidade = async (lat, lon) => { - logger.info('Iniciando consulta de viabilidade no GeoGrid', { lat, lon }); const url = apiConfig.geogridApiUrl; const apiKey = apiConfig.geogridApiKey; @@ -23,8 +22,8 @@ const consultaViabilidade = async (lat, lon) => { consultarIndividual: "S" }; - const urlParams = qs.stringify(params, { arrayFormat: 'brackets' }); - logger.info(`Chamada à API GeoGrid: ${url}?${urlParams}`); + // const urlParams = qs.stringify(params, { arrayFormat: 'brackets' }); + // logger.info(`Chamada à API GeoGrid: ${url}?${urlParams}`); const response = await axios.get(url, { params, @@ -39,7 +38,6 @@ const consultaViabilidade = async (lat, lon) => { const registros = response.data && response.data.registros; const primeiro = Array.isArray(registros) && registros.length ? registros[0] : null; - logger.info('Resposta recebida do GeoGrid', { data: primeiro }); // Retorna no formato esperado pelo controller (viabilidade.data.distancia) return { data: primeiro }; } catch (error) { diff --git a/src/shared/apis/googleService.js b/src/shared/apis/googleService.js index 4d93424..1eb66d0 100644 --- a/src/shared/apis/googleService.js +++ b/src/shared/apis/googleService.js @@ -6,7 +6,6 @@ const logger = require('../utils/logger.js'); // Geocodifica um endereço usando a API do Google Maps async function geocodeWithGoogle(address) { - logger.info('Iniciando geocodificação com Google', { address }); const key = apiConfig.googleApiKey; if (!key) { logger.error('Chave da API do Google não configurada.'); @@ -14,7 +13,6 @@ async function geocodeWithGoogle(address) { } try { const url = 'https://maps.googleapis.com/maps/api/geocode/json'; - logger.info('Consultando Google Geocoding API', { url, address }); const r = await axios.get(url, { params: { address, key }, timeout: 10000, @@ -34,7 +32,6 @@ async function geocodeWithGoogle(address) { const lng = loc && (loc.lng !== undefined ? Number(loc.lng) : NaN); if (!Number.isNaN(lat) && !Number.isNaN(lng)) { const coords = { lat, lon: lng }; - logger.info('Geocodificação bem-sucedida', { address, coords }); return coords; } } diff --git a/src/shared/config/dbConfig.js b/src/shared/config/dbConfig.js new file mode 100644 index 0000000..fa22d72 --- /dev/null +++ b/src/shared/config/dbConfig.js @@ -0,0 +1,25 @@ +const { Pool } = require('pg'); +const dotenv = require('dotenv'); +dotenv.config(); + +const dbHost = process.env.VIABILIDADES_DB_HOST; +const dbPort = process.env.VIABILIDADES_DB_PORT; +const dbUsername = process.env.VIABILIDADES_DB_USERNAME; +const dbPassword = process.env.VIABILIDADES_DB_PASSWORD; +const dbDatabase = process.env.VIABILIDADES_DB_DATABASE; + +// pool de conexões: + +const pool = new Pool({ + host: dbHost, + port: dbPort, + user: dbUsername, + password: dbPassword, + database: dbDatabase, + max: 20, // número máximo de conexões no pool + idleTimeoutMillis: 30000, // tempo máximo que uma conexão pode ficar ociosa antes de ser fechada + connectionTimeoutMillis: 2000 // tempo máximo para tentar conectar antes de lançar um erro +}); + +module.exports = {pool}; +