courrier multi account

This commit is contained in:
alma 2025-04-27 16:05:04 +02:00
parent b66081643f
commit 1aa9608722
4 changed files with 113 additions and 11 deletions

View File

@ -3,7 +3,11 @@ import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route'; import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { getUserEmailCredentials } from '@/lib/services/email-service'; import { getUserEmailCredentials } from '@/lib/services/email-service';
import { prefetchUserEmailData } from '@/lib/services/prefetch-service'; import { prefetchUserEmailData } from '@/lib/services/prefetch-service';
import { getCachedEmailCredentials, getRedisStatus, warmupRedisCache } from '@/lib/redis'; import { getCachedEmailCredentials, getRedisStatus, warmupRedisCache, getCachedImapSession, cacheImapSession } from '@/lib/redis';
// Keep track of last prefetch time for each user
const lastPrefetchMap = new Map<string, number>();
const PREFETCH_COOLDOWN_MS = 30000; // 30 seconds cooldown between prefetches
/** /**
* 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
@ -31,6 +35,14 @@ export async function GET() {
const userId = session.user.id; const userId = session.user.id;
// Check when we last prefetched for this user
const lastPrefetchTime = lastPrefetchMap.get(userId) || 0;
const now = Date.now();
const shouldPrefetch = now - lastPrefetchTime > PREFETCH_COOLDOWN_MS;
// Check if we have a cached session
const cachedSession = await getCachedImapSession(userId);
// First, check Redis cache for credentials // First, check Redis cache for credentials
let credentials = await getCachedEmailCredentials(userId); let credentials = await getCachedEmailCredentials(userId);
let credentialsSource = 'cache'; let credentialsSource = 'cache';
@ -51,11 +63,33 @@ export async function GET() {
}); });
} }
// Start prefetching email data in the background let prefetchStarted = false;
// We don't await this to avoid blocking the response
prefetchUserEmailData(userId).catch(err => { // Only prefetch if the cooldown period has elapsed
console.error('Background prefetch error:', err); if (shouldPrefetch) {
}); // Update the last prefetch time
lastPrefetchMap.set(userId, now);
// Start prefetching email data in the background
// We don't await this to avoid blocking the response
prefetchUserEmailData(userId).catch(err => {
console.error('Background prefetch error:', err);
});
prefetchStarted = true;
} else {
console.log(`Skipping prefetch for ${userId}, last prefetch was ${Math.round((now - lastPrefetchTime)/1000)}s ago`);
}
// Store last visit time in session data
if (cachedSession) {
await cacheImapSession(userId, {
...cachedSession,
lastVisit: now
});
} else {
await cacheImapSession(userId, { lastVisit: now });
}
// Return session info without sensitive data // Return session info without sensitive data
return NextResponse.json({ return NextResponse.json({
@ -63,8 +97,10 @@ export async function GET() {
hasEmailCredentials: true, hasEmailCredentials: true,
email: credentials.email, email: credentials.email,
redisStatus, redisStatus,
prefetchStarted: true, prefetchStarted,
credentialsSource credentialsSource,
lastVisit: cachedSession?.lastVisit,
mailboxes: cachedSession?.mailboxes || []
}); });
} catch (error) { } catch (error) {
console.error("Error checking session:", error); console.error("Error checking session:", error);

View File

@ -179,10 +179,10 @@ export default function CourrierPage() {
setAccounts(prev => { setAccounts(prev => {
const updated = [...prev]; const updated = [...prev];
updated[1] = { updated[1] = {
...updated[1], ...updated[1],
name: data.email, name: data.email,
email: data.email, email: data.email,
folders: mailboxes folders: data.mailboxes || mailboxes
}; };
return updated; return updated;
}); });
@ -228,7 +228,7 @@ export default function CourrierPage() {
return () => { return () => {
isMounted = false; isMounted = false;
}; };
}, [session?.user?.id, loadEmails, currentFolder, mailboxes]); }, [session?.user?.id, loadEmails]);
// Helper to get folder icons // Helper to get folder icons
const getFolderIcon = (folder: string) => { const getFolderIcon = (folder: string) => {

View File

@ -102,6 +102,7 @@ interface ImapSessionData {
connectionId?: string; connectionId?: string;
lastActive: number; lastActive: number;
mailboxes?: string[]; mailboxes?: string[];
lastVisit?: number;
} }
/** /**

View File

@ -10,6 +10,46 @@ import {
warmupRedisCache warmupRedisCache
} from '@/lib/redis'; } from '@/lib/redis';
// Keep track of ongoing prefetch operations to prevent duplicates
const prefetchInProgress = new Map<string, boolean>();
const lastPrefetchTime = new Map<string, number>();
const PREFETCH_COOLDOWN_MS = 30000; // 30 seconds between prefetch operations
/**
* Check if we should prefetch for a user based on cooldown
*/
function shouldPrefetch(userId: string, key: string = 'general'): boolean {
const prefetchKey = `${userId}:${key}`;
// Check if prefetch is already in progress
if (prefetchInProgress.get(prefetchKey)) {
console.log(`Prefetch already in progress for ${prefetchKey}`);
return false;
}
// Check cooldown
const lastTime = lastPrefetchTime.get(prefetchKey) || 0;
const now = Date.now();
if (now - lastTime < PREFETCH_COOLDOWN_MS) {
console.log(`Prefetch cooldown active for ${prefetchKey}, last was ${Math.round((now - lastTime)/1000)}s ago`);
return false;
}
// Mark as in progress and update last time
prefetchInProgress.set(prefetchKey, true);
lastPrefetchTime.set(prefetchKey, now);
return true;
}
/**
* Mark prefetch as completed
*/
function markPrefetchCompleted(userId: string, key: string = 'general'): void {
const prefetchKey = `${userId}:${key}`;
prefetchInProgress.set(prefetchKey, false);
}
/** /**
* Get cached emails with timeout to ensure fast UI response * Get cached emails with timeout to ensure fast UI response
* If cache access takes longer than timeout, return null to use regular IMAP fetch * If cache access takes longer than timeout, return null to use regular IMAP fetch
@ -73,6 +113,13 @@ export async function refreshEmailsInBackground(
page: number = 1, page: number = 1,
perPage: number = 20 perPage: number = 20
): Promise<void> { ): Promise<void> {
const prefetchKey = `refresh:${folder}:${page}`;
// Skip if already in progress or in cooldown
if (!shouldPrefetch(userId, prefetchKey)) {
return;
}
// Use setTimeout to ensure this runs after current execution context // Use setTimeout to ensure this runs after current execution context
setTimeout(async () => { setTimeout(async () => {
try { try {
@ -81,6 +128,8 @@ export async function refreshEmailsInBackground(
console.log(`Background refresh completed for ${userId}:${folder}`); console.log(`Background refresh completed for ${userId}:${folder}`);
} catch (error) { } catch (error) {
console.error('Background refresh error:', error); console.error('Background refresh error:', error);
} finally {
markPrefetchCompleted(userId, prefetchKey);
} }
}, 100); }, 100);
} }
@ -90,6 +139,11 @@ export async function refreshEmailsInBackground(
* This function should be called when a user logs in * This function should be called when a user logs in
*/ */
export async function prefetchUserEmailData(userId: string): Promise<void> { export async function prefetchUserEmailData(userId: string): Promise<void> {
// Skip if already in progress or in cooldown
if (!shouldPrefetch(userId)) {
return;
}
console.log(`Starting email prefetch for user ${userId}`); console.log(`Starting email prefetch for user ${userId}`);
const startTime = Date.now(); const startTime = Date.now();
@ -159,6 +213,8 @@ export async function prefetchUserEmailData(userId: string): Promise<void> {
console.log(`Email prefetch completed for user ${userId} in ${duration.toFixed(2)}s`); console.log(`Email prefetch completed for user ${userId} in ${duration.toFixed(2)}s`);
} catch (error) { } catch (error) {
console.error('Error during email prefetch:', error); console.error('Error during email prefetch:', error);
} finally {
markPrefetchCompleted(userId);
} }
} }
@ -172,6 +228,13 @@ export async function prefetchFolderEmails(
pages: number = 3, pages: number = 3,
startPage: number = 1 startPage: number = 1
): Promise<void> { ): Promise<void> {
const prefetchKey = `folder:${folder}:${startPage}`;
// Skip if already in progress or in cooldown
if (!shouldPrefetch(userId, prefetchKey)) {
return;
}
try { try {
console.log(`Prefetching ${pages} pages of emails for folder ${folder} starting from page ${startPage}`); console.log(`Prefetching ${pages} pages of emails for folder ${folder} starting from page ${startPage}`);
@ -201,5 +264,7 @@ export async function prefetchFolderEmails(
console.log(`Completed prefetching ${pages} pages of ${folder}`); console.log(`Completed prefetching ${pages} pages of ${folder}`);
} catch (error) { } catch (error) {
console.error(`Error prefetching folder ${folder}:`, error); console.error(`Error prefetching folder ${folder}:`, error);
} finally {
markPrefetchCompleted(userId, prefetchKey);
} }
} }