courrier multi account restore compose
This commit is contained in:
parent
0f9d84c6a9
commit
426c9d62ee
@ -40,7 +40,13 @@ export async function GET(request: Request) {
|
||||
// Try to get from Redis cache first, but only if it's not a search query
|
||||
if (!searchQuery) {
|
||||
const cacheKey = accountId ? `${session.user.id}:${accountId}:${folder}` : `${session.user.id}:${folder}`;
|
||||
const cachedEmails = await getCachedEmailList(session.user.id, folder, page, perPage);
|
||||
const cachedEmails = await getCachedEmailList(
|
||||
session.user.id,
|
||||
accountId || 'default',
|
||||
folder,
|
||||
page,
|
||||
perPage
|
||||
);
|
||||
if (cachedEmails) {
|
||||
console.log(`Using Redis cached emails for ${cacheKey}:${page}:${perPage}`);
|
||||
return NextResponse.json(cachedEmails);
|
||||
@ -85,12 +91,12 @@ export async function POST(request: Request) {
|
||||
|
||||
// Invalidate Redis cache for the folder
|
||||
if (folderName) {
|
||||
await invalidateFolderCache(session.user.id, folderName);
|
||||
await invalidateFolderCache(session.user.id, 'default', folderName);
|
||||
} else {
|
||||
// If no folder specified, invalidate all folders (using a wildcard pattern)
|
||||
const folders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk'];
|
||||
for (const folder of folders) {
|
||||
await invalidateFolderCache(session.user.id, folder);
|
||||
await invalidateFolderCache(session.user.id, 'default', folder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,33 +23,63 @@ const FOLDERS_CACHE_KEY = (userId: string, accountId: string) => `email:folders:
|
||||
*/
|
||||
export async function GET() {
|
||||
try {
|
||||
// Get session with detailed logging
|
||||
console.log('Attempting to get server session...');
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
if (!session) {
|
||||
console.error('No session found');
|
||||
return NextResponse.json({ error: 'No session found' }, { status: 401 });
|
||||
}
|
||||
|
||||
if (!session.user) {
|
||||
console.error('No user in session');
|
||||
return NextResponse.json({ error: 'No user in session' }, { status: 401 });
|
||||
}
|
||||
|
||||
if (!session.user.id) {
|
||||
console.error('No user ID in session');
|
||||
return NextResponse.json({ error: 'No user ID in session' }, { status: 401 });
|
||||
}
|
||||
|
||||
console.log('Session validated successfully:', {
|
||||
userId: session.user.id,
|
||||
email: session.user.email,
|
||||
name: session.user.name
|
||||
});
|
||||
|
||||
// Get Redis connection
|
||||
const redis = getRedisClient();
|
||||
if (!redis) {
|
||||
console.error('Redis connection failed');
|
||||
return NextResponse.json({ error: 'Redis connection failed' }, { status: 500 });
|
||||
}
|
||||
|
||||
// Get user with their accounts
|
||||
console.log('Fetching user with ID:', session.user.id);
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: session.user.id },
|
||||
include: { mailCredentials: true }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
console.error('User not found in database');
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Get all accounts for the user
|
||||
const accounts = (user.mailCredentials || []) as MailCredentials[];
|
||||
if (accounts.length === 0) {
|
||||
return NextResponse.json({ error: 'No email accounts found' }, { status: 404 });
|
||||
console.log('No email accounts found for user:', session.user.id);
|
||||
return NextResponse.json({
|
||||
authenticated: true,
|
||||
accounts: [],
|
||||
message: 'No email accounts found'
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Found ${accounts.length} accounts for user:`, accounts.map(a => a.email));
|
||||
|
||||
// Fetch folders for each account
|
||||
const accountsWithFolders = await Promise.all(
|
||||
accounts.map(async (account: MailCredentials) => {
|
||||
@ -57,6 +87,7 @@ export async function GET() {
|
||||
// Try to get folders from Redis cache first
|
||||
const cachedFolders = await redis.get(cacheKey);
|
||||
if (cachedFolders) {
|
||||
console.log(`Using cached folders for account ${account.email}`);
|
||||
return {
|
||||
...account,
|
||||
folders: JSON.parse(cachedFolders)
|
||||
@ -64,8 +95,10 @@ export async function GET() {
|
||||
}
|
||||
|
||||
// If not in cache, fetch from IMAP
|
||||
console.log(`Fetching folders from IMAP for account ${account.email}`);
|
||||
const client = await getImapConnection(user.id, account.id);
|
||||
if (!client) {
|
||||
console.warn(`Failed to get IMAP connection for account ${account.email}`);
|
||||
return {
|
||||
...account,
|
||||
folders: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']
|
||||
@ -74,6 +107,7 @@ export async function GET() {
|
||||
|
||||
try {
|
||||
const folders = await getMailboxes(client);
|
||||
console.log(`Fetched ${folders.length} folders for account ${account.email}`);
|
||||
// Cache the folders in Redis
|
||||
await redis.set(
|
||||
cacheKey,
|
||||
@ -96,12 +130,13 @@ export async function GET() {
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
authenticated: true,
|
||||
accounts: accountsWithFolders
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in session route:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ error: 'Internal server error', details: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
@ -206,13 +206,13 @@ export default function CourrierPage() {
|
||||
useEffect(() => {
|
||||
// Flag to prevent multiple initialization attempts
|
||||
let isMounted = true;
|
||||
let initAttempted = false;
|
||||
|
||||
let retryCount = 0;
|
||||
const MAX_RETRIES = 3;
|
||||
const RETRY_DELAY = 1000; // 1 second
|
||||
|
||||
const initSession = async () => {
|
||||
if (initAttempted) return;
|
||||
initAttempted = true;
|
||||
|
||||
try {
|
||||
if (!isMounted) return;
|
||||
setLoading(true);
|
||||
|
||||
// First check if Redis is ready before making API calls
|
||||
@ -224,6 +224,24 @@ export default function CourrierPage() {
|
||||
|
||||
// Call the session API to check email credentials and start prefetching
|
||||
const response = await fetch('/api/courrier/session');
|
||||
|
||||
// Handle 401 Unauthorized with retry logic
|
||||
if (response.status === 401) {
|
||||
if (retryCount < MAX_RETRIES) {
|
||||
retryCount++;
|
||||
console.log(`Session request failed (attempt ${retryCount}/${MAX_RETRIES}), retrying in ${RETRY_DELAY}ms...`);
|
||||
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
|
||||
return initSession();
|
||||
} else {
|
||||
console.error('Max retries reached for session request');
|
||||
throw new Error('Failed to authenticate session after multiple attempts');
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Session request failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Log the raw API response to inspect structure
|
||||
@ -312,7 +330,7 @@ export default function CourrierPage() {
|
||||
folders: accountFolders
|
||||
};
|
||||
console.log(`[DEBUG] Updated loading account to real account: ${account.email} with ID ${account.id}`);
|
||||
} else {
|
||||
} else if (index > 0) {
|
||||
// Add additional accounts as new entries
|
||||
updatedAccounts.push({
|
||||
id: account.id || `account-${index}`,
|
||||
@ -321,28 +339,31 @@ export default function CourrierPage() {
|
||||
color: account.color || 'bg-blue-500',
|
||||
folders: accountFolders
|
||||
});
|
||||
console.log(`[DEBUG] Added additional account: ${account.email} with ID ${account.id}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Fallback if accounts array is empty for some reason
|
||||
updatedAccounts.push({ id: 'all-accounts', name: 'All', email: '', color: 'bg-gray-500' });
|
||||
|
||||
const firstAccount = data.allAccounts[0];
|
||||
const accountFolders = (firstAccount.folders && Array.isArray(firstAccount.folders))
|
||||
? firstAccount.folders
|
||||
: (data.mailboxes && Array.isArray(data.mailboxes))
|
||||
? data.mailboxes
|
||||
: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk'];
|
||||
|
||||
updatedAccounts.push({
|
||||
id: firstAccount.id,
|
||||
name: firstAccount.display_name || firstAccount.email,
|
||||
email: firstAccount.email,
|
||||
color: firstAccount.color || 'bg-blue-500',
|
||||
folders: accountFolders
|
||||
// Add all accounts from the API response
|
||||
data.allAccounts.forEach((account: any) => {
|
||||
const accountFolders = (account.folders && Array.isArray(account.folders))
|
||||
? account.folders
|
||||
: (data.mailboxes && Array.isArray(data.mailboxes))
|
||||
? data.mailboxes
|
||||
: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk'];
|
||||
|
||||
updatedAccounts.push({
|
||||
id: account.id,
|
||||
name: account.display_name || account.email,
|
||||
email: account.email,
|
||||
color: account.color || 'bg-blue-500',
|
||||
folders: accountFolders
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if (data.email) {
|
||||
} else {
|
||||
// Fallback to single account if allAccounts is not available
|
||||
console.log(`[DEBUG] Fallback to single account: ${data.email}`);
|
||||
|
||||
|
||||
@ -80,6 +80,12 @@ export const useCourrier = () => {
|
||||
const loadEmails = useCallback(async (isLoadMore = false, accountId?: string) => {
|
||||
if (!session?.user?.id) return;
|
||||
|
||||
// Skip loading if accountId is 'loading-account'
|
||||
if (accountId === 'loading-account') {
|
||||
console.log('Skipping email load for loading account');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
@ -98,13 +104,20 @@ export const useCourrier = () => {
|
||||
queryParams.set('search', searchQuery);
|
||||
}
|
||||
|
||||
// Add accountId if provided
|
||||
if (accountId) {
|
||||
// Add accountId if provided and not 'loading-account'
|
||||
if (accountId && accountId !== 'all-accounts' && accountId !== 'loading-account') {
|
||||
queryParams.set('accountId', accountId);
|
||||
}
|
||||
|
||||
// First try Redis cache with low timeout
|
||||
const cachedEmails = await getCachedEmailsWithTimeout(session.user.id, currentFolder, currentRequestPage, perPage, 100);
|
||||
const cachedEmails = await getCachedEmailsWithTimeout(
|
||||
session.user.id,
|
||||
currentFolder,
|
||||
currentRequestPage,
|
||||
perPage,
|
||||
100,
|
||||
accountId && accountId !== 'all-accounts' && accountId !== 'loading-account' ? accountId : undefined
|
||||
);
|
||||
if (cachedEmails) {
|
||||
// Ensure cached data has emails array property
|
||||
if (Array.isArray(cachedEmails.emails)) {
|
||||
|
||||
@ -59,38 +59,27 @@ export async function getCachedEmailsWithTimeout(
|
||||
folder: string,
|
||||
page: number,
|
||||
perPage: number,
|
||||
timeoutMs: number = 100
|
||||
timeoutMs: number = 100,
|
||||
accountId?: string
|
||||
): Promise<any | null> {
|
||||
// Skip cache if accountId is 'loading-account'
|
||||
if (accountId === 'loading-account') {
|
||||
console.log(`Skipping cache for loading account`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
console.log(`Cache access timeout for ${userId}:${folder}:${page}:${perPage}`);
|
||||
console.log(`Cache access timeout for ${userId}:${folder}:${page}:${perPage}${accountId ? ` for account ${accountId}` : ''}`);
|
||||
resolve(null);
|
||||
}, timeoutMs);
|
||||
|
||||
getCachedEmailList(userId, folder, page, perPage)
|
||||
getCachedEmailList(userId, accountId || 'default', folder, page, perPage)
|
||||
.then(result => {
|
||||
clearTimeout(timeoutId);
|
||||
if (result) {
|
||||
console.log(`Using cached data for ${userId}:${folder}:${page}:${perPage}`);
|
||||
|
||||
// Validate and normalize the data structure
|
||||
if (typeof result === 'object') {
|
||||
// Make sure we have an emails array
|
||||
if (!result.emails && Array.isArray(result)) {
|
||||
// If result is an array, convert to proper structure
|
||||
resolve({ emails: result });
|
||||
} else if (!result.emails) {
|
||||
// If no emails property, add empty array
|
||||
resolve({ ...result, emails: [] });
|
||||
} else {
|
||||
// Normal case, return as is
|
||||
resolve(result);
|
||||
}
|
||||
} else {
|
||||
// Invalid data, return null
|
||||
console.warn('Invalid cached data format:', result);
|
||||
resolve(null);
|
||||
}
|
||||
console.log(`Using cached data for ${userId}:${folder}:${page}:${perPage}${accountId ? ` for account ${accountId}` : ''}`);
|
||||
resolve(result);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user