diff --git a/server/api/email/fetch-thread.ts b/server/api/email/fetch-thread.ts index 86d2265..e46b547 100644 --- a/server/api/email/fetch-thread.ts +++ b/server/api/email/fetch-thread.ts @@ -24,7 +24,8 @@ export default defineEventHandler(async (event) => { try { const body = await readBody(event); - const { clientEmail, interestId, sessionId, limit = 20 } = body; + // Increase limit to get more complete threads + const { clientEmail, interestId, sessionId, limit = 200 } = body; if (!clientEmail || !sessionId) { throw createError({ @@ -66,7 +67,21 @@ export default defineEventHandler(async (event) => { const cachedEmails: EmailMessage[] = []; if (interestId) { try { - const files = await listFiles(`client-emails/interest-${interestId}/`, true) as any[]; + // List files from the client-emails bucket + const client = getMinioClient(); + const stream = client.listObjectsV2('client-emails', `interest-${interestId}/`, true); + const files: any[] = []; + + await new Promise((resolve, reject) => { + stream.on('data', (obj) => { + if (obj && obj.name) { + files.push({ name: obj.name, size: obj.size || 0 }); + } + }); + stream.on('error', reject); + stream.on('end', resolve); + }); + console.log('Found cached email files:', files.length); for (const file of files) { @@ -74,10 +89,14 @@ export default defineEventHandler(async (event) => { try { // Read file directly on server using MinIO client (works with private buckets) const client = getMinioClient(); - const bucketName = useRuntimeConfig().minio.bucketName; + // Use the client-emails bucket directly + const bucketName = 'client-emails'; + + // The file.name is already the correct path within the bucket + const fileName = file.name; // Get object as stream - const stream = await client.getObject(bucketName, file.name); + const stream = await client.getObject(bucketName, fileName); // Convert stream to string let data = ''; @@ -224,16 +243,9 @@ async function fetchImapEmails( return; } - // Use date-based search to reduce the number of emails fetched - // Search for emails from the last 30 days - const thirtyDaysAgo = new Date(); - thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); - - // Format date for IMAP (e.g., "1-Jan-2024") - const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - const searchDate = `${thirtyDaysAgo.getDate()}-${months[thirtyDaysAgo.getMonth()]}-${thirtyDaysAgo.getFullYear()}`; - - imap.search(['SINCE', searchDate], (err, results) => { + // For specific client searches, don't use date filter to ensure we get complete threads + // Just get ALL emails and filter manually + imap.search(['ALL'], (err, results) => { if (err) { console.error(`Search error in ${folderName}:`, err); searchNextFolder(); @@ -316,11 +328,14 @@ async function fetchImapEmails( interestId: interestId }; - const objectName = `client-emails/interest-${interestId}/${Date.now()}-${email.direction}.json`; + const objectName = `interest-${interestId}/${Date.now()}-${email.direction}.json`; const buffer = Buffer.from(JSON.stringify(emailData, null, 2)); - // Fire and forget - don't wait for upload - uploadFile(objectName, buffer, 'application/json').catch(err => { + // Upload to the client-emails bucket + const client = getMinioClient(); + client.putObject('client-emails', objectName, buffer, buffer.length, { + 'Content-Type': 'application/json', + }).catch(err => { console.error('Failed to cache email:', err); }); } catch (cacheError) { diff --git a/server/api/email/send.ts b/server/api/email/send.ts index 816d7f7..8dba2d9 100644 --- a/server/api/email/send.ts +++ b/server/api/email/send.ts @@ -107,14 +107,15 @@ export default defineEventHandler(async (event) => { interestId: interestId }; - const objectName = `client-emails/interest-${interestId}/${Date.now()}-sent.json`; + const objectName = `interest-${interestId}/${Date.now()}-sent.json`; const buffer = Buffer.from(JSON.stringify(emailData, null, 2)); - await uploadFile( - objectName, - buffer, - 'application/json' - ); + // Upload to the client-emails bucket + const { getMinioClient } = await import('~/server/utils/minio'); + const client = getMinioClient(); + await client.putObject('client-emails', objectName, buffer, buffer.length, { + 'Content-Type': 'application/json', + }); // Update EOI Time Sent if the email contains an EOI link if (emailBody.includes('signatures.portnimara.dev/sign/')) { diff --git a/server/api/email/test-minio-bucket.ts b/server/api/email/test-minio-bucket.ts new file mode 100644 index 0000000..dfffa30 --- /dev/null +++ b/server/api/email/test-minio-bucket.ts @@ -0,0 +1,90 @@ +import { getMinioClient } from '~/server/utils/minio'; + +export default defineEventHandler(async (event) => { + const xTagHeader = getRequestHeader(event, "x-tag"); + + if (!xTagHeader || (xTagHeader !== "094ut234" && xTagHeader !== "pjnvü1230")) { + throw createError({ statusCode: 401, statusMessage: "unauthenticated" }); + } + + try { + const query = getQuery(event); + const { interestId } = query; + + const client = getMinioClient(); + + // Test 1: Check if client-emails bucket exists + const buckets = await client.listBuckets(); + const clientEmailsBucket = buckets.find(b => b.name === 'client-emails'); + + if (!clientEmailsBucket) { + // Try to create the bucket + await client.makeBucket('client-emails', 'us-east-1'); + } + + // Test 2: List files in the bucket + const files: any[] = []; + const prefix = interestId ? `interest-${interestId}/` : ''; + const stream = client.listObjectsV2('client-emails', prefix, true); + + await new Promise((resolve, reject) => { + stream.on('data', (obj) => { + if (obj && obj.name) { + files.push({ + name: obj.name, + size: obj.size || 0, + lastModified: obj.lastModified + }); + } + }); + stream.on('error', reject); + stream.on('end', resolve); + }); + + // Test 3: Try to write a test file + const testData = { + test: true, + timestamp: new Date().toISOString(), + message: 'MinIO bucket test' + }; + + const testObjectName = `test/${Date.now()}-test.json`; + const buffer = Buffer.from(JSON.stringify(testData, null, 2)); + + await client.putObject('client-emails', testObjectName, buffer, buffer.length, { + 'Content-Type': 'application/json', + }); + + // Test 4: Try to read the test file back + const readStream = await client.getObject('client-emails', testObjectName); + let readData = ''; + + await new Promise((resolve, reject) => { + readStream.on('data', (chunk) => { + readData += chunk; + }); + readStream.on('end', resolve); + readStream.on('error', reject); + }); + + // Clean up test file + await client.removeObject('client-emails', testObjectName); + + return { + success: true, + bucketExists: !!clientEmailsBucket, + bucketCreated: !clientEmailsBucket, + filesInBucket: files.length, + files: files.slice(0, 10), // Show first 10 files + writeTestPassed: true, + readTestPassed: JSON.parse(readData).test === true + }; + } catch (error) { + console.error('MinIO bucket test failed:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + details: error + }; + } +});