From 2ed5435a156eeb45afcdcd8cc9a5e04192ca0303 Mon Sep 17 00:00:00 2001 From: paritosh18 Date: Thu, 13 Nov 2025 16:04:20 +0530 Subject: [PATCH] resolvec --- .../handlers/addCompanyDetails.ts | 296 +++++++++--------- 1 file changed, 144 insertions(+), 152 deletions(-) diff --git a/src/modules/minglaradmin/handlers/addCompanyDetails.ts b/src/modules/minglaradmin/handlers/addCompanyDetails.ts index 0346349..4f955a0 100644 --- a/src/modules/minglaradmin/handlers/addCompanyDetails.ts +++ b/src/modules/minglaradmin/handlers/addCompanyDetails.ts @@ -1,185 +1,177 @@ -import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { PrismaService } from '../../../common/database/prisma.service'; -import { MinglarService } from '../services/minglar.service'; +import { HostService } from '../../host/services/host.service'; import ApiError from '../../../common/utils/helper/ApiError'; -import { verifyHostToken } from '../../../common/middlewares/jwt/authForMinglarAdmin'; +import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost'; import { hostCompanyDetailsSchema, REQUIRED_DOC_TYPES, } from '../../../common/utils/validation/host/hostCompanyDetails.validation'; -import { uploadFilesToS3 } from '../../../common/utils/helper/s3Upload'; -import { parseMultipartFormData } from '../../../common/utils/validation/host'; +import AWS from 'aws-sdk'; +import Busboy from 'busboy'; +import crypto from 'crypto'; +import config from '@/config/config'; -const prismaService = new PrismaService(); -const minglarService = new MinglarService(prismaService); +const prisma = new PrismaService(); +const hostService = new HostService(prisma); -export const handler = safeHandler(async ( - event: APIGatewayProxyEvent, - context?: Context -): Promise => { +const s3 = new AWS.S3({ + region: config.aws.region, +}); - // ✅ 1. Extract & verify token - const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']; - if (!token) { - throw new ApiError(400, 'This is a protected route. Please provide a valid token.'); - } - - const userInfo = await verifyHostToken(token); - - // ✅ 2. Check Content-Type and parse accordingly - const contentType = event.headers['content-type'] || event.headers['Content-Type'] || ''; - let parsedCompany: any; - let documentsWithS3Urls: Array<{ documentTypeXid: number; documentName: string; filePath: string }> = []; - - if (contentType.includes('multipart/form-data')) { - // ✅ Parse multipart/form-data - // API Gateway sets isBase64Encoded to true for binary media types - const isBase64Encoded = (event as any).isBase64Encoded === true; - const formData = parseMultipartFormData(event.body, contentType, isBase64Encoded); - - // ✅ Parse companyDetails from form field (should be JSON string) - const companyDetailsJson = formData.fields['companyDetails']; - if (!companyDetailsJson) { - throw new ApiError(400, 'Company details are required in form data'); +export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise => { + try { + // ✅ 1. Verify Token + // Extract token from headers + const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'] + if (!token) { + throw new ApiError(400, 'This is a protected route. Please provide a valid token.'); } - try { - parsedCompany = JSON.parse(companyDetailsJson); - } catch { - throw new ApiError(400, 'Invalid JSON in companyDetails field'); - } + // Authenticate user using the shared authForHost function + const userInfo = await verifyHostToken(token); - // ✅ Validate company details - const companyValidation = hostCompanyDetailsSchema.safeParse(parsedCompany); - if (!companyValidation.success) { - const errorMessages = companyValidation.error.issues.map(e => e.message).join(', '); - throw new ApiError(400, `Validation failed: ${errorMessages}`); - } - parsedCompany = companyValidation.data; + // ✅ 2. Ensure content-type is multipart/form-data + const contentType = event.headers['content-type'] || event.headers['Content-Type']; + if (!contentType?.startsWith('multipart/form-data')) + throw new ApiError(400, 'Content-Type must be multipart/form-data.'); - // ✅ Process uploaded files - if (formData.files.length === 0) { - throw new ApiError(400, 'At least one document file is required'); - } + if (!event.isBase64Encoded) + throw new ApiError(400, 'Event body must be base64 encoded for multipart uploads.'); - // ✅ Parse documents metadata (JSON array) - const documentsJson = formData.fields['documents']; - if (!documentsJson) { - throw new ApiError(400, 'Documents metadata is required in form data'); - } + const bodyBuffer = Buffer.from(event.body as string, 'base64'); + const fields: Record = {}; + const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = []; - let documentsMetadata: Array<{ documentTypeXid: number; documentName: string; fieldName: string }>; - try { - documentsMetadata = JSON.parse(documentsJson); - } catch { - throw new ApiError(400, 'Invalid JSON in documents field'); - } + // ✅ 3. Parse multipart data using Busboy + await new Promise((resolve, reject) => { + const bb = Busboy({ headers: { 'content-type': contentType } }); - if (!Array.isArray(documentsMetadata) || documentsMetadata.length === 0) { - throw new ApiError(400, 'Documents must be a non-empty array'); - } + bb.on('file', (fieldname, file, info) => { + const { filename, mimeType } = info; + const chunks: Buffer[] = []; + let totalSize = 0; + const MAX_SIZE = 5 * 1024 * 1024; // 5 MB - // ✅ Map files to document structure - const documentMetadata: Array<{ documentTypeXid: number; documentName: string; file: typeof formData.files[0] }> = []; + file.on('data', (chunk) => { + totalSize += chunk.length; + if (totalSize > MAX_SIZE) { + file.resume(); + return reject(new ApiError(400, `File ${filename} exceeds 5MB limit.`)); + } + chunks.push(chunk); + }); - for (const docMeta of documentsMetadata) { - const file = formData.files.find((f) => f.fieldName === docMeta.fieldName); - if (!file) { - throw new ApiError(400, `File not found for field: ${docMeta.fieldName}`); - } - - documentMetadata.push({ - documentTypeXid: docMeta.documentTypeXid, - documentName: docMeta.documentName, - file, + file.on('end', () => { + files.push({ + buffer: Buffer.concat(chunks), + mimeType, + fileName: filename, + fieldName: fieldname, + }); + }); }); + + bb.on('field', (fieldname, val) => { + try { + fields[fieldname] = JSON.parse(val); + } catch { + fields[fieldname] = val; + } + }); + + bb.on('close', resolve); + bb.on('error', reject); + bb.end(bodyBuffer); + }); + + // ✅ 4. Validate fields + if (!fields.companyDetails) throw new ApiError(400, 'Missing companyDetails field.'); + if (!fields.documents) throw new ApiError(400, 'Missing documents field.'); + if (files.length === 0) throw new ApiError(400, 'At least one document file is required.'); + + // ✅ Parse & validate JSON inputs + let companyDetails; + try { + companyDetails = typeof fields.companyDetails === 'string' ? JSON.parse(fields.companyDetails) : fields.companyDetails; + } catch { + throw new ApiError(400, 'Invalid JSON in companyDetails.'); } - // ✅ Ensure all required documents exist - const uploadedDocTypes = documentMetadata.map((doc) => doc.documentTypeXid); + const companyValidation = hostCompanyDetailsSchema.safeParse(companyDetails); + if (!companyValidation.success) { + const message = companyValidation.error.issues.map((e) => e.message).join(', '); + throw new ApiError(400, `Validation failed: ${message}`); + } + const parsedCompany = companyValidation.data; + + let documentsMetadata; + try { + documentsMetadata = typeof fields.documents === 'string' ? JSON.parse(fields.documents) : fields.documents; + } catch { + throw new ApiError(400, 'Invalid JSON in documents.'); + } + + if (!Array.isArray(documentsMetadata) || documentsMetadata.length === 0) + throw new ApiError(400, 'Documents must be a non-empty array.'); + + // ✅ 5. Map uploaded files to document metadata + const documentMetadata = documentsMetadata.map((doc: any) => { + const file = files.find((f) => f.fieldName === doc.fieldName); + if (!file) throw new ApiError(400, `File not found for field: ${doc.fieldName}`); + return { ...doc, file }; + }); + + // ✅ 6. Ensure all required document types exist + const uploadedDocTypes = documentMetadata.map((d) => d.documentTypeXid); const missingDocs = Object.entries(REQUIRED_DOC_TYPES) .filter(([_, typeId]) => !uploadedDocTypes.includes(typeId)) .map(([name]) => name); - - if (missingDocs.length > 0) { + if (missingDocs.length > 0) throw new ApiError(400, `Missing mandatory documents: ${missingDocs.join(', ')}`); - } - // ✅ Upload files to S3 - const filesToUpload = documentMetadata.map((doc) => ({ - fileData: doc.file.data.toString('base64'), - fileName: doc.file.fileName, - contentType: doc.file.contentType, - })); + // ✅ 7. Upload to S3 + const uploadedDocs: Array<{ documentTypeXid: number; documentName: string; filePath: string }> = []; + for (const doc of documentMetadata) { + const uniqueKey = `${userInfo.id}_${crypto.randomUUID()}_${doc.file.fileName}`; + const s3Key = `Documents/Host/${uniqueKey}`; + await s3 + .upload({ + Bucket: config.aws.bucketName, + Key: s3Key, + Body: doc.file.buffer, + ContentType: doc.file.mimeType, + ACL: 'private', + }) + .promise(); - const s3Urls = await uploadFilesToS3(filesToUpload, `host-documents/${userInfo.id}`); - - // ✅ Map S3 URLs to documents - documentsWithS3Urls = documentMetadata.map((doc, index) => ({ - documentTypeXid: doc.documentTypeXid, - documentName: doc.documentName, - filePath: s3Urls[index], - })); - } else { - // ✅ Fallback to JSON parsing (for backward compatibility) - let body: { companyDetails?: unknown; documents?: unknown[] }; - try { - body = event.body ? JSON.parse(event.body) : {}; - } catch { - throw new ApiError(400, 'Invalid JSON in request body'); - } - - const { companyDetails, documents } = body; - - if (!companyDetails) { - throw new ApiError(400, 'Company details are required'); - } - - // ✅ Validate company details - const companyValidation = hostCompanyDetailsSchema.safeParse(companyDetails); - if (!companyValidation.success) { - const errorMessages = companyValidation.error.issues.map(e => e.message).join(', '); - throw new ApiError(400, `Validation failed: ${errorMessages}`); - } - parsedCompany = companyValidation.data; - - // For JSON, we still expect base64 encoded files in documents array - // This maintains backward compatibility - if (documents && Array.isArray(documents) && documents.length > 0) { - const filesToUpload = documents.map((doc: any) => ({ - fileData: doc.fileData, - fileName: doc.documentName, - contentType: doc.contentType || 'application/pdf', - })); - - const s3Urls = await uploadFilesToS3(filesToUpload, `host-documents/${userInfo.id}`); - - documentsWithS3Urls = documents.map((doc: any, index: number) => ({ + uploadedDocs.push({ documentTypeXid: doc.documentTypeXid, documentName: doc.documentName, - filePath: s3Urls[index], - })); + filePath: `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`, + }); } + + // ✅ 8. Save company details + documents in DB via MinglarService + const createdHost = await hostService.addCompanyDetails(parsedCompany, uploadedDocs); + if (!createdHost) throw new ApiError(400, 'Failed to add company details.'); + + // ✅ 9. Success response + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + success: true, + message: 'Company details and documents uploaded successfully.', + data: createdHost, + }), + }; + } catch (error: any) { + console.error('❌ Error in addCompanyDetails:', error); + throw error; } - - // ✅ 7. Pass validated data to service - const createdHost = await minglarService.addCompanyDetails(parsedCompany, documentsWithS3Urls); - - if (!createdHost) { - throw new ApiError(400, 'Failed to add company details'); - } - - // ✅ 6. Success response - return { - statusCode: 200, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - }, - body: JSON.stringify({ - success: true, - message: 'Company details and documents uploaded successfully', - }), - }; });