From 38b0f162f8d54fa42a4865928eed14a5059b7973 Mon Sep 17 00:00:00 2001 From: alma Date: Sun, 4 May 2025 11:48:41 +0200 Subject: [PATCH] notifications --- .../notifications/leantime-adapter.ts | 353 +++++++++++++++++- 1 file changed, 341 insertions(+), 12 deletions(-) diff --git a/lib/services/notifications/leantime-adapter.ts b/lib/services/notifications/leantime-adapter.ts index 87733a71..adb52020 100644 --- a/lib/services/notifications/leantime-adapter.ts +++ b/lib/services/notifications/leantime-adapter.ts @@ -1,5 +1,20 @@ import { Notification, NotificationCount } from '@/lib/types/notification'; import { NotificationAdapter } from './notification-adapter.interface'; +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/app/api/auth/[...nextauth]/route'; + +// Leantime notification type from their API +interface LeantimeNotification { + id: number; + userId: number; + username: string; + message: string; + type: string; + moduleId: number; + url: string; + read: number; // 0 for unread, 1 for read + date: string; // ISO format date +} export class LeantimeAdapter implements NotificationAdapter { readonly sourceName = 'leantime'; @@ -9,34 +24,348 @@ export class LeantimeAdapter implements NotificationAdapter { constructor() { this.apiUrl = process.env.LEANTIME_API_URL || ''; this.apiToken = process.env.LEANTIME_TOKEN || ''; + + console.log('[LEANTIME_ADAPTER] Initialized with API URL and token'); } async getNotifications(userId: string, page = 1, limit = 20): Promise { - return []; + console.log(`[LEANTIME_ADAPTER] getNotifications called for userId: ${userId}, page: ${page}, limit: ${limit}`); + + try { + // Get the user's email directly from the session + const email = await this.getUserEmail(); + console.log(`[LEANTIME_ADAPTER] Retrieved email from session:`, email || 'null'); + + if (!email) { + console.error('[LEANTIME_ADAPTER] Could not get user email from session'); + return []; + } + + const leantimeUserId = await this.getLeantimeUserId(email); + console.log(`[LEANTIME_ADAPTER] Retrieved Leantime userId for email ${email}:`, leantimeUserId || 'null'); + + if (!leantimeUserId) { + console.error('[LEANTIME_ADAPTER] User not found in Leantime:', email); + return []; + } + + // Calculate pagination limits + const limitStart = (page - 1) * limit; + const limitEnd = limit; + + // Make request to Leantime API using the correct jsonrpc method + console.log('[LEANTIME_ADAPTER] Sending request to get all notifications'); + const jsonRpcBody = { + jsonrpc: '2.0', + method: 'leantime.rpc.Notifications.Notifications.getAllNotifications', + params: { + userId: leantimeUserId, + showNewOnly: 0, // Get all notifications, not just unread + limitStart: limitStart, + limitEnd: limitEnd, + filterOptions: [] // No additional filters + }, + 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(jsonRpcBody) + }); + + console.log('[LEANTIME_ADAPTER] Response status:', response.status); + + if (!response.ok) { + 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 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)) { + if (data.error) { + console.error(`[LEANTIME_ADAPTER] API error: ${data.error.message || JSON.stringify(data.error)}`); + } else { + console.error('[LEANTIME_ADAPTER] Invalid response format from Leantime notifications API'); + } + return []; + } + + const notifications = this.transformNotifications(data.result, userId); + console.log('[LEANTIME_ADAPTER] Transformed notifications count:', notifications.length); + return notifications; + } catch (error) { + console.error('[LEANTIME_ADAPTER] Error fetching Leantime notifications:', error); + return []; + } } async getNotificationCount(userId: string): Promise { - return { - total: 0, - unread: 0, - sources: { - leantime: { - total: 0, - unread: 0 + console.log(`[LEANTIME_ADAPTER] getNotificationCount called for userId: ${userId}`); + + try { + // Get all notifications and count them + const notifications = await this.getNotifications(userId, 1, 100); // Get up to 100 for counting + + // Count total and unread + const totalCount = notifications.length; + const unreadCount = notifications.filter(n => !n.isRead).length; + + console.log('[LEANTIME_ADAPTER] Notification counts:', { + total: totalCount, + unread: unreadCount + }); + + return { + total: totalCount, + unread: unreadCount, + sources: { + leantime: { + total: totalCount, + unread: unreadCount + } } - } - }; + }; + } catch (error) { + console.error('[LEANTIME_ADAPTER] Error fetching notification count:', error); + return { + total: 0, + unread: 0, + sources: { + leantime: { + total: 0, + unread: 0 + } + } + }; + } } async markAsRead(userId: string, notificationId: string): Promise { - return true; + console.log(`[LEANTIME_ADAPTER] markAsRead called for ${notificationId}`); + + try { + // Extract the source ID from our compound ID + const sourceId = notificationId.replace(`${this.sourceName}-`, ''); + + // Get user email and ID + const email = await this.getUserEmail(); + if (!email) { + console.error('[LEANTIME_ADAPTER] Could not get user email from session'); + return false; + } + + const leantimeUserId = await this.getLeantimeUserId(email); + if (!leantimeUserId) { + console.error('[LEANTIME_ADAPTER] User not found in Leantime:', email); + return false; + } + + // Make request to Leantime API to mark notification as read + const jsonRpcBody = { + jsonrpc: '2.0', + method: 'leantime.rpc.Notifications.Notifications.markNotificationAsRead', + params: { + userId: leantimeUserId, + notificationId: parseInt(sourceId) + }, + id: 1 + }; + + const response = await fetch(`${this.apiUrl}/api/jsonrpc`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': this.apiToken + }, + body: JSON.stringify(jsonRpcBody) + }); + + if (!response.ok) { + console.error(`[LEANTIME_ADAPTER] Failed to mark notification as read: ${response.status}`); + return false; + } + + const data = await response.json(); + return data.result === true || data.result === "true" || !!data.result; + } catch (error) { + console.error('[LEANTIME_ADAPTER] Error marking notification as read:', error); + return false; + } } async markAllAsRead(userId: string): Promise { - return true; + console.log(`[LEANTIME_ADAPTER] markAllAsRead called for ${userId}`); + + try { + // Get user email and ID + const email = await this.getUserEmail(); + if (!email) { + console.error('[LEANTIME_ADAPTER] Could not get user email from session'); + return false; + } + + const leantimeUserId = await this.getLeantimeUserId(email); + if (!leantimeUserId) { + console.error('[LEANTIME_ADAPTER] User not found in Leantime:', email); + return false; + } + + // Make request to Leantime API to mark all notifications as read + const jsonRpcBody = { + jsonrpc: '2.0', + method: 'leantime.rpc.Notifications.Notifications.markAllNotificationsAsRead', + params: { + userId: leantimeUserId + }, + id: 1 + }; + + const response = await fetch(`${this.apiUrl}/api/jsonrpc`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': this.apiToken + }, + body: JSON.stringify(jsonRpcBody) + }); + + if (!response.ok) { + console.error(`[LEANTIME_ADAPTER] Failed to mark all notifications as read: ${response.status}`); + return false; + } + + const data = await response.json(); + return data.result === true || data.result === "true" || !!data.result; + } catch (error) { + console.error('[LEANTIME_ADAPTER] Error marking all notifications as read:', error); + return false; + } } async isConfigured(): Promise { return !!(this.apiUrl && this.apiToken); } + + private transformNotifications(data: any[], userId: string): Notification[] { + if (!Array.isArray(data)) { + return []; + } + + return data.map(notification => { + // Determine properties from notification object + // Adjust these based on actual structure of Leantime notifications + const id = notification.id || notification._id || notification.notificationId; + const message = notification.message || notification.text || notification.content || ''; + const type = notification.type || 'notification'; + const read = notification.read || notification.isRead || 0; + const date = notification.date || notification.datetime || notification.createdDate || new Date().toISOString(); + const url = notification.url || notification.link || ''; + + return { + id: `${this.sourceName}-${id}`, + source: this.sourceName as 'leantime', + sourceId: id.toString(), + type: type, + title: type, // Use type as title if no specific title field + message: message, + link: url.startsWith('http') ? url : `${this.apiUrl}${url.startsWith('/') ? '' : '/'}${url}`, + isRead: read === 1 || read === true, + timestamp: new Date(date), + priority: 'normal', + user: { + id: userId, + name: notification.username || notification.userName || '' + }, + metadata: { + // Include any other useful fields from notification + moduleId: notification.moduleId || notification.module || '', + projectId: notification.projectId || '', + } + }; + }); + } + + // Helper function to get user's email directly from the session + private async getUserEmail(): Promise { + try { + const session = await getServerSession(authOptions); + if (!session || !session.user?.email) { + return null; + } + + return session.user.email; + } catch (error) { + console.error('[LEANTIME_ADAPTER] Error getting user email from session:', error); + return null; + } + } + + // Helper function to get Leantime user ID by email + private async getLeantimeUserId(email: string): Promise { + try { + if (!this.apiToken) { + return null; + } + + 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.users.getAll', + id: 1 + }), + }); + + if (!response.ok) { + return null; + } + + const data = await response.json(); + + if (!data.result || !Array.isArray(data.result)) { + return null; + } + + const users = data.result; + + // Find user with matching email (check in both username and email fields) + const user = users.find((u: any) => + u.username === email || + u.email === email || + (typeof u.username === 'string' && u.username.toLowerCase() === email.toLowerCase()) + ); + + if (user) { + return user.id; + } + + return null; + } catch (error) { + console.error('[LEANTIME_ADAPTER] Error getting Leantime user ID:', error); + return null; + } + } } \ No newline at end of file