210 lines
6.8 KiB
TypeScript
210 lines
6.8 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { ImapFlow } from 'imapflow';
|
|
import { getServerSession } from 'next-auth';
|
|
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
|
import { prisma } from '@/lib/prisma';
|
|
import { parseEmail } from '@/lib/server/email-parser';
|
|
import { LRUCache } from 'lru-cache';
|
|
|
|
// Simple in-memory cache for email content
|
|
const emailContentCache = new LRUCache<string, any>({
|
|
max: 100,
|
|
ttl: 1000 * 60 * 15, // 15 minutes
|
|
});
|
|
|
|
export async function GET(
|
|
request: Request,
|
|
{ params }: { params: { id: string } }
|
|
) {
|
|
try {
|
|
// 1. Get email ID from params (properly awaited)
|
|
const { id } = await Promise.resolve(params);
|
|
|
|
// 2. Authentication check
|
|
const session = await getServerSession(authOptions);
|
|
if (!session?.user?.id) {
|
|
return NextResponse.json(
|
|
{ error: 'Unauthorized' },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
|
|
// 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 from database
|
|
const credentials = await prisma.mailCredentials.findUnique({
|
|
where: {
|
|
userId: session.user.id
|
|
}
|
|
});
|
|
|
|
if (!credentials) {
|
|
return NextResponse.json(
|
|
{ error: 'No mail credentials found. Please configure your email account.' },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
|
|
// 5. Get the current folder from the request URL
|
|
const url = new URL(request.url);
|
|
const folder = url.searchParams.get('folder') || 'INBOX';
|
|
|
|
// 6. 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 {
|
|
await client.connect();
|
|
|
|
// 7. Open the folder
|
|
const mailbox = await client.mailboxOpen(folder);
|
|
console.log(`Mailbox opened: ${folder}, total messages: ${mailbox.exists}`);
|
|
|
|
// 8. Download the raw message data using a dynamic fetch approach
|
|
console.log(`Attempting to fetch message with UID: ${id}`);
|
|
|
|
// Create a loop to process all messages until we find the right one
|
|
let foundMessage = null;
|
|
const chunkSize = 10;
|
|
|
|
for (let i = 1; i <= mailbox.exists; i += chunkSize) {
|
|
const endIdx = Math.min(i + chunkSize - 1, mailbox.exists);
|
|
const range = `${i}:${endIdx}`;
|
|
|
|
console.log(`Scanning messages ${range}`);
|
|
|
|
// Fetch messages in chunks with UID
|
|
const messages = client.fetch(range, {
|
|
uid: true,
|
|
envelope: true,
|
|
flags: true,
|
|
bodyStructure: true,
|
|
source: true
|
|
});
|
|
|
|
for await (const message of messages) {
|
|
if (message.uid.toString() === id) {
|
|
console.log(`Found matching message with UID ${id}`);
|
|
foundMessage = message;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (foundMessage) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundMessage) {
|
|
console.log(`No message found with UID ${id}`);
|
|
return NextResponse.json(
|
|
{ error: `Email not found with UID ${id}` },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
console.log(`Successfully fetched message, parsing content...`);
|
|
|
|
// 9. Parse the email content
|
|
const parsedEmail = await parseEmail(foundMessage.source.toString());
|
|
|
|
// Debug the parsed email structure
|
|
console.log('Parsed email data structure:', {
|
|
hasHtml: !!parsedEmail.html,
|
|
hasText: !!parsedEmail.text,
|
|
htmlLength: parsedEmail.html ? parsedEmail.html.length : 0,
|
|
textLength: parsedEmail.text ? parsedEmail.text.length : 0,
|
|
attachmentsCount: parsedEmail.attachments ? parsedEmail.attachments.length : 0
|
|
});
|
|
|
|
// 10. Prepare the full email object with all needed data
|
|
const fullEmail = {
|
|
id,
|
|
from: foundMessage.envelope.from?.[0]?.address || '',
|
|
fromName: foundMessage.envelope.from?.[0]?.name ||
|
|
foundMessage.envelope.from?.[0]?.address?.split('@')[0] || '',
|
|
to: foundMessage.envelope.to?.map((addr: any) => addr.address).join(', ') || '',
|
|
subject: foundMessage.envelope.subject || '(No subject)',
|
|
date: foundMessage.envelope.date?.toISOString() || new Date().toISOString(),
|
|
content: parsedEmail.html || parsedEmail.text || '',
|
|
textContent: parsedEmail.text || '',
|
|
rawContent: foundMessage.source.toString(), // Include raw content for fallback
|
|
read: foundMessage.flags.has('\\Seen'),
|
|
starred: foundMessage.flags.has('\\Flagged'),
|
|
folder: folder,
|
|
hasAttachments: foundMessage.bodyStructure?.type === 'multipart',
|
|
attachments: parsedEmail.attachments || [],
|
|
flags: Array.from(foundMessage.flags),
|
|
headers: parsedEmail.headers || {}
|
|
};
|
|
|
|
// Log the structure of the email being returned
|
|
console.log('Returning email object with content structure:', {
|
|
id: fullEmail.id,
|
|
hasContent: !!fullEmail.content,
|
|
contentLength: fullEmail.content ? fullEmail.content.length : 0,
|
|
hasTextContent: !!fullEmail.textContent,
|
|
textContentLength: fullEmail.textContent ? fullEmail.textContent.length : 0
|
|
});
|
|
|
|
// 11. Mark as read if not already
|
|
if (!foundMessage.flags.has('\\Seen')) {
|
|
try {
|
|
// Use the same sequence range to mark message as read
|
|
for (let i = 1; i <= mailbox.exists; i += chunkSize) {
|
|
const endIdx = Math.min(i + chunkSize - 1, mailbox.exists);
|
|
const range = `${i}:${endIdx}`;
|
|
|
|
// Find message and mark as read
|
|
const messages = client.fetch(range, { uid: true });
|
|
for await (const message of messages) {
|
|
if (message.uid.toString() === id) {
|
|
await client.messageFlagsAdd(i.toString(), ['\\Seen']);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error marking message as read:', error);
|
|
}
|
|
}
|
|
|
|
// 12. Cache the email content
|
|
emailContentCache.set(cacheKey, fullEmail);
|
|
|
|
// 13. Return the full email
|
|
return NextResponse.json(fullEmail);
|
|
} finally {
|
|
// 14. Close the connection
|
|
try {
|
|
await client.logout();
|
|
} catch (e) {
|
|
console.error('Error during IMAP logout:', e);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching email:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to fetch email' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|