import { getServerSession } from "next-auth"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { NextResponse } from "next/server"; // 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 }); } 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); return NextResponse.json({ messages: sortedMessages, total: Object.keys(latestMessagePerRoom).length, hasMore: Object.keys(latestMessagePerRoom).length > 10 }, { status: 200 }); } catch (error) { console.error('Error in messages endpoint:', error); return NextResponse.json({ messages: [], total: 0, hasMore: false }, { status: 200 }); } } // 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]; }