- Adiciona src/admin-dashboard.js com lazy loading - KPIs: hoje vs media 30 dias por fluxo - Graficos de tendencia 30 dias (consolidado e por fluxo) - Ranking top 5 agentes com filtro de periodo - Adiciona sistema de cache (src/cache.js) - Cache com TTL e auto-refresh periodico (5-10min) - APIs: /admin/api/kpis, /admin/api/trend, /admin/api/top-agentes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
165 lines
3.6 KiB
JavaScript
165 lines
3.6 KiB
JavaScript
/**
|
|
* Cache System - Stale-While-Revalidate
|
|
*
|
|
* Mantém dados em memória com TTL e atualização periódica.
|
|
* Retorna dados do cache imediatamente enquanto atualiza em background.
|
|
*/
|
|
|
|
const cache = new Map();
|
|
const refreshIntervals = new Map();
|
|
|
|
const DEFAULT_TTL = 5 * 60 * 1000; // 5 minutos
|
|
const REFRESH_INTERVAL = 5 * 60 * 1000; // Atualiza a cada 5 minutos
|
|
|
|
/**
|
|
* Armazena valor no cache
|
|
* @param {string} key - Chave do cache
|
|
* @param {any} value - Valor a armazenar
|
|
* @param {number} ttl - Time-to-live em ms (opcional)
|
|
*/
|
|
function set(key, value, ttl = DEFAULT_TTL) {
|
|
cache.set(key, {
|
|
value,
|
|
timestamp: Date.now(),
|
|
ttl
|
|
});
|
|
console.log(`[Cache] SET ${key} (TTL: ${ttl/1000}s)`);
|
|
}
|
|
|
|
/**
|
|
* Recupera valor do cache
|
|
* @param {string} key - Chave do cache
|
|
* @returns {any|null} - Valor ou null se não encontrado/expirado
|
|
*/
|
|
function get(key) {
|
|
const entry = cache.get(key);
|
|
if (!entry) return null;
|
|
|
|
const age = Date.now() - entry.timestamp;
|
|
const isStale = age > entry.ttl;
|
|
|
|
if (isStale) {
|
|
console.log(`[Cache] STALE ${key} (age: ${Math.round(age/1000)}s)`);
|
|
}
|
|
|
|
return {
|
|
value: entry.value,
|
|
isStale,
|
|
age: Math.round(age / 1000)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Verifica se cache existe (mesmo que stale)
|
|
*/
|
|
function has(key) {
|
|
return cache.has(key);
|
|
}
|
|
|
|
/**
|
|
* Remove do cache
|
|
*/
|
|
function del(key) {
|
|
cache.delete(key);
|
|
if (refreshIntervals.has(key)) {
|
|
clearInterval(refreshIntervals.get(key));
|
|
refreshIntervals.delete(key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Limpa todo o cache
|
|
*/
|
|
function clear() {
|
|
cache.clear();
|
|
refreshIntervals.forEach(interval => clearInterval(interval));
|
|
refreshIntervals.clear();
|
|
}
|
|
|
|
/**
|
|
* Registra função para refresh periódico
|
|
* @param {string} key - Chave do cache
|
|
* @param {Function} fetchFn - Função async que busca os dados
|
|
* @param {number} interval - Intervalo de refresh em ms
|
|
*/
|
|
function registerAutoRefresh(key, fetchFn, interval = REFRESH_INTERVAL) {
|
|
// Limpa interval anterior se existir
|
|
if (refreshIntervals.has(key)) {
|
|
clearInterval(refreshIntervals.get(key));
|
|
}
|
|
|
|
// Função de refresh
|
|
const refresh = async () => {
|
|
try {
|
|
console.log(`[Cache] REFRESH ${key}`);
|
|
const value = await fetchFn();
|
|
set(key, value);
|
|
} catch (err) {
|
|
console.error(`[Cache] REFRESH ERROR ${key}:`, err.message);
|
|
}
|
|
};
|
|
|
|
// Faz refresh inicial
|
|
refresh();
|
|
|
|
// Agenda refreshes periódicos
|
|
const intervalId = setInterval(refresh, interval);
|
|
refreshIntervals.set(key, intervalId);
|
|
|
|
console.log(`[Cache] AUTO-REFRESH registered for ${key} (every ${interval/1000}s)`);
|
|
}
|
|
|
|
/**
|
|
* Helper: get-or-fetch com stale-while-revalidate
|
|
* Retorna cache (mesmo stale) imediatamente e atualiza em background se stale
|
|
*/
|
|
async function getOrFetch(key, fetchFn, ttl = DEFAULT_TTL) {
|
|
const cached = get(key);
|
|
|
|
if (cached) {
|
|
// Se stale, atualiza em background
|
|
if (cached.isStale) {
|
|
fetchFn().then(value => set(key, value, ttl)).catch(err => {
|
|
console.error(`[Cache] Background fetch error for ${key}:`, err.message);
|
|
});
|
|
}
|
|
return cached.value;
|
|
}
|
|
|
|
// Não tem cache, busca e aguarda
|
|
const value = await fetchFn();
|
|
set(key, value, ttl);
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Stats do cache
|
|
*/
|
|
function stats() {
|
|
const entries = [];
|
|
cache.forEach((entry, key) => {
|
|
const age = Date.now() - entry.timestamp;
|
|
entries.push({
|
|
key,
|
|
age: Math.round(age / 1000),
|
|
ttl: entry.ttl / 1000,
|
|
isStale: age > entry.ttl
|
|
});
|
|
});
|
|
return {
|
|
size: cache.size,
|
|
entries
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
set,
|
|
get,
|
|
has,
|
|
del,
|
|
clear,
|
|
registerAutoRefresh,
|
|
getOrFetch,
|
|
stats
|
|
};
|