fix: cache hotel response

This commit is contained in:
Simon Emanuelsson
2024-12-17 16:18:46 +01:00
parent 13a164242f
commit 1deab000bd
38 changed files with 339 additions and 246 deletions
+144 -121
View File
@@ -1,4 +1,7 @@
import { unstable_cache } from "next/cache"
import { ApiLang } from "@/constants/languages"
import { env } from "@/env/server"
import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { badRequestError } from "@/server/errors/trpc"
@@ -15,6 +18,8 @@ import { cache } from "@/utils/cache"
import { getHotelPageUrl } from "../contentstack/hotelPage/utils"
import { getVerifiedUser, parsedUser } from "../user/query"
import { additionalDataSchema } from "./schemas/additionalData"
import { meetingRoomsSchema } from "./schemas/meetingRoom"
import {
breakfastPackageInputSchema,
cityCoordinatesInputSchema,
@@ -54,133 +59,144 @@ 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"
import type { HotelInput } from "@/types/trpc/routers/hotel/hotel"
import type { HotelData } from "@/types/hotel"
import type { HotelPageUrl } from "@/types/trpc/routers/contentstack/hotelPage"
import { CityLocation } from "@/types/trpc/routers/hotel/locations"
import { meetingRoomsSchema } from "./schemas/meetingRoom"
import { env } from "@/env/server"
import { additionalDataSchema } from "./schemas/additionalData"
import type { HotelInput } from "@/types/trpc/routers/hotel/hotel"
import type { CityLocation } from "@/types/trpc/routers/hotel/locations"
export const getHotel = cache(
async (input: HotelInput, serviceToken: string) => {
const { hotelId, isCardOnlyPayment, language } = input
/**
* Since API expects the params appended and not just
* a comma separated string we need to initialize the
* SearchParams with a sequence of pairs
* (include=City&include=NearbyHotels&include=Restaurants etc.)
**/
const params = new URLSearchParams([
["hotelId", hotelId],
["include", "AdditionalData"],
["include", "City"],
["include", "NearbyHotels"],
["include", "Restaurants"],
["include", "RoomCategories"],
["language", toApiLang(language)],
])
metrics.hotel.counter.add(1, {
hotelId,
language,
})
console.info(
"api.hotels.hotelData start",
JSON.stringify({ query: { hotelId, params } })
)
const callable = unstable_cache(
async function (
hotelId: HotelInput["hotelId"],
language: HotelInput["language"],
isCardOnlyPayment?: HotelInput["isCardOnlyPayment"]
) {
/**
* Since API expects the params appended and not just
* a comma separated string we need to initialize the
* SearchParams with a sequence of pairs
* (include=City&include=NearbyHotels&include=Restaurants etc.)
**/
const params = new URLSearchParams([
["include", "AdditionalData"],
["include", "City"],
["include", "NearbyHotels"],
["include", "Restaurants"],
["include", "RoomCategories"],
["language", toApiLang(language)],
])
metrics.hotel.counter.add(1, {
hotelId,
language,
})
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}`,
},
// needs to clear default option as only
// cache or next.revalidate is permitted
cache: undefined,
next: {
revalidate: env.CACHE_TIME_HOTELDATA,
tags: [`${language}:hotel:${hotelId}`],
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
console.log({ text })
metrics.hotel.fail.add(1, {
hotelId,
language,
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,
const apiResponse = await api.get(
api.endpoints.v1.Hotel.Hotels.hotel(hotelId),
{
headers: {
Authorization: `Bearer ${serviceToken}`,
},
// needs to clear default option as only
// cache or next.revalidate is permitted
cache: undefined,
next: {
revalidate: env.CACHE_TIME_HOTELS,
tags: [`${language}:hotel:${hotelId}`],
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.hotel.fail.add(1, {
hotelId,
language,
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 = hotelSchema.safeParse(apiJson)
if (!validateHotelData.success) {
metrics.hotel.fail.add(1, {
hotelId,
language,
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()
}
metrics.hotel.success.add(1, {
hotelId,
language,
})
)
return null
}
console.info(
"api.hotels.hotelData success",
JSON.stringify({
query: { hotelId, params: params },
})
)
const hotelData = validateHotelData.data
const apiJson = await apiResponse.json()
const validateHotelData = hotelSchema.safeParse(apiJson)
if (isCardOnlyPayment) {
hotelData.hotel.merchantInformationData.alternatePaymentOptions = []
}
if (!validateHotelData.success) {
metrics.hotel.fail.add(1, {
hotelId,
language,
error_type: "validation_error",
error: JSON.stringify(validateHotelData.error),
})
const gallery = hotelData.additionalData?.gallery
if (gallery) {
const smallerImages = gallery.smallerImages
const hotelGalleryImages =
hotelData.hotel.hotelType === HotelTypeEnum.Signature
? smallerImages.slice(0, 10)
: smallerImages.slice(0, 6)
hotelData.hotel.galleryImages = hotelGalleryImages
}
console.error(
"api.hotels.hotelData validation error",
JSON.stringify({
query: { hotelId, params },
error: validateHotelData.error,
})
)
throw badRequestError()
}
metrics.hotel.success.add(1, {
hotelId,
language,
})
console.info(
"api.hotels.hotelData success",
JSON.stringify({
query: { hotelId, params: params },
})
return hotelData
},
[`${input.language}:hotel:${input.hotelId}:${!!input.isCardOnlyPayment}`],
{
revalidate: env.CACHE_TIME_HOTELS,
tags: [
`${input.language}:hotel:${input.hotelId}:${!!input.isCardOnlyPayment}`,
],
}
)
const hotelData = validateHotelData.data
if (isCardOnlyPayment) {
hotelData.hotel.merchantInformationData.alternatePaymentOptions = []
}
const gallery = hotelData.additionalData?.gallery
if (gallery) {
const smallerImages = gallery.smallerImages
const hotelGalleryImages =
hotelData.hotel.hotelType === HotelTypeEnum.Signature
? smallerImages.slice(0, 10)
: smallerImages.slice(0, 6)
hotelData.hotel.galleryImages = hotelGalleryImages
}
return hotelData
return callable(input.hotelId, input.language, input.isCardOnlyPayment)
}
)
@@ -731,9 +747,9 @@ export const hotelQueryRouter = router({
type: matchingRoom.mainBed.type,
extraBed: matchingRoom.fixedExtraBed
? {
type: matchingRoom.fixedExtraBed.type,
description: matchingRoom.fixedExtraBed.description,
}
type: matchingRoom.fixedExtraBed.type,
description: matchingRoom.fixedExtraBed.description,
}
: undefined,
}
}
@@ -861,7 +877,10 @@ export const hotelQueryRouter = router({
}
const cityId = locations
.filter((loc): loc is CityLocation => "type" in loc && loc.type === "cities")
.filter(
(loc): loc is CityLocation =>
"type" in loc && loc.type === "cities"
)
.find((loc) => loc.cityIdentifier === locationFilter.city)?.id
if (!cityId) {
@@ -973,7 +992,10 @@ export const hotelQueryRouter = router({
const hotels = await Promise.all(
hotelsToFetch.map(async (hotelId) => {
const [hotelData, url] = await Promise.all([
getHotel({ hotelId, language }, ctx.serviceToken),
getHotel(
{ hotelId, isCardOnlyPayment: false, language },
ctx.serviceToken
),
getHotelPageUrl(language, hotelId),
])
@@ -1000,7 +1022,8 @@ export const hotelQueryRouter = router({
)
return hotels.filter(
(hotel): hotel is { data: HotelData; url: HotelPageUrl } => !!hotel.data
(hotel): hotel is { data: HotelData; url: HotelPageUrl } =>
!!hotel.data
)
}),
}),
@@ -1096,7 +1119,7 @@ export const hotelQueryRouter = router({
Authorization: `Bearer ${ctx.serviceToken}`,
},
next: {
revalidate: TWENTYFOUR_HOURS,
revalidate: env.CACHE_TIME_HOTELS,
},
}