Include more details when throwing errors for debugging in Sentry * WIP throw errors with more details for debugging in Sentry * Fix throwing response-data * Clearer message when a response fails * Add message to errors * better typings * . * Try to send profileID and membershipNumber to Sentry when we fail to parse the apiResponse * rename notFound -> notFoundError * Merge branch 'master' of bitbucket.org:scandic-swap/web into chore/add-error-details-for-sentry Approved-by: Linus Flood
265 lines
9.5 KiB
TypeScript
265 lines
9.5 KiB
TypeScript
import { cache } from "react"
|
|
|
|
import { createCounter } from "@scandic-hotels/common/telemetry"
|
|
|
|
import { router } from "../../.."
|
|
import { PageContentTypeEnum } from "../../../enums/contentType"
|
|
import { notFoundError } from "../../../errors"
|
|
import { GetAccountPageMetadata } from "../../../graphql/Query/AccountPage/Metadata.graphql"
|
|
import { GetCampaignOverviewPageMetadata } from "../../../graphql/Query/CampaignOverviewPage/Metadata.graphql"
|
|
import { GetCampaignPageMetadata } from "../../../graphql/Query/CampaignPage/Metadata.graphql"
|
|
import { GetCollectionPageMetadata } from "../../../graphql/Query/CollectionPage/Metadata.graphql"
|
|
import { GetContentPageMetadata } from "../../../graphql/Query/ContentPage/Metadata.graphql"
|
|
import { GetDestinationCityPageMetadata } from "../../../graphql/Query/DestinationCityPage/Metadata.graphql"
|
|
import { GetDestinationCountryPageMetadata } from "../../../graphql/Query/DestinationCountryPage/Metadata.graphql"
|
|
import { GetDestinationOverviewPageMetadata } from "../../../graphql/Query/DestinationOverviewPage/Metadata.graphql"
|
|
import { GetHotelPageMetadata } from "../../../graphql/Query/HotelPage/Metadata.graphql"
|
|
import { GetLoyaltyPageMetadata } from "../../../graphql/Query/LoyaltyPage/Metadata.graphql"
|
|
import { GetPromoCampaignPageMetadata } from "../../../graphql/Query/PromoCampaignPage/Metadata.graphql"
|
|
import { GetStartPageMetadata } from "../../../graphql/Query/StartPage/Metadata.graphql"
|
|
import { request } from "../../../graphql/request"
|
|
import { contentStackUidWithServiceProcedure } from "../../../procedures"
|
|
import { generateTag } from "../../../utils/generateTag"
|
|
import { getHotel } from "../../hotels/services/getHotel"
|
|
import { getUrlsOfAllLanguages } from "../languageSwitcher/utils"
|
|
import { getMetadataInput } from "./input"
|
|
import { getNonContentstackUrls, rawMetadataSchema } from "./output"
|
|
import { affix, getCityData, getCountryData } from "./utils"
|
|
|
|
import type { LanguageSwitcherData } from "@scandic-hotels/common/constants/language"
|
|
import type { Lang } from "@scandic-hotels/common/constants/language"
|
|
import type { DocumentNode } from "graphql"
|
|
|
|
import type { RawMetadataSchema } from "./output"
|
|
|
|
const fetchMetadata = cache(async function fetchMemoizedMetadata<T>(
|
|
query: string | DocumentNode,
|
|
{ uid, lang }: { uid: string; lang: Lang }
|
|
) {
|
|
const getMetadataCounter = createCounter("trpc.contentstack.metadata.get")
|
|
const metricsGetMetadata = getMetadataCounter.init({ lang, uid })
|
|
|
|
metricsGetMetadata.start()
|
|
|
|
const response = await request<T>(
|
|
query,
|
|
{ locale: lang, uid },
|
|
{
|
|
key: generateTag(lang, uid, affix),
|
|
ttl: "max",
|
|
}
|
|
)
|
|
|
|
if (!response.data) {
|
|
metricsGetMetadata.noDataError()
|
|
|
|
throw notFoundError({
|
|
message: "GetMetadata returned no data",
|
|
errorDetails: { lang, uid },
|
|
})
|
|
}
|
|
|
|
metricsGetMetadata.success()
|
|
|
|
return response.data
|
|
})
|
|
|
|
type Alternates = {
|
|
canonical?: string | null
|
|
languages?: Record<string, string>
|
|
}
|
|
|
|
type Robots = {
|
|
index?: boolean
|
|
follow?: boolean
|
|
}
|
|
|
|
export const metadataQueryRouter = router({
|
|
get: contentStackUidWithServiceProcedure
|
|
.input(getMetadataInput)
|
|
.query(async ({ ctx, input }) => {
|
|
const variables = {
|
|
lang: ctx.lang,
|
|
uid: ctx.uid,
|
|
}
|
|
|
|
let urls: LanguageSwitcherData | null = null
|
|
if (
|
|
input.subpage ||
|
|
input.filterFromUrl ||
|
|
!ctx.uid ||
|
|
!ctx.contentType
|
|
) {
|
|
urls = getNonContentstackUrls(ctx.lang, `${ctx.lang}/${ctx.pathname}`)
|
|
} else {
|
|
urls = await getUrlsOfAllLanguages(ctx.lang, ctx.uid, ctx.contentType)
|
|
}
|
|
|
|
let data: unknown = null
|
|
switch (ctx.contentType) {
|
|
case PageContentTypeEnum.accountPage:
|
|
const accountPageResponse = await fetchMetadata<{
|
|
account_page: RawMetadataSchema
|
|
}>(GetAccountPageMetadata, variables)
|
|
data = accountPageResponse.account_page
|
|
break
|
|
case PageContentTypeEnum.campaignOverviewPage:
|
|
const campaignOverviewPageResponse = await fetchMetadata<{
|
|
campaign_overview_page: RawMetadataSchema
|
|
}>(GetCampaignOverviewPageMetadata, variables)
|
|
data = campaignOverviewPageResponse.campaign_overview_page
|
|
break
|
|
case PageContentTypeEnum.campaignPage:
|
|
const campaignPageResponse = await fetchMetadata<{
|
|
campaign_page: RawMetadataSchema
|
|
}>(GetCampaignPageMetadata, variables)
|
|
data = campaignPageResponse.campaign_page
|
|
break
|
|
case PageContentTypeEnum.collectionPage:
|
|
const collectionPageResponse = await fetchMetadata<{
|
|
collection_page: RawMetadataSchema
|
|
}>(GetCollectionPageMetadata, variables)
|
|
data = collectionPageResponse.collection_page
|
|
break
|
|
case PageContentTypeEnum.contentPage:
|
|
const contentPageResponse = await fetchMetadata<{
|
|
content_page: RawMetadataSchema
|
|
}>(GetContentPageMetadata, variables)
|
|
data = contentPageResponse.content_page
|
|
break
|
|
case PageContentTypeEnum.destinationOverviewPage:
|
|
const destinationOverviewPageResponse = await fetchMetadata<{
|
|
destination_overview_page: RawMetadataSchema
|
|
}>(GetDestinationOverviewPageMetadata, variables)
|
|
data = destinationOverviewPageResponse.destination_overview_page
|
|
break
|
|
case PageContentTypeEnum.destinationCountryPage:
|
|
const destinationCountryPageResponse = await fetchMetadata<{
|
|
destination_country_page: RawMetadataSchema
|
|
}>(GetDestinationCountryPageMetadata, variables)
|
|
|
|
const countryData = await getCountryData(
|
|
destinationCountryPageResponse.destination_country_page,
|
|
input,
|
|
ctx.serviceToken,
|
|
ctx.lang
|
|
)
|
|
data = {
|
|
...destinationCountryPageResponse.destination_country_page,
|
|
destinationData: countryData,
|
|
}
|
|
break
|
|
case PageContentTypeEnum.destinationCityPage:
|
|
const destinationCityPageResponse = await fetchMetadata<{
|
|
destination_city_page: RawMetadataSchema
|
|
}>(GetDestinationCityPageMetadata, variables)
|
|
const cityData = await getCityData(
|
|
destinationCityPageResponse.destination_city_page,
|
|
input,
|
|
ctx.serviceToken,
|
|
ctx.lang
|
|
)
|
|
data = {
|
|
...destinationCityPageResponse.destination_city_page,
|
|
destinationData: cityData,
|
|
}
|
|
break
|
|
case PageContentTypeEnum.loyaltyPage:
|
|
const loyaltyPageResponse = await fetchMetadata<{
|
|
loyalty_page: RawMetadataSchema
|
|
}>(GetLoyaltyPageMetadata, variables)
|
|
data = loyaltyPageResponse.loyalty_page
|
|
break
|
|
case PageContentTypeEnum.hotelPage:
|
|
const hotelPageResponse = await fetchMetadata<{
|
|
hotel_page: RawMetadataSchema
|
|
}>(GetHotelPageMetadata, variables)
|
|
const hotelPageData = hotelPageResponse.hotel_page
|
|
const hotelData = hotelPageData.hotel_page_id
|
|
? await getHotel(
|
|
{
|
|
hotelId: hotelPageData.hotel_page_id,
|
|
isCardOnlyPayment: false,
|
|
language: ctx.lang,
|
|
},
|
|
ctx.serviceToken
|
|
)
|
|
: null
|
|
data = {
|
|
...hotelPageData,
|
|
...(hotelData
|
|
? {
|
|
hotelData: {
|
|
...hotelData.hotel,
|
|
translatedCityName:
|
|
hotelData.cities?.[0]?.name || hotelData.hotel.cityName,
|
|
},
|
|
additionalHotelData: hotelData.additionalData,
|
|
hotelRestaurants: hotelData.restaurants,
|
|
}
|
|
: {}),
|
|
subpageUrl: input.subpage,
|
|
}
|
|
break
|
|
case PageContentTypeEnum.startPage:
|
|
const startPageResponse = await fetchMetadata<{
|
|
start_page: RawMetadataSchema
|
|
}>(GetStartPageMetadata, variables)
|
|
data = startPageResponse.start_page
|
|
break
|
|
case PageContentTypeEnum.promoCampaignPage:
|
|
const promoCampaignPageResponse = await fetchMetadata<{
|
|
promo_campaign_page: RawMetadataSchema
|
|
}>(GetPromoCampaignPageMetadata, variables)
|
|
data = promoCampaignPageResponse.promo_campaign_page
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
|
|
let alternates: Alternates | null = null
|
|
let robots: Robots | null = null
|
|
|
|
if (urls) {
|
|
const languages: Record<string, string> = {}
|
|
Object.entries(urls).forEach(([lang, { url }]) => {
|
|
languages[lang] = url
|
|
})
|
|
const canonical = urls[ctx.lang]?.url
|
|
alternates = {
|
|
canonical,
|
|
languages,
|
|
}
|
|
}
|
|
|
|
if (!data) {
|
|
alternates = null
|
|
}
|
|
|
|
if (input.noIndex) {
|
|
robots = {
|
|
index: false,
|
|
follow: false,
|
|
}
|
|
}
|
|
|
|
const transformMetadataCounter = createCounter(
|
|
"trpc.contentstack.metadata.transform"
|
|
)
|
|
const metricsTransformMetadata = transformMetadataCounter.init()
|
|
|
|
metricsTransformMetadata.start()
|
|
|
|
const rawMetadataResult = await rawMetadataSchema.safeParseAsync(data)
|
|
|
|
if (!rawMetadataResult.success) {
|
|
metricsTransformMetadata.validationError(rawMetadataResult.error)
|
|
return { rawMetadata: null, robots: null, alternates: null }
|
|
}
|
|
|
|
metricsTransformMetadata.success()
|
|
|
|
return { rawMetadata: rawMetadataResult.data, robots, alternates }
|
|
}),
|
|
})
|