From e286cffa493d0b9ac3ed428bfd5fb61a1f33bb90 Mon Sep 17 00:00:00 2001 From: Mayank Mishra Date: Mon, 5 Jan 2026 13:28:25 +0530 Subject: [PATCH] fixed the schema and the NA problem and multiple transport creation --- prisma/schema.prisma | 47 +++++++------- src/modules/host/dto/createActivity.schema.ts | 18 +++--- src/modules/host/handlers/mediaUploadToS3.ts | 61 +++++++++++-------- src/modules/host/services/host.service.ts | 50 ++++++++------- 4 files changed, 95 insertions(+), 81 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c594389..8073f26 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -874,19 +874,19 @@ model Activities { checkOutAddress String? @map("check_out_address") @db.VarChar(150) set_early_checkin_time_mins String @default("null") @map("set_early_checkin_time_mins") @db.VarChar(200) 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") - is_late_checking_allowed Boolean? @default(false) @map("is_late_checking_allowed") - equipmentAvailable Boolean? @default(false) @map("equipment_available") - equipmentIsChargeable Boolean? @default(false) @map("equipment_is_chargeable") - cancellationAvailable Boolean? @default(false) @map("cancellation_available") + foodAvailable Boolean? @map("food_available") + foodIsChargeable Boolean? @map("food_is_chargeable") + alcoholAvailable Boolean? @map("alcohol_available") + trainerAvailable Boolean? @map("trainer_available") + trainerIsChargeable Boolean? @map("trainer_is_chargeable") + pickUpDropAvailable Boolean? @map("pick_up_drop_available") + pickUpDropIsChargeable Boolean? @map("pick_up_drop_is_chargeable") + inActivityAvailable Boolean? @map("in_activity_available") + inActivityIsChargeable Boolean? @map("in_activity_is_chargeable") + is_late_checking_allowed Boolean? @map("is_late_checking_allowed") + equipmentAvailable Boolean? @map("equipment_available") + equipmentIsChargeable Boolean? @map("equipment_is_chargeable") + cancellationAvailable Boolean? @map("cancellation_available") // 🔹 Creator / owner userId Int? user User? @relation("UserActivities", fields: [userId], references: [id]) @@ -1385,7 +1385,6 @@ model ActivityPickUpDetails { createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") deletedAt DateTime? @map("deleted_at") - activityPickUpTransport ActivityPickUpTransport[] activityPickUpTransportTaxes ActivityPickUpTransportTaxes[] @@map("activity_pick_up_details") @@ -1393,17 +1392,15 @@ model ActivityPickUpDetails { } model ActivityPickUpTransport { - id Int @id @default(autoincrement()) - activityXid Int @map("activity_xid") - activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade) - transportModeXid Int @map("transport_mode_xid") - transportMode TransportModes @relation(fields: [transportModeXid], references: [id], onDelete: Restrict) - isActive Boolean @default(true) @map("is_active") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - deletedAt DateTime? @map("deleted_at") - activityPickUpDetails ActivityPickUpDetails? @relation(fields: [activityPickUpDetailsId], references: [id]) - activityPickUpDetailsId Int? + id Int @id @default(autoincrement()) + activityXid Int @map("activity_xid") + activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade) + transportModeXid Int @map("transport_mode_xid") + transportMode TransportModes @relation(fields: [transportModeXid], references: [id], onDelete: Restrict) + isActive Boolean @default(true) @map("is_active") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + deletedAt DateTime? @map("deleted_at") @@map("activity_pick_up_transport") @@schema("act") diff --git a/src/modules/host/dto/createActivity.schema.ts b/src/modules/host/dto/createActivity.schema.ts index 353895f..0fedaf4 100644 --- a/src/modules/host/dto/createActivity.schema.ts +++ b/src/modules/host/dto/createActivity.schema.ts @@ -47,7 +47,7 @@ export const PickupTransportDto = z.object({ /* ================= EQUIPMENT ================= */ export const EquipmentDto = z.object({ equipmentName: z.string(), - isEquipmentChargeable: z.boolean().optional().default(false), + isEquipmentChargeable: z.boolean().optional(), equipmentBasePrice: z.number().int().optional().default(0), equipmentTotalPrice: z.number().int().optional().default(0), }); @@ -55,7 +55,7 @@ export const EquipmentDto = z.object({ /* ================= NAVIGATION MODE ================= */ export const NavigationModeDto = z.object({ navigationModeXid: z.number().int(), - isChargeable: z.boolean().optional().default(false), + isChargeable: z.boolean().optional(), totalPrice: z.number().int().optional().default(0), }); @@ -114,23 +114,23 @@ export const CreateActivityDto = z.object({ durationHours: z.number().int().optional(), durationMins: z.number().int().optional(), - foodAvailable: z.boolean().optional().default(false), + foodAvailable: z.boolean().optional(), foodIsChargeable: z.boolean().optional().default(false), - alcoholAvailable: z.boolean().optional().default(false), + alcoholAvailable: z.boolean().optional(), - trainerAvailable: z.boolean().optional().default(false), + trainerAvailable: z.boolean().optional(), trainerIsChargeable: z.boolean().optional().default(false), - pickUpDropAvailable: z.boolean().optional().default(false), + pickUpDropAvailable: z.boolean().optional(), pickUpDropIsChargeable: z.boolean().optional().default(false), - inActivityAvailable: z.boolean().optional().default(false), + inActivityAvailable: z.boolean().optional(), inActivityIsChargeable: z.boolean().optional().default(false), - equipmentAvailable: z.boolean().optional().default(false), + equipmentAvailable: z.boolean().optional(), equipmentIsChargeable: z.boolean().optional().default(false), - cancellationAvailable: z.boolean().optional().default(false), + cancellationAvailable: z.boolean().optional(), cancellationAllowedBeforeMins: z.number().int().nullable().optional(), currencyXid: z.number().int().nullable().optional(), diff --git a/src/modules/host/handlers/mediaUploadToS3.ts b/src/modules/host/handlers/mediaUploadToS3.ts index 728eeb8..7ddb977 100644 --- a/src/modules/host/handlers/mediaUploadToS3.ts +++ b/src/modules/host/handlers/mediaUploadToS3.ts @@ -1,4 +1,3 @@ -// mediaPresignUpload.ts import { APIGatewayProxyHandler } from 'aws-lambda'; import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; @@ -11,10 +10,10 @@ const s3 = new S3Client({ region: config.aws.region }); export const handler: APIGatewayProxyHandler = async (event) => { try { const body = JSON.parse(event.body || '{}'); - const { fileName, mimeType } = body; + const { files } = body; - if (!fileName || !mimeType) { - throw new ApiError(400, 'fileName and mimeType are required'); + if (!Array.isArray(files) || files.length === 0) { + throw new ApiError(400, 'files array is required'); } const activityXid = event.pathParameters?.activityXid; @@ -22,35 +21,45 @@ export const handler: APIGatewayProxyHandler = async (event) => { throw new ApiError(400, 'activityXid is required in path parameters'); } - const safeFileName = fileName - .trim() - .replace(/\s+/g, '_') - .replace(/[^a-zA-Z0-9._-]/g, '') - .toLowerCase(); + const results = []; - // const key = `temporary/uploads/activity/activity_${activityXid}/${uuid()}_${safeFileName}`; - const key = `ActivityOnboarding/Activity_${activityXid}/Artifacts/${uuid()}_${safeFileName}`; + for (const file of files) { + const { fileName, mimeType } = file; - const command = new PutObjectCommand({ - Bucket: config.aws.bucketName!, - Key: key, - ContentType: mimeType, // IMPORTANT: must match frontend header - // ❌ DO NOT SET ACL - }); + if (!fileName || !mimeType) { + throw new ApiError(400, 'Each file must have fileName and mimeType'); + } - const uploadUrl = await getSignedUrl(s3, command, { - expiresIn: 300, // 5 minutes - }); + const safeFileName = fileName + .trim() + .replace(/\s+/g, '_') + .replace(/[^a-zA-Z0-9._-]/g, '') + .toLowerCase(); - return response(200, { - uploadUrl, - key, - fileUrl: `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${key}`, - }); + const key = `ActivityOnboarding/Activity_${activityXid}/Artifacts/${uuid()}_${safeFileName}`; + + const command = new PutObjectCommand({ + Bucket: config.aws.bucketName!, + Key: key, + ContentType: mimeType, + }); + + const uploadUrl = await getSignedUrl(s3, command, { + expiresIn: 300, + }); + + results.push({ + uploadUrl, + key, + fileUrl: `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${key}`, + }); + } + + return response(200, { files: results }); } catch (err) { console.error(err); - return response(500, 'Failed to generate presigned URL'); + return response(500, 'Failed to generate presigned URLs'); } }; diff --git a/src/modules/host/services/host.service.ts b/src/modules/host/services/host.service.ts index 2c6cf20..741ecf0 100644 --- a/src/modules/host/services/host.service.ts +++ b/src/modules/host/services/host.service.ts @@ -1747,6 +1747,7 @@ export class HostService { pickUpDropAvailable: true, pickUpDropIsChargeable: true, activityDurationMins: true, + totalScore: true, activityType: { select: { id: true, @@ -1830,6 +1831,7 @@ export class HostService { }, }, }, + orderBy: { createdAt: 'asc' }, }, }, }, @@ -2367,6 +2369,12 @@ export class HostService { const toBool = (v: any) => v === true || v === 'true' || v === 1 || v === '1'; + const toBoolOrNull = (v: any): boolean | null => { + if (v === null || v === undefined || v === '') return null; + return v === true || v === 'true' || v === 1 || v === '1'; + }; + + const toNumber = (v: any) => v === undefined || v === null || v === '' ? undefined : Number(v); @@ -2413,16 +2421,16 @@ export class HostService { /* ===================================================== * HARD NORMALIZATION (SERVICE-LEVEL) * ===================================================== */ - payload.foodAvailable = toBool(payload.foodAvailable); - payload.alcoholAvailable = toBool(payload.alcoholAvailable); - payload.trainerAvailable = toBool(payload.trainerAvailable); - payload.pickUpDropAvailable = toBool(payload.pickUpDropAvailable); - payload.inActivityAvailable = toBool(payload.inActivityAvailable); - payload.equipmentAvailable = toBool(payload.equipmentAvailable); - payload.cancellationAvailable = toBool(payload.cancellationAvailable); + payload.foodAvailable = toBoolOrNull(payload.foodAvailable); + payload.alcoholAvailable = toBoolOrNull(payload.alcoholAvailable); + payload.trainerAvailable = toBoolOrNull(payload.trainerAvailable); + payload.pickUpDropAvailable = toBoolOrNull(payload.pickUpDropAvailable); + payload.inActivityAvailable = toBoolOrNull(payload.inActivityAvailable); + payload.equipmentAvailable = toBoolOrNull(payload.equipmentAvailable); + payload.cancellationAvailable = toBoolOrNull(payload.cancellationAvailable); payload.isInstantBooking = toBool(payload.isInstantBooking); payload.isCheckOutSame = toBool(payload.isCheckOutSame); - payload.alcoholAvailable = toBool(payload.alcoholAvailable); + payload.alcoholAvailable = toBoolOrNull(payload.alcoholAvailable); payload.trainerTotalAmount = toNumber(payload.trainerTotalAmount); payload.cancellationAllowedBeforeMins = toNumber( @@ -2967,7 +2975,7 @@ export class HostService { await tx.activityPickUpTransport.deleteMany({ where: { - activityPickUpDetailsId: { in: oldPickupDetailIds }, + activityXid: Number(activityXid) }, }); @@ -2978,10 +2986,18 @@ export class HostService { if (Array.isArray(payload.pickupTransports)) { for (const transport of payload.pickupTransports) { - // transport.transportModeXid is here ✅ + + // ✅ CREATE TRANSPORT ONCE PER MODE + const pickupTransport = await tx.activityPickUpTransport.create({ + data: { + activityXid: activityXid, + transportModeXid: transport.transportModeXid, + }, + }); if (Array.isArray(transport.pickupDetails)) { for (const detail of transport.pickupDetails) { + const totalPrice = toNumber(detail.transportTotalPrice) ?? 0; const { basePrice, taxDetails } = computeBasePriceAndTaxes( @@ -2989,7 +3005,7 @@ export class HostService { rootTaxes, ); - /* 1️⃣ CREATE PICKUP DETAILS (PRICE OWNER) */ + /* 1️⃣ CREATE PICKUP DETAILS */ const pickupDetail = await tx.activityPickUpDetails.create({ data: { activitiesXid: activityXid, @@ -3002,16 +3018,7 @@ export class HostService { }, }); - /* 2️⃣ CREATE TRANSPORT MODE */ - await tx.activityPickUpTransport.create({ - data: { - activityXid: activityXid, // required by your schema - transportModeXid: transport.transportModeXid, - activityPickUpDetailsId: pickupDetail.id, - }, - }); - - /* 3️⃣ CREATE TAXES */ + /* 2️⃣ CREATE TAXES */ if (taxDetails.length) { await tx.activityPickUpTransportTaxes.createMany({ data: taxDetails.map((t) => ({ @@ -3027,6 +3034,7 @@ export class HostService { } } + /* -------------------------------- * 1️⃣2️⃣ CLEAN & CREATE NAVIGATION MODES WITH TAXES * -------------------------------- */