Merged in fix/SW-1754-overview-page-rate-limit (pull request #1412)

fix(SW-1754): Fix rate limit issue on Destination Overview Page

* fix(SW-1754): Fix rate limit issue on Destination Overview Page


Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-02-25 14:40:31 +00:00
parent 3867baadd6
commit bc50dcf286
27 changed files with 426 additions and 244 deletions

View File

@@ -1,6 +1,7 @@
import { ArrowRightIcon } from "@/components/Icons" import { ArrowRightIcon } from "@/components/Icons"
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
import Link from "@/components/TempDesignSystem/Link" import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import styles from "./destination.module.css" import styles from "./destination.module.css"
@@ -27,13 +28,17 @@ export default async function Destination({
<ul className={styles.citiesList}> <ul className={styles.citiesList}>
{cities.map((city) => ( {cities.map((city) => (
<li key={city.id}> <li key={city.id}>
<Link {city.url ? (
href={city.url ? city.url : ""} <Link
color="baseTextMediumContrast" href={city.url}
textDecoration="underline" color="baseTextMediumContrast"
> textDecoration="underline"
{`${city.name} (${city.hotelCount})`} >
</Link> {`${city.name} (${city.hotelCount})`}
</Link>
) : (
<Body>{`${city.name} (${city.hotelCount})`}</Body>
)}
</li> </li>
))} ))}
</ul> </ul>

View File

@@ -13,6 +13,22 @@
justify-items: start; justify-items: start;
} }
.imageWrapper {
position: relative;
}
.tripAdvisor {
position: absolute;
top: var(--Spacing-x2);
left: var(--Spacing-x2);
display: flex;
align-items: center;
gap: var(--Spacing-x-half);
background-color: var(--Base-Surface-Primary-light-Normal);
padding: var(--Spacing-x-quarter) var(--Spacing-x1);
border-radius: var(--Corner-radius-Small);
}
.intro { .intro {
display: grid; display: grid;
gap: var(--Spacing-x-half); gap: var(--Spacing-x-half);

View File

@@ -4,7 +4,7 @@ import Link from "next/link"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data" import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data"
import { ChevronRightSmallIcon } from "@/components/Icons" import { ChevronRightSmallIcon, TripAdvisorIcon } from "@/components/Icons"
import HotelLogo from "@/components/Icons/Logos" import HotelLogo from "@/components/Icons/Logos"
import ImageGallery from "@/components/ImageGallery" import ImageGallery from "@/components/ImageGallery"
import Button from "@/components/TempDesignSystem/Button" import Button from "@/components/TempDesignSystem/Button"
@@ -34,13 +34,23 @@ export default function HotelListingItem({
return ( return (
<article className={styles.container}> <article className={styles.container}>
<ImageGallery <div className={styles.imageWrapper}>
images={galleryImages} <ImageGallery
title={intl.formatMessage( images={galleryImages}
{ id: "{title} - Image gallery" }, title={intl.formatMessage(
{ title: hotel.name } { id: "{title} - Image gallery" },
{ title: hotel.name }
)}
/>
{hotel.ratings?.tripAdvisor.rating && (
<div className={styles.tripAdvisor}>
<TripAdvisorIcon color="burgundy" />
<Caption color="burgundy">
{hotel.ratings.tripAdvisor.rating}
</Caption>
</div>
)} )}
/> </div>
<div className={styles.content}> <div className={styles.content}>
<div className={styles.intro}> <div className={styles.intro}>
<HotelLogo hotelId={hotel.operaId} hotelType={hotel.hotelType} /> <HotelLogo hotelId={hotel.operaId} hotelType={hotel.hotelType} />

View File

@@ -4,8 +4,12 @@ query GetDestinationCityListData($locale: String!, $cityIdentifier: String!) {
all_destination_city_page( all_destination_city_page(
where: { where: {
OR: [ OR: [
{ destination_settings: { city_sweden: $cityIdentifier } }
{ destination_settings: { city_denmark: $cityIdentifier } } { destination_settings: { city_denmark: $cityIdentifier } }
{ destination_settings: { city_finland: $cityIdentifier } }
{ destination_settings: { city_germany: $cityIdentifier } }
{ destination_settings: { city_norway: $cityIdentifier } }
{ destination_settings: { city_poland: $cityIdentifier } }
{ destination_settings: { city_sweden: $cityIdentifier } }
] ]
} }
locale: $locale locale: $locale

View File

@@ -0,0 +1,20 @@
#import "../../Fragments/System.graphql"
query GetCityPageUrls($locale: String!) {
all_destination_city_page(locale: $locale) {
items {
url
destination_settings {
city_denmark
city_finland
city_germany
city_norway
city_poland
city_sweden
}
system {
...System
}
}
}
}

View File

@@ -0,0 +1,15 @@
#import "../../Fragments/System.graphql"
query GetCountryPageUrls($locale: String!) {
all_destination_country_page(locale: $locale) {
items {
url
destination_settings {
country
}
system {
...System
}
}
}
}

View File

@@ -33,20 +33,6 @@ query GetDestinationOverviewPageRefs($locale: String!, $uid: String!) {
} }
} }
query GetCountryPageUrl($locale: String!, $country: String!) {
all_destination_country_page(
where: { destination_settings: { country: $country } }
locale: $locale
) {
items {
url
system {
...System
}
}
}
}
query GetDaDeEnUrlsDestinationOverviewPage($uid: String!) { query GetDaDeEnUrlsDestinationOverviewPage($uid: String!) {
de: destination_overview_page(locale: "de", uid: $uid) { de: destination_overview_page(locale: "de", uid: $uid) {
url url

View File

@@ -1,9 +1,10 @@
#import "../../Fragments/System.graphql" #import "../../Fragments/System.graphql"
query GetHotelPageUrl($locale: String!, $hotelId: String!) { query GetHotelPageUrls($locale: String!) {
all_hotel_page(locale: $locale, where: { hotel_page_id: $hotelId }) { all_hotel_page(locale: $locale) {
items { items {
url url
hotel_page_id
system { system {
...System ...System
} }

View File

@@ -185,6 +185,56 @@ export const destinationCityPageSchema = z
} }
}) })
export const cityPageUrlsSchema = z
.object({
all_destination_city_page: z.object({
items: z.array(
z
.object({
url: z.string(),
destination_settings: z
.object({
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(),
})
.transform(
({
city_denmark,
city_finland,
city_germany,
city_norway,
city_poland,
city_sweden,
}) => {
const cities = [
city_denmark,
city_finland,
city_germany,
city_poland,
city_norway,
city_sweden,
].filter((city): city is string => Boolean(city))
return { city: cities[0] }
}
),
system: systemSchema,
})
.transform((data) => {
return {
city: data.destination_settings.city,
url: removeMultipleSlashes(`/${data.system.locale}/${data.url}`),
}
})
),
}),
})
.transform(({ all_destination_city_page }) => all_destination_city_page.items)
/** REFS */ /** REFS */
const destinationCityPageContentRefs = z const destinationCityPageContentRefs = z
.object({ .object({

View File

@@ -21,3 +21,13 @@ export const getDestinationCityPageSuccessCounter = meter.createCounter(
export const getDestinationCityPageFailCounter = meter.createCounter( export const getDestinationCityPageFailCounter = meter.createCounter(
"trpc.contentstack.destinationCityPage.get-fail" "trpc.contentstack.destinationCityPage.get-fail"
) )
export const getCityPageUrlsCounter = meter.createCounter(
"trpc.contentstack.cityPageUrls.get"
)
export const getCityPageUrlsSuccessCounter = meter.createCounter(
"trpc.contentstack.cityPageUrls.get-success"
)
export const getCityPageUrlsFailCounter = meter.createCounter(
"trpc.contentstack.cityPageUrls.get-fail"
)

View File

@@ -1,8 +1,21 @@
import { GetCityPageUrls } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityPageUrl.graphql"
import { request } from "@/lib/graphql/request"
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag" import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
import { cityPageUrlsSchema } from "./output"
import {
getCityPageUrlsCounter,
getCityPageUrlsFailCounter,
getCityPageUrlsSuccessCounter,
} from "./telemetry"
import { DestinationCityPageEnum } from "@/types/enums/destinationCityPage" import { DestinationCityPageEnum } from "@/types/enums/destinationCityPage"
import type { System } from "@/types/requests/system" import type { System } from "@/types/requests/system"
import type { DestinationCityPageRefs } from "@/types/trpc/routers/contentstack/destinationCityPage" import type {
DestinationCityPageRefs,
GetCityPageUrlsData,
} from "@/types/trpc/routers/contentstack/destinationCityPage"
import type { Lang } from "@/constants/languages" import type { Lang } from "@/constants/languages"
export function generatePageTags( export function generatePageTags(
@@ -51,3 +64,62 @@ export function getConnections({
return connections return connections
} }
export async function getCityPageUrls(lang: Lang) {
getCityPageUrlsCounter.add(1, { lang })
console.info(
"contentstack.cityPageUrls start",
JSON.stringify({ query: { lang } })
)
const tag = `${lang}:city_page_urls`
const response = await request<GetCityPageUrlsData>(
GetCityPageUrls,
{
locale: lang,
},
{
cache: "force-cache",
next: {
tags: [tag],
},
}
)
if (!response.data) {
getCityPageUrlsFailCounter.add(1, {
lang,
error_type: "not_found",
error: `Destination city pages not found for lang: ${lang}`,
})
console.error(
"contentstack.cityPageUrls not found error",
JSON.stringify({ query: { lang } })
)
return []
}
const validatedResponse = cityPageUrlsSchema.safeParse(response.data)
if (!validatedResponse.success) {
getCityPageUrlsFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedResponse.error),
})
console.error(
"contentstack.cityPageUrls validation error",
JSON.stringify({
query: { lang },
error: validatedResponse.error,
})
)
return []
}
getCityPageUrlsSuccessCounter.add(1, { lang })
console.info(
"contentstack.cityPageUrls success",
JSON.stringify({ query: { lang } })
)
return validatedResponse.data
}

View File

@@ -2,6 +2,8 @@ import { z } from "zod"
import { discriminatedUnionArray } from "@/lib/discriminatedUnion" import { discriminatedUnionArray } from "@/lib/discriminatedUnion"
import { removeMultipleSlashes } from "@/utils/url"
import { import {
accordionRefsSchema, accordionRefsSchema,
accordionSchema, accordionSchema,
@@ -120,6 +122,31 @@ export const destinationCountryPageSchema = z
} }
}) })
export const countryPageUrlsSchema = z
.object({
all_destination_country_page: z.object({
items: z.array(
z
.object({
url: z.string(),
destination_settings: z.object({
country: z.string(),
}),
system: systemSchema,
})
.transform((data) => {
return {
country: data.destination_settings.country,
url: removeMultipleSlashes(`/${data.system.locale}/${data.url}`),
}
})
),
}),
})
.transform(
({ all_destination_country_page }) => all_destination_country_page.items
)
/** REFS */ /** REFS */
const destinationCountryPageContentRefs = z const destinationCountryPageContentRefs = z
.object({ .object({

View File

@@ -31,3 +31,15 @@ export const getCityListDataSuccessCounter = meter.createCounter(
export const getCityListDataFailCounter = meter.createCounter( export const getCityListDataFailCounter = meter.createCounter(
"trpc.contentstack.cityListData.get-fail" "trpc.contentstack.cityListData.get-fail"
) )
export const getCountryPageUrlsCounter = meter.createCounter(
"trpc.contentstack.getCountryPageUrls"
)
export const getCountryPageUrlsSuccessCounter = meter.createCounter(
"trpc.contentstack.getCountryPageUrls-success"
)
export const getCountryPageUrlsFailCounter = meter.createCounter(
"trpc.contentstack.getCountryPageUrls-fail"
)

View File

@@ -1,5 +1,6 @@
import { env } from "@/env/server" import { env } from "@/env/server"
import { GetDestinationCityListData } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityListData.graphql" import { GetDestinationCityListData } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityListData.graphql"
import { GetCountryPageUrls } from "@/lib/graphql/Query/DestinationCountryPage/DestinationCountryPageUrl.graphql"
import { request } from "@/lib/graphql/request" import { request } from "@/lib/graphql/request"
import { toApiLang } from "@/server/utils" import { toApiLang } from "@/server/utils"
@@ -7,10 +8,14 @@ import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
import { getCitiesByCountry } from "../../hotels/utils" import { getCitiesByCountry } from "../../hotels/utils"
import { destinationCityListDataSchema } from "../destinationCityPage/output" import { destinationCityListDataSchema } from "../destinationCityPage/output"
import { countryPageUrlsSchema } from "./output"
import { import {
getCityListDataCounter, getCityListDataCounter,
getCityListDataFailCounter, getCityListDataFailCounter,
getCityListDataSuccessCounter, getCityListDataSuccessCounter,
getCountryPageUrlsCounter,
getCountryPageUrlsFailCounter,
getCountryPageUrlsSuccessCounter,
} from "./telemetry" } from "./telemetry"
import { ApiCountry, type Country } from "@/types/enums/country" import { ApiCountry, type Country } from "@/types/enums/country"
@@ -18,7 +23,10 @@ import { DestinationCountryPageEnum } from "@/types/enums/destinationCountryPage
import type { RequestOptionsWithOutBody } from "@/types/fetch" import type { RequestOptionsWithOutBody } from "@/types/fetch"
import type { System } from "@/types/requests/system" import type { System } from "@/types/requests/system"
import type { GetDestinationCityListDataResponse } from "@/types/trpc/routers/contentstack/destinationCityPage" import type { GetDestinationCityListDataResponse } from "@/types/trpc/routers/contentstack/destinationCityPage"
import type { DestinationCountryPageRefs } from "@/types/trpc/routers/contentstack/destinationCountryPage" import type {
DestinationCountryPageRefs,
GetCountryPageUrlsData,
} from "@/types/trpc/routers/contentstack/destinationCountryPage"
import type { Lang } from "@/constants/languages" import type { Lang } from "@/constants/languages"
export function generatePageTags( export function generatePageTags(
@@ -178,3 +186,66 @@ export async function getCityPages(
.flat() .flat()
.filter((city): city is NonNullable<typeof city> => !!city) .filter((city): city is NonNullable<typeof city> => !!city)
} }
export async function getCountryPageUrls(lang: Lang) {
getCountryPageUrlsCounter.add(1, { lang })
console.info(
"contentstack.countryPageUrls start",
JSON.stringify({ query: { lang } })
)
const tag = `${lang}:country_page_urls`
const response = await request<GetCountryPageUrlsData>(
GetCountryPageUrls,
{
locale: lang,
},
{
cache: "force-cache",
next: {
tags: [tag],
},
}
)
if (!response.data) {
getCountryPageUrlsFailCounter.add(1, {
lang,
error_type: "not_found",
error: `Country pages not found for lang: ${lang}`,
})
console.error(
"contentstack.countryPageUrls not found error",
JSON.stringify({ query: { lang } })
)
return []
}
const validatedCountryPageUrls = countryPageUrlsSchema.safeParse(
response.data
)
if (!validatedCountryPageUrls.success) {
getCountryPageUrlsFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedCountryPageUrls.error),
})
console.error(
"contentstack.countryPageUrls validation error",
JSON.stringify({
query: { lang },
error: validatedCountryPageUrls.error,
})
)
return []
}
getCountryPageUrlsSuccessCounter.add(1, { lang })
console.info(
"contentstack.countryPageUrls success",
JSON.stringify({ query: { lang } })
)
return validatedCountryPageUrls.data
}

View File

@@ -2,8 +2,6 @@ import { z } from "zod"
import { discriminatedUnionArray } from "@/lib/discriminatedUnion" import { discriminatedUnionArray } from "@/lib/discriminatedUnion"
import { removeMultipleSlashes } from "@/utils/url"
import { import {
cardGalleryRefsSchema, cardGalleryRefsSchema,
cardGallerySchema, cardGallerySchema,
@@ -40,27 +38,6 @@ export const destinationOverviewPageSchema = z.object({
}), }),
}) })
export const countryPageUrlSchema = z
.object({
all_destination_country_page: z.object({
items: z.array(
z
.object({
url: z.string(),
system: systemSchema,
})
.transform((data) => {
return {
url: removeMultipleSlashes(`/${data.system.locale}/${data.url}`),
}
})
),
}),
})
.transform(
({ all_destination_country_page }) => all_destination_country_page.items[0]
)
/** REFS */ /** REFS */
const destinationOverviewPageCardGalleryRef = z const destinationOverviewPageCardGalleryRef = z
.object({ .object({

View File

@@ -19,7 +19,8 @@ import {
getCountries, getCountries,
getHotelIdsByCityId, getHotelIdsByCityId,
} from "../../hotels/utils" } from "../../hotels/utils"
import { getCityListDataByCityIdentifier } from "../destinationCountryPage/utils" import { getCityPageUrls } from "../destinationCityPage/utils"
import { getCountryPageUrls } from "../destinationCountryPage/utils"
import { import {
destinationOverviewPageRefsSchema, destinationOverviewPageRefsSchema,
destinationOverviewPageSchema, destinationOverviewPageSchema,
@@ -32,7 +33,6 @@ import {
getDestinationOverviewPageRefsSuccessCounter, getDestinationOverviewPageRefsSuccessCounter,
getDestinationOverviewPageSuccessCounter, getDestinationOverviewPageSuccessCounter,
} from "./telemetry" } from "./telemetry"
import { getCountryPageUrl } from "./utils"
import type { DestinationsData } from "@/types/components/destinationOverviewPage/destinationsList/destinationsData" import type { DestinationsData } from "@/types/components/destinationOverviewPage/destinationsList/destinationsData"
import { import {
@@ -220,8 +220,8 @@ export const destinationOverviewPageQueryRouter = router({
revalidate: env.CACHE_TIME_HOTELS, revalidate: env.CACHE_TIME_HOTELS,
}, },
} }
const countries = await getCountries(options, params, ctx.lang) const countries = await getCountries(options, params, ctx.lang)
const countryPages = await getCountryPageUrls(ctx.lang)
if (!countries) { if (!countries) {
return null return null
@@ -237,6 +237,8 @@ export const destinationOverviewPageQueryRouter = router({
true true
) )
const cityPages = await getCityPageUrls(ctx.lang)
const destinations: DestinationsData = await Promise.all( const destinations: DestinationsData = await Promise.all(
Object.entries(citiesByCountry).map(async ([country, cities]) => { Object.entries(citiesByCountry).map(async ([country, cities]) => {
const citiesWithHotelCount = await Promise.all( const citiesWithHotelCount = await Promise.all(
@@ -252,29 +254,27 @@ export const destinationOverviewPageQueryRouter = router({
hotelIdsParams hotelIdsParams
) )
let cityUrl const cityPage = cityPages.find(
if (city.cityIdentifier) { (cityPage) => cityPage.city === city.cityIdentifier
cityUrl = await getCityListDataByCityIdentifier( )
ctx.lang,
city.cityIdentifier
)
}
return { return {
id: city.id, id: city.id,
name: city.name, name: city.name,
hotelIds: hotels, hotelIds: hotels,
hotelCount: hotels?.length ?? 0, hotelCount: hotels?.length ?? 0,
url: cityUrl?.url, url: cityPage?.url,
} }
}) })
) )
const countryUrl = await getCountryPageUrl(ctx.lang, country) const countryPage = countryPages.find(
(countryPage) => countryPage.country === country
)
return { return {
country, country,
countryUrl: countryUrl?.url, countryUrl: countryPage?.url,
numberOfHotels: citiesWithHotelCount.reduce( numberOfHotels: citiesWithHotelCount.reduce(
(acc, city) => acc + city.hotelCount, (acc, city) => acc + city.hotelCount,
0 0

View File

@@ -21,15 +21,3 @@ export const getDestinationOverviewPageSuccessCounter = meter.createCounter(
export const getDestinationOverviewPageFailCounter = meter.createCounter( export const getDestinationOverviewPageFailCounter = meter.createCounter(
"trpc.contentstack.destinationOverviewPage.get-fail" "trpc.contentstack.destinationOverviewPage.get-fail"
) )
export const getCountryPageUrlCounter = meter.createCounter(
"trpc.contentstack.getCountryPageUrl"
)
export const getCountryPageUrlSuccessCounter = meter.createCounter(
"trpc.contentstack.getCountryPageUrl-success"
)
export const getCountryPageUrlFailCounter = meter.createCounter(
"trpc.contentstack.getCountryPageUrl-fail"
)

View File

@@ -1,76 +0,0 @@
import { GetCountryPageUrl } from "@/lib/graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql"
import { request } from "@/lib/graphql/request"
import { countryPageUrlSchema } from "./output"
import {
getCountryPageUrlCounter,
getCountryPageUrlFailCounter,
getCountryPageUrlSuccessCounter,
} from "./telemetry"
import type { GetCountryPageUrlData } from "@/types/trpc/routers/contentstack/destinationOverviewPage"
import type { Lang } from "@/constants/languages"
export async function getCountryPageUrl(lang: Lang, country: string) {
getCountryPageUrlCounter.add(1, { lang, country })
console.info(
"contentstack.countryPageUrl start",
JSON.stringify({ query: { lang, country } })
)
const tag = `${lang}:country_page_url:${country}`
const response = await request<GetCountryPageUrlData>(
GetCountryPageUrl,
{
locale: lang,
country,
},
{
cache: "force-cache",
next: {
tags: [tag],
},
}
)
if (!response.data) {
getCountryPageUrlFailCounter.add(1, {
lang,
country,
error_type: "not_found",
error: `Country page not found for country: ${country}`,
})
console.error(
"contentstack.countryPageUrl not found error",
JSON.stringify({ query: { lang, country } })
)
return null
}
const validatedCountryPageUrl = countryPageUrlSchema.safeParse(response.data)
if (!validatedCountryPageUrl.success) {
getCountryPageUrlFailCounter.add(1, {
lang,
country,
error_type: "validation_error",
error: JSON.stringify(validatedCountryPageUrl.error),
})
console.error(
"contentstack.countryPageUrl validation error",
JSON.stringify({
query: { lang, country },
error: validatedCountryPageUrl.error,
})
)
return null
}
getCountryPageUrlSuccessCounter.add(1, { lang, country })
console.info(
"contentstack.countryPageUrl success",
JSON.stringify({ query: { lang, country } })
)
return validatedCountryPageUrl.data
}

View File

@@ -116,25 +116,23 @@ export const hotelPageRefsSchema = z.object({
}), }),
}) })
export const hotelPageUrlSchema = z export const hotelPageUrlsSchema = z
.object({ .object({
all_hotel_page: z.object({ all_hotel_page: z.object({
items: z items: z.array(
.array( z
z.object({ .object({
url: z.string(), url: z.string(),
hotel_page_id: z.string(),
system: systemSchema, system: systemSchema,
}) })
) .transform((data) => {
.max(1), return {
url: removeMultipleSlashes(`/${data.system.locale}/${data.url}`),
hotelId: data.hotel_page_id,
}
})
),
}), }),
}) })
.transform((data) => { .transform(({ all_hotel_page }) => all_hotel_page.items)
const page = data.all_hotel_page.items[0]
if (!page) {
return null
}
const lang = page.system.locale
return removeMultipleSlashes(`/${lang}/${page.url}`)
})

View File

@@ -22,12 +22,12 @@ export const getHotelPageFailCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-fail" "trpc.contentstack.hotelPage.get-fail"
) )
export const getHotelPageUrlCounter = meter.createCounter( export const getHotelPageUrlsCounter = meter.createCounter(
"trpc.contentstack.hotelPageUrl.get" "trpc.contentstack.hotelPageUrls.get"
) )
export const getHotelPageUrlSuccessCounter = meter.createCounter( export const getHotelPageUrlsSuccessCounter = meter.createCounter(
"trpc.contentstack.hotelPageUrl.get-success" "trpc.contentstack.hotelPageUrls.get-success"
) )
export const getHotelPageUrlFailCounter = meter.createCounter( export const getHotelPageUrlsFailCounter = meter.createCounter(
"trpc.contentstack.hotelPageUrl.get-fail" "trpc.contentstack.hotelPageUrls.get-fail"
) )

View File

@@ -1,29 +1,25 @@
import { GetHotelPageRefs } from "@/lib/graphql/Query/HotelPage/HotelPage.graphql" import { GetHotelPageRefs } from "@/lib/graphql/Query/HotelPage/HotelPage.graphql"
import { GetHotelPageUrl } from "@/lib/graphql/Query/HotelPage/HotelPageUrl.graphql" import { GetHotelPageUrls } from "@/lib/graphql/Query/HotelPage/HotelPageUrl.graphql"
import { request } from "@/lib/graphql/request" import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc" import { notFound } from "@/server/errors/trpc"
import { import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
generateHotelUrlTag,
generateTag,
generateTagsFromSystem,
} from "@/utils/generateTag"
import { hotelPageRefsSchema, hotelPageUrlSchema } from "./output" import { hotelPageRefsSchema, hotelPageUrlsSchema } from "./output"
import { import {
getHotelPageRefsCounter, getHotelPageRefsCounter,
getHotelPageRefsFailCounter, getHotelPageRefsFailCounter,
getHotelPageRefsSuccessCounter, getHotelPageRefsSuccessCounter,
getHotelPageUrlCounter, getHotelPageUrlsCounter,
getHotelPageUrlFailCounter, getHotelPageUrlsFailCounter,
getHotelPageUrlSuccessCounter, getHotelPageUrlsSuccessCounter,
} from "./telemetry" } from "./telemetry"
import { HotelPageEnum } from "@/types/enums/hotelPage" import { HotelPageEnum } from "@/types/enums/hotelPage"
import type { System } from "@/types/requests/system" import type { System } from "@/types/requests/system"
import type { import type {
GetHotelPageRefsSchema, GetHotelPageRefsSchema,
GetHotelPageUrlData, GetHotelPageUrlsData,
HotelPageRefs, HotelPageRefs,
} from "@/types/trpc/routers/contentstack/hotelPage" } from "@/types/trpc/routers/contentstack/hotelPage"
import type { Lang } from "@/constants/languages" import type { Lang } from "@/constants/languages"
@@ -136,63 +132,61 @@ export function getConnections({ hotel_page }: HotelPageRefs) {
return connections return connections
} }
export async function getHotelPageUrl(lang: Lang, hotelId: string) { export async function getHotelPageUrls(lang: Lang) {
getHotelPageUrlCounter.add(1, { lang, hotelId }) getHotelPageUrlsCounter.add(1, { lang })
console.info( console.info(
"contentstack.hotelPageUrl start", "contentstack.hotelPageUrls start",
JSON.stringify({ query: { lang, hotelId } }) JSON.stringify({ query: { lang } })
) )
const response = await request<GetHotelPageUrlData>( const tags = [`${lang}:hotel_page_urls`]
GetHotelPageUrl, const response = await request<GetHotelPageUrlsData>(
GetHotelPageUrls,
{ {
locale: lang, locale: lang,
hotelId,
}, },
{ {
cache: "force-cache", cache: "force-cache",
next: { next: {
tags: [generateHotelUrlTag(lang, hotelId)], tags,
}, },
} }
) )
if (!response.data) { if (!response.data) {
getHotelPageUrlFailCounter.add(1, { getHotelPageUrlsFailCounter.add(1, {
lang, lang,
hotelId,
error_type: "not_found", error_type: "not_found",
error: `Hotel page not found for hotelId: ${hotelId}`, error: `Hotel pages not found for lang: ${lang}`,
}) })
console.error( console.error(
"contentstack.hotelPageUrl not found error", "contentstack.hotelPageUrls not found error",
JSON.stringify({ query: { lang, hotelId } }) JSON.stringify({ query: { lang } })
) )
return null return []
} }
const validatedHotelPageUrl = hotelPageUrlSchema.safeParse(response.data) const validatedHotelPageUrls = hotelPageUrlsSchema.safeParse(response.data)
if (!validatedHotelPageUrl.success) { if (!validatedHotelPageUrls.success) {
getHotelPageUrlFailCounter.add(1, { getHotelPageUrlsFailCounter.add(1, {
lang, lang,
hotelId,
error_type: "validation_error", error_type: "validation_error",
error: JSON.stringify(validatedHotelPageUrl.error), error: JSON.stringify(validatedHotelPageUrls.error),
}) })
console.error( console.error(
"contentstack.hotelPageUrl validation error", "contentstack.hotelPageUrls validation error",
JSON.stringify({ JSON.stringify({
query: { lang, hotelId }, query: { lang },
error: validatedHotelPageUrl.error, error: validatedHotelPageUrls.error,
}) })
) )
return null return []
} }
getHotelPageUrlSuccessCounter.add(1, { lang, hotelId }) getHotelPageUrlsSuccessCounter.add(1, { lang })
console.info( console.info(
"contentstack.hotelPageUrl success", "contentstack.hotelPageUrl success",
JSON.stringify({ query: { lang, hotelId } }) JSON.stringify({ query: { lang } })
) )
return validatedHotelPageUrl.data return validatedHotelPageUrls.data
} }

View File

@@ -17,7 +17,7 @@ import { toApiLang } from "@/server/utils"
import { generateChildrenString } from "@/components/HotelReservation/utils" import { generateChildrenString } from "@/components/HotelReservation/utils"
import { cache } from "@/utils/cache" import { cache } from "@/utils/cache"
import { getHotelPageUrl } from "../contentstack/hotelPage/utils" import { getHotelPageUrls } from "../contentstack/hotelPage/utils"
import { getVerifiedUser, parsedUser } from "../user/query" import { getVerifiedUser, parsedUser } from "../user/query"
import { additionalDataSchema } from "./schemas/hotel/include/additionalData" import { additionalDataSchema } from "./schemas/hotel/include/additionalData"
import { meetingRoomsSchema } from "./schemas/meetingRoom" import { meetingRoomsSchema } from "./schemas/meetingRoom"
@@ -1100,21 +1100,21 @@ export const hotelQueryRouter = router({
) )
return [] return []
} }
const hotelPages = await getHotelPageUrls(language)
const hotels = await Promise.all( const hotels = await Promise.all(
hotelsToFetch.map(async (hotelId) => { hotelsToFetch.map(async (hotelId) => {
const [hotelData, url] = await Promise.all([ const hotelData = await getHotel(
getHotel( { hotelId, isCardOnlyPayment: false, language },
{ hotelId, isCardOnlyPayment: false, language }, ctx.serviceToken
ctx.serviceToken )
), const hotelPage = hotelPages.find(
getHotelPageUrl(language, hotelId), (page) => page.hotelId === hotelId
]) )
return hotelData return hotelData
? { ? {
...hotelData, ...hotelData,
url, url: hotelPage?.url ?? null,
} }
: null : null
}) })

View File

@@ -6,7 +6,7 @@ import { env } from "@/env/server"
import * as api from "@/lib/api" import * as api from "@/lib/api"
import { toApiLang } from "@/server/utils" import { toApiLang } from "@/server/utils"
import { getHotelPageUrl } from "../contentstack/hotelPage/utils" import { getHotelPageUrls } from "../contentstack/hotelPage/utils"
import { metrics } from "./metrics" import { metrics } from "./metrics"
import { import {
citiesByCountrySchema, citiesByCountrySchema,
@@ -484,17 +484,15 @@ export async function getHotelsByHotelIds(
lang: Lang, lang: Lang,
serviceToken: string serviceToken: string
) { ) {
const hotelPages = await getHotelPageUrls(lang)
const hotels = await Promise.all( const hotels = await Promise.all(
hotelIds.map(async (hotelId) => { hotelIds.map(async (hotelId) => {
const [hotelData, url] = await Promise.all([ const hotelData = await getHotel(
getHotel( { hotelId, language: lang, isCardOnlyPayment: false },
{ hotelId, language: lang, isCardOnlyPayment: false }, serviceToken
serviceToken )
), const hotelPage = hotelPages.find((page) => page.hotelId === hotelId)
getHotelPageUrl(lang, hotelId), return hotelData ? { ...hotelData, url: hotelPage?.url ?? null } : null
])
return hotelData ? { ...hotelData, url } : null
}) })
) )

View File

@@ -2,6 +2,7 @@ import type { z } from "zod"
import type { import type {
blocksSchema, blocksSchema,
cityPageUrlsSchema,
destinationCityListDataSchema, destinationCityListDataSchema,
destinationCityPageRefsSchema, destinationCityPageRefsSchema,
destinationCityPageSchema, destinationCityPageSchema,
@@ -18,6 +19,10 @@ export interface GetDestinationCityListDataResponse
export interface DestinationCityListData export interface DestinationCityListData
extends z.output<typeof destinationCityListDataSchema> {} extends z.output<typeof destinationCityListDataSchema> {}
export interface GetCityPageUrlsData
extends z.input<typeof cityPageUrlsSchema> {}
export interface CityPageUrlsData extends z.output<typeof cityPageUrlsSchema> {}
export interface DestinationCityListItem extends DestinationCityListData { export interface DestinationCityListItem extends DestinationCityListData {
cityName: string cityName: string

View File

@@ -2,6 +2,7 @@ import type { z } from "zod"
import type { import type {
blocksSchema, blocksSchema,
countryPageUrlsSchema,
destinationCountryPageRefsSchema, destinationCountryPageRefsSchema,
destinationCountryPageSchema, destinationCountryPageSchema,
} from "@/server/routers/contentstack/destinationCountryPage/output" } from "@/server/routers/contentstack/destinationCountryPage/output"
@@ -19,3 +20,6 @@ export interface GetDestinationCountryPageRefsSchema
export interface DestinationCountryPageRefs export interface DestinationCountryPageRefs
extends z.output<typeof destinationCountryPageRefsSchema> {} extends z.output<typeof destinationCountryPageRefsSchema> {}
export interface GetCountryPageUrlsData
extends z.input<typeof countryPageUrlsSchema> {}

View File

@@ -2,7 +2,6 @@ import type { z } from "zod"
import type { import type {
blocksSchema, blocksSchema,
countryPageUrlSchema,
destinationOverviewPageRefsSchema, destinationOverviewPageRefsSchema,
destinationOverviewPageSchema, destinationOverviewPageSchema,
} from "@/server/routers/contentstack/destinationOverviewPage/output" } from "@/server/routers/contentstack/destinationOverviewPage/output"
@@ -18,7 +17,4 @@ export interface GetDestinationOverviewPageRefsSchema
export interface DestinationOverviewPageRefs export interface DestinationOverviewPageRefs
extends z.output<typeof destinationOverviewPageRefsSchema> {} extends z.output<typeof destinationOverviewPageRefsSchema> {}
export interface GetCountryPageUrlData
extends z.input<typeof countryPageUrlSchema> {}
export type Block = z.output<typeof blocksSchema> export type Block = z.output<typeof blocksSchema>

View File

@@ -4,7 +4,7 @@ import type {
contentBlock, contentBlock,
hotelPageRefsSchema, hotelPageRefsSchema,
hotelPageSchema, hotelPageSchema,
hotelPageUrlSchema, hotelPageUrlsSchema,
} from "@/server/routers/contentstack/hotelPage/output" } from "@/server/routers/contentstack/hotelPage/output"
import type { activitiesCardSchema } from "@/server/routers/contentstack/schemas/blocks/activitiesCard" import type { activitiesCardSchema } from "@/server/routers/contentstack/schemas/blocks/activitiesCard"
import type { spaPageSchema } from "@/server/routers/contentstack/schemas/blocks/spaPage" import type { spaPageSchema } from "@/server/routers/contentstack/schemas/blocks/spaPage"
@@ -22,7 +22,6 @@ export interface GetHotelPageRefsSchema
extends z.input<typeof hotelPageRefsSchema> {} extends z.input<typeof hotelPageRefsSchema> {}
export interface HotelPageRefs extends z.output<typeof hotelPageRefsSchema> {} export interface HotelPageRefs extends z.output<typeof hotelPageRefsSchema> {}
export interface GetHotelPageUrlsData
export interface GetHotelPageUrlData extends z.input<typeof hotelPageUrlsSchema> {}
extends z.input<typeof hotelPageUrlSchema> {} export type HotelPageUrls = z.output<typeof hotelPageUrlsSchema>
export type HotelPageUrl = z.output<typeof hotelPageUrlSchema>