From 99cbe55a707e15ce4a3cf62b44e01b2bc15a62d1 Mon Sep 17 00:00:00 2001 From: Mayank Mishra Date: Wed, 19 Nov 2025 15:13:30 +0530 Subject: [PATCH 1/2] made getall pqq question prepopulate --- serverless.yml | 16 ++++++++ .../handlers/getAllPQQQuesWithAns.ts | 40 +++++++++++++++++++ .../services/prepopulate.service.ts | 20 ++++++++++ 3 files changed, 76 insertions(+) create mode 100644 src/modules/prepopulate/handlers/getAllPQQQuesWithAns.ts diff --git a/serverless.yml b/serverless.yml index 6b37a50..dad67aa 100644 --- a/serverless.yml +++ b/serverless.yml @@ -400,6 +400,22 @@ functions: method: get + getAllPqqQuesAns: + handler: src/modules/prepopulate/handlers/getAllPQQQuesWithAns.handler + package: + patterns: + - "src/modules/minglaradmin/**" + - "common/**" + - "src/common/**" + - "node_modules/@prisma/client/**" + - "node_modules/.prisma/**" + + events: + - httpApi: + path: /prepopulate/get-all-pqq-ques-ans + method: get + + assignAMToHost: handler: src/modules/minglaradmin/handlers/assignAM.handler package: diff --git a/src/modules/prepopulate/handlers/getAllPQQQuesWithAns.ts b/src/modules/prepopulate/handlers/getAllPQQQuesWithAns.ts new file mode 100644 index 0000000..cfee25c --- /dev/null +++ b/src/modules/prepopulate/handlers/getAllPQQQuesWithAns.ts @@ -0,0 +1,40 @@ +import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { PrismaService } from '../../../common/database/prisma.service'; +import { safeHandler } from '../../../common/utils/handlers/safeHandler'; +import ApiError from '../../../common/utils/helper/ApiError'; +import { PrePopulateService } from '../services/prepopulate.service'; +import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; + +const prismaService = new PrismaService(); +const prePopulateService = new PrePopulateService(prismaService); + +export const handler = safeHandler(async ( + event: APIGatewayProxyEvent, + context?: Context +): Promise => { + // 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.'); + } + + // Authenticate user using the shared authForHost function + await verifyHostToken(token); + + const result = await prePopulateService.getAllPQQQuesAndAns(); + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + success: true, + message: 'Data retrieved successfully', + data: result, + }), + }; +}); + diff --git a/src/modules/prepopulate/services/prepopulate.service.ts b/src/modules/prepopulate/services/prepopulate.service.ts index 4918e70..0b4e862 100644 --- a/src/modules/prepopulate/services/prepopulate.service.ts +++ b/src/modules/prepopulate/services/prepopulate.service.ts @@ -33,5 +33,25 @@ export class PrePopulateService { }) } + async getAllPQQQuesAndAns() { + return await this.prisma.pQQCategories.findMany({ + where: { isActive: true }, + include: { + pqqsubCategories: { + include: { + questions: { + include: { + PQQAnswers: true + } + } + } + } + }, + orderBy: { displayOrder: 'asc' } + }); + } + + + } From 13ffee5f7e89a3330a937b1b70615b681b8fdec4 Mon Sep 17 00:00:00 2001 From: Mayank Mishra Date: Wed, 19 Nov 2025 16:55:54 +0530 Subject: [PATCH 2/2] made submit pqq answer for host api --- prisma/schema.prisma | 66 ++--- serverless.yml | 322 +++++++++++----------- src/modules/host/handlers/submitPqqAns.ts | 166 +++++++++++ src/modules/host/services/host.service.ts | 55 ++++ 4 files changed, 414 insertions(+), 195 deletions(-) create mode 100644 src/modules/host/handlers/submitPqqAns.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9939ffa..27a5268 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -832,44 +832,44 @@ model Activities { host HostHeader @relation(fields: [hostXid], references: [id], onDelete: Cascade) activityTypeXid Int @map("activity_type_xid") activityType ActivityTypes @relation(fields: [activityTypeXid], references: [id], onDelete: Restrict) - frequenciesXid Int @map("frequencies_xid") - frequency Frequencies @relation(fields: [frequenciesXid], references: [id], onDelete: Restrict) - activityRefNumber String @map("activity_ref_number") - activityTitle String @map("activity_title") - activityDescription String @map("activity_description") - checkInLat Float @map("check_in_lat") - checkInLong Float @map("check_in_long") - checkInAddress String @map("check_in_address") - isCheckOutSame Boolean @default(true) @map("is_check_out_same") + frequenciesXid Int? @map("frequencies_xid") + frequency Frequencies? @relation(fields: [frequenciesXid], references: [id], onDelete: Restrict) + activityRefNumber String? @map("activity_ref_number") + activityTitle String? @map("activity_title") + activityDescription String? @map("activity_description") + checkInLat Float? @map("check_in_lat") + checkInLong Float? @map("check_in_long") + checkInAddress String? @map("check_in_address") + isCheckOutSame Boolean? @default(true) @map("is_check_out_same") checkOutLat Float? @map("check_out_lat") checkOutLong Float? @map("check_out_long") checkOutAddress String? @map("check_out_address") energyLevelXid Int? @map("energy_level_xid") energyLevel EnergyLevels? @relation(fields: [energyLevelXid], references: [id], onDelete: Restrict) - activityDurationMins Int @map("activity_duration_mins") - foodAvailable Boolean @default(false) @map("food_available") - foodIsChargeable Boolean @default(false) @map("food_is_chargeable") - alcoholAvailable Boolean @default(false) @map("alcohol_available") - trainerAvailable Boolean @default(false) @map("trainer_available") - trainerIsChargeable Boolean @default(false) @map("trainer_is_chargeable") - pickUpDropAvailable Boolean @default(false) @map("pick_up_drop_available") - pickUpDropIsChargeable Boolean @default(false) @map("pick_up_drop_is_chargeable") - inActivityAvailable Boolean @default(false) @map("in_activity_available") - inActivityIsChargeable Boolean @default(false) @map("in_activity_is_chargeable") - equipmentAvailable Boolean @default(false) @map("equipment_available") - equipmentIsChargeable Boolean @default(false) @map("equipment_is_chargeable") - cancellationAvailable Boolean @default(false) @map("cancellation_available") - cancellationAllowedBeforeMins Int @map("cancellation_allowed_before_mins") - currencyXid Int @map("currency_xid") - currencies Currencies @relation(fields: [currencyXid], references: [id], onDelete: Restrict) - sustainabilityScore Int @map("sustainability_score") - safetyScore Int @map("safety_score") - totalScore Int @map("total_score") - isInstantBooking Boolean @default(false) @map("is_instant_booking") - activityInternalStatus String @default("pending") @map("activity_internal_status") - activityDisplayStatus String @default("pending") @map("activity_display_status") - amInternalStatus String @default("pending") @map("am_internal_status") - amDisplayStatus String @default("pending") @map("am_display_status") + activityDurationMins Int? @map("activity_duration_mins") + foodAvailable Boolean? @default(false) @map("food_available") + foodIsChargeable Boolean? @default(false) @map("food_is_chargeable") + alcoholAvailable Boolean? @default(false) @map("alcohol_available") + trainerAvailable Boolean? @default(false) @map("trainer_available") + trainerIsChargeable Boolean? @default(false) @map("trainer_is_chargeable") + pickUpDropAvailable Boolean? @default(false) @map("pick_up_drop_available") + pickUpDropIsChargeable Boolean? @default(false) @map("pick_up_drop_is_chargeable") + inActivityAvailable Boolean? @default(false) @map("in_activity_available") + inActivityIsChargeable Boolean? @default(false) @map("in_activity_is_chargeable") + equipmentAvailable Boolean? @default(false) @map("equipment_available") + equipmentIsChargeable Boolean? @default(false) @map("equipment_is_chargeable") + cancellationAvailable Boolean? @default(false) @map("cancellation_available") + cancellationAllowedBeforeMins Int? @map("cancellation_allowed_before_mins") + currencyXid Int? @map("currency_xid") + currencies Currencies? @relation(fields: [currencyXid], references: [id], onDelete: Restrict) + sustainabilityScore Int? @map("sustainability_score") + safetyScore Int? @map("safety_score") + totalScore Int? @map("total_score") + isInstantBooking Boolean? @default(false) @map("is_instant_booking") + activityInternalStatus String? @default("pending") @map("activity_internal_status") + activityDisplayStatus String? @default("pending") @map("activity_display_status") + amInternalStatus String? @default("pending") @map("am_internal_status") + amDisplayStatus String? @default("pending") @map("am_display_status") isActive Boolean @default(true) @map("is_active") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") diff --git a/serverless.yml b/serverless.yml index dad67aa..6083dab 100644 --- a/serverless.yml +++ b/serverless.yml @@ -8,7 +8,7 @@ provider: apiGateway: binaryMediaTypes: - - "*/*" + - '*/*' minimumCompressionSize: 0 environment: @@ -41,8 +41,7 @@ provider: - s3:PutObject - s3:GetObject - s3:DeleteObject - Resource: "arn:aws:s3:::${env:S3_BUCKET_NAME}/*" - + Resource: 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*' # ------------------------------------------------------------ # ESBUILD @@ -56,11 +55,10 @@ custom: platform: node concurrency: 10 external: - - "@prisma/client" - - ".prisma" + - '@prisma/client' + - '.prisma' exclude: - - "aws-sdk" - + - 'aws-sdk' # ------------------------------------------------------------ # GLOBAL PACKAGE CONFIG (EMPTY PACKAGE STRATEGY) @@ -68,190 +66,178 @@ custom: package: individually: true patterns: - - "!**/*" # <-- DO NOT include anything globally - + - '!**/*' # <-- DO NOT include anything globally # ------------------------------------------------------------ # FUNCTIONS (ONLY required files included) # ------------------------------------------------------------ functions: - getHosts: handler: src/modules/host/handlers/host.handler package: patterns: - - "src/modules/host/handlers/host.*" - - "src/modules/host/services/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/host/handlers/host.*' + - 'src/modules/host/services/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /host method: get - verifyOtp: handler: src/modules/host/handlers/verifyOtp.handler package: patterns: - - "src/modules/host/handlers/verifyOtp.*" - - "src/modules/host/services/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/host/handlers/verifyOtp.*' + - 'src/modules/host/services/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /host/verify-otp method: post - loginForHost: handler: src/modules/host/handlers/loginForHost.handler package: patterns: - - "src/modules/host/handlers/loginForHost.*" - - "src/modules/host/services/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/host/handlers/loginForHost.*' + - 'src/modules/host/services/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /host/login method: post - registrationOfHost: handler: src/modules/host/handlers/registration.handler package: patterns: - - "src/modules/host/handlers/registration.*" - - "src/modules/host/services/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/host/handlers/registration.*' + - 'src/modules/host/services/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /host/registration method: post - createPasswordForHost: handler: src/modules/host/handlers/createPassword.handler package: patterns: - - "src/modules/host/handlers/createPassword.*" - - "src/modules/host/services/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/host/handlers/createPassword.*' + - 'src/modules/host/services/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /host/create-password method: post - addPaymentDetailsForHost: handler: src/modules/host/handlers/addPaymentDetails.handler package: patterns: - - "src/modules/host/handlers/addPaymentDetails.*" - - "src/modules/host/services/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/host/handlers/addPaymentDetails.*' + - 'src/modules/host/services/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /host/add-payment-details method: post - getHostById: handler: src/modules/host/handlers/getbyidhandler.handler package: patterns: - - "src/modules/host/handlers/getbyidhandler.*" - - "src/modules/host/services/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/host/handlers/getbyidhandler.*' + - 'src/modules/host/services/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /host/getById method: get - getStepperInfo: handler: src/modules/host/handlers/getStepper.handler package: patterns: - - "src/modules/host/handlers/getStepper.handler.*" - - "src/common/utils/handlers/safeHandler.*" - - "src/common/database/**" - - "src/modules/host/services/**" - - "common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/host/handlers/getStepper.handler.*' + - 'src/common/utils/handlers/safeHandler.*' + - 'src/common/database/**' + - 'src/modules/host/services/**' + - 'common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /stepper method: get - minglarRegistration: handler: src/modules/minglaradmin/handlers/registration.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /minglaradmin/registration method: post - minglarLoginForAdmin: handler: src/modules/minglaradmin/handlers/loginForMinglar.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /minglaradmin/login method: post - minglarCreatePassword: handler: src/modules/minglaradmin/handlers/createPassword.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: @@ -262,14 +248,14 @@ functions: handler: src/modules/minglaradmin/handlers/updateProfile.handler package: patterns: - - "src/modules/host/handlers/addCompanyDetails.*" - - "src/modules/host/services/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" - - "node_modules/@aws-sdk/**" - - "node_modules/@smithy/**" + - 'src/modules/host/handlers/addCompanyDetails.*' + - 'src/modules/host/services/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' + - 'node_modules/@aws-sdk/**' + - 'node_modules/@smithy/**' events: - httpApi: @@ -280,57 +266,56 @@ functions: handler: src/modules/minglaradmin/handlers/prepopulateTeammate.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /minglaradmin/prepopulate-Roles method: get - inviteTeammate: handler: src/modules/minglaradmin/handlers/inviteTeammate.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /minglaradmin/invite-teammate method: post - + getAllHostApplication: handler: src/modules/minglaradmin/handlers/getAllHostApplication.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /minglaradmin/get-all-host-applications method: get - + getAllInvitationDetails: handler: src/modules/minglaradmin/handlers/getAllInvitationDetails.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: @@ -341,11 +326,11 @@ functions: handler: src/modules/minglaradmin/handlers/addSuggestion.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: @@ -356,96 +341,109 @@ functions: handler: src/modules/minglaradmin/handlers/getSuggestion.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /minglaradmin/get-suggestion method: get - - + getAllCoadminAndAMDetails: handler: src/modules/minglaradmin/handlers/getAllCoadminAndAM.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /minglaradmin/get-all-coadmin-and-am-details method: get - - + getAllBankAndCurrencyDetails: handler: src/modules/prepopulate/handlers/getAllBankDetails.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /prepopulate/get-all-bank-currency-details method: get - - + getAllPqqQuesAns: handler: src/modules/prepopulate/handlers/getAllPQQQuesWithAns.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /prepopulate/get-all-pqq-ques-ans method: get - - + assignAMToHost: handler: src/modules/minglaradmin/handlers/assignAM.handler package: patterns: - - "src/modules/minglaradmin/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' events: - httpApi: path: /minglaradmin/assign-am-to-host method: patch - addCompanyDetails: handler: src/modules/host/handlers/addCompanyDetails.handler package: patterns: - - "src/modules/host/handlers/addCompanyDetails.*" - - "src/modules/host/services/**" - - "common/**" - - "src/common/**" - - "node_modules/@prisma/client/**" - - "node_modules/.prisma/**" - - "node_modules/@aws-sdk/**" - - "node_modules/@smithy/**" + - 'src/modules/host/handlers/addCompanyDetails.*' + - 'src/modules/host/services/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' + - 'node_modules/@aws-sdk/**' + - 'node_modules/@smithy/**' events: - httpApi: path: /host/add-company-details method: patch + + submitPqqAnswer: + handler: src/modules/host/handlers/submitPqqAns.handler + package: + patterns: + - 'src/modules/host/handlers/submitPqqAns.*' + - 'src/modules/host/services/**' + - 'common/**' + - 'src/common/**' + - 'node_modules/@prisma/client/**' + - 'node_modules/.prisma/**' + - 'node_modules/@aws-sdk/**' + - 'node_modules/@smithy/**' + + events: + - httpApi: + path: /host/submit-pqq-ans + method: put diff --git a/src/modules/host/handlers/submitPqqAns.ts b/src/modules/host/handlers/submitPqqAns.ts new file mode 100644 index 0000000..0130786 --- /dev/null +++ b/src/modules/host/handlers/submitPqqAns.ts @@ -0,0 +1,166 @@ +import config from '@/config/config'; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import AWS from 'aws-sdk'; +import Busboy from 'busboy'; +import crypto from 'crypto'; +import { PrismaService } from '../../../common/database/prisma.service'; +import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost'; +import { safeHandler } from '../../../common/utils/handlers/safeHandler'; +import ApiError from '../../../common/utils/helper/ApiError'; +import { HostService } from '../services/host.service'; + +const prisma = new PrismaService(); +const pqqService = new HostService(prisma); + +const s3 = new AWS.S3({ region: config.aws.region }); + +async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string): Promise { + const uniqueKey = `${crypto.randomUUID()}_${originalName}`; + const s3Key = `${prefix}/${uniqueKey}`; + + 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}`; +} + +export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise => { + try { + // 1) Auth + const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']; + if (!token) throw new ApiError(401, 'Missing token.'); + const user = await verifyHostToken(token); + + // 2) Content-Type check + 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"); + + if (!event.isBase64Encoded) + throw new ApiError(400, "Body must be base64 encoded"); + + const bodyBuffer = Buffer.from(event.body!, "base64"); + + const fields: any = {}; + const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = []; + + // 3) Parse multipart data + await new Promise((resolve, reject) => { + const bb = Busboy({ headers: { 'content-type': contentType } }); + + bb.on('file', (fieldname, file, info) => { + const { filename, mimeType } = info; + const chunks: Buffer[] = []; + let totalSize = 0; + const MAX_SIZE = 5 * 1024 * 1024; // 5 MB + + 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); + }); + + file.on('end', () => { + files.push({ + buffer: Buffer.concat(chunks), + mimeType, + fileName: filename, + fieldName: fieldname, + }); + }); + + file.on('error', (err) => { + reject(new ApiError(400, `File upload error: ${err.message}`)); + }); + }); + + bb.on('field', (fieldname, val) => { + try { + fields[fieldname] = JSON.parse(val); + } catch { + fields[fieldname] = val; + } + }); + + bb.on('close', () => { + console.log("✅ Busboy parsing completed"); + resolve(); + }); + + bb.on('error', (err) => { + console.error("❌ Busboy error:", err); + reject(new ApiError(400, `Multipart parsing error: ${err.message}`)); + }); + + bb.end(bodyBuffer); + }); + + console.log("📌 Parsed Files:", files); + + // 4) Extract required fields + const activityXid = Number(fields.activityXid); + const pqqQuestionXid = Number(fields.pqqQuestionXid); + const pqqAnswerXid = Number(fields.pqqAnswerXid); + const comments = fields.comments || null; + + if (!activityXid) throw new ApiError(400, "activityXid is required"); + if (!pqqQuestionXid) throw new ApiError(400, "pqqQuestionXid is required"); + if (!pqqAnswerXid) throw new ApiError(400, "pqqAnswerXid is required"); + + // 5) Create or update header + const header = await pqqService.createOrUpdateHeader( + activityXid, + pqqQuestionXid, + pqqAnswerXid, + comments + ); + + // 6) Upload files + const uploadedFiles: any[] = []; + + for (const file of files) { + const url = await uploadToS3( + file.buffer, + file.mimeType, + file.fileName, + `ActivityOnboarding/Activity_${activityXid}/supportings` + ); + + const supporting = await pqqService.addSupportingFile( + header.id, + file.mimeType, + url + ); + + uploadedFiles.push(supporting); + } + + return { + statusCode: 200, + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" + }, + body: JSON.stringify({ + success: true, + message: "PQQ answer submitted successfully", + data: { + headerId: header.id, + uploadedFiles + } + }) + }; + + } catch (error: any) { + console.error("❌ Error in submitPqqAnswer:", error); + throw error; + } +}); diff --git a/src/modules/host/services/host.service.ts b/src/modules/host/services/host.service.ts index 11176cd..b6b5682 100644 --- a/src/modules/host/services/host.service.ts +++ b/src/modules/host/services/host.service.ts @@ -450,4 +450,59 @@ export class HostService { return `HOSTREFNO-${String(nextId).padStart(6, '0')}`; } + async createOrUpdateHeader( + activityXid: number, + pqqQuestionXid: number, + pqqAnswerXid: number, + comments: string | null + ) { + // find existing header + const existing = await this.prisma.activityPQQheader.findFirst({ + where: { activityXid, pqqQuestionXid, deletedAt: null } + }); + + if (!existing) { + return await this.prisma.activityPQQheader.create({ + data: { + activityXid, + pqqQuestionXid, + pqqAnswerXid, + comments + } + }); + } + + // mark old supportings deleted + await this.prisma.activityPQQSupportings.updateMany({ + where: { activityPqqHeaderXid: existing.id }, + data: { + isActive: false, + deletedAt: new Date() + } + }); + + // update header + return await this.prisma.activityPQQheader.update({ + where: { id: existing.id }, + data: { + pqqAnswerXid, + comments + } + }); + } + + async addSupportingFile( + headerId: number, + mimeType: string, + fileUrl: string + ) { + return await this.prisma.activityPQQSupportings.create({ + data: { + activityPqqHeaderXid: headerId, + mediaType: mimeType, + mediaFileName: fileUrl + } + }); + } + }