feat(SW-664): Hotel listing component and queries for content pages

This commit is contained in:
Erik Tiekstra
2024-12-11 14:46:38 +01:00
parent 118f1afafa
commit 3939bf7cdc
32 changed files with 989 additions and 140 deletions

View File

@@ -18,6 +18,7 @@ import {
dynamicContentRefsSchema,
dynamicContentSchema as blockDynamicContentSchema,
} from "../schemas/blocks/dynamicContent"
import { hotelListingSchema } from "../schemas/blocks/hotelListing"
import {
shortcutsRefsSchema,
shortcutsSchema,
@@ -103,6 +104,12 @@ export const contentPageAccordion = z
})
.merge(accordionSchema)
export const contentPageHotelListing = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.HotelListing),
})
.merge(hotelListingSchema)
export const blocksSchema = z.discriminatedUnion("__typename", [
contentPageAccordion,
contentPageCards,
@@ -112,6 +119,7 @@ export const blocksSchema = z.discriminatedUnion("__typename", [
contentPageTable,
contentPageTextCols,
contentPageUspGrid,
contentPageHotelListing,
])
export const contentPageSidebarContent = z

View File

@@ -2,6 +2,8 @@ import { z } from "zod"
import { discriminatedUnionArray } from "@/lib/discriminatedUnion"
import { removeMultipleSlashes } from "@/utils/url"
import {
activitiesCardRefSchema,
activitiesCardSchema,
@@ -58,3 +60,26 @@ export const hotelPageRefsSchema = z.object({
url: z.string(),
}),
})
export const hotelPageUrlSchema = z
.object({
all_hotel_page: z.object({
items: z
.array(
z.object({
url: z.string(),
system: systemSchema,
})
)
.max(1),
}),
})
.transform((data) => {
const page = data.all_hotel_page.items[0]
if (!page) {
return null
}
const lang = page.system.locale
return removeMultipleSlashes(`/${lang}/${page.url}`)
})

View File

@@ -1,5 +1,3 @@
import { metrics } from "@opentelemetry/api"
import { GetHotelPage } from "@/lib/graphql/Query/HotelPage/HotelPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
@@ -8,20 +6,13 @@ import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag"
import { hotelPageSchema } from "./output"
import {
getHotelPageCounter,
getHotelPageFailCounter,
getHotelPageSuccessCounter,
} from "./telemetry"
import { GetHotelPageData } from "@/types/trpc/routers/contentstack/hotelPage"
// OpenTelemetry metrics
const meter = metrics.getMeter("trpc.contentstack.hotelPage")
const getHotelPageCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get"
)
const getHotelPageSuccessCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-success"
)
const getHotelPageFailCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-fail"
)
import type { GetHotelPageData } from "@/types/trpc/routers/contentstack/hotelPage"
export const hotelPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {

View File

@@ -0,0 +1,33 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.contentstack.hotelPage")
export const getHotelPageRefsCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get"
)
export const getHotelPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-fail"
)
export const getHotelPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-success"
)
export const getHotelPageCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get"
)
export const getHotelPageSuccessCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-success"
)
export const getHotelPageFailCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-fail"
)
export const getHotelPageUrlCounter = meter.createCounter(
"trpc.contentstack.hotelPageUrl.get"
)
export const getHotelPageUrlSuccessCounter = meter.createCounter(
"trpc.contentstack.hotelPageUrl.get-success"
)
export const getHotelPageUrlFailCounter = meter.createCounter(
"trpc.contentstack.hotelPageUrl.get-fail"
)

View File

