Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> git-subtree-dir: gti-maquina-comercial git-subtree-mainline:93ca82979egit-subtree-split:b77484946f
97 lines
3.3 KiB
JavaScript
97 lines
3.3 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { paths } from '../../env.mjs';
|
|
import { countLeads } from '../../treatment/queries.mjs';
|
|
|
|
const TIPOS = new Set(['excel', 'zpro-oportunidades', 'zpro-bulk']);
|
|
|
|
function parseExportRow(row) {
|
|
return {
|
|
...row,
|
|
filtro: JSON.parse(row.filtro_json || '{}'),
|
|
params: JSON.parse(row.params_json || '{}'),
|
|
arquivos: JSON.parse(row.arquivos_json || '[]'),
|
|
filtro_json: undefined,
|
|
params_json: undefined,
|
|
arquivos_json: undefined
|
|
};
|
|
}
|
|
|
|
export function registerExportRoutes(router, { db, jobs }) {
|
|
router.add('POST', '/api/exports', async (req, res, { json, body }) => {
|
|
const tipo = String(body?.tipo || '');
|
|
if (!TIPOS.has(tipo)) {
|
|
json(400, { erro: `tipo invalido. Use: ${[...TIPOS].join(', ')}` });
|
|
return;
|
|
}
|
|
|
|
const filtro = body?.filtro && typeof body.filtro === 'object' ? body.filtro : {};
|
|
const params = body?.params && typeof body.params === 'object' ? body.params : {};
|
|
|
|
const total = countLeads(db, filtro);
|
|
if (!total) {
|
|
json(400, { erro: 'nenhum lead corresponde ao filtro selecionado' });
|
|
return;
|
|
}
|
|
|
|
const inserted = db.prepare(
|
|
"INSERT INTO exports (tipo, filtro_json, params_json, status) VALUES (?, ?, ?, 'pending')"
|
|
).run(tipo, JSON.stringify(filtro), JSON.stringify(params));
|
|
const exportId = Number(inserted.lastInsertRowid);
|
|
|
|
const jobId = jobs.startJob({
|
|
type: 'exportacao',
|
|
script: 'src/export/export-job.mjs',
|
|
args: [`--export-id=${exportId}`],
|
|
meta: { exportId, tipo, total }
|
|
});
|
|
db.prepare('UPDATE exports SET job_id = ? WHERE id = ?').run(jobId, exportId);
|
|
|
|
json(202, { exportId, jobId, totalEstimado: total });
|
|
});
|
|
|
|
router.add('GET', '/api/exports', async (req, res, { json, query }) => {
|
|
const limit = Math.min(100, Math.max(1, Number(query.get('limit')) || 20));
|
|
const rows = db.prepare('SELECT * FROM exports ORDER BY id DESC LIMIT ?').all(limit);
|
|
json(200, { exports: rows.map(parseExportRow) });
|
|
});
|
|
|
|
router.add('GET', '/api/exports/:id', async (req, res, { json, params }) => {
|
|
const row = db.prepare('SELECT * FROM exports WHERE id = ?').get(Number(params.id));
|
|
if (!row) {
|
|
json(404, { erro: 'exportacao nao encontrada' });
|
|
return;
|
|
}
|
|
json(200, parseExportRow(row));
|
|
});
|
|
|
|
router.add('GET', '/api/exports/:id/download', async (req, res, { json, params, query }) => {
|
|
const row = db.prepare('SELECT * FROM exports WHERE id = ?').get(Number(params.id));
|
|
if (!row) {
|
|
json(404, { erro: 'exportacao nao encontrada' });
|
|
return;
|
|
}
|
|
|
|
const arquivos = JSON.parse(row.arquivos_json || '[]');
|
|
const nome = String(query.get('arquivo') || arquivos[0] || '');
|
|
if (!arquivos.includes(nome)) {
|
|
json(400, { erro: 'arquivo nao pertence a esta exportacao' });
|
|
return;
|
|
}
|
|
|
|
const dir = path.resolve(paths.exports, `export-${row.id}`);
|
|
const resolved = path.resolve(dir, nome);
|
|
if (!resolved.startsWith(dir + path.sep) || !fs.existsSync(resolved)) {
|
|
json(404, { erro: 'arquivo nao encontrado' });
|
|
return;
|
|
}
|
|
|
|
res.writeHead(200, {
|
|
'content-type': 'application/octet-stream',
|
|
'content-disposition': `attachment; filename="${encodeURIComponent(nome)}"`,
|
|
'content-length': fs.statSync(resolved).size
|
|
});
|
|
fs.createReadStream(resolved).pipe(res);
|
|
});
|
|
}
|