courrier multi account restore compose

This commit is contained in:
alma 2025-04-28 13:05:05 +02:00
parent b78590727b
commit 542a535ce1
2 changed files with 38 additions and 83 deletions

View File

@ -1,15 +1,11 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getServerSession } from 'next-auth'; import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth'; import { authOptions } from '@/lib/auth';
import { getUserEmailCredentials, getMailboxes } from '@/lib/services/email-service'; import { getMailboxes } from '@/lib/services/email-service';
import { prefetchUserEmailData } from '@/lib/services/prefetch-service';
import { getCachedEmailCredentials, getRedisStatus, warmupRedisCache, getCachedImapSession, cacheImapSession } from '@/lib/redis';
import { prisma } from '@/lib/prisma';
import { ImapFlow } from 'imapflow';
import { getRedisClient } from '@/lib/redis'; import { getRedisClient } from '@/lib/redis';
import { getImapConnection } from '@/lib/services/email-service'; import { getImapConnection } from '@/lib/services/email-service';
import { MailCredentials } from '@prisma/client'; import { MailCredentials } from '@prisma/client';
import { redis } from '@/lib/redis'; import { prisma } from '@/lib/prisma';
// Keep track of last prefetch time for each user // Keep track of last prefetch time for each user
const lastPrefetchMap = new Map<string, number>(); const lastPrefetchMap = new Map<string, number>();
@ -18,63 +14,9 @@ const PREFETCH_COOLDOWN_MS = 30000; // 30 seconds cooldown between prefetches
// Cache TTL for folders in Redis (5 minutes) // Cache TTL for folders in Redis (5 minutes)
const FOLDERS_CACHE_TTL = 3600; // 1 hour const FOLDERS_CACHE_TTL = 3600; // 1 hour
// Cache to store account folders to avoid repeated calls to the IMAP server
// const accountFoldersCache = new Map<string, string[]>();
// Redis key for folders cache // Redis key for folders cache
const FOLDERS_CACHE_KEY = (userId: string, accountId: string) => `email:folders:${userId}:${accountId}`; const FOLDERS_CACHE_KEY = (userId: string, accountId: string) => `email:folders:${userId}:${accountId}`;
/**
* Get folders for a specific account
*/
async function getAccountFolders(accountId: string, account: any): Promise<string[]> {
// Check cache first
const cacheKey = `folders:${accountId}`;
const cachedData = accountFoldersCache.get(cacheKey);
const now = Date.now();
if (cachedData && (now - cachedData.timestamp < FOLDERS_CACHE_TTL)) {
return cachedData.folders;
}
try {
// Connect to IMAP server for this account
const client = new ImapFlow({
host: account.host,
port: account.port,
secure: true,
auth: {
user: account.email,
pass: account.password,
},
logger: false,
tls: {
rejectUnauthorized: false
}
});
await client.connect();
// Get folders for this account
const folders = await getMailboxes(client);
// Close connection
await client.logout();
// Cache the result
accountFoldersCache.set(cacheKey, {
folders,
timestamp: now
});
return folders;
} catch (error) {
console.error(`Error fetching folders for account ${account.email}:`, error);
// Return fallback folders on error
return ['INBOX', 'Sent', 'Drafts', 'Trash'];
}
}
/** /**
* This endpoint is called when the app initializes to check if the user has email credentials * This endpoint is called when the app initializes to check if the user has email credentials
* and to start prefetching email data in the background if they do * and to start prefetching email data in the background if they do
@ -103,14 +45,14 @@ export async function GET() {
} }
// Get all accounts for the user // Get all accounts for the user
const accounts: MailCredentials[] = user.mailCredentials || []; const accounts = (user.mailCredentials || []) as MailCredentials[];
if (accounts.length === 0) { if (accounts.length === 0) {
return NextResponse.json({ error: 'No email accounts found' }, { status: 404 }); return NextResponse.json({ error: 'No email accounts found' }, { status: 404 });
} }
// Fetch folders for each account // Fetch folders for each account
const accountsWithFolders = await Promise.all( const accountsWithFolders = await Promise.all(
accounts.map(async (account) => { accounts.map(async (account: MailCredentials) => {
const cacheKey = FOLDERS_CACHE_KEY(user.id, account.id); const cacheKey = FOLDERS_CACHE_KEY(user.id, account.id);
// Try to get folders from Redis cache first // Try to get folders from Redis cache first
const cachedFolders = await redis.get(cacheKey); const cachedFolders = await redis.get(cacheKey);
@ -122,7 +64,7 @@ export async function GET() {
} }
// If not in cache, fetch from IMAP // If not in cache, fetch from IMAP
const client = await getImapConnection(account); const client = await getImapConnection(user.id, account.id);
if (!client) { if (!client) {
return { return {
...account, ...account,

View File

@ -66,7 +66,7 @@ export async function getImapConnection(
// If not in cache, get from database and cache them // If not in cache, get from database and cache them
if (!credentials) { if (!credentials) {
console.log(`Credentials not found in cache for ${userId}${accountId ? ` account ${accountId}` : ''}, attempting database lookup`); console.log(`Credentials not found in cache for ${userId}${accountId ? ` account ${accountId}` : ''}, attempting database lookup`);
credentials = await getUserEmailCredentials(userId); credentials = await getUserEmailCredentials(userId, accountId);
if (!credentials) { if (!credentials) {
throw new Error('No email credentials found'); throw new Error('No email credentials found');
} }
@ -154,24 +154,37 @@ export async function getImapConnection(
/** /**
* Get user's email credentials from database * Get user's email credentials from database
*/ */
export async function getUserEmailCredentials(userId: string): Promise<EmailCredentials | null> { export async function getUserEmailCredentials(userId: string, accountId?: string): Promise<EmailCredentials | null> {
const credentials = await prisma.mailCredentials.findFirst({ const credentials = await prisma.mailCredentials.findFirst({
where: { userId } where: accountId ? { userId, id: accountId } : { userId }
}); });
if (!credentials) return null; if (!credentials) return null;
const mailCredentials = credentials as unknown as {
email: string;
password: string;
host: string;
port: number;
secure: boolean;
smtp_host: string | null;
smtp_port: number | null;
smtp_secure: boolean | null;
display_name: string | null;
color: string | null;
};
return { return {
email: credentials.email, email: mailCredentials.email,
password: credentials.password, password: mailCredentials.password,
host: credentials.host, host: mailCredentials.host,
port: credentials.port, port: mailCredentials.port,
secure: credentials.secure, secure: mailCredentials.secure,
smtp_host: credentials.smtp_host, smtp_host: mailCredentials.smtp_host || undefined,
smtp_port: credentials.smtp_port, smtp_port: mailCredentials.smtp_port || undefined,
smtp_secure: credentials.smtp_secure, smtp_secure: mailCredentials.smtp_secure || false,
display_name: credentials.display_name, display_name: mailCredentials.display_name || undefined,
color: credentials.color color: mailCredentials.color || undefined
}; };
} }
@ -235,7 +248,7 @@ export async function getEmails(
// Try to get from cache first // Try to get from cache first
if (!searchQuery) { if (!searchQuery) {
const cacheKey = accountId ? `${userId}:${accountId}:${folder}` : `${userId}:${folder}`; const cacheKey = accountId ? `${userId}:${accountId}:${folder}` : `${userId}:${folder}`;
const cachedResult = await getCachedEmailList(userId, folder, page, perPage); const cachedResult = await getCachedEmailList(userId, accountId || 'default', folder, page, perPage);
if (cachedResult) { if (cachedResult) {
console.log(`Using cached email list for ${cacheKey}:${page}:${perPage}`); console.log(`Using cached email list for ${cacheKey}:${page}:${perPage}`);
return cachedResult; return cachedResult;
@ -331,7 +344,7 @@ export async function getEmails(
// Cache even empty results // Cache even empty results
if (!searchQuery) { if (!searchQuery) {
await cacheEmailList(userId, folder, page, perPage, result); await cacheEmailList(userId, accountId || 'default', folder, page, perPage, result);
} }
return result; return result;
@ -476,7 +489,7 @@ export async function getEmails(
// Always cache the result if it's not a search query, even for pagination // Always cache the result if it's not a search query, even for pagination
if (!searchQuery) { if (!searchQuery) {
console.log(`Caching email list for ${userId}:${folder}:${page}:${perPage}`); console.log(`Caching email list for ${userId}:${folder}:${page}:${perPage}`);
await cacheEmailList(userId, folder, page, perPage, result); await cacheEmailList(userId, accountId || 'default', folder, page, perPage, result);
} }
return result; return result;
@ -501,7 +514,7 @@ export async function getEmailContent(
folder: string = 'INBOX' folder: string = 'INBOX'
): Promise<EmailMessage> { ): Promise<EmailMessage> {
// Try to get from cache first // Try to get from cache first
const cachedEmail = await getCachedEmailContent(userId, emailId); const cachedEmail = await getCachedEmailContent(userId, folder, emailId);
if (cachedEmail) { if (cachedEmail) {
console.log(`Using cached email content for ${userId}:${emailId}`); console.log(`Using cached email content for ${userId}:${emailId}`);
return cachedEmail; return cachedEmail;
@ -582,7 +595,7 @@ export async function getEmailContent(
}; };
// Cache the email content // Cache the email content
await cacheEmailContent(userId, emailId, email); await cacheEmailContent(userId, folder, emailId, email);
return email; return email;
} finally { } finally {
@ -615,10 +628,10 @@ export async function markEmailReadStatus(
} }
// Invalidate content cache since the flags changed // Invalidate content cache since the flags changed
await invalidateEmailContentCache(userId, emailId); await invalidateEmailContentCache(userId, folder, emailId);
// Also invalidate folder cache because unread counts may have changed // Also invalidate folder cache because unread counts may have changed
await invalidateFolderCache(userId, folder); await invalidateFolderCache(userId, folder, folder);
return true; return true;
} catch (error) { } catch (error) {