From 807200d9e4155caba04a488ab8e3fa7b40525646 Mon Sep 17 00:00:00 2001 From: alma Date: Sun, 18 Jan 2026 14:53:59 +0100 Subject: [PATCH] attachments courrier --- lib/services/microsoft-graph-mail.ts | 180 ++++++++++++++++++--------- 1 file changed, 118 insertions(+), 62 deletions(-) diff --git a/lib/services/microsoft-graph-mail.ts b/lib/services/microsoft-graph-mail.ts index fdfa4bc..b1dd8cb 100644 --- a/lib/services/microsoft-graph-mail.ts +++ b/lib/services/microsoft-graph-mail.ts @@ -210,81 +210,137 @@ export async function fetchGraphEmail( hasAttachments: message.hasAttachments, attachmentsInResponse: !!message.attachments, attachmentsCount: message.attachments?.length || 0, + attachmentsType: typeof message.attachments, + attachmentsIsArray: Array.isArray(message.attachments), + attachmentsKeys: message.attachments ? Object.keys(message.attachments) : [], mailCredentialIdHash: Buffer.from(mailCredentialId).toString('base64').slice(0, 12), }); - // If email has attachments but they weren't included in the response, fetch them separately - if (message.hasAttachments && (!message.attachments || message.attachments.length === 0)) { - try { - logger.debug('Fetching attachments list from Graph API', { + // Handle case where $expand returns attachments in a different structure + // Sometimes attachments might be in message.attachments.value instead of message.attachments + if (message.attachments && !Array.isArray(message.attachments) && message.attachments.value) { + logger.debug('Attachments found in .value property', { + messageId, + count: message.attachments.value?.length || 0, + }); + message.attachments = message.attachments.value; + } + + // Process attachments - either from initial response or fetch separately + if (message.hasAttachments) { + // If attachments are already in the response, process them + if (message.attachments && Array.isArray(message.attachments) && message.attachments.length > 0) { + logger.debug('Processing attachments from initial response', { messageId, - mailCredentialIdHash: Buffer.from(mailCredentialId).toString('base64').slice(0, 12), - }); - - // Fetch attachments separately - don't use $select as it may cause issues - // Microsoft Graph API will return all attachment properties by default - const attachmentsResponse = await client.get(`/me/messages/${messageId}/attachments`); - - const attachments = attachmentsResponse.data.value || []; - - logger.debug('Fetched attachments from Graph API', { - messageId, - attachmentsCount: attachments.length, - mailCredentialIdHash: Buffer.from(mailCredentialId).toString('base64').slice(0, 12), + attachmentsCount: message.attachments.length, }); // Process attachments - fetch content if not included - if (attachments.length > 0) { - const attachmentsWithContent = await Promise.all( - 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; - } + 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; - } else { - logger.warn('Email has hasAttachments=true but no attachments returned', { + } + // Already has contentBytes, return as-is + return attachment; + }) + ); + + message.attachments = attachmentsWithContent; + } else { + // Attachments not in initial response, fetch them separately + try { + logger.debug('Fetching attachments list from Graph API', { messageId, mailCredentialIdHash: Buffer.from(mailCredentialId).toString('base64').slice(0, 12), }); + + // Fetch attachments separately - don't use $select as it may cause issues + // Microsoft Graph API will return all attachment properties by default + const attachmentsResponse = await client.get(`/me/messages/${messageId}/attachments`); + + const attachments = attachmentsResponse.data.value || []; + + logger.debug('Fetched attachments from Graph API', { + messageId, + attachmentsCount: attachments.length, + mailCredentialIdHash: Buffer.from(mailCredentialId).toString('base64').slice(0, 12), + }); + + // Process attachments - fetch content if not included + if (attachments.length > 0) { + const attachmentsWithContent = await Promise.all( + 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; + } else { + logger.warn('Email has hasAttachments=true but no attachments returned', { + messageId, + mailCredentialIdHash: Buffer.from(mailCredentialId).toString('base64').slice(0, 12), + }); + message.attachments = []; + } + } catch (attachmentsError: any) { + logger.error('Error fetching attachments from Graph API', { + messageId, + error: attachmentsError instanceof Error ? attachmentsError.message : String(attachmentsError), + status: attachmentsError.response?.status, + statusText: attachmentsError.response?.statusText, + responseData: attachmentsError.response?.data, + url: `/me/messages/${messageId}/attachments`, + }); + // Continue without attachments if fetch fails message.attachments = []; } - } catch (attachmentsError: any) { - logger.error('Error fetching attachments from Graph API', { - messageId, - error: attachmentsError instanceof Error ? attachmentsError.message : String(attachmentsError), - status: attachmentsError.response?.status, - statusText: attachmentsError.response?.statusText, - responseData: attachmentsError.response?.data, - url: `/me/messages/${messageId}/attachments`, - }); - // Continue without attachments if fetch fails - message.attachments = []; } } else { message.attachments = [];