port-nimara-client-portal/server/api/email/process-sales-eois.ts

255 lines
6.9 KiB
TypeScript
Raw Normal View History

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<number[]>((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<ParsedMail> {
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<ProcessedEOI> {
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;
}