From fc1dd882b654d1104a583e91d65390b2acd407fa Mon Sep 17 00:00:00 2001 From: alma Date: Sun, 4 May 2025 11:25:36 +0200 Subject: [PATCH] notifications --- app/api/debug/notifications/route.ts | 83 ++++++ .../notifications/leantime-adapter.ts | 244 ++++++++++++++---- .../notifications/notification-service.ts | 93 +++++-- 3 files changed, 346 insertions(+), 74 deletions(-) create mode 100644 app/api/debug/notifications/route.ts diff --git a/app/api/debug/notifications/route.ts b/app/api/debug/notifications/route.ts new file mode 100644 index 00000000..9ce804d9 --- /dev/null +++ b/app/api/debug/notifications/route.ts @@ -0,0 +1,83 @@ +import { NextResponse } from 'next/server'; +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/app/api/auth/[...nextauth]/route'; +import { NotificationService } from '@/lib/services/notifications/notification-service'; + +// GET /api/debug/notifications +export async function GET(request: Request) { + try { + // Authenticate user + const session = await getServerSession(authOptions); + if (!session || !session.user?.id) { + return NextResponse.json( + { error: "Not authenticated" }, + { status: 401 } + ); + } + + const userId = session.user.id; + console.log(`[DEBUG] Testing notifications for user ${userId}`); + + // Get environment variables status + const envStatus = { + LEANTIME_API_URL: process.env.LEANTIME_API_URL ? 'Set' : 'Not set', + LEANTIME_TOKEN: process.env.LEANTIME_TOKEN ? `Set (length: ${process.env.LEANTIME_TOKEN.length})` : 'Not set', + LEANTIME_API_KEY: process.env.LEANTIME_API_KEY ? `Set (length: ${process.env.LEANTIME_API_KEY.length})` : 'Not set', + KEYCLOAK_BASE_URL: process.env.KEYCLOAK_BASE_URL ? 'Set' : 'Not set', + KEYCLOAK_REALM: process.env.KEYCLOAK_REALM || 'Not set', + KEYCLOAK_ADMIN_CLIENT_ID: process.env.KEYCLOAK_ADMIN_CLIENT_ID ? 'Set' : 'Not set', + KEYCLOAK_ADMIN_CLIENT_SECRET: process.env.KEYCLOAK_ADMIN_CLIENT_SECRET ? 'Set (masked)' : 'Not set', + }; + + // Get user information + console.log(`[DEBUG] Getting user info for ${userId}`); + let userInfo = { + id: userId, + email: session.user.email || 'Unknown' + }; + + // Test notification service + console.log(`[DEBUG] Testing notification service`); + const notificationService = NotificationService.getInstance(); + + // Get notification count + console.log(`[DEBUG] Getting notification count`); + const startTimeCount = Date.now(); + const notificationCount = await notificationService.getNotificationCount(userId); + const timeForCount = Date.now() - startTimeCount; + + // Get notifications + console.log(`[DEBUG] Getting notifications`); + const startTimeNotifications = Date.now(); + const notifications = await notificationService.getNotifications(userId, 1, 10); + const timeForNotifications = Date.now() - startTimeNotifications; + + return NextResponse.json({ + success: true, + timestamp: new Date().toISOString(), + userInfo, + environmentVariables: envStatus, + notificationServiceTest: { + count: { + result: notificationCount, + timeMs: timeForCount + }, + notifications: { + count: notifications.length, + timeMs: timeForNotifications, + samples: notifications.slice(0, 3) // Only return first 3 notifications as samples + } + } + }); + } catch (error: any) { + console.error('[DEBUG] Error in debug notifications API:', error); + return NextResponse.json( + { + error: "Internal server error", + message: error.message, + stack: process.env.NODE_ENV === 'development' ? error.stack : undefined + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/lib/services/notifications/leantime-adapter.ts b/lib/services/notifications/leantime-adapter.ts index ac037614..cd3f73a8 100644 --- a/lib/services/notifications/leantime-adapter.ts +++ b/lib/services/notifications/leantime-adapter.ts @@ -23,20 +23,33 @@ export class LeantimeAdapter implements NotificationAdapter { // Load from environment variables, matching the pattern used in other Leantime integrations this.apiUrl = process.env.LEANTIME_API_URL || ''; this.apiToken = process.env.LEANTIME_TOKEN || ''; + + // Log configuration on initialization + console.log('[LEANTIME_ADAPTER] Initialized with:', { + apiUrlConfigured: !!this.apiUrl, + apiTokenConfigured: !!this.apiToken, + apiUrlPrefix: this.apiUrl ? this.apiUrl.substring(0, 30) + '...' : 'not set', + }); } async getNotifications(userId: string, page = 1, limit = 20): Promise { + console.log(`[LEANTIME_ADAPTER] getNotifications called for userId: ${userId}, page: ${page}, limit: ${limit}`); + try { // First get the Leantime user ID from email const email = await this.getUserEmail(userId); + console.log(`[LEANTIME_ADAPTER] Retrieved email for userId ${userId}:`, email || 'null'); + if (!email) { - console.error('Could not get user email for userId:', userId); + console.error('[LEANTIME_ADAPTER] Could not get user email for userId:', userId); return []; } const leantimeUserId = await this.getLeantimeUserId(email); + console.log(`[LEANTIME_ADAPTER] Retrieved Leantime userId for email ${email}:`, leantimeUserId || 'null'); + if (!leantimeUserId) { - console.error('User not found in Leantime:', email); + console.error('[LEANTIME_ADAPTER] User not found in Leantime:', email); return []; } @@ -44,88 +57,137 @@ export class LeantimeAdapter implements NotificationAdapter { const offset = (page - 1) * limit; // Make request to Leantime API using jsonrpc + console.log('[LEANTIME_ADAPTER] Sending request to Leantime API for notifications'); + const jsonRpcBody = { + jsonrpc: '2.0', + method: 'leantime.rpc.notifications.getAll', + params: { + userId: leantimeUserId, + limit: limit, + offset: offset + }, + id: 1 + }; + console.log('[LEANTIME_ADAPTER] Request body:', JSON.stringify(jsonRpcBody)); + const response = await fetch(`${this.apiUrl}/api/jsonrpc`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': this.apiToken }, - body: JSON.stringify({ - jsonrpc: '2.0', - method: 'leantime.rpc.notifications.getAll', - params: { - userId: leantimeUserId, - limit: limit, - offset: offset - }, - id: 1 - }) + body: JSON.stringify(jsonRpcBody) }); + console.log('[LEANTIME_ADAPTER] Response status:', response.status); + if (!response.ok) { - console.error(`Failed to fetch Leantime notifications: ${response.status}`); + const errorText = await response.text(); + console.error('[LEANTIME_ADAPTER] Failed to fetch Leantime notifications:', { + status: response.status, + body: errorText.substring(0, 200) + (errorText.length > 200 ? '...' : '') + }); return []; } - const data = await response.json(); + const responseText = await response.text(); + console.log('[LEANTIME_ADAPTER] Raw response:', responseText.substring(0, 200) + (responseText.length > 200 ? '...' : '')); + + const data = JSON.parse(responseText); + console.log('[LEANTIME_ADAPTER] Parsed response data:', { + hasResult: !!data.result, + resultIsArray: Array.isArray(data.result), + resultLength: Array.isArray(data.result) ? data.result.length : 'n/a', + error: data.error + }); + if (!data.result || !Array.isArray(data.result)) { - console.error('Invalid response format from Leantime notifications API'); + console.error('[LEANTIME_ADAPTER] Invalid response format from Leantime notifications API'); return []; } - return this.transformNotifications(data.result, userId); + const notifications = this.transformNotifications(data.result, userId); + console.log('[LEANTIME_ADAPTER] Transformed notifications count:', notifications.length); + return notifications; } catch (error) { - console.error('Error fetching Leantime notifications:', error); + console.error('[LEANTIME_ADAPTER] Error fetching Leantime notifications:', error); return []; } } async getNotificationCount(userId: string): Promise { + console.log(`[LEANTIME_ADAPTER] getNotificationCount called for userId: ${userId}`); + try { // First get the Leantime user ID from email const email = await this.getUserEmail(userId); + console.log(`[LEANTIME_ADAPTER] Retrieved email for userId ${userId}:`, email || 'null'); + if (!email) { - console.error('Could not get user email for userId:', userId); + console.error('[LEANTIME_ADAPTER] Could not get user email for userId:', userId); return this.getEmptyCount(); } const leantimeUserId = await this.getLeantimeUserId(email); + console.log(`[LEANTIME_ADAPTER] Retrieved Leantime userId for email ${email}:`, leantimeUserId || 'null'); + if (!leantimeUserId) { - console.error('User not found in Leantime:', email); + console.error('[LEANTIME_ADAPTER] User not found in Leantime:', email); return this.getEmptyCount(); } // Make request to Leantime API using jsonrpc + console.log('[LEANTIME_ADAPTER] Sending request to Leantime API for notification count'); + const jsonRpcBody = { + jsonrpc: '2.0', + method: 'leantime.rpc.notifications.getNotificationCount', + params: { + userId: leantimeUserId + }, + id: 1 + }; + console.log('[LEANTIME_ADAPTER] Request body:', JSON.stringify(jsonRpcBody)); + const response = await fetch(`${this.apiUrl}/api/jsonrpc`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': this.apiToken }, - body: JSON.stringify({ - jsonrpc: '2.0', - method: 'leantime.rpc.notifications.getNotificationCount', - params: { - userId: leantimeUserId - }, - id: 1 - }) + body: JSON.stringify(jsonRpcBody) }); + console.log('[LEANTIME_ADAPTER] Response status:', response.status); + if (!response.ok) { - console.error(`Failed to fetch Leantime notification count: ${response.status}`); + const errorText = await response.text(); + console.error('[LEANTIME_ADAPTER] Failed to fetch Leantime notification count:', { + status: response.status, + body: errorText.substring(0, 200) + (errorText.length > 200 ? '...' : '') + }); return this.getEmptyCount(); } - const data = await response.json(); + const responseText = await response.text(); + console.log('[LEANTIME_ADAPTER] Raw response:', responseText); + + const data = JSON.parse(responseText); + console.log('[LEANTIME_ADAPTER] Parsed response data:', { + hasResult: !!data.result, + resultData: data.result, + error: data.error + }); + if (!data.result) { - console.error('Invalid response format from Leantime notification count API'); + console.error('[LEANTIME_ADAPTER] Invalid response format from Leantime notification count API'); return this.getEmptyCount(); } const unreadCount = data.result.unread || 0; const totalCount = data.result.total || 0; + console.log('[LEANTIME_ADAPTER] Notification counts:', { unread: unreadCount, total: totalCount }); + return { total: totalCount, unread: unreadCount, @@ -137,7 +199,7 @@ export class LeantimeAdapter implements NotificationAdapter { } }; } catch (error) { - console.error('Error fetching Leantime notification count:', error); + console.error('[LEANTIME_ADAPTER] Error fetching Leantime notification count:', error); return this.getEmptyCount(); } } @@ -267,36 +329,69 @@ export class LeantimeAdapter implements NotificationAdapter { // Helper function to get user's email from ID (using existing method from your system) private async getUserEmail(userId: string): Promise { + console.log(`[LEANTIME_ADAPTER] Getting email for userId: ${userId}`); + try { // Fetch from Keycloak similar to how other components do - const response = await fetch(`${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${userId}`, { + const adminToken = await this.getAdminToken(); + console.log(`[LEANTIME_ADAPTER] Got admin token for Keycloak:`, adminToken ? 'token obtained' : 'failed to get token'); + + if (!adminToken) { + console.error('[LEANTIME_ADAPTER] Failed to get admin token for Keycloak'); + return null; + } + + const keycloakUrl = `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${userId}`; + console.log(`[LEANTIME_ADAPTER] Fetching user from Keycloak:`, keycloakUrl); + + const response = await fetch(keycloakUrl, { headers: { - 'Authorization': `Bearer ${await this.getAdminToken()}` + 'Authorization': `Bearer ${adminToken}` } }); + console.log(`[LEANTIME_ADAPTER] Keycloak response status:`, response.status); + if (!response.ok) { - console.error('Failed to get user from Keycloak'); + const errorText = await response.text(); + console.error('[LEANTIME_ADAPTER] Failed to get user from Keycloak:', { + status: response.status, + body: errorText.substring(0, 200) + (errorText.length > 200 ? '...' : '') + }); return null; } const userData = await response.json(); + console.log(`[LEANTIME_ADAPTER] Got user data from Keycloak:`, { + id: userData.id, + email: userData.email, + username: userData.username + }); + return userData.email || null; } catch (error) { - console.error('Error getting user email:', error); + console.error('[LEANTIME_ADAPTER] Error getting user email:', error); return null; } } // Helper function to get Leantime user ID by email (using similar pattern to the tasks API) private async getLeantimeUserId(email: string): Promise { + console.log(`[LEANTIME_ADAPTER] Getting Leantime userId for email: ${email}`); + try { if (!this.apiToken) { - console.error('LEANTIME_TOKEN is not set in environment variables'); + console.error('[LEANTIME_ADAPTER] LEANTIME_TOKEN is not set in environment variables'); return null; } - console.log('Fetching Leantime user for email:', email); + console.log('[LEANTIME_ADAPTER] Sending request to get all Leantime users'); + const jsonRpcBody = { + jsonrpc: '2.0', + method: 'leantime.rpc.users.getAll', + id: 1 + }; + console.log('[LEANTIME_ADAPTER] Request body:', JSON.stringify(jsonRpcBody)); const response = await fetch(`${this.apiUrl}/api/jsonrpc`, { method: 'POST', @@ -304,46 +399,85 @@ export class LeantimeAdapter implements NotificationAdapter { 'Content-Type': 'application/json', 'X-API-Key': this.apiToken }, - body: JSON.stringify({ - jsonrpc: '2.0', - method: 'leantime.rpc.users.getAll', - id: 1 - }), + body: JSON.stringify(jsonRpcBody), }); + console.log('[LEANTIME_ADAPTER] Response status:', response.status); + if (!response.ok) { - console.error('Failed to fetch Leantime users'); + const errorText = await response.text(); + console.error('[LEANTIME_ADAPTER] Failed to fetch Leantime users:', { + status: response.status, + body: errorText.substring(0, 200) + (errorText.length > 200 ? '...' : '') + }); return null; } - const data = await response.json(); + const responseText = await response.text(); + console.log('[LEANTIME_ADAPTER] Raw response (truncated):', responseText.substring(0, 200) + (responseText.length > 200 ? '...' : '')); + + const data = JSON.parse(responseText); + console.log('[LEANTIME_ADAPTER] Parsed response data:', { + hasResult: !!data.result, + resultIsArray: Array.isArray(data.result), + resultLength: Array.isArray(data.result) ? data.result.length : 'n/a', + error: data.error + }); if (!data.result || !Array.isArray(data.result)) { - console.error('Invalid response format from Leantime users API'); + console.error('[LEANTIME_ADAPTER] Invalid response format from Leantime users API'); return null; } const users = data.result; + console.log(`[LEANTIME_ADAPTER] Searching for user with email ${email} among ${users.length} users`); + + // Log a few user objects to check structure + if (users.length > 0) { + console.log('[LEANTIME_ADAPTER] Sample user object:', JSON.stringify(users[0])); + } + const user = users.find((u: any) => u.username === email); if (user) { - console.log('Found Leantime user:', { id: user.id, username: user.username }); + console.log('[LEANTIME_ADAPTER] Found Leantime user:', { id: user.id, username: user.username }); return user.id; } else { - console.log('No Leantime user found for email:', email); + console.log('[LEANTIME_ADAPTER] No Leantime user found for email:', email); + // Try alternative property names that might contain the email + const alternativeUser = users.find((u: any) => + u.email === email || + u.mail === email || + (typeof u.username === 'string' && u.username.toLowerCase() === email.toLowerCase()) + ); + + if (alternativeUser) { + console.log('[LEANTIME_ADAPTER] Found Leantime user with alternative property match:', { + id: alternativeUser.id, + username: alternativeUser.username, + email: alternativeUser.email || alternativeUser.mail + }); + return alternativeUser.id; + } + return null; } } catch (error) { - console.error('Error getting Leantime user ID:', error); + console.error('[LEANTIME_ADAPTER] Error getting Leantime user ID:', error); return null; } } // Helper function to get admin token (similar to the user management API) private async getAdminToken(): Promise { + console.log('[LEANTIME_ADAPTER] Getting admin token from Keycloak'); + try { + const keycloakTokenUrl = `${process.env.KEYCLOAK_BASE_URL}/realms/master/protocol/openid-connect/token`; + console.log('[LEANTIME_ADAPTER] Keycloak token URL:', keycloakTokenUrl); + const response = await fetch( - `${process.env.KEYCLOAK_BASE_URL}/realms/master/protocol/openid-connect/token`, + keycloakTokenUrl, { method: 'POST', headers: { @@ -357,15 +491,23 @@ export class LeantimeAdapter implements NotificationAdapter { } ); + console.log('[LEANTIME_ADAPTER] Keycloak token response status:', response.status); + if (!response.ok) { - console.error('Failed to get admin token'); + const errorText = await response.text(); + console.error('[LEANTIME_ADAPTER] Failed to get admin token:', { + status: response.status, + body: errorText.substring(0, 200) + (errorText.length > 200 ? '...' : '') + }); return null; } const tokenData = await response.json(); + console.log('[LEANTIME_ADAPTER] Successfully obtained admin token'); + return tokenData.access_token; } catch (error) { - console.error('Error getting admin token:', error); + console.error('[LEANTIME_ADAPTER] Error getting admin token:', error); return null; } } diff --git a/lib/services/notifications/notification-service.ts b/lib/services/notifications/notification-service.ts index 83114fb3..cdb89335 100644 --- a/lib/services/notifications/notification-service.ts +++ b/lib/services/notifications/notification-service.ts @@ -17,6 +17,8 @@ export class NotificationService { private static REFRESH_LOCK_TTL = 30; // 30 seconds constructor() { + console.log('[NOTIFICATION_SERVICE] Initializing notification service'); + // Register adapters this.registerAdapter(new LeantimeAdapter()); @@ -25,6 +27,8 @@ export class NotificationService { // this.registerAdapter(new GiteaAdapter()); // this.registerAdapter(new DolibarrAdapter()); // this.registerAdapter(new MoodleAdapter()); + + console.log('[NOTIFICATION_SERVICE] Registered adapters:', Array.from(this.adapters.keys())); } /** @@ -32,6 +36,7 @@ export class NotificationService { */ public static getInstance(): NotificationService { if (!NotificationService.instance) { + console.log('[NOTIFICATION_SERVICE] Creating new notification service instance'); NotificationService.instance = new NotificationService(); } return NotificationService.instance; @@ -42,13 +47,14 @@ export class NotificationService { */ private registerAdapter(adapter: NotificationAdapter): void { this.adapters.set(adapter.sourceName, adapter); - console.log(`Registered notification adapter: ${adapter.sourceName}`); + console.log(`[NOTIFICATION_SERVICE] Registered notification adapter: ${adapter.sourceName}`); } /** * Get all notifications for a user from all configured sources */ async getNotifications(userId: string, page = 1, limit = 20): Promise { + console.log(`[NOTIFICATION_SERVICE] getNotifications called for user ${userId}, page ${page}, limit ${limit}`); const redis = getRedisClient(); const cacheKey = NotificationService.NOTIFICATIONS_LIST_CACHE_KEY(userId, page, limit); @@ -67,31 +73,49 @@ export class NotificationService { return JSON.parse(cachedData); } } catch (error) { - console.error('Error retrieving notifications from cache:', error); + console.error('[NOTIFICATION_SERVICE] Error retrieving notifications from cache:', error); } // No cached data, fetch from all adapters - console.log(`[NOTIFICATION_SERVICE] Fetching notifications for user ${userId}`); + console.log(`[NOTIFICATION_SERVICE] Fetching notifications for user ${userId} from ${this.adapters.size} adapters`); const allNotifications: Notification[] = []; - const promises = Array.from(this.adapters.values()) - .map(adapter => adapter.isConfigured() - .then(configured => configured ? adapter.getNotifications(userId, page, limit) : []) - .catch(error => { - console.error(`Error fetching notifications from ${adapter.sourceName}:`, error); + const adapterEntries = Array.from(this.adapters.entries()); + console.log(`[NOTIFICATION_SERVICE] Available adapters: ${adapterEntries.map(([name]) => name).join(', ')}`); + + const promises = adapterEntries.map(async ([name, adapter]) => { + console.log(`[NOTIFICATION_SERVICE] Checking if adapter ${name} is configured`); + try { + const configured = await adapter.isConfigured(); + console.log(`[NOTIFICATION_SERVICE] Adapter ${name} is configured: ${configured}`); + + if (configured) { + console.log(`[NOTIFICATION_SERVICE] Fetching notifications from ${name} for user ${userId}`); + const notifications = await adapter.getNotifications(userId, page, limit); + console.log(`[NOTIFICATION_SERVICE] Got ${notifications.length} notifications from ${name}`); + return notifications; + } else { + console.log(`[NOTIFICATION_SERVICE] Skipping adapter ${name} as it is not configured`); return []; - }) - ); + } + } catch (error) { + console.error(`[NOTIFICATION_SERVICE] Error fetching notifications from ${name}:`, error); + return []; + } + }); const results = await Promise.all(promises); // Combine all notifications - results.forEach(notifications => { + results.forEach((notifications, index) => { + const adapterName = adapterEntries[index][0]; + console.log(`[NOTIFICATION_SERVICE] Adding ${notifications.length} notifications from ${adapterName}`); allNotifications.push(...notifications); }); // Sort by timestamp (newest first) allNotifications.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); + console.log(`[NOTIFICATION_SERVICE] Total notifications after sorting: ${allNotifications.length}`); // Store in cache try { @@ -101,8 +125,9 @@ export class NotificationService { 'EX', NotificationService.LIST_CACHE_TTL ); + console.log(`[NOTIFICATION_SERVICE] Cached ${allNotifications.length} notifications for user ${userId}`); } catch (error) { - console.error('Error caching notifications:', error); + console.error('[NOTIFICATION_SERVICE] Error caching notifications:', error); } return allNotifications; @@ -112,6 +137,7 @@ export class NotificationService { * Get notification counts for a user */ async getNotificationCount(userId: string): Promise { + console.log(`[NOTIFICATION_SERVICE] getNotificationCount called for user ${userId}`); const redis = getRedisClient(); const cacheKey = NotificationService.NOTIFICATION_COUNT_CACHE_KEY(userId); @@ -130,11 +156,11 @@ export class NotificationService { return JSON.parse(cachedData); } } catch (error) { - console.error('Error retrieving notification counts from cache:', error); + console.error('[NOTIFICATION_SERVICE] Error retrieving notification counts from cache:', error); } // No cached data, fetch counts from all adapters - console.log(`[NOTIFICATION_SERVICE] Fetching notification counts for user ${userId}`); + console.log(`[NOTIFICATION_SERVICE] Fetching notification counts for user ${userId} from ${this.adapters.size} adapters`); const aggregatedCount: NotificationCount = { total: 0, @@ -142,21 +168,39 @@ export class NotificationService { sources: {} }; - const promises = Array.from(this.adapters.values()) - .map(adapter => adapter.isConfigured() - .then(configured => configured ? adapter.getNotificationCount(userId) : null) - .catch(error => { - console.error(`Error fetching notification count from ${adapter.sourceName}:`, error); + const adapterEntries = Array.from(this.adapters.entries()); + console.log(`[NOTIFICATION_SERVICE] Available adapters for count: ${adapterEntries.map(([name]) => name).join(', ')}`); + + const promises = adapterEntries.map(async ([name, adapter]) => { + console.log(`[NOTIFICATION_SERVICE] Checking if adapter ${name} is configured for count`); + try { + const configured = await adapter.isConfigured(); + console.log(`[NOTIFICATION_SERVICE] Adapter ${name} is configured for count: ${configured}`); + + if (configured) { + console.log(`[NOTIFICATION_SERVICE] Fetching notification count from ${name} for user ${userId}`); + const count = await adapter.getNotificationCount(userId); + console.log(`[NOTIFICATION_SERVICE] Got count from ${name}:`, count); + return count; + } else { + console.log(`[NOTIFICATION_SERVICE] Skipping adapter ${name} for count as it is not configured`); return null; - }) - ); + } + } catch (error) { + console.error(`[NOTIFICATION_SERVICE] Error fetching notification count from ${name}:`, error); + return null; + } + }); const results = await Promise.all(promises); // Combine all counts - results.forEach(count => { + results.forEach((count, index) => { if (!count) return; + const adapterName = adapterEntries[index][0]; + console.log(`[NOTIFICATION_SERVICE] Adding counts from ${adapterName}: total=${count.total}, unread=${count.unread}`); + aggregatedCount.total += count.total; aggregatedCount.unread += count.unread; @@ -166,6 +210,8 @@ export class NotificationService { }); }); + console.log(`[NOTIFICATION_SERVICE] Aggregated counts for user ${userId}:`, aggregatedCount); + // Store in cache try { await redis.set( @@ -174,8 +220,9 @@ export class NotificationService { 'EX', NotificationService.COUNT_CACHE_TTL ); + console.log(`[NOTIFICATION_SERVICE] Cached notification counts for user ${userId}`); } catch (error) { - console.error('Error caching notification counts:', error); + console.error('[NOTIFICATION_SERVICE] Error caching notification counts:', error); } return aggregatedCount;