@@ -1,37 +1,32 @@
import { metrics } from "@opentelemetry/api"
import { Lang } from "@/constants/languages"
import { GetHotelPageRefs } from "@/lib/graphql/Query/HotelPage/HotelPage.graphql"
import { GetHotelPageUrl } from "@/lib/graphql/Query/HotelPage/HotelPageUrl.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
import {
generateHotelUrlTag,
generateTag,
generateTagsFromSystem,
} from "@/utils/generateTag"
import { hotelPageRefsSchema } from "./output"
import { hotelPageRefsSchema, hotelPageUrlSchema } from "./output"
import {
getHotelPageRefsCounter,
getHotelPageRefsFailCounter,
getHotelPageRefsSuccessCounter,
getHotelPageUrlCounter,
getHotelPageUrlFailCounter,
getHotelPageUrlSuccessCounter,
} from "./telemetry"
import { HotelPageEnum } from "@/types/enums/hotelPage"
import { System } from "@/types/requests/system"
import {
import type { System } from "@/types/requests/system"
import type {
GetHotelPageRefsSchema,
GetHotelPageUrlData,
HotelPageRefs,
} from "@/types/trpc/routers/contentstack/hotelPage"
const meter = metrics.getMeter("trpc.hotelPage")
// OpenTelemetry metrics: HotelPage
export const getHotelPageCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get"
)
const getHotelPageRefsCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get"
)
const getHotelPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-fail"
)
const getHotelPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-success"
)
import type { Lang } from "@/constants/languages"
export async function fetchHotelPageRefs(lang: Lang, uid: string) {
getHotelPageRefsCounter.add(1, { lang, uid })
@@ -140,3 +135,64 @@ export function getConnections({ hotel_page }: HotelPageRefs) {
}
return connections
}
export async function getHotelPageUrl(lang: Lang, hotelId: string) {
getHotelPageUrlCounter.add(1, { lang, hotelId })
console.info(
"contentstack.hotelPageUrl start",
JSON.stringify({ query: { lang, hotelId } })
)
const response = await request<GetHotelPageUrlData>(
GetHotelPageUrl,
{
locale: lang,
hotelId,
},
{
cache: "force-cache",
next: {
tags: [generateHotelUrlTag(lang, hotelId)],
},
}
)
if (!response.data) {
getHotelPageUrlFailCounter.add(1, {
lang,
hotelId,
error_type: "not_found",
error: `Hotel page not found for hotelId: ${hotelId}`,
})
console.error(
"contentstack.hotelPageUrl not found error",
JSON.stringify({ query: { lang, hotelId } })
)
return null
}
const validatedHotelPageUrl = hotelPageUrlSchema.safeParse(response.data)
if (!validatedHotelPageUrl.success) {
getHotelPageUrlFailCounter.add(1, {
lang,
hotelId,
error_type: "validation_error",
error: JSON.stringify(validatedHotelPageUrl.error),
})
console.error(
"contentstack.hotelPageUrl validation error",
JSON.stringify({
query: { lang, hotelId },
error: validatedHotelPageUrl.error,
})
)
return null
}
getHotelPageUrlSuccessCounter.add(1, { lang, hotelId })
console.info(
"contentstack.hotelPageUrl success",
JSON.stringify({ query: { lang, hotelId } })
)
return validatedHotelPageUrl.data
}

View File

@@ -0,0 +1,62 @@
import { z } from "zod"
import { BlocksEnums } from "@/types/enums/blocks"
import { Country } from "@/types/enums/country"
export const locationFilterSchema = z
.object({
country: z.nativeEnum(Country).nullable(),
city_denmark: z.string().optional().nullable(),
city_finland: z.string().optional().nullable(),
city_germany: z.string().optional().nullable(),
city_poland: z.string().optional().nullable(),
city_norway: z.string().optional().nullable(),
city_sweden: z.string().optional().nullable(),
excluded: z.array(z.string()),
})
.transform((data) => {
const cities = [
data.city_denmark,
data.city_finland,
data.city_germany,
data.city_poland,
data.city_norway,
data.city_sweden,
].filter((city): city is string => Boolean(city))
// When there are multiple city values, we return null as the filter is invalid.
if (cities.length > 1) {
return null
}
return {
country: cities.length ? null : data.country,
city: cities.length ? cities[0] : null,
excluded: data.excluded,
}
})
export const hotelListingSchema = z.object({
typename: z
.literal(BlocksEnums.block.HotelListing)
.default(BlocksEnums.block.HotelListing),
hotel_listing: z
.object({
heading: z.string().optional(),
location_filter: locationFilterSchema,
manual_filter: z
.object({
hotels: z.array(z.string()),
})
.transform((data) => ({ hotels: data.hotels.filter(Boolean) })),
content_type: z.enum(["hotel", "restaurant", "meeting"]),
})
.transform(({ heading, location_filter, manual_filter, content_type }) => {
return {
heading,
locationFilter: location_filter,
hotelsToInclude: manual_filter.hotels,
contentType: content_type,
}
}),
})

