feat(SW-664): Hotel listing component and queries for content pages
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
import { metrics } from "@opentelemetry/api"
|
||||
|
||||
import * as api from "@/lib/api"
|
||||
import { dt } from "@/lib/dt"
|
||||
import { badRequestError } from "@/server/errors/trpc"
|
||||
import {
|
||||
contentStackBaseWithServiceProcedure,
|
||||
publicProcedure,
|
||||
router,
|
||||
safeProtectedServiceProcedure,
|
||||
@@ -13,12 +12,14 @@ import { toApiLang } from "@/server/utils"
|
||||
|
||||
import { cache } from "@/utils/cache"
|
||||
|
||||
import { getHotelPageUrl } from "../contentstack/hotelPage/utils"
|
||||
import { getVerifiedUser, parsedUser } from "../user/query"
|
||||
import {
|
||||
getBreakfastPackageInputSchema,
|
||||
getCityCoordinatesInputSchema,
|
||||
getHotelDataInputSchema,
|
||||
getHotelsAvailabilityInputSchema,
|
||||
getHotelsInput,
|
||||
getRatesInputSchema,
|
||||
getRoomPackagesInputSchema,
|
||||
getRoomsAvailabilityInputSchema,
|
||||
@@ -33,10 +34,35 @@ import {
|
||||
getRoomPackagesSchema,
|
||||
getRoomsAvailabilitySchema,
|
||||
} from "./output"
|
||||
import {
|
||||
breakfastPackagesCounter,
|
||||
breakfastPackagesFailCounter,
|
||||
breakfastPackagesSuccessCounter,
|
||||
getHotelCounter,
|
||||
getHotelFailCounter,
|
||||
getHotelsCounter,
|
||||
getHotelsFailCounter,
|
||||
getHotelsSuccessCounter,
|
||||
getHotelSuccessCounter,
|
||||
getPackagesCounter,
|
||||
getPackagesFailCounter,
|
||||
getPackagesSuccessCounter,
|
||||
hotelsAvailabilityCounter,
|
||||
hotelsAvailabilityFailCounter,
|
||||
hotelsAvailabilitySuccessCounter,
|
||||
roomsAvailabilityCounter,
|
||||
roomsAvailabilityFailCounter,
|
||||
roomsAvailabilitySuccessCounter,
|
||||
selectedRoomAvailabilityCounter,
|
||||
selectedRoomAvailabilityFailCounter,
|
||||
selectedRoomAvailabilitySuccessCounter,
|
||||
} from "./telemetry"
|
||||
import tempRatesData from "./tempRatesData.json"
|
||||
import {
|
||||
getCitiesByCountry,
|
||||
getCountries,
|
||||
getHotelIdsByCityId,
|
||||
getHotelIdsByCountry,
|
||||
getLocations,
|
||||
TWENTYFOUR_HOURS,
|
||||
} from "./utils"
|
||||
@@ -45,57 +71,9 @@ import type { BedTypeSelection } from "@/types/components/hotelReservation/enter
|
||||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||||
import { HotelTypeEnum } from "@/types/enums/hotelType"
|
||||
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
||||
|
||||
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"
|
||||
)
|
||||
import type { Hotel } from "@/types/hotel"
|
||||
import type { HotelPageUrl } from "@/types/trpc/routers/contentstack/hotelPage"
|
||||
import type { CityLocation } from "@/types/trpc/routers/hotel/locations"
|
||||
|
||||
export const getHotelData = cache(
|
||||
async (input: HotelDataInput, serviceToken: string) => {
|
||||
@@ -695,6 +673,199 @@ export const hotelQueryRouter = router({
|
||||
return getHotelData(input, ctx.serviceToken)
|
||||
}),
|
||||
}),
|
||||
hotels: router({
|
||||
get: contentStackBaseWithServiceProcedure
|
||||
.input(getHotelsInput)
|
||||
.query(async function ({ ctx, input }) {
|
||||
const { locationFilter, hotelsToInclude } = input
|
||||
|
||||
const language = ctx.lang
|
||||
const options: RequestOptionsWithOutBody = {
|
||||
cache: "force-cache",
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.serviceToken}`,
|
||||
},
|
||||
next: {
|
||||
revalidate: TWENTYFOUR_HOURS,
|
||||
},
|
||||
}
|
||||
|
||||
let hotelsToFetch: string[] = []
|
||||
|
||||
getHotelsCounter.add(1, {
|
||||
input: JSON.stringify(input),
|
||||
language,
|
||||
})
|
||||
console.info(
|
||||
"api.hotel.hotels start",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
...input,
|
||||
language,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
if (hotelsToInclude.length) {
|
||||
hotelsToFetch = hotelsToInclude
|
||||
} else if (locationFilter?.city) {
|
||||
const locationsParams = new URLSearchParams({
|
||||
language: toApiLang(ctx.lang),
|
||||
})
|
||||
const locations = await getLocations(
|
||||
ctx.lang,
|
||||
options,
|
||||
locationsParams,
|
||||
null
|
||||
)
|
||||
if (!locations || "error" in locations) {
|
||||
return []
|
||||
}
|
||||
|
||||
const cityId = locations
|
||||
.filter((loc): loc is CityLocation => loc.type === "cities")
|
||||
.find((loc) => loc.cityIdentifier === locationFilter.city)?.id
|
||||
|
||||
if (!cityId) {
|
||||
getHotelsFailCounter.add(1, {
|
||||
input: JSON.stringify(input),
|
||||
language,
|
||||
error_type: "not_found",
|
||||
error: `CityId not found for cityIdentifier: ${locationFilter.city}`,
|
||||
})
|
||||
|
||||
console.error(
|
||||
"api.hotel.hotels not found error",
|
||||
JSON.stringify({
|
||||
query: { ...input, language },
|
||||
error: `CityId not found for cityIdentifier: ${locationFilter.city}`,
|
||||
})
|
||||
)
|
||||
return []
|
||||
}
|
||||
const hotelIdsParams = new URLSearchParams({
|
||||
language: ctx.lang,
|
||||
city: cityId,
|
||||
onlyBasicInfo: "true",
|
||||
})
|
||||
const hotelIds = await getHotelIdsByCityId(
|
||||
cityId,
|
||||
options,
|
||||
hotelIdsParams
|
||||
)
|
||||
|
||||
if (!hotelIds?.length) {
|
||||
getHotelsFailCounter.add(1, {
|
||||
cityId,
|
||||
language,
|
||||
error_type: "not_found",
|
||||
error: `No hotelIds found for cityId: ${cityId}`,
|
||||
})
|
||||
|
||||
console.error(
|
||||
"api.hotel.hotels not found error",
|
||||
JSON.stringify({
|
||||
query: { cityId, language },
|
||||
error: `No hotelIds found for cityId: ${cityId}`,
|
||||
})
|
||||
)
|
||||
return []
|
||||
}
|
||||
|
||||
const filteredHotelIds = hotelIds.filter(
|
||||
(id) => !locationFilter.excluded.includes(id)
|
||||
)
|
||||
|
||||
hotelsToFetch = filteredHotelIds
|
||||
} else if (locationFilter?.country) {
|
||||
const hotelIdsParams = new URLSearchParams({
|
||||
language: ctx.lang,
|
||||
country: locationFilter.country,
|
||||
onlyBasicInfo: "true",
|
||||
})
|
||||
const hotelIds = await getHotelIdsByCountry(
|
||||
locationFilter.country,
|
||||
options,
|
||||
hotelIdsParams
|
||||
)
|
||||
|
||||
if (!hotelIds?.length) {
|
||||
getHotelsFailCounter.add(1, {
|
||||
country: locationFilter.country,
|
||||
language,
|
||||
error_type: "not_found",
|
||||
error: `No hotelIds found for country: ${locationFilter.country}`,
|
||||
})
|
||||
|
||||
console.error(
|
||||
"api.hotel.hotels not found error",
|
||||
JSON.stringify({
|
||||
query: { country: locationFilter.country, language },
|
||||
error: `No hotelIds found for cityId: ${locationFilter.country}`,
|
||||
})
|
||||
)
|
||||
return []
|
||||
}
|
||||
|
||||
const filteredHotelIds = hotelIds.filter(
|
||||
(id) => !locationFilter.excluded.includes(id)
|
||||
)
|
||||
|
||||
hotelsToFetch = filteredHotelIds
|
||||
}
|
||||
|
||||
if (!hotelsToFetch.length) {
|
||||
getHotelsFailCounter.add(1, {
|
||||
input: JSON.stringify(input),
|
||||
language,
|
||||
error_type: "not_found",
|
||||
error: `Couldn't find any hotels for given input: ${JSON.stringify(input)}`,
|
||||
})
|
||||
|
||||
console.error(
|
||||
"api.hotel.hotels not found error",
|
||||
JSON.stringify({
|
||||
query: JSON.stringify(input),
|
||||
error: `Couldn't find any hotels for given input: ${JSON.stringify(input)}`,
|
||||
})
|
||||
)
|
||||
return []
|
||||
}
|
||||
|
||||
const hotels = await Promise.all(
|
||||
hotelsToFetch.map(async (hotelId) => {
|
||||
const [hotelData, url] = await Promise.all([
|
||||
getHotelData({ hotelId, language }, ctx.serviceToken),
|
||||
getHotelPageUrl(language, hotelId),
|
||||
])
|
||||
|
||||
return {
|
||||
data: hotelData?.data.attributes,
|
||||
url,
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
getHotelsSuccessCounter.add(1, {
|
||||
input: JSON.stringify(input),
|
||||
language,
|
||||
})
|
||||
|
||||
console.info(
|
||||
"api.hotels success",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
input: JSON.stringify(input),
|
||||
language,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
return hotels.filter(
|
||||
(hotel): hotel is { data: Hotel; url: HotelPageUrl } => !!hotel.data
|
||||
)
|
||||
}),
|
||||
}),
|
||||
locations: router({
|
||||
get: serviceProcedure.query(async function ({ ctx }) {
|
||||
const searchParams = new URLSearchParams()
|
||||
|
||||
Reference in New Issue
Block a user