feat: rediseño UI completo + infra email + stats

This commit is contained in:
hacklab 2026-04-20 00:46:00 +02:00
parent 93d75ddafe
commit 24401c0ee5
37 changed files with 2162 additions and 412 deletions

55
api/services/stats.js Normal file
View file

@ -0,0 +1,55 @@
'use strict';
const fs = require('fs');
const path = require('path');
const DATA_FILE = path.join(__dirname, '..', 'data', 'stats.json');
const EMPTY = () => ({
total_sent: 0,
total_redirected: 0,
total_errors: 0,
total_searches: 0,
by_provider: Object.create(null), // null prototype — previene prototype pollution
updated: null,
});
function load() {
try {
return JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
} catch {
return EMPTY();
}
}
function save(data) {
const dir = path.dirname(DATA_FILE);
fs.mkdirSync(dir, { recursive: true });
const tmp = DATA_FILE + '.tmp';
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf8');
fs.renameSync(tmp, DATA_FILE); // atómico en mismo filesystem
}
function record({ type, provider }) {
const s = load();
if (type === 'sent') s.total_sent++;
else if (type === 'redirected') s.total_redirected++;
else if (type === 'error') s.total_errors++;
else if (type === 'search') s.total_searches++;
if (provider && (type === 'sent' || type === 'redirected')) {
if (!s.by_provider[provider]) s.by_provider[provider] = { sent: 0, redirected: 0 };
if (type === 'sent') s.by_provider[provider].sent++;
if (type === 'redirected') s.by_provider[provider].redirected++;
}
s.updated = new Date().toISOString().slice(0, 10);
save(s);
}
function get() {
return load();
}
module.exports = { record, get };