From 95fc180d3c10c054088e7d7f21212f9855ba944c Mon Sep 17 00:00:00 2001 From: alma Date: Sun, 11 Jan 2026 22:42:00 +0100 Subject: [PATCH] notifications big --- .../notifications/rocketchat-adapter.ts | 200 +++++++++++++++++- lib/types/notification.ts | 4 +- 2 files changed, 199 insertions(+), 5 deletions(-) diff --git a/lib/services/notifications/rocketchat-adapter.ts b/lib/services/notifications/rocketchat-adapter.ts index 677ea3e..24e1c2d 100644 --- a/lib/services/notifications/rocketchat-adapter.ts +++ b/lib/services/notifications/rocketchat-adapter.ts @@ -315,9 +315,203 @@ export class RocketChatAdapter implements NotificationAdapter { } async getNotifications(userId: string, page = 1, limit = 20): Promise { - // For now, return empty array - we can implement this later if needed - // The notification count is what matters for the badge - return []; + logger.debug('[ROCKETCHAT_ADAPTER] getNotifications called', { userId, page, limit }); + + try { + const email = await this.getUserEmail(); + if (!email) { + logger.error('[ROCKETCHAT_ADAPTER] Could not get user email'); + return []; + } + + const rocketChatUserId = await this.getRocketChatUserId(email); + if (!rocketChatUserId) { + logger.debug('[ROCKETCHAT_ADAPTER] User not found in RocketChat'); + return []; + } + + const userToken = await this.getUserToken(rocketChatUserId); + if (!userToken) { + logger.error('[ROCKETCHAT_ADAPTER] Could not get user token'); + return []; + } + + const userHeaders = { + 'X-Auth-Token': userToken.authToken, + 'X-User-Id': userToken.userId, + 'Content-Type': 'application/json' + }; + + // Get user's subscriptions with unread messages + const subscriptionsResponse = await fetch(`${this.baseUrl}/api/v1/subscriptions.get`, { + method: 'GET', + headers: userHeaders + }); + + if (!subscriptionsResponse.ok) { + logger.error('[ROCKETCHAT_ADAPTER] Failed to get subscriptions', { + status: subscriptionsResponse.status, + }); + return []; + } + + const subscriptionsData = await subscriptionsResponse.json(); + if (!subscriptionsData.success || !Array.isArray(subscriptionsData.update)) { + logger.error('[ROCKETCHAT_ADAPTER] Invalid subscriptions response'); + return []; + } + + // Filter subscriptions with unread messages + const userSubscriptions = subscriptionsData.update.filter((sub: any) => { + return (sub.unread > 0 || sub.alert) && ['d', 'c', 'p'].includes(sub.t); + }); + + // Get user info for comparison + const usersResponse = await fetch(`${this.baseUrl}/api/v1/users.list`, { + method: 'GET', + headers: { + 'X-Auth-Token': process.env.ROCKET_CHAT_TOKEN!, + 'X-User-Id': process.env.ROCKET_CHAT_USER_ID!, + 'Content-Type': 'application/json' + } + }); + + let currentUser: any = null; + if (usersResponse.ok) { + const usersData = await usersResponse.json(); + if (usersData.success && Array.isArray(usersData.users)) { + const username = email.split('@')[0]; + currentUser = usersData.users.find((u: any) => + u.username === username || u.emails?.some((e: any) => e.address === email) + ); + } + } + + const notifications: Notification[] = []; + + // Fetch messages for each subscription with unread messages + for (const subscription of userSubscriptions) { + try { + // Determine the correct endpoint based on room type + let endpoint; + switch (subscription.t) { + case 'c': + endpoint = 'channels.messages'; + break; + case 'p': + endpoint = 'groups.messages'; + break; + case 'd': + endpoint = 'im.messages'; + break; + default: + continue; + } + + const queryParams = new URLSearchParams({ + roomId: subscription.rid, + count: String(Math.max(subscription.unread, 1)) // Fetch at least 1 message + }); + + const messagesResponse = await fetch( + `${this.baseUrl}/api/v1/${endpoint}?${queryParams}`, { + method: 'GET', + headers: userHeaders + } + ); + + if (!messagesResponse.ok) { + logger.error('[ROCKETCHAT_ADAPTER] Failed to get messages for room', { + roomName: subscription.name, + status: messagesResponse.status, + }); + continue; + } + + const messageData = await messagesResponse.json(); + + if (messageData.success && messageData.messages?.length > 0) { + // Get the latest unread message (skip own messages) + const unreadMessages = messageData.messages.filter((msg: any) => { + // Skip messages sent by current user + if (currentUser && msg.u?._id === currentUser._id) { + return false; + } + // Skip system messages + if (msg.t || !msg.msg) { + return false; + } + return true; + }); + + if (unreadMessages.length > 0) { + const latestMessage = unreadMessages[0]; + const messageUser = latestMessage.u || {}; + const roomName = subscription.fname || subscription.name || subscription.name; + + // Determine room type for link + let roomTypePath = 'channel'; + if (subscription.t === 'd') roomTypePath = 'direct'; + else if (subscription.t === 'p') roomTypePath = 'group'; + + const notification: Notification = { + id: `rocketchat-${latestMessage._id}`, + source: 'rocketchat', + sourceId: latestMessage._id, + type: 'message', + title: subscription.t === 'd' + ? `Message de ${messageUser.name || messageUser.username || 'Utilisateur'}` + : `${messageUser.name || messageUser.username || 'Utilisateur'} dans ${roomName}`, + message: latestMessage.msg || '', + link: `${this.baseUrl}/${roomTypePath}/${subscription.name}`, + isRead: false, // All messages here are unread + timestamp: new Date(latestMessage.ts), + priority: subscription.alert ? 'high' : 'normal', + user: { + id: messageUser._id || '', + name: messageUser.name || messageUser.username || 'Utilisateur', + }, + metadata: { + roomId: subscription.rid, + roomName: roomName, + roomType: subscription.t, + unreadCount: subscription.unread, + } + }; + + notifications.push(notification); + } + } + } catch (error) { + logger.error('[ROCKETCHAT_ADAPTER] Error fetching messages for room', { + roomName: subscription.name, + error: error instanceof Error ? error.message : String(error), + }); + continue; + } + } + + // Sort by timestamp (newest first) and apply pagination + notifications.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); + + const startIndex = (page - 1) * limit; + const endIndex = startIndex + limit; + const paginatedNotifications = notifications.slice(startIndex, endIndex); + + logger.debug('[ROCKETCHAT_ADAPTER] getNotifications result', { + total: notifications.length, + returned: paginatedNotifications.length, + page, + limit, + }); + + return paginatedNotifications; + } catch (error) { + logger.error('[ROCKETCHAT_ADAPTER] Error getting notifications', { + error: error instanceof Error ? error.message : String(error), + }); + return []; + } } async markAsRead(userId: string, notificationId: string): Promise { diff --git a/lib/types/notification.ts b/lib/types/notification.ts index a93a3cd..0711a69 100644 --- a/lib/types/notification.ts +++ b/lib/types/notification.ts @@ -1,8 +1,8 @@ export interface Notification { id: string; - source: 'leantime' | 'nextcloud' | 'gitea' | 'dolibarr' | 'moodle'; + source: 'leantime' | 'rocketchat' | 'email' | 'nextcloud' | 'gitea' | 'dolibarr' | 'moodle'; sourceId: string; // Original ID from the source system - type: string; // Type of notification (e.g., 'task', 'mention', 'comment') + type: string; // Type of notification (e.g., 'task', 'mention', 'comment', 'message') title: string; message: string; link?: string; // Link to view the item in the source system