feat: add checkAvailabilityDetails API endpoint and implement schedule details retrieval logic

This commit is contained in:
paritosh18
2026-02-09 15:34:13 +05:30
parent 73c528d1cc
commit c216d128a6
3 changed files with 223 additions and 1 deletions

View File

@@ -1,9 +1,9 @@
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { getPresignedUrl } from '../../../common/middlewares/aws/getPreSignedUrl';
import { ACTIVITY_AM_DISPLAY_STATUS, ACTIVITY_AM_INTERNAL_STATUS, ACTIVITY_DISPLAY_STATUS, ACTIVITY_INTERNAL_STATUS, SCHEDULING_TYPE } from '../../../common/utils/constants/host.constant';
import ApiError from '../../../common/utils/helper/ApiError';
import { ScheduleActivityDTO } from '../../../common/utils/validation/host/createSchedulingOfAct.validation';
import { getPresignedUrl } from '../../../common/middlewares/aws/getPreSignedUrl';
import config from '../../../config/config';
@@ -317,6 +317,100 @@ export class SchedulingService {
return response;
}
/**
* Return full schedule header + venue + slots for a given activity and date
*/
async getScheduleDetailsForDate(
activityXid: number,
selectedDate: string
) {
const date = new Date(selectedDate);
if (isNaN(date.getTime())) {
throw new ApiError(400, 'Invalid date format');
}
const weekDay = date.toLocaleDateString('en-US', { weekday: 'long' }).toUpperCase();
const dayOfMonth = date.getDate();
const scheduleHeaders = await this.prisma.scheduleHeader.findMany({
where: {
activityXid,
isActive: true,
startDate: { lte: date },
OR: [
{ endDate: null },
{ endDate: { gte: date } },
],
},
include: {
activityVenue: {
select: {
id: true,
venueName: true,
venueLabel: true,
venueCapacity: true,
},
},
scheduleRecurrences: {
where: { isActive: true },
},
ScheduleDetails: {
where: {
isActive: true,
OR: [
{ occurenceDate: date },
{ weekDay: weekDay },
{ dayOfMonth: dayOfMonth },
],
},
},
Cancellations: {
where: {
occurenceDate: date,
isActive: true,
},
},
},
});
if (!scheduleHeaders.length) return [];
const response = scheduleHeaders.map((header) => {
const cancelledSlotIds = new Set(header.Cancellations.map(c => c.slotXid));
const slots = header.ScheduleDetails
.filter(slot => !cancelledSlotIds.has(slot.id))
.map(slot => ({
slotId: slot.id,
occurenceDate: slot.occurenceDate,
weekDay: slot.weekDay,
dayOfMonth: slot.dayOfMonth,
startTime: slot.startTime,
endTime: slot.endTime,
maxCapacity: slot.maxCapacity,
}));
return {
scheduleHeaderXid: header.id,
scheduleType: header.scheduleType,
startDate: header.startDate,
endDate: header.endDate,
earlyCheckInMins: header.earlyCheckInMins,
bookingCutOffMins: header.bookingCutOffMins,
activityVenue: {
venueXid: header.activityVenue.id,
venueName: header.activityVenue.venueName,
venueLabel: header.activityVenue.venueLabel,
venueCapacity: header.activityVenue.venueCapacity,
},
slots,
};
});
return response;
}
async getVenueFromVenueXid(venueXid: number, activityXid: number) {
return await this.prisma.activityVenues.findUnique({
where: { id: venueXid, activityXid: activityXid, isActive: true },

View File

@@ -0,0 +1,113 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { SchedulingService } from '../../../host/services/activityScheduling.service';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
const schedulingService = new SchedulingService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
const activityXid = Number(event.pathParameters?.activity_xid);
if (!activityXid || isNaN(activityXid)) {
throw new ApiError(400, 'Valid activityXid is required');
}
// selected date may be passed as query param `selectedDate`
const selectedDate =
event.queryStringParameters?.selectedDate ||
event.queryStringParameters?.date ||
(event.body ? JSON.parse(event.body).selectedDate : undefined);
if (!selectedDate) {
throw new ApiError(400, 'selectedDate query parameter is required');
}
// Fetch activity details (basic) and schedule details for the selected date
const activityDetails = await userService.getActivityDetailsById(userId, activityXid);
const scheduleDetails = await schedulingService.getScheduleDetailsForDate(activityXid, selectedDate);
// Shape response to match UI: only include fields shown in image
const activity = activityDetails.activity;
// Rooms: combine ActivityVenues with schedule info per venue
const rooms = (activity.ActivityVenues || []).map((v: any) => {
// find schedule header for this venue
const header = scheduleDetails.find((h: any) => h.activityVenue?.venueXid === v.id);
const slotCount = header ? (header.slots || []).length : 0;
return {
venueXid: v.id,
venueName: v.venueName,
venueLabel: v.venueLabel,
venueCapacity: v.venueCapacity,
availableSeats: v.availableSeats ?? null,
price: v.ActivityPrices?.[0]?.sellPrice ?? null,
slotsCount: slotCount,
};
});
// Slots: aggregate slots across scheduleDetails
const slots: any[] = [];
for (const h of scheduleDetails) {
for (const s of h.slots || []) {
// status heuristic based on maxCapacity
let status = 'Available';
if (s.maxCapacity === 0) status = 'Housefull';
else if (s.maxCapacity <= 2) status = '2 Slots Left';
else if (s.maxCapacity <= 5) status = 'Fast Filling';
slots.push({
slotId: s.slotId,
startTime: s.startTime,
endTime: s.endTime,
status,
maxCapacity: s.maxCapacity,
venueXid: h.activityVenue?.venueXid,
});
}
}
// derive check-in/out from slot times (earliest start, latest end)
const startTimes = slots.map(s => s.startTime).filter(Boolean);
const endTimes = slots.map(s => s.endTime).filter(Boolean);
const checkInTime = startTimes.length ? startTimes.sort()[0] : null;
const checkOutTime = endTimes.length ? endTimes.sort().reverse()[0] : null;
const responsePayload = {
selectedDate,
rooms,
slots,
checkInTime,
checkOutTime,
};
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({ success: true, data: responsePayload }),
};
});