NeahNew/lib/services/notifications/leantime-adapter.ts
2025-05-04 11:01:30 +02:00

236 lines
6.1 KiB
TypeScript

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<Notification[]> {
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<NotificationCount> {
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<boolean> {
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<boolean> {
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<boolean> {
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
}
}));
}
}