courrier multi account restore compose
This commit is contained in:
parent
bd8b39ad9b
commit
9f5617b424
@ -1,9 +1,11 @@
|
|||||||
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 { EmailService } from '@/lib/services/email-service';
|
import { getMailboxes } from '@/lib/services/email-service';
|
||||||
import { prisma } from '@/lib/prisma';
|
import { getRedisClient } from '@/lib/redis';
|
||||||
|
import { getImapConnection } from '@/lib/services/email-service';
|
||||||
import { MailCredentials } from '@prisma/client';
|
import { MailCredentials } from '@prisma/client';
|
||||||
|
import { prisma } from '@/lib/prisma';
|
||||||
|
|
||||||
// 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>();
|
||||||
@ -21,37 +23,54 @@ const FOLDERS_CACHE_KEY = (userId: string, accountId: string) => `email:folders:
|
|||||||
*/
|
*/
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
// Authentication check
|
// Get session with detailed logging
|
||||||
|
console.log('Attempting to get server session...');
|
||||||
const session = await getServerSession(authOptions);
|
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
|
// 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: {
|
include: { mailCredentials: true }
|
||||||
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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const accounts = Array.isArray(user.mailCredentials) ? user.mailCredentials : [];
|
// Get all accounts for the user
|
||||||
|
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: [],
|
||||||
@ -59,20 +78,54 @@ export async function GET() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get folders for each account using EmailService
|
console.log(`Found ${accounts.length} accounts for user:`, accounts.map(a => a.email));
|
||||||
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 folders = await emailService.getFolders(user.id, {
|
const cacheKey = FOLDERS_CACHE_KEY(user.id, account.id);
|
||||||
...account,
|
// Try to get folders from Redis cache first
|
||||||
imapHost: account.host,
|
const cachedFolders = await redis.get(cacheKey);
|
||||||
imapPort: account.port,
|
if (cachedFolders) {
|
||||||
imapSecure: true
|
console.log(`Using cached folders for account ${account.email}`);
|
||||||
});
|
return {
|
||||||
return {
|
...account,
|
||||||
...account,
|
folders: JSON.parse(cachedFolders)
|
||||||
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']
|
||||||
|
};
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -83,7 +136,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' },
|
{ error: 'Internal server error', details: error instanceof Error ? error.message : 'Unknown error' },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,27 +46,6 @@ import { useCourrier, EmailData } from '@/hooks/use-courrier';
|
|||||||
// Import the prefetching function
|
// Import the prefetching function
|
||||||
import { prefetchFolderEmails } from '@/lib/services/prefetch-service';
|
import { prefetchFolderEmails } from '@/lib/services/prefetch-service';
|
||||||
|
|
||||||
// Import the Account interface
|
|
||||||
import { Account } from '@/lib/types';
|
|
||||||
|
|
||||||
// Frontend-specific account interface
|
|
||||||
interface FrontendAccount {
|
|
||||||
id: string | number;
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
color: string;
|
|
||||||
folders?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the redundant interfaces
|
|
||||||
interface EmailWithFlags {
|
|
||||||
id: string;
|
|
||||||
read?: boolean;
|
|
||||||
flags?: {
|
|
||||||
seen?: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
@ -82,6 +61,22 @@ function SimplifiedLoadingFix() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Account {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
color: string;
|
||||||
|
folders?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EmailWithFlags {
|
||||||
|
id: string;
|
||||||
|
read?: boolean;
|
||||||
|
flags?: {
|
||||||
|
seen?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default function CourrierPage() {
|
export default function CourrierPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
@ -129,18 +124,16 @@ export default function CourrierPage() {
|
|||||||
const [showAddAccountForm, setShowAddAccountForm] = useState(false);
|
const [showAddAccountForm, setShowAddAccountForm] = useState(false);
|
||||||
|
|
||||||
// Email accounts for the sidebar
|
// Email accounts for the sidebar
|
||||||
const defaultAccounts: Account[] = [
|
const [accounts, setAccounts] = useState<Account[]>([
|
||||||
{ id: 'all-accounts', name: 'All', email: '', color: 'bg-gray-500', folders: [] },
|
{ id: 'all-accounts', name: 'All', email: '', color: 'bg-gray-500' },
|
||||||
{ id: 'loading-account', name: 'Loading...', email: '', color: 'bg-blue-500', folders: [] }
|
{ id: 'loading-account', name: 'Loading...', email: '', color: 'bg-blue-500', folders: [] }
|
||||||
];
|
]);
|
||||||
|
|
||||||
const [accounts, setAccounts] = useState<Account[]>(defaultAccounts);
|
|
||||||
const [selectedAccount, setSelectedAccount] = useState<Account | null>(null);
|
const [selectedAccount, setSelectedAccount] = useState<Account | null>(null);
|
||||||
|
|
||||||
// Track expanded folders for each account
|
// Track expanded folders for each account
|
||||||
const [expandedAccounts, setExpandedAccounts] = useState<Record<string, boolean>>({});
|
const [expandedAccounts, setExpandedAccounts] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
// Update account folders when mailboxes change
|
// Update account folders when mailboxes change - update this to maintain account IDs
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('Mailboxes updated:', mailboxes);
|
console.log('Mailboxes updated:', mailboxes);
|
||||||
setAccounts(prev => {
|
setAccounts(prev => {
|
||||||
@ -150,7 +143,7 @@ export default function CourrierPage() {
|
|||||||
if (updated[1]) {
|
if (updated[1]) {
|
||||||
updated[1] = {
|
updated[1] = {
|
||||||
...updated[1],
|
...updated[1],
|
||||||
folders: mailboxes || []
|
folders: mailboxes
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
console.log('Updated accounts with new mailboxes:', updated);
|
console.log('Updated accounts with new mailboxes:', updated);
|
||||||
@ -202,9 +195,12 @@ export default function CourrierPage() {
|
|||||||
// If accounts array becomes empty (bug), restore default accounts
|
// If accounts array becomes empty (bug), restore default accounts
|
||||||
if (!accounts || accounts.length === 0) {
|
if (!accounts || accounts.length === 0) {
|
||||||
console.warn('Accounts array is empty, restoring defaults');
|
console.warn('Accounts array is empty, restoring defaults');
|
||||||
setAccounts(defaultAccounts);
|
setAccounts([
|
||||||
|
{ id: 'all-accounts', name: 'All', email: '', color: 'bg-gray-500' },
|
||||||
|
{ id: 'loading-account', name: 'Loading...', email: '', color: 'bg-blue-500', folders: mailboxes }
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}, [accounts]);
|
}, [accounts, mailboxes]);
|
||||||
|
|
||||||
// Initialize session and start prefetching
|
// Initialize session and start prefetching
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -300,31 +296,81 @@ export default function CourrierPage() {
|
|||||||
setPrefetchStarted(Boolean(data.prefetchStarted));
|
setPrefetchStarted(Boolean(data.prefetchStarted));
|
||||||
|
|
||||||
// Create a copy of the current accounts to update
|
// Create a copy of the current accounts to update
|
||||||
const updatedAccounts: Account[] = [
|
const updatedAccounts = [...accounts];
|
||||||
{
|
|
||||||
id: 'all-accounts' as string | number,
|
|
||||||
name: 'All',
|
|
||||||
email: '',
|
|
||||||
color: 'bg-gray-500',
|
|
||||||
folders: []
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Check if we have multiple accounts returned
|
// Check if we have multiple accounts returned
|
||||||
if (data.allAccounts && Array.isArray(data.allAccounts)) {
|
if (data.allAccounts && Array.isArray(data.allAccounts) && data.allAccounts.length > 0) {
|
||||||
data.allAccounts.forEach((account: any) => {
|
console.log('[DEBUG] Multiple accounts found:', data.allAccounts.length);
|
||||||
// Get folders for this account
|
|
||||||
const accountFolders = account.folders || [];
|
// First, validate the structure of each account
|
||||||
|
data.allAccounts.forEach((account: any, index: number) => {
|
||||||
updatedAccounts.push({
|
console.log(`[DEBUG] Account ${index+1} structure check:`, {
|
||||||
id: account.id || `account-${Date.now()}`,
|
id: account.id,
|
||||||
name: account.display_name || account.email,
|
|
||||||
email: account.email,
|
email: account.email,
|
||||||
color: account.color || 'bg-blue-500',
|
display_name: account.display_name,
|
||||||
folders: accountFolders
|
hasFolders: !!account.folders,
|
||||||
} as Account);
|
foldersIsArray: Array.isArray(account.folders),
|
||||||
|
foldersCount: Array.isArray(account.folders) ? account.folders.length : 0,
|
||||||
|
folders: account.folders || []
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else if (data.email) {
|
|
||||||
|
// Keep the All account at position 0
|
||||||
|
if (updatedAccounts.length > 0) {
|
||||||
|
// Replace the loading account with the real one
|
||||||
|
data.allAccounts.forEach((account: any, index: number) => {
|
||||||
|
// Ensure folders are valid
|
||||||
|
const accountFolders = (account.folders && Array.isArray(account.folders))
|
||||||
|
? account.folders
|
||||||
|
: (data.mailboxes && Array.isArray(data.mailboxes))
|
||||||
|
? data.mailboxes
|
||||||
|
: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk'];
|
||||||
|
|
||||||
|
// If we're updating the first real account (index 0 in API, position 1 in our array)
|
||||||
|
if (index === 0 && updatedAccounts.length > 1) {
|
||||||
|
// Update the loading account in place to maintain references
|
||||||
|
updatedAccounts[1] = {
|
||||||
|
id: account.id, // Use the real account ID
|
||||||
|
name: account.display_name || account.email,
|
||||||
|
email: account.email,
|
||||||
|
color: account.color || 'bg-blue-500',
|
||||||
|
folders: accountFolders
|
||||||
|
};
|
||||||
|
console.log(`[DEBUG] Updated loading account to real account: ${account.email} with ID ${account.id}`);
|
||||||
|
} else if (index > 0) {
|
||||||
|
// Add additional accounts as new entries
|
||||||
|
updatedAccounts.push({
|
||||||
|
id: account.id || `account-${index}`,
|
||||||
|
name: account.display_name || account.email,
|
||||||
|
email: account.email,
|
||||||
|
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' });
|
||||||
|
|
||||||
|
// 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 {
|
||||||
// Fallback to single account if allAccounts is not available
|
// Fallback to single account if allAccounts is not available
|
||||||
console.log(`[DEBUG] Fallback to single account: ${data.email}`);
|
console.log(`[DEBUG] Fallback to single account: ${data.email}`);
|
||||||
|
|
||||||
@ -335,13 +381,26 @@ export default function CourrierPage() {
|
|||||||
const folderList = (data.mailboxes && data.mailboxes.length > 0) ?
|
const folderList = (data.mailboxes && data.mailboxes.length > 0) ?
|
||||||
data.mailboxes : fallbackFolders;
|
data.mailboxes : fallbackFolders;
|
||||||
|
|
||||||
updatedAccounts.push({
|
// Update the loading account if it exists
|
||||||
id: 'default-account' as string | number,
|
if (updatedAccounts.length > 1) {
|
||||||
name: data.displayName || data.email,
|
updatedAccounts[1] = {
|
||||||
email: data.email,
|
id: 'default-account', // Use consistent ID
|
||||||
color: 'bg-blue-500',
|
name: data.displayName || data.email,
|
||||||
folders: folderList
|
email: data.email,
|
||||||
} as Account);
|
color: 'bg-blue-500',
|
||||||
|
folders: folderList
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Fallback if accounts array is empty
|
||||||
|
updatedAccounts.push({ id: 'all-accounts', name: 'All', email: '', color: 'bg-gray-500' });
|
||||||
|
updatedAccounts.push({
|
||||||
|
id: 'default-account',
|
||||||
|
name: data.displayName || data.email,
|
||||||
|
email: data.email,
|
||||||
|
color: 'bg-blue-500',
|
||||||
|
folders: folderList
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Setting accounts:', updatedAccounts);
|
console.log('Setting accounts:', updatedAccounts);
|
||||||
@ -444,13 +503,12 @@ export default function CourrierPage() {
|
|||||||
// Also prefetch additional pages to make scrolling smoother
|
// Also prefetch additional pages to make scrolling smoother
|
||||||
if (session?.user?.id) {
|
if (session?.user?.id) {
|
||||||
// Prefetch next 2 pages beyond the current next page
|
// Prefetch next 2 pages beyond the current next page
|
||||||
const accountIdStr = selectedAccount?.id ? String(selectedAccount.id) : undefined;
|
|
||||||
prefetchFolderEmails(
|
prefetchFolderEmails(
|
||||||
session.user.id,
|
session.user.id,
|
||||||
currentFolder,
|
currentFolder,
|
||||||
2,
|
2,
|
||||||
nextPage + 1,
|
nextPage + 1,
|
||||||
accountIdStr
|
selectedAccount?.id
|
||||||
).catch(err => {
|
).catch(err => {
|
||||||
console.error(`Error prefetching additional pages for ${currentFolder}:`, err);
|
console.error(`Error prefetching additional pages for ${currentFolder}:`, err);
|
||||||
});
|
});
|
||||||
@ -514,36 +572,32 @@ export default function CourrierPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle mailbox change with prefetching
|
// Handle mailbox change with prefetching
|
||||||
const handleMailboxChange = (folder: string, accountId?: string | number) => {
|
const handleMailboxChange = (folder: string, accountId?: string) => {
|
||||||
// Reset to page 1 when changing folders
|
// Reset to page 1 when changing folders
|
||||||
setPage(1);
|
setPage(1);
|
||||||
setCurrentView(folder);
|
|
||||||
changeFolder(folder);
|
|
||||||
|
|
||||||
// If we have a specific accountId, store it with the folder
|
// If we have a specific accountId, store it with the folder
|
||||||
if (accountId) {
|
if (accountId) {
|
||||||
setSelectedAccount(accounts.find(a => String(a.id) === String(accountId)) || null);
|
// Store the current account ID with the folder change
|
||||||
console.log(`Changing folder to ${folder} for account ${accountId}`);
|
console.log(`Changing folder to ${folder} for account ${accountId}`);
|
||||||
} else {
|
|
||||||
setSelectedAccount(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change folder in the state
|
||||||
|
changeFolder(folder);
|
||||||
|
setCurrentView(folder);
|
||||||
|
|
||||||
// Start prefetching additional pages for this folder
|
// Start prefetching additional pages for this folder
|
||||||
if (session?.user?.id && folder) {
|
if (session?.user?.id && folder) {
|
||||||
// First two pages are most important - prefetch immediately
|
// First two pages are most important - prefetch immediately
|
||||||
const accountIdStr = accountId ? String(accountId) : undefined;
|
prefetchFolderEmails(session.user.id, folder, 3, 1, accountId).catch(err => {
|
||||||
prefetchFolderEmails(session.user.id, folder, 3, 1, accountIdStr).catch(err => {
|
console.error(`Error prefetching ${folder}:`, err);
|
||||||
console.error('Error prefetching folder emails:', err);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle sending email
|
// Handle sending email
|
||||||
const handleSendEmail = async (emailData: EmailData): Promise<void> => {
|
const handleSendEmail = async (emailData: EmailData) => {
|
||||||
const result = await sendEmail(emailData);
|
return await sendEmail(emailData);
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error || 'Failed to send email');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete confirmation
|
// Handle delete confirmation
|
||||||
|
|||||||
45
lib/auth.ts
45
lib/auth.ts
@ -1,6 +1,5 @@
|
|||||||
import { NextAuthOptions, User } from 'next-auth';
|
import { NextAuthOptions } from 'next-auth';
|
||||||
import CredentialsProvider from 'next-auth/providers/credentials';
|
import CredentialsProvider from 'next-auth/providers/credentials';
|
||||||
import { JWT } from 'next-auth/jwt';
|
|
||||||
|
|
||||||
export const authOptions: NextAuthOptions = {
|
export const authOptions: NextAuthOptions = {
|
||||||
providers: [
|
providers: [
|
||||||
@ -16,54 +15,18 @@ export const authOptions: NextAuthOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement actual authentication logic
|
// TODO: Implement actual authentication logic
|
||||||
// For now, generate a unique ID based on email
|
|
||||||
const userId = Buffer.from(credentials.email).toString('base64');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: userId,
|
id: '1',
|
||||||
email: credentials.email,
|
email: credentials.email,
|
||||||
name: credentials.email.split('@')[0],
|
name: credentials.email.split('@')[0],
|
||||||
first_name: credentials.email.split('@')[0],
|
};
|
||||||
last_name: '',
|
|
||||||
username: credentials.email,
|
|
||||||
role: ['user']
|
|
||||||
} satisfies User;
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
session: {
|
session: {
|
||||||
strategy: 'jwt',
|
strategy: 'jwt',
|
||||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
|
||||||
},
|
},
|
||||||
jwt: {
|
|
||||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
|
||||||
},
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
pages: {
|
pages: {
|
||||||
signIn: '/courrier/login',
|
signIn: '/login',
|
||||||
},
|
},
|
||||||
callbacks: {
|
|
||||||
async jwt({ token, user }) {
|
|
||||||
if (user) {
|
|
||||||
token.id = user.id;
|
|
||||||
token.email = user.email;
|
|
||||||
token.first_name = user.first_name;
|
|
||||||
token.last_name = user.last_name;
|
|
||||||
token.username = user.username;
|
|
||||||
token.role = user.role;
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
},
|
|
||||||
async session({ session, token }) {
|
|
||||||
if (token) {
|
|
||||||
session.user.id = token.id as string;
|
|
||||||
session.user.email = token.email as string;
|
|
||||||
session.user.first_name = token.first_name as string;
|
|
||||||
session.user.last_name = token.last_name as string;
|
|
||||||
session.user.username = token.username as string;
|
|
||||||
session.user.role = token.role as string[];
|
|
||||||
}
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
@ -18,8 +18,6 @@ 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 {
|
||||||
@ -32,112 +30,6 @@ 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