Add handler for creating full activity with related records

This commit is contained in:
paritosh18
2025-12-18 19:37:32 +05:30
parent c9b507f969
commit 53785bd5f2
2 changed files with 338 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service';
const hostService = new HostService(prismaClient);
export const handler = safeHandler(
async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
// Verify authentication token
const token =
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
401,
'This is a protected route. Please provide a valid token.',
);
}
// Verify token and get user info
const userInfo = await verifyHostToken(token);
let body: any = {};
try {
body = 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');
}
// Create full activity and related records
const createdData = await hostService.createFullActivity(userInfo.id, body);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Activity created successfully',
data: createdData
}),
};
},
);

View File

@@ -1867,6 +1867,278 @@ export class HostService {
});
}
/**
* Create a full activity with related records based on payload from the onboarding form.
* This method will create Activities + ActivityOtherDetails + ActivitiesMedia +
* ActivityVenues + ActivityPrices + ActivityFoodTypes + ActivityCuisine +
* ActivityPickUpTransport/Details + ActivityNavigationModes + ActivityEquipments +
* ActivityAmenities + ActivityEligibility and also seed PQQ headers.
*/
async createFullActivity(userId: number, payload: any) {
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;
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;
if (frequenciesXid) {
const freq = await tx.frequencies.findUnique({ where: { id: Number(frequenciesXid) } });
if (!freq) throw new ApiError(404, 'Frequency not found');
}
const referenceNumber = await generateActivityRefNumber(tx);
const activityData: any = {
hostXid: host.id,
activityTypeXid: Number(activityTypeXid),
frequenciesXid: frequenciesXid ? Number(frequenciesXid) : null,
activityRefNumber: referenceNumber,
};
// 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.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.checkOutAddress) activityData.checkOutAddress = String(payload.checkOutAddress).substring(0,150);
if (payload.energyLevelXid) activityData.energyLevelXid = Number(payload.energyLevelXid);
// duration: accept minutes or hours+minutes
if (payload.activityDurationMins) activityData.activityDurationMins = Number(payload.activityDurationMins);
else if (payload.durationHours || payload.durationMins) {
const hrs = Number(payload.durationHours || 0);
const mins = Number(payload.durationMins || 0);
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]);
}
}
if (payload.currencyXid) activityData.currencyXid = Number(payload.currencyXid);
if (payload.sustainabilityScore) activityData.sustainabilityScore = Number(payload.sustainabilityScore);
if (payload.safetyScore) 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) {
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,
}
});
}
// Media
if (Array.isArray(payload.media) && payload.media.length) {
const mediaData = payload.media.map((m: any, idx: number) => ({
activityXid: createdActivity.id,
mediaType: String(m.mediaType || 'image'),
mediaFileName: String(m.mediaFileName),
displayOrder: idx + 1,
}));
await tx.activitiesMedia.createMany({ data: mediaData });
}
// Venues + Prices
if (Array.isArray(payload.venues) && payload.venues.length) {
for (const venue of payload.venues) {
const v = await tx.activityVenues.create({
data: {
activityXid: createdActivity.id,
venueName: String(venue.venueName).substring(0,50),
venueCapacity: venue.venueCapacity ? Number(venue.venueCapacity) : 0,
availableSeats: venue.availableSeats ? Number(venue.availableSeats) : 0,
isMinPeopleReqMandatory: Boolean(venue.isMinPeopleReqMandatory || false),
minPeopleRequired: venue.minPeopleRequired ? Number(venue.minPeopleRequired) : null,
minReqfullfilledBeforeMins: venue.minReqfullfilledBeforeMins ? Number(venue.minReqfullfilledBeforeMins) : null,
venueDescription: venue.venueDescription ? String(venue.venueDescription).substring(0,200) : null,
}
});
if (Array.isArray(venue.prices) && venue.prices.length) {
const pricesData = venue.prices.map((p: any) => ({
activityVenueXid: v.id,
noOfSession: Number(p.noOfSession || 1),
isPackage: Boolean(p.isPackage || false),
sessionValidity: Number(p.sessionValidity || 0),
sessionValidityFrequency: String(p.sessionValidityFrequency || 'Days'),
basePrice: Number(p.basePrice || 0),
sellPrice: Number(p.sellPrice || 0),
}));
await tx.activityPrices.createMany({ data: pricesData });
}
}
}
// Food types
if (Array.isArray(payload.foodTypeIds) && payload.foodTypeIds.length) {
const ft = payload.foodTypeIds.map((id: any) => ({ activityXid: createdActivity.id, foodTypeXid: Number(id) }));
await tx.activityFoodTypes.createMany({ data: ft });
}
// Cuisines
if (Array.isArray(payload.cuisineIds) && payload.cuisineIds.length) {
const cs = payload.cuisineIds.map((id: any) => ({ activityXid: createdActivity.id, foodCuisineXid: Number(id) }));
await tx.activityCuisine.createMany({ data: cs });
}
// Pick up transport + details
if (Array.isArray(payload.pickupTransports) && payload.pickupTransports.length) {
for (const pt of payload.pickupTransports) {
const transport = await tx.activityPickUpTransport.create({
data: {
activityXid: createdActivity.id,
transportModeXid: Number(pt.transportModeXid),
isTransportModeChargeable: Boolean(pt.isTransportModeChargeable || false),
}
});
if (Array.isArray(pt.pickupDetails) && pt.pickupDetails.length) {
const pd = pt.pickupDetails.map((d: any) => ({
activityPickUpTransportXid: transport.id,
isPickUp: Boolean(d.isPickUp || false),
locationLat: d.locationLat ? Number(d.locationLat) : null,
locationLong: d.locationLong ? Number(d.locationLong) : null,
locationAddress: d.locationAddress ? String(d.locationAddress).substring(0,150) : null,
transportBasePrice: d.transportBasePrice ? Number(d.transportBasePrice) : 0,
transportTotalPrice: d.transportTotalPrice ? Number(d.transportTotalPrice) : 0,
}));
await tx.activityPickUpDetails.createMany({ data: pd });
}
}
}
// Navigation modes
if (Array.isArray(payload.navigationModes) && payload.navigationModes.length) {
const navs = payload.navigationModes.map((n: any) => ({
activityXid: createdActivity.id,
navigationModeXid: Number(n.navigationModeXid),
isInActivityChargeable: Boolean(n.isInActivityChargeable || false),
navigationModesBasePrice: Number(n.basePrice || 0),
navigationModesTotalPrice: Number(n.totalPrice || 0),
}));
await tx.activityNavigationModes.createMany({ data: navs });
}
// Equipments
if (Array.isArray(payload.equipments) && payload.equipments.length) {
const eqs = payload.equipments.map((e: any) => ({
activityXid: createdActivity.id,
equipmentName: String(e.equipmentName).substring(0,30),
isEquipmentChargeable: Boolean(e.isEquipmentChargeable || false),
equipmentBasePrice: Number(e.equipmentBasePrice || 0),
equipmentTotalPrice: Number(e.equipmentTotalPrice || 0),
}));
await tx.activityEquipments.createMany({ data: eqs });
}
// Amenities
if (Array.isArray(payload.amenitiesIds) && payload.amenitiesIds.length) {
const ams = payload.amenitiesIds.map((id: any) => ({ activityXid: createdActivity.id, amenitiesXid: Number(id) }));
await tx.activityAmenities.createMany({ data: ams });
}
// Eligibility
if (payload.eligibility) {
const e = payload.eligibility;
await tx.activityEligibility.create({
data: {
activityXid: createdActivity.id,
isAgeRestriction: Boolean(e.isAgeRestriction || false),
ageRestrictionXid: e.ageRestrictionXid ? Number(e.ageRestrictionXid) : null,
isWeightRestriction: Boolean(e.isWeightRestriction || false),
weightRestrictionName: e.weightRestrictionName ? String(e.weightRestrictionName).substring(0,30) : null,
weightEntered: e.weightEntered ? Number(e.weightEntered) : null,
weightIn: e.weightIn ? String(e.weightIn).substring(0,30) : null,
minWeight: e.minWeight ? Number(e.minWeight) : null,
maxWeight: e.maxWeight ? Number(e.maxWeight) : null,
isHeightRestriction: Boolean(e.isHeightRestriction || false),
heightRestrictionName: e.heightRestrictionName ? String(e.heightRestrictionName).substring(0,30) : null,
heightEntered: e.heightEntered ? Number(e.heightEntered) : null,
minHeight: e.minHeight ? Number(e.minHeight) : null,
maxHeight: e.maxHeight ? Number(e.maxHeight) : null,
}
});
}
// ----------------- PQQ seeding (copy of existing logic) -----------------
const questions = await tx.pQQCategories.findMany({
where: { isActive: true },
select: {
id: true,
categoryName: true,
displayOrder: true,
pqqsubCategories: {
where: { isActive: true },
select: {
id: true,
subCategoryName: true,
displayOrder: true,
questions: {
where: { isActive: true },
select: {
id: true,
questionName: true,
maxPoints: true,
displayOrder: true,
},
orderBy: { displayOrder: 'asc' },
},
},
orderBy: { displayOrder: 'asc' },
},
},
orderBy: { displayOrder: 'asc' },
});
const allQuestions: number[] = [];
for (const cat of questions) {
for (const sub of cat.pqqsubCategories) {
for (const q of sub.questions) {
allQuestions.push(q.id);
}
}
}
if (allQuestions.length) {
await tx.activityPQQheader.createMany({
data: allQuestions.map((id) => ({
activityXid: createdActivity.id,
pqqQuestionXid: id,
pqqAnswerXid: null,
})),
});
}
return { activity_xid: createdActivity.id };
});
}
async getAllPQUpdatedResponse(activityXid: number) {
const pqqHeaderData = await this.prisma.activityPQQheader.findMany({