From 54802eaa4f9c7b86c3b0428b52ee6f52b17dac30 Mon Sep 17 00:00:00 2001 From: alma Date: Sun, 18 Jan 2026 14:45:00 +0100 Subject: [PATCH] SignIn --- .../attachment/[attachmentIndex]/route.ts | 57 ++++++++++++-- lib/services/microsoft-graph-mail.ts | 77 ++++++++++++++++++- 2 files changed, 126 insertions(+), 8 deletions(-) diff --git a/app/api/courrier/[id]/attachment/[attachmentIndex]/route.ts b/app/api/courrier/[id]/attachment/[attachmentIndex]/route.ts index 2b71c22..ae25eaf 100644 --- a/app/api/courrier/[id]/attachment/[attachmentIndex]/route.ts +++ b/app/api/courrier/[id]/attachment/[attachmentIndex]/route.ts @@ -97,16 +97,59 @@ export async function GET( if (attachment.content) { attachmentBuffer = Buffer.from(attachment.content, 'base64'); } else { - // Need to fetch from Graph API - this requires the attachment ID - // For now, return error as we need to modify the email fetching to include attachment IDs - logger.error('[ATTACHMENT] Graph API attachment requires ID but content not cached', { + // Need to fetch from Graph API - try to get attachment ID from email metadata + // First, re-fetch the email to get attachment IDs if available + logger.debug('[ATTACHMENT] Attachment content not cached, fetching from Graph API', { emailId, attachmentIndex: attachmentIdx, + mailCredentialIdHash: graphCheck.mailCredentialId ? Buffer.from(graphCheck.mailCredentialId).toString('base64').slice(0, 12) : null, }); - return NextResponse.json( - { error: "Attachment content not available. Please refresh the email to load attachment data." }, - { status: 404 } - ); + + try { + // Re-fetch the email with full attachment data + const { fetchGraphEmail, fetchGraphAttachment } = await import('@/lib/services/microsoft-graph-mail'); + const graphMessage = await fetchGraphEmail(graphCheck.mailCredentialId, emailId); + + if (!graphMessage.attachments || attachmentIdx >= graphMessage.attachments.length) { + return NextResponse.json( + { error: "Attachment not found" }, + { status: 404 } + ); + } + + const graphAttachment = graphMessage.attachments[attachmentIdx]; + + // If still no contentBytes, try fetching by attachment ID + if (!graphAttachment.contentBytes && graphAttachment.id) { + const attachmentData = await fetchGraphAttachment( + graphCheck.mailCredentialId, + emailId, + graphAttachment.id + ); + attachmentBuffer = Buffer.from(attachmentData.contentBytes, 'base64'); + } else if (graphAttachment.contentBytes) { + attachmentBuffer = Buffer.from(graphAttachment.contentBytes, 'base64'); + } else { + logger.error('[ATTACHMENT] Graph API attachment has no content and no ID', { + emailId, + attachmentIndex: attachmentIdx, + }); + return NextResponse.json( + { error: "Attachment content not available" }, + { status: 404 } + ); + } + } catch (fetchError) { + logger.error('[ATTACHMENT] Error fetching attachment from Graph API', { + emailId, + attachmentIndex: attachmentIdx, + error: fetchError instanceof Error ? fetchError.message : String(fetchError), + }); + return NextResponse.json( + { error: "Failed to fetch attachment from Microsoft Graph API" }, + { status: 500 } + ); + } } } catch (error) { logger.error('[ATTACHMENT] Error fetching Graph API attachment', { diff --git a/lib/services/microsoft-graph-mail.ts b/lib/services/microsoft-graph-mail.ts index 8c2c0d1..49ff436 100644 --- a/lib/services/microsoft-graph-mail.ts +++ b/lib/services/microsoft-graph-mail.ts @@ -185,6 +185,7 @@ export async function fetchGraphEmails( /** * Fetch a single email by ID from Microsoft Graph + * Automatically fetches attachment content if not included in initial response */ export async function fetchGraphEmail( mailCredentialId: string, @@ -199,7 +200,45 @@ export async function fetchGraphEmail( }, }); - return response.data; + const message = response.data; + + // If email has attachments but they don't have contentBytes, fetch them individually + if (message.hasAttachments && message.attachments && Array.isArray(message.attachments)) { + const attachmentsWithContent = await Promise.all( + message.attachments.map(async (attachment: any) => { + // If contentBytes is missing, fetch the attachment content + if (!attachment.contentBytes && attachment.id) { + try { + logger.debug('Fetching attachment content from Graph API', { + messageId, + attachmentId: attachment.id, + attachmentName: attachment.name, + }); + + const attachmentData = await fetchGraphAttachment(mailCredentialId, messageId, attachment.id); + return { + ...attachment, + contentBytes: attachmentData.contentBytes, + }; + } catch (error) { + logger.error('Error fetching attachment content', { + messageId, + attachmentId: attachment.id, + error: error instanceof Error ? error.message : String(error), + }); + // Return attachment without content if fetch fails + return attachment; + } + } + // Already has contentBytes, return as-is + return attachment; + }) + ); + + message.attachments = attachmentsWithContent; + } + + return message; } catch (error: any) { logger.error('Error fetching email from Microsoft Graph', { mailCredentialIdHash: Buffer.from(mailCredentialId).toString('base64').slice(0, 12), @@ -385,3 +424,39 @@ export async function getGraphUnreadCount( throw error; } } + +/** + * Fetch a single attachment by ID from a message using Microsoft Graph API + */ +export async function fetchGraphAttachment( + mailCredentialId: string, + messageId: string, + attachmentId: string +): Promise<{ + id: string; + name: string; + contentType: string; + size: number; + contentBytes: string; // Base64 encoded content +}> { + try { + const client = await getMicrosoftGraphClient(mailCredentialId); + + const response = await client.get(`/me/messages/${messageId}/attachments/${attachmentId}`); + + return response.data; + } catch (error: any) { + logger.error('Error fetching attachment from Microsoft Graph', { + mailCredentialIdHash: Buffer.from(mailCredentialId).toString('base64').slice(0, 12), + messageId, + attachmentId, + error: error instanceof Error ? error.message : String(error), + }); + + if (error.response?.status === 401 || error.response?.status === 403) { + throw new Error('Microsoft Graph API access denied - may need to re-authenticate with Mail.Read permissions'); + } + + throw error; + } +}