courrier multi account restore compose
This commit is contained in:
parent
9f5617b424
commit
093125df23
@ -7,7 +7,12 @@ import {
|
|||||||
testEmailConnection
|
testEmailConnection
|
||||||
} from '@/lib/services/email-service';
|
} from '@/lib/services/email-service';
|
||||||
import { prefetchUserEmailData } from '@/lib/services/prefetch-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) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
@ -49,13 +54,18 @@ export async function POST(request: Request) {
|
|||||||
// Invalidate all cached data for this user as they are changing their credentials
|
// Invalidate all cached data for this user as they are changing their credentials
|
||||||
await invalidateUserEmailCache(session.user.id);
|
await invalidateUserEmailCache(session.user.id);
|
||||||
|
|
||||||
// Save credentials in the database and Redis
|
// Create credentials object with required fields
|
||||||
await saveUserEmailCredentials(session.user.id, email, {
|
const credentials = {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
host,
|
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
|
// Start prefetching email data in the background
|
||||||
// We don't await this to avoid blocking the response
|
// We don't await this to avoid blocking the response
|
||||||
@ -67,10 +77,7 @@ export async function POST(request: Request) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in login handler:', error);
|
console.error('Error in login handler:', error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{ error: 'An unexpected error occurred' },
|
||||||
error: 'An unexpected error occurred',
|
|
||||||
details: error instanceof Error ? error.message : 'Unknown error'
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -78,7 +85,6 @@ export async function POST(request: Request) {
|
|||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
// Authenticate user
|
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
if (!session?.user?.id) {
|
if (!session?.user?.id) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@ -87,8 +93,26 @@ export async function GET() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user credentials from database
|
// First try to get from Redis cache
|
||||||
const credentials = await getUserEmailCredentials(session.user.id);
|
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) {
|
if (!credentials) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@ -97,14 +121,8 @@ export async function GET() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return credentials without the password
|
return NextResponse.json(credentials);
|
||||||
return NextResponse.json({
|
|
||||||
email: credentials.email,
|
|
||||||
host: credentials.host,
|
|
||||||
port: credentials.port
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching credentials:', error);
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Failed to retrieve credentials' },
|
{ error: 'Failed to retrieve credentials' },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
|
|||||||
@ -4,9 +4,26 @@ import { authOptions } from '@/lib/auth';
|
|||||||
import { getMailboxes } from '@/lib/services/email-service';
|
import { getMailboxes } from '@/lib/services/email-service';
|
||||||
import { getRedisClient } from '@/lib/redis';
|
import { getRedisClient } from '@/lib/redis';
|
||||||
import { getImapConnection } from '@/lib/services/email-service';
|
import { getImapConnection } from '@/lib/services/email-service';
|
||||||
import { MailCredentials } from '@prisma/client';
|
|
||||||
import { prisma } from '@/lib/prisma';
|
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
|
// Keep track of last prefetch time for each user
|
||||||
const lastPrefetchMap = new Map<string, number>();
|
const lastPrefetchMap = new Map<string, number>();
|
||||||
const PREFETCH_COOLDOWN_MS = 30000; // 30 seconds cooldown between prefetches
|
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() {
|
export async function GET() {
|
||||||
try {
|
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
|
// Get session with detailed logging
|
||||||
console.log('Attempting to get server session...');
|
console.log('Attempting to get server session...');
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
console.error('No session found');
|
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) {
|
if (!session.user) {
|
||||||
console.error('No user in session');
|
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) {
|
if (!session.user.id) {
|
||||||
console.error('No user ID in session');
|
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:', {
|
console.log('Session validated successfully:', {
|
||||||
@ -48,13 +81,6 @@ export async function GET() {
|
|||||||
name: session.user.name
|
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);
|
console.log('Fetching user with ID:', session.user.id);
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
@ -64,7 +90,11 @@ export async function GET() {
|
|||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.error('User not found in database');
|
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
|
// 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);
|
console.log('No email accounts found for user:', session.user.id);
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
|
hasEmailCredentials: false,
|
||||||
accounts: [],
|
accounts: [],
|
||||||
message: 'No email accounts found'
|
message: 'No email accounts found'
|
||||||
});
|
});
|
||||||
@ -84,12 +115,17 @@ export async function GET() {
|
|||||||
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 cacheKey = FOLDERS_CACHE_KEY(user.id, account.id);
|
||||||
|
|
||||||
|
try {
|
||||||
// Try to get folders from Redis cache first
|
// Try to get folders from Redis cache first
|
||||||
const cachedFolders = await redis.get(cacheKey);
|
const cachedFolders = await redis.get(cacheKey);
|
||||||
if (cachedFolders) {
|
if (cachedFolders) {
|
||||||
console.log(`Using cached folders for account ${account.email}`);
|
console.log(`Using cached folders for account ${account.email}`);
|
||||||
return {
|
return {
|
||||||
...account,
|
id: account.id,
|
||||||
|
email: account.email,
|
||||||
|
display_name: account.display_name,
|
||||||
|
color: account.color,
|
||||||
folders: JSON.parse(cachedFolders)
|
folders: JSON.parse(cachedFolders)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -100,14 +136,17 @@ export async function GET() {
|
|||||||
if (!client) {
|
if (!client) {
|
||||||
console.warn(`Failed to get IMAP connection for account ${account.email}`);
|
console.warn(`Failed to get IMAP connection for account ${account.email}`);
|
||||||
return {
|
return {
|
||||||
...account,
|
id: account.id,
|
||||||
|
email: account.email,
|
||||||
|
display_name: account.display_name,
|
||||||
|
color: account.color,
|
||||||
folders: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']
|
folders: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const folders = await getMailboxes(client);
|
const folders = await getMailboxes(client);
|
||||||
console.log(`Fetched ${folders.length} folders for account ${account.email}`);
|
console.log(`Fetched ${folders.length} folders for account ${account.email}`);
|
||||||
|
|
||||||
// Cache the folders in Redis
|
// Cache the folders in Redis
|
||||||
await redis.set(
|
await redis.set(
|
||||||
cacheKey,
|
cacheKey,
|
||||||
@ -115,14 +154,21 @@ export async function GET() {
|
|||||||
'EX',
|
'EX',
|
||||||
FOLDERS_CACHE_TTL
|
FOLDERS_CACHE_TTL
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...account,
|
id: account.id,
|
||||||
|
email: account.email,
|
||||||
|
display_name: account.display_name,
|
||||||
|
color: account.color,
|
||||||
folders
|
folders
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching folders for account ${account.id}:`, error);
|
console.error(`Error fetching folders for account ${account.id}:`, error);
|
||||||
return {
|
return {
|
||||||
...account,
|
id: account.id,
|
||||||
|
email: account.email,
|
||||||
|
display_name: account.display_name,
|
||||||
|
color: account.color,
|
||||||
folders: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']
|
folders: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -131,12 +177,17 @@ export async function GET() {
|
|||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
|
hasEmailCredentials: true,
|
||||||
accounts: accountsWithFolders
|
accounts: accountsWithFolders
|
||||||
});
|
});
|
||||||
} 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' },
|
{
|
||||||
|
authenticated: false,
|
||||||
|
error: 'Internal server error',
|
||||||
|
details: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
},
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -156,7 +156,12 @@ export async function getImapConnection(
|
|||||||
*/
|
*/
|
||||||
export async function getUserEmailCredentials(userId: string, accountId?: string): Promise<EmailCredentials | null> {
|
export async function getUserEmailCredentials(userId: string, accountId?: string): Promise<EmailCredentials | null> {
|
||||||
const credentials = await prisma.mailCredentials.findFirst({
|
const credentials = await prisma.mailCredentials.findFirst({
|
||||||
where: accountId ? { userId, id: accountId } : { userId }
|
where: {
|
||||||
|
AND: [
|
||||||
|
{ userId },
|
||||||
|
{ email: accountId }
|
||||||
|
]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!credentials) return null;
|
if (!credentials) return null;
|
||||||
@ -197,7 +202,6 @@ export async function saveUserEmailCredentials(
|
|||||||
credentials: EmailCredentials
|
credentials: EmailCredentials
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
console.log('Saving credentials for user:', userId, 'account:', accountId);
|
console.log('Saving credentials for user:', userId, 'account:', accountId);
|
||||||
console.log('Saving credentials for user:', userId, 'account:', credentials);
|
|
||||||
|
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
throw new Error('No credentials provided');
|
throw new Error('No credentials provided');
|
||||||
@ -217,22 +221,34 @@ export async function saveUserEmailCredentials(
|
|||||||
color: credentials.color || null
|
color: credentials.color || null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save to database
|
try {
|
||||||
|
// Save to database using the unique constraint on [userId, email]
|
||||||
await prisma.mailCredentials.upsert({
|
await prisma.mailCredentials.upsert({
|
||||||
where: {
|
where: {
|
||||||
id: accountId,
|
id: await prisma.mailCredentials.findFirst({
|
||||||
userId
|
where: {
|
||||||
|
AND: [
|
||||||
|
{ userId },
|
||||||
|
{ email: accountId }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
select: { id: true }
|
||||||
|
}).then(result => result?.id ?? '')
|
||||||
},
|
},
|
||||||
update: dbCredentials,
|
update: dbCredentials,
|
||||||
create: {
|
create: {
|
||||||
id: accountId,
|
|
||||||
userId,
|
userId,
|
||||||
...dbCredentials
|
...dbCredentials
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cache the full credentials object in Redis (with all fields)
|
// Cache the full credentials object in Redis
|
||||||
await cacheEmailCredentials(userId, accountId, credentials);
|
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
|
// Helper type for IMAP fetch options
|
||||||
@ -274,10 +290,12 @@ export async function getEmails(
|
|||||||
if (accountId) {
|
if (accountId) {
|
||||||
try {
|
try {
|
||||||
// Get account from database
|
// Get account from database
|
||||||
const account = await prisma.mailCredentials.findUnique({
|
const account = await prisma.mailCredentials.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: accountId,
|
AND: [
|
||||||
userId
|
{ userId },
|
||||||
|
{ email: accountId }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user