courrier redis login
This commit is contained in:
parent
04c0a089ac
commit
6c94b7f8f8
3
.env
3
.env
@ -77,3 +77,6 @@ IMAP_HOST=mail.infomaniak.com
|
|||||||
IMAP_PORT=993
|
IMAP_PORT=993
|
||||||
|
|
||||||
NEWS_API_URL="http://172.16.0.104:8000"
|
NEWS_API_URL="http://172.16.0.104:8000"
|
||||||
|
REDIS_HOST=localhost
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_PASSWORD=mySecretPassword
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
getUserEmailCredentials,
|
getUserEmailCredentials,
|
||||||
testEmailConnection
|
testEmailConnection
|
||||||
} from '@/lib/services/email-service';
|
} from '@/lib/services/email-service';
|
||||||
|
import { prefetchUserEmailData } from '@/lib/services/prefetch-service';
|
||||||
import { cacheEmailCredentials, invalidateUserEmailCache } from '@/lib/redis';
|
import { cacheEmailCredentials, invalidateUserEmailCache } from '@/lib/redis';
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
@ -56,6 +57,12 @@ export async function POST(request: Request) {
|
|||||||
port: parseInt(port)
|
port: parseInt(port)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Start prefetching email data in the background
|
||||||
|
// We don't await this to avoid blocking the response
|
||||||
|
prefetchUserEmailData(session.user.id).catch(err => {
|
||||||
|
console.error('Background prefetch error:', err);
|
||||||
|
});
|
||||||
|
|
||||||
return NextResponse.json({ success: true });
|
return NextResponse.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in login handler:', error);
|
console.error('Error in login handler:', error);
|
||||||
|
|||||||
64
app/api/courrier/session/route.ts
Normal file
64
app/api/courrier/session/route.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This endpoint is called when the app initializes to check if the user has email credentials
|
||||||
|
* and to start prefetching email data in the background if they do
|
||||||
|
*/
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
// Get server session to verify authentication
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
// Check if user is authenticated
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return NextResponse.json({
|
||||||
|
authenticated: false,
|
||||||
|
message: "Not authenticated"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = session.user.id;
|
||||||
|
|
||||||
|
// First, check Redis cache for credentials
|
||||||
|
let credentials = await getCachedEmailCredentials(userId);
|
||||||
|
|
||||||
|
// If not in cache, check database
|
||||||
|
if (!credentials) {
|
||||||
|
credentials = await getUserEmailCredentials(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no credentials found
|
||||||
|
if (!credentials) {
|
||||||
|
return NextResponse.json({
|
||||||
|
authenticated: true,
|
||||||
|
hasEmailCredentials: false,
|
||||||
|
message: "No email credentials found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start prefetching email data in the background
|
||||||
|
// We don't await this to avoid blocking the response
|
||||||
|
prefetchUserEmailData(userId).catch(err => {
|
||||||
|
console.error('Background prefetch error:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return session info without sensitive data
|
||||||
|
return NextResponse.json({
|
||||||
|
authenticated: true,
|
||||||
|
hasEmailCredentials: true,
|
||||||
|
email: credentials.email,
|
||||||
|
prefetchStarted: true
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking session:", error);
|
||||||
|
return NextResponse.json({
|
||||||
|
authenticated: false,
|
||||||
|
error: "Internal Server Error"
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
import {
|
import {
|
||||||
Mail, Loader2, AlertCircle,
|
Mail, Loader2, AlertCircle,
|
||||||
MoreVertical, Settings, Plus as PlusIcon, Trash2, Edit,
|
MoreVertical, Settings, Plus as PlusIcon, Trash2, Edit,
|
||||||
@ -37,6 +38,9 @@ import { DeleteConfirmDialog, LoginNeededAlert } from '@/components/email/EmailD
|
|||||||
// Import the custom hook
|
// Import the custom hook
|
||||||
import { useCourrier, EmailData } from '@/hooks/use-courrier';
|
import { useCourrier, EmailData } from '@/hooks/use-courrier';
|
||||||
|
|
||||||
|
// Import the prefetching function
|
||||||
|
import { prefetchFolderEmails } from '@/lib/services/prefetch-service';
|
||||||
|
|
||||||
// Simplified version for this component
|
// Simplified version for this component
|
||||||
function SimplifiedLoadingFix() {
|
function SimplifiedLoadingFix() {
|
||||||
// In production, don't render anything
|
// In production, don't render anything
|
||||||
@ -62,6 +66,7 @@ interface Account {
|
|||||||
|
|
||||||
export default function CourrierPage() {
|
export default function CourrierPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
// Get all the email functionality from the hook
|
// Get all the email functionality from the hook
|
||||||
const {
|
const {
|
||||||
@ -101,6 +106,7 @@ export default function CourrierPage() {
|
|||||||
const [currentView, setCurrentView] = useState('INBOX');
|
const [currentView, setCurrentView] = useState('INBOX');
|
||||||
const [unreadCount, setUnreadCount] = useState(0);
|
const [unreadCount, setUnreadCount] = useState(0);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [prefetchStarted, setPrefetchStarted] = useState(false);
|
||||||
|
|
||||||
// Mock accounts for the sidebar
|
// Mock accounts for the sidebar
|
||||||
const [accounts, setAccounts] = useState<Account[]>([
|
const [accounts, setAccounts] = useState<Account[]>([
|
||||||
@ -127,6 +133,29 @@ export default function CourrierPage() {
|
|||||||
setUnreadCount(unreadInInbox);
|
setUnreadCount(unreadInInbox);
|
||||||
}, [emails, currentFolder]);
|
}, [emails, currentFolder]);
|
||||||
|
|
||||||
|
// Initialize session and start prefetching
|
||||||
|
useEffect(() => {
|
||||||
|
const initSession = async () => {
|
||||||
|
try {
|
||||||
|
// Call the session API to check email credentials and start prefetching
|
||||||
|
const response = await fetch('/api/courrier/session');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.authenticated && data.hasEmailCredentials) {
|
||||||
|
console.log('Session initialized, prefetching started');
|
||||||
|
setPrefetchStarted(true);
|
||||||
|
} else if (data.authenticated && !data.hasEmailCredentials) {
|
||||||
|
// User is authenticated but doesn't have email credentials
|
||||||
|
setShowLoginNeeded(true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing session:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initSession();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Helper to get folder icons
|
// Helper to get folder icons
|
||||||
const getFolderIcon = (folder: string) => {
|
const getFolderIcon = (folder: string) => {
|
||||||
const folderLower = folder.toLowerCase();
|
const folderLower = folder.toLowerCase();
|
||||||
@ -216,10 +245,17 @@ export default function CourrierPage() {
|
|||||||
setShowComposeModal(true);
|
setShowComposeModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle mailbox change
|
// Handle mailbox change with prefetching
|
||||||
const handleMailboxChange = (folder: string) => {
|
const handleMailboxChange = (folder: string) => {
|
||||||
changeFolder(folder);
|
changeFolder(folder);
|
||||||
setCurrentView(folder);
|
setCurrentView(folder);
|
||||||
|
|
||||||
|
// Start prefetching additional pages for this folder
|
||||||
|
if (session?.user?.id && folder) {
|
||||||
|
prefetchFolderEmails(session.user.id, folder, 3).catch(err => {
|
||||||
|
console.error(`Error prefetching ${folder}:`, err);
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle sending email
|
// Handle sending email
|
||||||
|
|||||||
111
lib/services/prefetch-service.ts
Normal file
111
lib/services/prefetch-service.ts
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { getImapConnection, getEmails, getEmailContent } from './email-service';
|
||||||
|
import {
|
||||||
|
cacheEmailList,
|
||||||
|
cacheEmailContent,
|
||||||
|
cacheImapSession
|
||||||
|
} from '@/lib/redis';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch basic email data for faster initial loading
|
||||||
|
* This function should be called when a user logs in
|
||||||
|
*/
|
||||||
|
export async function prefetchUserEmailData(userId: string): Promise<void> {
|
||||||
|
console.log(`Starting email prefetch for user ${userId}`);
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Connect to IMAP server
|
||||||
|
const client = await getImapConnection(userId);
|
||||||
|
|
||||||
|
// 1. Prefetch mailbox list
|
||||||
|
const mailboxes = await client.list();
|
||||||
|
const mailboxPaths = mailboxes.map(mailbox => mailbox.path);
|
||||||
|
|
||||||
|
// Cache mailbox list in session data
|
||||||
|
await cacheImapSession(userId, {
|
||||||
|
lastActive: Date.now(),
|
||||||
|
mailboxes: mailboxPaths
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Prefetched ${mailboxPaths.length} folders for user ${userId}`);
|
||||||
|
|
||||||
|
// 2. Prefetch email lists for important folders
|
||||||
|
const importantFolders = [
|
||||||
|
'INBOX',
|
||||||
|
mailboxPaths.find(path => path.toLowerCase().includes('sent')) || 'Sent',
|
||||||
|
mailboxPaths.find(path => path.toLowerCase().includes('draft')) || 'Drafts'
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
// Fetch first page of each important folder
|
||||||
|
for (const folder of importantFolders) {
|
||||||
|
try {
|
||||||
|
console.log(`Prefetching emails for ${folder}`);
|
||||||
|
const emailList = await getEmails(userId, folder, 1, 20);
|
||||||
|
console.log(`Prefetched ${emailList.emails.length} emails for ${folder}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error prefetching emails for folder ${folder}:`, error);
|
||||||
|
// Continue with other folders even if one fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Prefetch content of recent unread emails in INBOX
|
||||||
|
try {
|
||||||
|
// Get the list again (it's already cached so this will be fast)
|
||||||
|
const inboxList = await getEmails(userId, 'INBOX', 1, 20);
|
||||||
|
|
||||||
|
// Prefetch content for up to 5 recent unread emails
|
||||||
|
const unreadEmails = inboxList.emails
|
||||||
|
.filter(email => !email.flags.seen)
|
||||||
|
.slice(0, 5);
|
||||||
|
|
||||||
|
if (unreadEmails.length > 0) {
|
||||||
|
console.log(`Prefetching content for ${unreadEmails.length} unread emails`);
|
||||||
|
|
||||||
|
// Fetch content in parallel for speed
|
||||||
|
await Promise.allSettled(
|
||||||
|
unreadEmails.map(email =>
|
||||||
|
getEmailContent(userId, email.id, 'INBOX')
|
||||||
|
.catch(err => console.error(`Error prefetching email ${email.id}:`, err))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Completed prefetching content for unread emails`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error prefetching unread email content:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const duration = (Date.now() - startTime) / 1000;
|
||||||
|
console.log(`Email prefetch completed for user ${userId} in ${duration.toFixed(2)}s`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during email prefetch:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch a specific folder's emails
|
||||||
|
* This can be used when the user navigates to a folder to preload more pages
|
||||||
|
*/
|
||||||
|
export async function prefetchFolderEmails(
|
||||||
|
userId: string,
|
||||||
|
folder: string,
|
||||||
|
pages: number = 3
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log(`Prefetching ${pages} pages of emails for folder ${folder}`);
|
||||||
|
|
||||||
|
// Fetch multiple pages in parallel
|
||||||
|
await Promise.allSettled(
|
||||||
|
Array.from({ length: pages }, (_, i) => i + 1).map(page =>
|
||||||
|
getEmails(userId, folder, page, 20)
|
||||||
|
.catch(err => console.error(`Error prefetching page ${page} of ${folder}:`, err))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Completed prefetching ${pages} pages of ${folder}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error prefetching folder ${folder}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
scripts/test-redis-env.js
Normal file
45
scripts/test-redis-env.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
const Redis = require('ioredis');
|
||||||
|
|
||||||
|
console.log('Redis configuration from environment:');
|
||||||
|
console.log('- Host:', process.env.REDIS_HOST);
|
||||||
|
console.log('- Port:', process.env.REDIS_PORT);
|
||||||
|
console.log('- Password:', process.env.REDIS_PASSWORD ? '******** (set)' : '(not set)');
|
||||||
|
|
||||||
|
const redis = new Redis({
|
||||||
|
host: process.env.REDIS_HOST,
|
||||||
|
port: process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined,
|
||||||
|
password: process.env.REDIS_PASSWORD,
|
||||||
|
maxRetriesPerRequest: 3,
|
||||||
|
retryStrategy: (times) => Math.min(times * 100, 3000)
|
||||||
|
});
|
||||||
|
|
||||||
|
redis.on('connect', () => {
|
||||||
|
console.log('✅ Connected to Redis successfully!');
|
||||||
|
|
||||||
|
// Test a simple operation
|
||||||
|
redis.set('test-key', 'Test value from environment test')
|
||||||
|
.then(() => redis.get('test-key'))
|
||||||
|
.then((value) => {
|
||||||
|
console.log('✅ Successfully set and retrieved a test key:', value);
|
||||||
|
redis.quit();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('❌ Error during Redis operations:', err);
|
||||||
|
redis.quit();
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
redis.on('error', (err) => {
|
||||||
|
console.error('❌ Redis connection error:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a timeout to avoid hanging indefinitely
|
||||||
|
setTimeout(() => {
|
||||||
|
console.error('❌ Connection timeout');
|
||||||
|
process.exit(1);
|
||||||
|
}, 5000);
|
||||||
Loading…
Reference in New Issue
Block a user