courrier preview
This commit is contained in:
parent
96e16ea80d
commit
2eaca502b2
@ -39,9 +39,10 @@ export async function GET(request: Request) {
|
||||
const folder = searchParams.get("folder") || "INBOX";
|
||||
const searchQuery = searchParams.get("search") || "";
|
||||
const accountId = searchParams.get("accountId") || "";
|
||||
const checkOnly = searchParams.get("checkOnly") === "true";
|
||||
|
||||
// CRITICAL FIX: Log exact parameters received by the API
|
||||
console.log(`[API] Received request with: folder=${folder}, accountId=${accountId}, page=${page}`);
|
||||
console.log(`[API] Received request with: folder=${folder}, accountId=${accountId}, page=${page}, checkOnly=${checkOnly}`);
|
||||
|
||||
// CRITICAL FIX: More robust parameter normalization
|
||||
// 1. If folder contains an account prefix, extract it but DO NOT use it
|
||||
@ -61,8 +62,8 @@ export async function GET(request: Request) {
|
||||
// CRITICAL FIX: Enhanced logging for parameter resolution
|
||||
console.log(`[API] Using normalized parameters: folder=${normalizedFolder}, accountId=${effectiveAccountId}`);
|
||||
|
||||
// Try to get from Redis cache first, but only if it's not a search query
|
||||
if (!searchQuery) {
|
||||
// Try to get from Redis cache first, but only if it's not a search query and not checkOnly
|
||||
if (!searchQuery && !checkOnly) {
|
||||
// CRITICAL FIX: Use consistent cache key format with the correct account ID
|
||||
console.log(`[API] Checking Redis cache for ${session.user.id}:${effectiveAccountId}:${normalizedFolder}:${page}:${perPage}`);
|
||||
const cachedEmails = await getCachedEmailList(
|
||||
@ -87,13 +88,14 @@ export async function GET(request: Request) {
|
||||
normalizedFolder, // folder (without prefix)
|
||||
page, // page
|
||||
perPage, // perPage
|
||||
effectiveAccountId // accountId
|
||||
effectiveAccountId, // accountId
|
||||
checkOnly // checkOnly flag - only check for new emails without loading full content
|
||||
);
|
||||
|
||||
// CRITICAL FIX: Log when emails are returned from IMAP
|
||||
console.log(`[API] Successfully fetched ${emailsResult.emails.length} emails from IMAP for account ${effectiveAccountId}`);
|
||||
|
||||
// The result is already cached in the getEmails function
|
||||
// The result is already cached in the getEmails function (if not checkOnly)
|
||||
return NextResponse.json(emailsResult);
|
||||
} catch (error: any) {
|
||||
console.error("[API] Error fetching emails:", error);
|
||||
|
||||
@ -964,6 +964,68 @@ export const useEmailState = () => {
|
||||
}
|
||||
}, [dispatch, session?.user, state.isLoadingUnreadCounts, logEmailOp]);
|
||||
|
||||
// Function to check for new emails without disrupting the user
|
||||
const checkForNewEmails = useCallback(async () => {
|
||||
if (!session?.user?.id || !state.selectedAccount) return;
|
||||
|
||||
// Don't check if already loading emails
|
||||
if (state.isLoading) return;
|
||||
|
||||
try {
|
||||
// Get normalized parameters using helper function
|
||||
const accountId = state.selectedAccount ? state.selectedAccount.id : undefined;
|
||||
const { normalizedFolder, effectiveAccountId, prefixedFolder } =
|
||||
normalizeFolderAndAccount(state.currentFolder, accountId);
|
||||
|
||||
logEmailOp('CHECK_NEW_EMAILS', `Checking for new emails in ${prefixedFolder}`);
|
||||
|
||||
// Quietly check for new emails with a special parameter
|
||||
const queryParams = new URLSearchParams({
|
||||
folder: normalizedFolder,
|
||||
page: '1',
|
||||
perPage: '10', // Just get the top 10 to check if any are new
|
||||
accountId: effectiveAccountId,
|
||||
checkOnly: 'true' // Special parameter to indicate this is just a check
|
||||
});
|
||||
|
||||
const response = await fetch(`/api/courrier/emails?${queryParams.toString()}`, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
cache: 'no-cache'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to check for new emails: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// If we have emails and the newest one is different from our current newest
|
||||
if (data.emails && data.emails.length > 0 && state.emails.length > 0) {
|
||||
const newestEmailId = data.emails[0].id;
|
||||
const currentNewestEmailId = state.emails[0].id;
|
||||
|
||||
if (newestEmailId !== currentNewestEmailId) {
|
||||
logEmailOp('NEW_EMAILS', `Found new emails, newest ID: ${newestEmailId}`);
|
||||
|
||||
// Show a toast notification
|
||||
toast({
|
||||
title: "New emails",
|
||||
description: "You have new emails in your inbox",
|
||||
duration: 5000
|
||||
});
|
||||
|
||||
// Refresh the current view to show the new emails
|
||||
loadEmails(state.page, state.perPage, false);
|
||||
} else {
|
||||
logEmailOp('CHECK_NEW_EMAILS', 'No new emails found');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking for new emails:', error);
|
||||
}
|
||||
}, [session?.user?.id, state.selectedAccount, state.currentFolder, state.isLoading, state.emails, state.page, state.perPage, toast, loadEmails, logEmailOp]);
|
||||
|
||||
// Calculate and update unread counts
|
||||
const updateUnreadCounts = useCallback(() => {
|
||||
// Skip if no emails or accounts
|
||||
@ -1023,6 +1085,23 @@ export const useEmailState = () => {
|
||||
// Deliberately exclude unreadCountMap to prevent infinite loops
|
||||
}, [state.emails, updateUnreadCounts]);
|
||||
|
||||
// Set up periodic check for new emails
|
||||
useEffect(() => {
|
||||
if (!state.emails || state.emails.length === 0) return;
|
||||
|
||||
// Set up a periodic check for new emails at the same interval as unread counts
|
||||
const checkNewEmailsId = setInterval(() => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
checkForNewEmails();
|
||||
}
|
||||
}, 60000); // 1 minute - same as unread count refresh
|
||||
|
||||
// Cleanup interval on unmount or state change
|
||||
return () => {
|
||||
clearInterval(checkNewEmailsId);
|
||||
};
|
||||
}, [state.emails, checkForNewEmails]);
|
||||
|
||||
// Tracking when an email is viewed to optimize unread count refreshes
|
||||
const lastViewedEmailRef = useRef<number | null>(null);
|
||||
const fetchFailuresRef = useRef<number>(0);
|
||||
@ -1076,6 +1155,7 @@ export const useEmailState = () => {
|
||||
selectAccount,
|
||||
handleLoadMore,
|
||||
fetchUnreadCounts,
|
||||
viewEmail
|
||||
viewEmail,
|
||||
checkForNewEmails
|
||||
};
|
||||
};
|
||||
@ -28,6 +28,7 @@ export interface EmailListResult {
|
||||
totalPages: number;
|
||||
folder: string;
|
||||
mailboxes: string[];
|
||||
newestEmailId: number;
|
||||
}
|
||||
|
||||
// Connection pool to reuse IMAP clients
|
||||
@ -482,10 +483,11 @@ export async function getEmails(
|
||||
folder: string,
|
||||
page: number = 1,
|
||||
perPage: number = 20,
|
||||
accountId?: string
|
||||
accountId?: string,
|
||||
checkOnly: boolean = false
|
||||
): Promise<EmailListResult> {
|
||||
// Normalize folder name and handle account ID
|
||||
console.log(`[getEmails] Processing request for folder: ${folder}, normalized to ${folder}, account: ${accountId || 'default'}`);
|
||||
console.log(`[getEmails] Processing request for folder: ${folder}, normalized to ${folder}, account: ${accountId || 'default'}, checkOnly: ${checkOnly}`);
|
||||
|
||||
try {
|
||||
// The getImapConnection function already handles 'default' accountId by finding the first available account
|
||||
@ -517,21 +519,57 @@ export async function getEmails(
|
||||
perPage,
|
||||
totalPages: 0,
|
||||
folder,
|
||||
mailboxes
|
||||
mailboxes,
|
||||
newestEmailId: 0
|
||||
};
|
||||
|
||||
await cacheEmailList(
|
||||
userId,
|
||||
resolvedAccountId, // Use the guaranteed string account ID
|
||||
folder,
|
||||
page,
|
||||
perPage,
|
||||
emptyResult
|
||||
);
|
||||
// Only cache if not in checkOnly mode
|
||||
if (!checkOnly) {
|
||||
await cacheEmailList(
|
||||
userId,
|
||||
resolvedAccountId, // Use the guaranteed string account ID
|
||||
folder,
|
||||
page,
|
||||
perPage,
|
||||
emptyResult
|
||||
);
|
||||
}
|
||||
|
||||
return emptyResult;
|
||||
}
|
||||
|
||||
// If checkOnly mode, we just fetch the most recent email's ID to compare
|
||||
if (checkOnly) {
|
||||
console.log(`[getEmails] checkOnly mode: fetching only the most recent email ID`);
|
||||
|
||||
// Get the most recent message (highest sequence number)
|
||||
const lastMessageSequence = totalEmails.toString();
|
||||
console.log(`[getEmails] Fetching latest message with sequence: ${lastMessageSequence}`);
|
||||
|
||||
const messages = await client.fetch(lastMessageSequence, {
|
||||
uid: true
|
||||
});
|
||||
|
||||
let newestEmailId = 0;
|
||||
for await (const message of messages) {
|
||||
newestEmailId = message.uid;
|
||||
}
|
||||
|
||||
console.log(`[getEmails] Latest email UID: ${newestEmailId}`);
|
||||
|
||||
// Return minimal result with just the newest email ID
|
||||
return {
|
||||
emails: [],
|
||||
totalEmails,
|
||||
page,
|
||||
perPage,
|
||||
totalPages,
|
||||
folder,
|
||||
mailboxes,
|
||||
newestEmailId
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate message range for pagination
|
||||
const start = Math.max(1, totalEmails - (page * perPage) + 1);
|
||||
const end = Math.max(1, totalEmails - ((page - 1) * perPage));
|
||||
@ -541,11 +579,19 @@ export async function getEmails(
|
||||
const messages = await client.fetch(`${start}:${end}`, {
|
||||
envelope: true,
|
||||
flags: true,
|
||||
bodyStructure: true
|
||||
bodyStructure: true,
|
||||
uid: true
|
||||
});
|
||||
|
||||
const emails: EmailMessage[] = [];
|
||||
let newestEmailId = 0;
|
||||
|
||||
for await (const message of messages) {
|
||||
// Track the newest email ID (highest UID)
|
||||
if (message.uid > newestEmailId) {
|
||||
newestEmailId = message.uid;
|
||||
}
|
||||
|
||||
const email: EmailMessage = {
|
||||
id: message.uid.toString(),
|
||||
from: message.envelope.from?.map(addr => ({
|
||||
@ -578,33 +624,31 @@ export async function getEmails(
|
||||
emails.push(email);
|
||||
}
|
||||
|
||||
// Cache the result with the effective account ID
|
||||
await cacheEmailList(
|
||||
userId,
|
||||
resolvedAccountId, // Use the guaranteed string account ID
|
||||
folder,
|
||||
page,
|
||||
perPage,
|
||||
{
|
||||
emails,
|
||||
totalEmails: totalEmails,
|
||||
page,
|
||||
perPage,
|
||||
totalPages: Math.ceil(totalEmails / perPage),
|
||||
folder: folder,
|
||||
mailboxes: mailboxes
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
// Prepare the result
|
||||
const result = {
|
||||
emails,
|
||||
totalEmails: totalEmails,
|
||||
totalEmails,
|
||||
page,
|
||||
perPage,
|
||||
totalPages: Math.ceil(totalEmails / perPage),
|
||||
folder: folder,
|
||||
mailboxes: mailboxes
|
||||
folder,
|
||||
mailboxes,
|
||||
newestEmailId
|
||||
};
|
||||
|
||||
// Cache the result with the effective account ID (only if not in checkOnly mode)
|
||||
if (!checkOnly) {
|
||||
await cacheEmailList(
|
||||
userId,
|
||||
resolvedAccountId, // Use the guaranteed string account ID
|
||||
folder,
|
||||
page,
|
||||
perPage,
|
||||
result
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error fetching emails:', error);
|
||||
throw error;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user