fixed the schema and the NA problem and multiple transport creation
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
* -------------------------------- */
|
||||
|
||||
Reference in New Issue
Block a user