385 lines
13 KiB
TypeScript
385 lines
13 KiB
TypeScript
import { getServerSession } from "next-auth";
|
|
import { authOptions } from "@/app/api/auth/options";
|
|
import { NextResponse } from "next/server";
|
|
import { getCachedMessagesData, cacheMessagesData } from "@/lib/redis";
|
|
|
|
// Helper function to get user token using admin credentials
|
|
async function getUserToken(baseUrl: string) {
|
|
try {
|
|
// Step 1: Use admin token to authenticate
|
|
const adminHeaders = {
|
|
'X-Auth-Token': process.env.ROCKET_CHAT_TOKEN!,
|
|
'X-User-Id': process.env.ROCKET_CHAT_USER_ID!,
|
|
'Content-Type': 'application/json'
|
|
};
|
|
|
|
// Step 2: Create user token using admin credentials
|
|
const createTokenResponse = await fetch(`${baseUrl}/api/v1/users.createToken`, {
|
|
method: 'POST',
|
|
headers: adminHeaders
|
|
});
|
|
|
|
if (!createTokenResponse.ok) {
|
|
console.error('Failed to create user token:', createTokenResponse.status);
|
|
return null;
|
|
}
|
|
|
|
const tokenData = await createTokenResponse.json();
|
|
return {
|
|
authToken: tokenData.data.authToken,
|
|
userId: tokenData.data.userId
|
|
};
|
|
} catch (error) {
|
|
console.error('Error getting user token:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function GET(request: Request) {
|
|
try {
|
|
const session = await getServerSession(authOptions);
|
|
|
|
if (!session?.user?.email) {
|
|
console.error('No valid session or email found');
|
|
return NextResponse.json({ messages: [] }, { status: 200 });
|
|
}
|
|
|
|
// Check for force refresh parameter
|
|
const url = new URL(request.url);
|
|
const forceRefresh = url.searchParams.get('refresh') === 'true';
|
|
|
|
// Try to get data from cache if not forcing refresh
|
|
if (!forceRefresh) {
|
|
const cachedMessages = await getCachedMessagesData(session.user.id);
|
|
if (cachedMessages) {
|
|
console.log(`Using cached messages data for user ${session.user.id}`);
|
|
return NextResponse.json(cachedMessages);
|
|
}
|
|
}
|
|
|
|
const baseUrl = process.env.NEXT_PUBLIC_IFRAME_PAROLE_URL?.split('/channel')[0];
|
|
if (!baseUrl) {
|
|
console.error('Failed to get Rocket.Chat base URL');
|
|
return NextResponse.json({ error: 'Server configuration error' }, { status: 500 });
|
|
}
|
|
|
|
console.log('Using Rocket.Chat base URL:', baseUrl);
|
|
|
|
// Step 1: Use admin token to authenticate
|
|
const adminHeaders = {
|
|
'X-Auth-Token': process.env.ROCKET_CHAT_TOKEN!,
|
|
'X-User-Id': process.env.ROCKET_CHAT_USER_ID!,
|
|
'Content-Type': 'application/json'
|
|
};
|
|
|
|
// Step 2: Get the current user's Rocket.Chat ID
|
|
const username = session.user.email.split('@')[0];
|
|
if (!username) {
|
|
console.error('No username found in session email');
|
|
return NextResponse.json({ messages: [] }, { status: 200 });
|
|
}
|
|
|
|
// Get all users to find the current user
|
|
const usersResponse = await fetch(`${baseUrl}/api/v1/users.list`, {
|
|
method: 'GET',
|
|
headers: adminHeaders
|
|
});
|
|
|
|
if (!usersResponse.ok) {
|
|
console.error('Failed to get users list:', usersResponse.status);
|
|
return NextResponse.json({ messages: [] }, { status: 200 });
|
|
}
|
|
|
|
const usersData = await usersResponse.json();
|
|
console.log('Users list response:', {
|
|
success: usersData.success,
|
|
count: usersData.count,
|
|
usersCount: usersData.users?.length
|
|
});
|
|
|
|
// Find the current user in the list
|
|
const currentUser = usersData.users.find((user: any) =>
|
|
user.username === username || user.emails?.some((email: any) => email.address === session.user.email)
|
|
);
|
|
|
|
if (!currentUser) {
|
|
console.error('User not found in users list');
|
|
return NextResponse.json({ messages: [] }, { status: 200 });
|
|
}
|
|
|
|
console.log('Found Rocket.Chat user:', {
|
|
username: currentUser.username,
|
|
id: currentUser._id
|
|
});
|
|
|
|
// Step 3: Create a token for the current user
|
|
const createTokenResponse = await fetch(`${baseUrl}/api/v1/users.createToken`, {
|
|
method: 'POST',
|
|
headers: adminHeaders,
|
|
body: JSON.stringify({
|
|
userId: currentUser._id
|
|
})
|
|
});
|
|
|
|
if (!createTokenResponse.ok) {
|
|
console.error('Failed to create user token:', createTokenResponse.status);
|
|
const errorText = await createTokenResponse.text();
|
|
console.error('Create token error details:', errorText);
|
|
return NextResponse.json({ messages: [] }, { status: 200 });
|
|
}
|
|
|
|
const tokenData = await createTokenResponse.json();
|
|
|
|
// Use the user's token for subsequent requests
|
|
const userHeaders = {
|
|
'X-Auth-Token': tokenData.data.authToken,
|
|
'X-User-Id': currentUser._id,
|
|
'Content-Type': 'application/json'
|
|
};
|
|
|
|
// Step 4: Get user's subscriptions using user token
|
|
const subscriptionsResponse = await fetch(`${baseUrl}/api/v1/subscriptions.get`, {
|
|
method: 'GET',
|
|
headers: userHeaders
|
|
});
|
|
|
|
if (!subscriptionsResponse.ok) {
|
|
console.error('Failed to get subscriptions:', subscriptionsResponse.status);
|
|
const errorText = await subscriptionsResponse.text();
|
|
console.error('Subscriptions error details:', errorText);
|
|
return NextResponse.json({ messages: [] }, { status: 200 });
|
|
}
|
|
|
|
const subscriptionsData = await subscriptionsResponse.json();
|
|
|
|
if (!subscriptionsData.success || !Array.isArray(subscriptionsData.update)) {
|
|
console.error('Invalid subscriptions response structure');
|
|
return NextResponse.json({ messages: [] }, { status: 200 });
|
|
}
|
|
|
|
// Filter subscriptions for the current user
|
|
const userSubscriptions = subscriptionsData.update.filter((sub: any) => {
|
|
// Only include rooms with unread messages or alerts
|
|
if (!(sub.unread > 0 || sub.alert)) {
|
|
return false;
|
|
}
|
|
// Include all types of rooms the user is subscribed to
|
|
return ['d', 'c', 'p'].includes(sub.t);
|
|
});
|
|
|
|
console.log('Filtered user subscriptions:', {
|
|
userId: currentUser._id,
|
|
username: currentUser.username,
|
|
totalSubscriptions: userSubscriptions.length,
|
|
subscriptionDetails: userSubscriptions.map((sub: any) => ({
|
|
type: sub.t,
|
|
name: sub.fname || sub.name,
|
|
rid: sub.rid,
|
|
alert: sub.alert,
|
|
unread: sub.unread,
|
|
userMentions: sub.userMentions
|
|
}))
|
|
});
|
|
|
|
const messages: any[] = [];
|
|
const processedRooms = new Set();
|
|
const latestMessagePerRoom: { [key: string]: any } = {};
|
|
|
|
// Step 5: Fetch messages using user token
|
|
for (const subscription of userSubscriptions) {
|
|
try {
|
|
// Determine the correct endpoint and parameters based on room type
|
|
let endpoint;
|
|
switch (subscription.t) {
|
|
case 'c':
|
|
endpoint = 'channels.messages';
|
|
break;
|
|
case 'p':
|
|
endpoint = 'groups.messages';
|
|
break;
|
|
case 'd':
|
|
endpoint = 'im.messages';
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
const queryParams = new URLSearchParams({
|
|
roomId: subscription.rid,
|
|
count: String(Math.max(subscription.unread, 5)) // Fetch at least the number of unread messages
|
|
});
|
|
|
|
const messagesResponse = await fetch(
|
|
`${baseUrl}/api/v1/${endpoint}?${queryParams}`, {
|
|
method: 'GET',
|
|
headers: userHeaders
|
|
}
|
|
);
|
|
|
|
if (!messagesResponse.ok) {
|
|
console.error(`Failed to get messages for room ${subscription.name}:`, messagesResponse.status);
|
|
continue;
|
|
}
|
|
|
|
const messageData = await messagesResponse.json();
|
|
console.log(`Messages for room ${subscription.fname || subscription.name}:`, {
|
|
success: messageData.success,
|
|
count: messageData.count,
|
|
hasMessages: messageData.messages?.length > 0
|
|
});
|
|
|
|
if (messageData.success && messageData.messages?.length > 0) {
|
|
// Filter out system messages and join notifications for channels
|
|
const validMessages = messageData.messages.filter((message: any) => {
|
|
// Skip messages sent by the current user
|
|
if (message.u._id === currentUser._id) {
|
|
return false;
|
|
}
|
|
|
|
// For channels, apply strict filtering
|
|
if (subscription.t === 'c') {
|
|
if (!message.msg || // No message text
|
|
message.t || // System message
|
|
!message.u || // No user info
|
|
message.msg.includes('has joined the channel') ||
|
|
message.msg.includes('has left the channel') ||
|
|
message.msg.includes('added') ||
|
|
message.msg.includes('removed')) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
|
|
// Only process the latest valid message from this room
|
|
if (validMessages.length > 0) {
|
|
// Get the latest message (they should already be sorted by timestamp)
|
|
const latestMessage = validMessages[0];
|
|
const messageUser = latestMessage.u || {};
|
|
const username = messageUser.username || 'unknown';
|
|
|
|
// Skip if this is our own message (double-check)
|
|
if (messageUser._id === currentUser._id) {
|
|
continue;
|
|
}
|
|
|
|
// Get proper display names
|
|
let roomDisplayName = subscription.fname || subscription.name;
|
|
let userDisplayName = messageUser.name || username;
|
|
|
|
// Handle call messages
|
|
let messageText = latestMessage.msg || '';
|
|
if (messageText.includes('started a call')) {
|
|
messageText = '📞 Call received';
|
|
}
|
|
|
|
// Format timestamp
|
|
const timestamp = new Date(latestMessage.ts);
|
|
const now = new Date();
|
|
let formattedTime = '';
|
|
|
|
if (isNaN(timestamp.getTime())) {
|
|
formattedTime = 'Invalid Date';
|
|
} else if (timestamp.toDateString() === now.toDateString()) {
|
|
formattedTime = timestamp.toLocaleTimeString('fr-FR', {
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
} else {
|
|
formattedTime = timestamp.toLocaleDateString('fr-FR', {
|
|
day: '2-digit',
|
|
month: 'short'
|
|
});
|
|
}
|
|
|
|
// Create initials for the sender
|
|
const initials = userDisplayName
|
|
.split(' ')
|
|
.map((n: string) => n[0])
|
|
.slice(0, 2)
|
|
.join('')
|
|
.toUpperCase();
|
|
|
|
const processedMessage = {
|
|
id: latestMessage._id,
|
|
text: messageText,
|
|
timestamp: formattedTime,
|
|
rawTimestamp: latestMessage.ts,
|
|
roomName: roomDisplayName,
|
|
roomType: subscription.t,
|
|
sender: {
|
|
_id: messageUser._id,
|
|
username: username,
|
|
name: userDisplayName,
|
|
initials: initials,
|
|
color: getAvatarColor(username)
|
|
},
|
|
isOwnMessage: messageUser._id === currentUser._id,
|
|
room: {
|
|
id: subscription.rid,
|
|
type: subscription.t,
|
|
name: roomDisplayName,
|
|
isChannel: subscription.t === 'c',
|
|
isPrivateGroup: subscription.t === 'p',
|
|
isDirect: subscription.t === 'd',
|
|
link: `${baseUrl}/${subscription.t === 'd' ? 'direct' : subscription.t === 'p' ? 'group' : 'channel'}/${subscription.name}`,
|
|
unread: subscription.unread,
|
|
alert: subscription.alert,
|
|
userMentions: subscription.userMentions
|
|
}
|
|
};
|
|
|
|
// Store this message if it's the latest for this room
|
|
if (!latestMessagePerRoom[subscription.rid] ||
|
|
new Date(latestMessage.ts).getTime() > new Date(latestMessagePerRoom[subscription.rid].rawTimestamp).getTime()) {
|
|
latestMessagePerRoom[subscription.rid] = processedMessage;
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error fetching messages for room ${subscription.name}:`, error);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Convert the latest messages object to an array and sort by timestamp
|
|
const sortedMessages = Object.values(latestMessagePerRoom)
|
|
.sort((a, b) => {
|
|
const dateA = new Date(a.rawTimestamp);
|
|
const dateB = new Date(b.rawTimestamp);
|
|
return dateB.getTime() - dateA.getTime();
|
|
})
|
|
.slice(0, 10);
|
|
|
|
const finalResponse = { messages: sortedMessages, total: Object.keys(latestMessagePerRoom).length, hasMore: Object.keys(latestMessagePerRoom).length > 10 };
|
|
|
|
// Cache the results
|
|
await cacheMessagesData(session.user.id, finalResponse);
|
|
|
|
return NextResponse.json(finalResponse);
|
|
} catch (error) {
|
|
console.error('Error fetching messages:', error);
|
|
return NextResponse.json({ error: 'Failed to fetch messages' }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
// Helper function to generate consistent avatar colors
|
|
function getAvatarColor(username: string): string {
|
|
const colors = [
|
|
'#FF7452', // Coral
|
|
'#4CAF50', // Green
|
|
'#2196F3', // Blue
|
|
'#9C27B0', // Purple
|
|
'#FF9800', // Orange
|
|
'#00BCD4', // Cyan
|
|
'#795548', // Brown
|
|
'#607D8B' // Blue Grey
|
|
];
|
|
|
|
// Generate a consistent index based on username
|
|
const index = username
|
|
.split('')
|
|
.reduce((acc, char) => acc + char.charCodeAt(0), 0) % colors.length;
|
|
|
|
return colors[index];
|
|
}
|