import { uploadFile, createBucketIfNotExists, getMinioClient } from '~/server/utils/minio'; import { updateInterestEOIDocument } from '~/server/utils/nocodb'; import { requireAuth } from '~/server/utils/auth'; import formidable from 'formidable'; import { promises as fs } from 'fs'; import mime from 'mime-types'; export default defineEventHandler(async (event) => { console.log('[EOI Upload] Request received'); // Check authentication (x-tag header OR Keycloak session) await requireAuth(event); try { // Get interestId from query params 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', }); } // Ensure bucket exists console.log('[EOI Upload] Ensuring client-portal bucket exists'); await createBucketIfNotExists('client-portal'); // Parse multipart form data const form = formidable({ maxFileSize: 50 * 1024 * 1024, // 50MB limit 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 - 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(); 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 const documentData = { title: uploadedFile.originalFilename || 'EOI Document', filename: fileName, url: url, size: uploadedFile.size, uploadedAt: new Date().toISOString() }; // Update interest with EOI document information console.log('[EOI Upload] Updating interest with EOI document info'); console.log('[EOI Upload] Document data:', JSON.stringify(documentData, null, 2)); try { await updateInterestEOIDocument(interestId, documentData); console.log('[EOI Upload] Successfully updated EOI document in database'); } catch (dbError: any) { console.error('[EOI Upload] Failed to update database with EOI document:', dbError); console.error('[EOI Upload] Database error details:', dbError.data || dbError.message); throw new Error(`Failed to update database: ${dbError.message}`); } // Update the status fields for uploaded (signed) EOI const updateData: any = { 'EOI Status': 'Signed', 'EOI Time Sent': new Date().toISOString(), 'Sales Process Level': 'Signed LOI and NDA' }; console.log('[EOI Upload] Updating interest status fields for signed EOI'); console.log('[EOI Upload] Status update data:', JSON.stringify(updateData, null, 2)); try { // Update the interest - using internal server call (no auth headers needed) await $fetch('/api/update-interest', { method: 'POST', body: { id: interestId, data: updateData } }); console.log('[EOI Upload] Successfully updated interest status'); } catch (statusError: any) { console.error('[EOI Upload] Failed to update interest status:', statusError); console.error('[EOI Upload] Status error details:', statusError.data || statusError.message); // Don't throw here - the file was uploaded successfully } console.log('[EOI Upload] Upload completed successfully'); return { success: true, document: documentData, message: 'EOI document uploaded successfully', }; } catch (error: any) { 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', }); } }); async function getCurrentSalesLevel(interestId: string): Promise { try { // Using internal server call (no auth headers needed) const interest = await $fetch(`/api/get-interest-by-id`, { params: { id: interestId, }, }); return interest['Sales Process Level'] || ''; } catch (error) { console.error('Failed to get current sales level:', error); return ''; } } function shouldUpdateSalesLevel(currentLevel: string): boolean { const levelsBeforeLOI = [ 'General Qualified Interest', 'Specific Qualified Interest' ]; return levelsBeforeLOI.includes(currentLevel); }