FEAT: configurar sessão, rotas de autenticação e processamento de CSV

- Adiciona/ativa express-session antes das rotas para permitir uso de req.session
- Registra rotas de autenticação (/login, /auth/callback) e middleware de proteção
- Serve assets estáticos em /public
- Implementa upload/processing CSV (ViaCEP -> Google geocoding, consulta Geogrid, cache de coordenadas)
- Adiciona endpoints de status/download e tratamento de retries/backoff para chamadas externas
- Melhora logs e handling de erros
This commit is contained in:
Gabriel Amancio 2025-10-17 14:12:27 -03:00
parent 46d66f9c1b
commit 67a0a83a99
3 changed files with 44 additions and 29 deletions

15
app.js
View File

@ -8,11 +8,13 @@ const csv = require("csv-parser");
const fastCsv = require("fast-csv"); const fastCsv = require("fast-csv");
const axios = require("axios"); const axios = require("axios");
const cors = require("cors"); const cors = require("cors");
const session = require("express-session"); // adiciona session
const { geocodeWithGoogle } = require("./service/geocodeService"); const { geocodeWithGoogle } = require("./service/geocodeService");
const { fetchJson } = require("./service/fetchService"); const { fetchJson } = require("./service/fetchService");
const { BASE_BACKOFF_MS, MAX_RETRIES, REQUEST_DELAY_MS, sleep } = require("./service/retryService"); const { BASE_BACKOFF_MS, MAX_RETRIES, REQUEST_DELAY_MS, sleep } = require("./service/retryService");
const { API_URL, HEADERS } = require("./config/apiConfig"); const { API_URL, HEADERS } = require("./config/apiConfig");
const { normalizePartnerSigla } = require("./service/normalizeService"); const { normalizePartnerSigla } = require("./service/normalizeService");
const authRoutes = require("./routes/authRoutes.js");
function createApp() { function createApp() {
const upload = multer({ dest: "uploads/" }); const upload = multer({ dest: "uploads/" });
@ -21,6 +23,19 @@ function createApp() {
app.use(express.static(path.join(__dirname, "public"))); app.use(express.static(path.join(__dirname, "public")));
app.use(express.json()); app.use(express.json());
// session deve ser configurada antes de usar req.session nas rotas/middleware
app.use(
session({
secret: process.env.SESSION_SECRET || "change-me",
resave: false,
saveUninitialized: false,
cookie: {
// ajuste conforme produção (secure: true se rodando em HTTPS)
maxAge: 24 * 60 * 60 * 1000,
},
})
);
async function getMinDistance(lat, lon) { async function getMinDistance(lat, lon) {
// tenta várias vezes com backoff exponencial; trata 429 usando Retry-After se disponível // tenta várias vezes com backoff exponencial; trata 429 usando Retry-After se disponível
let attempt = 0; let attempt = 0;

View File

@ -1,18 +1,15 @@
import express from "express"; const express = require("express");
import {getAuthUrl, getTokenFomCode} from "../service/authService.js"; const { getAuthUrl, getTokenFromCode } = require("../service/authService");
const router = express.Router(); const router = express.Router();
// Rota para iniciar o fluxo de autenticação // Rota para iniciar o fluxo de autenticação
router.get("/login", (req, res) => { router.get("/login", (req, res) => {
const authUrl = getAuthUrl(); const authUrl = getAuthUrl();
return res.redirect(authUrl); return res.redirect(authUrl);
}); });
// Rota de callback após autenticação // Rota de callback após autenticação
router.get("/auth/callback", async (req, res) => { router.get("/auth/callback", async (req, res) => {
const code = req.query.code; const code = req.query.code;
@ -21,15 +18,15 @@ router.get("/auth/callback", async (req, res) => {
} }
try { try {
const tokens = await getTokenFomCode(code); const tokens = await getTokenFromCode(code);
// Armazena os tokens na sessão do usuário // Armazena os tokens na sessão do usuário
if (!req.session) req.session = {};
req.session.tokens = tokens; req.session.tokens = tokens;
return res.redirect("/public/index.html"); return res.redirect("/public/index.html");
} catch (error) { } catch (error) {
console.error("Erro ao obter tokens:", error); console.error("Erro ao obter tokens:", error);
return res.status(500).send("Erro ao processar a autenticação."); return res.status(500).send("Erro ao processar a autenticação.");
} }
});
}) module.exports = router;
export default router;

View File

@ -1,6 +1,5 @@
import axios from "axios"; const axios = require("axios");
import dotenv from "dotenv"; require("dotenv").config();
dotenv.config();
const tenantId = process.env.OAUTH_TENANT_ID; const tenantId = process.env.OAUTH_TENANT_ID;
const clientId = process.env.OAUTH_CLIENT_ID; const clientId = process.env.OAUTH_CLIENT_ID;
@ -8,7 +7,7 @@ const clientSecret = process.env.OAUTH_CLIENT_SECRET;
const redirectUri = process.env.OAUTH_REDIRECT_URI; const redirectUri = process.env.OAUTH_REDIRECT_URI;
// Função que gera o link de login para o usuário // Função que gera o link de login para o usuário
export function getAuthUrl() { function getAuthUrl() {
const params = new URLSearchParams({ const params = new URLSearchParams({
client_id: clientId, client_id: clientId,
response_type: "code", response_type: "code",
@ -22,7 +21,7 @@ export function getAuthUrl() {
} }
// Troca o "authorization code" por tokens // Troca o "authorization code" por tokens
export async function getTokenFromCode(authCode) { async function getTokenFromCode(authCode) {
const url = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; const url = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
const params = new URLSearchParams({ const params = new URLSearchParams({
@ -34,6 +33,10 @@ export async function getTokenFromCode(authCode) {
client_secret: clientSecret, client_secret: clientSecret,
}); });
const response = await axios.post(url, params); const response = await axios.post(url, params.toString(), {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
return response.data; return response.data;
} }
module.exports = { getAuthUrl, getTokenFromCode };