From 4b6d3fd991efb0d9b0a911727f0cd72dae266bb5 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 10 Jun 2025 15:01:04 +0200 Subject: [PATCH] updates --- components/ClientEmailSection.vue | 53 ++++++++++++++++++++++++++++--- server/api/email/send.ts | 49 ++++++++++++++++++++++++++-- server/api/eoi/upload-document.ts | 43 ++++++++++++++++++++++--- 3 files changed, 133 insertions(+), 12 deletions(-) diff --git a/components/ClientEmailSection.vue b/components/ClientEmailSection.vue index f54a637..f84850d 100644 --- a/components/ClientEmailSection.vue +++ b/components/ClientEmailSection.vue @@ -14,8 +14,8 @@
- -
+ +
Refresh Emails + + + Reconnect + + + Disconnect +
@@ -415,8 +433,14 @@ watch(() => props.interest.Id, () => { }); const loadEmailThread = async () => { + if (!sessionId.value) { + console.log('[ClientEmailSection] No sessionId available, skipping email thread load'); + return; + } + isRefreshing.value = true; try { + console.log('[ClientEmailSection] Loading email thread for:', props.interest['Email Address']); const response = await $fetch<{ success: boolean; emails: any[]; @@ -426,15 +450,18 @@ const loadEmailThread = async () => { 'x-tag': '094ut234' }, body: { - email: props.interest['Email Address'] + clientEmail: props.interest['Email Address'], + interestId: props.interest.Id.toString(), + sessionId: sessionId.value } }); if (response.success) { + console.log('[ClientEmailSection] Successfully loaded', response.emails?.length || 0, 'emails'); emailThreads.value = response.emails || []; } } catch (error) { - console.error('Failed to load email thread:', error); + console.error('[ClientEmailSection] Failed to load email thread:', error); } finally { isRefreshing.value = false; } @@ -641,6 +668,24 @@ const onCredentialsSaved = (data: { sessionId: string }) => { toast.success('Email credentials saved successfully'); }; +const disconnectEmail = () => { + // Clear stored credentials + localStorage.removeItem('emailSessionId'); + localStorage.removeItem('emailSignature'); + sessionId.value = ''; + hasEmailCredentials.value = false; + emailThreads.value = []; + toast.success('Email disconnected'); +}; + +const reconnectEmail = () => { + // Clear current session to force re-authentication + localStorage.removeItem('emailSessionId'); + sessionId.value = ''; + hasEmailCredentials.value = false; + toast.info('Please enter your email credentials'); +}; + const formatDate = (dateString: string) => { if (!dateString) return ''; diff --git a/server/api/email/send.ts b/server/api/email/send.ts index 68c4144..d08b858 100644 --- a/server/api/email/send.ts +++ b/server/api/email/send.ts @@ -6,7 +6,10 @@ import { updateInterest } from '~/server/utils/nocodb'; export default defineEventHandler(async (event) => { const xTagHeader = getRequestHeader(event, "x-tag"); + console.log('[Email Send] Request received with x-tag:', xTagHeader); + if (!xTagHeader || (xTagHeader !== "094ut234" && xTagHeader !== "pjnvü1230")) { + console.error('[Email Send] Authentication failed - invalid x-tag'); throw createError({ statusCode: 401, statusMessage: "unauthenticated" }); } @@ -23,7 +26,22 @@ export default defineEventHandler(async (event) => { attachments = [] } = body; + console.log('[Email Send] Request body:', { + to, + subject, + hasBody: !!emailBody, + interestId, + hasSessionId: !!sessionId, + attachmentCount: attachments.length + }); + if (!to || !subject || !emailBody || !sessionId) { + console.error('[Email Send] Missing required fields:', { + hasTo: !!to, + hasSubject: !!subject, + hasBody: !!emailBody, + hasSessionId: !!sessionId + }); throw createError({ statusCode: 400, statusMessage: "To, subject, body, and sessionId are required" @@ -31,8 +49,10 @@ export default defineEventHandler(async (event) => { } // Get encrypted credentials from session + console.log('[Email Send] Getting credentials for sessionId:', sessionId); const encryptedCredentials = getCredentialsFromSession(sessionId); if (!encryptedCredentials) { + console.error('[Email Send] No credentials found for sessionId:', sessionId); throw createError({ statusCode: 401, statusMessage: "Email credentials not found. Please reconnect." @@ -40,7 +60,22 @@ export default defineEventHandler(async (event) => { } // Decrypt credentials - const { email, password } = decryptCredentials(encryptedCredentials); + console.log('[Email Send] Decrypting credentials'); + let email: string; + let password: string; + + try { + const decrypted = decryptCredentials(encryptedCredentials); + email = decrypted.email; + password = decrypted.password; + console.log('[Email Send] Successfully decrypted credentials for:', email); + } catch (decryptError) { + console.error('[Email Send] Failed to decrypt credentials:', decryptError); + throw createError({ + statusCode: 401, + statusMessage: "Failed to decrypt email credentials. Please reconnect." + }); + } // Get user info for signature const defaultName = email.split('@')[0].replace('.', ' ').replace(/\b\w/g, l => l.toUpperCase()); @@ -84,6 +119,7 @@ export default defineEventHandler(async (event) => { }); // Prepare email attachments + console.log('[Email Send] Processing', attachments.length, 'attachments'); const emailAttachments = []; if (attachments && attachments.length > 0) { const client = getMinioClient(); @@ -92,6 +128,7 @@ export default defineEventHandler(async (event) => { try { // Determine which bucket to use const bucket = attachment.bucket || 'client-portal'; // Default to client-portal + console.log('[Email Send] Processing attachment:', attachment.name, 'from bucket:', bucket, 'path:', attachment.path); // Download file from MinIO const stream = await client.getObject(bucket, attachment.path); @@ -104,13 +141,14 @@ export default defineEventHandler(async (event) => { }); const content = Buffer.concat(chunks); + console.log('[Email Send] Successfully downloaded attachment:', attachment.name, 'size:', content.length); emailAttachments.push({ filename: attachment.name, content: content }); } catch (error) { - console.error(`Failed to attach file ${attachment.name} from bucket ${attachment.bucket}:`, error); + console.error(`[Email Send] Failed to attach file ${attachment.name} from bucket ${attachment.bucket}:`, error); // Continue with other attachments } } @@ -118,6 +156,8 @@ export default defineEventHandler(async (event) => { // Send email const fromName = sig.name || defaultName; + console.log('[Email Send] Sending email from:', email, 'to:', to); + const info = await transporter.sendMail({ from: `"${fromName}" <${email}>`, to: to, @@ -126,6 +166,8 @@ export default defineEventHandler(async (event) => { html: htmlBody, // HTML version with signature attachments: emailAttachments }); + + console.log('[Email Send] Email sent successfully, messageId:', info.messageId); // Store email in MinIO for thread history and update EOI Time Sent if (interestId) { @@ -175,7 +217,8 @@ export default defineEventHandler(async (event) => { messageId: info.messageId }; } catch (error) { - console.error('Failed to send email:', error); + console.error('[Email Send] Failed to send email:', error); + console.error('[Email Send] Error stack:', error instanceof Error ? error.stack : 'No stack trace'); if (error instanceof Error) { throw createError({ statusCode: 500, diff --git a/server/api/eoi/upload-document.ts b/server/api/eoi/upload-document.ts index fd15a34..43891b4 100644 --- a/server/api/eoi/upload-document.ts +++ b/server/api/eoi/upload-document.ts @@ -7,7 +7,10 @@ import mime from 'mime-types'; export default defineEventHandler(async (event) => { const xTagHeader = getRequestHeader(event, "x-tag"); + console.log('[EOI Upload] Request received with x-tag:', xTagHeader); + if (!xTagHeader || (xTagHeader !== "094ut234" && xTagHeader !== "pjnvü1230")) { + console.error('[EOI Upload] Authentication failed - invalid x-tag'); throw createError({ statusCode: 401, statusMessage: "unauthenticated" }); } @@ -16,7 +19,10 @@ export default defineEventHandler(async (event) => { const query = getQuery(event); const interestId = query.interestId as string; + console.log('[EOI Upload] Interest ID:', interestId); + if (!interestId) { + console.error('[EOI Upload] No interest ID provided'); throw createError({ statusCode: 400, statusMessage: 'Interest ID is required', @@ -24,6 +30,7 @@ export default defineEventHandler(async (event) => { } // Ensure bucket exists + console.log('[EOI Upload] Ensuring client-portal bucket exists'); await createBucketIfNotExists('client-portal'); // Parse multipart form data @@ -32,39 +39,61 @@ export default defineEventHandler(async (event) => { keepExtensions: true, }); + console.log('[EOI Upload] Parsing multipart form data'); const [fields, files] = await form.parse(event.node.req); // Handle the uploaded file const uploadedFile = Array.isArray(files.file) ? files.file[0] : files.file; if (!uploadedFile) { + console.error('[EOI Upload] No file uploaded in request'); throw createError({ statusCode: 400, statusMessage: 'No file uploaded', }); } + console.log('[EOI Upload] File received:', { + originalFilename: uploadedFile.originalFilename, + size: uploadedFile.size, + mimetype: uploadedFile.mimetype + }); + // Read file buffer + console.log('[EOI Upload] Reading file from disk'); const fileBuffer = await fs.readFile(uploadedFile.filepath); - // Generate filename with timestamp + // Generate filename with timestamp - ensure it goes to EOIs folder const timestamp = Date.now(); const sanitizedName = uploadedFile.originalFilename?.replace(/[^a-zA-Z0-9.-]/g, '_') || 'eoi-document.pdf'; const fileName = `EOIs/${interestId}-${timestamp}-${sanitizedName}`; + console.log('[EOI Upload] Generated filename:', fileName); + // Get content type const contentType = mime.lookup(uploadedFile.originalFilename || '') || 'application/pdf'; + console.log('[EOI Upload] Content type:', contentType); // Upload to MinIO client-portal bucket + console.log('[EOI Upload] Uploading to MinIO client-portal bucket'); const client = getMinioClient(); - await client.putObject('client-portal', fileName, fileBuffer, fileBuffer.length, { - 'Content-Type': contentType, - }); + + try { + await client.putObject('client-portal', fileName, fileBuffer, fileBuffer.length, { + 'Content-Type': contentType, + }); + console.log('[EOI Upload] Successfully uploaded to MinIO'); + } catch (minioError: any) { + console.error('[EOI Upload] MinIO upload failed:', minioError); + throw new Error(`Failed to upload to MinIO: ${minioError.message}`); + } // Clean up temp file + console.log('[EOI Upload] Cleaning up temp file'); await fs.unlink(uploadedFile.filepath); // Get download URL for the uploaded file + console.log('[EOI Upload] Generating presigned URL'); const url = await client.presignedGetObject('client-portal', fileName, 24 * 60 * 60); // 24 hour expiry // Prepare document data for database @@ -77,6 +106,7 @@ export default defineEventHandler(async (event) => { }; // Update interest with EOI document information + console.log('[EOI Upload] Updating interest with EOI document info'); await updateInterestEOIDocument(interestId, documentData); // Also update the status fields @@ -84,6 +114,7 @@ export default defineEventHandler(async (event) => { 'EOI Status': 'Waiting for Signatures', 'EOI Time Sent': new Date().toISOString() }; + console.log('[EOI Upload] Updating interest status fields'); // Update Sales Process Level if it's below "LOI and NDA Sent" const currentLevel = await getCurrentSalesLevel(interestId); @@ -103,13 +134,15 @@ export default defineEventHandler(async (event) => { } }); + console.log('[EOI Upload] Upload completed successfully'); return { success: true, document: documentData, message: 'EOI document uploaded successfully', }; } catch (error: any) { - console.error('Failed to upload EOI document:', error); + console.error('[EOI Upload] Failed to upload EOI document:', error); + console.error('[EOI Upload] Error stack:', error.stack); throw createError({ statusCode: 500, statusMessage: error.message || 'Failed to upload EOI document',