From 2e4f31868467a36b9ffa54529ad27f5ccee186e1 Mon Sep 17 00:00:00 2001 From: paritosh18 Date: Thu, 18 Dec 2025 20:05:30 +0530 Subject: [PATCH] Add CreateActivityDto and update createFullActivity method for activity creation --- src/modules/host/dto/createActivity.schema.ts | 125 ++++++++++++++++++ .../OnBoarding/CreateNewActivity.ts | 22 ++- src/modules/host/services/host.service.ts | 58 ++++---- 3 files changed, 166 insertions(+), 39 deletions(-) create mode 100644 src/modules/host/dto/createActivity.schema.ts diff --git a/src/modules/host/dto/createActivity.schema.ts b/src/modules/host/dto/createActivity.schema.ts new file mode 100644 index 0000000..8cbd928 --- /dev/null +++ b/src/modules/host/dto/createActivity.schema.ts @@ -0,0 +1,125 @@ +import { z } from 'zod'; + +export const MediaDto = z.object({ + mediaType: z.string().optional(), + mediaFileName: z.string(), +}); + +export const PriceDto = z.object({ + noOfSession: z.number().int().optional().default(1), + isPackage: z.boolean().optional().default(false), + sessionValidity: z.number().int().optional().default(0), + sessionValidityFrequency: z.string().optional().default('Days'), + basePrice: z.number().int().optional().default(0), + sellPrice: z.number().int().optional().default(0), +}); + +export const VenueDto = z.object({ + venueName: z.string(), + venueCapacity: z.number().int().optional().default(0), + availableSeats: z.number().int().optional().default(0), + isMinPeopleReqMandatory: z.boolean().optional().default(false), + minPeopleRequired: z.number().int().nullable().optional(), + minReqfullfilledBeforeMins: z.number().int().nullable().optional(), + venueDescription: z.string().optional(), + prices: z.array(PriceDto).optional().default([]), +}); + +export const PickupDetailDto = z.object({ + isPickUp: z.boolean().optional().default(false), + locationLat: z.number().optional().nullable(), + locationLong: z.number().optional().nullable(), + locationAddress: z.string().optional().nullable(), + transportBasePrice: z.number().int().optional().default(0), + transportTotalPrice: z.number().int().optional().default(0), +}); + +export const PickupTransportDto = z.object({ + transportModeXid: z.number().int(), + isTransportModeChargeable: z.boolean().optional().default(false), + pickupDetails: z.array(PickupDetailDto).optional().default([]), +}); + +export const NavigationModeDto = z.object({ + navigationModeXid: z.number().int(), + isInActivityChargeable: z.boolean().optional().default(false), + basePrice: z.number().int().optional().default(0), + totalPrice: z.number().int().optional().default(0), +}); + +export const EquipmentDto = z.object({ + equipmentName: z.string(), + isEquipmentChargeable: z.boolean().optional().default(false), + equipmentBasePrice: z.number().int().optional().default(0), + equipmentTotalPrice: z.number().int().optional().default(0), +}); + +export const EligibilityDto = z.object({ + isAgeRestriction: z.boolean().optional().default(false), + ageRestrictionXid: z.number().int().nullable().optional(), + isWeightRestriction: z.boolean().optional().default(false), + weightRestrictionName: z.string().optional().nullable(), + weightEntered: z.number().int().nullable().optional(), + weightIn: z.string().optional().nullable(), + minWeight: z.number().int().nullable().optional(), + maxWeight: z.number().int().nullable().optional(), + isHeightRestriction: z.boolean().optional().default(false), + heightRestrictionName: z.string().optional().nullable(), + heightEntered: z.number().int().nullable().optional(), + minHeight: z.number().int().nullable().optional(), + maxHeight: z.number().int().nullable().optional(), +}); + +export const OtherDetailsDto = z.object({ + exclusiveNotes: z.string().optional(), + dosNotes: z.string().optional(), + dontsNotes: z.string().optional(), + tipsNotes: z.string().optional(), + termsAndCondition: z.string().optional(), +}); + +export const CreateActivityDto = z.object({ + activityTypeXid: z.number().int(), + frequenciesXid: z.number().int().nullable().optional(), + activityTitle: z.string().optional(), + activityDescription: z.string().optional(), + checkInLat: z.number().optional().nullable(), + checkInLong: z.number().optional().nullable(), + checkInAddress: z.string().optional().nullable(), + isCheckOutSame: z.boolean().optional().default(true), + checkOutLat: z.number().optional().nullable(), + checkOutLong: z.number().optional().nullable(), + checkOutAddress: z.string().optional().nullable(), + energyLevelXid: z.number().int().nullable().optional(), + activityDurationMins: z.number().int().nullable().optional(), + durationHours: z.number().int().optional(), + durationMins: z.number().int().optional(), + foodAvailable: z.boolean().optional().default(false), + foodIsChargeable: z.boolean().optional().default(false), + alcoholAvailable: z.boolean().optional().default(false), + trainerAvailable: z.boolean().optional().default(false), + trainerIsChargeable: z.boolean().optional().default(false), + pickUpDropAvailable: z.boolean().optional().default(false), + pickUpDropIsChargeable: z.boolean().optional().default(false), + inActivityAvailable: z.boolean().optional().default(false), + inActivityIsChargeable: z.boolean().optional().default(false), + equipmentAvailable: z.boolean().optional().default(false), + equipmentIsChargeable: z.boolean().optional().default(false), + cancellationAvailable: z.boolean().optional().default(false), + currencyXid: z.number().int().nullable().optional(), + sustainabilityScore: z.number().int().optional().nullable(), + safetyScore: z.number().int().optional().nullable(), + isInstantBooking: z.boolean().optional().default(false), + media: z.array(MediaDto).optional().default([]), + venues: z.array(VenueDto).optional().default([]), + foodTypeIds: z.array(z.number().int()).optional().default([]), + cuisineIds: z.array(z.number().int()).optional().default([]), + pickupTransports: z.array(PickupTransportDto).optional().default([]), + navigationModes: z.array(NavigationModeDto).optional().default([]), + equipments: z.array(EquipmentDto).optional().default([]), + amenitiesIds: z.array(z.number().int()).optional().default([]), + eligibility: EligibilityDto.optional(), + otherDetails: OtherDetailsDto.optional(), +}); + +export type CreateActivityInput = z.infer; diff --git a/src/modules/host/handlers/Activity_Hub/OnBoarding/CreateNewActivity.ts b/src/modules/host/handlers/Activity_Hub/OnBoarding/CreateNewActivity.ts index 59e15a5..e0aaa60 100644 --- a/src/modules/host/handlers/Activity_Hub/OnBoarding/CreateNewActivity.ts +++ b/src/modules/host/handlers/Activity_Hub/OnBoarding/CreateNewActivity.ts @@ -5,6 +5,7 @@ import { Context, } from 'aws-lambda'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service'; +import { CreateActivityDto } from '../../../dto/createActivity.schema'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import ApiError from '../../../../../common/utils/helper/ApiError'; import { HostService } from '../../../services/host.service'; @@ -29,26 +30,23 @@ export const handler = safeHandler( // Verify token and get user info const userInfo = await verifyHostToken(token); - let body: any = {}; + let rawBody: any = {}; try { - body = event.body ? JSON.parse(event.body) : {}; + rawBody = event.body ? JSON.parse(event.body) : {}; } catch (err) { throw new ApiError(400, 'Invalid JSON in request body'); } - // Accept multiple possible keys from the frontend payload - const rawActivityType = body.activityTypeXid ?? body.activityType ?? body.activity_type_xid; - const rawFrequencies = body.frequenciesXid ?? body.frequencies ?? body.frequencies_xid; - - const activityTypeXid = rawActivityType !== undefined && rawActivityType !== null ? Number(rawActivityType) : undefined; - const frequenciesXid = rawFrequencies !== undefined && rawFrequencies !== null ? Number(rawFrequencies) : undefined; - - if (!activityTypeXid || isNaN(activityTypeXid)) { - throw new ApiError(400, 'activityTypeXid is required and must be a number'); + // Validate request body with Zod DTO + let dto: any; + try { + dto = CreateActivityDto.parse(rawBody); + } catch (err: any) { + throw new ApiError(400, 'Invalid payload: ' + (err?.message || 'validation failed')); } // Create full activity and related records - const createdData = await hostService.createFullActivity(userInfo.id, body); + const createdData = await hostService.createFullActivity(userInfo.id, dto as any); return { statusCode: 200, diff --git a/src/modules/host/services/host.service.ts b/src/modules/host/services/host.service.ts index dc3c519..5ceb87b 100644 --- a/src/modules/host/services/host.service.ts +++ b/src/modules/host/services/host.service.ts @@ -1874,18 +1874,18 @@ export class HostService { * ActivityPickUpTransport/Details + ActivityNavigationModes + ActivityEquipments + * ActivityAmenities + ActivityEligibility and also seed PQQ headers. */ - async createFullActivity(userId: number, payload: any) { + async createFullActivity(userId: number, payload: import('../dto/createActivity.schema').CreateActivityInput) { return await this.prisma.$transaction(async (tx) => { const host = await tx.hostHeader.findFirst({ where: { userXid: userId, isActive: true } }); if (!host) throw new ApiError(404, 'Host not found for the user'); - const activityTypeXid = payload.activityTypeXid ?? payload.activityType ?? payload.activity_type_xid; + const activityTypeXid = payload.activityTypeXid; if (!activityTypeXid) throw new ApiError(400, 'activityTypeXid is required'); const activityType = await tx.activityTypes.findUnique({ where: { id: Number(activityTypeXid) } }); if (!activityType) throw new ApiError(404, 'Activity type not found'); - const frequenciesXid = payload.frequenciesXid ?? payload.frequencies ?? payload.frequencies_xid; + const frequenciesXid = payload.frequenciesXid ?? null; if (frequenciesXid) { const freq = await tx.frequencies.findUnique({ where: { id: Number(frequenciesXid) } }); if (!freq) throw new ApiError(404, 'Frequency not found'); @@ -1903,12 +1903,12 @@ export class HostService { // Simple mappings if (payload.activityTitle) activityData.activityTitle = String(payload.activityTitle).substring(0, 30); if (payload.activityDescription) activityData.activityDescription = String(payload.activityDescription).substring(0, 80); - if (payload.checkInLat) activityData.checkInLat = Number(payload.checkInLat); - if (payload.checkInLong) activityData.checkInLong = Number(payload.checkInLong); + if (payload.checkInLat !== undefined && payload.checkInLat !== null) activityData.checkInLat = Number(payload.checkInLat); + if (payload.checkInLong !== undefined && payload.checkInLong !== null) activityData.checkInLong = Number(payload.checkInLong); if (payload.checkInAddress) activityData.checkInAddress = String(payload.checkInAddress).substring(0,150); if (payload.isCheckOutSame !== undefined) activityData.isCheckOutSame = Boolean(payload.isCheckOutSame); - if (payload.checkOutLat) activityData.checkOutLat = Number(payload.checkOutLat); - if (payload.checkOutLong) activityData.checkOutLong = Number(payload.checkOutLong); + if (payload.checkOutLat !== undefined && payload.checkOutLat !== null) activityData.checkOutLat = Number(payload.checkOutLat); + if (payload.checkOutLong !== undefined && payload.checkOutLong !== null) activityData.checkOutLong = Number(payload.checkOutLong); if (payload.checkOutAddress) activityData.checkOutAddress = String(payload.checkOutAddress).substring(0,150); if (payload.energyLevelXid) activityData.energyLevelXid = Number(payload.energyLevelXid); @@ -1920,35 +1920,39 @@ export class HostService { activityData.activityDurationMins = hrs * 60 + mins; } - // Booleans - const boolFields = [ - 'foodAvailable','foodIsChargeable','alcoholAvailable','trainerAvailable','trainerIsChargeable', - 'pickUpDropAvailable','pickUpDropIsChargeable','inActivityAvailable','inActivityIsChargeable', - 'equipmentAvailable','equipmentIsChargeable','cancellationAvailable','isInstantBooking' - ]; - for (const k of boolFields) { - if (payload[k] !== undefined) { - activityData[k] = Boolean(payload[k]); - } - } + // Booleans (assign explicitly for type-safety) + if (payload.foodAvailable !== undefined) activityData.foodAvailable = payload.foodAvailable; + if (payload.foodIsChargeable !== undefined) activityData.foodIsChargeable = payload.foodIsChargeable; + if (payload.alcoholAvailable !== undefined) activityData.alcoholAvailable = payload.alcoholAvailable; + if (payload.trainerAvailable !== undefined) activityData.trainerAvailable = payload.trainerAvailable; + if (payload.trainerIsChargeable !== undefined) activityData.trainerIsChargeable = payload.trainerIsChargeable; + if (payload.pickUpDropAvailable !== undefined) activityData.pickUpDropAvailable = payload.pickUpDropAvailable; + if (payload.pickUpDropIsChargeable !== undefined) activityData.pickUpDropIsChargeable = payload.pickUpDropIsChargeable; + if (payload.inActivityAvailable !== undefined) activityData.inActivityAvailable = payload.inActivityAvailable; + if (payload.inActivityIsChargeable !== undefined) activityData.inActivityIsChargeable = payload.inActivityIsChargeable; + if (payload.equipmentAvailable !== undefined) activityData.equipmentAvailable = payload.equipmentAvailable; + if (payload.equipmentIsChargeable !== undefined) activityData.equipmentIsChargeable = payload.equipmentIsChargeable; + if (payload.cancellationAvailable !== undefined) activityData.cancellationAvailable = payload.cancellationAvailable; + if (payload.isInstantBooking !== undefined) activityData.isInstantBooking = payload.isInstantBooking; if (payload.currencyXid) activityData.currencyXid = Number(payload.currencyXid); - if (payload.sustainabilityScore) activityData.sustainabilityScore = Number(payload.sustainabilityScore); - if (payload.safetyScore) activityData.safetyScore = Number(payload.safetyScore); + if (payload.sustainabilityScore !== undefined && payload.sustainabilityScore !== null) activityData.sustainabilityScore = Number(payload.sustainabilityScore); + if (payload.safetyScore !== undefined && payload.safetyScore !== null) activityData.safetyScore = Number(payload.safetyScore); // Create activity const createdActivity = await tx.activities.create({ data: activityData }); - // Other details - if (payload.exclusiveNotes || payload.dosNotes || payload.dontsNotes || payload.tipsNotes || payload.termsAndCondition) { + // Other details (nested DTO) + const od = (payload as any).otherDetails; + if (od && (od.exclusiveNotes || od.dosNotes || od.dontsNotes || od.tipsNotes || od.termsAndCondition)) { await tx.activityOtherDetails.create({ data: { activityXid: createdActivity.id, - exclusiveNotes: payload.exclusiveNotes ? String(payload.exclusiveNotes).substring(0,50) : null, - dosNotes: payload.dosNotes ? String(payload.dosNotes).substring(0,200) : null, - dontsNotes: payload.dontsNotes ? String(payload.dontsNotes).substring(0,200) : null, - tipsNotes: payload.tipsNotes ? String(payload.tipsNotes).substring(0,100) : null, - termsAndCondition: payload.termsAndCondition ? String(payload.termsAndCondition).substring(0,500) : null, + exclusiveNotes: od.exclusiveNotes ? String(od.exclusiveNotes).substring(0,50) : null, + dosNotes: od.dosNotes ? String(od.dosNotes).substring(0,200) : null, + dontsNotes: od.dontsNotes ? String(od.dontsNotes).substring(0,200) : null, + tipsNotes: od.tipsNotes ? String(od.tipsNotes).substring(0,100) : null, + termsAndCondition: od.termsAndCondition ? String(od.termsAndCondition).substring(0,500) : null, } }); }