courrier multi account restore compose
This commit is contained in:
parent
0368bd1069
commit
2fada01eba
@ -45,15 +45,24 @@ export async function POST(
|
|||||||
|
|
||||||
const { folder = 'INBOX', accountId, isRead = true } = await request.json();
|
const { folder = 'INBOX', accountId, isRead = true } = await request.json();
|
||||||
|
|
||||||
|
// Extract account ID from folder name if present and none was explicitly provided
|
||||||
|
const folderAccountId = folder.includes(':') ? folder.split(':')[0] : accountId;
|
||||||
|
|
||||||
|
// Use the most specific account ID available
|
||||||
|
const effectiveAccountId = folderAccountId || accountId || 'default';
|
||||||
|
|
||||||
|
// Normalize folder name by removing account prefix if present
|
||||||
|
const normalizedFolder = folder.includes(':') ? folder.split(':')[1] : folder;
|
||||||
|
|
||||||
// Log operation details for debugging
|
// Log operation details for debugging
|
||||||
console.log(`Marking email ${emailId} as ${isRead ? 'read' : 'unread'} in folder ${folder}${accountId ? ` for account ${accountId}` : ''}`);
|
console.log(`Marking email ${emailId} as ${isRead ? 'read' : 'unread'} in folder ${normalizedFolder} for account ${effectiveAccountId}`);
|
||||||
|
|
||||||
const success = await markEmailReadStatus(
|
const success = await markEmailReadStatus(
|
||||||
session.user.id,
|
session.user.id,
|
||||||
emailId,
|
emailId,
|
||||||
isRead,
|
isRead,
|
||||||
folder,
|
normalizedFolder,
|
||||||
accountId
|
effectiveAccountId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|||||||
@ -37,31 +37,42 @@ export async function GET(request: Request) {
|
|||||||
const searchQuery = searchParams.get("search") || "";
|
const searchQuery = searchParams.get("search") || "";
|
||||||
const accountId = searchParams.get("accountId") || "";
|
const accountId = searchParams.get("accountId") || "";
|
||||||
|
|
||||||
|
// Extract account ID from folder name if present and none was explicitly provided
|
||||||
|
const folderAccountId = folder.includes(':') ? folder.split(':')[0] : accountId;
|
||||||
|
|
||||||
|
// Use the most specific account ID available
|
||||||
|
const effectiveAccountId = folderAccountId || accountId || 'default';
|
||||||
|
|
||||||
|
// Normalize folder name by removing account prefix if present
|
||||||
|
const normalizedFolder = folder.includes(':') ? folder.split(':')[1] : folder;
|
||||||
|
|
||||||
|
// Log the request details for debugging
|
||||||
|
console.log(`Email request: user=${session.user.id}, folder=${normalizedFolder}, account=${effectiveAccountId}, page=${page}`);
|
||||||
|
|
||||||
// Try to get from Redis cache first, but only if it's not a search query
|
// Try to get from Redis cache first, but only if it's not a search query
|
||||||
if (!searchQuery) {
|
if (!searchQuery) {
|
||||||
const cacheKey = accountId ? `${session.user.id}:${accountId}:${folder}` : `${session.user.id}:${folder}`;
|
|
||||||
const cachedEmails = await getCachedEmailList(
|
const cachedEmails = await getCachedEmailList(
|
||||||
session.user.id,
|
session.user.id,
|
||||||
accountId || 'default',
|
effectiveAccountId,
|
||||||
folder,
|
normalizedFolder,
|
||||||
page,
|
page,
|
||||||
perPage
|
perPage
|
||||||
);
|
);
|
||||||
if (cachedEmails) {
|
if (cachedEmails) {
|
||||||
console.log(`Using Redis cached emails for ${cacheKey}:${page}:${perPage}`);
|
console.log(`Using Redis cached emails for ${session.user.id}:${effectiveAccountId}:${normalizedFolder}:${page}:${perPage}`);
|
||||||
return NextResponse.json(cachedEmails);
|
return NextResponse.json(cachedEmails);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Redis cache miss for ${session.user.id}:${folder}:${page}:${perPage}, fetching emails from IMAP`);
|
console.log(`Redis cache miss for ${session.user.id}:${effectiveAccountId}:${normalizedFolder}:${page}:${perPage}, fetching emails from IMAP`);
|
||||||
|
|
||||||
// Use the email service to fetch emails, passing the accountId if provided
|
// Use the email service to fetch emails with the normalized folder and effective account ID
|
||||||
const emailsResult = await getEmails(
|
const emailsResult = await getEmails(
|
||||||
session.user.id,
|
session.user.id,
|
||||||
folder,
|
normalizedFolder,
|
||||||
page,
|
page,
|
||||||
perPage,
|
perPage,
|
||||||
accountId || undefined
|
effectiveAccountId
|
||||||
);
|
);
|
||||||
|
|
||||||
// The result is already cached in the getEmails function
|
// The result is already cached in the getEmails function
|
||||||
|
|||||||
@ -266,11 +266,22 @@ export const useCourrier = () => {
|
|||||||
const changeFolder = useCallback(async (folder: string, accountId?: string) => {
|
const changeFolder = useCallback(async (folder: string, accountId?: string) => {
|
||||||
console.log(`Changing folder to ${folder} for account ${accountId || 'default'}`);
|
console.log(`Changing folder to ${folder} for account ${accountId || 'default'}`);
|
||||||
try {
|
try {
|
||||||
|
// Extract account ID from folder name if present and none was explicitly provided
|
||||||
|
const folderAccountId = folder.includes(':') ? folder.split(':')[0] : accountId;
|
||||||
|
|
||||||
|
// Use the most specific account ID available
|
||||||
|
const effectiveAccountId = folderAccountId || accountId || 'default';
|
||||||
|
|
||||||
|
// Normalize folder name by removing account prefix if present
|
||||||
|
const normalizedFolder = folder.includes(':') ? folder.split(':')[1] : folder;
|
||||||
|
|
||||||
|
console.log(`Folder change normalized: ${normalizedFolder}, account: ${effectiveAccountId}`);
|
||||||
|
|
||||||
// Reset selected email
|
// Reset selected email
|
||||||
setSelectedEmail(null);
|
setSelectedEmail(null);
|
||||||
setSelectedEmailIds([]);
|
setSelectedEmailIds([]);
|
||||||
|
|
||||||
// Record the new folder
|
// Record the new folder (preserving account prefix if present)
|
||||||
setCurrentFolder(folder);
|
setCurrentFolder(folder);
|
||||||
|
|
||||||
// Reset search query when changing folders
|
// Reset search query when changing folders
|
||||||
@ -289,8 +300,8 @@ export const useCourrier = () => {
|
|||||||
// This helps prevent race conditions when multiple folders are clicked quickly
|
// This helps prevent race conditions when multiple folders are clicked quickly
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
// Call loadEmails with correct boolean parameter type
|
// Call loadEmails with correct boolean parameter type and account ID
|
||||||
await loadEmails(false, accountId);
|
await loadEmails(false, effectiveAccountId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error changing to folder ${folder}:`, error);
|
console.error(`Error changing to folder ${folder}:`, error);
|
||||||
setError(error instanceof Error ? error.message : 'Unknown error');
|
setError(error instanceof Error ? error.message : 'Unknown error');
|
||||||
@ -323,11 +334,27 @@ export const useCourrier = () => {
|
|||||||
// Fetch a single email's content
|
// Fetch a single email's content
|
||||||
const fetchEmailContent = useCallback(async (emailId: string, accountId?: string, folderOverride?: string) => {
|
const fetchEmailContent = useCallback(async (emailId: string, accountId?: string, folderOverride?: string) => {
|
||||||
try {
|
try {
|
||||||
|
// Use the provided folder or current folder
|
||||||
const folderToUse = folderOverride || currentFolder;
|
const folderToUse = folderOverride || currentFolder;
|
||||||
|
|
||||||
|
// Extract account ID from folder name if present and none was explicitly provided
|
||||||
|
const folderAccountId = folderToUse.includes(':') ? folderToUse.split(':')[0] : accountId;
|
||||||
|
|
||||||
|
// Use the most specific account ID available
|
||||||
|
const effectiveAccountId = folderAccountId || accountId || 'default';
|
||||||
|
|
||||||
|
// Normalize folder name by removing account prefix if present
|
||||||
|
const normalizedFolder = folderToUse.includes(':') ? folderToUse.split(':')[1] : folderToUse;
|
||||||
|
|
||||||
|
console.log(`Fetching email content for ID ${emailId} from folder ${normalizedFolder}, account: ${effectiveAccountId}`);
|
||||||
|
|
||||||
const query = new URLSearchParams({
|
const query = new URLSearchParams({
|
||||||
folder: folderToUse,
|
folder: normalizedFolder,
|
||||||
});
|
});
|
||||||
if (accountId) query.set('accountId', accountId);
|
|
||||||
|
// Always include account ID in query params
|
||||||
|
query.set('accountId', effectiveAccountId);
|
||||||
|
|
||||||
const response = await fetch(`/api/courrier/${emailId}?${query.toString()}`);
|
const response = await fetch(`/api/courrier/${emailId}?${query.toString()}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to fetch email content: ${response.status}`);
|
throw new Error(`Failed to fetch email content: ${response.status}`);
|
||||||
@ -350,7 +377,14 @@ export const useCourrier = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the accountId from the email
|
// Get the accountId from the email
|
||||||
const emailAccountId = emailToMark.accountId;
|
const emailAccountId = emailToMark.accountId || 'default';
|
||||||
|
|
||||||
|
// Normalize folder name by removing account prefix if present
|
||||||
|
const normalizedFolder = emailToMark.folder.includes(':')
|
||||||
|
? emailToMark.folder.split(':')[1]
|
||||||
|
: emailToMark.folder;
|
||||||
|
|
||||||
|
console.log(`Marking email ${emailId} as ${isRead ? 'read' : 'unread'} in folder ${normalizedFolder}, account: ${emailAccountId}`);
|
||||||
|
|
||||||
const response = await fetch(`/api/courrier/${emailId}/mark-read`, {
|
const response = await fetch(`/api/courrier/${emailId}/mark-read`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -359,7 +393,7 @@ export const useCourrier = () => {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
isRead,
|
isRead,
|
||||||
folder: currentFolder,
|
folder: normalizedFolder,
|
||||||
accountId: emailAccountId
|
accountId: emailAccountId
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -383,20 +417,39 @@ export const useCourrier = () => {
|
|||||||
console.error('Error marking email as read:', error);
|
console.error('Error marking email as read:', error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}, [emails, selectedEmail, currentFolder]);
|
}, [emails, selectedEmail]);
|
||||||
|
|
||||||
// Select an email to view
|
// Select an email to view
|
||||||
const handleEmailSelect = useCallback(async (emailId: string, accountId: string, folderOverride: string) => {
|
const handleEmailSelect = useCallback(async (emailId: string, accountId: string, folderOverride: string) => {
|
||||||
|
console.log(`Selecting email ${emailId} from account ${accountId} in folder ${folderOverride}`);
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
|
// Normalize account ID if not provided
|
||||||
|
const effectiveAccountId = accountId || 'default';
|
||||||
|
|
||||||
|
// Normalize folder name by removing account prefix if present
|
||||||
|
const normalizedFolder = folderOverride.includes(':') ? folderOverride.split(':')[1] : folderOverride;
|
||||||
|
|
||||||
// Find the email in the current list
|
// Find the email in the current list
|
||||||
const email = emails.find(e => e.id === emailId && e.accountId === accountId && e.folder === folderOverride);
|
const email = emails.find(e =>
|
||||||
|
e.id === emailId &&
|
||||||
|
(e.accountId === effectiveAccountId) &&
|
||||||
|
(e.folder === normalizedFolder || e.folder === folderOverride)
|
||||||
|
);
|
||||||
|
|
||||||
if (!email) {
|
if (!email) {
|
||||||
throw new Error('Email not found');
|
console.log(`Email ${emailId} not found in current list. Fetching from API.`);
|
||||||
|
const fullEmail = await fetchEmailContent(emailId, effectiveAccountId, normalizedFolder);
|
||||||
|
setSelectedEmail(fullEmail);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If content is not fetched, get the full content
|
// If content is not fetched, get the full content
|
||||||
if (!email.contentFetched) {
|
if (!email.contentFetched) {
|
||||||
const fullEmail = await fetchEmailContent(emailId, accountId, folderOverride);
|
console.log(`Fetching content for email ${emailId}`);
|
||||||
|
const fullEmail = await fetchEmailContent(emailId, effectiveAccountId, normalizedFolder);
|
||||||
|
|
||||||
// Merge the full content with the email
|
// Merge the full content with the email
|
||||||
const updatedEmail = {
|
const updatedEmail = {
|
||||||
...email,
|
...email,
|
||||||
@ -404,12 +457,14 @@ export const useCourrier = () => {
|
|||||||
attachments: fullEmail.attachments,
|
attachments: fullEmail.attachments,
|
||||||
contentFetched: true
|
contentFetched: true
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the email in the list
|
// Update the email in the list
|
||||||
setEmails(emails.map(e => e.id === emailId ? updatedEmail : e));
|
setEmails(emails.map(e => e.id === emailId ? updatedEmail : e));
|
||||||
setSelectedEmail(updatedEmail);
|
setSelectedEmail(updatedEmail);
|
||||||
} else {
|
} else {
|
||||||
setSelectedEmail(email);
|
setSelectedEmail(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the email as read if it's not already
|
// Mark the email as read if it's not already
|
||||||
if (!email.flags.seen) {
|
if (!email.flags.seen) {
|
||||||
markEmailAsRead(emailId, true);
|
markEmailAsRead(emailId, true);
|
||||||
|
|||||||
@ -195,7 +195,7 @@ export async function getUserEmailCredentials(userId: string, accountId?: string
|
|||||||
secure: mailCredentials.secure,
|
secure: mailCredentials.secure,
|
||||||
smtp_host: mailCredentials.smtp_host || undefined,
|
smtp_host: mailCredentials.smtp_host || undefined,
|
||||||
smtp_port: mailCredentials.smtp_port || undefined,
|
smtp_port: mailCredentials.smtp_port || undefined,
|
||||||
smtp_secure: mailCredentials.smtp_secure || false,
|
smtp_secure: mailCredentials.smtp_secure ?? false,
|
||||||
display_name: mailCredentials.display_name || undefined,
|
display_name: mailCredentials.display_name || undefined,
|
||||||
color: mailCredentials.color || undefined
|
color: mailCredentials.color || undefined
|
||||||
};
|
};
|
||||||
@ -282,26 +282,33 @@ export async function getEmails(
|
|||||||
let client: ImapFlow | undefined;
|
let client: ImapFlow | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Extract the actual folder name (remove account prefix if present)
|
// Extract account ID from folder name if present and none was explicitly provided
|
||||||
const actualFolder = folder.includes(':') ? folder.split(':')[1] : folder;
|
const folderAccountId = folder.includes(':') ? folder.split(':')[0] : accountId;
|
||||||
console.log(`Fetching emails for folder: ${folder} (actual: ${actualFolder})`);
|
|
||||||
|
|
||||||
// Get IMAP connection
|
// Use the most specific account ID available
|
||||||
client = await getImapConnection(userId, accountId);
|
const effectiveAccountId = folderAccountId || accountId || 'default';
|
||||||
|
|
||||||
|
// Normalize folder name by removing account prefix if present
|
||||||
|
const normalizedFolder = folder.includes(':') ? folder.split(':')[1] : folder;
|
||||||
|
|
||||||
|
console.log(`[getEmails] Processing request for folder: ${folder}, normalized to ${normalizedFolder}, account: ${effectiveAccountId}`);
|
||||||
|
|
||||||
|
// Get IMAP connection using the effective account ID
|
||||||
|
client = await getImapConnection(userId, effectiveAccountId);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error('Failed to establish IMAP connection');
|
throw new Error('Failed to establish IMAP connection');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open mailbox with the actual folder name
|
// Open mailbox with the normalized folder name
|
||||||
await client.mailboxOpen(actualFolder);
|
await client.mailboxOpen(normalizedFolder);
|
||||||
const mailbox = client.mailbox;
|
const mailbox = client.mailbox;
|
||||||
if (!mailbox || typeof mailbox === 'boolean') {
|
if (!mailbox || typeof mailbox === 'boolean') {
|
||||||
throw new Error(`Failed to open mailbox: ${actualFolder}`);
|
throw new Error(`Failed to open mailbox: ${normalizedFolder}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get total messages
|
// Get total messages
|
||||||
const total = mailbox.exists || 0;
|
const total = mailbox.exists || 0;
|
||||||
console.log(`Total messages in ${actualFolder}: ${total}`);
|
console.log(`Total messages in ${normalizedFolder} for account ${effectiveAccountId}: ${total}`);
|
||||||
|
|
||||||
// If no messages, return empty result
|
// If no messages, return empty result
|
||||||
if (total === 0) {
|
if (total === 0) {
|
||||||
@ -311,7 +318,7 @@ export async function getEmails(
|
|||||||
page,
|
page,
|
||||||
perPage,
|
perPage,
|
||||||
totalPages: 0,
|
totalPages: 0,
|
||||||
folder: actualFolder,
|
folder: normalizedFolder,
|
||||||
mailboxes: []
|
mailboxes: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -319,7 +326,7 @@ export async function getEmails(
|
|||||||
// Calculate message range for pagination
|
// Calculate message range for pagination
|
||||||
const start = Math.max(1, total - (page * perPage) + 1);
|
const start = Math.max(1, total - (page * perPage) + 1);
|
||||||
const end = Math.max(1, total - ((page - 1) * perPage));
|
const end = Math.max(1, total - ((page - 1) * perPage));
|
||||||
console.log(`Fetching messages ${start}:${end} from ${actualFolder}`);
|
console.log(`Fetching messages ${start}:${end} from ${normalizedFolder} for account ${effectiveAccountId}`);
|
||||||
|
|
||||||
// Fetch messages
|
// Fetch messages
|
||||||
const messages = await client.fetch(`${start}:${end}`, {
|
const messages = await client.fetch(`${start}:${end}`, {
|
||||||
@ -351,9 +358,9 @@ export async function getEmails(
|
|||||||
},
|
},
|
||||||
size: message.size || 0,
|
size: message.size || 0,
|
||||||
hasAttachments: message.bodyStructure?.childNodes?.some(node => node.disposition === 'attachment') || false,
|
hasAttachments: message.bodyStructure?.childNodes?.some(node => node.disposition === 'attachment') || false,
|
||||||
folder: actualFolder,
|
folder: normalizedFolder,
|
||||||
contentFetched: false,
|
contentFetched: false,
|
||||||
accountId: accountId || 'default',
|
accountId: effectiveAccountId,
|
||||||
content: {
|
content: {
|
||||||
text: '',
|
text: '',
|
||||||
html: ''
|
html: ''
|
||||||
@ -362,18 +369,16 @@ export async function getEmails(
|
|||||||
emails.push(email);
|
emails.push(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the result if we have an accountId
|
// Cache the result with the effective account ID
|
||||||
if (accountId) {
|
await cacheEmailList(userId, effectiveAccountId, normalizedFolder, page, perPage, {
|
||||||
await cacheEmailList(userId, accountId, actualFolder, page, perPage, {
|
|
||||||
emails,
|
emails,
|
||||||
totalEmails: total,
|
totalEmails: total,
|
||||||
page,
|
page,
|
||||||
perPage,
|
perPage,
|
||||||
totalPages: Math.ceil(total / perPage),
|
totalPages: Math.ceil(total / perPage),
|
||||||
folder: actualFolder,
|
folder: normalizedFolder,
|
||||||
mailboxes: []
|
mailboxes: []
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
emails,
|
emails,
|
||||||
@ -381,7 +386,7 @@ export async function getEmails(
|
|||||||
page,
|
page,
|
||||||
perPage,
|
perPage,
|
||||||
totalPages: Math.ceil(total / perPage),
|
totalPages: Math.ceil(total / perPage),
|
||||||
folder: actualFolder,
|
folder: normalizedFolder,
|
||||||
mailboxes: []
|
mailboxes: []
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -423,55 +428,60 @@ export async function getEmailContent(
|
|||||||
throw new Error('Email ID must be a number');
|
throw new Error('Email ID must be a number');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove accountId prefix if present in folder name
|
|
||||||
const actualFolder = folder.includes(':') ? folder.split(':')[1] : folder;
|
|
||||||
// Extract account ID from folder name if present and none was explicitly provided
|
// Extract account ID from folder name if present and none was explicitly provided
|
||||||
const effectiveAccountId = folder.includes(':') && !accountId ? folder.split(':')[0] : accountId;
|
const folderAccountId = folder.includes(':') ? folder.split(':')[0] : accountId;
|
||||||
|
|
||||||
// Use normalized folder name for cache key
|
// Use the most specific account ID available
|
||||||
const cacheKey = effectiveAccountId || 'default';
|
const effectiveAccountId = folderAccountId || accountId || 'default';
|
||||||
const cachedEmail = await getCachedEmailContent(userId, cacheKey, emailId);
|
|
||||||
|
// Normalize folder name by removing account prefix if present
|
||||||
|
const normalizedFolder = folder.includes(':') ? folder.split(':')[1] : folder;
|
||||||
|
|
||||||
|
console.log(`[getEmailContent] Fetching email ${emailId} from folder ${normalizedFolder}, account ${effectiveAccountId}`);
|
||||||
|
|
||||||
|
// Use normalized folder name and effective account ID for cache key
|
||||||
|
const cachedEmail = await getCachedEmailContent(userId, effectiveAccountId, emailId);
|
||||||
if (cachedEmail) {
|
if (cachedEmail) {
|
||||||
console.log(`Using cached email content for ${userId}:${cacheKey}:${emailId}`);
|
console.log(`Using cached email content for ${userId}:${effectiveAccountId}:${emailId}`);
|
||||||
return cachedEmail;
|
return cachedEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Cache miss for email content ${userId}:${cacheKey}:${emailId}, fetching from IMAP`);
|
console.log(`Cache miss for email content ${userId}:${effectiveAccountId}:${emailId}, fetching from IMAP`);
|
||||||
|
|
||||||
const client = await getImapConnection(userId, effectiveAccountId);
|
const client = await getImapConnection(userId, effectiveAccountId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Log connection details with account context
|
// Log connection details with account context
|
||||||
console.log(`[DEBUG] Fetching email ${emailId} from folder ${actualFolder} for account ${effectiveAccountId || 'default'}`);
|
console.log(`[DEBUG] Fetching email ${emailId} from folder ${normalizedFolder} for account ${effectiveAccountId}`);
|
||||||
|
|
||||||
// Open mailbox with error handling
|
// Open mailbox with error handling
|
||||||
const mailbox = await client.mailboxOpen(actualFolder);
|
const mailbox = await client.mailboxOpen(normalizedFolder);
|
||||||
if (!mailbox || typeof mailbox === 'boolean') {
|
if (!mailbox || typeof mailbox === 'boolean') {
|
||||||
throw new Error(`Failed to open mailbox: ${actualFolder} for account ${effectiveAccountId || 'default'}`);
|
throw new Error(`Failed to open mailbox: ${normalizedFolder} for account ${effectiveAccountId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log mailbox status with account context
|
// Log mailbox status with account context
|
||||||
console.log(`[DEBUG] Mailbox ${actualFolder} opened for account ${effectiveAccountId || 'default'}, total messages: ${mailbox.exists}`);
|
console.log(`[DEBUG] Mailbox ${normalizedFolder} opened for account ${effectiveAccountId}, total messages: ${mailbox.exists}`);
|
||||||
|
|
||||||
// Get the UIDVALIDITY and UIDNEXT values
|
// Get the UIDVALIDITY and UIDNEXT values
|
||||||
const uidValidity = mailbox.uidValidity;
|
const uidValidity = mailbox.uidValidity;
|
||||||
const uidNext = mailbox.uidNext;
|
const uidNext = mailbox.uidNext;
|
||||||
|
|
||||||
console.log(`[DEBUG] Mailbox UIDVALIDITY: ${uidValidity}, UIDNEXT: ${uidNext} for account ${effectiveAccountId || 'default'}`);
|
console.log(`[DEBUG] Mailbox UIDVALIDITY: ${uidValidity}, UIDNEXT: ${uidNext} for account ${effectiveAccountId}`);
|
||||||
|
|
||||||
// Validate UID exists in mailbox
|
// Validate UID exists in mailbox
|
||||||
if (numericId >= uidNext) {
|
if (numericId >= uidNext) {
|
||||||
throw new Error(`Email ID ${numericId} is greater than or equal to the highest UID in mailbox (${uidNext}) for account ${effectiveAccountId || 'default'}`);
|
throw new Error(`Email ID ${numericId} is greater than or equal to the highest UID in mailbox (${uidNext}) for account ${effectiveAccountId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, try to get the sequence number for this UID
|
// First, try to get the sequence number for this UID
|
||||||
const searchResult = await client.search({ uid: numericId.toString() });
|
const searchResult = await client.search({ uid: numericId.toString() });
|
||||||
if (!searchResult || searchResult.length === 0) {
|
if (!searchResult || searchResult.length === 0) {
|
||||||
throw new Error(`Email with UID ${numericId} not found in folder ${actualFolder} for account ${effectiveAccountId || 'default'}`);
|
throw new Error(`Email with UID ${numericId} not found in folder ${normalizedFolder} for account ${effectiveAccountId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sequenceNumber = searchResult[0];
|
const sequenceNumber = searchResult[0];
|
||||||
console.log(`[DEBUG] Found sequence number ${sequenceNumber} for UID ${numericId} in account ${effectiveAccountId || 'default'}`);
|
console.log(`[DEBUG] Found sequence number ${sequenceNumber} for UID ${numericId} in account ${effectiveAccountId}`);
|
||||||
|
|
||||||
// Now fetch using the sequence number
|
// Now fetch using the sequence number
|
||||||
const message = await client.fetchOne(sequenceNumber.toString(), {
|
const message = await client.fetchOne(sequenceNumber.toString(), {
|
||||||
@ -482,7 +492,7 @@ export async function getEmailContent(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(`Email not found with sequence number ${sequenceNumber} in folder ${actualFolder} for account ${effectiveAccountId || 'default'}`);
|
throw new Error(`Email not found with sequence number ${sequenceNumber} in folder ${normalizedFolder} for account ${effectiveAccountId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { source, envelope, flags, size } = message;
|
const { source, envelope, flags, size } = message;
|
||||||
@ -537,21 +547,21 @@ export async function getEmailContent(
|
|||||||
text: parsedEmail.text || '',
|
text: parsedEmail.text || '',
|
||||||
html: rawHtml || ''
|
html: rawHtml || ''
|
||||||
},
|
},
|
||||||
folder: actualFolder,
|
folder: normalizedFolder,
|
||||||
contentFetched: true,
|
contentFetched: true,
|
||||||
size: size || 0,
|
size: size || 0,
|
||||||
accountId: effectiveAccountId || 'default'
|
accountId: effectiveAccountId
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cache the email content with account-specific key
|
// Cache the email content with effective account ID
|
||||||
await cacheEmailContent(userId, cacheKey, emailId, email);
|
await cacheEmailContent(userId, effectiveAccountId, emailId, email);
|
||||||
|
|
||||||
return email;
|
return email;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ERROR] Email fetch failed:', {
|
console.error('[ERROR] Email fetch failed:', {
|
||||||
userId,
|
userId,
|
||||||
emailId,
|
emailId,
|
||||||
folder: actualFolder,
|
folder: normalizedFolder,
|
||||||
accountId: effectiveAccountId,
|
accountId: effectiveAccountId,
|
||||||
error: error instanceof Error ? error.message : 'Unknown error',
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
details: error instanceof Error ? error.stack : undefined
|
details: error instanceof Error ? error.stack : undefined
|
||||||
@ -576,15 +586,21 @@ export async function markEmailReadStatus(
|
|||||||
folder: string = 'INBOX',
|
folder: string = 'INBOX',
|
||||||
accountId?: string
|
accountId?: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// Normalize folder name by removing account prefix if present
|
|
||||||
const actualFolder = folder.includes(':') ? folder.split(':')[1] : folder;
|
|
||||||
// Extract account ID from folder name if present and none was explicitly provided
|
// Extract account ID from folder name if present and none was explicitly provided
|
||||||
const effectiveAccountId = folder.includes(':') && !accountId ? folder.split(':')[0] : accountId;
|
const folderAccountId = folder.includes(':') ? folder.split(':')[0] : accountId;
|
||||||
|
|
||||||
|
// Use the most specific account ID available
|
||||||
|
const effectiveAccountId = folderAccountId || accountId || 'default';
|
||||||
|
|
||||||
|
// Normalize folder name by removing account prefix if present
|
||||||
|
const normalizedFolder = folder.includes(':') ? folder.split(':')[1] : folder;
|
||||||
|
|
||||||
|
console.log(`[markEmailReadStatus] Marking email ${emailId} as ${isRead ? 'read' : 'unread'} in folder ${normalizedFolder}, account ${effectiveAccountId}`);
|
||||||
|
|
||||||
const client = await getImapConnection(userId, effectiveAccountId);
|
const client = await getImapConnection(userId, effectiveAccountId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.mailboxOpen(actualFolder);
|
await client.mailboxOpen(normalizedFolder);
|
||||||
|
|
||||||
if (isRead) {
|
if (isRead) {
|
||||||
await client.messageFlagsAdd(emailId, ['\\Seen']);
|
await client.messageFlagsAdd(emailId, ['\\Seen']);
|
||||||
@ -592,18 +608,15 @@ export async function markEmailReadStatus(
|
|||||||
await client.messageFlagsRemove(emailId, ['\\Seen']);
|
await client.messageFlagsRemove(emailId, ['\\Seen']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use normalized cacheKey for consistency
|
|
||||||
const cacheKey = effectiveAccountId || 'default';
|
|
||||||
|
|
||||||
// Invalidate content cache since the flags changed
|
// Invalidate content cache since the flags changed
|
||||||
await invalidateEmailContentCache(userId, cacheKey, emailId);
|
await invalidateEmailContentCache(userId, effectiveAccountId, emailId);
|
||||||
|
|
||||||
// Also invalidate folder cache because unread counts may have changed
|
// Also invalidate folder cache because unread counts may have changed
|
||||||
await invalidateFolderCache(userId, cacheKey, actualFolder);
|
await invalidateFolderCache(userId, effectiveAccountId, normalizedFolder);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error marking email ${emailId} as ${isRead ? 'read' : 'unread'}:`, error);
|
console.error(`Error marking email ${emailId} as ${isRead ? 'read' : 'unread'} in folder ${normalizedFolder}, account ${effectiveAccountId}:`, error);
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user