courrier multi account restore compose
This commit is contained in:
parent
d4b28b7974
commit
1506cc7390
@ -159,7 +159,7 @@ export async function getUserEmailCredentials(userId: string, accountId?: string
|
||||
where: {
|
||||
AND: [
|
||||
{ userId },
|
||||
{ email: accountId }
|
||||
accountId ? { id: accountId } : {}
|
||||
]
|
||||
}
|
||||
});
|
||||
@ -272,265 +272,90 @@ export async function getEmails(
|
||||
searchQuery: string = '',
|
||||
accountId?: string
|
||||
): Promise<EmailListResult> {
|
||||
console.log(`Fetching emails for user ${userId}${accountId ? ` account ${accountId}` : ''} in folder ${folder}`);
|
||||
|
||||
// Try to get from cache first
|
||||
if (!searchQuery) {
|
||||
const cacheKey = accountId ? `${userId}:${accountId}:${folder}` : `${userId}:${folder}`;
|
||||
const cachedResult = await getCachedEmailList(userId, accountId || 'default', folder, page, perPage);
|
||||
if (cachedResult) {
|
||||
console.log(`Using cached email list for ${cacheKey}:${page}:${perPage}`);
|
||||
return cachedResult;
|
||||
}
|
||||
const cacheKey = accountId
|
||||
? `email:list:${userId}:${accountId}:${folder}:${page}:${perPage}:${searchQuery}`
|
||||
: `email:list:${userId}:${folder}:${page}:${perPage}:${searchQuery}`;
|
||||
|
||||
const cached = await getCachedEmailList(cacheKey);
|
||||
if (cached) {
|
||||
console.log(`Using cached email list for ${cacheKey}`);
|
||||
return cached;
|
||||
}
|
||||
|
||||
console.log(`Cache miss for emails ${userId}:${folder}:${page}:${perPage}${accountId ? ` for account ${accountId}` : ''}, fetching from IMAP`);
|
||||
|
||||
// If accountId is provided, connect to that specific account
|
||||
let client: ImapFlow;
|
||||
|
||||
if (accountId) {
|
||||
try {
|
||||
// Get account from database
|
||||
const account = await prisma.mailCredentials.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
{ userId },
|
||||
{ email: accountId }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new Error(`Account with ID ${accountId} not found`);
|
||||
}
|
||||
|
||||
// Connect to IMAP server for this specific account
|
||||
client = new ImapFlow({
|
||||
host: account.host,
|
||||
port: account.port,
|
||||
secure: true, // Default to secure connection
|
||||
auth: {
|
||||
user: account.email,
|
||||
pass: account.password,
|
||||
},
|
||||
logger: false,
|
||||
tls: {
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
} catch (error) {
|
||||
console.error(`Error connecting to account ${accountId}:`, error);
|
||||
// Fallback to default connection
|
||||
client = await getImapConnection(userId);
|
||||
}
|
||||
} else {
|
||||
// Use the default connection logic
|
||||
client = await getImapConnection(userId);
|
||||
|
||||
// Get IMAP connection for the specific account
|
||||
const client = await getImapConnection(userId, accountId);
|
||||
if (!client) {
|
||||
throw new Error('Failed to get IMAP connection');
|
||||
}
|
||||
|
||||
let mailboxes: string[] = [];
|
||||
|
||||
|
||||
try {
|
||||
console.log(`[DEBUG] Fetching mailboxes for user ${userId}`);
|
||||
// Get list of mailboxes first
|
||||
try {
|
||||
mailboxes = await getMailboxes(client);
|
||||
console.log(`[DEBUG] Found ${mailboxes.length} mailboxes:`, mailboxes);
|
||||
|
||||
// Save mailboxes in session data
|
||||
const cachedSession = await getCachedImapSession(userId);
|
||||
await cacheImapSession(userId, {
|
||||
...(cachedSession || { lastActive: Date.now() }),
|
||||
mailboxes
|
||||
});
|
||||
console.log(`[DEBUG] Updated cached session with mailboxes for user ${userId}`);
|
||||
} catch (mailboxError) {
|
||||
console.error(`[ERROR] Failed to fetch mailboxes:`, mailboxError);
|
||||
}
|
||||
// Select the mailbox
|
||||
await client.mailboxOpen(folder);
|
||||
|
||||
// Open mailbox
|
||||
const mailboxData = await client.mailboxOpen(folder);
|
||||
const totalMessages = mailboxData.exists;
|
||||
// Get total count
|
||||
const totalEmails = await client.mailbox.messages.total;
|
||||
const totalPages = Math.ceil(totalEmails / perPage);
|
||||
|
||||
// Calculate range based on total messages
|
||||
const endIdx = page * perPage;
|
||||
const startIdx = (page - 1) * perPage + 1;
|
||||
const from = Math.max(totalMessages - endIdx + 1, 1);
|
||||
const to = Math.max(totalMessages - startIdx + 1, 1);
|
||||
|
||||
// Empty result if no messages
|
||||
if (totalMessages === 0 || from > to) {
|
||||
const result = {
|
||||
emails: [],
|
||||
totalEmails: 0,
|
||||
page,
|
||||
perPage,
|
||||
totalPages: 0,
|
||||
folder,
|
||||
mailboxes
|
||||
};
|
||||
|
||||
// Cache even empty results
|
||||
if (!searchQuery) {
|
||||
await cacheEmailList(userId, accountId || 'default', folder, page, perPage, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Search if needed
|
||||
let messageIds: any[] = [];
|
||||
if (searchQuery) {
|
||||
messageIds = await client.search({ body: searchQuery });
|
||||
messageIds = messageIds.filter(id => id >= from && id <= to);
|
||||
} else {
|
||||
messageIds = Array.from({ length: to - from + 1 }, (_, i) => from + i);
|
||||
}
|
||||
// Calculate range for this page
|
||||
const start = (page - 1) * perPage + 1;
|
||||
const end = Math.min(start + perPage - 1, totalEmails);
|
||||
|
||||
// Fetch messages
|
||||
const messages = await client.fetch(`${start}:${end}`, {
|
||||
envelope: true,
|
||||
flags: true,
|
||||
bodyStructure: true,
|
||||
internalDate: true,
|
||||
size: true,
|
||||
bodyParts: [
|
||||
{ part: 'TEXT', query: { headers: true } }
|
||||
]
|
||||
});
|
||||
|
||||
// Process messages
|
||||
const emails: EmailMessage[] = [];
|
||||
|
||||
for (const id of messageIds) {
|
||||
try {
|
||||
// Define fetch options with proper typing
|
||||
const fetchOptions: any = {
|
||||
envelope: true,
|
||||
flags: true,
|
||||
bodyStructure: true,
|
||||
internalDate: true,
|
||||
size: true,
|
||||
bodyParts: [{
|
||||
part: '1',
|
||||
query: { type: "text" },
|
||||
limit: 5000
|
||||
}]
|
||||
};
|
||||
|
||||
const message = await client.fetchOne(id, fetchOptions);
|
||||
|
||||
if (!message) continue;
|
||||
|
||||
const { envelope, flags, bodyStructure, internalDate, size, bodyParts } = message;
|
||||
|
||||
// Extract preview content
|
||||
let preview = '';
|
||||
if (bodyParts && typeof bodyParts === 'object') {
|
||||
// Convert to array if it's a Map
|
||||
const partsArray = Array.isArray(bodyParts)
|
||||
? bodyParts
|
||||
: Array.from(bodyParts.values());
|
||||
|
||||
const textPart = partsArray.find((part: any) => part.type === 'text/plain');
|
||||
const htmlPart = partsArray.find((part: any) => part.type === 'text/html');
|
||||
const content = textPart?.content || htmlPart?.content || '';
|
||||
|
||||
if (typeof content === 'string') {
|
||||
preview = content.substring(0, 150) + '...';
|
||||
} else if (Buffer.isBuffer(content)) {
|
||||
preview = content.toString('utf-8', 0, 150) + '...';
|
||||
}
|
||||
}
|
||||
|
||||
// Process attachments
|
||||
const attachments: EmailAttachment[] = [];
|
||||
|
||||
const processAttachments = (node: any, path: Array<string | number> = []) => {
|
||||
if (!node) return;
|
||||
|
||||
if (node.type === 'attachment') {
|
||||
attachments.push({
|
||||
contentId: node.contentId,
|
||||
filename: node.filename || 'attachment',
|
||||
contentType: node.contentType,
|
||||
size: node.size,
|
||||
path: [...path, node.part].join('.')
|
||||
});
|
||||
}
|
||||
|
||||
if (node.childNodes) {
|
||||
node.childNodes.forEach((child: any, index: number) => {
|
||||
processAttachments(child, [...path, node.part || index + 1]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (bodyStructure) {
|
||||
processAttachments(bodyStructure);
|
||||
}
|
||||
|
||||
// Convert flags from Set to boolean checks
|
||||
const flagsArray = Array.from(flags as Set<string>);
|
||||
|
||||
emails.push({
|
||||
id: id.toString(),
|
||||
messageId: envelope.messageId,
|
||||
subject: envelope.subject || "(No Subject)",
|
||||
from: envelope.from.map((f: any) => ({
|
||||
name: f.name || f.address,
|
||||
address: f.address,
|
||||
})),
|
||||
to: envelope.to.map((t: any) => ({
|
||||
name: t.name || t.address,
|
||||
address: t.address,
|
||||
})),
|
||||
cc: (envelope.cc || []).map((c: any) => ({
|
||||
name: c.name || c.address,
|
||||
address: c.address,
|
||||
})),
|
||||
bcc: (envelope.bcc || []).map((b: any) => ({
|
||||
name: b.name || b.address,
|
||||
address: b.address,
|
||||
})),
|
||||
date: internalDate || new Date(),
|
||||
flags: {
|
||||
seen: flagsArray.includes("\\Seen"),
|
||||
flagged: flagsArray.includes("\\Flagged"),
|
||||
answered: flagsArray.includes("\\Answered"),
|
||||
deleted: flagsArray.includes("\\Deleted"),
|
||||
draft: flagsArray.includes("\\Draft"),
|
||||
},
|
||||
hasAttachments: attachments.length > 0,
|
||||
attachments,
|
||||
size,
|
||||
preview,
|
||||
folder,
|
||||
contentFetched: false
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error fetching message ${id}:`, error);
|
||||
}
|
||||
for await (const message of messages) {
|
||||
const email: EmailMessage = {
|
||||
id: message.uid.toString(),
|
||||
from: message.envelope.from[0]?.address || '',
|
||||
to: message.envelope.to.map(addr => addr.address).join(', '),
|
||||
subject: message.envelope.subject || '',
|
||||
date: message.internalDate,
|
||||
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,
|
||||
hasAttachments: message.bodyStructure?.childNodes?.some(node => node.disposition === 'attachment') || false
|
||||
};
|
||||
emails.push(email);
|
||||
}
|
||||
|
||||
// Sort by date, newest first
|
||||
emails.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||
|
||||
const result = {
|
||||
|
||||
const result: EmailListResult = {
|
||||
emails,
|
||||
totalEmails: totalMessages,
|
||||
totalEmails,
|
||||
page,
|
||||
perPage,
|
||||
totalPages: Math.ceil(totalMessages / perPage),
|
||||
totalPages,
|
||||
folder,
|
||||
mailboxes
|
||||
mailboxes: await getMailboxes(client)
|
||||
};
|
||||
|
||||
// Always cache the result if it's not a search query, even for pagination
|
||||
if (!searchQuery) {
|
||||
console.log(`Caching email list for ${userId}:${folder}:${page}:${perPage}`);
|
||||
await cacheEmailList(userId, accountId || 'default', folder, page, perPage, result);
|
||||
}
|
||||
|
||||
|
||||
// Cache the result
|
||||
await cacheEmailList(cacheKey, result);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching emails for ${userId}${accountId ? ` account ${accountId}` : ''}:`, error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Don't logout, keep connection in pool
|
||||
if (folder !== 'INBOX') {
|
||||
try {
|
||||
await client.mailboxClose();
|
||||
} catch (error) {
|
||||
console.error('Error closing mailbox:', error);
|
||||
}
|
||||
}
|
||||
// Don't close the connection, it's managed by the connection pool
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user