From 266805950ed008c09aaccb7485a1506afbc94f93 Mon Sep 17 00:00:00 2001 From: alma Date: Mon, 28 Apr 2025 15:12:38 +0200 Subject: [PATCH] courrier multi account restore compose --- lib/services/email-service.ts | 184 ++++++++++++++-------------------- 1 file changed, 77 insertions(+), 107 deletions(-) diff --git a/lib/services/email-service.ts b/lib/services/email-service.ts index 805699ea..2cb14274 100644 --- a/lib/services/email-service.ts +++ b/lib/services/email-service.ts @@ -266,114 +266,84 @@ interface FetchOptions { */ export async function getEmails( userId: string, - folder: string = 'INBOX', + folder: string, page: number = 1, perPage: number = 20, - searchQuery: string = '', accountId?: string -): Promise { - console.log(`Fetching emails for user ${userId}${accountId ? ` account ${accountId}` : ''} in folder ${folder}`); - - // Extract the base folder name if it's an account-specific folder - const baseFolder = accountId && folder.includes(`-${accountId}`) - ? folder.split(`-${accountId}`)[0] - : folder; - - console.log(`Using base folder name: ${baseFolder} for IMAP operations`); - - // Try to get from cache first - const cached = await getCachedEmailList(userId, accountId || 'default', folder, page, perPage); - if (cached) { - console.log(`Using cached email list for ${userId}${accountId ? ` account ${accountId}` : ''} in folder ${folder}`); - return cached; - } - - // Get IMAP connection for the specific account - const client = await getImapConnection(userId, accountId); - if (!client) { - throw new Error('Failed to get IMAP connection'); - } - +): Promise { try { - // Open the mailbox using the base folder name - await client.mailboxOpen(baseFolder); + // Extract the base folder name by removing the accountId suffix if present + const baseFolder = accountId && folder.includes(`-${accountId}`) + ? folder.split(`-${accountId}`)[0] + : folder; + + console.log(`Fetching emails for folder: ${baseFolder} (original: ${folder})`); + + // Get IMAP connection for the account + const imap = await getImapConnection(userId, accountId); + if (!imap) { + throw new Error('Failed to establish IMAP connection'); + } + + // Open the mailbox + await imap.mailboxOpen(baseFolder); - // Get total count of messages - const status = await client.status(baseFolder, { messages: true }); - const totalEmails = status.messages || 0; - const totalPages = Math.ceil(totalEmails / perPage); - - // Calculate the range of messages to fetch - const start = (page - 1) * perPage + 1; - const end = Math.min(start + perPage - 1, totalEmails); + // Calculate message range for pagination + const totalMessages = await imap.status(baseFolder, { messages: true }); + const total = totalMessages.messages || 0; + const start = Math.max(1, total - (page * perPage) + 1); + const end = Math.max(1, total - ((page - 1) * perPage)); + // Fetch messages + const messages = await imap.fetch(`${start}:${end}`, { + envelope: true, + flags: true, + bodyStructure: true, + uid: true + }); + const emails: EmailMessage[] = []; - if (totalEmails > 0) { - // Fetch messages in the specified range - const messages = await client.fetch(`${start}:${end}`, { - envelope: true, - flags: true, - bodyStructure: true, - size: true, - bodyParts: ['HEADER'] - }); - - for await (const message of messages) { - const email: EmailMessage = { - id: message.uid.toString(), - from: (message.envelope?.from || []).map(addr => ({ - name: addr.name || '', - address: addr.address || '' - })), - to: (message.envelope?.to || []).map(addr => ({ - name: addr.name || '', - address: addr.address || '' - })), - subject: message.envelope?.subject || '', - date: message.internalDate || new Date(), - flags: { - seen: message.flags.has('\\Seen'), - answered: message.flags.has('\\Answered'), - flagged: message.flags.has('\\Flagged'), - draft: message.flags.has('\\Draft'), - deleted: message.flags.has('\\Deleted') - }, - size: message.size || 0, - hasAttachments: message.bodyStructure?.childNodes?.some(node => node.disposition === 'attachment') || false, - folder: folder, // Use the original folder name with account ID - contentFetched: false, - accountId: accountId, - content: '', - messageId: message.envelope?.messageId || undefined - }; - emails.push(email); - } + for await (const message of messages) { + const email: EmailMessage = { + id: message.uid.toString(), + from: (message.envelope?.from || []).map(addr => ({ + name: addr.name || '', + address: addr.address || '' + })), + to: (message.envelope?.to || []).map(addr => ({ + name: addr.name || '', + address: addr.address || '' + })), + subject: message.envelope?.subject || '', + date: message.envelope?.date || new Date(), + flags: { + seen: message.flags.has('\\Seen'), + answered: message.flags.has('\\Answered'), + flagged: message.flags.has('\\Flagged'), + draft: message.flags.has('\\Draft'), + deleted: message.flags.has('\\Deleted') + }, + size: message.size || 0, + hasAttachments: message.bodyStructure?.childNodes?.some(node => node.disposition === 'attachment') || false, + folder: folder, + contentFetched: false, + accountId: accountId || '', + content: '', + messageId: message.envelope?.messageId || undefined + }; + emails.push(email); } - const result: EmailListResult = { - emails, - totalEmails, - page, - perPage, - totalPages, - folder, - mailboxes: await getMailboxes(client, accountId) // Pass accountId to getMailboxes - }; + // Cache the result + if (accountId) { + await cacheEmailList(userId, accountId, folder, page, perPage, emails); + } - // Cache the result with the account-specific folder - await cacheEmailList(userId, accountId || 'default', folder, page, perPage, result); - - return result; + return emails; } catch (error) { - console.error(`Error fetching emails for folder ${baseFolder}:`, error); + console.error(`Error fetching emails for folder ${folder}:`, error); throw error; - } finally { - try { - await client.mailboxClose(); - } catch (error) { - console.error('Error closing mailbox:', error); - } } } @@ -594,7 +564,7 @@ export async function getMailboxes(client: ImapFlow, accountId?: string): Promis try { const mailboxes = await client.list(); - // Map special folders to standard names, but keep account-specific paths + // Map special folders to standard names const specialFolders = new Map([ ['Sent Messages', 'Sent'], ['Sent Items', 'Sent'], @@ -604,31 +574,31 @@ export async function getMailboxes(client: ImapFlow, accountId?: string): Promis ['Spam', 'Junk'] ]); - // Process mailboxes and map special folders while preserving account-specific paths + // Process mailboxes and map special folders const processedMailboxes = mailboxes.map(mailbox => { const path = mailbox.path; // Check if this is a special folder for (const [special, standard] of specialFolders) { if (path.includes(special)) { - // Include accountId in the folder name to make it unique per account - return accountId ? `${standard}-${accountId}` : standard; + return standard; } } - // Include accountId in the folder name to make it unique per account - return accountId ? `${path}-${accountId}` : path; + return path; }); - // Ensure we have all standard folders with account-specific names - const standardFolders = accountId - ? ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk'].map(f => `${f}-${accountId}`) - : ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']; + // Ensure we have all standard folders + const standardFolders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']; + // Combine standard folders with custom folders const uniqueFolders = new Set([...standardFolders, ...processedMailboxes]); - return Array.from(uniqueFolders); + // If accountId is provided, append it to each folder name + return accountId + ? Array.from(uniqueFolders).map(f => `${f}-${accountId}`) + : Array.from(uniqueFolders); } catch (error) { console.error('Error fetching mailboxes:', error); - // Return default folders on error, with account-specific names if accountId is provided + // Return default folders on error const defaultFolders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']; return accountId ? defaultFolders.map(f => `${f}-${accountId}`)