Files
web/server/routers/hotels/query.ts
Tobias Johansson 0cbffc7904 Merged in feat/SW-613-refactor-hotelreservation-sidepeek (pull request #805)
Feat/SW-613 refactor hotelreservation sidepeek

* feat(SW-613): move sidepeek paralell route to apply for all of hotelreservation

* feat(SW-613): refactor sidepeek logic to a unified approach for hotelreservation flow

* feat(SW-613): fix issue where room was not selected properly in sidepeek

* fix(SW-613): move back preload to layout

* fix(SW-613): move preload to dedicated file

* fix(SW-613): refactor sidepeek to work with hotel page

* feat(SW-613): added sidepeek button for room card


Approved-by: Simon.Emanuelsson
2024-11-06 12:09:27 +00:00

1157 lines
33 KiB
TypeScript

import { metrics } from "@opentelemetry/api"
import { Lang } from "@/constants/languages"
import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { GetHotelPage } from "@/lib/graphql/Query/HotelPage/HotelPage.graphql"
import { request } from "@/lib/graphql/request"
import {
badRequestError,
notFound,
serverErrorByStatus,
} from "@/server/errors/trpc"
import {
contentStackUidWithServiceProcedure,
publicProcedure,
router,
safeProtectedServiceProcedure,
serviceProcedure,
} from "@/server/trpc"
import { toApiLang } from "@/server/utils"
import { hotelPageSchema } from "../contentstack/hotelPage/output"
import {
fetchHotelPageRefs,
generatePageTags,
getHotelPageCounter,
validateHotelPageRefs,
} from "../contentstack/hotelPage/utils"
import { getVerifiedUser, parsedUser } from "../user/query"
import {
getRoomPackagesInputSchema,
getRoomPackagesSchema,
} from "./schemas/packages"
import {
getBreakfastPackageInputSchema,
getHotelDataInputSchema,
getHotelInputSchema,
getHotelsAvailabilityInputSchema,
getRatesInputSchema,
getRoomsAvailabilityInputSchema,
getSelectedRoomAvailabilityInputSchema,
type HotelDataInput,
HotelIncludeEnum,
} from "./input"
import {
breakfastPackagesSchema,
getHotelDataSchema,
getHotelsAvailabilitySchema,
getRatesSchema,
getRoomsAvailabilitySchema,
} from "./output"
import tempRatesData from "./tempRatesData.json"
import {
getCitiesByCountry,
getCountries,
getLocations,
TWENTYFOUR_HOURS,
} from "./utils"
import { FacilityCardTypeEnum } from "@/types/components/hotelPage/facilities"
import type { BedType } from "@/types/components/hotelReservation/enterDetails/bedType"
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import type { RequestOptionsWithOutBody } from "@/types/fetch"
import type { Facility } from "@/types/hotel"
import type { GetHotelPageData } from "@/types/trpc/routers/contentstack/hotelPage"
const meter = metrics.getMeter("trpc.hotels")
const getHotelCounter = meter.createCounter("trpc.hotel.get")
const getHotelSuccessCounter = meter.createCounter("trpc.hotel.get-success")
const getHotelFailCounter = meter.createCounter("trpc.hotel.get-fail")
const getPackagesCounter = meter.createCounter("trpc.hotel.packages.get")
const getPackagesSuccessCounter = meter.createCounter(
"trpc.hotel.packages.get-success"
)
const getPackagesFailCounter = meter.createCounter(
"trpc.hotel.packages.get-fail"
)
const hotelsAvailabilityCounter = meter.createCounter(
"trpc.hotel.availability.hotels"
)
const hotelsAvailabilitySuccessCounter = meter.createCounter(
"trpc.hotel.availability.hotels-success"
)
const hotelsAvailabilityFailCounter = meter.createCounter(
"trpc.hotel.availability.hotels-fail"
)
const roomsAvailabilityCounter = meter.createCounter(
"trpc.hotel.availability.rooms"
)
const roomsAvailabilitySuccessCounter = meter.createCounter(
"trpc.hotel.availability.rooms-success"
)
const roomsAvailabilityFailCounter = meter.createCounter(
"trpc.hotel.availability.rooms-fail"
)
const selectedRoomAvailabilityCounter = meter.createCounter(
"trpc.hotel.availability.room"
)
const selectedRoomAvailabilitySuccessCounter = meter.createCounter(
"trpc.hotel.availability.room-success"
)
const selectedRoomAvailabilityFailCounter = meter.createCounter(
"trpc.hotel.availability.room-fail"
)
const breakfastPackagesCounter = meter.createCounter("trpc.package.breakfast")
const breakfastPackagesSuccessCounter = meter.createCounter(
"trpc.package.breakfast-success"
)
const breakfastPackagesFailCounter = meter.createCounter(
"trpc.package.breakfast-fail"
)
async function getContentstackData(lang: Lang, uid?: string | null) {
if (!uid) {
return null
}
const contentPageRefsData = await fetchHotelPageRefs(lang, uid)
const contentPageRefs = validateHotelPageRefs(contentPageRefsData, lang, uid)
if (!contentPageRefs) {
return null
}
const tags = generatePageTags(contentPageRefs, lang)
getHotelPageCounter.add(1, { lang, uid })
console.info(
"contentstack.hotelPage start",
JSON.stringify({
query: { lang, uid },
})
)
const response = await request<GetHotelPageData>(
GetHotelPage,
{
locale: lang,
uid,
},
{
cache: "force-cache",
next: {
tags,
},
}
)
if (!response.data) {
throw notFound(response)
}
const hotelPageData = hotelPageSchema.safeParse(response.data)
if (!hotelPageData.success) {
console.error(
`Failed to validate Hotel Page - (uid: ${uid}, lang: ${lang})`
)
console.error(hotelPageData.error)
return null
}
return hotelPageData.data.hotel_page
}
export async function getHotelData(
input: HotelDataInput,
serviceToken: string
) {
const { hotelId, language, include, isCardOnlyPayment } = input
const params: Record<string, string> = {
hotelId,
language,
}
if (include) {
params.include = include.join(",")
}
getHotelCounter.add(1, {
hotelId,
language,
include,
})
console.info(
"api.hotels.hotelData start",
JSON.stringify({ query: { hotelId, params } })
)
const apiResponse = await api.get(
api.endpoints.v1.Hotel.Hotels.hotel(hotelId),
{
headers: {
Authorization: `Bearer ${serviceToken}`,
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
getHotelFailCounter.add(1, {
hotelId,
language,
include,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.hotelData error",
JSON.stringify({
query: { hotelId, params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const validateHotelData = getHotelDataSchema.safeParse(apiJson)
if (!validateHotelData.success) {
getHotelFailCounter.add(1, {
hotelId,
language,
include,
error_type: "validation_error",
error: JSON.stringify(validateHotelData.error),
})
console.error(
"api.hotels.hotelData validation error",
JSON.stringify({
query: { hotelId, params },
error: validateHotelData.error,
})
)
throw badRequestError()
}
getHotelSuccessCounter.add(1, {
hotelId,
language,
include,
})
console.info(
"api.hotels.hotelData success",
JSON.stringify({
query: { hotelId, params: params },
})
)
if (isCardOnlyPayment) {
validateHotelData.data.data.attributes.merchantInformationData.alternatePaymentOptions =
[]
}
return validateHotelData.data
}
export const hotelQueryRouter = router({
get: contentStackUidWithServiceProcedure
.input(getHotelInputSchema)
.query(async ({ ctx, input }) => {
const { lang, uid } = ctx
const { include } = input
const contentstackData = await getContentstackData(lang, uid)
const hotelId = contentstackData?.hotel_page_id
if (!hotelId) {
throw notFound(`Hotel not found for uid: ${uid}`)
}
const apiLang = toApiLang(lang)
const params: Record<string, string> = {
hotelId,
language: apiLang,
}
if (include) {
params.include = include.join(",")
}
getHotelCounter.add(1, { hotelId, lang, include })
console.info(
"api.hotels.hotel start",
JSON.stringify({
query: { hotelId, params },
})
)
const apiResponse = await api.get(
api.endpoints.v1.Hotel.Hotels.hotel(hotelId),
{
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
getHotelFailCounter.add(1, {
hotelId,
lang,
include,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.hotel error",
JSON.stringify({
query: { hotelId, params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
throw serverErrorByStatus(apiResponse.status, apiResponse)
}
const apiJson = await apiResponse.json()
const validatedHotelData = getHotelDataSchema.safeParse(apiJson)
if (!validatedHotelData.success) {
getHotelFailCounter.add(1, {
hotelId,
lang,
include,
error_type: "validation_error",
error: JSON.stringify(validatedHotelData.error),
})
console.error(
"api.hotels.hotel validation error",
JSON.stringify({
query: { hotelId, params },
error: validatedHotelData.error,
})
)
throw badRequestError()
}
const included = validatedHotelData.data.included || []
const hotelAttributes = validatedHotelData.data.data.attributes
const images = hotelAttributes.gallery?.smallerImages
const hotelAlerts = hotelAttributes.meta?.specialAlerts || []
const roomCategories = included
? included.filter((item) => item.type === "roomcategories")
: []
const activities = contentstackData?.content
? contentstackData?.content[0]
: null
const facilities: Facility[] = [
{
...apiJson.data.attributes.restaurantImages,
id: FacilityCardTypeEnum.restaurant,
},
{
...apiJson.data.attributes.conferencesAndMeetings,
id: FacilityCardTypeEnum.conference,
},
{
...apiJson.data.attributes.healthAndWellness,
id: FacilityCardTypeEnum.wellness,
},
]
getHotelSuccessCounter.add(1, { hotelId, lang, include })
console.info(
"api.hotels.hotel success",
JSON.stringify({
query: { hotelId, params: params },
})
)
return {
hotelId,
hotelName: hotelAttributes.name,
hotelDescription: hotelAttributes.hotelContent.texts.descriptions.short,
hotelLocation: hotelAttributes.location,
hotelAddress: hotelAttributes.address,
hotelRatings: hotelAttributes.ratings,
hotelDetailedFacilities: hotelAttributes.detailedFacilities,
hotelImages: images,
pointsOfInterest: hotelAttributes.pointsOfInterest,
roomCategories,
activitiesCard: activities?.upcoming_activities_card,
facilities,
alerts: hotelAlerts,
faq: contentstackData?.faq,
}
}),
availability: router({
hotels: serviceProcedure
.input(getHotelsAvailabilityInputSchema)
.query(async ({ input, ctx }) => {
const { lang } = ctx
const apiLang = toApiLang(lang)
const {
cityId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
attachedProfileId,
} = input
const params: Record<string, string | number> = {
roomStayStartDate,
roomStayEndDate,
adults,
...(children && { children }),
promotionCode,
reservationProfileType,
attachedProfileId,
language: apiLang,
}
hotelsAvailabilityCounter.add(1, {
cityId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
})
console.info(
"api.hotels.hotelsAvailability start",
JSON.stringify({ query: { cityId, params } })
)
const apiResponse = await api.get(
api.endpoints.v1.Availability.city(cityId),
{
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
hotelsAvailabilityFailCounter.add(1, {
cityId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.hotelsAvailability error",
JSON.stringify({
query: { cityId, params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const validateAvailabilityData =
getHotelsAvailabilitySchema.safeParse(apiJson)
if (!validateAvailabilityData.success) {
hotelsAvailabilityFailCounter.add(1, {
cityId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
error_type: "validation_error",
error: JSON.stringify(validateAvailabilityData.error),
})
console.error(
"api.hotels.hotelsAvailability validation error",
JSON.stringify({
query: { cityId, params },
error: validateAvailabilityData.error,
})
)
throw badRequestError()
}
hotelsAvailabilitySuccessCounter.add(1, {
cityId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
})
console.info(
"api.hotels.hotelsAvailability success",
JSON.stringify({
query: { cityId, params: params },
})
)
return {
availability: validateAvailabilityData.data.data
.filter(
(hotels) =>
hotels.attributes.status === AvailabilityEnum.Available
)
.flatMap((hotels) => hotels.attributes),
}
}),
rooms: serviceProcedure
.input(getRoomsAvailabilityInputSchema)
.query(async ({ input, ctx }) => {
const {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
attachedProfileId,
rateCode,
} = input
const params: Record<string, string | number | undefined> = {
roomStayStartDate,
roomStayEndDate,
adults,
...(children && { children }),
promotionCode,
reservationProfileType,
attachedProfileId,
}
roomsAvailabilityCounter.add(1, {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
})
console.info(
"api.hotels.roomsAvailability start",
JSON.stringify({ query: { hotelId, params } })
)
const apiResponse = await api.get(
api.endpoints.v1.Availability.hotel(hotelId.toString()),
{
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
roomsAvailabilityFailCounter.add(1, {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.roomsAvailability error",
JSON.stringify({
query: { hotelId, params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const validateAvailabilityData =
getRoomsAvailabilitySchema.safeParse(apiJson)
if (!validateAvailabilityData.success) {
roomsAvailabilityFailCounter.add(1, {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
error_type: "validation_error",
error: JSON.stringify(validateAvailabilityData.error),
})
console.error(
"api.hotels.roomsAvailability validation error",
JSON.stringify({
query: { hotelId, params },
error: validateAvailabilityData.error,
})
)
throw badRequestError()
}
roomsAvailabilitySuccessCounter.add(1, {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
})
console.info(
"api.hotels.roomsAvailability success",
JSON.stringify({
query: { hotelId, params: params },
})
)
if (rateCode) {
validateAvailabilityData.data.mustBeGuaranteed =
validateAvailabilityData.data.rateDefinitions.filter(
(rate) => rate.rateCode === rateCode
)[0].mustBeGuaranteed
}
return validateAvailabilityData.data
}),
room: serviceProcedure
.input(getSelectedRoomAvailabilityInputSchema)
.query(async ({ input, ctx }) => {
const {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
attachedProfileId,
rateCode,
roomTypeCode,
} = input
const params: Record<string, string | number | undefined> = {
roomStayStartDate,
roomStayEndDate,
adults,
...(children && { children }),
promotionCode,
reservationProfileType,
attachedProfileId,
language: toApiLang(ctx.lang),
}
selectedRoomAvailabilityCounter.add(1, {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
})
console.info(
"api.hotels.selectedRoomAvailability start",
JSON.stringify({ query: { hotelId, params } })
)
const apiResponseAvailability = await api.get(
api.endpoints.v1.Availability.hotel(hotelId.toString()),
{
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
},
params
)
if (!apiResponseAvailability.ok) {
const text = await apiResponseAvailability.text()
selectedRoomAvailabilityFailCounter.add(1, {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
error_type: "http_error",
error: JSON.stringify({
status: apiResponseAvailability.status,
statusText: apiResponseAvailability.statusText,
text,
}),
})
console.error(
"api.hotels.selectedRoomAvailability error",
JSON.stringify({
query: { hotelId, params },
error: {
status: apiResponseAvailability.status,
statusText: apiResponseAvailability.statusText,
text,
},
})
)
return null
}
const apiJsonAvailability = await apiResponseAvailability.json()
const validateAvailabilityData =
getRoomsAvailabilitySchema.safeParse(apiJsonAvailability)
if (!validateAvailabilityData.success) {
selectedRoomAvailabilityFailCounter.add(1, {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
error_type: "validation_error",
error: JSON.stringify(validateAvailabilityData.error),
})
console.error(
"api.hotels.selectedRoomAvailability validation error",
JSON.stringify({
query: { hotelId, params },
error: validateAvailabilityData.error,
})
)
throw badRequestError()
}
const hotelData = await getHotelData(
{
hotelId,
language: ctx.lang,
include: [HotelIncludeEnum.RoomCategories],
},
ctx.serviceToken
)
const selectedRoom = validateAvailabilityData.data.roomConfigurations
.filter((room) => room.status === "Available")
.find((room) => room.roomTypeCode === roomTypeCode)
const availableRoomsInCategory =
validateAvailabilityData.data.roomConfigurations.filter(
(room) =>
room.status === "Available" &&
room.roomType === selectedRoom?.roomType
)
if (!selectedRoom) {
console.error("No matching room found")
return null
}
const memberRate = selectedRoom.products.find(
(rate) => rate.productType.member?.rateCode === rateCode
)?.productType.member
const publicRate = selectedRoom.products.find(
(rate) => rate.productType.public?.rateCode === rateCode
)?.productType.public
const mustBeGuaranteed =
validateAvailabilityData.data.rateDefinitions.filter(
(rate) => rate.rateCode === rateCode
)[0].mustBeGuaranteed
const cancellationText =
validateAvailabilityData.data.rateDefinitions.find(
(rate) => rate.rateCode === rateCode
)?.cancellationText ?? ""
const bedTypes = availableRoomsInCategory
.map((availRoom) => {
const matchingRoom = hotelData?.included
?.find((room) => room.name === availRoom.roomType)
?.roomTypes.find(
(roomType) => roomType.code === availRoom.roomTypeCode
)
if (matchingRoom) {
return {
description: matchingRoom.mainBed.description,
size: matchingRoom.mainBed.widthRange,
value: matchingRoom.code,
}
}
})
.filter((bed): bed is BedType => Boolean(bed))
selectedRoomAvailabilitySuccessCounter.add(1, {
hotelId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
promotionCode,
reservationProfileType,
})
console.info(
"api.hotels.selectedRoomAvailability success",
JSON.stringify({
query: { hotelId, params: params },
})
)
return {
selectedRoom,
mustBeGuaranteed,
cancellationText,
memberRate,
publicRate,
bedTypes,
}
}),
}),
rates: router({
get: publicProcedure
.input(getRatesInputSchema)
.query(async ({ input, ctx }) => {
// TODO: Do a real API call when the endpoint is ready
// const { hotelId } = input
// const params = new URLSearchParams()
// const apiLang = toApiLang(language)
// params.set("hotelId", hotelId.toString())
// params.set("language", apiLang)
console.info("api.hotels.rates start", JSON.stringify({}))
const validatedHotelData = getRatesSchema.safeParse(tempRatesData)
if (!tempRatesData) {
console.error(
"api.hotels.rates error",
JSON.stringify({ error: null })
)
//Can't return null here since consuming component does not handle null yet
// return null
}
if (!validatedHotelData.success) {
console.error(
"api.hotels.rates validation error",
JSON.stringify({
error: validatedHotelData.error,
})
)
throw badRequestError()
}
console.info("api.hotels.rates success", JSON.stringify({}))
return validatedHotelData.data
}),
}),
hotelData: router({
get: serviceProcedure
.input(getHotelDataInputSchema)
.query(async ({ ctx, input }) => {
return getHotelData(input, ctx.serviceToken)
}),
}),
locations: router({
get: serviceProcedure.query(async function ({ ctx }) {
const searchParams = new URLSearchParams()
searchParams.set("language", toApiLang(ctx.lang))
const options: RequestOptionsWithOutBody = {
// needs to clear default option as only
// cache or next.revalidate is permitted
cache: undefined,
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
next: {
revalidate: TWENTYFOUR_HOURS,
},
}
const countries = await getCountries(options, searchParams, ctx.lang)
let citiesByCountry = null
if (countries) {
citiesByCountry = await getCitiesByCountry(
countries,
options,
searchParams,
ctx.lang
)
}
const locations = await getLocations(
ctx.lang,
options,
searchParams,
citiesByCountry
)
if (Array.isArray(locations)) {
return {
data: locations,
}
}
return locations
}),
}),
packages: router({
get: serviceProcedure
.input(getRoomPackagesInputSchema)
.query(async ({ input, ctx }) => {
const { hotelId, startDate, endDate, adults, children, packageCodes } =
input
const { lang } = ctx
const apiLang = toApiLang(lang)
const searchParams = new URLSearchParams({
startDate,
endDate,
adults: adults.toString(),
children: children.toString(),
language: apiLang,
})
packageCodes.forEach((code) => {
searchParams.append("packageCodes", code)
})
const params = searchParams.toString()
getPackagesCounter.add(1, {
hotelId,
})
console.info(
"api.hotels.packages start",
JSON.stringify({ query: { hotelId, params } })
)
const apiResponse = await api.get(
api.endpoints.v1.Package.Packages.hotel(hotelId),
{
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
},
params
)
if (!apiResponse.ok) {
getPackagesFailCounter.add(1, {
hotelId,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
}),
})
console.error(
"api.hotels.packages error",
JSON.stringify({ query: { hotelId, params } })
)
throw serverErrorByStatus(apiResponse.status, apiResponse)
}
const apiJson = await apiResponse.json()
const validatedPackagesData = getRoomPackagesSchema.safeParse(apiJson)
if (!validatedPackagesData.success) {
getHotelFailCounter.add(1, {
hotelId,
error_type: "validation_error",
error: JSON.stringify(validatedPackagesData.error),
})
console.error(
"api.hotels.packages validation error",
JSON.stringify({
query: { hotelId, params },
error: validatedPackagesData.error,
})
)
throw badRequestError()
}
getPackagesSuccessCounter.add(1, {
hotelId,
})
console.info(
"api.hotels.packages success",
JSON.stringify({ query: { hotelId, params: params } })
)
return validatedPackagesData.data
}),
breakfast: safeProtectedServiceProcedure
.input(getBreakfastPackageInputSchema)
.query(async function ({ ctx, input }) {
const { lang } = ctx
const apiLang = toApiLang(lang)
const params = {
Adults: input.adults,
EndDate: dt(input.toDate).format("YYYY-MM-DD"),
StartDate: dt(input.fromDate).format("YYYY-MM-DD"),
language: apiLang,
}
const metricsData = { ...params, hotelId: input.hotelId }
breakfastPackagesCounter.add(1, metricsData)
console.info(
"api.package.breakfast start",
JSON.stringify({ query: metricsData })
)
const apiResponse = await api.get(
api.endpoints.v1.Package.Breakfast.hotel(input.hotelId),
{
cache: undefined,
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
next: {
revalidate: 60,
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
breakfastPackagesFailCounter.add(1, {
...metricsData,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.hotelsAvailability error",
JSON.stringify({
query: metricsData,
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const breakfastPackages = breakfastPackagesSchema.safeParse(apiJson)
if (!breakfastPackages.success) {
hotelsAvailabilityFailCounter.add(1, {
...metricsData,
error_type: "validation_error",
error: JSON.stringify(breakfastPackages.error),
})
console.error(
"api.package.breakfast validation error",
JSON.stringify({
query: metricsData,
error: breakfastPackages.error,
})
)
return null
}
breakfastPackagesSuccessCounter.add(1, metricsData)
console.info(
"api.package.breakfast success",
JSON.stringify({
query: metricsData,
})
)
if (ctx.session?.token) {
const apiUser = await getVerifiedUser({ session: ctx.session })
if (apiUser && !("error" in apiUser)) {
const user = parsedUser(apiUser.data, false)
if (
user.membership &&
["L6", "L7"].includes(user.membership.membershipLevel)
) {
const originalBreakfastPackage = breakfastPackages.data.find(
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
)
const freeBreakfastPackage = breakfastPackages.data.find(
(pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
)
if (freeBreakfastPackage && freeBreakfastPackage.localPrice) {
if (
originalBreakfastPackage &&
originalBreakfastPackage.localPrice
) {
freeBreakfastPackage.localPrice.price =
originalBreakfastPackage.localPrice.price
}
return [freeBreakfastPackage]
}
}
}
}
return breakfastPackages.data.filter(
(pkg) => pkg.code !== BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
)
}),
}),
})