diff --git a/app/api/courrier/route.ts b/app/api/courrier/route.ts index 9c20a1c2..ca640004 100644 --- a/app/api/courrier/route.ts +++ b/app/api/courrier/route.ts @@ -40,7 +40,13 @@ export async function GET(request: Request) { // Try to get from Redis cache first, but only if it's not a search query if (!searchQuery) { const cacheKey = accountId ? `${session.user.id}:${accountId}:${folder}` : `${session.user.id}:${folder}`; - const cachedEmails = await getCachedEmailList(session.user.id, folder, page, perPage); + const cachedEmails = await getCachedEmailList( + session.user.id, + accountId || 'default', + folder, + page, + perPage + ); if (cachedEmails) { console.log(`Using Redis cached emails for ${cacheKey}:${page}:${perPage}`); return NextResponse.json(cachedEmails); @@ -85,12 +91,12 @@ export async function POST(request: Request) { // Invalidate Redis cache for the folder if (folderName) { - await invalidateFolderCache(session.user.id, folderName); + await invalidateFolderCache(session.user.id, 'default', folderName); } else { // If no folder specified, invalidate all folders (using a wildcard pattern) const folders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']; for (const folder of folders) { - await invalidateFolderCache(session.user.id, folder); + await invalidateFolderCache(session.user.id, 'default', folder); } } diff --git a/app/api/courrier/session/route.ts b/app/api/courrier/session/route.ts index 97cec398..ace33571 100644 --- a/app/api/courrier/session/route.ts +++ b/app/api/courrier/session/route.ts @@ -23,33 +23,63 @@ const FOLDERS_CACHE_KEY = (userId: string, accountId: string) => `email:folders: */ export async function GET() { try { + // Get session with detailed logging + console.log('Attempting to get server session...'); const session = await getServerSession(authOptions); - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + + if (!session) { + console.error('No session found'); + return NextResponse.json({ error: 'No session found' }, { status: 401 }); } + + if (!session.user) { + console.error('No user in session'); + return NextResponse.json({ error: 'No user in session' }, { status: 401 }); + } + + if (!session.user.id) { + console.error('No user ID in session'); + return NextResponse.json({ error: 'No user ID in session' }, { status: 401 }); + } + + console.log('Session validated successfully:', { + userId: session.user.id, + email: session.user.email, + name: session.user.name + }); // Get Redis connection const redis = getRedisClient(); if (!redis) { + console.error('Redis connection failed'); return NextResponse.json({ error: 'Redis connection failed' }, { status: 500 }); } // Get user with their accounts + console.log('Fetching user with ID:', session.user.id); const user = await prisma.user.findUnique({ where: { id: session.user.id }, include: { mailCredentials: true } }); if (!user) { + console.error('User not found in database'); return NextResponse.json({ error: 'User not found' }, { status: 404 }); } // Get all accounts for the user const accounts = (user.mailCredentials || []) as MailCredentials[]; if (accounts.length === 0) { - return NextResponse.json({ error: 'No email accounts found' }, { status: 404 }); + console.log('No email accounts found for user:', session.user.id); + return NextResponse.json({ + authenticated: true, + accounts: [], + message: 'No email accounts found' + }); } + console.log(`Found ${accounts.length} accounts for user:`, accounts.map(a => a.email)); + // Fetch folders for each account const accountsWithFolders = await Promise.all( accounts.map(async (account: MailCredentials) => { @@ -57,6 +87,7 @@ export async function GET() { // Try to get folders from Redis cache first const cachedFolders = await redis.get(cacheKey); if (cachedFolders) { + console.log(`Using cached folders for account ${account.email}`); return { ...account, folders: JSON.parse(cachedFolders) @@ -64,8 +95,10 @@ export async function GET() { } // If not in cache, fetch from IMAP + console.log(`Fetching folders from IMAP for account ${account.email}`); const client = await getImapConnection(user.id, account.id); if (!client) { + console.warn(`Failed to get IMAP connection for account ${account.email}`); return { ...account, folders: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk'] @@ -74,6 +107,7 @@ export async function GET() { try { const folders = await getMailboxes(client); + console.log(`Fetched ${folders.length} folders for account ${account.email}`); // Cache the folders in Redis await redis.set( cacheKey, @@ -96,12 +130,13 @@ export async function GET() { ); return NextResponse.json({ + authenticated: true, accounts: accountsWithFolders }); } catch (error) { console.error('Error in session route:', error); return NextResponse.json( - { error: 'Internal server error' }, + { error: 'Internal server error', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } ); } diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index 208547f3..f7c39eee 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -206,13 +206,13 @@ export default function CourrierPage() { useEffect(() => { // Flag to prevent multiple initialization attempts let isMounted = true; - let initAttempted = false; - + let retryCount = 0; + const MAX_RETRIES = 3; + const RETRY_DELAY = 1000; // 1 second + const initSession = async () => { - if (initAttempted) return; - initAttempted = true; - try { + if (!isMounted) return; setLoading(true); // First check if Redis is ready before making API calls @@ -224,6 +224,24 @@ export default function CourrierPage() { // Call the session API to check email credentials and start prefetching const response = await fetch('/api/courrier/session'); + + // Handle 401 Unauthorized with retry logic + if (response.status === 401) { + if (retryCount < MAX_RETRIES) { + retryCount++; + console.log(`Session request failed (attempt ${retryCount}/${MAX_RETRIES}), retrying in ${RETRY_DELAY}ms...`); + await new Promise(resolve => setTimeout(resolve, RETRY_DELAY)); + return initSession(); + } else { + console.error('Max retries reached for session request'); + throw new Error('Failed to authenticate session after multiple attempts'); + } + } + + if (!response.ok) { + throw new Error(`Session request failed with status ${response.status}`); + } + const data = await response.json(); // Log the raw API response to inspect structure @@ -312,7 +330,7 @@ export default function CourrierPage() { folders: accountFolders }; console.log(`[DEBUG] Updated loading account to real account: ${account.email} with ID ${account.id}`); - } else { + } else if (index > 0) { // Add additional accounts as new entries updatedAccounts.push({ id: account.id || `account-${index}`, @@ -321,28 +339,31 @@ export default function CourrierPage() { color: account.color || 'bg-blue-500', folders: accountFolders }); + console.log(`[DEBUG] Added additional account: ${account.email} with ID ${account.id}`); } }); } else { // Fallback if accounts array is empty for some reason updatedAccounts.push({ id: 'all-accounts', name: 'All', email: '', color: 'bg-gray-500' }); - const firstAccount = data.allAccounts[0]; - const accountFolders = (firstAccount.folders && Array.isArray(firstAccount.folders)) - ? firstAccount.folders - : (data.mailboxes && Array.isArray(data.mailboxes)) - ? data.mailboxes - : ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']; - - updatedAccounts.push({ - id: firstAccount.id, - name: firstAccount.display_name || firstAccount.email, - email: firstAccount.email, - color: firstAccount.color || 'bg-blue-500', - folders: accountFolders + // Add all accounts from the API response + data.allAccounts.forEach((account: any) => { + const accountFolders = (account.folders && Array.isArray(account.folders)) + ? account.folders + : (data.mailboxes && Array.isArray(data.mailboxes)) + ? data.mailboxes + : ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']; + + updatedAccounts.push({ + id: account.id, + name: account.display_name || account.email, + email: account.email, + color: account.color || 'bg-blue-500', + folders: accountFolders + }); }); } - } else if (data.email) { + } else { // Fallback to single account if allAccounts is not available console.log(`[DEBUG] Fallback to single account: ${data.email}`); diff --git a/hooks/use-courrier.ts b/hooks/use-courrier.ts index 42515a6c..e5601ef0 100644 --- a/hooks/use-courrier.ts +++ b/hooks/use-courrier.ts @@ -80,6 +80,12 @@ export const useCourrier = () => { const loadEmails = useCallback(async (isLoadMore = false, accountId?: string) => { if (!session?.user?.id) return; + // Skip loading if accountId is 'loading-account' + if (accountId === 'loading-account') { + console.log('Skipping email load for loading account'); + return; + } + setIsLoading(true); setError(null); @@ -98,13 +104,20 @@ export const useCourrier = () => { queryParams.set('search', searchQuery); } - // Add accountId if provided - if (accountId) { + // Add accountId if provided and not 'loading-account' + if (accountId && accountId !== 'all-accounts' && accountId !== 'loading-account') { queryParams.set('accountId', accountId); } // First try Redis cache with low timeout - const cachedEmails = await getCachedEmailsWithTimeout(session.user.id, currentFolder, currentRequestPage, perPage, 100); + const cachedEmails = await getCachedEmailsWithTimeout( + session.user.id, + currentFolder, + currentRequestPage, + perPage, + 100, + accountId && accountId !== 'all-accounts' && accountId !== 'loading-account' ? accountId : undefined + ); if (cachedEmails) { // Ensure cached data has emails array property if (Array.isArray(cachedEmails.emails)) { diff --git a/lib/services/prefetch-service.ts b/lib/services/prefetch-service.ts index 362b84c4..3ea4381f 100644 --- a/lib/services/prefetch-service.ts +++ b/lib/services/prefetch-service.ts @@ -59,38 +59,27 @@ export async function getCachedEmailsWithTimeout( folder: string, page: number, perPage: number, - timeoutMs: number = 100 + timeoutMs: number = 100, + accountId?: string ): Promise { + // Skip cache if accountId is 'loading-account' + if (accountId === 'loading-account') { + console.log(`Skipping cache for loading account`); + return null; + } + return new Promise((resolve) => { const timeoutId = setTimeout(() => { - console.log(`Cache access timeout for ${userId}:${folder}:${page}:${perPage}`); + console.log(`Cache access timeout for ${userId}:${folder}:${page}:${perPage}${accountId ? ` for account ${accountId}` : ''}`); resolve(null); }, timeoutMs); - getCachedEmailList(userId, folder, page, perPage) + getCachedEmailList(userId, accountId || 'default', folder, page, perPage) .then(result => { clearTimeout(timeoutId); if (result) { - console.log(`Using cached data for ${userId}:${folder}:${page}:${perPage}`); - - // Validate and normalize the data structure - if (typeof result === 'object') { - // Make sure we have an emails array - if (!result.emails && Array.isArray(result)) { - // If result is an array, convert to proper structure - resolve({ emails: result }); - } else if (!result.emails) { - // If no emails property, add empty array - resolve({ ...result, emails: [] }); - } else { - // Normal case, return as is - resolve(result); - } - } else { - // Invalid data, return null - console.warn('Invalid cached data format:', result); - resolve(null); - } + console.log(`Using cached data for ${userId}:${folder}:${page}:${perPage}${accountId ? ` for account ${accountId}` : ''}`); + resolve(result); } else { resolve(null); }