diff --git a/src/modules/host/handlers/updateHostProfile.ts b/src/modules/host/handlers/updateHostProfile.ts index 65461b5..3058045 100644 --- a/src/modules/host/handlers/updateHostProfile.ts +++ b/src/modules/host/handlers/updateHostProfile.ts @@ -1,11 +1,18 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import dayjs from 'dayjs'; import { z } from 'zod'; +import AWS from 'aws-sdk'; +import config from '../../../config/config'; import { prismaClient } from '../../../common/database/prisma.lambda.service'; import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost'; import { safeHandler } from '../../../common/utils/handlers/safeHandler'; -import ApiError from '../../../common/utils/helper/ApiError'; +import ApiError from "../../../common/utils/helper/ApiError"; import { ROLE } from '../../../common/utils/constants/common.constant'; +import { parseMultipartFormData } from '../../../common/utils/helper/parseMultipartFormData'; + +const s3 = new AWS.S3({ + region: config.aws.region, +}); const updateHostProfileSchema = z .strictObject({ @@ -17,6 +24,8 @@ const updateHostProfileSchema = z mobileNumber: z.string().min(5).max(15).optional(), dateOfBirth: z.string().min(1).optional(), + profileImage: z.string().url().optional(), + // Address address1: z.string().min(1).optional(), address2: z.string().min(1).optional(), @@ -30,6 +39,33 @@ const updateHostProfileSchema = z }) .strip(); +async function uploadProfileImageToS3(buffer: Buffer, mimeType: string, originalName: string, userId: number) { + const sanitizeFileName = (name: string) => { + return name + .toLowerCase() + .replace(/[^a-z0-9.]/g, '_') + .replace(/_+/g, '_') + .replace(/^_+|_+$/g, ''); + }; + + const fileExtension = originalName.split('.').pop() || 'jpg'; + const fileName = `profile_image.${fileExtension}`; + const sanitizedFileName = sanitizeFileName(fileName); + const s3Key = `Host/ProfileImages/${userId}/${sanitizedFileName}`; + + await s3 + .upload({ + Bucket: config.aws.bucketName, + Key: s3Key, + Body: buffer, + ContentType: mimeType, + ACL: 'private', + }) + .promise(); + + return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`; +} + function parseDob(dateOfBirth: string): Date { const parsed = dayjs(dateOfBirth, ['YYYY-MM-DD', 'MM/DD/YYYY', 'DD/MM/YYYY'], true); if (!parsed.isValid()) { @@ -105,7 +141,18 @@ async function ensureHostUser(tx: any, userId: number) { if (user.roleXid !== ROLE.HOST) throw new ApiError(403, 'Access denied.'); } -async function updateUserIfNeeded(tx: any, userId: number, input: { firstName?: string; lastName?: string | null; isdCode?: string; mobileNumber?: string; dateOfBirth?: string }) { +async function updateUserIfNeeded( + tx: any, + userId: number, + input: { + firstName?: string; + lastName?: string | null; + isdCode?: string; + mobileNumber?: string; + dateOfBirth?: string; + profileImage?: string; + }, +) { const userUpdateData: any = {}; if (input.firstName !== undefined) userUpdateData.firstName = input.firstName || null; if (input.lastName !== undefined) userUpdateData.lastName = input.lastName; @@ -114,6 +161,9 @@ async function updateUserIfNeeded(tx: any, userId: number, input: { firstName?: if (input.dateOfBirth !== undefined) { userUpdateData.dateOfBirth = input.dateOfBirth ? parseDob(input.dateOfBirth) : null; } + if (input.profileImage !== undefined) { + userUpdateData.profileImage = input.profileImage || null; + } if (!hasAnyDefined(userUpdateData)) return; @@ -210,7 +260,56 @@ export const handler = safeHandler(async ( throw new ApiError(400, 'Invalid user id'); } - const body = parseJsonBody(event); + const contentType = event.headers['Content-Type'] || event.headers['content-type'] || ''; + const isMultipart = contentType.includes('multipart/form-data'); + + let body: any; + + if (isMultipart) { + const isBase64Encoded = event.isBase64Encoded || false; + const { fields, files } = parseMultipartFormData(event.body || null, contentType, isBase64Encoded); + + const multipartBody: any = {}; + + const copyIfPresent = (key: string) => { + if (fields[key] !== undefined) { + multipartBody[key] = fields[key]; + } + }; + + ['fullName', 'firstName', 'lastName', 'isdCode', 'mobileNumber', 'dateOfBirth', 'address1', 'address2', 'pinCode'].forEach( + copyIfPresent, + ); + + const parseNumberField = (key: string) => { + if (fields[key] !== undefined) { + const value = Number(fields[key]); + if (!Number.isNaN(value)) { + multipartBody[key] = value; + } + } + }; + + ['countryXid', 'stateXid', 'cityXid'].forEach(parseNumberField); + + const profileImageFile = files.find((f) => f.fieldName === 'profileImage'); + if (profileImageFile) { + const uploadedUrl = await uploadProfileImageToS3( + profileImageFile.data, + profileImageFile.contentType, + profileImageFile.fileName, + userId, + ); + multipartBody.profileImage = uploadedUrl; + } else if (fields.profileImage) { + multipartBody.profileImage = fields.profileImage; + } + + body = multipartBody; + } else { + body = parseJsonBody(event); + } + const data = validateBody(body); const name = normalizeNameFields(data); const address = buildAddressInput(data); @@ -223,6 +322,7 @@ export const handler = safeHandler(async ( isdCode: data.isdCode, mobileNumber: data.mobileNumber, dateOfBirth: data.dateOfBirth, + profileImage: data.profileImage, }); await upsertAddressIfNeeded(tx, userId, address); return getProfileSnapshot(tx, userId);