import { NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from "@/app/api/auth/options"; import { getMailboxes } from '@/lib/services/email-service'; import { getRedisClient } from '@/lib/redis'; import { getImapConnection } from '@/lib/services/email-service'; import { prisma } from '@/lib/prisma'; import bcrypt from 'bcryptjs'; // 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(); const PREFETCH_COOLDOWN_MS = 30000; // 30 seconds cooldown between prefetches // Cache TTL for folders in Redis (5 minutes) const FOLDERS_CACHE_TTL = 3600; // 1 hour // Redis key for folders cache const FOLDERS_CACHE_KEY = (userId: string, accountId: string) => `email:folders:${userId}:${accountId}`; /** * Ensure user exists in database, creating if missing * Uses session data from Keycloak to populate user record */ async function ensureUserExists(session: any): Promise { const userId = session.user.id; const userEmail = session.user.email; if (!userId || !userEmail) { throw new Error('Missing required user data in session'); } try { // Check if user exists const existingUser = await prisma.user.findUnique({ where: { id: userId } }); if (existingUser) { return; } // User doesn't exist, create it console.log(`User ${userId} not found in database, creating from session data...`); // Generate a temporary random password (not used for auth, Keycloak handles that) const tempPassword = await bcrypt.hash(Math.random().toString(36).slice(-10), 10); await prisma.user.create({ data: { id: userId, // Use Keycloak user ID email: userEmail, password: tempPassword, // Temporary password (Keycloak handles authentication) createdAt: new Date(), updatedAt: new Date(), } }); console.log(`Successfully created user ${userId} (${userEmail}) in database`); } catch (error) { console.error(`Error ensuring user exists:`, error); // If it's a unique constraint error, user might have been created by another request if (error instanceof Error && error.message.includes('Unique constraint')) { console.log('User may have been created by concurrent request, continuing...'); return; } throw error; } } /** * 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 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({ authenticated: false, error: 'No session found' }, { status: 401 }); } if (!session.user) { console.error('No user in session'); 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({ authenticated: false, 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 }); // Ensure user exists in database (create if missing) try { await ensureUserExists(session); } catch (error) { console.error(`Error ensuring user exists:`, error); return NextResponse.json({ authenticated: true, hasEmailCredentials: false, error: 'Failed to ensure user exists in database', details: error instanceof Error ? error.message : 'Unknown error' }); } // Get user with their accounts console.log('Fetching user with ID:', session.user.id); const user = await prisma.user.findUnique({ where: { id: session.user.id }, include: { mailCredentials: true } }); if (!user) { console.error('User not found in database after creation attempt'); return NextResponse.json({ authenticated: true, hasEmailCredentials: false, error: 'User not found in database' }); } // Get all accounts for the user const accounts = (user.mailCredentials || []) as MailCredentials[]; if (accounts.length === 0) { console.log('No email accounts found for user:', session.user.id); return NextResponse.json({ authenticated: true, hasEmailCredentials: false, accounts: [], message: 'No email accounts found' }); } console.log(`Found ${accounts.length} accounts for user:`, accounts.map(a => a.email)); // Fetch folders for each account const accountsWithFolders = await Promise.all( accounts.map(async (account: MailCredentials) => { const cacheKey = FOLDERS_CACHE_KEY(user.id, account.id); 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, JSON.stringify(folders), 'EX', FOLDERS_CACHE_TTL ); return { 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 { id: account.id, email: account.email, display_name: account.display_name, color: account.color, folders: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk'] }; } }) ); return NextResponse.json({ authenticated: true, hasEmailCredentials: true, allAccounts: accountsWithFolders }); } catch (error) { console.error('Error in session route:', error); return NextResponse.json( { authenticated: false, error: 'Internal server error', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } ); } }