Files
web/apps/scandic-web/server/routers/contentstack/metadata/query.ts
2025-04-23 22:40:46 +00:00

244 lines
8.4 KiB
TypeScript

import { cache } from "react"
import { GetAccountPageMetadata } from "@/lib/graphql/Query/AccountPage/Metadata.graphql"
import { GetCollectionPageMetadata } from "@/lib/graphql/Query/CollectionPage/Metadata.graphql"
import { GetContentPageMetadata } from "@/lib/graphql/Query/ContentPage/Metadata.graphql"
import { GetDestinationCityPageMetadata } from "@/lib/graphql/Query/DestinationCityPage/Metadata.graphql"
import { GetDestinationCountryPageMetadata } from "@/lib/graphql/Query/DestinationCountryPage/Metadata.graphql"
import { GetDestinationOverviewPageMetadata } from "@/lib/graphql/Query/DestinationOverviewPage/Metadata.graphql"
import { GetHotelPageMetadata } from "@/lib/graphql/Query/HotelPage/Metadata.graphql"
import { GetLoyaltyPageMetadata } from "@/lib/graphql/Query/LoyaltyPage/Metadata.graphql"
import { GetStartPageMetadata } from "@/lib/graphql/Query/StartPage/Metadata.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentStackUidWithServiceProcedure, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag"
import { getHotel } from "../../hotels/utils"
import { getUrlsOfAllLanguages } from "../languageSwitcher/utils"
import { getMetadataInput } from "./input"
import { getNonContentstackUrls, metadataSchema } from "./output"
import { affix, getCityData, getCountryData } from "./utils"
import type { Metadata } from "next"
import { PageContentTypeEnum } from "@/types/requests/contentType"
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
import type { RawMetadataSchema } from "@/types/trpc/routers/contentstack/metadata"
import type { Lang } from "@/constants/languages"
const fetchMetadata = cache(async function fetchMemoizedMetadata<T>(
query: string,
{ 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) {
const notFoundError = notFound(response)
metricsGetMetadata.noDataError()
throw notFoundError
}
metricsGetMetadata.success()
return response.data
})
async function getTransformedMetadata(data: unknown) {
const transformMetadataCounter = createCounter(
"trpc.contentstack",
"metadata.transform"
)
const metricsTransformMetadata = transformMetadataCounter.init()
metricsTransformMetadata.start()
const validatedMetadata = await metadataSchema.safeParseAsync(data)
if (!validatedMetadata.success) {
metricsTransformMetadata.validationError(validatedMetadata.error)
return null
}
metricsTransformMetadata.success()
return validatedMetadata.data
}
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 alternates: Metadata["alternates"] = null
if (urls) {
const languages: Record<string, string> = {}
Object.entries(urls)
.filter(([lang]) => lang !== ctx.lang)
.forEach(([lang, { url }]) => {
languages[lang] = url
})
const canonical = urls[ctx.lang]?.url
alternates = {
canonical,
languages,
}
}
let metadata: Metadata | null = null
switch (ctx.contentType) {
case PageContentTypeEnum.accountPage:
const accountPageResponse = await fetchMetadata<{
account_page: RawMetadataSchema
}>(GetAccountPageMetadata, variables)
metadata = await getTransformedMetadata(
accountPageResponse.account_page
)
break
case PageContentTypeEnum.collectionPage:
const collectionPageResponse = await fetchMetadata<{
collection_page: RawMetadataSchema
}>(GetCollectionPageMetadata, variables)
metadata = await getTransformedMetadata(
collectionPageResponse.collection_page
)
break
case PageContentTypeEnum.contentPage:
const contentPageResponse = await fetchMetadata<{
content_page: RawMetadataSchema
}>(GetContentPageMetadata, variables)
metadata = await getTransformedMetadata(
contentPageResponse.content_page
)
break
case PageContentTypeEnum.destinationOverviewPage:
const destinationOverviewPageResponse = await fetchMetadata<{
destination_overview_page: RawMetadataSchema
}>(GetDestinationOverviewPageMetadata, variables)
metadata = await getTransformedMetadata(
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
)
metadata = await getTransformedMetadata({
...destinationCountryPageResponse.destination_country_page,
...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
)
metadata = await getTransformedMetadata({
...destinationCityPageResponse.destination_city_page,
...cityData,
})
break
case PageContentTypeEnum.loyaltyPage:
const loyaltyPageResponse = await fetchMetadata<{
loyalty_page: RawMetadataSchema
}>(GetLoyaltyPageMetadata, variables)
metadata = await getTransformedMetadata(
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
metadata = await getTransformedMetadata({
...hotelPageData,
...(hotelData
? {
hotelData: hotelData.hotel,
additionalHotelData: hotelData.additionalData,
hotelRestaurants: hotelData.restaurants,
}
: {}),
subpageUrl: input.subpage,
})
break
case PageContentTypeEnum.startPage:
const startPageResponse = await fetchMetadata<{
start_page: RawMetadataSchema
}>(GetStartPageMetadata, variables)
metadata = await getTransformedMetadata(startPageResponse.start_page)
default:
break
}
if (metadata) {
if (alternates) {
metadata.alternates = alternates
}
if (input.noIndex) {
metadata.robots = {
index: false,
follow: false,
}
}
}
return metadata
}),
})