import { parseEmail, getIMAPConnection } from '~/server/utils/email-utils'; import { uploadFile } from '~/server/utils/minio'; import { getInterestByFieldAsync, updateInterest } from '~/server/utils/nocodb'; import type { ParsedMail } from 'mailparser'; interface ProcessedEOI { clientName: string; interestId?: string; fileName: string; processed: boolean; error?: string; } 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 { console.log('[Process Sales EOIs] Starting email processing...'); // Sales email credentials const credentials = { user: 'sales@portnimara.com', password: 'MDze7cSClQok8qWOf23X8Mb6lArdk0i42YnwJ1FskdtO2NCc9', host: 'mail.portnimara.com', port: 993, tls: true }; const connection = await getIMAPConnection(credentials); const results: ProcessedEOI[] = []; try { // Open inbox await new Promise((resolve, reject) => { connection.openBox('INBOX', false, (err: any, box: any) => { if (err) reject(err); else resolve(box); }); }); // Search for unread emails with attachments const searchCriteria = ['UNSEEN']; const messages = await new Promise((resolve, reject) => { connection.search(searchCriteria, (err: any, results: any) => { if (err) reject(err); else resolve(results || []); }); }); console.log(`[Process Sales EOIs] Found ${messages.length} unread messages`); for (const msgNum of messages) { try { const parsedEmail = await fetchAndParseEmail(connection, msgNum); if (parsedEmail.attachments && parsedEmail.attachments.length > 0) { // Process PDF attachments for (const attachment of parsedEmail.attachments) { if (attachment.contentType === 'application/pdf') { const result = await processEOIAttachment( attachment, parsedEmail.subject || '', parsedEmail.from?.text || '' ); results.push(result); } } } // Mark as read connection.addFlags(msgNum, '\\Seen', (err: any) => { if (err) console.error('Failed to mark message as read:', err); }); } catch (error) { console.error(`[Process Sales EOIs] Error processing message ${msgNum}:`, error); } } connection.end(); } catch (error) { connection.end(); throw error; } return { success: true, processed: results.length, results }; } catch (error: any) { console.error('[Process Sales EOIs] Failed to process emails:', error); throw createError({ statusCode: 500, statusMessage: error.message || 'Failed to process sales emails', }); } }); async function fetchAndParseEmail(connection: any, msgNum: number): Promise { return new Promise((resolve, reject) => { const fetch = connection.fetch(msgNum, { bodies: '', struct: true }); fetch.on('message', (msg: any) => { let buffer = ''; msg.on('body', (stream: any) => { stream.on('data', (chunk: any) => { buffer += chunk.toString('utf8'); }); stream.once('end', async () => { try { const parsed = await parseEmail(buffer); resolve(parsed); } catch (err) { reject(err); } }); }); }); fetch.once('error', reject); }); } async function processEOIAttachment( attachment: any, subject: string, from: string ): Promise { const fileName = attachment.filename || 'unknown.pdf'; try { console.log(`[Process Sales EOIs] Processing attachment: ${fileName}`); // Try to extract client name from filename or subject const clientName = extractClientName(fileName, subject); if (!clientName) { return { clientName: 'Unknown', fileName, processed: false, error: 'Could not identify client from filename or subject' }; } // Find interest by client name const interest = await getInterestByFieldAsync('Full Name', clientName); if (!interest) { return { clientName, fileName, processed: false, error: `No interest found for client: ${clientName}` }; } // Generate unique filename const timestamp = Date.now(); const uploadFileName = `EOIs/${interest.Id}-${timestamp}-${fileName}`; // Upload to MinIO await uploadFile(uploadFileName, attachment.content, 'application/pdf'); // Update interest with EOI document const documentData = { title: fileName, filename: uploadFileName, url: `/api/files/proxy-download?fileName=${encodeURIComponent(uploadFileName)}`, size: attachment.size, mimetype: 'application/pdf', icon: 'mdi-file-pdf-box', uploadedAt: new Date().toISOString(), source: 'email', from: from }; // Get existing documents and add new one const existingDocs = interest['EOI Document'] || []; const updatedDocs = [...existingDocs, documentData]; // Update interest await updateInterest(interest.Id.toString(), { 'EOI Document': updatedDocs, 'EOI Status': 'Signed', 'Sales Process Level': 'Signed LOI and NDA' }); console.log(`[Process Sales EOIs] Successfully processed EOI for ${clientName}`); return { clientName, interestId: interest.Id.toString(), fileName, processed: true }; } catch (error: any) { console.error(`[Process Sales EOIs] Error processing attachment:`, error); return { clientName: 'Unknown', fileName, processed: false, error: error.message }; } } function extractClientName(fileName: string, subject: string): string | null { // Try to extract from filename patterns like: // "John_Doe_EOI_signed.pdf" // "EOI_John_Doe.pdf" // "John Doe - EOI.pdf" // First try filename const filePatterns = [ /^(.+?)[-_]EOI/i, /EOI[-_](.+?)\.pdf/i, /^(.+?)_signed/i, /^(.+?)\s*-\s*EOI/i ]; for (const pattern of filePatterns) { const match = fileName.match(pattern); if (match && match[1]) { return match[1].replace(/[_-]/g, ' ').trim(); } } // Then try subject const subjectPatterns = [ /EOI\s+(?:for\s+)?(.+?)(?:\s+signed)?$/i, /Signed\s+EOI\s*[-:]?\s*(.+)$/i, /(.+?)\s*EOI\s*(?:signed|completed)/i ]; for (const pattern of subjectPatterns) { const match = subject.match(pattern); if (match && match[1]) { return match[1].trim(); } } return null; }