diff --git a/controller/viabilidadeController.js b/controller/viabilidadeController.js
index f20b125..f60e3ad 100644
--- a/controller/viabilidadeController.js
+++ b/controller/viabilidadeController.js
@@ -1,6 +1,8 @@
const { consultarViabilidade } = require('../service/viabilidadeService');
-const { processCsvFile } = require('../service/csvService');
-const { getJob } = require('../service/jobStore.service');
+const { processCsvFile, countValidLines } = require('../service/csvService');
+const { getJob, createJob } = require('../service/jobStore.service');
+const fs = require('fs');
+const path = require('path');
// Controlador para consultar viabilidade
@@ -25,15 +27,27 @@ async function uploadCsvFile(req, res) {
const filePath = req.file.path;
const originalName = req.file.originalname || req.file.filename || 'input.csv';
- const out = await processCsvFile(filePath, originalName);
+ // Conta as linhas válidas primeiro
+ const total = await countValidLines(filePath);
+ if (total === 0) {
+ return res.status(400).json({ error: 'Nenhuma linha válida encontrada no CSV. Verifique se há colunas CEP e Número.' });
+ }
- // normaliza retorno (processCsvFile pode retornar string ou objeto)
- const outputPath = (typeof out === 'string') ? out : (out && out.outputPath) || null;
+ // Cria o job
+ const jobId = createJob(total);
- return res.json({ outputPath });
+ // Inicia o processamento em background (não aguarda)
+ processCsvFile(jobId, filePath, originalName).catch(err => {
+ console.error('Erro no processamento em background:', err);
+ // Em caso de erro, marca o job como falhado
+ require('../service/jobStore.service').failJob(jobId, err.message);
+ });
+
+ // Retorna o jobId imediatamente para acompanhamento em tempo real
+ return res.json({ jobId });
} catch (error) {
- console.error("Erro ao processar CSV:", error && (error.message || error));
- return res.status(500).json({ error: 'Erro ao processar CSV' });
+ console.error("Erro ao iniciar processamento do CSV:", error && (error.message || error));
+ return res.status(500).json({ error: 'Erro ao iniciar processamento do CSV' });
}
}
@@ -51,4 +65,89 @@ async function getJobController(req, res) {
}
}
-module.exports = { consultarViabilidadeController, uploadCsvFile, getJobController };
\ No newline at end of file
+// Controlador para download do CSV processado
+// Verifica se o job está concluído e serve o arquivo para download
+async function downloadCsvController(req, res) {
+ try {
+ const jobId = req.params.jobId;
+ const job = getJob(jobId);
+
+ // Verifica se o job existe, está concluído e tem um link de download
+ if (!job) {
+ return res.status(404).json({ error: 'Job não encontrado' });
+ }
+ if (job.status !== 'done') {
+ return res.status(400).json({ error: 'Processamento ainda não concluído' });
+ }
+ if (!job.download) {
+ return res.status(404).json({ error: 'Arquivo de download não disponível' });
+ }
+
+ // Extrai o nome do arquivo do campo download (ex: '/download/processed_123.csv' -> 'processed_123.csv')
+ const filename = path.basename(job.download);
+ const filePath = path.join(__dirname, '..', 'outputs', filename);
+
+ // Verifica se o arquivo existe no sistema de arquivos
+ if (!fs.existsSync(filePath)) {
+ return res.status(404).json({ error: 'Arquivo não encontrado no servidor' });
+ }
+
+ // Inicia o download do arquivo
+ res.download(filePath, filename, (err) => {
+ if (err) {
+ console.error("Erro ao fazer download do arquivo:", err);
+ // Se o download falhar, envia erro (mas headers já podem ter sido enviados)
+ if (!res.headersSent) {
+ res.status(500).json({ error: 'Erro ao fazer download do arquivo' });
+ }
+ }
+ });
+ } catch (error) {
+ console.error("Erro no controlador de download:", error && (error.message || error));
+ if (!res.headersSent) {
+ res.status(500).json({ error: 'Erro interno no servidor' });
+ }
+ }
+}
+
+// Controlador para download dos modelos de CSV
+// Aceita parâmetro :type ('cep' ou 'geo') para escolher qual modelo baixar
+async function downloadModelController(req, res) {
+ try {
+ const type = req.params.type;
+ let filename;
+
+ // Define o nome do arquivo baseado no tipo
+ if (type === 'cep') {
+ filename = 'modelo.viabilidade-cep.csv';
+ } else if (type === 'geo') {
+ filename = 'modelo.viabilidade-geolocalizacao.csv';
+ } else {
+ return res.status(400).json({ error: 'Tipo de modelo inválido. Use "cep" ou "geo".' });
+ }
+
+ const filePath = path.join(__dirname, '..', 'models', filename);
+
+ // Verifica se o arquivo existe
+ if (!fs.existsSync(filePath)) {
+ return res.status(404).json({ error: 'Arquivo de modelo não encontrado' });
+ }
+
+ // Inicia o download do arquivo
+ res.download(filePath, filename, (err) => {
+ if (err) {
+ console.error("Erro ao fazer download do modelo:", err);
+ if (!res.headersSent) {
+ res.status(500).json({ error: 'Erro ao fazer download do modelo' });
+ }
+ }
+ });
+ } catch (error) {
+ console.error("Erro no controlador de download de modelo:", error && (error.message || error));
+ if (!res.headersSent) {
+ res.status(500).json({ error: 'Erro interno no servidor' });
+ }
+ }
+}
+
+module.exports = { consultarViabilidadeController, uploadCsvFile, getJobController, downloadCsvController, downloadModelController };
\ No newline at end of file
diff --git a/public/main.js b/public/main.js
index c3c43c2..e7d3436 100644
--- a/public/main.js
+++ b/public/main.js
@@ -36,7 +36,7 @@ async function pollJob(jobId) {
if (j.status === 'done') {
bar.style.width = '100%';
bar.innerText = '100%';
- resEl.innerHTML = `Concluído. Baixar CSV processado`;
+ resEl.innerHTML = `Concluído. Baixar CSV processado`;
return;
}
if (j.status === 'error') {
@@ -84,39 +84,29 @@ document.getElementById('btnConsultaCep').addEventListener('click', async () =>
}
});
-// baixar modelo
-// botão que inicia download de todos os modelos individualmente
+// baixar modelos
+// botão que inicia download dos dois modelos simultaneamente via endpoints
document.addEventListener("DOMContentLoaded", () => {
const btn = document.getElementById("card__button-download");
if (!btn) return;
- btn.addEventListener("click", async (e) => {
+ btn.addEventListener("click", (e) => {
e.preventDefault();
- try {
- const resp = await fetch("/download-models/list");
- if (!resp.ok) throw new Error("Não foi possível obter lista de modelos");
- const data = await resp.json();
- const files = data.files || [];
- if (!files.length) {
- alert("Nenhum arquivo de modelo disponível");
- return;
- }
+ // Baixar modelo de CEP
+ const link1 = document.createElement('a');
+ link1.href = '/download-model/cep';
+ link1.download = 'modelo.viabilidade-cep.csv';
+ document.body.appendChild(link1);
+ link1.click();
+ link1.remove();
- // Para cada arquivo, cria um e clica nele para iniciar download individual
- files.forEach((fname) => {
- const url = "/download-model/" + encodeURIComponent(fname);
- const a = document.createElement("a");
- a.href = url;
- a.download = fname;
- // necessário anexar no DOM para funcionar em alguns navegadores
- document.body.appendChild(a);
- a.click();
- a.remove();
- });
- } catch (err) {
- console.error("Erro ao baixar modelos:", err);
- alert("Erro ao iniciar downloads: " + (err.message || err));
- }
+ // Baixar modelo de geolocalização
+ const link2 = document.createElement('a');
+ link2.href = '/download-model/geo';
+ link2.download = 'modelo.viabilidade-geolocalizacao.csv';
+ document.body.appendChild(link2);
+ link2.click();
+ link2.remove();
});
});
\ No newline at end of file
diff --git a/routes/viabilidadeRoutes.js b/routes/viabilidadeRoutes.js
index f50ccd0..32b7218 100644
--- a/routes/viabilidadeRoutes.js
+++ b/routes/viabilidadeRoutes.js
@@ -13,12 +13,13 @@ router.post('/viabilidade', viabilidadeController.consultarViabilidadeController
// rota de upload agora usa multer.single('csvfile')
router.post('/upload', upload.single('csvfile'), viabilidadeController.uploadCsvFile);
-router.get('/status/:jobId', (req, res) => {
- const job = viabilidadeController.getJobController(req.params.jobId);
- if (!job) {
- return res.status(404).json({ error: 'Job não encontrado' });
- }
- res.json(job);
-});
+// Rota para verificar status do job
+router.get('/status/:jobId', viabilidadeController.getJobController);
+
+// Rota para download do CSV processado
+router.get('/download/:jobId', viabilidadeController.downloadCsvController);
+
+// Rota para download dos modelos
+router.get('/download-model/:type', viabilidadeController.downloadModelController);
module.exports = router;
\ No newline at end of file
diff --git a/service/csvService.js b/service/csvService.js
index 64c753f..9360951 100644
--- a/service/csvService.js
+++ b/service/csvService.js
@@ -46,9 +46,11 @@ async function countValidLines(inputPath) {
}
// nova função: processa CSV linha a linha, chama consultarViabilidade e gera CSV de saída
-async function processCsvFile(inputPath) {
+// Recebe jobId já criado no controller
+async function processCsvFile(jobId, inputPath, originalName) {
const total = await countValidLines(inputPath);
- const jobId = createJob(total);
+ // Job já criado no controller
+ // const jobId = createJob(total);
const baseName = path.parse(inputPath).name;
const outputFilename = `processed_${Date.now()}_${baseName}.csv`;
const outputPath = path.join(__dirname, '..', 'outputs', outputFilename);
@@ -137,9 +139,9 @@ async function processCsvFile(inputPath) {
outStream.end();
await once(outStream, 'finish');
- finishJob(jobId, `/download/${path.basename(outputPath)}`);
+ finishJob(jobId, path.basename(outputPath));
- return { jobId, outputPath };
+ return outputPath;
}
-module.exports = { processCsvFile };
\ No newline at end of file
+module.exports = { processCsvFile, countValidLines };
\ No newline at end of file