Skip to content

Commit 503ad10

Browse files
committed
Merge pull request #8 from RubensGJ/dev
😍Merge: Refatoração do módulo de cotações, novas rotas analíticas e ajustes de documentação/logs
2 parents fb04289 + e45a1a4 commit 503ad10

27 files changed

Lines changed: 1689 additions & 344 deletions

openapi.yaml

Lines changed: 474 additions & 3 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"scripts": {
77
"start": "node src/app.js",
88
"dev": "node --watch src/app.js",
9-
"postinstall": "npx puppeteer browsers install chrome",
9+
"postinstall": "node scripts/installChrome.js",
1010
"test": "echo \"Error: no test specified\" && exit 1"
1111
},
1212
"keywords": [

scripts/installChrome.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const { spawnSync } = require("child_process");
4+
const puppeteer = require("puppeteer");
5+
6+
// Escreve mensagens simples do postinstall no console.
7+
function log(message) {
8+
console.log(`[POSTINSTALL] ${message}`);
9+
}
10+
11+
// Descobre o caminho do CLI interno do Puppeteer usado na instalacao do Chrome.
12+
function getPuppeteerCliPath() {
13+
const packageJsonPath = require.resolve("puppeteer/package.json");
14+
return path.join(path.dirname(packageJsonPath), "lib", "cjs", "puppeteer", "node", "cli.js");
15+
}
16+
17+
// Remove o cache quebrado quando a pasta existe, mas o executavel nao.
18+
function removeBrokenBrowserFolder(executablePath) {
19+
const browserFolder = path.dirname(path.dirname(executablePath));
20+
21+
if (!fs.existsSync(browserFolder)) {
22+
return;
23+
}
24+
25+
log(`Cache incompleto detectado em ${browserFolder}. Removendo pasta corrompida.`);
26+
fs.rmSync(browserFolder, { recursive: true, force: true });
27+
}
28+
29+
// Executa o comando oficial do Puppeteer para instalar o Chrome.
30+
function installChrome() {
31+
const cliPath = getPuppeteerCliPath();
32+
const result = spawnSync(process.execPath, [cliPath, "browsers", "install", "chrome"], {
33+
stdio: "inherit",
34+
});
35+
36+
if (result.error) {
37+
throw result.error;
38+
}
39+
40+
if (result.status !== 0) {
41+
process.exit(result.status || 1);
42+
}
43+
}
44+
45+
// Evita reinstalar o Chrome quando ele ja esta pronto e corrige cache quebrado.
46+
function main() {
47+
const executablePath = puppeteer.executablePath();
48+
49+
if (fs.existsSync(executablePath)) {
50+
log("Chrome do Puppeteer ja esta disponivel.");
51+
return;
52+
}
53+
54+
removeBrokenBrowserFolder(executablePath);
55+
log("Instalando Chrome do Puppeteer.");
56+
installChrome();
57+
}
58+
59+
main();

src/app.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ const OPENAPI_FILE = path.resolve(__dirname, "..", "openapi.yaml");
1818
const openApiDocument = YAML.load(OPENAPI_FILE);
1919
const logger = criarLogger("API");
2020

21+
// Habilita middlewares globais usados por toda a aplicacao.
2122
app.use(cors());
2223
app.use(express.json());
2324
app.use(requestLogger);
2425

26+
// Rota simples para monitoramento e health check.
2527
app.get("/health", (req, res) => {
2628
res.status(200).json({
2729
status: "ok",
@@ -30,6 +32,7 @@ app.get("/health", (req, res) => {
3032
});
3133
});
3234

35+
// Rota inicial para indicar rapidamente se a API esta online.
3336
app.get("/", (req, res) => {
3437
res.status(200).json({
3538
status: "ok",
@@ -39,15 +42,22 @@ app.get("/", (req, res) => {
3942
});
4043
});
4144

45+
// Entrega o arquivo OpenAPI bruto para quem quiser baixar ou integrar.
4246
app.get("/openapi.yaml", (req, res) => {
4347
res.sendFile(OPENAPI_FILE);
4448
});
49+
50+
// Publica a documentacao Swagger da API.
4551
app.use("/docs", swaggerUi.serve, swaggerUi.setup(openApiDocument));
4652

53+
// Registra as rotas protegidas de cotacoes.
4754
app.use("/api/cotacoes", authenticateToken, cotacoesRoutes);
55+
56+
// Trata rotas inexistentes e erros da API.
4857
app.use(notFoundHandler);
4958
app.use(errorHandler);
5059

60+
// Inicializa os recursos obrigatorios antes de a API aceitar requisicoes.
5161
async function bootstrap() {
5262
logger.info("Iniciando aplicacao.");
5363
validateAuthConfig();
@@ -56,6 +66,7 @@ async function bootstrap() {
5666
logger.sucesso("Aplicacao pronta para receber requisicoes.");
5767
}
5868

69+
// Sobe o servidor HTTP e inicia o scheduler de coletas automaticas.
5970
async function startServer() {
6071
await bootstrap();
6172

@@ -67,6 +78,7 @@ async function startServer() {
6778
startCotacaoScheduler();
6879
}
6980

81+
// Encerra o processo se a aplicacao falhar ainda na subida.
7082
startServer().catch((error) => {
7183
logger.erro("Falha ao iniciar aplicacao.", error);
7284
process.exit(1);

src/database/cotacoesRepository.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const { query } = require("./db");
22
const { getNowInBrasiliaISO } = require("../utils/dateTime");
33

4+
// Converte o JSON salvo no banco para um array padrao usado pelos services.
45
function parsePayload(payloadJson) {
56
if (Array.isArray(payloadJson)) {
67
return payloadJson;
@@ -18,6 +19,7 @@ function parsePayload(payloadJson) {
1819
}
1920
}
2021

22+
// Mapeia uma linha do banco para o formato de snapshot usado na aplicacao.
2123
function mapSnapshotRow(row) {
2224
if (!row) return null;
2325

@@ -34,6 +36,7 @@ function mapSnapshotRow(row) {
3436
};
3537
}
3638

39+
// Salva um novo snapshot no historico e atualiza a tabela com a ultima coleta.
3740
async function saveSnapshot({
3841
source,
3942
payload,
@@ -85,6 +88,7 @@ async function saveSnapshot({
8588
};
8689
}
8790

91+
// Busca o snapshot mais recente de uma fonte especifica.
8892
async function getLatestSnapshot(source) {
8993
const result = await query(
9094
`
@@ -99,6 +103,7 @@ async function getLatestSnapshot(source) {
99103
return mapSnapshotRow(row);
100104
}
101105

106+
// Lista snapshots mais recentes, com ou sem filtro por fonte.
102107
async function listSnapshots({ source = null, limit = 50 }) {
103108
const parsedLimit = Number(limit);
104109
const safeLimit = Number.isFinite(parsedLimit) ? Math.min(Math.max(parsedLimit, 1), 500) : 50;
@@ -127,8 +132,48 @@ async function listSnapshots({ source = null, limit = 50 }) {
127132
return result.rows.map(mapSnapshotRow);
128133
}
129134

135+
// Lista snapshots dentro de um periodo para montar consultas historicas.
136+
async function listSnapshotsByPeriod({ source = null, startDate = null, endDate = null, limit = 500 }) {
137+
const parsedLimit = Number(limit);
138+
const safeLimit = Number.isFinite(parsedLimit) ? Math.min(Math.max(parsedLimit, 1), 2000) : 500;
139+
const values = [];
140+
const conditions = [];
141+
142+
if (source) {
143+
values.push(source);
144+
conditions.push(`fonte = $${values.length}`);
145+
}
146+
147+
if (startDate) {
148+
values.push(startDate);
149+
conditions.push(`coletado_em >= $${values.length}`);
150+
}
151+
152+
if (endDate) {
153+
values.push(endDate);
154+
conditions.push(`coletado_em <= $${values.length}`);
155+
}
156+
157+
values.push(safeLimit);
158+
159+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
160+
const result = await query(
161+
`
162+
SELECT id, fonte, dados_json, quantidade_itens, coletado_em, janela_horario, tipo_disparo, criado_em
163+
FROM cotacoes_historico
164+
${whereClause}
165+
ORDER BY coletado_em ASC, id ASC
166+
LIMIT $${values.length}
167+
`,
168+
values
169+
);
170+
171+
return result.rows.map(mapSnapshotRow);
172+
}
173+
130174
module.exports = {
131175
getLatestSnapshot,
132176
listSnapshots,
177+
listSnapshotsByPeriod,
133178
saveSnapshot,
134179
};

src/database/db.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const { criarLogger } = require("../logs/logger");
44
let pool = null;
55
const logger = criarLogger("BANCO");
66

7+
// Converte variaveis de ambiente textuais para booleano.
78
function parseBoolean(value, defaultValue = true) {
89
if (value === undefined || value === null || value === "") {
910
return defaultValue;
@@ -13,6 +14,7 @@ function parseBoolean(value, defaultValue = true) {
1314
return ["1", "true", "yes", "y", "on"].includes(normalized);
1415
}
1516

17+
// Decide se a conexao com o banco deve usar SSL.
1618
function shouldUseSsl(connectionString) {
1719
const explicit = process.env.DATABASE_SSL;
1820
if (explicit !== undefined) {
@@ -22,6 +24,7 @@ function shouldUseSsl(connectionString) {
2224
return connectionString.includes("neon.tech");
2325
}
2426

27+
// Cria o pool do Postgres uma unica vez e reaproveita nas proximas consultas.
2528
function getPool() {
2629
if (pool) {
2730
return pool;
@@ -42,10 +45,12 @@ function getPool() {
4245
return pool;
4346
}
4447

48+
// Executa uma query simples usando o pool compartilhado da aplicacao.
4549
async function query(text, params = []) {
4650
return getPool().query(text, params);
4751
}
4852

53+
// Garante que as tabelas e indices usados pela API existam no banco.
4954
async function initDatabase() {
5055
logger.info("Garantindo estrutura do banco.");
5156

src/database/requestLogsRepository.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const { query } = require("./db");
22

3+
// Salva no banco apenas os dados principais da requisicao HTTP concluida.
34
async function saveRequestLog({ metodo, rota, statusCode, duracaoMs }) {
45
await query(
56
`

src/errors/AppError.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Representa erros esperados da aplicacao com status HTTP e detalhes opcionais.
12
class AppError extends Error {
23
constructor(message, statusCode = 500, details = null) {
34
super(message);

src/jobs/cotacaoScheduler.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const { criarLogger } = require("../logs/logger");
44

55
const logger = criarLogger("SCHEDULER");
66

7-
//helper
7+
// Converte variaveis do .env para booleano no scheduler.
88
function parseBoolean(value, defaultValue) {
99
if (value === undefined || value === null || value === "") {
1010
return defaultValue;
@@ -14,21 +14,21 @@ function parseBoolean(value, defaultValue) {
1414
return ["1", "true", "yes", "y", "on"].includes(normalized);
1515
}
1616

17-
//pega o tempo agendado do env
17+
// Le as expressoes cron configuradas para os horarios de coleta automatica.
1818
function getCronExpressions() {
1919
const first = (process.env.SCHEDULER_CRON_1 || "0 12 * * *").trim();
2020
const second = (process.env.SCHEDULER_CRON_2 || "0 15 * * *").trim();
2121
return [first, second];
2222
}
2323

24-
//
24+
// Define o nome exibido no historico para cada horario agendado.
2525
function getSlotLabel(index, expression) {
2626
if (index === 0) return "12:00";
2727
if (index === 1) return "15:00";
2828
return expression;
2929
}
3030

31-
//
31+
// Registra os jobs cron que atualizam as cotacoes automaticamente.
3232
function startCotacaoScheduler() {
3333
const enabled = parseBoolean(process.env.SCHEDULER_ENABLED, true);
3434
if (!enabled) {

src/logs/logger.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
// Gera o timestamp padrao usado em todas as mensagens de log.
12
function getTimestamp() {
23
return new Date().toISOString();
34
}
45

6+
// Extrai uma mensagem legivel quando o log recebe um erro.
57
function getMensagemErro(error) {
68
if (!error) {
79
return "";
@@ -18,6 +20,7 @@ function getMensagemErro(error) {
1820
return String(error);
1921
}
2022

23+
// Escreve uma linha de log com formato padronizado no console.
2124
function escrever(nivel, contexto, mensagem, error = null) {
2225
const linha = `[${getTimestamp()}] [${nivel}] [${contexto}] ${mensagem}`;
2326
const detalheErro = getMensagemErro(error);
@@ -35,6 +38,7 @@ function escrever(nivel, contexto, mensagem, error = null) {
3538
}
3639
}
3740

41+
// Cria um objeto de log com o contexto informado para reutilizar no projeto.
3842
function criarLogger(contexto) {
3943
return {
4044
info(mensagem) {

0 commit comments

Comments
 (0)