import { Notification, NotificationCount } from '@/lib/types/notification'; import { NotificationAdapter } from './notification-adapter.interface'; import { prisma } from '@/lib/prisma'; // 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'; private apiUrl: string; private apiKey: string; constructor() { // Load from environment or database config this.apiUrl = process.env.LEANTIME_API_URL || ''; this.apiKey = process.env.LEANTIME_API_KEY || ''; } private async getLeantimeCredentials(userId: string): Promise<{ url: string, apiKey: string } | null> { // Get Leantime credentials from the database for this user const credentials = await prisma.userServiceCredentials.findFirst({ where: { userId: userId, service: 'leantime' } }); if (!credentials) { return null; } return { url: credentials.serviceUrl || this.apiUrl, apiKey: credentials.accessToken || this.apiKey, }; } async getNotifications(userId: string, page = 1, limit = 20): Promise { const credentials = await this.getLeantimeCredentials(userId); if (!credentials) { return []; } try { // Calculate offset for pagination const offset = (page - 1) * limit; // Make request to Leantime API const response = await fetch( `${credentials.url}/api/notifications?limit=${limit}&offset=${offset}`, { headers: { 'Authorization': `Bearer ${credentials.apiKey}`, 'Content-Type': 'application/json' } } ); if (!response.ok) { console.error(`Failed to fetch Leantime notifications: ${response.status}`); return []; } const data = await response.json(); return this.transformNotifications(data, userId); } catch (error) { console.error('Error fetching Leantime notifications:', error); return []; } } async getNotificationCount(userId: string): Promise { const credentials = await this.getLeantimeCredentials(userId); if (!credentials) { return { total: 0, unread: 0, sources: { leantime: { total: 0, unread: 0 } } }; } try { // Make request to Leantime API const response = await fetch( `${credentials.url}/api/notifications/count`, { headers: { 'Authorization': `Bearer ${credentials.apiKey}`, 'Content-Type': 'application/json' } } ); if (!response.ok) { console.error(`Failed to fetch Leantime notification count: ${response.status}`); return { total: 0, unread: 0, sources: { leantime: { total: 0, unread: 0 } } }; } const data = await response.json(); const unreadCount = data.unread || 0; const totalCount = data.total || 0; return { total: totalCount, unread: unreadCount, sources: { leantime: { total: totalCount, unread: unreadCount } } }; } catch (error) { console.error('Error fetching Leantime notification count:', error); return { total: 0, unread: 0, sources: { leantime: { total: 0, unread: 0 } } }; } } async markAsRead(userId: string, notificationId: string): Promise { const credentials = await this.getLeantimeCredentials(userId); if (!credentials) { return false; } try { // Extract the source ID from our compound ID const sourceId = notificationId.replace(`${this.sourceName}-`, ''); // Make request to Leantime API const response = await fetch( `${credentials.url}/api/notifications/${sourceId}/read`, { method: 'POST', headers: { 'Authorization': `Bearer ${credentials.apiKey}`, 'Content-Type': 'application/json' } } ); return response.ok; } catch (error) { console.error('Error marking Leantime notification as read:', error); return false; } } async markAllAsRead(userId: string): Promise { const credentials = await this.getLeantimeCredentials(userId); if (!credentials) { return false; } try { // Make request to Leantime API const response = await fetch( `${credentials.url}/api/notifications/read-all`, { method: 'POST', headers: { 'Authorization': `Bearer ${credentials.apiKey}`, 'Content-Type': 'application/json' } } ); return response.ok; } catch (error) { console.error('Error marking all Leantime notifications as read:', error); return false; } } async isConfigured(): Promise { return !!(this.apiUrl && this.apiKey); } private transformNotifications(data: LeantimeNotification[], userId: string): Notification[] { if (!Array.isArray(data)) { return []; } return data.map(notification => ({ id: `${this.sourceName}-${notification.id}`, source: this.sourceName as 'leantime', sourceId: notification.id.toString(), type: notification.type, title: notification.type, // Leantime doesn't provide a separate title message: notification.message, link: notification.url, isRead: notification.read === 1, timestamp: new Date(notification.date), priority: 'normal', // Default priority as Leantime doesn't specify user: { id: userId, name: notification.username }, metadata: { moduleId: notification.moduleId } })); } }