Files
web/packages/trpc/lib/routers/contentstack/base/query.ts
Joakim Jäderberg 99537b13e8 Merged in chore/add-error-details-for-sentry (pull request #3378)
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
2026-01-12 09:01:44 +00:00

450 lines
12 KiB
TypeScript

import { cache } from "react"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "../../.."
import { notFoundError } from "../../../errors"
import { GetContactConfig } from "../../../graphql/Query/ContactConfig.graphql"
import { GetFooter, GetFooterRef } from "../../../graphql/Query/Footer.graphql"
import { GetHeader, GetHeaderRef } from "../../../graphql/Query/Header.graphql"
import {
GetSiteConfig,
GetSiteConfigRef,
} from "../../../graphql/Query/SiteConfig.graphql"
import {
GetSitewideCampaignBanner,
GetSitewideCampaignBannerRef,
} from "../../../graphql/Query/SitewideCampaignBanner.graphql"
import { request } from "../../../graphql/request"
import { contentstackBaseProcedure } from "../../../procedures"
import { langInput } from "../../../utils"
import {
generateRefsResponseTag,
generateTag,
generateTags,
generateTagsFromSystem,
} from "../../../utils/generateTag"
import {
type ContactConfigData,
headerRefsSchema,
headerSchema,
siteConfigRefSchema,
siteConfigSchema,
sitewideCampaignBannerRefSchema,
sitewideCampaignBannerSchema,
validateContactConfigSchema,
validateFooterConfigSchema,
validateFooterRefConfigSchema,
} from "./output"
import {
getAlertPhoneContactData,
getConnections,
getFooterConnections,
getSiteConfigConnections,
getSitewideCampaignBannerConnections,
} from "./utils"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { FooterDataRaw, FooterRefDataRaw } from "../../../types/footer"
import type {
GetHeader as GetHeaderData,
GetHeaderRefs,
} from "../../../types/header"
import type {
GetSiteConfigData,
GetSiteConfigRefData,
GetSitewideCampaignBannerData,
GetSitewideCampaignBannerRefData,
} from "../../../types/siteConfig"
const getContactConfig = cache(async (lang: Lang) => {
const getContactConfigCounter = createCounter(
"trpc.contentstack.contactConfig.get"
)
const metricsGetContactConfig = getContactConfigCounter.init({ lang })
metricsGetContactConfig.start()
const variables = {
locale: lang,
}
const response = await request<ContactConfigData>(
GetContactConfig,
variables,
{
key: `${lang}:contact`,
ttl: "max",
}
)
if (!response.data) {
metricsGetContactConfig.noDataError()
throw notFoundError({
message: "GetContactConfig returned no data",
errorDetails: variables,
})
}
const verifiedData = validateContactConfigSchema.safeParse(response.data)
if (!verifiedData.success) {
metricsGetContactConfig.validationError(verifiedData.error)
return null
}
metricsGetContactConfig.success()
return verifiedData.data.all_contact_config.items[0]
})
export const baseQueryRouter = router({
contact: contentstackBaseProcedure.query(async ({ ctx }) => {
return await getContactConfig(ctx.lang)
}),
header: contentstackBaseProcedure.query(async ({ ctx }) => {
const { lang } = ctx
const getHeaderRefsCounter = createCounter(
"trpc.contentstack.header.get.refs"
)
const metricsGetHeaderRefs = getHeaderRefsCounter.init({ lang })
metricsGetHeaderRefs.start()
const variables = { locale: lang }
const responseRef = await request<GetHeaderRefs>(GetHeaderRef, variables, {
key: generateRefsResponseTag(lang, "header"),
ttl: "max",
})
if (!responseRef.data) {
metricsGetHeaderRefs.noDataError()
throw notFoundError({
message: "GetHeaderRef returned no data",
errorDetails: variables,
})
}
const validatedHeaderRefs = headerRefsSchema.safeParse(responseRef.data)
if (!validatedHeaderRefs.success) {
metricsGetHeaderRefs.validationError(validatedHeaderRefs.error)
return null
}
metricsGetHeaderRefs.success()
const connections = getConnections(validatedHeaderRefs.data)
const getHeaderCounter = createCounter("trpc.contentstack.header.get")
const metricsGetHeader = getHeaderCounter.init({ lang })
metricsGetHeader.start()
const tags = [
generateTagsFromSystem(lang, connections),
generateTag(lang, validatedHeaderRefs.data.header.system.uid),
].flat()
const response = await request<GetHeaderData>(GetHeader, variables, {
key: tags,
ttl: "max",
})
if (!response.data) {
metricsGetHeader.noDataError()
throw notFoundError({
message: "GetHeader returned no data",
errorDetails: variables,
})
}
const validatedHeaderConfig = headerSchema.safeParse(response.data)
if (!validatedHeaderConfig.success) {
metricsGetHeader.validationError(validatedHeaderConfig.error)
return null
}
metricsGetHeader.success()
return {
data: validatedHeaderConfig.data.header,
}
}),
footer: contentstackBaseProcedure.query(async ({ ctx }) => {
const { lang } = ctx
const getFooterRefsCounter = createCounter(
"trpc.contentstack.footer.get.refs"
)
const metricsGetFooterRefs = getFooterRefsCounter.init({ lang })
metricsGetFooterRefs.start()
const variables = { locale: lang }
const responseRef = await request<FooterRefDataRaw>(
GetFooterRef,
variables,
{
key: generateRefsResponseTag(lang, "footer"),
ttl: "max",
}
)
if (!responseRef.data) {
metricsGetFooterRefs.noDataError()
throw notFoundError({
message: "GetFooterRef returned no data",
errorDetails: variables,
})
}
const validatedFooterRefs = validateFooterRefConfigSchema.safeParse(
responseRef.data
)
if (!validatedFooterRefs.success) {
metricsGetFooterRefs.validationError(validatedFooterRefs.error)
return null
}
metricsGetFooterRefs.success()
const connections = getFooterConnections(validatedFooterRefs.data)
const footerUID = responseRef.data.all_footer.items[0].system.uid
const getFooterCounter = createCounter("trpc.contentstack.footer.get")
const metricsGetFooter = getFooterCounter.init({ lang })
metricsGetFooter.start()
const tags = [
generateTags(lang, connections),
generateTag(lang, footerUID),
].flat()
const response = await request<FooterDataRaw>(GetFooter, variables, {
key: tags,
ttl: "max",
})
if (!response.data) {
metricsGetFooter.noDataError()
throw notFoundError({
message: "GetFooter returned no data",
errorDetails: variables,
})
}
const validatedFooterConfig = validateFooterConfigSchema.safeParse(
response.data
)
if (!validatedFooterConfig.success) {
metricsGetFooter.validationError(validatedFooterConfig.error)
return null
}
metricsGetFooter.success()
return validatedFooterConfig.data
}),
sitewideCampaignBanner: router({
get: contentstackBaseProcedure
.input(langInput)
.query(async ({ input, ctx }) => {
const lang = input.lang ?? ctx.lang
const getSitewideCampaignBannerRefsCounter = createCounter(
"trpc.contentstack.sitewideCampaignBanner.get.refs"
)
const metricsGetSitewideCampaignBannerRefs =
getSitewideCampaignBannerRefsCounter.init({
lang,
})
metricsGetSitewideCampaignBannerRefs.start()
const refVariables = { locale: lang }
const responseRef = await request<GetSitewideCampaignBannerRefData>(
GetSitewideCampaignBannerRef,
refVariables,
{
key: generateRefsResponseTag(lang, "sitewide_campaign_banner"),
ttl: "max",
}
)
if (!responseRef.data) {
metricsGetSitewideCampaignBannerRefs.noDataError()
throw notFoundError({
message: "GetSitewideCampaignBannerRef returned no data",
errorDetails: refVariables,
})
}
const validatedSitewideCampaignBannerRef =
sitewideCampaignBannerRefSchema.safeParse(responseRef.data)
if (!validatedSitewideCampaignBannerRef.success) {
metricsGetSitewideCampaignBannerRefs.validationError(
validatedSitewideCampaignBannerRef.error
)
return null
}
const connections = getSitewideCampaignBannerConnections(
validatedSitewideCampaignBannerRef.data
)
const tags = [generateTagsFromSystem(lang, connections)].flat()
metricsGetSitewideCampaignBannerRefs.success()
const getSitewideCampaignBannerCounter = createCounter(
"trpc.contentstack.sitewideCampaignBanner.get"
)
const metricsGetSitewideCampaignBanner =
getSitewideCampaignBannerCounter.init({
lang,
})
metricsGetSitewideCampaignBanner.start()
const variables = { locale: lang }
const sitewideCampaignBannerResponse =
await request<GetSitewideCampaignBannerData>(
GetSitewideCampaignBanner,
variables,
{ key: tags, ttl: "max" }
)
if (!sitewideCampaignBannerResponse.data) {
metricsGetSitewideCampaignBanner.noDataError()
throw notFoundError({
message: "GetSitewideCampaignBanner returned no data",
errorDetails: variables,
})
}
const validatedSitewideCampaignBanner =
sitewideCampaignBannerSchema.safeParse(
sitewideCampaignBannerResponse.data
)
if (!validatedSitewideCampaignBanner.success) {
metricsGetSitewideCampaignBanner.validationError(
validatedSitewideCampaignBanner.error
)
return null
}
metricsGetSitewideCampaignBanner.success()
return validatedSitewideCampaignBanner.data
}),
}),
siteConfig: contentstackBaseProcedure
.input(langInput)
.query(async ({ input, ctx }) => {
const lang = input.lang ?? ctx.lang
const getSiteConfigRefsCounter = createCounter(
"trpc.contentstack.siteConfig.get.refs"
)
const metricsGetSiteConfigRefs = getSiteConfigRefsCounter.init({ lang })
metricsGetSiteConfigRefs.start()
const refVariables = {
locale: lang,
}
const responseRef = await request<GetSiteConfigRefData>(
GetSiteConfigRef,
refVariables,
{
key: generateRefsResponseTag(lang, "site_config"),
ttl: "max",
}
)
if (!responseRef.data) {
metricsGetSiteConfigRefs.noDataError()
throw notFoundError({
message: "SiteConfigRefs returned no data",
errorDetails: refVariables,
})
}
const validatedSiteConfigRef = siteConfigRefSchema.safeParse(
responseRef.data
)
if (!validatedSiteConfigRef.success) {
metricsGetSiteConfigRefs.validationError(validatedSiteConfigRef.error)
return null
}
const connections = getSiteConfigConnections(validatedSiteConfigRef.data)
const siteConfigUid = responseRef.data.all_site_config.items[0].system.uid
const tags = [
generateTagsFromSystem(lang, connections),
generateTag(lang, siteConfigUid),
].flat()
metricsGetSiteConfigRefs.success()
const getSiteConfigCounter = createCounter(
"trpc.contentstack.siteConfig.get"
)
const metricsGetSiteConfig = getSiteConfigCounter.init({ lang })
metricsGetSiteConfig.start()
const variables = { locale: lang }
const [siteConfigResponse, contactConfig] = await Promise.all([
request<GetSiteConfigData>(GetSiteConfig, variables, {
key: tags,
ttl: "max",
}),
getContactConfig(lang),
])
if (!siteConfigResponse.data) {
metricsGetSiteConfig.noDataError()
throw notFoundError({
message: "SiteConfig returned no data",
errorDetails: variables,
})
}
const validatedSiteConfig = siteConfigSchema.safeParse(
siteConfigResponse.data
)
if (!validatedSiteConfig.success) {
metricsGetSiteConfig.validationError(validatedSiteConfig.error)
return null
}
metricsGetSiteConfig.success()
const { sitewideAlert } = validatedSiteConfig.data
return {
...validatedSiteConfig.data,
sitewideAlert: sitewideAlert
? {
...sitewideAlert,
phoneContact: contactConfig
? getAlertPhoneContactData(sitewideAlert, contactConfig)
: null,
}
: null,
}
}),
})