courrier redis
This commit is contained in:
parent
3f415be882
commit
5ea4d457fe
@ -3,7 +3,7 @@ import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
||||
import { getUserEmailCredentials } from '@/lib/services/email-service';
|
||||
import { prefetchUserEmailData } from '@/lib/services/prefetch-service';
|
||||
import { getCachedEmailCredentials } from '@/lib/redis';
|
||||
import { getCachedEmailCredentials, getRedisStatus, warmupRedisCache } from '@/lib/redis';
|
||||
|
||||
/**
|
||||
* This endpoint is called when the app initializes to check if the user has email credentials
|
||||
@ -11,6 +11,12 @@ import { getCachedEmailCredentials } from '@/lib/redis';
|
||||
*/
|
||||
export async function GET() {
|
||||
try {
|
||||
// Warm up Redis connection
|
||||
await warmupRedisCache();
|
||||
|
||||
// Get Redis status to include in response
|
||||
const redisStatus = await getRedisStatus();
|
||||
|
||||
// Get server session to verify authentication
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
@ -18,6 +24,7 @@ export async function GET() {
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({
|
||||
authenticated: false,
|
||||
redisStatus,
|
||||
message: "Not authenticated"
|
||||
});
|
||||
}
|
||||
@ -26,10 +33,12 @@ export async function GET() {
|
||||
|
||||
// First, check Redis cache for credentials
|
||||
let credentials = await getCachedEmailCredentials(userId);
|
||||
let credentialsSource = 'cache';
|
||||
|
||||
// If not in cache, check database
|
||||
if (!credentials) {
|
||||
credentials = await getUserEmailCredentials(userId);
|
||||
credentialsSource = 'database';
|
||||
}
|
||||
|
||||
// If no credentials found
|
||||
@ -37,6 +46,7 @@ export async function GET() {
|
||||
return NextResponse.json({
|
||||
authenticated: true,
|
||||
hasEmailCredentials: false,
|
||||
redisStatus,
|
||||
message: "No email credentials found"
|
||||
});
|
||||
}
|
||||
@ -52,7 +62,9 @@ export async function GET() {
|
||||
authenticated: true,
|
||||
hasEmailCredentials: true,
|
||||
email: credentials.email,
|
||||
prefetchStarted: true
|
||||
redisStatus,
|
||||
prefetchStarted: true,
|
||||
credentialsSource
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error checking session:", error);
|
||||
|
||||
@ -41,6 +41,9 @@ import { useCourrier, EmailData } from '@/hooks/use-courrier';
|
||||
// Import the prefetching function
|
||||
import { prefetchFolderEmails } from '@/lib/services/prefetch-service';
|
||||
|
||||
// Import the RedisCacheStatus component
|
||||
import { RedisCacheStatus } from '@/components/debug/RedisCacheStatus';
|
||||
|
||||
// Simplified version for this component
|
||||
function SimplifiedLoadingFix() {
|
||||
// In production, don't render anything
|
||||
@ -70,7 +73,7 @@ export default function CourrierPage() {
|
||||
|
||||
// Get all the email functionality from the hook
|
||||
const {
|
||||
emails,
|
||||
emails = [],
|
||||
selectedEmail,
|
||||
selectedEmailIds,
|
||||
currentFolder,
|
||||
@ -130,21 +133,34 @@ export default function CourrierPage() {
|
||||
// Calculate unread count (this would be replaced with actual data in production)
|
||||
useEffect(() => {
|
||||
// Example: counting unread emails in the inbox
|
||||
const unreadInInbox = emails.filter(email => !email.read && currentFolder === 'INBOX').length;
|
||||
const unreadInInbox = (emails || []).filter(email => !email.read && currentFolder === 'INBOX').length;
|
||||
setUnreadCount(unreadInInbox);
|
||||
}, [emails, currentFolder]);
|
||||
|
||||
// Initialize session and start prefetching
|
||||
useEffect(() => {
|
||||
// Flag to prevent multiple initialization attempts
|
||||
let isMounted = true;
|
||||
|
||||
const initSession = async () => {
|
||||
try {
|
||||
// First check if Redis is ready before making API calls
|
||||
const redisStatus = await fetch('/api/redis/status').then(res => res.json()).catch(() => null);
|
||||
|
||||
// Call the session API to check email credentials and start prefetching
|
||||
const response = await fetch('/api/courrier/session');
|
||||
const data = await response.json();
|
||||
|
||||
if (!isMounted) return;
|
||||
|
||||
if (data.authenticated && data.hasEmailCredentials) {
|
||||
console.log('Session initialized, prefetching started');
|
||||
setPrefetchStarted(true);
|
||||
|
||||
// Preload first page of emails for faster initial rendering
|
||||
if (session?.user?.id) {
|
||||
loadEmails();
|
||||
}
|
||||
} else if (data.authenticated && !data.hasEmailCredentials) {
|
||||
// User is authenticated but doesn't have email credentials
|
||||
setShowLoginNeeded(true);
|
||||
@ -155,7 +171,11 @@ export default function CourrierPage() {
|
||||
};
|
||||
|
||||
initSession();
|
||||
}, []);
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, [session?.user?.id, loadEmails]);
|
||||
|
||||
// Helper to get folder icons
|
||||
const getFolderIcon = (folder: string) => {
|
||||
@ -290,6 +310,7 @@ export default function CourrierPage() {
|
||||
return (
|
||||
<>
|
||||
<SimplifiedLoadingFix />
|
||||
<RedisCacheStatus />
|
||||
|
||||
{/* Main layout */}
|
||||
<main className="w-full h-screen bg-black">
|
||||
|
||||
49
components/debug/RedisCacheStatus.tsx
Normal file
49
components/debug/RedisCacheStatus.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* Debug component to show Redis connection status
|
||||
* Only visible in development mode
|
||||
*/
|
||||
export function RedisCacheStatus() {
|
||||
const [status, setStatus] = useState<'connected' | 'error' | 'loading'>('loading');
|
||||
const [lastCheck, setLastCheck] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
async function checkRedis() {
|
||||
try {
|
||||
setStatus('loading');
|
||||
const response = await fetch('/api/redis/status');
|
||||
const data = await response.json();
|
||||
setStatus(data.status);
|
||||
setLastCheck(new Date().toLocaleTimeString());
|
||||
} catch (e) {
|
||||
setStatus('error');
|
||||
setLastCheck(new Date().toLocaleTimeString());
|
||||
}
|
||||
}
|
||||
|
||||
checkRedis();
|
||||
const interval = setInterval(checkRedis, 30000); // Check every 30 seconds
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
// Only show in development mode
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-4 left-4 text-xs bg-gray-800/80 text-white p-2 rounded shadow-md z-50">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2 h-2 rounded-full ${
|
||||
status === 'connected' ? 'bg-green-500' :
|
||||
status === 'loading' ? 'bg-yellow-500' : 'bg-red-500'
|
||||
}`}></div>
|
||||
<span>Redis: {status}</span>
|
||||
<span className="opacity-60">({lastCheck})</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -94,7 +94,16 @@ export const useCourrier = () => {
|
||||
// First try Redis cache with low timeout
|
||||
const cachedEmails = await getCachedEmailsWithTimeout(session.user.id, currentFolder, page, perPage, 100);
|
||||
if (cachedEmails) {
|
||||
setEmails(cachedEmails);
|
||||
// Ensure cached data has emails array property
|
||||
if (Array.isArray(cachedEmails.emails)) {
|
||||
setEmails(prevEmails => isLoadMore ? [...prevEmails, ...cachedEmails.emails] : cachedEmails.emails);
|
||||
} else if (Array.isArray(cachedEmails)) {
|
||||
// Direct array response
|
||||
setEmails(prevEmails => isLoadMore ? [...prevEmails, ...cachedEmails] : cachedEmails);
|
||||
} else {
|
||||
console.warn('Invalid cache format:', cachedEmails);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
// Still refresh in background for fresh data
|
||||
@ -129,7 +138,8 @@ export const useCourrier = () => {
|
||||
if (isLoadMore) {
|
||||
setEmails(prev => [...prev, ...data.emails]);
|
||||
} else {
|
||||
setEmails(data.emails);
|
||||
// Ensure we always set an array even if API returns invalid data
|
||||
setEmails(Array.isArray(data.emails) ? data.emails : []);
|
||||
}
|
||||
|
||||
setTotalEmails(data.totalEmails);
|
||||
@ -147,6 +157,8 @@ export const useCourrier = () => {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading emails:', err);
|
||||
// Set emails to empty array on error to prevent runtime issues
|
||||
setEmails([]);
|
||||
setError(err instanceof Error ? err.message : 'Failed to load emails');
|
||||
toast({
|
||||
variant: "destructive",
|
||||
|
||||
39
lib/redis.ts
39
lib/redis.ts
@ -332,6 +332,45 @@ export async function invalidateEmailContentCache(
|
||||
await redis.del(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warm up Redis connection to avoid cold starts
|
||||
*/
|
||||
export async function warmupRedisCache(): Promise<boolean> {
|
||||
try {
|
||||
// Ping Redis to establish connection early
|
||||
const redis = getRedisClient();
|
||||
await redis.ping();
|
||||
console.log('Redis connection warmed up');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error warming up Redis:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Redis connection status
|
||||
*/
|
||||
export async function getRedisStatus(): Promise<{
|
||||
status: 'connected' | 'error';
|
||||
ping?: string;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const redis = getRedisClient();
|
||||
const pong = await redis.ping();
|
||||
return {
|
||||
status: 'connected',
|
||||
ping: pong
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'error',
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate all user email caches (email lists and content)
|
||||
*/
|
||||
|
||||
@ -6,7 +6,8 @@ import {
|
||||
cacheEmailContent,
|
||||
cacheImapSession,
|
||||
getCachedEmailList,
|
||||
getRedisClient
|
||||
getRedisClient,
|
||||
warmupRedisCache
|
||||
} from '@/lib/redis';
|
||||
|
||||
/**
|
||||
@ -31,7 +32,25 @@ export async function getCachedEmailsWithTimeout(
|
||||
clearTimeout(timeoutId);
|
||||
if (result) {
|
||||
console.log(`Using cached data for ${userId}:${folder}:${page}:${perPage}`);
|
||||
resolve(result);
|
||||
|
||||
// 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);
|
||||
}
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
@ -167,20 +186,4 @@ export async function prefetchFolderEmails(
|
||||
} catch (error) {
|
||||
console.error(`Error prefetching folder ${folder}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Warm up Redis connection to avoid cold starts
|
||||
*/
|
||||
export async function warmupRedisCache(): Promise<boolean> {
|
||||
try {
|
||||
// Ping Redis to establish connection early
|
||||
const redis = getRedisClient();
|
||||
await redis.ping();
|
||||
console.log('Redis connection warmed up');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error warming up Redis:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user