View File

@@ -1,6 +1,7 @@
import { z } from "zod"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { Country } from "@/types/enums/country"
export const getHotelsAvailabilityInputSchema = z.object({
cityId: z.string(),
@@ -53,6 +54,18 @@ export const getHotelDataInputSchema = z.object({
export type HotelDataInput = z.input<typeof getHotelDataInputSchema>
export const getHotelsInput = z.object({
locationFilter: z
.object({
city: z.string().nullable(),
country: z.nativeEnum(Country).nullable(),
excluded: z.array(z.string()),
})
.nullable(),
hotelsToInclude: z.array(z.string()),
})
export interface GetHotelsInput extends z.infer<typeof getHotelsInput> {}
export const getBreakfastPackageInputSchema = z.object({
adults: z.number().min(1, { message: "at least one adult is required" }),
fromDate: z

View File

@@ -870,3 +870,14 @@ export const getRoomPackagesSchema = z
.optional(),
})
.transform((data) => data.data?.attributes?.packages ?? [])
export const getHotelIdsByCityIdSchema = z
.object({
data: z.array(
z.object({
// We only care about the hotel id
id: z.string(),
})
),
})
.transform((data) => data.data.map((hotel) => hotel.id))

View File

@@ -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()

View File

@@ -0,0 +1,74 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.hotels")
export const getHotelCounter = meter.createCounter("trpc.hotel.get")
export const getHotelSuccessCounter = meter.createCounter(
"trpc.hotel.get-success"
)
export const getHotelFailCounter = meter.createCounter("trpc.hotel.get-fail")
export const getPackagesCounter = meter.createCounter("trpc.hotel.packages.get")
export const getPackagesSuccessCounter = meter.createCounter(
"trpc.hotel.packages.get-success"
)
export const getPackagesFailCounter = meter.createCounter(
"trpc.hotel.packages.get-fail"
)
export const hotelsAvailabilityCounter = meter.createCounter(
"trpc.hotel.availability.hotels"
)
export const hotelsAvailabilitySuccessCounter = meter.createCounter(
"trpc.hotel.availability.hotels-success"
)
export const hotelsAvailabilityFailCounter = meter.createCounter(
"trpc.hotel.availability.hotels-fail"
)
export const roomsAvailabilityCounter = meter.createCounter(
"trpc.hotel.availability.rooms"
)
export const roomsAvailabilitySuccessCounter = meter.createCounter(
"trpc.hotel.availability.rooms-success"
)
export const roomsAvailabilityFailCounter = meter.createCounter(
"trpc.hotel.availability.rooms-fail"
)
export const selectedRoomAvailabilityCounter = meter.createCounter(
"trpc.hotel.availability.room"
)
export const selectedRoomAvailabilitySuccessCounter = meter.createCounter(
"trpc.hotel.availability.room-success"
)
export const selectedRoomAvailabilityFailCounter = meter.createCounter(
"trpc.hotel.availability.room-fail"
)
export const breakfastPackagesCounter = meter.createCounter(
"trpc.package.breakfast"
)
export const breakfastPackagesSuccessCounter = meter.createCounter(
"trpc.package.breakfast-success"
)
export const breakfastPackagesFailCounter = meter.createCounter(
"trpc.package.breakfast-fail"
)
export const getHotelsCounter = meter.createCounter("trpc.hotel.hotels.get")
export const getHotelsSuccessCounter = meter.createCounter(
"trpc.hotel.hotels.get-success"
)
export const getHotelsFailCounter = meter.createCounter(
"trpc.hotel.hotels.get-fail"
)
export const getHotelIdsCounter = meter.createCounter(
"trpc.hotel.hotel-ids.get"
)
export const getHotelIdsSuccessCounter = meter.createCounter(
"trpc.hotel.hotel-ids.get-success"
)
export const getHotelIdsFailCounter = meter.createCounter(
"trpc.hotel.hotel-ids.get-fail"
)

View File

@@ -10,11 +10,18 @@ import {
apiLocationsSchema,
type CitiesGroupedByCountry,
type Countries,
getHotelIdsByCityIdSchema,
} from "./output"
import {
getHotelIdsCounter,
getHotelIdsFailCounter,
getHotelIdsSuccessCounter,
} from "./telemetry"
import type { Country } from "@/types/enums/country"
import type { RequestOptionsWithOutBody } from "@/types/fetch"
import { PointOfInterestGroupEnum } from "@/types/hotel"
import { HotelLocation } from "@/types/trpc/routers/hotel/locations"
import type { HotelLocation } from "@/types/trpc/routers/hotel/locations"
import type { Lang } from "@/constants/languages"
import type { Endpoint } from "@/lib/api/endpoints"
@@ -258,3 +265,145 @@ export async function getLocations(
{ revalidate: TWENTYFOUR_HOURS }
)(params, citiesByCountry)
}
export async function getHotelIdsByCityId(
cityId: string,
options: RequestOptionsWithOutBody,
params: URLSearchParams
) {
return unstable_cache(
async function (params: URLSearchParams) {
getHotelIdsCounter.add(1, { cityId })
console.info(
"api.hotel.hotel-ids start",
JSON.stringify({ query: { cityId } })
)
const apiResponse = await api.get(
api.endpoints.v1.Hotel.hotels,
options,
params
)
if (!apiResponse.ok) {
const responseMessage = await apiResponse.text()
getHotelIdsFailCounter.add(1, {
cityId,
error_type: "http_error",
error: responseMessage,
})
console.error(
"api.hotel.hotel-ids fetch error",
JSON.stringify({
query: { cityId },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text: responseMessage,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const validatedHotelIds = getHotelIdsByCityIdSchema.safeParse(apiJson)
if (!validatedHotelIds.success) {
getHotelIdsFailCounter.add(1, {
cityId,
error_type: "validation_error",
error: JSON.stringify(validatedHotelIds.error),
})
console.error(
"api.hotel.hotel-ids validation error",
JSON.stringify({
query: { cityId },
error: validatedHotelIds.error,
})
)
return null
}
getHotelIdsSuccessCounter.add(1, { cityId })
console.info(
"api.hotel.hotel-ids success",
JSON.stringify({ query: { cityId } })
)
return validatedHotelIds.data
},
[`hotels`, params.toString()],
{ revalidate: TWENTYFOUR_HOURS }
)(params)
}
export async function getHotelIdsByCountry(
country: Country,
options: RequestOptionsWithOutBody,
params: URLSearchParams
) {
return unstable_cache(
async function (params: URLSearchParams) {
getHotelIdsCounter.add(1, { country })
console.info(
"api.hotel.hotel-ids start",
JSON.stringify({ query: { country } })
)
const apiResponse = await api.get(
api.endpoints.v1.Hotel.hotels,
options,
params
)
if (!apiResponse.ok) {
const responseMessage = await apiResponse.text()
getHotelIdsFailCounter.add(1, {
country,
error_type: "http_error",
error: responseMessage,
})
console.error(
"api.hotel.hotel-ids fetch error",
JSON.stringify({
query: { country },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text: responseMessage,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const validatedHotelIds = getHotelIdsByCityIdSchema.safeParse(apiJson)
if (!validatedHotelIds.success) {
getHotelIdsFailCounter.add(1, {
country,
error_type: "validation_error",
error: JSON.stringify(validatedHotelIds.error),
})
console.error(
"api.hotel.hotel-ids validation error",
JSON.stringify({
query: { country },
error: validatedHotelIds.error,
})
)
return null
}
getHotelIdsSuccessCounter.add(1, { country })
console.info(
"api.hotel.hotel-ids success",
JSON.stringify({ query: { country } })
)
return validatedHotelIds.data
},
[`hotels`, params.toString()],
{ revalidate: TWENTYFOUR_HOURS }
)(params)
}