Commit inicial: GTI Maquina Comercial
App local (Node + SQLite) para captura, tratamento e exportacao de leads comerciais, com dashboard de gestao, login/usuarios e integracao Z-PRO. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import http from 'node:http';
|
||||
import { paths } from '../env.mjs';
|
||||
import { createRouter } from './router.mjs';
|
||||
import { serveStatic } from './static.mjs';
|
||||
import { openSse } from './sse.mjs';
|
||||
import { registerMetaRoutes } from './routes/meta.mjs';
|
||||
import { registerCaptureRoutes } from './routes/capture.mjs';
|
||||
import { registerExtractionRoutes } from './routes/extractions.mjs';
|
||||
import { registerLeadRoutes } from './routes/leads.mjs';
|
||||
import { registerExportRoutes } from './routes/exports.mjs';
|
||||
import { registerAuthRoutes } from './routes/auth.mjs';
|
||||
import { getCookie, getSession, SESSION_COOKIE } from './session.mjs';
|
||||
|
||||
const PUBLIC_API_PATHS = new Set(['/api/auth/login', '/api/health']);
|
||||
|
||||
const BODY_LIMIT = 1024 * 1024; // 1MB
|
||||
|
||||
function readJsonBody(req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let size = 0;
|
||||
const chunks = [];
|
||||
req.on('data', (chunk) => {
|
||||
size += chunk.length;
|
||||
if (size > BODY_LIMIT) {
|
||||
reject(new Error('corpo da requisicao muito grande'));
|
||||
req.destroy();
|
||||
return;
|
||||
}
|
||||
chunks.push(chunk);
|
||||
});
|
||||
req.on('end', () => {
|
||||
if (!chunks.length) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
|
||||
} catch {
|
||||
reject(new Error('JSON invalido no corpo da requisicao'));
|
||||
}
|
||||
});
|
||||
req.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
export function createAppServer({ db, jobs, log = () => {} }) {
|
||||
const router = createRouter();
|
||||
const deps = { db, jobs };
|
||||
|
||||
registerAuthRoutes(router, deps);
|
||||
registerMetaRoutes(router, deps);
|
||||
registerCaptureRoutes(router, deps);
|
||||
registerExtractionRoutes(router, deps);
|
||||
registerLeadRoutes(router, deps);
|
||||
registerExportRoutes(router, deps);
|
||||
|
||||
// SSE de progresso dos jobs (rota especial: gerencia a resposta manualmente)
|
||||
router.add('GET', '/api/jobs/:jobId/events', async (req, res, { params }) => {
|
||||
const sse = openSse(res);
|
||||
const unsubscribe = jobs.subscribe(params.jobId, (event) => {
|
||||
const name = event.t === 'done' ? 'done' : event.t === 'error' ? 'error' : event.t;
|
||||
sse.send(name, event);
|
||||
});
|
||||
|
||||
if (unsubscribe === null) {
|
||||
sse.send('error', { t: 'error', message: 'job nao encontrado (o servidor pode ter sido reiniciado)' });
|
||||
sse.close();
|
||||
return;
|
||||
}
|
||||
|
||||
req.on('close', () => {
|
||||
unsubscribe();
|
||||
sse.close();
|
||||
});
|
||||
});
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, 'http://127.0.0.1');
|
||||
const pathname = url.pathname;
|
||||
|
||||
const json = (status, data) => {
|
||||
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
|
||||
res.end(JSON.stringify(data));
|
||||
};
|
||||
|
||||
const session = getSession(getCookie(req, SESSION_COOKIE));
|
||||
const user = session?.user || null;
|
||||
|
||||
try {
|
||||
if (pathname.startsWith('/api/') && !PUBLIC_API_PATHS.has(pathname) && !user) {
|
||||
json(401, { erro: 'não autenticado' });
|
||||
return;
|
||||
}
|
||||
|
||||
const matched = router.match(req.method, pathname);
|
||||
if (matched) {
|
||||
let body = null;
|
||||
if (req.method === 'POST' || req.method === 'PUT') {
|
||||
body = await readJsonBody(req);
|
||||
}
|
||||
await matched.handler(req, res, { params: matched.params, query: url.searchParams, body, json, user });
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method === 'GET' && !pathname.startsWith('/api/')) {
|
||||
if ((pathname === '/' || pathname === '/index.html') && !user) {
|
||||
res.writeHead(302, { location: '/login.html' });
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
if (serveStatic(paths.public, pathname, res)) return;
|
||||
}
|
||||
|
||||
json(404, { erro: `rota nao encontrada: ${req.method} ${pathname}` });
|
||||
} catch (error) {
|
||||
log(`erro em ${req.method} ${pathname}: ${error.stack || error.message}`);
|
||||
if (!res.headersSent) {
|
||||
json(500, { erro: String(error.message || error) });
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return server;
|
||||
}
|
||||
Reference in New Issue
Block a user