This commit is contained in:
alma 2026-01-18 14:45:00 +01:00
parent aadc8ebc6b
commit 54802eaa4f
2 changed files with 126 additions and 8 deletions

View File

@ -97,17 +97,60 @@ 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,
});
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. Please refresh the email to load attachment data." },
{ 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', {
emailId,

View File

@ -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;
}
}