added api for scan qr and get info of person and actvity
This commit is contained in:
@@ -106,3 +106,21 @@ operatorGetActivitiesByDate:
|
||||
- httpApi:
|
||||
path: /activities-by-date
|
||||
method: get
|
||||
|
||||
operatorGetReservationByCheckInCode:
|
||||
handler: src/modules/host/handlers/operator/getReservationByCheckInCode.handler
|
||||
memorySize: 384
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/host/handlers/operator/**'
|
||||
- 'src/modules/host/services/operatorActivity.service.ts'
|
||||
- 'src/modules/host/dto/operator.activity.dto.ts'
|
||||
- 'src/common/**'
|
||||
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /reservation-by-checkin-code
|
||||
method: get
|
||||
|
||||
@@ -2,6 +2,10 @@ export class GetActivitiesByDateRequestDTO {
|
||||
activityDate?: string; // ISO date format: YYYY-MM-DD (optional, defaults to today)
|
||||
}
|
||||
|
||||
export class GetReservationByCheckInCodeRequestDTO {
|
||||
checkInCode!: string;
|
||||
}
|
||||
|
||||
export class DateBreakdownDTO {
|
||||
date: string;
|
||||
count: number;
|
||||
@@ -24,3 +28,53 @@ export class GetActivitiesByDateResponseDTO {
|
||||
totalCount: number;
|
||||
};
|
||||
}
|
||||
|
||||
export class OperatorReservationPersonalDetailsDTO {
|
||||
fullName: string;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
role: string | null;
|
||||
mobileNumber: string | null;
|
||||
profileImage: string | null;
|
||||
profileImagePreSignedUrl: string | null;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export class OperatorReservationBookingInformationDTO {
|
||||
activityName: string | null;
|
||||
slot: string | null;
|
||||
startTime: string | null;
|
||||
endTime: string | null;
|
||||
track: string | null;
|
||||
trackLabel: string | null;
|
||||
date: string | null;
|
||||
dateLabel: string | null;
|
||||
bookedOn: string | null;
|
||||
bookedOnLabel: string | null;
|
||||
}
|
||||
|
||||
export class OperatorReservationBookingIncludedDTO {
|
||||
food: string;
|
||||
selectedFoodTypes: string[];
|
||||
equipment: string;
|
||||
selectedEquipments: string[];
|
||||
trainerOrGuide: string;
|
||||
pickupLocation: string | null;
|
||||
}
|
||||
|
||||
export class OperatorReservationByCheckInCodeDTO {
|
||||
itineraryHeaderXid: number;
|
||||
itineraryActivityXid: number;
|
||||
bookingId: string | null;
|
||||
checkInCode: string | null;
|
||||
reservationStatus: string | null;
|
||||
personalDetails: OperatorReservationPersonalDetailsDTO;
|
||||
bookingInformation: OperatorReservationBookingInformationDTO;
|
||||
bookingIncluded: OperatorReservationBookingIncludedDTO;
|
||||
}
|
||||
|
||||
export class GetReservationByCheckInCodeResponseDTO {
|
||||
success: boolean;
|
||||
message: string;
|
||||
data: OperatorReservationByCheckInCodeDTO;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
APIGatewayProxyEvent,
|
||||
APIGatewayProxyResult,
|
||||
Context,
|
||||
} from 'aws-lambda';
|
||||
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||
import { verifyOperatorToken } from '../../../../common/middlewares/jwt/authForHost';
|
||||
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||
import { GetReservationByCheckInCodeRequestDTO } from '../../dto/operator.activity.dto';
|
||||
import { OperatorActivityService } from '../../services/operatorActivity.service';
|
||||
|
||||
const operatorActivityService = new OperatorActivityService(prismaClient);
|
||||
|
||||
export const handler = safeHandler(
|
||||
async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context,
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
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.',
|
||||
);
|
||||
}
|
||||
|
||||
const operatorInfo = await verifyOperatorToken(token);
|
||||
const operatorId = Number(operatorInfo.id);
|
||||
|
||||
if (!operatorId || Number.isNaN(operatorId)) {
|
||||
throw new ApiError(400, 'Invalid operator ID');
|
||||
}
|
||||
|
||||
const requestDTO: GetReservationByCheckInCodeRequestDTO = {
|
||||
checkInCode:
|
||||
event.queryStringParameters?.checkInCode?.trim() ||
|
||||
event.queryStringParameters?.offlineCode?.trim() ||
|
||||
'',
|
||||
};
|
||||
|
||||
if (!requestDTO.checkInCode) {
|
||||
throw new ApiError(400, 'checkInCode is required.');
|
||||
}
|
||||
|
||||
const result = await operatorActivityService.getReservationByCheckInCode(
|
||||
operatorId,
|
||||
requestDTO.checkInCode,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Reservation details fetched successfully',
|
||||
data: result,
|
||||
}),
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -3,12 +3,63 @@ import { PrismaClient } from '@prisma/client';
|
||||
import { getPresignedUrl } from '../../../common/middlewares/aws/getPreSignedUrl';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import config from '../../../config/config';
|
||||
import { ActivitySummaryDTO } from '../dto/operator.activity.dto';
|
||||
import {
|
||||
ActivitySummaryDTO,
|
||||
OperatorReservationByCheckInCodeDTO,
|
||||
} from '../dto/operator.activity.dto';
|
||||
|
||||
const formatDateOnly = (date: Date): string => date.toISOString().split('T')[0];
|
||||
|
||||
const formatReadableDateTime = (date: Date): string =>
|
||||
new Intl.DateTimeFormat('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true,
|
||||
}).format(date);
|
||||
|
||||
const formatReadableDate = (date: Date): string =>
|
||||
new Intl.DateTimeFormat('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
}).format(date);
|
||||
|
||||
const getDateLabel = (date: Date): string => {
|
||||
const today = new Date();
|
||||
return formatDateOnly(today) === formatDateOnly(date)
|
||||
? 'Today'
|
||||
: formatReadableDate(date);
|
||||
};
|
||||
|
||||
const buildFullName = (
|
||||
firstName?: string | null,
|
||||
lastName?: string | null,
|
||||
): string => `${firstName ?? ''} ${lastName ?? ''}`.trim();
|
||||
|
||||
@Injectable()
|
||||
export class OperatorActivityService {
|
||||
constructor(private prisma: PrismaClient) {}
|
||||
|
||||
private async attachPresignedUrl(
|
||||
fileName?: string | null,
|
||||
): Promise<string | null> {
|
||||
if (!fileName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return await getPresignedUrl(config.aws.bucketName, fileName);
|
||||
} catch (error) {
|
||||
console.error(`Failed to generate presigned URL for ${fileName}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getActivitiesByDate(
|
||||
operatorId: number,
|
||||
activityDate?: string,
|
||||
@@ -41,9 +92,7 @@ export class OperatorActivityService {
|
||||
const hostXids = hostMembers.map((m) => m.hostXid);
|
||||
|
||||
// Use today's date if not provided
|
||||
const queryDate = activityDate
|
||||
? new Date(activityDate)
|
||||
: new Date();
|
||||
const queryDate = activityDate ? new Date(activityDate) : new Date();
|
||||
|
||||
// Validate date format
|
||||
if (isNaN(queryDate.getTime())) {
|
||||
@@ -59,47 +108,48 @@ export class OperatorActivityService {
|
||||
|
||||
// Get all schedule occurrences from today onwards (not just query date)
|
||||
// This includes future dates to show all scheduled dates
|
||||
const allScheduleOccurrences = await this.prisma.scheduleOccurences.findMany({
|
||||
where: {
|
||||
occurenceDate: {
|
||||
gte: queryDate,
|
||||
},
|
||||
isActive: true,
|
||||
scheduleHeader: {
|
||||
activity: {
|
||||
hostXid: {
|
||||
in: hostXids,
|
||||
const allScheduleOccurrences =
|
||||
await this.prisma.scheduleOccurences.findMany({
|
||||
where: {
|
||||
occurenceDate: {
|
||||
gte: queryDate,
|
||||
},
|
||||
isActive: true,
|
||||
scheduleHeader: {
|
||||
activity: {
|
||||
hostXid: {
|
||||
in: hostXids,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
scheduleHeader: {
|
||||
include: {
|
||||
activity: {
|
||||
select: {
|
||||
id: true,
|
||||
activityTitle: true,
|
||||
isActive: true,
|
||||
ActivitiesMedia: {
|
||||
where: {
|
||||
isCoverImage: true,
|
||||
isActive: true,
|
||||
include: {
|
||||
scheduleHeader: {
|
||||
include: {
|
||||
activity: {
|
||||
select: {
|
||||
id: true,
|
||||
activityTitle: true,
|
||||
isActive: true,
|
||||
ActivitiesMedia: {
|
||||
where: {
|
||||
isCoverImage: true,
|
||||
isActive: true,
|
||||
},
|
||||
select: {
|
||||
mediaFileName: true,
|
||||
},
|
||||
take: 1,
|
||||
},
|
||||
select: {
|
||||
mediaFileName: true,
|
||||
},
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
occurenceDate: 'asc',
|
||||
},
|
||||
});
|
||||
orderBy: {
|
||||
occurenceDate: 'asc',
|
||||
},
|
||||
});
|
||||
|
||||
if (allScheduleOccurrences.length === 0) {
|
||||
return {
|
||||
@@ -124,7 +174,7 @@ export class OperatorActivityService {
|
||||
// Process all schedule occurrences to build activity list with all scheduled dates
|
||||
for (const occurrence of allScheduleOccurrences) {
|
||||
const activity = occurrence.scheduleHeader.activity;
|
||||
|
||||
|
||||
if (!activityMap.has(activity.id)) {
|
||||
let coverImage: string | null = null;
|
||||
let coverImageUrl: string | null = null;
|
||||
@@ -156,12 +206,16 @@ export class OperatorActivityService {
|
||||
} else {
|
||||
// Add to scheduled dates if not already present
|
||||
const existingActivity = activityMap.get(activity.id)!;
|
||||
const occurrenceDateStr = new Date(occurrence.occurenceDate).toISOString().split('T')[0];
|
||||
const occurrenceDateStr = new Date(occurrence.occurenceDate)
|
||||
.toISOString()
|
||||
.split('T')[0];
|
||||
const dateExists = existingActivity.scheduledDates.some(
|
||||
(d) => d.toISOString().split('T')[0] === occurrenceDateStr
|
||||
(d) => d.toISOString().split('T')[0] === occurrenceDateStr,
|
||||
);
|
||||
if (!dateExists) {
|
||||
existingActivity.scheduledDates.push(new Date(occurrence.occurenceDate));
|
||||
existingActivity.scheduledDates.push(
|
||||
new Date(occurrence.occurenceDate),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,22 +254,22 @@ export class OperatorActivityService {
|
||||
number,
|
||||
Map<string, Set<number>>
|
||||
>();
|
||||
|
||||
|
||||
allBookings.forEach((booking) => {
|
||||
if (booking.activityXid) {
|
||||
if (!bookingsByActivityAndDate.has(booking.activityXid)) {
|
||||
bookingsByActivityAndDate.set(booking.activityXid, new Map());
|
||||
}
|
||||
|
||||
|
||||
const dateStr = new Date(booking.occurenceDate)
|
||||
.toISOString()
|
||||
.split('T')[0];
|
||||
const dateMap = bookingsByActivityAndDate.get(booking.activityXid)!;
|
||||
|
||||
|
||||
if (!dateMap.has(dateStr)) {
|
||||
dateMap.set(dateStr, new Set());
|
||||
}
|
||||
|
||||
|
||||
dateMap.get(dateStr)!.add(booking.itineraryHeaderXid);
|
||||
}
|
||||
});
|
||||
@@ -224,8 +278,10 @@ export class OperatorActivityService {
|
||||
const activities: ActivitySummaryDTO[] = Array.from(
|
||||
activityMap.values(),
|
||||
).map((activity) => {
|
||||
const activityBookings = bookingsByActivityAndDate.get(activity.activityId);
|
||||
|
||||
const activityBookings = bookingsByActivityAndDate.get(
|
||||
activity.activityId,
|
||||
);
|
||||
|
||||
// Get bookings for each scheduled date
|
||||
const dateBreakdown = Array.from(allScheduledDates)
|
||||
.sort()
|
||||
@@ -239,7 +295,8 @@ export class OperatorActivityService {
|
||||
|
||||
// Count for the query date only
|
||||
const queryDateStr = queryDate.toISOString().split('T')[0];
|
||||
const countForQueryDate = activityBookings?.get(queryDateStr)?.size || 0;
|
||||
const countForQueryDate =
|
||||
activityBookings?.get(queryDateStr)?.size || 0;
|
||||
|
||||
return {
|
||||
activityName: activity.activityTitle,
|
||||
@@ -253,7 +310,9 @@ export class OperatorActivityService {
|
||||
// Total count is bookings for the requested date only
|
||||
const queryDateStr = queryDate.toISOString().split('T')[0];
|
||||
const totalCount = allBookings.filter(
|
||||
(b) => new Date(b.occurenceDate).toISOString().split('T')[0] === queryDateStr
|
||||
(b) =>
|
||||
new Date(b.occurenceDate).toISOString().split('T')[0] ===
|
||||
queryDateStr,
|
||||
).length;
|
||||
|
||||
return {
|
||||
@@ -271,4 +330,269 @@ export class OperatorActivityService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getReservationByCheckInCode(
|
||||
operatorId: number,
|
||||
checkInCode: string,
|
||||
): Promise<OperatorReservationByCheckInCodeDTO> {
|
||||
try {
|
||||
const normalizedCheckInCode = checkInCode.trim();
|
||||
if (!normalizedCheckInCode) {
|
||||
throw new ApiError(400, 'checkInCode is required');
|
||||
}
|
||||
|
||||
const hostMembers = await this.prisma.hostMembers.findMany({
|
||||
where: {
|
||||
userXid: operatorId,
|
||||
isActive: true,
|
||||
memberStatus: 'accepted',
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
hostXid: true,
|
||||
},
|
||||
});
|
||||
|
||||
const hostXids = hostMembers.map((member) => member.hostXid);
|
||||
if (hostXids.length === 0) {
|
||||
throw new ApiError(404, 'Reservation not found for this check-in code');
|
||||
}
|
||||
|
||||
const reservation = await this.prisma.itineraryDetails.findFirst({
|
||||
where: {
|
||||
offlineCode: normalizedCheckInCode,
|
||||
itineraryKind: 'ACTIVITY',
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
itineraryActivity: {
|
||||
itineraryType: 'ACTIVITY',
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
activity: {
|
||||
hostXid: {
|
||||
in: hostXids,
|
||||
},
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
itineraryMemberXid: true,
|
||||
offlineCode: true,
|
||||
activityStatus: true,
|
||||
createdAt: true,
|
||||
paidOn: true,
|
||||
itineraryMember: {
|
||||
select: {
|
||||
id: true,
|
||||
memberRole: true,
|
||||
member: {
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
mobileNumber: true,
|
||||
profileImage: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
itineraryActivity: {
|
||||
select: {
|
||||
id: true,
|
||||
occurenceDate: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
bookingStatus: true,
|
||||
venue: {
|
||||
select: {
|
||||
id: true,
|
||||
venueName: true,
|
||||
venueLabel: true,
|
||||
},
|
||||
},
|
||||
itineraryHeader: {
|
||||
select: {
|
||||
id: true,
|
||||
itineraryNo: true,
|
||||
createdAt: true,
|
||||
},
|
||||
},
|
||||
activity: {
|
||||
select: {
|
||||
id: true,
|
||||
activityTitle: true,
|
||||
checkInAddress: true,
|
||||
ActivityPickUpDetails: {
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
isPickUp: true,
|
||||
locationAddress: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
itineraryActivitySelections: {
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
itineraryMemberXid: true,
|
||||
isFoodOpted: true,
|
||||
isTrainerOpted: true,
|
||||
selectedFoodTypes: {
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
activityFoodType: {
|
||||
select: {
|
||||
foodType: {
|
||||
select: {
|
||||
foodTypeName: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
selectedEquipments: {
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
activityEquipment: {
|
||||
select: {
|
||||
equipmentName: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!reservation) {
|
||||
throw new ApiError(404, 'Reservation not found for this check-in code');
|
||||
}
|
||||
|
||||
const activitySelection =
|
||||
reservation.itineraryActivity.itineraryActivitySelections.find(
|
||||
(selection) =>
|
||||
selection.itineraryMemberXid === reservation.itineraryMemberXid,
|
||||
) ?? null;
|
||||
|
||||
const selectedFoodTypes = (activitySelection?.selectedFoodTypes ?? [])
|
||||
.map(
|
||||
(selectedFoodType) =>
|
||||
selectedFoodType.activityFoodType.foodType.foodTypeName,
|
||||
)
|
||||
.filter(Boolean);
|
||||
const selectedEquipments = (activitySelection?.selectedEquipments ?? [])
|
||||
.map(
|
||||
(selectedEquipment) =>
|
||||
selectedEquipment.activityEquipment.equipmentName,
|
||||
)
|
||||
.filter(Boolean);
|
||||
|
||||
const pickupLocation =
|
||||
reservation.itineraryActivity.activity.ActivityPickUpDetails.find(
|
||||
(detail) => detail.isPickUp && detail.locationAddress,
|
||||
)?.locationAddress ??
|
||||
reservation.itineraryActivity.activity.ActivityPickUpDetails.find(
|
||||
(detail) => detail.locationAddress,
|
||||
)?.locationAddress ??
|
||||
reservation.itineraryActivity.activity.checkInAddress ??
|
||||
null;
|
||||
|
||||
const member = reservation.itineraryMember.member;
|
||||
const fullName =
|
||||
buildFullName(member.firstName, member.lastName) || 'Guest';
|
||||
const profileImagePreSignedUrl = await this.attachPresignedUrl(
|
||||
member.profileImage,
|
||||
);
|
||||
const occurenceDate = new Date(
|
||||
reservation.itineraryActivity.occurenceDate,
|
||||
);
|
||||
const bookedOnDate =
|
||||
reservation.paidOn ??
|
||||
reservation.createdAt ??
|
||||
reservation.itineraryActivity.itineraryHeader.createdAt;
|
||||
|
||||
return {
|
||||
itineraryHeaderXid: reservation.itineraryActivity.itineraryHeader.id,
|
||||
itineraryActivityXid: reservation.itineraryActivity.id,
|
||||
bookingId: reservation.itineraryActivity.itineraryHeader.itineraryNo,
|
||||
checkInCode: reservation.offlineCode,
|
||||
reservationStatus:
|
||||
reservation.activityStatus ??
|
||||
reservation.itineraryActivity.bookingStatus,
|
||||
personalDetails: {
|
||||
fullName,
|
||||
firstName: member.firstName,
|
||||
lastName: member.lastName,
|
||||
role: reservation.itineraryMember.memberRole,
|
||||
mobileNumber: member.mobileNumber,
|
||||
profileImage: member.profileImage,
|
||||
profileImagePreSignedUrl,
|
||||
tags: [],
|
||||
},
|
||||
bookingInformation: {
|
||||
activityName: reservation.itineraryActivity.activity.activityTitle,
|
||||
slot:
|
||||
reservation.itineraryActivity.startTime &&
|
||||
reservation.itineraryActivity.endTime
|
||||
? `${reservation.itineraryActivity.startTime} - ${reservation.itineraryActivity.endTime}`
|
||||
: null,
|
||||
startTime: reservation.itineraryActivity.startTime,
|
||||
endTime: reservation.itineraryActivity.endTime,
|
||||
track: reservation.itineraryActivity.venue?.venueName ?? null,
|
||||
trackLabel: reservation.itineraryActivity.venue?.venueLabel ?? null,
|
||||
date: formatDateOnly(occurenceDate),
|
||||
dateLabel: getDateLabel(occurenceDate),
|
||||
bookedOn: bookedOnDate ? bookedOnDate.toISOString() : null,
|
||||
bookedOnLabel: bookedOnDate
|
||||
? formatReadableDateTime(bookedOnDate)
|
||||
: null,
|
||||
},
|
||||
bookingIncluded: {
|
||||
food:
|
||||
selectedFoodTypes.length > 0 ? selectedFoodTypes.join(', ') : 'No',
|
||||
selectedFoodTypes,
|
||||
equipment: selectedEquipments.length > 0 ? 'Yes' : 'No',
|
||||
selectedEquipments,
|
||||
trainerOrGuide: activitySelection?.isTrainerOpted ? 'Yes' : 'No',
|
||||
pickupLocation,
|
||||
},
|
||||
// description1: null,
|
||||
// description2: null,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new ApiError(
|
||||
500,
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Error fetching reservation details',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user