panel 2 courier api restore
This commit is contained in:
parent
1e4f07058b
commit
ec725ae8c1
@ -35,12 +35,14 @@ export async function POST(
|
|||||||
{ params }: { params: { id: string } }
|
{ params }: { params: { id: string } }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
// Get session
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
if (!session?.user?.id) {
|
if (!session?.user?.id) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailId = params.id;
|
// Properly await the params object before accessing its properties
|
||||||
|
const { id: emailId } = await Promise.resolve(params);
|
||||||
if (!emailId) {
|
if (!emailId) {
|
||||||
return NextResponse.json({ error: 'Email ID is required' }, { status: 400 });
|
return NextResponse.json({ error: 'Email ID is required' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,32 +1,39 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth/next';
|
||||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
||||||
import { prisma } from '@/lib/prisma';
|
import { prisma } from '@/lib/prisma';
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
// Verify user is authenticated
|
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
if (!session?.user?.id) {
|
if (!session || !session.user?.id) {
|
||||||
|
console.log("No authenticated session found");
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Unauthorized' },
|
{ error: "Not authenticated" },
|
||||||
{ status: 401 }
|
{ status: 401 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get credentials from database
|
console.log(`Attempting to fetch mail credentials for user ${session.user.id}`);
|
||||||
|
|
||||||
|
// Get mail credentials
|
||||||
const credentials = await prisma.mailCredentials.findUnique({
|
const credentials = await prisma.mailCredentials.findUnique({
|
||||||
where: { userId: session.user.id }
|
where: {
|
||||||
|
userId: session.user.id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
|
console.log(`No mail credentials found for user ${session.user.id}`);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'No mail credentials found', credentials: null },
|
{ error: "No mail credentials found" },
|
||||||
{ status: 404 }
|
{ status: 404 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return only what's needed (especially the email)
|
console.log(`Found credentials for email: ${credentials.email}`);
|
||||||
|
|
||||||
|
// Return only necessary credential details
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
credentials: {
|
credentials: {
|
||||||
email: credentials.email,
|
email: credentials.email,
|
||||||
@ -35,9 +42,17 @@ export async function GET(request: Request) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching credentials:', error);
|
console.error("Error fetching mail credentials:", error);
|
||||||
|
|
||||||
|
// Log more detailed error information
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error("Error name:", error.name);
|
||||||
|
console.error("Error message:", error.message);
|
||||||
|
console.error("Error stack:", error.stack);
|
||||||
|
}
|
||||||
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Failed to fetch credentials' },
|
{ error: "Failed to fetch mail credentials" },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,33 +3,25 @@ import { ImapFlow } from 'imapflow';
|
|||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
||||||
import { prisma } from '@/lib/prisma';
|
import { prisma } from '@/lib/prisma';
|
||||||
|
import { simpleParser } from 'mailparser';
|
||||||
|
|
||||||
|
// Type definitions
|
||||||
|
interface EmailCacheEntry {
|
||||||
|
data: any;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CredentialsCacheEntry {
|
||||||
|
client: any; // Use any for ImapFlow to avoid type issues
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
// Email cache structure
|
// Email cache structure
|
||||||
interface EmailCache {
|
const emailListCache: Record<string, EmailCacheEntry> = {};
|
||||||
[key: string]: {
|
const credentialsCache: Record<string, CredentialsCacheEntry> = {};
|
||||||
data: any;
|
|
||||||
timestamp: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Credentials cache to reduce database queries
|
// Cache TTL in milliseconds
|
||||||
interface CredentialsCache {
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
||||||
[userId: string]: {
|
|
||||||
credentials: any;
|
|
||||||
timestamp: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In-memory caches with expiration
|
|
||||||
// Make emailListCache available globally for other routes
|
|
||||||
if (!global.emailListCache) {
|
|
||||||
global.emailListCache = {};
|
|
||||||
}
|
|
||||||
const emailListCache: EmailCache = global.emailListCache;
|
|
||||||
const credentialsCache: CredentialsCache = {};
|
|
||||||
|
|
||||||
// Cache TTL in milliseconds (5 minutes)
|
|
||||||
const CACHE_TTL = 5 * 60 * 1000;
|
|
||||||
|
|
||||||
// Helper function to get credentials with caching
|
// Helper function to get credentials with caching
|
||||||
async function getCredentialsWithCache(userId: string) {
|
async function getCredentialsWithCache(userId: string) {
|
||||||
@ -38,7 +30,7 @@ async function getCredentialsWithCache(userId: string) {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
if (cachedCreds && now - cachedCreds.timestamp < CACHE_TTL) {
|
if (cachedCreds && now - cachedCreds.timestamp < CACHE_TTL) {
|
||||||
return cachedCreds.credentials;
|
return cachedCreds.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise fetch from database
|
// Otherwise fetch from database
|
||||||
@ -49,12 +41,21 @@ async function getCredentialsWithCache(userId: string) {
|
|||||||
// Cache the result
|
// Cache the result
|
||||||
if (credentials) {
|
if (credentials) {
|
||||||
credentialsCache[userId] = {
|
credentialsCache[userId] = {
|
||||||
credentials,
|
client: credentialsCache[userId]?.client || new ImapFlow({
|
||||||
|
host: credentials.host,
|
||||||
|
port: credentials.port,
|
||||||
|
secure: true,
|
||||||
|
auth: {
|
||||||
|
user: credentials.email,
|
||||||
|
pass: credentials.password,
|
||||||
|
},
|
||||||
|
logger: false,
|
||||||
|
}),
|
||||||
timestamp: now
|
timestamp: now
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return credentials;
|
return credentialsCache[userId]?.client || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retry logic for IMAP operations
|
// Retry logic for IMAP operations
|
||||||
@ -82,203 +83,289 @@ async function retryOperation<T>(operation: () => Promise<T>, maxAttempts = 3, d
|
|||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
try {
|
try {
|
||||||
console.log('Courrier API call received');
|
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
if (!session?.user?.id) {
|
if (!session || !session.user?.id) {
|
||||||
console.log('No authenticated session found');
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Unauthorized' },
|
{ error: "Not authenticated" },
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
console.log('User authenticated:', session.user.id);
|
|
||||||
|
|
||||||
// Get URL 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 skipCache = url.searchParams.get('skipCache') === 'true';
|
|
||||||
console.log('Request parameters:', { folder, page, limit, skipCache });
|
|
||||||
|
|
||||||
// Generate cache key based on request parameters
|
|
||||||
const cacheKey = `${session.user.id}:${folder}:${page}:${limit}:full`;
|
|
||||||
|
|
||||||
// Check cache first if not explicitly skipped
|
|
||||||
if (!skipCache && emailListCache[cacheKey]) {
|
|
||||||
const { data, timestamp } = emailListCache[cacheKey];
|
|
||||||
// Return cached data if it's fresh (less than 1 minute old)
|
|
||||||
if (Date.now() - timestamp < 60000) {
|
|
||||||
console.log('Returning cached email data');
|
|
||||||
return NextResponse.json(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get credentials from cache or database
|
|
||||||
const credentials = await getCredentialsWithCache(session.user.id);
|
|
||||||
console.log('Credentials retrieved:', credentials ? 'yes' : 'no');
|
|
||||||
|
|
||||||
if (!credentials) {
|
|
||||||
console.log('No mail credentials found for user');
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'No mail credentials found. Please configure your email account.' },
|
|
||||||
{ status: 401 }
|
{ status: 401 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate start and end sequence numbers
|
// Get mail credentials
|
||||||
const start = (page - 1) * limit + 1;
|
const credentials = await prisma.mailCredentials.findUnique({
|
||||||
const end = start + limit - 1;
|
where: {
|
||||||
console.log('Fetching emails from range:', { start, end });
|
userId: session.user.id,
|
||||||
|
|
||||||
// Connect to IMAP server
|
|
||||||
console.log('Connecting to IMAP server:', credentials.host, credentials.port);
|
|
||||||
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
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
if (!credentials) {
|
||||||
await client.connect();
|
return NextResponse.json(
|
||||||
console.log('Connected to IMAP server');
|
{ error: "No mail credentials found" },
|
||||||
|
{ status: 404 }
|
||||||
// Get list of all mailboxes first
|
);
|
||||||
const mailboxes = await client.list();
|
}
|
||||||
const availableFolders = mailboxes.map(box => box.path);
|
|
||||||
console.log('Available folders:', availableFolders);
|
const { searchParams } = new URL(request.url);
|
||||||
|
const page = parseInt(searchParams.get("page") || "1");
|
||||||
// Open the requested mailbox
|
const perPage = parseInt(searchParams.get("perPage") || "20");
|
||||||
console.log('Opening mailbox:', folder);
|
const folder = searchParams.get("folder") || "INBOX";
|
||||||
const mailbox = await client.mailboxOpen(folder);
|
const searchQuery = searchParams.get("search") || "";
|
||||||
console.log('Mailbox stats:', {
|
|
||||||
exists: mailbox.exists,
|
// Check for entry in emailCache
|
||||||
name: mailbox.path,
|
const cacheKey = `${session.user.id}:${folder}:${page}:${perPage}:${searchQuery}`;
|
||||||
flags: mailbox.flags
|
const cachedEmails = emailListCache[cacheKey];
|
||||||
});
|
|
||||||
|
if (cachedEmails) {
|
||||||
const result = [];
|
console.log(`Using cached emails for ${cacheKey}`);
|
||||||
|
return NextResponse.json(cachedEmails.data);
|
||||||
// Only try to fetch if the mailbox has messages
|
}
|
||||||
if (mailbox.exists > 0) {
|
|
||||||
// Adjust start and end to be within bounds
|
console.log(`Cache miss for ${cacheKey}, fetching from IMAP`);
|
||||||
const adjustedStart = Math.min(start, mailbox.exists);
|
|
||||||
const adjustedEnd = Math.min(end, mailbox.exists);
|
// Fetch from IMAP
|
||||||
console.log('Adjusted fetch range:', { adjustedStart, adjustedEnd });
|
const cacheCredKey = `credentials:${session.user.id}`;
|
||||||
|
let imapClient: any = credentialsCache[cacheCredKey]?.client || null;
|
||||||
// Fetch both metadata AND full content
|
|
||||||
const fetchOptions: any = {
|
if (!imapClient) {
|
||||||
envelope: true,
|
// Create IMAP client
|
||||||
flags: true,
|
const connectWithRetry = async (retries = 3, delay = 1000): Promise<any> => {
|
||||||
bodyStructure: true,
|
|
||||||
source: true // Include full email source
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('Fetching messages with options:', fetchOptions);
|
|
||||||
const fetchPromises = [];
|
|
||||||
for (let i = adjustedStart; i <= adjustedEnd; i++) {
|
|
||||||
// Convert to string sequence number as required by ImapFlow
|
|
||||||
fetchPromises.push(client.fetchOne(`${i}`, fetchOptions));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const results = await Promise.all(fetchPromises);
|
console.log(`Attempting to connect to IMAP server (${credentials.host}:${credentials.port})...`);
|
||||||
|
const client = new ImapFlow({
|
||||||
for (const message of results) {
|
host: credentials.host,
|
||||||
if (!message) continue; // Skip undefined messages
|
port: credentials.port,
|
||||||
|
secure: true,
|
||||||
console.log('Processing message ID:', message.uid);
|
auth: {
|
||||||
const emailData: any = {
|
user: credentials.email,
|
||||||
id: message.uid,
|
pass: credentials.password,
|
||||||
from: message.envelope.from?.[0]?.address || '',
|
},
|
||||||
fromName: message.envelope.from?.[0]?.name || message.envelope.from?.[0]?.address?.split('@')[0] || '',
|
logger: false,
|
||||||
to: message.envelope.to?.map(addr => addr.address).join(', ') || '',
|
});
|
||||||
subject: message.envelope.subject || '(No subject)',
|
|
||||||
date: message.envelope.date?.toISOString() || new Date().toISOString(),
|
await client.connect();
|
||||||
read: message.flags.has('\\Seen'),
|
console.log("Successfully connected to IMAP server");
|
||||||
starred: message.flags.has('\\Flagged'),
|
return client;
|
||||||
folder: mailbox.path,
|
} catch (error) {
|
||||||
hasAttachments: message.bodyStructure?.type === 'multipart',
|
if (retries > 0) {
|
||||||
flags: Array.from(message.flags),
|
console.log(`Connection failed, retrying... (${retries} attempts left)`);
|
||||||
content: message.source?.toString() || '' // Include full email content
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
};
|
return connectWithRetry(retries - 1, delay * 1.5);
|
||||||
|
|
||||||
result.push(emailData);
|
|
||||||
}
|
}
|
||||||
} catch (fetchError) {
|
throw error;
|
||||||
console.error('Error fetching emails:', fetchError);
|
|
||||||
// Continue with any successfully fetched messages
|
|
||||||
}
|
}
|
||||||
} else {
|
};
|
||||||
console.log('No messages in mailbox');
|
|
||||||
|
try {
|
||||||
|
imapClient = await connectWithRetry();
|
||||||
|
// Cache for future use
|
||||||
|
credentialsCache[cacheCredKey] = {
|
||||||
|
client: imapClient,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Failed to connect to IMAP server after retries:", error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Failed to connect to IMAP server", message: error.message },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Using cached IMAP client connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get mailboxes
|
||||||
|
const getMailboxes = async () => {
|
||||||
|
const mailboxes = [];
|
||||||
|
for await (const mailbox of imapClient.listMailboxes()) {
|
||||||
|
mailboxes.push(mailbox);
|
||||||
|
}
|
||||||
|
return mailboxes;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup paging
|
||||||
|
const startIdx = (page - 1) * perPage + 1;
|
||||||
|
const endIdx = page * perPage;
|
||||||
|
|
||||||
|
let emails: any[] = [];
|
||||||
|
let mailboxData = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Select and lock mailbox
|
||||||
|
mailboxData = await imapClient.mailboxOpen(folder);
|
||||||
|
console.log(`Opened mailbox ${folder}, ${mailboxData.exists} messages total`);
|
||||||
|
|
||||||
|
// Calculate range based on total messages
|
||||||
|
const totalMessages = mailboxData.exists;
|
||||||
|
const from = Math.max(totalMessages - endIdx + 1, 1);
|
||||||
|
const to = Math.max(totalMessages - startIdx + 1, 1);
|
||||||
|
|
||||||
|
// Skip if no messages or invalid range
|
||||||
|
if (totalMessages === 0 || from > to) {
|
||||||
|
console.log("No messages in range, returning empty array");
|
||||||
|
const result = {
|
||||||
|
emails: [],
|
||||||
|
totalEmails: 0,
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
totalPages: 0,
|
||||||
|
folder,
|
||||||
|
mailboxes: await getMailboxes(),
|
||||||
|
};
|
||||||
|
emailListCache[cacheKey] = {
|
||||||
|
data: result,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
return NextResponse.json(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseData = {
|
console.log(`Fetching messages ${from}:${to} (page ${page}, ${perPage} per page)`);
|
||||||
emails: result,
|
|
||||||
folders: availableFolders,
|
// Search if needed
|
||||||
total: mailbox.exists,
|
let messageIds: any[] = [];
|
||||||
hasMore: end < mailbox.exists
|
if (searchQuery) {
|
||||||
|
console.log(`Searching for: "${searchQuery}"`);
|
||||||
|
messageIds = await imapClient.search({
|
||||||
|
body: searchQuery
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter to our page range
|
||||||
|
messageIds = messageIds.filter(id => id >= from && id <= to);
|
||||||
|
console.log(`Found ${messageIds.length} messages matching search`);
|
||||||
|
} else {
|
||||||
|
messageIds = Array.from(
|
||||||
|
{ length: to - from + 1 },
|
||||||
|
(_, i) => from + i
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch messages with their content
|
||||||
|
for (const id of messageIds) {
|
||||||
|
try {
|
||||||
|
const message = await imapClient.fetchOne(id, {
|
||||||
|
envelope: true,
|
||||||
|
flags: true,
|
||||||
|
bodyStructure: true,
|
||||||
|
internalDate: true,
|
||||||
|
size: true,
|
||||||
|
source: true // Include full message source to get content
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!message) continue;
|
||||||
|
|
||||||
|
const { envelope, flags, bodyStructure, internalDate, size, source } = message;
|
||||||
|
|
||||||
|
// Extract content from the message source
|
||||||
|
let content = '';
|
||||||
|
if (source) {
|
||||||
|
const parsedEmail = await simpleParser(source.toString());
|
||||||
|
// Get HTML or text content
|
||||||
|
content = parsedEmail.html || parsedEmail.text || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert attachments to our format
|
||||||
|
const attachments: Array<{
|
||||||
|
contentId?: string;
|
||||||
|
filename: string;
|
||||||
|
contentType: string;
|
||||||
|
size: number;
|
||||||
|
path: string;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
const processAttachments = (node: any, path: Array<string | number> = []) => {
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
if (node.type === 'attachment') {
|
||||||
|
attachments.push({
|
||||||
|
contentId: node.contentId,
|
||||||
|
filename: node.filename || 'attachment',
|
||||||
|
contentType: node.contentType,
|
||||||
|
size: node.size,
|
||||||
|
path: [...path, node.part].join('.')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.childNodes) {
|
||||||
|
node.childNodes.forEach((child: any, index: number) => {
|
||||||
|
processAttachments(child, [...path, node.part || index + 1]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bodyStructure) {
|
||||||
|
processAttachments(bodyStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert flags from Set to boolean checks
|
||||||
|
const flagsArray = Array.from(flags as Set<string>);
|
||||||
|
|
||||||
|
emails.push({
|
||||||
|
id,
|
||||||
|
messageId: envelope.messageId,
|
||||||
|
subject: envelope.subject || "(No Subject)",
|
||||||
|
from: envelope.from.map((f: any) => ({
|
||||||
|
name: f.name || f.address,
|
||||||
|
address: f.address,
|
||||||
|
})),
|
||||||
|
to: envelope.to.map((t: any) => ({
|
||||||
|
name: t.name || t.address,
|
||||||
|
address: t.address,
|
||||||
|
})),
|
||||||
|
cc: (envelope.cc || []).map((c: any) => ({
|
||||||
|
name: c.name || c.address,
|
||||||
|
address: c.address,
|
||||||
|
})),
|
||||||
|
bcc: (envelope.bcc || []).map((b: any) => ({
|
||||||
|
name: b.name || b.address,
|
||||||
|
address: b.address,
|
||||||
|
})),
|
||||||
|
date: internalDate || new Date(),
|
||||||
|
flags: {
|
||||||
|
seen: flagsArray.includes("\\Seen"),
|
||||||
|
flagged: flagsArray.includes("\\Flagged"),
|
||||||
|
answered: flagsArray.includes("\\Answered"),
|
||||||
|
deleted: flagsArray.includes("\\Deleted"),
|
||||||
|
draft: flagsArray.includes("\\Draft"),
|
||||||
|
},
|
||||||
|
hasAttachments: attachments.length > 0,
|
||||||
|
attachments,
|
||||||
|
size,
|
||||||
|
content // Include content directly in email object
|
||||||
|
});
|
||||||
|
} catch (messageError) {
|
||||||
|
console.error(`Error fetching message ${id}:`, messageError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by date, newest first
|
||||||
|
emails.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
emails,
|
||||||
|
totalEmails: totalMessages,
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
totalPages: Math.ceil(totalMessages / perPage),
|
||||||
|
folder,
|
||||||
|
mailboxes: await getMailboxes(),
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Response summary:', {
|
|
||||||
emailCount: result.length,
|
|
||||||
folderCount: availableFolders.length,
|
|
||||||
total: mailbox.exists,
|
|
||||||
hasMore: end < mailbox.exists
|
|
||||||
});
|
|
||||||
|
|
||||||
// Cache the result
|
// Cache the result
|
||||||
emailListCache[cacheKey] = {
|
emailListCache[cacheKey] = {
|
||||||
data: responseData,
|
data: result,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
};
|
};
|
||||||
|
|
||||||
return NextResponse.json(responseData);
|
return NextResponse.json(result);
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in IMAP operations:', error);
|
|
||||||
let errorMessage = 'Failed to fetch emails';
|
|
||||||
let statusCode = 500;
|
|
||||||
|
|
||||||
// Type guard for Error objects
|
|
||||||
if (error instanceof Error) {
|
|
||||||
errorMessage = error.message;
|
|
||||||
|
|
||||||
// Handle specific error cases
|
|
||||||
if (errorMessage.includes('authentication') || errorMessage.includes('login')) {
|
|
||||||
statusCode = 401;
|
|
||||||
errorMessage = 'Authentication failed. Please check your email credentials.';
|
|
||||||
} else if (errorMessage.includes('connect')) {
|
|
||||||
errorMessage = 'Failed to connect to email server. Please check your settings.';
|
|
||||||
} else if (errorMessage.includes('timeout')) {
|
|
||||||
errorMessage = 'Connection timed out. Please try again later.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: errorMessage },
|
|
||||||
{ status: statusCode }
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
// If we opened a mailbox, close it
|
||||||
await client.logout();
|
if (mailboxData) {
|
||||||
console.log('IMAP client logged out');
|
await imapClient.mailboxClose();
|
||||||
} catch (e) {
|
|
||||||
console.error('Error during logout:', e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('Error in courrier route:', error);
|
console.error("Error in GET:", error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'An unexpected error occurred' },
|
{ error: "Internal server error", message: error.message },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,17 +117,20 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
console.log('Loading content for email:', email.id);
|
console.log('Loading content for email:', email.id);
|
||||||
console.log('Email content length:', email.content?.length || 0);
|
console.log('Email content length:', email.content?.length || 0);
|
||||||
|
|
||||||
if (!email.content) {
|
// Check if content is available in either content property or body property (for backward compatibility)
|
||||||
|
const emailContent = email.content || email.body || '';
|
||||||
|
|
||||||
|
if (!emailContent) {
|
||||||
console.log('No content available for email:', email.id);
|
console.log('No content available for email:', email.id);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setContent(<div className="text-gray-500">No content available</div>);
|
setContent(<div className="text-gray-500">No content available</div>);
|
||||||
setDebugInfo('Email has no content property');
|
setDebugInfo('No content available for this email');
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedEmail = email.content.trim();
|
const formattedEmail = emailContent.trim();
|
||||||
if (!formattedEmail) {
|
if (!formattedEmail) {
|
||||||
console.log('Empty content for email:', email.id);
|
console.log('Empty content for email:', email.id);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -187,7 +190,7 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
return () => {
|
return () => {
|
||||||
mounted = false;
|
mounted = false;
|
||||||
};
|
};
|
||||||
}, [email?.id, email?.content]);
|
}, [email?.id, email?.content, email?.body]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user