fixes: favoritos y mas cosas

Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
This commit is contained in:
Omar Sánchez Pizarro
2026-01-20 20:06:28 +01:00
parent d5f0ba4e03
commit 05f0455744
20 changed files with 1691 additions and 350 deletions

View File

@@ -88,6 +88,8 @@ async function createIndexes() {
// Índices para sesiones (con TTL)
await db.collection('sessions').createIndex({ token: 1 }, { unique: true });
await db.collection('sessions').createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 });
await db.collection('sessions').createIndex({ username: 1, fingerprint: 1 });
await db.collection('sessions').createIndex({ fingerprint: 1 });
// Índices para workers
await db.collection('workers').createIndex({ username: 1 });
@@ -259,6 +261,54 @@ export function getRateLimiter() {
return rateLimiter;
}
// Obtener información del rate limiter y bloqueos (para memoria)
export async function getRateLimiterInfo() {
if (!rateLimiter) {
return {
enabled: false,
type: 'none',
blocks: [],
stats: {
totalBlocks: 0,
activeBlocks: 0,
},
};
}
try {
// RateLimiterMemory no expone directamente las claves bloqueadas
// Necesitamos usar una aproximación diferente
// Por ahora, retornamos información básica
return {
enabled: true,
type: 'memory',
blocks: [],
stats: {
totalBlocks: 0,
activeBlocks: 0,
},
config: {
points: RATE_LIMIT.POINTS,
duration: RATE_LIMIT.DURATION,
blockDuration: RATE_LIMIT.BLOCK_DURATION,
},
note: 'Rate limiter en memoria no expone información detallada de bloqueos',
};
} catch (error) {
console.error('Error obteniendo información del rate limiter:', error.message);
return {
enabled: true,
type: 'memory',
blocks: [],
stats: {
totalBlocks: 0,
activeBlocks: 0,
},
error: error.message,
};
}
}
export function getConfig() {
return config;
}
@@ -303,6 +353,9 @@ export async function getNotifiedArticles(filter = {}) {
.sort({ createdAt: -1, modified_at: -1 })
.toArray();
// Obtener el username del usuario actual para incluir su is_favorite
const currentUsername = filter.currentUsername || filter.username;
// Filtrar y transformar artículos según el usuario solicitado
return articles.map(article => {
// Si hay filtro de username, solo devolver el user_info correspondiente
@@ -350,7 +403,15 @@ export async function getNotifiedArticles(filter = {}) {
if (firstUserInfo) {
result.username = firstUserInfo.username;
result.worker_name = firstUserInfo.worker_name;
result.is_favorite = firstUserInfo.is_favorite || false;
// Si hay un usuario actual, buscar su is_favorite específico, sino usar el del primer user_info
if (currentUsername) {
const currentUserInfo = (article.user_info || []).find(
ui => ui.username === currentUsername
);
result.is_favorite = currentUserInfo ? (currentUserInfo.is_favorite || false) : false;
} else {
result.is_favorite = firstUserInfo.is_favorite || false;
}
result.notifiedAt =
firstUserInfo.notified_at?.getTime?.() ||
(typeof firstUserInfo.notified_at === 'number' ? firstUserInfo.notified_at : null) ||
@@ -579,6 +640,9 @@ export async function searchArticles(searchQuery, filter = {}, searchMode = 'AND
.sort({ createdAt: -1, modified_at: -1 })
.toArray();
// Obtener el username del usuario actual para incluir su is_favorite
const currentUsername = filter.currentUsername || filter.username;
// Transformar artículos según el usuario solicitado (similar a getNotifiedArticles)
return articles.map(article => {
let relevantUserInfo = null;
@@ -619,7 +683,15 @@ export async function searchArticles(searchQuery, filter = {}, searchMode = 'AND
if (firstUserInfo) {
result.username = firstUserInfo.username;
result.worker_name = firstUserInfo.worker_name;
result.is_favorite = firstUserInfo.is_favorite || false;
// Si hay un usuario actual, buscar su is_favorite específico, sino usar el del primer user_info
if (currentUsername) {
const currentUserInfo = (article.user_info || []).find(
ui => ui.username === currentUsername
);
result.is_favorite = currentUserInfo ? (currentUserInfo.is_favorite || false) : false;
} else {
result.is_favorite = firstUserInfo.is_favorite || false;
}
result.notifiedAt =
firstUserInfo.notified_at?.getTime?.() ||
(typeof firstUserInfo.notified_at === 'number' ? firstUserInfo.notified_at : null) ||
@@ -978,7 +1050,7 @@ export async function setTelegramConfig(username, telegramConfig) {
}
// Funciones para sesiones
export async function createSession(username) {
export async function createSession(username, fingerprint = null, deviceInfo = null) {
if (!db) {
throw new Error('MongoDB no está disponible');
}
@@ -989,9 +1061,22 @@ export async function createSession(username) {
try {
const sessionsCollection = db.collection('sessions');
// Si hay fingerprint, eliminar sesiones anteriores del mismo dispositivo/usuario
if (fingerprint) {
await sessionsCollection.deleteMany({
username,
fingerprint,
expiresAt: { $gt: new Date() } // Solo eliminar sesiones activas
});
}
// Crear nueva sesión con fingerprint
await sessionsCollection.insertOne({
token,
username,
fingerprint: fingerprint || null,
deviceInfo: deviceInfo || null,
createdAt: new Date(),
expiresAt,
});
@@ -1046,15 +1131,95 @@ export async function deleteUserSessions(username) {
}
}
export async function getAllSessions() {
if (!db) {
return [];
}
try {
const sessionsCollection = db.collection('sessions');
const sessions = await sessionsCollection.find({}).toArray();
return sessions.map(session => ({
token: session.token,
username: session.username,
fingerprint: session.fingerprint || null,
deviceInfo: session.deviceInfo || null,
createdAt: session.createdAt,
expiresAt: session.expiresAt,
isExpired: session.expiresAt ? new Date(session.expiresAt) < new Date() : false,
}));
} catch (error) {
console.error('Error obteniendo todas las sesiones:', error.message);
return [];
}
}
// Funciones para artículos
export async function getArticle(platform, id) {
export async function getArticle(platform, id, currentUsername = null) {
if (!db) {
return null;
}
try {
const articlesCollection = db.collection('articles');
return await articlesCollection.findOne({ platform, id });
const article = await articlesCollection.findOne({ platform, id });
if (!article) {
return null;
}
// Transformar el artículo similar a getNotifiedArticles para incluir is_favorite
const result = {
...article,
_id: article._id.toString(),
expiresAt: article.expiresAt?.getTime() || null,
};
const fallbackTime =
article.createdAt?.getTime?.() ||
(typeof article.createdAt === 'number' ? article.createdAt : null) ||
article.modified_at?.getTime?.() ||
null;
// Si hay un usuario actual, buscar su is_favorite específico
if (currentUsername) {
const currentUserInfo = (article.user_info || []).find(
ui => ui.username === currentUsername
);
if (currentUserInfo) {
result.is_favorite = currentUserInfo.is_favorite || false;
result.notifiedAt =
currentUserInfo.notified_at?.getTime?.() ||
(typeof currentUserInfo.notified_at === 'number' ? currentUserInfo.notified_at : null) ||
fallbackTime;
} else {
// Si no hay user_info para este usuario, usar false
result.is_favorite = false;
}
} else {
// Sin usuario específico, usar el primer user_info o datos generales
const firstUserInfo = (article.user_info || [])[0];
if (firstUserInfo) {
result.username = firstUserInfo.username;
result.worker_name = firstUserInfo.worker_name;
result.is_favorite = firstUserInfo.is_favorite || false;
result.notifiedAt =
firstUserInfo.notified_at?.getTime?.() ||
(typeof firstUserInfo.notified_at === 'number' ? firstUserInfo.notified_at : null) ||
fallbackTime;
} else {
// Compatibilidad con estructura antigua
result.username = article.username;
result.worker_name = article.worker_name;
result.is_favorite = article.is_favorite || false;
result.notifiedAt =
article.notifiedAt?.getTime?.() ||
(typeof article.notifiedAt === 'number' ? article.notifiedAt : null) ||
fallbackTime;
}
}
return result;
} catch (error) {
console.error('Error obteniendo artículo:', error.message);
return null;