diff --git a/app/api/courrier/account/route.ts b/app/api/courrier/account/route.ts index c8c34d6..bd10728 100644 --- a/app/api/courrier/account/route.ts +++ b/app/api/courrier/account/route.ts @@ -105,13 +105,13 @@ async function ensureUserExists(session: any): Promise { export async function POST(request: Request) { // Authenticate user (declare outside try to access in catch) - const session = await getServerSession(authOptions); - if (!session?.user?.id) { - return NextResponse.json( - { error: 'Unauthorized' }, - { status: 401 } - ); - } + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } try { @@ -265,10 +265,10 @@ export async function POST(request: Request) { export async function DELETE(request: Request) { // Authenticate user (declare outside try to access in catch) - const session = await getServerSession(authOptions); - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - } + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } try { const { searchParams } = new URL(request.url); @@ -351,13 +351,13 @@ export async function DELETE(request: Request) { export async function PATCH(request: Request) { // Authenticate user (declare outside try to access in catch) - const session = await getServerSession(authOptions); - if (!session?.user?.id) { - return NextResponse.json( - { error: 'Unauthorized' }, - { status: 401 } - ); - } + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } try { diff --git a/app/api/courrier/route.ts b/app/api/courrier/route.ts index 32a41b3..8c880d6 100644 --- a/app/api/courrier/route.ts +++ b/app/api/courrier/route.ts @@ -24,13 +24,13 @@ const emailListCache: Record = {}; export async function GET(request: Request) { // Authenticate user (declare outside try to access in catch) - const session = await getServerSession(authOptions); - if (!session || !session.user?.id) { - return NextResponse.json( - { error: "Not authenticated" }, - { status: 401 } - ); - } + const session = await getServerSession(authOptions); + if (!session || !session.user?.id) { + return NextResponse.json( + { error: "Not authenticated" }, + { status: 401 } + ); + } try { diff --git a/app/api/rocket-chat/messages/route.ts b/app/api/rocket-chat/messages/route.ts index fc556bb..bd0206a 100644 --- a/app/api/rocket-chat/messages/route.ts +++ b/app/api/rocket-chat/messages/route.ts @@ -79,7 +79,12 @@ export async function GET(request: Request) { return NextResponse.json({ error: 'Server configuration error' }, { status: 500 }); } - logger.debug('[ROCKET_CHAT] Using Rocket.Chat base URL'); + logger.debug('[ROCKET_CHAT] Using Rocket.Chat base URL', { + baseUrl, + hasToken: !!process.env.ROCKET_CHAT_TOKEN, + hasUserId: !!process.env.ROCKET_CHAT_USER_ID, + hasSecret: !!process.env.ROCKET_CHAT_CREATE_TOKEN_SECRET, + }); // Step 1: Use admin token to authenticate const adminHeaders = { @@ -100,15 +105,37 @@ export async function GET(request: Request) { // Get all users to find the current user let usersData; try { - usersData = await fetchJsonWithTimeout(`${baseUrl}/api/v1/users.list`, { + const usersListUrl = `${baseUrl}/api/v1/users.list`; + logger.debug('[ROCKET_CHAT] Fetching users list', { url: usersListUrl }); + + usersData = await fetchJsonWithTimeout(usersListUrl, { method: 'GET', timeout: 10000, // 10 seconds headers: adminHeaders }); - } catch (error) { - logger.error('[ROCKET_CHAT] Error fetching users list', { - error: error instanceof Error ? error.message : String(error), + + logger.debug('[ROCKET_CHAT] Successfully fetched users list', { + success: usersData.success, + count: usersData.count, }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error('[ROCKET_CHAT] Error fetching users list', { + error: errorMessage, + baseUrl, + url: `${baseUrl}/api/v1/users.list`, + hasToken: !!process.env.ROCKET_CHAT_TOKEN, + hasUserId: !!process.env.ROCKET_CHAT_USER_ID, + }); + + // If it's an HTML response error, log more details + if (errorMessage.includes('text/html')) { + logger.error('[ROCKET_CHAT] RocketChat returned HTML instead of JSON - possible authentication or URL issue', { + baseUrl, + checkUrl: `${baseUrl}/api/v1/users.list`, + }); + } + // Return empty messages instead of failing completely return NextResponse.json({ messages: [] }, { status: 200 }); } @@ -412,10 +439,28 @@ export async function GET(request: Request) { return NextResponse.json(finalResponse); } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); logger.error('[ROCKET_CHAT] Error fetching messages', { - error: error instanceof Error ? error.message : String(error), + error: errorMessage, + baseUrl: process.env.NEXT_PUBLIC_IFRAME_PAROLE_URL?.split('/channel')[0], + hasToken: !!process.env.ROCKET_CHAT_TOKEN, + hasUserId: !!process.env.ROCKET_CHAT_USER_ID, + hasSecret: !!process.env.ROCKET_CHAT_CREATE_TOKEN_SECRET, }); - return NextResponse.json({ error: 'Failed to fetch messages' }, { status: 500 }); + + // If it's an HTML response error, provide more helpful message + if (errorMessage.includes('text/html') || errorMessage.includes('HTML')) { + logger.error('[ROCKET_CHAT] RocketChat is returning HTML - possible causes:', { + cause1: 'Invalid or expired ROCKET_CHAT_TOKEN', + cause2: 'Invalid or expired ROCKET_CHAT_USER_ID', + cause3: 'Incorrect baseUrl (check NEXT_PUBLIC_IFRAME_PAROLE_URL)', + cause4: 'RocketChat server is down or unreachable', + cause5: 'RocketChat API endpoint changed or requires different authentication', + }); + } + + // Return empty messages instead of error to prevent widget from breaking + return NextResponse.json({ messages: [], total: 0, hasMore: false, totalUnreadCount: 0 }, { status: 200 }); } } diff --git a/app/api/rocket-chat/user-token/route.ts b/app/api/rocket-chat/user-token/route.ts index cf6e157..f067d27 100644 --- a/app/api/rocket-chat/user-token/route.ts +++ b/app/api/rocket-chat/user-token/route.ts @@ -27,6 +27,13 @@ export async function GET(request: Request) { ); } + logger.debug('[ROCKET_CHAT_USER_TOKEN] Using Rocket.Chat base URL', { + baseUrl, + hasToken: !!process.env.ROCKET_CHAT_TOKEN, + hasUserId: !!process.env.ROCKET_CHAT_USER_ID, + hasSecret: !!process.env.ROCKET_CHAT_CREATE_TOKEN_SECRET, + }); + // Use admin token to authenticate const adminHeaders = { 'X-Auth-Token': process.env.ROCKET_CHAT_TOKEN!, @@ -46,14 +53,26 @@ export async function GET(request: Request) { // Get all users to find the current user let usersResponse; + const usersListUrl = `${baseUrl}/api/v1/users.list`; try { - usersResponse = await fetch(`${baseUrl}/api/v1/users.list`, { + logger.debug('[ROCKET_CHAT_USER_TOKEN] Fetching users list', { url: usersListUrl }); + + usersResponse = await fetch(usersListUrl, { method: 'GET', headers: adminHeaders }); + + logger.debug('[ROCKET_CHAT_USER_TOKEN] Users list response', { + status: usersResponse.status, + statusText: usersResponse.statusText, + contentType: usersResponse.headers.get('content-type'), + }); } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); logger.error('[ROCKET_CHAT_USER_TOKEN] Error fetching users list', { - error: error instanceof Error ? error.message : String(error), + error: errorMessage, + baseUrl, + url: usersListUrl, }); return NextResponse.json( { error: 'Failed to connect to RocketChat' }, @@ -125,13 +144,13 @@ export async function GET(request: Request) { let createTokenResponse; try { createTokenResponse = await fetch(`${baseUrl}/api/v1/users.createToken`, { - method: 'POST', - headers: adminHeaders, - body: JSON.stringify({ - userId: currentUser._id, - secret: secret - }) - }); + method: 'POST', + headers: adminHeaders, + body: JSON.stringify({ + userId: currentUser._id, + secret: secret + }) + }); } catch (error) { logger.error('[ROCKET_CHAT_USER_TOKEN] Error creating token', { error: error instanceof Error ? error.message : String(error), diff --git a/app/mission-tab/[missionId]/page.tsx b/app/mission-tab/[missionId]/page.tsx index 852a255..8e3b56a 100644 --- a/app/mission-tab/[missionId]/page.tsx +++ b/app/mission-tab/[missionId]/page.tsx @@ -221,7 +221,7 @@ export default function MissionTabDetailPage() {
{mission.logoUrl ? ( {mission.name} {mission.logoUrl ? ( {mission.name}
- ); + ); } export default async function Home() { @@ -70,47 +70,47 @@ export default async function Home() { return ( <> -
-
- {/* First row */} -
+
+
+ {/* First row */} +
}> -
- -
+
+ +
}> -
- -
+
+ +
}> -
- -
+
+ +
}> -
- -
-
+
+
- - {/* Second row */} -
+ +
+ + {/* Second row */} +
}> -
- -
+
+ +
}> -
- -
-
+
+
+
-
+
+
); } diff --git a/components/calendar/calendar-widget.tsx b/components/calendar/calendar-widget.tsx index 79e75f5..f978478 100644 --- a/components/calendar/calendar-widget.tsx +++ b/components/calendar/calendar-widget.tsx @@ -36,7 +36,7 @@ export function CalendarWidget() { const fetchUpcomingEvents = async () => { try { setLoading(true); - + // Récupérer d'abord les calendriers de l'utilisateur const calendarsRes = await fetch("/api/calendars"); @@ -45,7 +45,7 @@ export function CalendarWidget() { } const calendars = await calendarsRes.json(); - + if (calendars.length === 0) { setEvents([]); setLoading(false); @@ -55,7 +55,7 @@ export function CalendarWidget() { // Date actuelle et date dans 7 jours const now = new Date(); const nextWeek = addDays(now, 7); - + // Helper function to get display name for calendar const getCalendarDisplayName = (calendar: any) => { // If calendar is synced to an external account, use the account name @@ -88,7 +88,7 @@ export function CalendarWidget() { return events.map((event: any) => ({ ...event, calendarName: getCalendarDisplayName(calendar), - calendarColor: calendar.color, + calendarColor: calendar.color, })); }); @@ -105,10 +105,10 @@ export function CalendarWidget() { // Limiter à 5 événements pour l'affichage const upcomingEvents = sortedEvents.slice(0, 5); - + // Calculate current event count (all events, not just displayed) const currentEventCount = sortedEvents.length; - + // Prepare notification items for all upcoming events const notificationItems = sortedEvents.map(event => ({ id: event.id, diff --git a/components/parole.tsx b/components/parole.tsx index 116c63d..b9ec04b 100644 --- a/components/parole.tsx +++ b/components/parole.tsx @@ -70,8 +70,8 @@ export function Parole() { // Check if response is JSON before trying to parse const contentType = response.headers.get('content-type') || ''; if (contentType.includes('application/json')) { - const errorData = await response.json(); - throw new Error(errorData.error || 'Failed to fetch messages'); + const errorData = await response.json(); + throw new Error(errorData.error || 'Failed to fetch messages'); } else { // Response is HTML (probably an error page) const errorText = await response.text(); diff --git a/lib/utils/fetch-with-timeout.ts b/lib/utils/fetch-with-timeout.ts index d166925..1628434 100644 --- a/lib/utils/fetch-with-timeout.ts +++ b/lib/utils/fetch-with-timeout.ts @@ -78,8 +78,18 @@ export async function fetchJsonWithTimeout( ): Promise { const response = await fetchWithTimeout(url, options); + // Clone response early so we can read it multiple times if needed + const clonedResponse = response.clone(); + if (!response.ok) { - const errorText = await response.text().catch(() => 'Unknown error'); + const errorText = await clonedResponse.text().catch(() => 'Unknown error'); + const contentType = response.headers.get('content-type') || ''; + + // If it's HTML, provide more context + if (contentType.includes('text/html') || errorText.trim().startsWith('( // Check if response is JSON if (!contentType.includes('application/json')) { - // Try to read the response text to see what we got - const responseText = await response.text().catch(() => 'Unable to read response'); - const preview = responseText.substring(0, 200); + // Read the response text to see what we got + const responseText = await clonedResponse.text().catch(() => 'Unable to read response'); + const preview = responseText.substring(0, 500); // If it looks like HTML, provide a more helpful error if (responseText.trim().startsWith(' 'Unable to read response'); - throw new Error(`Failed to parse JSON response. Content: ${responseText.substring(0, 200)}`); + throw new Error(`Failed to parse JSON response from ${url}. Content: ${responseText.substring(0, 500)}`); } }