courrier multi account restore compose

This commit is contained in:
alma 2025-04-28 14:21:41 +02:00
parent 9f5617b424
commit 093125df23
3 changed files with 164 additions and 77 deletions

View File

@ -7,7 +7,12 @@ import {
testEmailConnection
} from '@/lib/services/email-service';
import { prefetchUserEmailData } from '@/lib/services/prefetch-service';
import { cacheEmailCredentials, invalidateUserEmailCache } from '@/lib/redis';
import {
cacheEmailCredentials,
invalidateUserEmailCache,
getCachedEmailCredentials
} from '@/lib/redis';
import { prisma } from '@/lib/prisma';
export async function POST(request: Request) {
try {
@ -49,13 +54,18 @@ export async function POST(request: Request) {
// Invalidate all cached data for this user as they are changing their credentials
await invalidateUserEmailCache(session.user.id);
// Save credentials in the database and Redis
await saveUserEmailCredentials(session.user.id, email, {
// Create credentials object with required fields
const credentials = {
email,
password,
host,
port: parseInt(port)
});
port: parseInt(port),
secure: true // Default to secure connection
};
// Save credentials in the database and Redis
// Use email as the accountId since it's unique per user
await saveUserEmailCredentials(session.user.id, email, credentials);
// Start prefetching email data in the background
// We don't await this to avoid blocking the response
@ -67,10 +77,7 @@ export async function POST(request: Request) {
} catch (error) {
console.error('Error in login handler:', error);
return NextResponse.json(
{
error: 'An unexpected error occurred',
details: error instanceof Error ? error.message : 'Unknown error'
},
{ error: 'An unexpected error occurred' },
{ status: 500 }
);
}
@ -78,7 +85,6 @@ export async function POST(request: Request) {
export async function GET() {
try {
// Authenticate user
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
return NextResponse.json(
@ -87,8 +93,26 @@ export async function GET() {
);
}
// Get user credentials from database
const credentials = await getUserEmailCredentials(session.user.id);
// First try to get from Redis cache
let credentials = await getCachedEmailCredentials(session.user.id, 'default');
// If not in cache, get from database
if (!credentials) {
credentials = await prisma.mailCredentials.findUnique({
where: {
userId: session.user.id
},
select: {
email: true,
host: true,
port: true
}
});
} else {
// Remove password from response
const { password, ...safeCredentials } = credentials;
credentials = safeCredentials;
}
if (!credentials) {
return NextResponse.json(
@ -97,14 +121,8 @@ export async function GET() {
);
}
// Return credentials without the password
return NextResponse.json({
email: credentials.email,
host: credentials.host,
port: credentials.port
});
return NextResponse.json(credentials);
} catch (error) {
console.error('Error fetching credentials:', error);
return NextResponse.json(
{ error: 'Failed to retrieve credentials' },
{ status: 500 }

View File

@ -4,9 +4,26 @@ import { authOptions } from '@/lib/auth';
import { getMailboxes } from '@/lib/services/email-service';
import { getRedisClient } from '@/lib/redis';
import { getImapConnection } from '@/lib/services/email-service';
import { MailCredentials } from '@prisma/client';
import { prisma } from '@/lib/prisma';
// Define extended MailCredentials type
interface MailCredentials {
id: string;
userId: string;
email: string;
password: string;
host: string;
port: number;
secure?: boolean;
smtp_host?: string | null;
smtp_port?: number | null;
smtp_secure?: boolean | null;
display_name?: string | null;
color?: string | null;
createdAt: Date;
updatedAt: Date;
}
// Keep track of last prefetch time for each user
const lastPrefetchMap = new Map<string, number>();
const PREFETCH_COOLDOWN_MS = 30000; // 30 seconds cooldown between prefetches
@ -23,23 +40,39 @@ const FOLDERS_CACHE_KEY = (userId: string, accountId: string) => `email:folders:
*/
export async function GET() {
try {
// Get Redis connection first to ensure it's available
const redis = getRedisClient();
if (!redis) {
console.error('Redis connection failed');
return NextResponse.json({ error: 'Redis connection failed' }, { status: 500 });
}
// Get session with detailed logging
console.log('Attempting to get server session...');
const session = await getServerSession(authOptions);
if (!session) {
console.error('No session found');
return NextResponse.json({ error: 'No session found' }, { status: 401 });
return NextResponse.json({
authenticated: false,
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 });
return NextResponse.json({
authenticated: false,
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 });
return NextResponse.json({
authenticated: false,
error: 'No user ID in session'
}, { status: 401 });
}
console.log('Session validated successfully:', {
@ -48,13 +81,6 @@ export async function GET() {
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({
@ -64,7 +90,11 @@ export async function GET() {
if (!user) {
console.error('User not found in database');
return NextResponse.json({ error: 'User not found' }, { status: 404 });
return NextResponse.json({
authenticated: true,
hasEmailCredentials: false,
error: 'User not found in database'
});
}
// Get all accounts for the user
@ -73,6 +103,7 @@ export async function GET() {
console.log('No email accounts found for user:', session.user.id);
return NextResponse.json({
authenticated: true,
hasEmailCredentials: false,
accounts: [],
message: 'No email accounts found'
});
@ -84,30 +115,38 @@ export async function GET() {
const accountsWithFolders = await Promise.all(
accounts.map(async (account: MailCredentials) => {
const cacheKey = FOLDERS_CACHE_KEY(user.id, account.id);
// 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)
};
}
// 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']
};
}
try {
// 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 {
id: account.id,
email: account.email,
display_name: account.display_name,
color: account.color,
folders: JSON.parse(cachedFolders)
};
}
// 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 {
id: account.id,
email: account.email,
display_name: account.display_name,
color: account.color,
folders: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']
};
}
const folders = await getMailboxes(client);
console.log(`Fetched ${folders.length} folders for account ${account.email}`);
// Cache the folders in Redis
await redis.set(
cacheKey,
@ -115,14 +154,21 @@ export async function GET() {
'EX',
FOLDERS_CACHE_TTL
);
return {
...account,
id: account.id,
email: account.email,
display_name: account.display_name,
color: account.color,
folders
};
} catch (error) {
console.error(`Error fetching folders for account ${account.id}:`, error);
return {
...account,
id: account.id,
email: account.email,
display_name: account.display_name,
color: account.color,
folders: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']
};
}
@ -131,12 +177,17 @@ export async function GET() {
return NextResponse.json({
authenticated: true,
hasEmailCredentials: true,
accounts: accountsWithFolders
});
} catch (error) {
console.error('Error in session route:', error);
return NextResponse.json(
{ error: 'Internal server error', details: error instanceof Error ? error.message : 'Unknown error' },
{
authenticated: false,
error: 'Internal server error',
details: error instanceof Error ? error.message : 'Unknown error'
},
{ status: 500 }
);
}

View File

@ -156,7 +156,12 @@ export async function getImapConnection(
*/
export async function getUserEmailCredentials(userId: string, accountId?: string): Promise<EmailCredentials | null> {
const credentials = await prisma.mailCredentials.findFirst({
where: accountId ? { userId, id: accountId } : { userId }
where: {
AND: [
{ userId },
{ email: accountId }
]
}
});
if (!credentials) return null;
@ -197,7 +202,6 @@ export async function saveUserEmailCredentials(
credentials: EmailCredentials
): Promise<void> {
console.log('Saving credentials for user:', userId, 'account:', accountId);
console.log('Saving credentials for user:', userId, 'account:', credentials);
if (!credentials) {
throw new Error('No credentials provided');
@ -217,22 +221,34 @@ export async function saveUserEmailCredentials(
color: credentials.color || null
};
// Save to database
await prisma.mailCredentials.upsert({
where: {
id: accountId,
userId
},
update: dbCredentials,
create: {
id: accountId,
userId,
...dbCredentials
}
});
// Cache the full credentials object in Redis (with all fields)
await cacheEmailCredentials(userId, accountId, credentials);
try {
// Save to database using the unique constraint on [userId, email]
await prisma.mailCredentials.upsert({
where: {
id: await prisma.mailCredentials.findFirst({
where: {
AND: [
{ userId },
{ email: accountId }
]
},
select: { id: true }
}).then(result => result?.id ?? '')
},
update: dbCredentials,
create: {
userId,
...dbCredentials
}
});
// Cache the full credentials object in Redis
await cacheEmailCredentials(userId, accountId, credentials);
console.log('Successfully saved and cached credentials for user:', userId);
} catch (error) {
console.error('Error saving credentials:', error);
throw error;
}
}
// Helper type for IMAP fetch options
@ -274,10 +290,12 @@ export async function getEmails(
if (accountId) {
try {
// Get account from database
const account = await prisma.mailCredentials.findUnique({
where: {
id: accountId,
userId
const account = await prisma.mailCredentials.findFirst({
where: {
AND: [
{ userId },
{ email: accountId }
]
}
});