Add handler for creating full activity with related records
This commit is contained in:
@@ -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
|
||||
}),
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user