panel 2 courier api restore
This commit is contained in:
parent
74f78f09f8
commit
d9cf5ce3a0
@ -10,8 +10,9 @@ const menuItems = {
|
|||||||
missions: "https://example.com/missions"
|
missions: "https://example.com/missions"
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SectionPage({ params }: { params: { section: string } }) {
|
export default async function SectionPage({ params }: { params: { section: string } }) {
|
||||||
const iframeUrl = menuItems[params.section as keyof typeof menuItems]
|
const { section } = await Promise.resolve(params);
|
||||||
|
const iframeUrl = menuItems[section as keyof typeof menuItems]
|
||||||
|
|
||||||
if (!iframeUrl) {
|
if (!iframeUrl) {
|
||||||
notFound()
|
notFound()
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export async function POST(
|
|||||||
{ params }: { params: { id: string } }
|
{ params }: { params: { id: string } }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const id = params.id;
|
const { id } = await Promise.resolve(params);
|
||||||
|
|
||||||
// Authentication check
|
// Authentication check
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export async function GET(
|
|||||||
{ params }: { params: { id: string } }
|
{ params }: { params: { id: string } }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const id = params.id;
|
const { id } = await Promise.resolve(params);
|
||||||
|
|
||||||
// Authentication check
|
// Authentication check
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|||||||
@ -4,13 +4,51 @@ 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';
|
||||||
|
|
||||||
// Simple email cache structure
|
// Email cache structure
|
||||||
interface EmailCache {
|
interface EmailCache {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple in-memory cache
|
// Credentials cache to reduce database queries
|
||||||
const cache: EmailCache = {};
|
interface CredentialsCache {
|
||||||
|
[userId: string]: {
|
||||||
|
credentials: any;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In-memory caches with expiration
|
||||||
|
const emailListCache: EmailCache = {};
|
||||||
|
const credentialsCache: CredentialsCache = {};
|
||||||
|
|
||||||
|
// Cache TTL in milliseconds (5 minutes)
|
||||||
|
const CACHE_TTL = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
// Helper function to get credentials with caching
|
||||||
|
async function getCredentialsWithCache(userId: string) {
|
||||||
|
// Check if we have fresh cached credentials
|
||||||
|
const cachedCreds = credentialsCache[userId];
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (cachedCreds && now - cachedCreds.timestamp < CACHE_TTL) {
|
||||||
|
return cachedCreds.credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise fetch from database
|
||||||
|
const credentials = await prisma.mailCredentials.findUnique({
|
||||||
|
where: { userId }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
if (credentials) {
|
||||||
|
credentialsCache[userId] = {
|
||||||
|
credentials,
|
||||||
|
timestamp: now
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
try {
|
try {
|
||||||
@ -22,12 +60,28 @@ export async function GET(request: Request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get credentials from database
|
// Get URL parameters
|
||||||
const credentials = await prisma.mailCredentials.findUnique({
|
const url = new URL(request.url);
|
||||||
where: {
|
const folder = url.searchParams.get('folder') || 'INBOX';
|
||||||
userId: session.user.id
|
const page = parseInt(url.searchParams.get('page') || '1');
|
||||||
|
const limit = parseInt(url.searchParams.get('limit') || '20');
|
||||||
|
const preview = url.searchParams.get('preview') === 'true';
|
||||||
|
const skipCache = url.searchParams.get('skipCache') === 'true';
|
||||||
|
|
||||||
|
// Generate cache key based on request parameters
|
||||||
|
const cacheKey = `${session.user.id}:${folder}:${page}:${limit}:${preview}`;
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return NextResponse.json(data);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// Get credentials from cache or database
|
||||||
|
const credentials = await getCredentialsWithCache(session.user.id);
|
||||||
|
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@ -36,13 +90,6 @@ export async function GET(request: Request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get query 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';
|
|
||||||
|
|
||||||
// Calculate start and end sequence numbers
|
// Calculate start and end sequence numbers
|
||||||
const start = (page - 1) * limit + 1;
|
const start = (page - 1) * limit + 1;
|
||||||
const end = start + limit - 1;
|
const end = start + limit - 1;
|
||||||
@ -119,12 +166,20 @@ export async function GET(request: Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json({
|
const responseData = {
|
||||||
emails: result,
|
emails: result,
|
||||||
folders: availableFolders,
|
folders: availableFolders,
|
||||||
total: mailbox.exists,
|
total: mailbox.exists,
|
||||||
hasMore: end < mailbox.exists
|
hasMore: end < mailbox.exists
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
emailListCache[cacheKey] = {
|
||||||
|
data: responseData,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
return NextResponse.json(responseData);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
await client.logout();
|
await client.logout();
|
||||||
|
|||||||
@ -102,6 +102,7 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
const [content, setContent] = useState<React.ReactNode>(null);
|
const [content, setContent] = useState<React.ReactNode>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [debugInfo, setDebugInfo] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let mounted = true;
|
let mounted = true;
|
||||||
@ -110,10 +111,16 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
if (!email) return;
|
if (!email) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
setDebugInfo(null);
|
||||||
try {
|
try {
|
||||||
|
console.log('Loading content for email:', email.id);
|
||||||
|
console.log('Email content length:', email.content?.length || 0);
|
||||||
|
|
||||||
if (!email.content) {
|
if (!email.content) {
|
||||||
|
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');
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -121,31 +128,44 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
|
|
||||||
const formattedEmail = email.content.trim();
|
const formattedEmail = email.content.trim();
|
||||||
if (!formattedEmail) {
|
if (!formattedEmail) {
|
||||||
|
console.log('Empty content for email:', email.id);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setContent(<div className="text-gray-500">No content available</div>);
|
setContent(<div className="text-gray-500">Email content is empty</div>);
|
||||||
|
setDebugInfo('Email content is empty string');
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Parsing email content:', formattedEmail.substring(0, 100) + '...');
|
||||||
const parsedEmail = await decodeEmail(formattedEmail);
|
const parsedEmail = await decodeEmail(formattedEmail);
|
||||||
|
console.log('Parsed email result:', {
|
||||||
|
hasHtml: !!parsedEmail.html,
|
||||||
|
hasText: !!parsedEmail.text,
|
||||||
|
htmlLength: parsedEmail.html?.length || 0,
|
||||||
|
textLength: parsedEmail.text?.length || 0
|
||||||
|
});
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (parsedEmail.html) {
|
if (parsedEmail.html) {
|
||||||
|
const sanitizedHtml = DOMPurify.sanitize(parsedEmail.html);
|
||||||
setContent(
|
setContent(
|
||||||
<div
|
<div
|
||||||
className="email-content prose prose-sm max-w-none dark:prose-invert"
|
className="email-content prose prose-sm max-w-none dark:prose-invert"
|
||||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(parsedEmail.html) }}
|
dangerouslySetInnerHTML={{ __html: sanitizedHtml }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
setDebugInfo('Rendered HTML content');
|
||||||
} else if (parsedEmail.text) {
|
} else if (parsedEmail.text) {
|
||||||
setContent(
|
setContent(
|
||||||
<div className="email-content whitespace-pre-wrap">
|
<div className="email-content whitespace-pre-wrap">
|
||||||
{parsedEmail.text}
|
{parsedEmail.text}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
setDebugInfo('Rendered text content');
|
||||||
} else {
|
} else {
|
||||||
setContent(<div className="text-gray-500">No content available</div>);
|
setContent(<div className="text-gray-500">No displayable content available</div>);
|
||||||
|
setDebugInfo('No HTML or text content in parsed email');
|
||||||
}
|
}
|
||||||
setError(null);
|
setError(null);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -154,6 +174,7 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
console.error('Error rendering email content:', err);
|
console.error('Error rendering email content:', err);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setError('Error rendering email content. Please try again.');
|
setError('Error rendering email content. Please try again.');
|
||||||
|
setDebugInfo(err instanceof Error ? err.message : 'Unknown error');
|
||||||
setContent(null);
|
setContent(null);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -165,7 +186,7 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
return () => {
|
return () => {
|
||||||
mounted = false;
|
mounted = false;
|
||||||
};
|
};
|
||||||
}, [email?.content]);
|
}, [email?.id, email?.content]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@ -176,10 +197,28 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div className="text-red-500">{error}</div>;
|
return (
|
||||||
|
<div className="text-red-500 p-4">
|
||||||
|
<div>{error}</div>
|
||||||
|
{debugInfo && (
|
||||||
|
<div className="mt-2 text-xs text-gray-500">
|
||||||
|
Debug info: {debugInfo}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return content || <div className="text-gray-500">No content available</div>;
|
return (
|
||||||
|
<>
|
||||||
|
{content || <div className="text-gray-500">No content available</div>}
|
||||||
|
{debugInfo && process.env.NODE_ENV !== 'production' && (
|
||||||
|
<div className="mt-4 p-2 border border-gray-200 rounded text-xs text-gray-500">
|
||||||
|
Debug: {debugInfo}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderEmailContent(email: Email) {
|
function renderEmailContent(email: Email) {
|
||||||
@ -523,9 +562,14 @@ export default function CourrierPage() {
|
|||||||
checkCredentials();
|
checkCredentials();
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
// Update the loadEmails function
|
// Update the loadEmails function to prevent redundant API calls
|
||||||
const loadEmails = async (isLoadMore = false) => {
|
const loadEmails = async (isLoadMore = false) => {
|
||||||
try {
|
try {
|
||||||
|
// Don't reload if we're already loading
|
||||||
|
if (isLoadingInitial || isLoadingMore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoadMore) {
|
if (isLoadMore) {
|
||||||
setIsLoadingMore(true);
|
setIsLoadingMore(true);
|
||||||
} else {
|
} else {
|
||||||
@ -533,7 +577,17 @@ export default function CourrierPage() {
|
|||||||
}
|
}
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const response = await fetch(`/api/courrier?folder=${encodeURIComponent(currentView)}&page=${page}&limit=${emailsPerPage}`);
|
// Create a cache key for this request
|
||||||
|
const cacheKey = `${currentView}-${page}-${emailsPerPage}`;
|
||||||
|
|
||||||
|
// Add timestamp parameter to force fresh data when needed
|
||||||
|
const timestamp = isLoadMore || page > 1 ? '' : `&_t=${Date.now()}`;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/courrier?folder=${encodeURIComponent(currentView)}&page=${page}&limit=${emailsPerPage}${timestamp}`,
|
||||||
|
{ cache: 'no-store' }
|
||||||
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to load emails');
|
throw new Error('Failed to load emails');
|
||||||
}
|
}
|
||||||
@ -548,13 +602,13 @@ export default function CourrierPage() {
|
|||||||
// Process emails keeping exact folder names and sort by date
|
// Process emails keeping exact folder names and sort by date
|
||||||
const processedEmails = (data.emails || [])
|
const processedEmails = (data.emails || [])
|
||||||
.map((email: any) => ({
|
.map((email: any) => ({
|
||||||
id: Number(email.id),
|
id: email.id,
|
||||||
accountId: 1,
|
accountId: 1,
|
||||||
from: email.from || '',
|
from: email.from || '',
|
||||||
fromName: email.fromName || email.from?.split('@')[0] || '',
|
fromName: email.fromName || email.from?.split('@')[0] || '',
|
||||||
to: email.to || '',
|
to: email.to || '',
|
||||||
subject: email.subject || '(No subject)',
|
subject: email.subject || '(No subject)',
|
||||||
body: email.body || '',
|
content: email.preview || '', // Store preview as initial content
|
||||||
date: email.date || new Date().toISOString(),
|
date: email.date || new Date().toISOString(),
|
||||||
read: email.read || false,
|
read: email.read || false,
|
||||||
starred: email.starred || false,
|
starred: email.starred || false,
|
||||||
@ -562,7 +616,7 @@ export default function CourrierPage() {
|
|||||||
cc: email.cc,
|
cc: email.cc,
|
||||||
bcc: email.bcc,
|
bcc: email.bcc,
|
||||||
flags: email.flags || [],
|
flags: email.flags || [],
|
||||||
raw: email.body || ''
|
hasAttachments: email.hasAttachments || false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Sort emails by date, ensuring most recent first
|
// Sort emails by date, ensuring most recent first
|
||||||
@ -572,6 +626,18 @@ export default function CourrierPage() {
|
|||||||
return dateB - dateA; // Most recent first
|
return dateB - dateA; // Most recent first
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Combine with existing emails when loading more
|
||||||
|
setEmails(prev => {
|
||||||
|
if (isLoadMore) {
|
||||||
|
// Filter out duplicates when appending
|
||||||
|
const existingIds = new Set(prev.map(email => email.id));
|
||||||
|
const uniqueNewEmails = sortedEmails.filter((email: Email) => !existingIds.has(email.id));
|
||||||
|
return [...prev, ...uniqueNewEmails];
|
||||||
|
} else {
|
||||||
|
return sortedEmails;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Only update unread count if we're in the Inbox folder
|
// Only update unread count if we're in the Inbox folder
|
||||||
if (currentView === 'INBOX') {
|
if (currentView === 'INBOX') {
|
||||||
const unreadInboxEmails = sortedEmails.filter(
|
const unreadInboxEmails = sortedEmails.filter(
|
||||||
@ -580,30 +646,17 @@ export default function CourrierPage() {
|
|||||||
setUnreadCount(unreadInboxEmails);
|
setUnreadCount(unreadInboxEmails);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoadMore) {
|
// Update pagination info
|
||||||
// When loading more, merge with existing emails and re-sort
|
setHasMore(data.hasMore);
|
||||||
setEmails(prev => {
|
|
||||||
const combined = [...prev, ...sortedEmails];
|
|
||||||
return combined.sort((a: Email, b: Email) => {
|
|
||||||
const dateA = new Date(a.date).getTime();
|
|
||||||
const dateB = new Date(b.date).getTime();
|
|
||||||
return dateB - dateA; // Most recent first
|
|
||||||
});
|
|
||||||
});
|
|
||||||
setPage(prev => prev + 1);
|
|
||||||
} else {
|
|
||||||
// For initial load or refresh, just use the sorted emails
|
|
||||||
setEmails(sortedEmails);
|
|
||||||
setPage(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update hasMore based on API response
|
setError(null);
|
||||||
setHasMore(data.hasMore || false);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to load emails');
|
console.error('Error loading emails:', err);
|
||||||
|
setError('Failed to load emails. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setIsLoadingMore(false);
|
setIsLoadingMore(false);
|
||||||
|
setIsLoadingInitial(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user