diff --git a/app/api/courrier/route.ts b/app/api/courrier/route.ts index bd0f93f5..4f523722 100644 --- a/app/api/courrier/route.ts +++ b/app/api/courrier/route.ts @@ -100,9 +100,15 @@ function getCacheKey(userId: string, folder: string, page: number, limit: number return `${userId}:${folder}:${page}:${limit}`; } -export async function GET(request: Request) { +export async function GET( + request: Request, + { params }: { params: { id: string } } +) { try { - // 1. Authentication + // 1. Properly await params to avoid Next.js error + const { id } = await Promise.resolve(params); + + // 2. Authentication check const session = await getServerSession(authOptions); if (!session?.user?.id) { return NextResponse.json( @@ -111,24 +117,14 @@ export async function GET(request: Request) { ); } - // 2. Parse request parameters - const url = new URL(request.url); - const folder = url.searchParams.get('folder') || 'INBOX'; - const page = parseInt(url.searchParams.get('page') || '1'); - const limit = parseInt(url.searchParams.get('limit') || '20'); - const preview = url.searchParams.get('preview') === 'true'; - const forceRefresh = url.searchParams.get('refresh') === 'true'; - - // 3. Check cache first (unless refresh requested) - const cacheKey = getCacheKey(session.user.id, folder, page, limit); - if (!forceRefresh) { - const cachedData = emailCache.get(cacheKey); - if (cachedData) { - return NextResponse.json(cachedData); - } + // 3. Check cache first + const cacheKey = `email:${session.user.id}:${id}`; + const cachedEmail = emailContentCache.get(cacheKey); + if (cachedEmail) { + return NextResponse.json(cachedEmail); } - // 4. Get credentials + // 4. Get credentials from database const credentials = await prisma.mailCredentials.findUnique({ where: { userId: session.user.id @@ -142,109 +138,81 @@ export async function GET(request: Request) { ); } - // 5. Get IMAP client from pool (or create new) - const client = await getImapClient(session.user.id, credentials); + // 5. Create IMAP client + const client = new ImapFlow({ + host: credentials.host, + port: credentials.port, + secure: true, + auth: { + user: credentials.email, + pass: credentials.password, + }, + logger: false, + emitLogs: false, + tls: { + rejectUnauthorized: false + }, + disableAutoIdle: true + }); try { - // 6. Get mailboxes (with caching) - let availableFolders: string[]; - const foldersCacheKey = `folders:${session.user.id}`; - const cachedFolders = emailCache.get(foldersCacheKey); + await client.connect(); - if (cachedFolders) { - availableFolders = cachedFolders; - } else { - const mailboxes = await client.list(); - availableFolders = mailboxes.map(box => box.path); - emailCache.set(foldersCacheKey, availableFolders); - } + // 6. Open INBOX + await client.mailboxOpen('INBOX'); - // 7. Open mailbox - const mailbox = await client.mailboxOpen(folder); - - const result: Email[] = []; - - // 8. Fetch emails (if any exist) - // Define start and end variables HERE - let start = 1; - let end = 0; - - if (mailbox.exists > 0) { - // Calculate range with boundaries - start = Math.min((page - 1) * limit + 1, mailbox.exists); - end = Math.min(start + limit - 1, mailbox.exists); - - // Use sequence numbers in descending order for newest first - const range = `${mailbox.exists - end + 1}:${mailbox.exists - start + 1}`; - - // Fetch messages with optimized options - const options: any = { - envelope: true, - flags: true, - bodyStructure: true - }; - - // Only fetch preview if requested - if (preview) { - options.bodyParts = ['TEXT', 'HTML']; - } - - const messages = await client.fetch(range, options); - - // Process messages - for await (const message of messages) { - // Extract preview content correctly - let previewContent = null; - if (preview && message.bodyParts) { - // Try HTML first, then TEXT - const htmlPart = message.bodyParts.get('HTML'); - const textPart = message.bodyParts.get('TEXT'); - previewContent = htmlPart?.toString() || textPart?.toString() || null; - } - - const email: Email = { - id: message.uid.toString(), - from: message.envelope.from?.[0]?.address || '', - fromName: message.envelope.from?.[0]?.name || - message.envelope.from?.[0]?.address?.split('@')[0] || '', - to: message.envelope.to?.map((addr: any) => addr.address).join(', ') || '', - subject: message.envelope.subject || '(No subject)', - date: message.envelope.date?.toISOString() || new Date().toISOString(), - read: message.flags.has('\\Seen'), - starred: message.flags.has('\\Flagged'), - folder: mailbox.path, - hasAttachments: message.bodyStructure?.type === 'multipart', - flags: Array.from(message.flags), - preview: previewContent - }; - - result.push(email); - } - } - - // 9. Prepare response data - const responseData = { - emails: result, - folders: availableFolders, - total: mailbox.exists, - hasMore: end < mailbox.exists, - page, - limit + // 7. Fetch the email with UID search + const options = { + uid: true, // This is crucial - we must specify uid:true to fetch by UID + source: true, + envelope: true, + bodyStructure: true, + flags: true }; - // 10. Cache the results - emailCache.set(cacheKey, responseData); + // 7. Fetch by UID + const message = await client.fetchOne(id, options); + + if (!message) { + return NextResponse.json( + { error: 'Email not found' }, + { status: 404 } + ); + } - return NextResponse.json(responseData); - } catch (error) { - // Connection error - remove from pool - connectionPool.delete(session.user.id); - throw error; + // 8. Parse the email content + const emailContent = { + id: message.uid, + from: message.envelope.from?.[0]?.address || '', + fromName: message.envelope.from?.[0]?.name || + message.envelope.from?.[0]?.address?.split('@')[0] || '', + to: message.envelope.to?.map((addr: any) => addr.address).join(', ') || '', + subject: message.envelope.subject || '(No subject)', + date: message.envelope.date?.toISOString() || new Date().toISOString(), + content: message.source?.toString() || '', + read: message.flags.has('\\Seen'), + starred: message.flags.has('\\Flagged'), + flags: Array.from(message.flags), + hasAttachments: message.bodyStructure?.type === 'multipart' + }; + + // 9. Cache the email content + emailContentCache.set(cacheKey, emailContent); + + // 10. Return the email content + return NextResponse.json(emailContent); + } finally { + // 11. Close the connection + try { + await client.logout(); + } catch (e) { + console.error('Error during IMAP logout:', e); + } } } catch (error) { - console.error('Error in courrier route:', error); + console.error('Error fetching email:', error); return NextResponse.json( - { error: 'An unexpected error occurred' }, + { error: 'Failed to fetch email content' }, { status: 500 } ); } diff --git a/app/layout.tsx b/app/layout.tsx index cbf2d066..a1835d2f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -15,7 +15,7 @@ export default async function RootLayout({ children: React.ReactNode; }) { const session = await getServerSession(authOptions); - const headersList = headers(); + const headersList = await headers(); const pathname = headersList.get("x-pathname") || ""; const isSignInPage = pathname === "/signin";