fix:logs and new articles
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
This commit is contained in:
@@ -63,6 +63,15 @@ async function initRedis() {
|
||||
redisClient.on('error', (err) => console.error('Redis Client Error', err));
|
||||
await redisClient.connect();
|
||||
console.log('✅ Conectado a Redis');
|
||||
|
||||
// Inicializar claves conocidas para evitar notificar artículos existentes
|
||||
try {
|
||||
const initialKeys = await redisClient.keys('notified:*');
|
||||
notifiedArticleKeys = new Set(initialKeys);
|
||||
console.log(`📋 ${notifiedArticleKeys.size} artículos ya notificados detectados`);
|
||||
} catch (error) {
|
||||
console.error('Error inicializando claves de artículos:', error.message);
|
||||
}
|
||||
} else {
|
||||
console.log('ℹ️ Redis no configurado, usando modo memoria');
|
||||
}
|
||||
@@ -446,7 +455,7 @@ app.get('/api/articles/search', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Obtener logs (últimas líneas)
|
||||
// Obtener logs (últimas líneas o nuevas líneas desde un número de línea)
|
||||
app.get('/api/logs', (req, res) => {
|
||||
try {
|
||||
// Intentar múltiples ubicaciones posibles
|
||||
@@ -457,7 +466,7 @@ app.get('/api/logs', (req, res) => {
|
||||
if (existsSync(altPath)) {
|
||||
logFile = altPath;
|
||||
} else {
|
||||
return res.json({ logs: [] });
|
||||
return res.json({ logs: [], totalLines: 0, lastLineNumber: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,19 +474,37 @@ app.get('/api/logs', (req, res) => {
|
||||
try {
|
||||
const stats = statSync(logFile);
|
||||
if (stats.isDirectory()) {
|
||||
return res.json({ logs: ['Error: monitor.log es un directorio. Por favor, elimínalo y reinicia.'] });
|
||||
return res.json({ logs: ['Error: monitor.log es un directorio. Por favor, elimínalo y reinicia.'], totalLines: 0, lastLineNumber: 0 });
|
||||
}
|
||||
} catch (e) {
|
||||
return res.json({ logs: [] });
|
||||
return res.json({ logs: [], totalLines: 0, lastLineNumber: 0 });
|
||||
}
|
||||
|
||||
const logs = readFileSync(logFile, 'utf8');
|
||||
const lines = logs.split('\n').filter(l => l.trim());
|
||||
const limit = parseInt(req.query.limit) || 100;
|
||||
const lastLines = lines.slice(-limit);
|
||||
// Mantener orden natural: más antiguo a más reciente
|
||||
const logsContent = readFileSync(logFile, 'utf8');
|
||||
const allLines = logsContent.split('\n').filter(l => l.trim());
|
||||
const totalLines = allLines.length;
|
||||
|
||||
res.json({ logs: lastLines });
|
||||
// Si se proporciona since (número de línea desde el que empezar), devolver solo las nuevas
|
||||
const sinceLine = parseInt(req.query.since) || 0;
|
||||
|
||||
if (sinceLine > 0 && sinceLine < totalLines) {
|
||||
// Devolver solo las líneas nuevas después de sinceLine
|
||||
const newLines = allLines.slice(sinceLine);
|
||||
return res.json({
|
||||
logs: newLines,
|
||||
totalLines: totalLines,
|
||||
lastLineNumber: totalLines - 1 // Índice de la última línea
|
||||
});
|
||||
} else {
|
||||
// Carga inicial: devolver las últimas líneas
|
||||
const limit = parseInt(req.query.limit) || 500;
|
||||
const lastLines = allLines.slice(-limit);
|
||||
return res.json({
|
||||
logs: lastLines,
|
||||
totalLines: totalLines,
|
||||
lastLineNumber: totalLines - 1 // Índice de la última línea
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
@@ -580,8 +607,8 @@ if (!existsSync(watchLogPath)) {
|
||||
}
|
||||
}
|
||||
|
||||
// Watch files for changes (ya no vigilamos FAVORITES_PATH porque usa Redis)
|
||||
const watcher = watch([WORKERS_PATH, watchLogPath].filter(p => existsSync(p)), {
|
||||
// Watch files for changes (ya no vigilamos logs porque usa polling)
|
||||
const watcher = watch([WORKERS_PATH].filter(p => existsSync(p)), {
|
||||
persistent: true,
|
||||
ignoreInitial: true,
|
||||
});
|
||||
@@ -591,17 +618,100 @@ watcher.on('change', async (path) => {
|
||||
if (path === WORKERS_PATH) {
|
||||
const workers = readJSON(WORKERS_PATH);
|
||||
broadcast({ type: 'workers_updated', data: workers });
|
||||
} else if (path === LOG_PATH) {
|
||||
broadcast({ type: 'logs_updated' });
|
||||
}
|
||||
});
|
||||
|
||||
// Rastrear artículos ya notificados para detectar nuevos
|
||||
let notifiedArticleKeys = new Set();
|
||||
let articlesCheckInterval = null;
|
||||
|
||||
// Función para detectar y enviar artículos nuevos
|
||||
async function checkForNewArticles() {
|
||||
if (!redisClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const currentKeys = await redisClient.keys('notified:*');
|
||||
const currentKeysSet = new Set(currentKeys);
|
||||
|
||||
// Encontrar claves nuevas
|
||||
const newKeys = currentKeys.filter(key => !notifiedArticleKeys.has(key));
|
||||
|
||||
if (newKeys.length > 0) {
|
||||
// Obtener los artículos nuevos
|
||||
const newArticles = [];
|
||||
for (const key of newKeys) {
|
||||
try {
|
||||
const value = await redisClient.get(key);
|
||||
if (value) {
|
||||
// Intentar parsear como JSON
|
||||
let articleData = {};
|
||||
try {
|
||||
articleData = JSON.parse(value);
|
||||
} catch (e) {
|
||||
// Si no es JSON válido, extraer información de la key
|
||||
const parts = key.split(':');
|
||||
if (parts.length >= 3) {
|
||||
articleData = {
|
||||
platform: parts[1],
|
||||
id: parts.slice(2).join(':'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Añadir información adicional si está disponible
|
||||
if (articleData.platform && articleData.id) {
|
||||
newArticles.push({
|
||||
platform: articleData.platform || 'unknown',
|
||||
id: articleData.id || 'unknown',
|
||||
title: articleData.title || null,
|
||||
price: articleData.price || null,
|
||||
currency: articleData.currency || '€',
|
||||
url: articleData.url || null,
|
||||
images: articleData.images || [],
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error obteniendo artículo de Redis (${key}):`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Enviar artículos nuevos por WebSocket
|
||||
if (newArticles.length > 0) {
|
||||
broadcast({
|
||||
type: 'new_articles',
|
||||
data: newArticles
|
||||
});
|
||||
}
|
||||
|
||||
// Actualizar el set de claves notificadas
|
||||
notifiedArticleKeys = currentKeysSet;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error verificando artículos nuevos:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar el check de artículos cuando Redis esté listo
|
||||
async function startArticleMonitoring() {
|
||||
if (redisClient) {
|
||||
// Iniciar intervalo para verificar nuevos artículos cada 3 segundos
|
||||
articlesCheckInterval = setInterval(checkForNewArticles, 3000);
|
||||
console.log('✅ Monitoreo de artículos nuevos iniciado');
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar servidor
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
async function startServer() {
|
||||
await initRedis();
|
||||
|
||||
// Iniciar monitoreo de artículos nuevos
|
||||
await startArticleMonitoring();
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`🚀 Servidor backend ejecutándose en http://localhost:${PORT}`);
|
||||
console.log(`📡 WebSocket disponible en ws://localhost:${PORT}`);
|
||||
|
||||
Reference in New Issue
Block a user