notifications
This commit is contained in:
parent
b404ab84a3
commit
0a214256ef
114
app/api/debug/leantime-methods/route.ts
Normal file
114
app/api/debug/leantime-methods/route.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
||||
|
||||
// GET /api/debug/leantime-methods
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
// Authenticate user
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user?.id) {
|
||||
return NextResponse.json(
|
||||
{ error: "Not authenticated" },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
// Check environment variables
|
||||
if (!process.env.LEANTIME_API_URL || !process.env.LEANTIME_TOKEN) {
|
||||
return NextResponse.json(
|
||||
{ error: "Missing Leantime API configuration" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// Methods to test
|
||||
const methodsToTest = [
|
||||
// User related methods
|
||||
'leantime.rpc.users.getAll',
|
||||
|
||||
// Notification methods to try
|
||||
'leantime.rpc.notifications.getNotifications',
|
||||
'leantime.rpc.notifications.getAllNotifications',
|
||||
'leantime.rpc.notifications.getMyNotifications',
|
||||
'leantime.rpc.notifications.markNotificationRead',
|
||||
|
||||
// Alternative paths to try
|
||||
'leantime.rpc.Notifications.getNotifications',
|
||||
'leantime.rpc.Notifications.getAllNotifications',
|
||||
'leantime.rpc.Notifications.getMyNotifications',
|
||||
|
||||
// More generic paths
|
||||
'notifications.getNotifications',
|
||||
'notifications.getAll',
|
||||
'notifications.getAllByUser',
|
||||
|
||||
// Alternative namespaces
|
||||
'leantime.domain.notifications.getNotifications',
|
||||
'leantime.domain.notifications.getAll',
|
||||
];
|
||||
|
||||
// Test each method
|
||||
const results = await Promise.all(
|
||||
methodsToTest.map(async (method) => {
|
||||
console.log(`[LEANTIME_DEBUG] Testing method: ${method}`);
|
||||
|
||||
const response = await fetch(`${process.env.LEANTIME_API_URL}/api/jsonrpc`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': process.env.LEANTIME_TOKEN || ''
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: method,
|
||||
params: {
|
||||
// Include some common parameters that might be needed
|
||||
userId: 2, // Using user ID 2 since that was found in logs
|
||||
status: 'open',
|
||||
limit: 10
|
||||
},
|
||||
id: 1
|
||||
})
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
let parsedResponse;
|
||||
try {
|
||||
parsedResponse = JSON.parse(responseText);
|
||||
} catch (e) {
|
||||
parsedResponse = { parseError: "Invalid JSON response" };
|
||||
}
|
||||
|
||||
return {
|
||||
method,
|
||||
status: response.status,
|
||||
success: response.ok && !parsedResponse.error,
|
||||
response: parsedResponse
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Find successful methods
|
||||
const successfulMethods = results.filter(result => result.success);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
totalMethodsTested: methodsToTest.length,
|
||||
successfulMethods: successfulMethods.length,
|
||||
successfulMethodNames: successfulMethods.map(m => m.method),
|
||||
results
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('[LEANTIME_DEBUG] Error testing Leantime methods:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Internal server error",
|
||||
message: error.message,
|
||||
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -3,17 +3,21 @@ 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
|
||||
// Leantime task type
|
||||
interface LeantimeTask {
|
||||
id: number | string;
|
||||
headline: string;
|
||||
projectName: string;
|
||||
projectId: number;
|
||||
status: number;
|
||||
dateToFinish: string | null;
|
||||
milestone: string | null;
|
||||
details: string | null;
|
||||
createdOn: string;
|
||||
editedOn: string | null;
|
||||
editorId: string;
|
||||
editorFirstname: string;
|
||||
editorLastname: string;
|
||||
}
|
||||
|
||||
export class LeantimeAdapter implements NotificationAdapter {
|
||||
@ -55,17 +59,15 @@ export class LeantimeAdapter implements NotificationAdapter {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Calculate offset for pagination
|
||||
const offset = (page - 1) * limit;
|
||||
// Since notifications API doesn't work, get assigned tasks instead
|
||||
console.log('[LEANTIME_ADAPTER] Getting assigned tasks as notifications');
|
||||
|
||||
// Make request to Leantime API using jsonrpc
|
||||
console.log('[LEANTIME_ADAPTER] Sending request to Leantime API for notifications');
|
||||
const jsonRpcBody = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'leantime.rpc.notifications.getNotifications',
|
||||
method: 'leantime.rpc.tickets.getAll', // Using the tasks API
|
||||
params: {
|
||||
userId: leantimeUserId,
|
||||
limit: limit
|
||||
status: "open" // Only get open tasks
|
||||
},
|
||||
id: 1
|
||||
};
|
||||
@ -84,7 +86,7 @@ export class LeantimeAdapter implements NotificationAdapter {
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('[LEANTIME_ADAPTER] Failed to fetch Leantime notifications:', {
|
||||
console.error('[LEANTIME_ADAPTER] Failed to fetch Leantime tasks:', {
|
||||
status: response.status,
|
||||
body: errorText.substring(0, 200) + (errorText.length > 200 ? '...' : '')
|
||||
});
|
||||
@ -106,16 +108,17 @@ export class LeantimeAdapter implements NotificationAdapter {
|
||||
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');
|
||||
console.error('[LEANTIME_ADAPTER] Invalid response format from Leantime tasks API');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
const notifications = this.transformNotifications(data.result, userId);
|
||||
console.log('[LEANTIME_ADAPTER] Transformed notifications count:', notifications.length);
|
||||
// Convert tasks to notifications format
|
||||
const notifications = this.transformTasksToNotifications(data.result, userId);
|
||||
console.log('[LEANTIME_ADAPTER] Transformed task notifications count:', notifications.length);
|
||||
return notifications;
|
||||
} catch (error) {
|
||||
console.error('[LEANTIME_ADAPTER] Error fetching Leantime notifications:', error);
|
||||
console.error('[LEANTIME_ADAPTER] Error fetching Leantime tasks:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@ -141,13 +144,15 @@ export class LeantimeAdapter implements NotificationAdapter {
|
||||
return this.getEmptyCount();
|
||||
}
|
||||
|
||||
// Make request to Leantime API using jsonrpc to get all notifications
|
||||
console.log('[LEANTIME_ADAPTER] Sending request to Leantime API for notification count');
|
||||
// Since notifications API doesn't work, count assigned tasks instead
|
||||
console.log('[LEANTIME_ADAPTER] Getting open tasks count');
|
||||
|
||||
const jsonRpcBody = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'leantime.rpc.notifications.getNotifications',
|
||||
method: 'leantime.rpc.tickets.getAll', // Using the tasks API
|
||||
params: {
|
||||
userId: leantimeUserId
|
||||
userId: leantimeUserId,
|
||||
status: "open" // Only count open tasks
|
||||
},
|
||||
id: 1
|
||||
};
|
||||
@ -166,7 +171,7 @@ export class LeantimeAdapter implements NotificationAdapter {
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('[LEANTIME_ADAPTER] Failed to fetch Leantime notification count:', {
|
||||
console.error('[LEANTIME_ADAPTER] Failed to fetch Leantime task count:', {
|
||||
status: response.status,
|
||||
body: errorText.substring(0, 200) + (errorText.length > 200 ? '...' : '')
|
||||
});
|
||||
@ -188,113 +193,59 @@ export class LeantimeAdapter implements NotificationAdapter {
|
||||
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');
|
||||
console.error('[LEANTIME_ADAPTER] Invalid response format from Leantime tasks API');
|
||||
}
|
||||
return this.getEmptyCount();
|
||||
}
|
||||
|
||||
// Count total and unread notifications
|
||||
const notifications = data.result;
|
||||
const totalCount = notifications.length;
|
||||
const unreadCount = notifications.filter((n: any) => n.read === 0).length;
|
||||
// Count total tasks
|
||||
const totalCount = data.result.length;
|
||||
|
||||
// Count urgent tasks (based on date to finish being soon)
|
||||
const now = new Date();
|
||||
const twoDaysFromNow = new Date(now);
|
||||
twoDaysFromNow.setDate(now.getDate() + 2);
|
||||
|
||||
const urgentTasks = data.result.filter((task: LeantimeTask) => {
|
||||
if (task.dateToFinish) {
|
||||
const taskDeadline = new Date(task.dateToFinish);
|
||||
return taskDeadline <= twoDaysFromNow;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const urgentCount = urgentTasks.length;
|
||||
|
||||
console.log('[LEANTIME_ADAPTER] Notification counts:', { unread: unreadCount, total: totalCount });
|
||||
console.log('[LEANTIME_ADAPTER] Task counts:', { total: totalCount, urgent: urgentCount });
|
||||
|
||||
return {
|
||||
total: totalCount,
|
||||
unread: unreadCount,
|
||||
unread: urgentCount,
|
||||
sources: {
|
||||
leantime: {
|
||||
total: totalCount,
|
||||
unread: unreadCount
|
||||
unread: urgentCount
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[LEANTIME_ADAPTER] Error fetching Leantime notification count:', error);
|
||||
console.error('[LEANTIME_ADAPTER] Error fetching Leantime task count:', error);
|
||||
return this.getEmptyCount();
|
||||
}
|
||||
}
|
||||
|
||||
async markAsRead(userId: string, notificationId: string): Promise<boolean> {
|
||||
try {
|
||||
// Extract the source ID from our compound ID
|
||||
const sourceId = notificationId.replace(`${this.sourceName}-`, '');
|
||||
|
||||
// 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.markNotificationRead',
|
||||
params: {
|
||||
id: parseInt(sourceId)
|
||||
},
|
||||
id: 1
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('[LEANTIME_ADAPTER] Failed to mark Leantime notification as read:', {
|
||||
status: response.status,
|
||||
body: errorText.substring(0, 200) + (errorText.length > 200 ? '...' : '')
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
console.error(`[LEANTIME_ADAPTER] API error: ${data.error.message || JSON.stringify(data.error)}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return data.result === true;
|
||||
} catch (error) {
|
||||
console.error('[LEANTIME_ADAPTER] Error marking Leantime notification as read:', error);
|
||||
return false;
|
||||
}
|
||||
// Since we're using tasks as notifications, marking as read doesn't make sense
|
||||
// We'll just return true to avoid errors
|
||||
console.log(`[LEANTIME_ADAPTER] markAsRead called for ${notificationId}, returning true since we're using tasks as notifications`);
|
||||
return true;
|
||||
}
|
||||
|
||||
async markAllAsRead(userId: string): Promise<boolean> {
|
||||
try {
|
||||
// Get the user's email directly from the session
|
||||
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;
|
||||
}
|
||||
|
||||
// Get all unread notifications
|
||||
const notifications = await this.getNotifications(userId);
|
||||
const unreadNotifications = notifications.filter(n => !n.isRead);
|
||||
|
||||
if (unreadNotifications.length === 0) {
|
||||
// If there are no unread notifications, consider it a success
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mark each notification as read individually
|
||||
const promises = unreadNotifications.map(notification =>
|
||||
this.markAsRead(userId, notification.id)
|
||||
);
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
return results.every(result => result);
|
||||
} catch (error) {
|
||||
console.error('[LEANTIME_ADAPTER] Error marking all Leantime notifications as read:', error);
|
||||
return false;
|
||||
}
|
||||
// Since we're using tasks as notifications, marking all as read doesn't make sense
|
||||
// We'll just return true to avoid errors
|
||||
console.log(`[LEANTIME_ADAPTER] markAllAsRead called, returning true since we're using tasks as notifications`);
|
||||
return true;
|
||||
}
|
||||
|
||||
async isConfigured(): Promise<boolean> {
|
||||
@ -314,30 +265,53 @@ export class LeantimeAdapter implements NotificationAdapter {
|
||||
};
|
||||
}
|
||||
|
||||
private transformNotifications(data: LeantimeNotification[], userId: string): Notification[] {
|
||||
if (!Array.isArray(data)) {
|
||||
private transformTasksToNotifications(tasks: LeantimeTask[], userId: string): Notification[] {
|
||||
if (!Array.isArray(tasks)) {
|
||||
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
|
||||
return tasks.map(task => {
|
||||
// Calculate if the task is urgent (due within 2 days)
|
||||
let priority = 'normal' as 'normal' | 'high' | 'low';
|
||||
let isRead = true;
|
||||
|
||||
if (task.dateToFinish) {
|
||||
const now = new Date();
|
||||
const taskDeadline = new Date(task.dateToFinish);
|
||||
const twoDaysFromNow = new Date(now);
|
||||
twoDaysFromNow.setDate(now.getDate() + 2);
|
||||
|
||||
if (taskDeadline <= now) {
|
||||
priority = 'high';
|
||||
isRead = false;
|
||||
} else if (taskDeadline <= twoDaysFromNow) {
|
||||
priority = 'high';
|
||||
isRead = false;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Create notification from task
|
||||
return {
|
||||
id: `${this.sourceName}-${task.id}`,
|
||||
source: this.sourceName as 'leantime',
|
||||
sourceId: task.id.toString(),
|
||||
type: 'task',
|
||||
title: `Task: ${task.headline}`,
|
||||
message: `Project: ${task.projectName} - ${task.details ? task.details.substring(0, 100) : 'No details'}`,
|
||||
link: `${this.apiUrl}/tickets/showTicket/${task.id}`,
|
||||
isRead: isRead,
|
||||
timestamp: new Date(task.editedOn || task.createdOn),
|
||||
priority: priority,
|
||||
user: {
|
||||
id: userId,
|
||||
name: `${task.editorFirstname} ${task.editorLastname}`
|
||||
},
|
||||
metadata: {
|
||||
projectId: task.projectId,
|
||||
status: task.status
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to get user's email directly from the session
|
||||
|
||||
Loading…
Reference in New Issue
Block a user