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 { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { getUserEmailCredentials, 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 { getMailboxes } from '@/lib/services/email-service';
import { getRedisClient } from '@/lib/redis';
import { getImapConnection } from '@/lib/services/email-service';
import { MailCredentials } from '@prisma/client';
import { redis } from '@/lib/redis';
import { prisma } from '@/lib/prisma';
// Keep track of last prefetch time for each user
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)
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
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
* 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
const accounts: MailCredentials[] = user.mailCredentials || [];
const accounts = (user.mailCredentials || []) as MailCredentials[];
if (accounts.length === 0) {
return NextResponse.json({ error: 'No email accounts found' }, { status: 404 });
}
// Fetch folders for each account
const accountsWithFolders = await Promise.all(
accounts.map(async (account) => {
accounts.map(async (account: MailCredentials) => {
const cacheKey = FOLDERS_CACHE_KEY(user.id, account.id);
// Try to get folders from Redis cache first
const cachedFolders = await redis.get(cacheKey);
@ -122,7 +64,7 @@ export async function GET() {
}
// If not in cache, fetch from IMAP
const client = await getImapConnection(account);
const client = await getImapConnection(user.id, account.id);
if (!client) {
return {
...account,

View File

@ -66,7 +66,7 @@ export async function getImapConnection(
// If not in cache, get from database and cache them
if (!credentials) {
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) {
throw new Error('No email credentials found');
}
@ -154,24 +154,37 @@ export async function getImapConnection(
/**
* 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({
where: { userId }
where: accountId ? { userId, id: accountId } : { userId }
});
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 {
email: credentials.email,
password: credentials.password,
host: credentials.host,
port: credentials.port,
secure: credentials.secure,
smtp_host: credentials.smtp_host,
smtp_port: credentials.smtp_port,
smtp_secure: credentials.smtp_secure,
display_name: credentials.display_name,
color: credentials.color
email: mailCredentials.email,
password: mailCredentials.password,
host: mailCredentials.host,
port: mailCredentials.port,
secure: mailCredentials.secure,
smtp_host: mailCredentials.smtp_host || undefined,
smtp_port: mailCredentials.smtp_port || undefined,
smtp_secure: mailCredentials.smtp_secure || false,
display_name: mailCredentials.display_name || undefined,
color: mailCredentials.color || undefined
};
}
@ -235,7 +248,7 @@ export async function getEmails(
// Try to get from cache first
if (!searchQuery) {
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) {
console.log(`Using cached email list for ${cacheKey}:${page}:${perPage}`);
return cachedResult;
@ -331,7 +344,7 @@ export async function getEmails(
// Cache even empty results
if (!searchQuery) {
await cacheEmailList(userId, folder, page, perPage, result);
await cacheEmailList(userId, accountId || 'default', folder, page, perPage, 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
if (!searchQuery) {
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;
@ -501,7 +514,7 @@ export async function getEmailContent(
folder: string = 'INBOX'
): Promise<EmailMessage> {
// Try to get from cache first
const cachedEmail = await getCachedEmailContent(userId, emailId);
const cachedEmail = await getCachedEmailContent(userId, folder, emailId);
if (cachedEmail) {
console.log(`Using cached email content for ${userId}:${emailId}`);
return cachedEmail;
@ -582,7 +595,7 @@ export async function getEmailContent(
};
// Cache the email content
await cacheEmailContent(userId, emailId, email);
await cacheEmailContent(userId, folder, emailId, email);
return email;
} finally {
@ -615,10 +628,10 @@ export async function markEmailReadStatus(
}
// 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
await invalidateFolderCache(userId, folder);
await invalidateFolderCache(userId, folder, folder);
return true;
} catch (error) {