courrier multi account restore compose
This commit is contained in:
parent
0fbc339447
commit
bd8b39ad9b
@ -1,11 +1,9 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/lib/auth';
|
import { authOptions } from '@/lib/auth';
|
||||||
import { getMailboxes } from '@/lib/services/email-service';
|
import { EmailService } 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';
|
import { prisma } from '@/lib/prisma';
|
||||||
|
import { MailCredentials } from '@prisma/client';
|
||||||
|
|
||||||
// Keep track of last prefetch time for each user
|
// Keep track of last prefetch time for each user
|
||||||
const lastPrefetchMap = new Map<string, number>();
|
const lastPrefetchMap = new Map<string, number>();
|
||||||
@ -23,54 +21,37 @@ const FOLDERS_CACHE_KEY = (userId: string, accountId: string) => `email:folders:
|
|||||||
*/
|
*/
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
// Get session with detailed logging
|
// Authentication check
|
||||||
console.log('Attempting to get server session...');
|
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
if (!session?.user?.id) {
|
||||||
if (!session) {
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
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
|
// Get user with their accounts
|
||||||
console.log('Fetching user with ID:', session.user.id);
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: session.user.id },
|
where: { id: session.user.id },
|
||||||
include: { mailCredentials: true }
|
include: {
|
||||||
|
mailCredentials: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
password: true,
|
||||||
|
host: true,
|
||||||
|
port: true,
|
||||||
|
userId: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.error('User not found in database');
|
|
||||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all accounts for the user
|
const accounts = Array.isArray(user.mailCredentials) ? user.mailCredentials : [];
|
||||||
const accounts = (user.mailCredentials || []) as MailCredentials[];
|
|
||||||
if (accounts.length === 0) {
|
if (accounts.length === 0) {
|
||||||
console.log('No email accounts found for user:', session.user.id);
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
accounts: [],
|
accounts: [],
|
||||||
@ -78,54 +59,20 @@ export async function GET() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Found ${accounts.length} accounts for user:`, accounts.map(a => a.email));
|
// Get folders for each account using EmailService
|
||||||
|
const emailService = EmailService.getInstance();
|
||||||
// Fetch folders for each account
|
|
||||||
const accountsWithFolders = await Promise.all(
|
const accountsWithFolders = await Promise.all(
|
||||||
accounts.map(async (account: MailCredentials) => {
|
accounts.map(async (account: MailCredentials) => {
|
||||||
const cacheKey = FOLDERS_CACHE_KEY(user.id, account.id);
|
const folders = await emailService.getFolders(user.id, {
|
||||||
// Try to get folders from Redis cache first
|
...account,
|
||||||
const cachedFolders = await redis.get(cacheKey);
|
imapHost: account.host,
|
||||||
if (cachedFolders) {
|
imapPort: account.port,
|
||||||
console.log(`Using cached folders for account ${account.email}`);
|
imapSecure: true
|
||||||
return {
|
});
|
||||||
...account,
|
return {
|
||||||
folders: JSON.parse(cachedFolders)
|
...account,
|
||||||
};
|
folders
|
||||||
}
|
};
|
||||||
|
|
||||||
// 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 {
|
|
||||||
const folders = await getMailboxes(client);
|
|
||||||
console.log(`Fetched ${folders.length} folders for account ${account.email}`);
|
|
||||||
// Cache the folders in Redis
|
|
||||||
await redis.set(
|
|
||||||
cacheKey,
|
|
||||||
JSON.stringify(folders),
|
|
||||||
'EX',
|
|
||||||
FOLDERS_CACHE_TTL
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...account,
|
|
||||||
folders
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error fetching folders for account ${account.id}:`, error);
|
|
||||||
return {
|
|
||||||
...account,
|
|
||||||
folders: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -136,7 +83,7 @@ export async function GET() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in session route:', error);
|
console.error('Error in session route:', error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Internal server error', details: error instanceof Error ? error.message : 'Unknown error' },
|
{ error: 'Internal server error' },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,8 @@ import {
|
|||||||
invalidateEmailContentCache
|
invalidateEmailContentCache
|
||||||
} from '@/lib/redis';
|
} from '@/lib/redis';
|
||||||
import { EmailCredentials, EmailMessage, EmailAddress, EmailAttachment } from '@/lib/types';
|
import { EmailCredentials, EmailMessage, EmailAddress, EmailAttachment } from '@/lib/types';
|
||||||
|
import { MailCredentials } from '@prisma/client';
|
||||||
|
import { getRedisClient } from '@/lib/redis';
|
||||||
|
|
||||||
// Types specific to this service
|
// Types specific to this service
|
||||||
export interface EmailListResult {
|
export interface EmailListResult {
|
||||||
@ -30,6 +32,112 @@ export interface EmailListResult {
|
|||||||
mailboxes: string[];
|
mailboxes: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FOLDERS_CACHE_TTL = 3600; // 1 hour
|
||||||
|
const FOLDERS_CACHE_KEY = (userId: string, accountId: string) => `email:folders:${userId}:${accountId}`;
|
||||||
|
|
||||||
|
interface ExtendedMailCredentials extends MailCredentials {
|
||||||
|
imapHost: string;
|
||||||
|
imapPort: number;
|
||||||
|
imapSecure: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EmailService {
|
||||||
|
private static instance: EmailService;
|
||||||
|
private connections: Map<string, ImapFlow> = new Map();
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
public static getInstance(): EmailService {
|
||||||
|
if (!EmailService.instance) {
|
||||||
|
EmailService.instance = new EmailService();
|
||||||
|
}
|
||||||
|
return EmailService.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getConnectionKey(userId: string, accountId: string): string {
|
||||||
|
return `${userId}:${accountId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getConnection(userId: string, account: ExtendedMailCredentials): Promise<ImapFlow | null> {
|
||||||
|
const key = this.getConnectionKey(userId, account.id);
|
||||||
|
|
||||||
|
if (this.connections.has(key)) {
|
||||||
|
return this.connections.get(key)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = new ImapFlow({
|
||||||
|
host: account.imapHost,
|
||||||
|
port: account.imapPort,
|
||||||
|
secure: account.imapSecure,
|
||||||
|
auth: {
|
||||||
|
user: account.email,
|
||||||
|
pass: account.password
|
||||||
|
},
|
||||||
|
logger: false
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.connect();
|
||||||
|
this.connections.set(key, client);
|
||||||
|
return client;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to create IMAP connection for ${account.email}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getFolders(userId: string, account: ExtendedMailCredentials): Promise<string[]> {
|
||||||
|
const redis = getRedisClient();
|
||||||
|
const cacheKey = FOLDERS_CACHE_KEY(userId, account.id);
|
||||||
|
|
||||||
|
// Try cache first
|
||||||
|
if (redis) {
|
||||||
|
const cachedFolders = await redis.get(cacheKey);
|
||||||
|
if (cachedFolders) {
|
||||||
|
return JSON.parse(cachedFolders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch from IMAP
|
||||||
|
const client = await this.getConnection(userId, account);
|
||||||
|
if (!client) {
|
||||||
|
return ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk'];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const folders = await this.fetchMailboxes(client);
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
if (redis) {
|
||||||
|
await redis.set(cacheKey, JSON.stringify(folders), 'EX', FOLDERS_CACHE_TTL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return folders;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching folders for ${account.email}:`, error);
|
||||||
|
return ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchMailboxes(client: ImapFlow): Promise<string[]> {
|
||||||
|
const folders: string[] = [];
|
||||||
|
const mailboxes = await client.list();
|
||||||
|
for (const mailbox of mailboxes) {
|
||||||
|
folders.push(mailbox.path);
|
||||||
|
}
|
||||||
|
return folders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async closeConnection(userId: string, accountId: string): Promise<void> {
|
||||||
|
const key = this.getConnectionKey(userId, accountId);
|
||||||
|
const client = this.connections.get(key);
|
||||||
|
if (client) {
|
||||||
|
await client.logout();
|
||||||
|
this.connections.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Connection pool to reuse IMAP clients
|
// Connection pool to reuse IMAP clients
|
||||||
const connectionPool: Record<string, { client: ImapFlow; lastUsed: number }> = {};
|
const connectionPool: Record<string, { client: ImapFlow; lastUsed: number }> = {};
|
||||||
const CONNECTION_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
const CONNECTION_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user