import { NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from "@/app/api/auth/options"; import { getEmails } from '@/lib/services/email-service'; import { getCachedEmailList, cacheEmailList, invalidateFolderCache } from '@/lib/redis'; import { PrismaClient } from '@prisma/client'; import { logger } from '@/lib/logger'; const prisma = new PrismaClient(); // Simple in-memory cache (will be removed in a future update) interface EmailCacheEntry { data: any; timestamp: number; } // Cache for 1 minute only const CACHE_TTL = 60 * 1000; const emailListCache: Record = {}; export async function GET(request: Request) { // Authenticate user (declare outside try to access in catch) const session = await getServerSession(authOptions); if (!session || !session.user?.id) { return NextResponse.json( { error: "Not authenticated" }, { status: 401 } ); } try { // Extract query parameters const { searchParams } = new URL(request.url); const page = parseInt(searchParams.get("page") || "1"); const perPage = parseInt(searchParams.get("perPage") || "20"); const folder = searchParams.get("folder") || "INBOX"; const searchQuery = searchParams.get("search") || ""; const accountId = searchParams.get("accountId") || ""; const checkOnly = searchParams.get("checkOnly") === "true"; const refresh = searchParams.get("refresh") === "true"; // CRITICAL FIX: Log exact parameters received by the API logger.debug('[COURRIER_API] Received request', { folder, accountIdHash: accountId ? Buffer.from(accountId).toString('base64').slice(0, 12) : null, page, checkOnly, refresh, userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), }); // CRITICAL FIX: More robust parameter normalization // 1. If folder contains an account prefix, extract it but DO NOT use it // 2. Always prioritize the explicit accountId parameter let normalizedFolder = folder; let effectiveAccountId = accountId || 'default'; if (folder.includes(':')) { const parts = folder.split(':'); const folderAccountId = parts[0]; normalizedFolder = parts[1]; logger.debug('[COURRIER_API] Folder has prefix, normalized', { folderAccountId, normalizedFolder }); // We intentionally DO NOT use folderAccountId here - the explicit accountId parameter takes precedence } // CRITICAL FIX: Enhanced logging for parameter resolution logger.debug('[COURRIER_API] Using normalized parameters', { folder: normalizedFolder, accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), }); // If refresh=true, invalidate cache before fetching if (refresh) { logger.debug('[COURRIER_API] Refresh requested - invalidating cache', { userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), folder: normalizedFolder }); await invalidateFolderCache(session.user.id, effectiveAccountId, normalizedFolder); } // Try to get from Redis cache first, but only if it's not a search query, not checkOnly, and not refresh if (!searchQuery && !checkOnly && !refresh) { // CRITICAL FIX: Use consistent cache key format with the correct account ID logger.debug('[COURRIER_API] Checking Redis cache', { userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), folder: normalizedFolder, page, perPage }); const cachedEmails = await getCachedEmailList( session.user.id, effectiveAccountId, // Use effective account ID for consistent cache key normalizedFolder, // Use normalized folder name without prefix page, perPage ); if (cachedEmails) { logger.debug('[COURRIER_API] Using Redis cached emails', { userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), folder: normalizedFolder, page, perPage }); return NextResponse.json(cachedEmails); } } logger.debug('[COURRIER_API] Redis cache miss, fetching from IMAP', { userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), folder: normalizedFolder, page, perPage }); // Use the email service to fetch emails with the normalized folder and effective account ID // CRITICAL FIX: Pass parameters in the correct order and with proper values const emailsResult = await getEmails( session.user.id, // userId normalizedFolder, // folder (without prefix) page, // page perPage, // perPage effectiveAccountId, // accountId checkOnly // checkOnly flag - only check for new emails without loading full content ); // CRITICAL FIX: Log when emails are returned from IMAP logger.debug('[COURRIER_API] Successfully fetched emails from IMAP', { count: emailsResult.emails.length, accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), }); // The result is already cached in the getEmails function (if not checkOnly) return NextResponse.json(emailsResult); } catch (error: any) { logger.error('[COURRIER_API] Error fetching emails', { error: error instanceof Error ? error.message : String(error), userIdHash: session?.user?.id ? Buffer.from(session.user.id).toString('base64').slice(0, 12) : null, }); return NextResponse.json( { error: "Failed to fetch emails", message: error.message }, { status: 500 } ); } } export async function POST(request: Request) { try { const session = await getServerSession(authOptions); if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const { emailId, folderName, accountId } = await request.json(); if (!emailId) { return NextResponse.json({ error: 'Missing emailId parameter' }, { status: 400 }); } // Use account ID or default if not provided const effectiveAccountId = accountId || 'default'; // Normalize folder name by removing account prefix if present const normalizedFolder = folderName && folderName.includes(':') ? folderName.split(':')[1] : folderName; // Log the cache invalidation operation logger.debug('[COURRIER_API] Invalidating cache', { userIdHash: Buffer.from(session.user.id).toString('base64').slice(0, 12), accountIdHash: Buffer.from(effectiveAccountId).toString('base64').slice(0, 12), folder: normalizedFolder || 'all folders' }); // Invalidate Redis cache for the folder if (normalizedFolder) { await invalidateFolderCache(session.user.id, effectiveAccountId, normalizedFolder); } 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, effectiveAccountId, folder); } } return NextResponse.json({ success: true }); } catch (error) { logger.error('[COURRIER_API] Error in POST handler', { error: error instanceof Error ? error.message : String(error) }); return NextResponse.json({ error: 'An unexpected error occurred' }, { status: 500 }); } }