notifications
This commit is contained in:
parent
8f523d65c0
commit
509f9ea0aa
@ -1,6 +1,5 @@
|
||||
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 {
|
||||
@ -18,53 +17,50 @@ interface LeantimeNotification {
|
||||
export class LeantimeAdapter implements NotificationAdapter {
|
||||
readonly sourceName = 'leantime';
|
||||
private apiUrl: string;
|
||||
private apiKey: string;
|
||||
private apiToken: string;
|
||||
|
||||
constructor() {
|
||||
// Load from environment or database config
|
||||
// Load from environment variables, matching the pattern used in other Leantime integrations
|
||||
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,
|
||||
};
|
||||
this.apiToken = process.env.LEANTIME_TOKEN || '';
|
||||
}
|
||||
|
||||
async getNotifications(userId: string, page = 1, limit = 20): Promise<Notification[]> {
|
||||
const credentials = await this.getLeantimeCredentials(userId);
|
||||
if (!credentials) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
// First get the Leantime user ID from email
|
||||
const email = await this.getUserEmail(userId);
|
||||
if (!email) {
|
||||
console.error('Could not get user email for userId:', userId);
|
||||
return [];
|
||||
}
|
||||
|
||||
const leantimeUserId = await this.getLeantimeUserId(email);
|
||||
if (!leantimeUserId) {
|
||||
console.error('User not found in Leantime:', email);
|
||||
return [];
|
||||
}
|
||||
|
||||
// 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'
|
||||
}
|
||||
}
|
||||
);
|
||||
// Make request to Leantime API using jsonrpc
|
||||
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
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch Leantime notifications: ${response.status}`);
|
||||
@ -72,7 +68,12 @@ export class LeantimeAdapter implements NotificationAdapter {
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return this.transformNotifications(data, userId);
|
||||
if (!data.result || !Array.isArray(data.result)) {
|
||||
console.error('Invalid response format from Leantime notifications API');
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.transformNotifications(data.result, userId);
|
||||
} catch (error) {
|
||||
console.error('Error fetching Leantime notifications:', error);
|
||||
return [];
|
||||
@ -80,49 +81,50 @@ export class LeantimeAdapter implements NotificationAdapter {
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
);
|
||||
// First get the Leantime user ID from email
|
||||
const email = await this.getUserEmail(userId);
|
||||
if (!email) {
|
||||
console.error('Could not get user email for userId:', userId);
|
||||
return this.getEmptyCount();
|
||||
}
|
||||
|
||||
const leantimeUserId = await this.getLeantimeUserId(email);
|
||||
if (!leantimeUserId) {
|
||||
console.error('User not found in Leantime:', email);
|
||||
return this.getEmptyCount();
|
||||
}
|
||||
|
||||
// Make request to Leantime API using jsonrpc
|
||||
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
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch Leantime notification count: ${response.status}`);
|
||||
return {
|
||||
total: 0,
|
||||
unread: 0,
|
||||
sources: {
|
||||
leantime: {
|
||||
total: 0,
|
||||
unread: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
return this.getEmptyCount();
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const unreadCount = data.unread || 0;
|
||||
const totalCount = data.total || 0;
|
||||
if (!data.result) {
|
||||
console.error('Invalid response format from Leantime notification count API');
|
||||
return this.getEmptyCount();
|
||||
}
|
||||
|
||||
const unreadCount = data.result.unread || 0;
|
||||
const totalCount = data.result.total || 0;
|
||||
|
||||
return {
|
||||
total: totalCount,
|
||||
@ -136,42 +138,39 @@ export class LeantimeAdapter implements NotificationAdapter {
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching Leantime notification count:', error);
|
||||
return {
|
||||
total: 0,
|
||||
unread: 0,
|
||||
sources: {
|
||||
leantime: {
|
||||
total: 0,
|
||||
unread: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
return this.getEmptyCount();
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
);
|
||||
// Make request to Leantime API using jsonrpc
|
||||
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.markAsRead',
|
||||
params: {
|
||||
notificationId: sourceId
|
||||
},
|
||||
id: 1
|
||||
})
|
||||
});
|
||||
|
||||
return response.ok;
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to mark Leantime notification as read: ${response.status}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.result === true;
|
||||
} catch (error) {
|
||||
console.error('Error marking Leantime notification as read:', error);
|
||||
return false;
|
||||
@ -179,25 +178,44 @@ export class LeantimeAdapter implements NotificationAdapter {
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
);
|
||||
// First get the Leantime user ID from email
|
||||
const email = await this.getUserEmail(userId);
|
||||
if (!email) {
|
||||
console.error('Could not get user email for userId:', userId);
|
||||
return false;
|
||||
}
|
||||
|
||||
const leantimeUserId = await this.getLeantimeUserId(email);
|
||||
if (!leantimeUserId) {
|
||||
console.error('User not found in Leantime:', email);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make request to Leantime API using jsonrpc
|
||||
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.markAllAsRead',
|
||||
params: {
|
||||
userId: leantimeUserId
|
||||
},
|
||||
id: 1
|
||||
})
|
||||
});
|
||||
|
||||
return response.ok;
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to mark all Leantime notifications as read: ${response.status}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.result === true;
|
||||
} catch (error) {
|
||||
console.error('Error marking all Leantime notifications as read:', error);
|
||||
return false;
|
||||
@ -205,7 +223,20 @@ export class LeantimeAdapter implements NotificationAdapter {
|
||||
}
|
||||
|
||||
async isConfigured(): Promise<boolean> {
|
||||
return !!(this.apiUrl && this.apiKey);
|
||||
return !!(this.apiUrl && this.apiToken);
|
||||
}
|
||||
|
||||
private getEmptyCount(): NotificationCount {
|
||||
return {
|
||||
total: 0,
|
||||
unread: 0,
|
||||
sources: {
|
||||
leantime: {
|
||||
total: 0,
|
||||
unread: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private transformNotifications(data: LeantimeNotification[], userId: string): Notification[] {
|
||||
@ -233,4 +264,109 @@ 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<string | null> {
|
||||
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}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${await this.getAdminToken()}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Failed to get user from Keycloak');
|
||||
return null;
|
||||
}
|
||||
|
||||
const userData = await response.json();
|
||||
return userData.email || null;
|
||||
} catch (error) {
|
||||
console.error('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<number | null> {
|
||||
try {
|
||||
if (!this.apiToken) {
|
||||
console.error('LEANTIME_TOKEN is not set in environment variables');
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('Fetching Leantime user for email:', email);
|
||||
|
||||
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) {
|
||||
console.error('Failed to fetch Leantime users');
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.result || !Array.isArray(data.result)) {
|
||||
console.error('Invalid response format from Leantime users API');
|
||||
return null;
|
||||
}
|
||||
|
||||
const users = data.result;
|
||||
const user = users.find((u: any) => u.username === email);
|
||||
|
||||
if (user) {
|
||||
console.log('Found Leantime user:', { id: user.id, username: user.username });
|
||||
return user.id;
|
||||
} else {
|
||||
console.log('No Leantime user found for email:', email);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting Leantime user ID:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get admin token (similar to the user management API)
|
||||
private async getAdminToken(): Promise<string | null> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${process.env.KEYCLOAK_BASE_URL}/realms/master/protocol/openid-connect/token`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'client_credentials',
|
||||
client_id: process.env.KEYCLOAK_ADMIN_CLIENT_ID || '',
|
||||
client_secret: process.env.KEYCLOAK_ADMIN_CLIENT_SECRET || '',
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Failed to get admin token');
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenData = await response.json();
|
||||
return tokenData.access_token;
|
||||
} catch (error) {
|
||||
console.error('Error getting admin token:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user