236 lines
6.1 KiB
TypeScript
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
|
|
}
|
|
}));
|
|
}
|
|
}
|