Merged in feat/rework-contentstack (pull request #3493)

Feat(SW-3708): refactor contentstack fetching (removing all refs) and cache invalidation

* Remove all REFS

* Revalidate correct language

* PR fixes

* PR fixes

* Throw when errors from contentstack api


Approved-by: Joakim Jäderberg
This commit is contained in:
Linus Flood
2026-01-27 12:38:36 +00:00
parent a5e214f783
commit 5fc93472f4
193 changed files with 489 additions and 9018 deletions

View File

@@ -3,14 +3,9 @@ import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { IconName } from "@scandic-hotels/design-system/Icons/iconName"
import {
linkConnectionRefs,
linkConnectionSchema,
} from "../schemas/linkConnection"
import { systemSchema } from "../schemas/system"
import { linkConnectionSchema } from "../schemas/linkConnection"
export type UsePointsModalData = z.output<typeof usePointsModalSchema>
export type UsePointsModalRefsData = z.output<typeof usePointsModalRefsSchema>
type LinkGroupItem = z.infer<typeof linkGroupItemSchema>
const linkGroupItemSchema = z.intersection(
@@ -57,16 +52,3 @@ export const usePointsModalSchema = z.object({
})
.optional(),
})
export const usePointsModalRefsSchema = z.object({
all_usepointsmodal: z
.object({
items: z.array(
z.object({
link_group: z.array(linkConnectionRefs),
system: systemSchema,
})
),
})
.optional(),
})

View File

@@ -2,66 +2,15 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "../../.."
import { notFoundError } from "../../../errors"
import {
GetUsePointsModal,
GetUsePointsModalRefs,
} from "../../../graphql/Query/UsePointsModal.graphql"
import { GetUsePointsModal } from "../../../graphql/Query/UsePointsModal.graphql"
import { request } from "../../../graphql/request"
import { contentstackBaseProcedure } from "../../../procedures"
import {
generateRefsResponseTag,
generateTag,
generateTagsFromSystem,
} from "../../../utils/generateTag"
import {
type UsePointsModalData,
type UsePointsModalRefsData,
usePointsModalRefsSchema,
usePointsModalSchema,
} from "./output"
import { getConnections } from "./utils"
import { generateTag } from "../../../utils/generateTag"
import { type UsePointsModalData, usePointsModalSchema } from "./output"
export const usePointsModalQueryRouter = router({
get: contentstackBaseProcedure.query(async ({ ctx }) => {
const { lang, uid } = ctx
const getRefsCounter = createCounter(
"trpc.contentstack.usePointsModal.get.refs"
)
const metricsRefs = getRefsCounter.init({
lang,
uid,
})
metricsRefs.start()
const refsTag = generateRefsResponseTag(lang, "usepointsmodal")
const variables = { locale: lang }
const refsResponse = await request<UsePointsModalRefsData>(
GetUsePointsModalRefs,
variables,
{
key: refsTag,
ttl: "max",
}
)
if (!refsResponse.data) {
metricsRefs.noDataError()
throw notFoundError({
message: "GetUsePointsModalRefs returned no data",
errorDetails: variables,
})
}
const validatedRefsData = usePointsModalRefsSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
metricsRefs.validationError(validatedRefsData.error)
return null
}
metricsRefs.success()
const getCounter = createCounter("trpc.contentstack.usePointsModal.get")
@@ -71,18 +20,13 @@ export const usePointsModalQueryRouter = router({
})
metrics.start()
const connections = getConnections(validatedRefsData.data)
const tags = [
generateTagsFromSystem(lang, connections),
generateTag(lang, "usepointsmodal"),
].flat()
const variables = { locale: lang }
const response = await request<UsePointsModalData>(
GetUsePointsModal,
variables,
{
key: tags,
key: generateTag(lang, "usepointsmodal"),
ttl: "max",
}
)

View File

@@ -1,17 +0,0 @@
import type { System } from "../schemas/system"
import type { UsePointsModalRefsData } from "./output"
export function getConnections({ all_usepointsmodal }: UsePointsModalRefsData) {
const connections: System["system"][] = all_usepointsmodal?.items?.[0]
? [all_usepointsmodal?.items?.[0]?.system]
: []
if (all_usepointsmodal && all_usepointsmodal.items) {
all_usepointsmodal?.items.forEach((item) => {
item.link_group.forEach((link) => {
link.link?.uid && connections.push(link.link)
})
})
}
return connections
}

View File

@@ -4,18 +4,9 @@ import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/i
import { AccountPageEnum } from "../../../types/accountPageEnum"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
accordionRefsSchema,
accordionSchema,
} from "../schemas/blocks/accordion"
import {
dynamicContentRefsSchema,
dynamicContentSchema,
} from "../schemas/blocks/dynamicContent"
import {
shortcutsRefsSchema,
shortcutsSchema,
} from "../schemas/blocks/shortcuts"
import { accordionSchema } from "../schemas/blocks/accordion"
import { dynamicContentSchema } from "../schemas/blocks/dynamicContent"
import { shortcutsSchema } from "../schemas/blocks/shortcuts"
import { textContentSchema } from "../schemas/blocks/textContent"
import { systemSchema } from "../schemas/system"
@@ -73,37 +64,3 @@ export const accountPageSchema = z.object({
url: z.string(),
}),
})
const accountPageAccordionRefs = z
.object({
__typename: z.literal(AccountPageEnum.ContentStack.blocks.Accordion),
})
.merge(accordionRefsSchema)
const accountPageDynamicContentRefs = z
.object({
__typename: z.literal(AccountPageEnum.ContentStack.blocks.DynamicContent),
})
.merge(dynamicContentRefsSchema)
const accountPageShortcutsRefs = z
.object({
__typename: z.literal(AccountPageEnum.ContentStack.blocks.ShortCuts),
})
.merge(shortcutsRefsSchema)
const accountPageContentItemRefs = z.discriminatedUnion("__typename", [
z.object({
__typename: z.literal(AccountPageEnum.ContentStack.blocks.TextContent),
}),
accountPageAccordionRefs,
accountPageDynamicContentRefs,
accountPageShortcutsRefs,
])
export const accountPageRefsSchema = z.object({
account_page: z.object({
content: discriminatedUnionArray(accountPageContentItemRefs.options),
system: systemSchema,
}),
})

View File

@@ -2,82 +2,31 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "../../.."
import { notFoundError } from "../../../errors"
import {
GetAccountPage,
GetAccountPageRefs,
} from "../../../graphql/Query/AccountPage/AccountPage.graphql"
import { GetAccountPage } from "../../../graphql/Query/AccountPage/AccountPage.graphql"
import { request } from "../../../graphql/request"
import { contentstackExtendedProcedureUID } from "../../../procedures"
import {
generateRefsResponseTag,
generateTag,
generateTagsFromSystem,
} from "../../../utils/generateTag"
import { accountPageRefsSchema, accountPageSchema } from "./output"
import { getConnections } from "./utils"
import { generateTag } from "../../../utils/generateTag"
import { accountPageSchema } from "./output"
import type {
GetAccountPageRefsSchema,
GetAccountPageSchema,
} from "../../../types/accountPage"
import type { GetAccountPageSchema } from "../../../types/accountPage"
import type { TrackingPageData } from "../../types"
export const accountPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
const getAccountPageRefsCounter = createCounter(
"trpc.contentstack.accountPage.get.refs"
)
const metricsRefs = getAccountPageRefsCounter.init({ lang, uid })
metricsRefs.start()
const variables = { locale: lang, uid }
const refsResponse = await request<GetAccountPageRefsSchema>(
GetAccountPageRefs,
variables,
{
key: generateRefsResponseTag(lang, uid),
ttl: "max",
}
)
if (!refsResponse.data) {
metricsRefs.noDataError()
throw notFoundError({
message: "GetAccountPageRefs returned no data",
errorDetails: variables,
})
}
const validatedAccountPageRefs = accountPageRefsSchema.safeParse(
refsResponse.data
)
if (!validatedAccountPageRefs.success) {
metricsRefs.validationError(validatedAccountPageRefs.error)
return null
}
const connections = getConnections(validatedAccountPageRefs.data)
const tags = [
generateTagsFromSystem(lang, connections),
generateTag(lang, validatedAccountPageRefs.data.account_page.system.uid),
].flat()
metricsRefs.success()
const getAccountPageCounter = createCounter(
"trpc.contentstack.accountPage.get"
)
const metrics = getAccountPageCounter.init({ lang, uid })
metrics.start()
const variables = { locale: lang, uid }
const response = await request<GetAccountPageSchema>(
GetAccountPage,
variables,
{
key: tags,
key: generateTag(lang, uid),
ttl: "max",
}
)

View File

@@ -1,29 +0,0 @@
import { AccountPageEnum } from "../../../types/accountPageEnum"
import type { AccountPageRefs } from "../../../types/accountPage"
import type { System } from "../schemas/system"
export function getConnections({ account_page }: AccountPageRefs) {
const connections: System["system"][] = [account_page.system]
if (account_page.content) {
account_page.content.forEach((block) => {
switch (block.__typename) {
case AccountPageEnum.ContentStack.blocks.ShortCuts: {
if (block.shortcuts.shortcuts.length) {
connections.push(...block.shortcuts.shortcuts.filter((c) => !!c))
}
break
}
case AccountPageEnum.ContentStack.blocks.DynamicContent: {
if (block.dynamic_content.link) {
connections.push(block.dynamic_content.link)
}
break
}
}
})
}
return connections
}

View File

@@ -11,20 +11,14 @@ import { nullableStringValidator } from "@scandic-hotels/common/utils/zod/string
import { discriminatedUnion } from "../../../utils/discriminatedUnion"
import {
infoCardBlockRefsSchema,
infoCardBlockSchema,
transformCardBlockRefs,
transformInfoCardBlock,
} from "../schemas/blocks/cardsGrid"
import { linkConnectionRefsSchema } from "../schemas/blocks/utils/linkConnection"
import {
linkRefsUnionSchema,
linkUnionSchema,
rawLinkUnionSchema,
transformPageLink,
transformPageLinkRef,
} from "../schemas/pageLinks"
import { systemSchema } from "../schemas/system"
// Help me write this zod schema based on the type ContactConfig
export const validateContactConfigSchema = z.object({
@@ -218,150 +212,6 @@ export const validateFooterConfigSchema = z
}
})
const pageConnectionRefs = z.object({
edges: z
.array(
z.object({
node: z.object({
system: systemSchema,
}),
})
)
.max(1),
})
export const validateFooterRefConfigSchema = z.object({
all_footer: z.object({
items: z
.array(
z.object({
main_links: z
.array(
z.object({
pageConnection: pageConnectionRefs,
})
)
.nullable(),
secondary_links: z
.array(
z.object({
links: z.array(
z.object({
pageConnection: pageConnectionRefs,
})
),
})
)
.nullable(),
tertiary_links: z
.array(
z.object({
pageConnection: pageConnectionRefs,
})
)
.nullable(),
system: systemSchema,
})
)
.length(1),
}),
})
/**
* New Header Validation
*/
const linkRefsSchema = z
.object({
linkConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
})
.transform((data) => {
if (data.linkConnection.edges.length) {
const link = transformPageLinkRef(data.linkConnection.edges[0].node)
if (link) {
return {
link,
}
}
}
return { link: null }
})
const menuItemsRefsSchema = z.intersection(
linkRefsSchema,
z
.object({
cardConnection: z.object({
edges: z.array(
z.object({
node: infoCardBlockRefsSchema,
})
),
}),
see_all_link: linkRefsSchema,
submenu: z.array(
z.object({
links: z.array(linkRefsSchema),
})
),
})
.transform((data) => {
let card = null
if (data.cardConnection.edges.length) {
card = transformCardBlockRefs(data.cardConnection.edges[0].node)
}
return {
card,
see_all_link: data.see_all_link,
submenu: data.submenu,
}
})
)
const topLinkRefsSchema = z.object({
logged_in: linkRefsSchema.nullable(),
logged_out: linkRefsSchema.nullable(),
})
export const headerRefsSchema = z
.object({
all_header: z.object({
items: z
.array(
z.object({
menu_items: z.array(menuItemsRefsSchema),
system: systemSchema,
top_link: topLinkRefsSchema,
})
)
.max(1),
}),
})
.transform((data) => {
if (!data.all_header.items.length) {
logger.error(`Zod Error - No header returned in refs request`)
throw new ZodError([
{
code: ZodIssueCode.custom,
fatal: true,
message: "No header returned (Refs)",
path: ["all_header.items"],
},
])
}
return {
header: data.all_header.items[0],
}
})
const internalOrExternalLinkSchema = z
.object({
is_contentstack_link: z.boolean().nullish(),
@@ -687,57 +537,6 @@ export const siteConfigSchema = z
}
})
const sidepeekContentRefSchema = z.object({
content: z.object({
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
}),
})
const alertConnectionRefSchema = z.object({
edges: z.array(
z.object({
node: z.object({
link: linkRefsSchema,
sidepeek_content: sidepeekContentRefSchema,
system: systemSchema,
}),
})
),
visible_on: z.array(z.string()).nullish().default([]),
})
export const siteConfigRefSchema = z.object({
all_site_config: z.object({
items: z.array(
z.object({
sitewide_alert: z
.object({
alerts: z
.array(
z.object({
alertConnection: alertConnectionRefSchema,
})
)
.nullable(),
})
.transform((data) => {
const sitewideAlertWeb = data.alerts?.find((alert) =>
alert.alertConnection.visible_on?.includes(AlertVisibleOnEnum.WEB)
)
return { alert: sitewideAlertWeb || null }
}),
system: systemSchema,
})
),
}),
})
const bannerSchema = z
.object({
tag: z.string(),
@@ -762,20 +561,6 @@ const bannerSchema = z
}
})
const bannerRefSchema = z
.object({
link: linkConnectionRefsSchema,
visible_on: z.array(z.string()).nullish().default([]),
system: systemSchema,
})
.transform(({ link, visible_on, system }) => {
return {
linkSystem: link,
visible_on,
system,
}
})
export const sitewideCampaignBannerSchema = z
.object({
all_sitewide_campaign_banner: z.object({
@@ -806,29 +591,3 @@ export const sitewideCampaignBannerSchema = z
return sitewideCampaignBannerWeb?.node ?? null
})
export const sitewideCampaignBannerRefSchema = z.object({
all_sitewide_campaign_banner: z
.object({
items: z.array(
z.object({
bannerConnection: z.object({
edges: z.array(
z.object({
node: bannerRefSchema,
})
),
}),
system: systemSchema,
})
),
})
.transform((data) => {
const webBanner = data.items.find((item) => {
const bannerNode = item.bannerConnection.edges[0]?.node
return bannerNode?.visible_on?.includes(AlertVisibleOnEnum.WEB)
})
return webBanner ?? null
}),
})

View File

@@ -5,57 +5,31 @@ 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 { GetFooter } from "../../../graphql/Query/Footer.graphql"
import { GetHeader } from "../../../graphql/Query/Header.graphql"
import { GetSiteConfig } from "../../../graphql/Query/SiteConfig.graphql"
import { GetSitewideCampaignBanner } 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 { generateTag } 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 { getAlertPhoneContactData } 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 { FooterDataRaw } from "../../../types/footer"
import type { GetHeader as GetHeaderData } from "../../../types/header"
import type {
GetSiteConfigData,
GetSiteConfigRefData,
GetSitewideCampaignBannerData,
GetSitewideCampaignBannerRefData,
} from "../../../types/siteConfig"
const getContactConfig = cache(async (lang: Lang) => {
@@ -103,51 +77,13 @@ export const baseQueryRouter = router({
}),
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 variables = { locale: lang }
const response = await request<GetHeaderData>(GetHeader, variables, {
key: tags,
key: generateTag(lang, "header"),
ttl: "max",
})
@@ -168,64 +104,17 @@ export const baseQueryRouter = router({
metricsGetHeader.success()
return {
data: validatedHeaderConfig.data.header,
}
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 variables = { locale: lang }
const response = await request<FooterDataRaw>(GetFooter, variables, {
key: tags,
key: generateTag(lang, "footer"),
ttl: "max",
})
@@ -256,52 +145,6 @@ export const baseQueryRouter = router({
.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"
)
@@ -317,7 +160,7 @@ export const baseQueryRouter = router({
await request<GetSitewideCampaignBannerData>(
GetSitewideCampaignBanner,
variables,
{ key: tags, ttl: "max" }
{ key: generateTag(lang, "sitewide_campaign_banner"), ttl: "max" }
)
if (!sitewideCampaignBannerResponse.data) {
@@ -351,52 +194,6 @@ export const baseQueryRouter = router({
.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"
)
@@ -407,7 +204,7 @@ export const baseQueryRouter = router({
const variables = { locale: lang }
const [siteConfigResponse, contactConfig] = await Promise.all([
request<GetSiteConfigData>(GetSiteConfig, variables, {
key: tags,
key: generateTag(lang, "site_config"),
ttl: "max",
}),
getContactConfig(lang),

View File

@@ -5,112 +5,9 @@ import { logger } from "@scandic-hotels/common/logger"
import { getValueFromContactConfig } from "../../../utils/contactConfig"
import type { Edges } from "../../../types/edges"
import type { FooterRefDataRaw } from "../../../types/footer"
import type { HeaderRefs } from "../../../types/header"
import type { NodeRefs } from "../../../types/refs"
import type {
AlertOutput,
GetSiteConfigRefData,
GetSitewideCampaignBannerRefData,
} from "../../../types/siteConfig"
import type { System } from "../schemas/system"
import type { AlertOutput } from "../../../types/siteConfig"
import type { ContactConfig } from "./output"
export function getConnections({ header }: HeaderRefs) {
const connections: System["system"][] = [header.system]
if (header.top_link) {
if (header.top_link.logged_in?.link) {
connections.push(header.top_link.logged_in.link)
}
if (header.top_link.logged_out?.link) {
connections.push(header.top_link.logged_out.link)
}
}
if (header.menu_items.length) {
header.menu_items.forEach((menuItem) => {
if (menuItem.card) {
connections.push(...menuItem.card)
}
if (menuItem.link) {
connections.push(menuItem.link)
}
if (menuItem.see_all_link?.link) {
connections.push(menuItem.see_all_link.link)
}
if (menuItem.submenu.length) {
menuItem.submenu.forEach((subMenuItem) => {
if (subMenuItem.links.length) {
subMenuItem.links.forEach((link) => {
if (link?.link) {
connections.push(link.link)
}
})
}
})
}
})
}
return connections
}
export function getFooterConnections(refs: FooterRefDataRaw) {
const connections: Edges<NodeRefs>[] = []
const footerData = refs.all_footer.items[0]
const mainLinks = footerData.main_links
const secondaryLinks = footerData.secondary_links
const tertiaryLinks = footerData.tertiary_links
if (mainLinks) {
mainLinks.forEach(({ pageConnection }) => {
connections.push(pageConnection)
})
}
secondaryLinks?.forEach(({ links }) => {
if (links) {
links.forEach(({ pageConnection }) => {
connections.push(pageConnection)
})
}
})
if (tertiaryLinks) {
tertiaryLinks.forEach(({ pageConnection }) => {
connections.push(pageConnection)
})
}
return connections
}
export function getSiteConfigConnections(refs: GetSiteConfigRefData) {
const siteConfigData = refs.all_site_config.items[0]
const connections: System["system"][] = []
if (!siteConfigData.sitewide_alert.alert) return connections
const alertConnection = siteConfigData.sitewide_alert.alert.alertConnection
alertConnection.edges.forEach(({ node }) => {
connections.push(node.system)
const link = node.link.link
if (link) {
connections.push(link)
}
node.sidepeek_content.content.embedded_itemsConnection.edges.forEach(
({ node }) => {
if (node.system) {
connections.push(node.system)
}
}
)
})
return connections
}
export function getAlertPhoneContactData(
alert: AlertOutput,
contactConfig: ContactConfig
@@ -139,21 +36,3 @@ export const safeUnion = <T extends z.ZodTypeAny>(schema: T) =>
return null
}
}, schema)
export function getSitewideCampaignBannerConnections(
refs: GetSitewideCampaignBannerRefData
) {
const system = refs.all_sitewide_campaign_banner?.system
const banner =
refs.all_sitewide_campaign_banner?.bannerConnection.edges[0]?.node
const connections: System["system"][] = []
if (system) {
connections.push(system)
}
if (banner?.system) {
connections.push(banner.system)
}
return connections
}

View File

@@ -5,28 +5,6 @@ import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { systemSchema } from "../schemas/system"
import { homeBreadcrumbs } from "./utils"
export const breadcrumbsRefsSchema = z.object({
web: z
.object({
breadcrumbs: z
.object({
title: z.string(),
parentsConnection: z.object({
edges: z.array(
z.object({
node: z.object({
system: systemSchema,
}),
})
),
}),
})
.optional(),
})
.optional(),
system: systemSchema,
})
export const rawBreadcrumbsDataSchema = z.object({
url: z.string(),
web: z.object({

View File

@@ -6,105 +6,35 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "../../.."
import { PageContentTypeEnum } from "../../../enums/contentType"
import { notFoundError } from "../../../errors"
import {
GetMyPagesBreadcrumbs,
GetMyPagesBreadcrumbsRefs,
} from "../../../graphql/Query/Breadcrumbs/AccountPage.graphql"
import {
GetCampaignOverviewPageBreadcrumbs,
GetCampaignOverviewPageBreadcrumbsRefs,
} from "../../../graphql/Query/Breadcrumbs/CampaignOverviewPage.graphql"
import {
GetCampaignPageBreadcrumbs,
GetCampaignPageBreadcrumbsRefs,
} from "../../../graphql/Query/Breadcrumbs/CampaignPage.graphql"
import {
GetCollectionPageBreadcrumbs,
GetCollectionPageBreadcrumbsRefs,
} from "../../../graphql/Query/Breadcrumbs/CollectionPage.graphql"
import {
GetContentPageBreadcrumbs,
GetContentPageBreadcrumbsRefs,
} from "../../../graphql/Query/Breadcrumbs/ContentPage.graphql"
import {
GetDestinationCityPageBreadcrumbs,
GetDestinationCityPageBreadcrumbsRefs,
} from "../../../graphql/Query/Breadcrumbs/DestinationCityPage.graphql"
import {
GetDestinationCountryPageBreadcrumbs,
GetDestinationCountryPageBreadcrumbsRefs,
} from "../../../graphql/Query/Breadcrumbs/DestinationCountryPage.graphql"
import {
GetDestinationOverviewPageBreadcrumbs,
GetDestinationOverviewPageBreadcrumbsRefs,
} from "../../../graphql/Query/Breadcrumbs/DestinationOverviewPage.graphql"
import {
GetHotelPageBreadcrumbs,
GetHotelPageBreadcrumbsRefs,
} from "../../../graphql/Query/Breadcrumbs/HotelPage.graphql"
import {
GetLoyaltyPageBreadcrumbs,
GetLoyaltyPageBreadcrumbsRefs,
} from "../../../graphql/Query/Breadcrumbs/LoyaltyPage.graphql"
import {
GetPromoCampaignPageBreadcrumbs,
GetPromoCampaignPageBreadcrumbsRefs,
} from "../../../graphql/Query/Breadcrumbs/PromoCampaignPage.graphql"
import { GetMyPagesBreadcrumbs } from "../../../graphql/Query/Breadcrumbs/AccountPage.graphql"
import { GetCampaignOverviewPageBreadcrumbs } from "../../../graphql/Query/Breadcrumbs/CampaignOverviewPage.graphql"
import { GetCampaignPageBreadcrumbs } from "../../../graphql/Query/Breadcrumbs/CampaignPage.graphql"
import { GetCollectionPageBreadcrumbs } from "../../../graphql/Query/Breadcrumbs/CollectionPage.graphql"
import { GetContentPageBreadcrumbs } from "../../../graphql/Query/Breadcrumbs/ContentPage.graphql"
import { GetDestinationCityPageBreadcrumbs } from "../../../graphql/Query/Breadcrumbs/DestinationCityPage.graphql"
import { GetDestinationCountryPageBreadcrumbs } from "../../../graphql/Query/Breadcrumbs/DestinationCountryPage.graphql"
import { GetDestinationOverviewPageBreadcrumbs } from "../../../graphql/Query/Breadcrumbs/DestinationOverviewPage.graphql"
import { GetHotelPageBreadcrumbs } from "../../../graphql/Query/Breadcrumbs/HotelPage.graphql"
import { GetLoyaltyPageBreadcrumbs } from "../../../graphql/Query/Breadcrumbs/LoyaltyPage.graphql"
import { GetPromoCampaignPageBreadcrumbs } from "../../../graphql/Query/Breadcrumbs/PromoCampaignPage.graphql"
import { request } from "../../../graphql/request"
import { contentstackExtendedProcedureUID } from "../../../procedures"
import { generateRefsResponseTag } from "../../../utils/generateTag"
import { breadcrumbsRefsSchema, breadcrumbsSchema } from "./output"
import { getTags } from "./utils"
import { generateTag } from "../../../utils/generateTag"
import { breadcrumbsSchema } from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type {
BreadcrumbsRefsSchema,
RawBreadcrumbsSchema,
} from "../../../types/breadcrumbs"
import type { RawBreadcrumbsSchema } from "../../../types/breadcrumbs"
interface BreadcrumbsPageData<T> {
dataKey: keyof T
refQuery: string | DocumentNode
query: string | DocumentNode
}
const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs<T>(
{ dataKey, refQuery, query }: BreadcrumbsPageData<T>,
{ dataKey, query }: BreadcrumbsPageData<T>,
{ uid, lang }: { uid: string; lang: Lang }
) {
const getBreadcrumbsRefsCounter = createCounter(
"trpc.contentstack.breadcrumbs.get.refs"
)
const metricsGetBreadcrumbsRefs = getBreadcrumbsRefsCounter.init({
lang,
uid,
})
metricsGetBreadcrumbsRefs.start()
const refsResponse = await request<{ [K in keyof T]: BreadcrumbsRefsSchema }>(
refQuery,
{ locale: lang, uid },
{
key: generateRefsResponseTag(lang, uid, "breadcrumbs"),
ttl: "max",
}
)
const validatedRefsData = breadcrumbsRefsSchema.safeParse(
refsResponse.data[dataKey]
)
if (!validatedRefsData.success) {
metricsGetBreadcrumbsRefs.validationError(validatedRefsData.error)
return []
}
metricsGetBreadcrumbsRefs.success()
const tags = getTags(validatedRefsData.data, lang)
const getBreadcrumbsCounter = createCounter(
"trpc.contentstack.breadcrumbs.get"
)
@@ -117,7 +47,7 @@ const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs<T>(
const variables = { locale: lang, uid }
const response = await request<T>(query, variables, {
key: tags,
key: generateTag(lang, uid),
ttl: "max",
})
@@ -158,7 +88,6 @@ export const breadcrumbsQueryRouter = router({
}>(
{
dataKey: "account_page",
refQuery: GetMyPagesBreadcrumbsRefs,
query: GetMyPagesBreadcrumbs,
},
variables
@@ -169,7 +98,6 @@ export const breadcrumbsQueryRouter = router({
}>(
{
dataKey: "campaign_overview_page",
refQuery: GetCampaignOverviewPageBreadcrumbsRefs,
query: GetCampaignOverviewPageBreadcrumbs,
},
variables
@@ -180,7 +108,6 @@ export const breadcrumbsQueryRouter = router({
}>(
{
dataKey: "campaign_page",
refQuery: GetCampaignPageBreadcrumbsRefs,
query: GetCampaignPageBreadcrumbs,
},
variables
@@ -191,7 +118,6 @@ export const breadcrumbsQueryRouter = router({
}>(
{
dataKey: "collection_page",
refQuery: GetCollectionPageBreadcrumbsRefs,
query: GetCollectionPageBreadcrumbs,
},
variables
@@ -202,7 +128,6 @@ export const breadcrumbsQueryRouter = router({
}>(
{
dataKey: "content_page",
refQuery: GetContentPageBreadcrumbsRefs,
query: GetContentPageBreadcrumbs,
},
variables
@@ -213,7 +138,6 @@ export const breadcrumbsQueryRouter = router({
}>(
{
dataKey: "destination_overview_page",
refQuery: GetDestinationOverviewPageBreadcrumbsRefs,
query: GetDestinationOverviewPageBreadcrumbs,
},
variables
@@ -224,7 +148,6 @@ export const breadcrumbsQueryRouter = router({
}>(
{
dataKey: "destination_country_page",
refQuery: GetDestinationCountryPageBreadcrumbsRefs,
query: GetDestinationCountryPageBreadcrumbs,
},
variables
@@ -235,7 +158,6 @@ export const breadcrumbsQueryRouter = router({
}>(
{
dataKey: "destination_city_page",
refQuery: GetDestinationCityPageBreadcrumbsRefs,
query: GetDestinationCityPageBreadcrumbs,
},
variables
@@ -246,7 +168,6 @@ export const breadcrumbsQueryRouter = router({
}>(
{
dataKey: "hotel_page",
refQuery: GetHotelPageBreadcrumbsRefs,
query: GetHotelPageBreadcrumbs,
},
variables
@@ -257,7 +178,6 @@ export const breadcrumbsQueryRouter = router({
}>(
{
dataKey: "loyalty_page",
refQuery: GetLoyaltyPageBreadcrumbsRefs,
query: GetLoyaltyPageBreadcrumbs,
},
variables
@@ -268,7 +188,6 @@ export const breadcrumbsQueryRouter = router({
}>(
{
dataKey: "promo_campaign_page",
refQuery: GetPromoCampaignPageBreadcrumbsRefs,
query: GetPromoCampaignPageBreadcrumbs,
},
variables

View File

@@ -1,11 +1,5 @@
import { Lang } from "@scandic-hotels/common/constants/language"
import { generateTag, generateTags } from "../../../utils/generateTag"
import type { BreadcrumbsRefsSchema } from "../../../types/breadcrumbs"
import type { Edges } from "../../../types/edges"
import type { NodeRefs } from "../../../types/refs"
export const affix = "breadcrumbs"
// TODO: Make these editable in CMS?
@@ -43,20 +37,3 @@ export const homeBreadcrumbs: {
uid: "sv",
},
}
export function getConnections(data: BreadcrumbsRefsSchema) {
const connections: Edges<NodeRefs>[] = []
if (data.web?.breadcrumbs) {
connections.push(data.web.breadcrumbs.parentsConnection)
}
return connections
}
export function getTags(data: BreadcrumbsRefsSchema, lang: Lang) {
const connections = getConnections(data)
const tags = generateTags(lang, connections)
tags.push(generateTag(lang, data.system.uid, affix))
return tags
}

View File

@@ -10,19 +10,10 @@ import {
includedHotelsSchema,
} from "../campaignPage/output"
import { promoHeroSchema } from "../promoCampaignPage/output"
import {
allCampaignsRefsSchema,
allCampaignsSchema,
} from "../schemas/blocks/allCampaigns"
import {
carouselCardsRefsSchema,
carouselCardsSchema,
} from "../schemas/blocks/carouselCards"
import { allCampaignsSchema } from "../schemas/blocks/allCampaigns"
import { carouselCardsSchema } from "../schemas/blocks/carouselCards"
import { campaignOverviewPageHotelListingSchema } from "../schemas/blocks/hotelListing"
import {
linkAndTitleSchema,
linkConnectionRefs,
} from "../schemas/linkConnection"
import { linkAndTitleSchema } from "../schemas/linkConnection"
import { systemSchema } from "../schemas/system"
const navigationLinksSchema = z
@@ -196,49 +187,3 @@ export const campaignOverviewPageSchema = z.object({
url: z.string(),
}),
})
/** REFS */
const campaignOverviewPageHeaderRefs = z.object({
navigation_links: z.array(linkConnectionRefs),
})
const campaignOverviewPageCarouselCardsRef = z
.object({
__typename: z.literal(
CampaignOverviewPageEnum.ContentStack.blocks.CarouselCards
),
})
.merge(carouselCardsRefsSchema)
const campaignOverviewPageAllCampaignsRef = z
.object({
__typename: z.literal(
CampaignOverviewPageEnum.ContentStack.blocks.AllCampaigns
),
})
.merge(allCampaignsRefsSchema)
const blockRefsSchema = z.discriminatedUnion("__typename", [
campaignOverviewPageAllCampaignsRef,
campaignOverviewPageCarouselCardsRef,
])
export const campaignOverviewPageRefsSchema = z.object({
campaign_overview_page: z.object({
header: campaignOverviewPageHeaderRefs,
top_campaign_block: z.object({
campaignConnection: z.object({
edges: z.array(
z.object({
node: z.object({
system: systemSchema,
}),
})
),
}),
}),
blocks: discriminatedUnionArray(blockRefsSchema.options).nullable(),
system: systemSchema,
}),
})

View File

@@ -2,72 +2,20 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "../../.."
import { notFoundError } from "../../../errors"
import {
GetCampaignOverviewPage,
GetCampaignOverviewPageRefs,
} from "../../../graphql/Query/CampaignOverviewPage/CampaignOverviewPage.graphql"
import { GetCampaignOverviewPage } from "../../../graphql/Query/CampaignOverviewPage/CampaignOverviewPage.graphql"
import { request } from "../../../graphql/request"
import { contentStackUidWithServiceProcedure } from "../../../procedures"
import { generateRefsResponseTag } from "../../../utils/generateTag"
import {
campaignOverviewPageRefsSchema,
campaignOverviewPageSchema,
} from "./output"
import { generatePageTags } from "./utils"
import { generateTag } from "../../../utils/generateTag"
import { campaignOverviewPageSchema } from "./output"
import type {
GetCampaignOverviewPageData,
GetCampaignOverviewPageRefsData,
} from "../../../types/campaignOverviewPage"
import type { GetCampaignOverviewPageData } from "../../../types/campaignOverviewPage"
import type { TrackingPageData } from "../../types"
export const campaignOverviewPageQueryRouter = router({
get: contentStackUidWithServiceProcedure.query(async ({ ctx }) => {
const { lang, uid } = ctx
const getCampaignOverviewPageRefsCounter = createCounter(
"trpc.contentstack.campaignOverviewPage.get.refs"
)
const metricsGetCampaignOverviewPageRefs =
getCampaignOverviewPageRefsCounter.init({
lang,
uid,
})
metricsGetCampaignOverviewPageRefs.start()
const refVariables = { locale: lang, uid }
const refsResponse = await request<GetCampaignOverviewPageRefsData>(
GetCampaignOverviewPageRefs,
refVariables,
{
key: generateRefsResponseTag(lang, uid),
ttl: "max",
}
)
if (!refsResponse.data) {
metricsGetCampaignOverviewPageRefs.noDataError()
throw notFoundError({
message: "GetCampaignOverviewPageRefs returned no data",
errorDetails: refVariables,
})
}
const validatedRefsData = campaignOverviewPageRefsSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
metricsGetCampaignOverviewPageRefs.validationError(
validatedRefsData.error
)
return null
}
metricsGetCampaignOverviewPageRefs.success()
const tags = generatePageTags(validatedRefsData.data, lang)
const cacheKey = generateTag(lang, uid)
const getCampaignOverviewPageCounter = createCounter(
"trpc.contentstack.campaignOverviewPage.get"
@@ -84,7 +32,7 @@ export const campaignOverviewPageQueryRouter = router({
GetCampaignOverviewPage,
variables,
{
key: tags,
key: `${cacheKey}:campaignOverviewPage`,
ttl: "max",
}
)

View File

@@ -1,63 +0,0 @@
import { CampaignOverviewPageEnum } from "../../../types/campaignOverviewPageEnum"
import { generateTag, generateTagsFromSystem } from "../../../utils/generateTag"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { System } from "../../../routers/contentstack/schemas/system"
import type { CampaignOverviewPageRefs } from "../../../types/campaignOverviewPage"
export function generatePageTags(
validatedData: CampaignOverviewPageRefs,
lang: Lang
): string[] {
const connections = getConnections(validatedData)
return [
generateTagsFromSystem(lang, connections),
generateTag(lang, validatedData.campaign_overview_page.system.uid),
].flat()
}
export function getConnections({
campaign_overview_page,
}: CampaignOverviewPageRefs) {
const connections: System["system"][] = [campaign_overview_page.system]
if (campaign_overview_page.header.navigation_links) {
campaign_overview_page.header.navigation_links.forEach((link) => {
if (link.link) {
connections.push(link.link)
}
})
}
if (campaign_overview_page.top_campaign_block.campaignConnection) {
campaign_overview_page.top_campaign_block.campaignConnection.edges.forEach(
({ node }) => {
connections.push(node.system)
}
)
}
if (campaign_overview_page.blocks) {
campaign_overview_page.blocks.forEach((block) => {
switch (block.__typename) {
case CampaignOverviewPageEnum.ContentStack.blocks.CarouselCards: {
block.carousel_cards.card_groups.forEach((group) => {
group.cardConnection.edges.forEach(({ node }) => {
connections.push(node.system)
})
})
break
}
case CampaignOverviewPageEnum.ContentStack.blocks.AllCampaigns: {
block.all_campaigns.campaignsConnection.edges.forEach(({ node }) => {
if (node.system) {
connections.push(node.system)
}
})
break
}
}
})
}
return connections
}

View File

@@ -5,20 +5,11 @@ import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { CampaignPageEnum } from "../../../types/campaignPage"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
accordionRefsSchema,
accordionSchema,
} from "../schemas/blocks/accordion"
import {
carouselCardsRefsSchema,
carouselCardsSchema,
} from "../schemas/blocks/carouselCards"
import { accordionSchema } from "../schemas/blocks/accordion"
import { carouselCardsSchema } from "../schemas/blocks/carouselCards"
import { essentialsBlockSchema } from "../schemas/blocks/essentials"
import { campaignPageHotelListingSchema } from "../schemas/blocks/hotelListing"
import {
linkConnectionRefs,
linkConnectionSchema,
} from "../schemas/linkConnection"
import { linkConnectionSchema } from "../schemas/linkConnection"
import { systemSchema } from "../schemas/system"
import { getCarouselCardsBlockWithBookingCodeLinks } from "./utils"
@@ -258,46 +249,3 @@ export const campaignPagesByHotelUidSchema = z
}),
})
.transform((data) => data.all_campaign_page.items)
/** REFS */
const campaignPageCarouselCardsRef = z
.object({
__typename: z.literal(CampaignPageEnum.ContentStack.blocks.CarouselCards),
})
.merge(carouselCardsRefsSchema)
const campaignPageAccordionRefs = z
.object({
__typename: z.literal(CampaignPageEnum.ContentStack.blocks.Accordion),
})
.merge(accordionRefsSchema)
const campaignPageBlockRefsItem = z.discriminatedUnion("__typename", [
campaignPageCarouselCardsRef,
campaignPageAccordionRefs,
])
const heroRefsSchema = z.object({
button: linkConnectionRefs,
})
export const campaignPageRefsSchema = z.object({
campaign_page: z.object({
hero: heroRefsSchema,
blocks: discriminatedUnionArray(
campaignPageBlockRefsItem.options
).nullable(),
system: systemSchema,
}),
})
export const campaignPagesByHotelUidRefsSchema = z
.object({
all_campaign_page: z.object({
items: z.array(
z.object({
system: systemSchema,
})
),
}),
})
.transform((data) => data.all_campaign_page.items.map((item) => item.system))

View File

@@ -2,64 +2,20 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "../../.."
import { notFoundError } from "../../../errors"
import {
GetCampaignPage,
GetCampaignPageRefs,
} from "../../../graphql/Query/CampaignPage/CampaignPage.graphql"
import { GetCampaignPage } from "../../../graphql/Query/CampaignPage/CampaignPage.graphql"
import { request } from "../../../graphql/request"
import { contentStackUidWithServiceProcedure } from "../../../procedures"
import { generateRefsResponseTag } from "../../../utils/generateTag"
import { campaignPageRefsSchema, campaignPageSchema } from "./output"
import { generatePageTags } from "./utils"
import { generateTag } from "../../../utils/generateTag"
import { campaignPageSchema } from "./output"
import type {
GetCampaignPageData,
GetCampaignPageRefsData,
} from "../../../types/campaignPage"
import type { GetCampaignPageData } from "../../../types/campaignPage"
import type { TrackingPageData } from "../../types"
export const campaignPageQueryRouter = router({
get: contentStackUidWithServiceProcedure.query(async ({ ctx }) => {
const { lang, uid } = ctx
const getCampaignPageRefsCounter = createCounter(
"trpc.contentstack.campaignPage.get.refs"
)
const metricsGetCampaignPageRefs = getCampaignPageRefsCounter.init({
lang,
uid,
})
metricsGetCampaignPageRefs.start()
const refVariables = { locale: lang, uid }
const refsResponse = await request<GetCampaignPageRefsData>(
GetCampaignPageRefs,
refVariables,
{
key: generateRefsResponseTag(lang, uid),
ttl: "max",
}
)
if (!refsResponse.data) {
metricsGetCampaignPageRefs.noDataError()
throw notFoundError({
message: "GetCampaignPageRefs returned no data",
errorDetails: refVariables,
})
}
const validatedRefsData = campaignPageRefsSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
metricsGetCampaignPageRefs.validationError(validatedRefsData.error)
return null
}
metricsGetCampaignPageRefs.success()
const tags = generatePageTags(validatedRefsData.data, lang)
const cacheKey = generateTag(lang, uid)
const getCampaignPageCounter = createCounter(
"trpc.contentstack.campaignPage.get"
@@ -76,7 +32,7 @@ export const campaignPageQueryRouter = router({
GetCampaignPage,
variables,
{
key: tags,
key: `${cacheKey}:campaignPage`,
ttl: "max",
}
)

View File

@@ -1,72 +1,17 @@
import { dt } from "@scandic-hotels/common/dt"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { notFoundError } from "../../../errors"
import {
GetCampaignPagesByHotelUid,
GetCampaignPagesByHotelUidRefs,
} from "../../../graphql/Query/CampaignPage/CampaignPagesByHotelUid.graphql"
import { GetCampaignPagesByHotelUid } from "../../../graphql/Query/CampaignPage/CampaignPagesByHotelUid.graphql"
import { request } from "../../../graphql/request"
import {
CampaignPageEnum,
type CampaignPageRefs,
} from "../../../types/campaignPage"
import {
generateRefsResponseTag,
generateTag,
generateTagsFromSystem,
} from "../../../utils/generateTag"
import {
campaignPagesByHotelUidRefsSchema,
campaignPagesByHotelUidSchema,
} from "./output"
import { generateTag } from "../../../utils/generateTag"
import { campaignPagesByHotelUidSchema } from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type {
CarouselCardsBlock,
GetCampaignPagesByHotelUidData,
GetCampaignPagesByHotelUidRefsData,
} from "../../../types/campaignPage"
import type { System } from "../schemas/system"
export function generatePageTags(
validatedData: CampaignPageRefs,
lang: Lang
): string[] {
const connections = getConnections(validatedData)
return [
generateTagsFromSystem(lang, connections),
generateTag(lang, validatedData.campaign_page.system.uid),
].flat()
}
export function getConnections({ campaign_page }: CampaignPageRefs) {
const connections: System["system"][] = [campaign_page.system]
if (campaign_page.blocks) {
campaign_page.blocks.forEach((block) => {
switch (block.__typename) {
case CampaignPageEnum.ContentStack.blocks.CarouselCards: {
block.carousel_cards.card_groups.forEach((group) => {
group.cardConnection.edges.forEach(({ node }) => {
connections.push(node.system)
})
})
break
}
case CampaignPageEnum.ContentStack.blocks.Accordion: {
if (block.accordion.length) {
connections.push(...block.accordion.filter((c) => !!c))
}
break
}
}
})
}
return connections
}
export function getCarouselCardsBlockWithBookingCodeLinks(
block: CarouselCardsBlock,
@@ -105,57 +50,6 @@ export async function getCampaignPagesByHotelPageUid(
lang: Lang
) {
const today = dt().format("YYYY-MM-DD")
const getCampaignPagesByHotelUidRefsCounter = createCounter(
"trpc.contentstack.campaignPage.byHotelUid.get.refs"
)
const metricsGetCampaignPagesByHotelUidRefs =
getCampaignPagesByHotelUidRefsCounter.init({
lang,
hotelPageUid,
today,
})
metricsGetCampaignPagesByHotelUidRefs.start()
const refsTag = generateRefsResponseTag(
lang,
`${hotelPageUid}-${today}`,
"hotel_page_campaigns"
)
const variables = {
locale: lang,
hotelPageUid,
today,
}
const refsResponse = await request<GetCampaignPagesByHotelUidRefsData>(
GetCampaignPagesByHotelUidRefs,
variables,
{
key: refsTag,
ttl: "1d",
}
)
if (!refsResponse.data) {
metricsGetCampaignPagesByHotelUidRefs.noDataError()
throw notFoundError({
message: "GetCampaignPagesByHotelUidRefs returned no data",
errorDetails: variables,
})
}
const validatedRefsData = campaignPagesByHotelUidRefsSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
metricsGetCampaignPagesByHotelUidRefs.validationError(
validatedRefsData.error
)
return null
}
metricsGetCampaignPagesByHotelUidRefs.success()
const tags = generateTagsFromSystem(lang, validatedRefsData.data)
const getCampaignPagesByHotelUidCounter = createCounter(
"trpc.contentstack.campaignPage.byHotelUid.get"
@@ -169,15 +63,17 @@ export async function getCampaignPagesByHotelPageUid(
metricsGetCampaignPagesByHotelUid.start()
const variables = {
locale: lang,
hotelPageUid,
today,
}
const response = await request<GetCampaignPagesByHotelUidData>(
GetCampaignPagesByHotelUid,
variables,
{
locale: lang,
hotelPageUid,
today,
},
{
key: [...tags, refsTag],
key: generateTag(lang, `${hotelPageUid}-${today}`),
ttl: "1d",
}
)

View File

@@ -4,30 +4,15 @@ import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/i
import { CollectionPageEnum } from "../../../types/collectionPage"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
cardGridRefsSchema,
cardsGridSchema,
} from "../schemas/blocks/cardsGrid"
import {
dynamicContentRefsSchema,
dynamicContentSchema as blockDynamicContentSchema,
} from "../schemas/blocks/dynamicContent"
import {
shortcutsRefsSchema,
shortcutsSchema,
} from "../schemas/blocks/shortcuts"
import { uspGridRefsSchema, uspGridSchema } from "../schemas/blocks/uspGrid"
import {
videoCardRefsSchema,
videoCardSchema,
} from "../schemas/blocks/videoCard"
import {
linkAndTitleSchema,
linkConnectionRefs,
} from "../schemas/linkConnection"
import { cardsGridSchema } from "../schemas/blocks/cardsGrid"
import { dynamicContentSchema as blockDynamicContentSchema } from "../schemas/blocks/dynamicContent"
import { shortcutsSchema } from "../schemas/blocks/shortcuts"
import { uspGridSchema } from "../schemas/blocks/uspGrid"
import { videoCardSchema } from "../schemas/blocks/videoCard"
import { linkAndTitleSchema } from "../schemas/linkConnection"
import { internalOrExternalLinkSchema } from "../schemas/pageLinks"
import { systemSchema } from "../schemas/system"
import { transformedVideoSchema, videoRefSchema } from "../schemas/video"
import { transformedVideoSchema } from "../schemas/video"
// Block schemas
export const collectionPageCards = z
@@ -116,60 +101,3 @@ export const collectionPageSchema = z.object({
url: z.string(),
}),
})
/** REFS */
const collectionPageCardsRefs = z
.object({
__typename: z.literal(CollectionPageEnum.ContentStack.blocks.CardsGrid),
})
.merge(cardGridRefsSchema)
const collectionPageShortcutsRefs = z
.object({
__typename: z.literal(CollectionPageEnum.ContentStack.blocks.Shortcuts),
})
.merge(shortcutsRefsSchema)
const collectionPageUspGridRefs = z
.object({
__typename: z.literal(CollectionPageEnum.ContentStack.blocks.UspGrid),
})
.merge(uspGridRefsSchema)
const collectionPageDynamicContentRefs = z
.object({
__typename: z.literal(
CollectionPageEnum.ContentStack.blocks.DynamicContent
),
})
.merge(dynamicContentRefsSchema)
const collectionPageVideoCardRefs = z
.object({
__typename: z.literal(CollectionPageEnum.ContentStack.blocks.VideoCard),
})
.merge(videoCardRefsSchema)
const collectionPageBlockRefsItem = z.discriminatedUnion("__typename", [
collectionPageShortcutsRefs,
collectionPageDynamicContentRefs,
collectionPageCardsRefs,
collectionPageUspGridRefs,
collectionPageVideoCardRefs,
])
const collectionPageHeaderRefs = z.object({
navigation_links: z.array(linkConnectionRefs),
top_primary_button: linkConnectionRefs.nullable(),
})
export const collectionPageRefsSchema = z.object({
collection_page: z.object({
hero_video: videoRefSchema.nullish(),
header: collectionPageHeaderRefs,
blocks: discriminatedUnionArray(
collectionPageBlockRefsItem.options
).nullable(),
system: systemSchema,
}),
})

View File

@@ -4,12 +4,8 @@ import { router } from "../../.."
import { GetCollectionPage } from "../../../graphql/Query/CollectionPage/CollectionPage.graphql"
import { request } from "../../../graphql/request"
import { contentstackExtendedProcedureUID } from "../../../procedures"
import { generateTag } from "../../../utils/generateTag"
import { collectionPageSchema } from "./output"
import {
fetchCollectionPageRefs,
generatePageTags,
validateCollectionPageRefs,
} from "./utils"
import type { GetCollectionPageSchema } from "../../../types/collectionPage"
import type { TrackingPageData } from "../../types"
@@ -18,17 +14,7 @@ export const collectionPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
const collectionPageRefsData = await fetchCollectionPageRefs(lang, uid)
const collectionPageRefs = validateCollectionPageRefs(
collectionPageRefsData,
lang,
uid
)
if (!collectionPageRefs) {
return null
}
const tags = generatePageTags(collectionPageRefs, lang)
const cacheKey = generateTag(lang, uid)
const getCollectionPageCounter = createCounter(
"trpc.contentstack.collectionPage.get"
@@ -44,7 +30,7 @@ export const collectionPageQueryRouter = router({
GetCollectionPage,
{ locale: lang, uid },
{
key: tags,
key: `${cacheKey}:collectionPage`,
ttl: "max",
}
)

View File

@@ -1,144 +0,0 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { notFoundError } from "../../../errors"
import { GetCollectionPageRefs } from "../../../graphql/Query/CollectionPage/CollectionPage.graphql"
import { request } from "../../../graphql/request"
import {
CollectionPageEnum,
type CollectionPageRefs,
type GetCollectionPageRefsSchema,
} from "../../../types/collectionPage"
import {
generateRefsResponseTag,
generateTag,
generateTagsFromAssetSystem,
generateTagsFromSystem,
} from "../../../utils/generateTag"
import { collectionPageRefsSchema } from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { AssetSystem, System } from "../schemas/system"
export async function fetchCollectionPageRefs(lang: Lang, uid: string) {
const getCollectionPageRefsCounter = createCounter(
"trpc.contentstack.collectionPage.get.refs"
)
const metricsGetCollectionPageRefs = getCollectionPageRefsCounter.init({
lang,
uid,
})
metricsGetCollectionPageRefs.start()
const cacheKey = generateRefsResponseTag(lang, uid)
const variables = {
locale: lang,
uid,
}
const refsResponse = await request<GetCollectionPageRefsSchema>(
GetCollectionPageRefs,
variables,
{
key: cacheKey,
ttl: "max",
}
)
if (!refsResponse.data) {
metricsGetCollectionPageRefs.noDataError()
throw notFoundError({
message: "GetCollectionPageRefs returned no data",
errorDetails: variables,
})
}
return refsResponse.data
}
export function validateCollectionPageRefs(
data: GetCollectionPageRefsSchema,
lang: Lang,
uid: string
) {
const getCollectionPageRefsCounter = createCounter(
"trpc.contentstack.collectionPage.get.refs"
)
const metricsGetCollectionPageRefs = getCollectionPageRefsCounter.init({
lang,
uid,
})
const validatedData = collectionPageRefsSchema.safeParse(data)
if (!validatedData.success) {
metricsGetCollectionPageRefs.validationError(validatedData.error)
return null
}
metricsGetCollectionPageRefs.success()
return validatedData.data
}
export function generatePageTags(
validatedData: CollectionPageRefs,
lang: Lang
): string[] {
const { connections, assetConnections } = getConnections(validatedData)
return [
generateTagsFromSystem(lang, connections),
generateTagsFromAssetSystem(assetConnections),
generateTag(lang, validatedData.collection_page.system.uid),
].flat()
}
export function getConnections({ collection_page }: CollectionPageRefs) {
const connections: System["system"][] = [collection_page.system]
const assetConnections: AssetSystem[] = []
if (collection_page.hero_video?.sourceConnection.edges[0]) {
assetConnections.push(
collection_page.hero_video.sourceConnection.edges[0].node
)
}
if (collection_page.blocks) {
collection_page.blocks.forEach((block) => {
const typeName = block.__typename
switch (typeName) {
case CollectionPageEnum.ContentStack.blocks.Shortcuts:
if (block.shortcuts.shortcuts.length) {
connections.push(...block.shortcuts.shortcuts.filter((c) => !!c))
}
break
case CollectionPageEnum.ContentStack.blocks.CardsGrid:
if (block.cards_grid.length) {
connections.push(...block.cards_grid)
}
break
case CollectionPageEnum.ContentStack.blocks.UspGrid:
if (block.usp_grid.length) {
connections.push(...block.usp_grid.filter((c) => !!c))
}
break
case CollectionPageEnum.ContentStack.blocks.VideoCard:
if (block.video_card?.system) {
connections.push(block.video_card.system)
}
if (block.video_card?.video.sourceConnection.edges[0]) {
assetConnections.push(
block.video_card.video.sourceConnection.edges[0].node
)
}
break
case CollectionPageEnum.ContentStack.blocks.DynamicContent:
break
default:
const _exhaustiveCheck: never = typeName
break
}
})
}
return { connections, assetConnections }
}

View File

@@ -4,68 +4,29 @@ import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/i
import { ContentPageEnum } from "../../../types/contentPage"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
accordionRefsSchema,
accordionSchema,
} from "../schemas/blocks/accordion"
import {
cardGridRefsSchema,
cardsGridSchema,
} from "../schemas/blocks/cardsGrid"
import {
contentRefsSchema as blockContentRefsSchema,
contentSchema as blockContentSchema,
} from "../schemas/blocks/content"
import {
dynamicContentRefsSchema,
dynamicContentSchema as blockDynamicContentSchema,
} from "../schemas/blocks/dynamicContent"
import { accordionSchema } from "../schemas/blocks/accordion"
import { cardsGridSchema } from "../schemas/blocks/cardsGrid"
import { contentSchema as blockContentSchema } from "../schemas/blocks/content"
import { dynamicContentSchema as blockDynamicContentSchema } from "../schemas/blocks/dynamicContent"
import { contentPageHotelListingSchema } from "../schemas/blocks/hotelListing"
import { jotformRefsSchema, jotformSchema } from "../schemas/blocks/jotform"
import {
shortcutsRefsSchema,
shortcutsSchema,
} from "../schemas/blocks/shortcuts"
import { jotformSchema } from "../schemas/blocks/jotform"
import { shortcutsSchema } from "../schemas/blocks/shortcuts"
import { tableSchema } from "../schemas/blocks/table"
import { textColsRefsSchema, textColsSchema } from "../schemas/blocks/textCols"
import { uspGridRefsSchema, uspGridSchema } from "../schemas/blocks/uspGrid"
import { videoBlockRefsSchema, videoBlockSchema } from "../schemas/blocks/video"
import {
videoCardRefsSchema,
videoCardSchema,
} from "../schemas/blocks/videoCard"
import {
dynamicContentRefsSchema as headerDynamicContentRefsSchema,
dynamicContentSchema as headerDynamicContentSchema,
} from "../schemas/headers/dynamicContent"
import {
linkAndTitleSchema,
linkConnectionRefs,
} from "../schemas/linkConnection"
import { textColsSchema } from "../schemas/blocks/textCols"
import { uspGridSchema } from "../schemas/blocks/uspGrid"
import { videoBlockSchema } from "../schemas/blocks/video"
import { videoCardSchema } from "../schemas/blocks/videoCard"
import { dynamicContentSchema as headerDynamicContentSchema } from "../schemas/headers/dynamicContent"
import { linkAndTitleSchema } from "../schemas/linkConnection"
import { internalOrExternalLinkSchema } from "../schemas/pageLinks"
import {
contentRefsSchema as sidebarContentRefsSchema,
contentSchema as sidebarContentSchema,
} from "../schemas/sidebar/content"
import { contentSchema as sidebarContentSchema } from "../schemas/sidebar/content"
import { dynamicContentSchema as sidebarDynamicContentSchema } from "../schemas/sidebar/dynamicContent"
import {
joinLoyaltyContactRefsSchema,
joinLoyaltyContactSchema,
} from "../schemas/sidebar/joinLoyaltyContact"
import {
quickLinksRefschema,
quickLinksSchema,
} from "../schemas/sidebar/quickLinks"
import {
scriptedCardRefschema,
scriptedCardsSchema,
} from "../schemas/sidebar/scriptedCard"
import {
teaserCardRefschema,
teaserCardsSchema,
} from "../schemas/sidebar/teaserCard"
import { joinLoyaltyContactSchema } from "../schemas/sidebar/joinLoyaltyContact"
import { quickLinksSchema } from "../schemas/sidebar/quickLinks"
import { scriptedCardsSchema } from "../schemas/sidebar/scriptedCard"
import { teaserCardsSchema } from "../schemas/sidebar/teaserCard"
import { systemSchema } from "../schemas/system"
import { transformedVideoSchema, videoRefSchema } from "../schemas/video"
import { transformedVideoSchema } from "../schemas/video"
// Block schemas
export const contentPageCards = z
@@ -250,137 +211,3 @@ export const contentPageSchema = z.object({
url: z.string(),
}),
})
/** REFS */
const contentPageCardsRefs = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.CardsGrid),
})
.merge(cardGridRefsSchema)
const contentPageBlockContentRefs = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.Content),
})
.merge(blockContentRefsSchema)
const contentPageDynamicContentRefs = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.DynamicContent),
})
.merge(dynamicContentRefsSchema)
const contentPageShortcutsRefs = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.Shortcuts),
})
.merge(shortcutsRefsSchema)
const contentPageTextColsRefs = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.TextCols),
})
.merge(textColsRefsSchema)
const contentPageUspGridRefs = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.UspGrid),
})
.merge(uspGridRefsSchema)
const contentPageAccordionRefs = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.Accordion),
})
.merge(accordionRefsSchema)
const contentPageVideoCardRefs = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.VideoCard),
})
.merge(videoCardRefsSchema)
const contentPageVideoRefs = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.Video),
})
.merge(videoBlockRefsSchema)
const contentPageJotformRefs = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.Jotform),
})
.merge(jotformRefsSchema)
const contentPageBlockRefsItem = z.discriminatedUnion("__typename", [
contentPageAccordionRefs,
contentPageBlockContentRefs,
contentPageShortcutsRefs,
contentPageCardsRefs,
contentPageDynamicContentRefs,
contentPageTextColsRefs,
contentPageUspGridRefs,
contentPageVideoCardRefs,
contentPageJotformRefs,
contentPageVideoRefs,
])
const contentPageSidebarContentRef = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.Content),
})
.merge(sidebarContentRefsSchema)
const contentPageSidebarJoinLoyaltyContactRef = z
.object({
__typename: z.literal(
ContentPageEnum.ContentStack.sidebar.JoinLoyaltyContact
),
})
.merge(joinLoyaltyContactRefsSchema)
const contentPageSidebarScriptedCardRef = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.ScriptedCard),
})
.merge(scriptedCardRefschema)
const contentPageSidebarTeaserCardRef = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.TeaserCard),
})
.merge(teaserCardRefschema)
const contentPageSidebarQuickLinksRef = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.QuickLinks),
})
.merge(quickLinksRefschema)
const contentPageSidebarRefsItem = z.discriminatedUnion("__typename", [
contentPageSidebarContentRef,
contentPageSidebarJoinLoyaltyContactRef,
contentPageSidebarScriptedCardRef,
contentPageSidebarTeaserCardRef,
contentPageSidebarQuickLinksRef,
])
const contentPageHeaderRefs = z.object({
navigation_links: z.array(linkConnectionRefs),
top_primary_button: linkConnectionRefs.nullable(),
dynamic_content: headerDynamicContentRefsSchema.nullish(),
})
export const contentPageRefsSchema = z.object({
content_page: z.object({
hero_video: videoRefSchema.nullish(),
header: contentPageHeaderRefs,
blocks: discriminatedUnionArray(
contentPageBlockRefsItem.options
).nullable(),
sidebar: discriminatedUnionArray(
contentPageSidebarRefsItem.options
).nullable(),
system: systemSchema,
}),
})

View File

@@ -8,8 +8,8 @@ import {
GetContentPageBlocksBatch2,
} from "../../../graphql/Query/ContentPage/ContentPage.graphql"
import { contentstackExtendedProcedureUID } from "../../../procedures"
import { generateTag } from "../../../utils/generateTag"
import { contentPageSchema } from "./output"
import { fetchContentPageRefs, generatePageTags } from "./utils"
import type { GetContentPageSchema } from "../../../types/contentPage"
import type { TrackingPageData } from "../../types"
@@ -18,13 +18,8 @@ export const contentPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
const contentPageRefs = await fetchContentPageRefs(lang, uid)
if (!contentPageRefs) {
return null
}
const tags = generatePageTags(contentPageRefs, lang)
// by fetching references when a child entry is published
const cacheKey = generateTag(lang, uid)
const getContentPageCounter = createCounter(
"trpc.contentstack.contentPage.get"
@@ -41,7 +36,7 @@ export const contentPageQueryRouter = router({
document: GetContentPage,
variables: { locale: lang, uid },
cacheOptions: {
key: `${tags.join(",")}:contentPage`,
key: `${cacheKey}:contentPage`,
ttl: "max",
},
},
@@ -50,7 +45,7 @@ export const contentPageQueryRouter = router({
document: GetContentPageBlocksBatch1,
variables: { locale: lang, uid },
cacheOptions: {
key: `${tags.join(",")}:contentPageBlocksBatch1`,
key: `${cacheKey}:contentPageBlocksBatch1`,
ttl: "max",
},
},
@@ -59,7 +54,7 @@ export const contentPageQueryRouter = router({
document: GetContentPageBlocksBatch2,
variables: { locale: lang, uid },
cacheOptions: {
key: `${tags.join(",")}:contentPageBlocksBatch2`,
key: `${cacheKey}:contentPageBlocksBatch2`,
ttl: "max",
},
},

View File

@@ -1,218 +0,0 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { notFoundError } from "../../../errors"
import { batchRequest } from "../../../graphql/batchRequest"
import {
GetContentPageBlocksRefs,
GetContentPageRefs,
} from "../../../graphql/Query/ContentPage/ContentPage.graphql"
import { ContentPageEnum } from "../../../types/contentPage"
import {
generateRefsResponseTag,
generateTag,
generateTagsFromAssetSystem,
generateTagsFromSystem,
} from "../../../utils/generateTag"
import { contentPageRefsSchema } from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type {
ContentPageRefs,
GetContentPageRefsSchema,
} from "../../../types/contentPage"
import type { AssetSystem, System } from "../schemas/system"
export async function fetchContentPageRefs(lang: Lang, uid: string) {
const getContentPageRefsCounter = createCounter(
"trpc.contentstack.contentPage.get.refs"
)
const metricsGetContentPageRefs = getContentPageRefsCounter.init({
lang,
uid,
})
metricsGetContentPageRefs.start()
const variables = { locale: lang, uid }
const res = await batchRequest<GetContentPageRefsSchema>([
{
document: GetContentPageRefs,
variables,
cacheOptions: {
key: generateRefsResponseTag(lang, uid),
ttl: "max",
},
},
{
document: GetContentPageBlocksRefs,
variables,
cacheOptions: {
key: generateTag(lang, uid + 1),
ttl: "max",
},
},
])
if (!res.data) {
metricsGetContentPageRefs.noDataError()
throw notFoundError({
message: "GetContentPageRefs/GetContentPageBlocksRefs returned no data",
errorDetails: variables,
})
}
const validatedData = contentPageRefsSchema.safeParse(res.data)
if (!validatedData.success) {
metricsGetContentPageRefs.validationError(validatedData.error)
return null
}
metricsGetContentPageRefs.success()
return validatedData.data
}
export function generatePageTags(
validatedData: ContentPageRefs,
lang: Lang
): string[] {
const { connections, assetConnections } = getConnections(validatedData)
return [
generateTagsFromSystem(lang, connections),
generateTagsFromAssetSystem(assetConnections),
generateTag(lang, validatedData.content_page.system.uid),
].flat()
}
export function getConnections({ content_page }: ContentPageRefs) {
const connections: System["system"][] = [content_page.system]
const assetConnections: AssetSystem[] = []
if (content_page.hero_video?.sourceConnection.edges[0]) {
assetConnections.push(
content_page.hero_video.sourceConnection.edges[0].node
)
}
if (content_page.blocks) {
content_page.blocks.forEach((block) => {
const typeName = block.__typename
switch (typeName) {
case ContentPageEnum.ContentStack.blocks.Accordion:
if (block.accordion.length) {
connections.push(...block.accordion.filter((c) => !!c))
}
break
case ContentPageEnum.ContentStack.blocks.Content:
if (block?.content?.length) {
block.content.forEach((contentBlock) => {
if ("system" in contentBlock) {
assetConnections.push(contentBlock)
} else {
connections.push(contentBlock)
}
})
}
break
case ContentPageEnum.ContentStack.blocks.CardsGrid:
if (block.cards_grid.length) {
connections.push(...block.cards_grid)
}
break
case ContentPageEnum.ContentStack.blocks.DynamicContent:
if (block.dynamic_content.link) {
connections.push(block.dynamic_content.link)
}
break
case ContentPageEnum.ContentStack.blocks.Shortcuts:
if (block.shortcuts.shortcuts.length) {
connections.push(...block.shortcuts.shortcuts.filter((c) => !!c))
}
break
case ContentPageEnum.ContentStack.blocks.TextCols:
if (block.text_cols.length) {
connections.push(...block.text_cols)
}
break
case ContentPageEnum.ContentStack.blocks.UspGrid:
if (block.usp_grid.length) {
connections.push(...block.usp_grid.filter((c) => !!c))
}
break
case ContentPageEnum.ContentStack.blocks.CardsGrid:
if (block.cards_grid.length) {
block.cards_grid.forEach((card) => {
connections.push(card)
})
}
break
case ContentPageEnum.ContentStack.blocks.VideoCard:
if (block.video_card) {
connections.push(block.video_card.system)
}
if (block.video_card?.video.sourceConnection.edges[0]) {
assetConnections.push(
block.video_card.video.sourceConnection.edges[0].node
)
}
break
case ContentPageEnum.ContentStack.blocks.Video:
if (block.video?.sourceConnection.edges[0]) {
assetConnections.push(block.video.sourceConnection.edges[0].node)
}
break
case ContentPageEnum.ContentStack.blocks.Jotform:
if (block.jotform) {
connections.push(block.jotform.system)
}
break
default:
const _exhaustiveCheck: never = typeName
break
}
})
}
if (content_page.sidebar) {
content_page.sidebar.forEach((block) => {
const typeName = block.__typename
switch (typeName) {
case ContentPageEnum.ContentStack.sidebar.Content:
if (block.content?.length) {
block.content.forEach((contentBlock) => {
if ("system" in contentBlock) {
assetConnections.push(contentBlock)
} else {
connections.push(contentBlock)
}
})
}
break
case ContentPageEnum.ContentStack.sidebar.JoinLoyaltyContact:
if (block.join_loyalty_contact?.button) {
connections.push(block.join_loyalty_contact.button)
}
break
case ContentPageEnum.ContentStack.sidebar.ScriptedCard:
if (block.scripted_card?.length) {
connections.push(...block.scripted_card)
}
break
case ContentPageEnum.ContentStack.sidebar.TeaserCard:
if (block.teaser_card?.length) {
connections.push(...block.teaser_card)
}
break
case ContentPageEnum.ContentStack.sidebar.QuickLinks:
if (block.shortcuts.shortcuts.length) {
connections.push(...block.shortcuts.shortcuts.filter((c) => !!c))
}
break
default:
const _exhaustiveCheck: never = typeName
break
}
})
}
return { connections, assetConnections }
}

View File

@@ -6,21 +6,11 @@ import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { DestinationCityPageEnum } from "../../../types/destinationCityPage"
import { isDefined } from "../../../utils"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
accordionRefsSchema,
accordionSchema,
} from "../schemas/blocks/accordion"
import { contentRefsSchema, contentSchema } from "../schemas/blocks/content"
import {
destinationFiltersRefsSchema,
transformedDestinationFiltersSchema,
} from "../schemas/destinationFilters"
import { accordionSchema } from "../schemas/blocks/accordion"
import { contentSchema } from "../schemas/blocks/content"
import { transformedDestinationFiltersSchema } from "../schemas/destinationFilters"
import { mapLocationSchema } from "../schemas/mapLocation"
import {
linkRefsUnionSchema,
linkUnionSchema,
transformPageLink,
} from "../schemas/pageLinks"
import { linkUnionSchema, transformPageLink } from "../schemas/pageLinks"
import { systemSchema } from "../schemas/system"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
@@ -227,47 +217,3 @@ export const batchedCityPageUrlsSchema = z
.transform((allItems) => {
return allItems.flatMap((item) => item.data)
})
/** REFS */
const destinationCityPageContentRefs = z
.object({
__typename: z.literal(DestinationCityPageEnum.ContentStack.blocks.Content),
})
.merge(contentRefsSchema)
const destinationCityPageAccordionRefs = z
.object({
__typename: z.literal(
DestinationCityPageEnum.ContentStack.blocks.Accordion
),
})
.merge(accordionRefsSchema)
const blocksRefsSchema = z.discriminatedUnion("__typename", [
destinationCityPageAccordionRefs,
destinationCityPageContentRefs,
])
export const destinationCityPageRefsSchema = z.object({
destination_city_page: z.object({
destination_settings: destinationCityPageDestinationSettingsSchema,
sidepeek_content: z
.object({
content: z
.object({
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
})
.nullish(),
})
.nullish(),
blocks: discriminatedUnionArray(blocksRefsSchema.options).nullable(),
seo_filters: destinationFiltersRefsSchema,
system: systemSchema,
}),
})

View File

@@ -2,67 +2,21 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "../../.."
import { notFoundError } from "../../../errors"
import {
GetDestinationCityPage,
GetDestinationCityPageRefs,
} from "../../../graphql/Query/DestinationCityPage/DestinationCityPage.graphql"
import { GetDestinationCityPage } from "../../../graphql/Query/DestinationCityPage/DestinationCityPage.graphql"
import { request } from "../../../graphql/request"
import { contentStackUidWithServiceProcedure } from "../../../procedures"
import { generateRefsResponseTag } from "../../../utils/generateTag"
import { generateTag } from "../../../utils/generateTag"
import { getCityByCityIdentifier } from "../../hotels/services/getCityByCityIdentifier"
import {
destinationCityPageRefsSchema,
destinationCityPageSchema,
} from "./output"
import { generatePageTags } from "./utils"
import { destinationCityPageSchema } from "./output"
import type {
GetDestinationCityPageData,
GetDestinationCityPageRefsSchema,
} from "../../../types/destinationCityPage"
import type { GetDestinationCityPageData } from "../../../types/destinationCityPage"
import type { TrackingPageData } from "../../types"
export const destinationCityPageQueryRouter = router({
get: contentStackUidWithServiceProcedure.query(async ({ ctx }) => {
const { lang, uid, serviceToken } = ctx
const getDestinationCityPageRefsCounter = createCounter(
"trpc.contentstack.destinationCityPage.get.refs"
)
const metricsGetDestinationCityPageRefs =
getDestinationCityPageRefsCounter.init({ lang, uid })
metricsGetDestinationCityPageRefs.start()
const variables = { locale: lang, uid }
const refsResponse = await request<GetDestinationCityPageRefsSchema>(
GetDestinationCityPageRefs,
variables,
{
key: generateRefsResponseTag(lang, uid),
ttl: "max",
}
)
if (!refsResponse.data) {
metricsGetDestinationCityPageRefs.noDataError()
throw notFoundError({
message: "GetDestinationCityPageRefs returned no data",
errorDetails: variables,
})
}
const validatedRefsData = destinationCityPageRefsSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
metricsGetDestinationCityPageRefs.validationError(validatedRefsData.error)
return null
}
metricsGetDestinationCityPageRefs.success()
const tags = generatePageTags(validatedRefsData.data, lang)
const cacheKey = generateTag(lang, uid)
const getDestinationCityPageCounter = createCounter(
"trpc.contentstack.destinationCityPage.get"
@@ -74,11 +28,12 @@ export const destinationCityPageQueryRouter = router({
metricsGetDestinationCityPage.start()
const variables = { locale: lang, uid }
const response = await request<GetDestinationCityPageData>(
GetDestinationCityPage,
variables,
{
key: tags,
key: `${cacheKey}:destinationCityPage`,
ttl: "max",
}
)

View File

@@ -3,72 +3,14 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { GetCityPageCount } from "../../../graphql/Query/DestinationCityPage/DestinationCityPageCount.graphql"
import { GetCityPageUrls } from "../../../graphql/Query/DestinationCityPage/DestinationCityPageUrl.graphql"
import { request } from "../../../graphql/request"
import { DestinationCityPageEnum } from "../../../types/destinationCityPage"
import { generateTag, generateTagsFromSystem } from "../../../utils/generateTag"
import { batchedCityPageUrlsSchema, cityPageCountSchema } from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type {
DestinationCityPageRefs,
GetCityPageCountData,
GetCityPageUrlsData,
} from "../../../types/destinationCityPage"
import type { System } from "../schemas/system"
export function generatePageTags(
validatedData: DestinationCityPageRefs,
lang: Lang
): string[] {
const connections = getConnections(validatedData)
return [
// This tag is added for the city list data on country pages to invalidate the list when city page changes.
generateTag(
lang,
`city_list_data:${validatedData.destination_city_page.destination_settings.city}`
),
generateTagsFromSystem(lang, connections),
generateTag(lang, validatedData.destination_city_page.system.uid),
].flat()
}
export function getConnections({
destination_city_page,
}: DestinationCityPageRefs) {
const connections: System["system"][] = [destination_city_page.system]
if (destination_city_page.blocks) {
destination_city_page.blocks.forEach((block) => {
switch (block.__typename) {
case DestinationCityPageEnum.ContentStack.blocks.Accordion: {
if (block.accordion.length) {
connections.push(...block.accordion.filter((c) => !!c))
}
break
}
case DestinationCityPageEnum.ContentStack.blocks.Content:
{
if (block?.content?.length) {
// TS has trouble infering the filtered types
// @ts-ignore
connections.push(...block.content)
}
}
break
}
})
}
if (destination_city_page.sidepeek_content) {
destination_city_page.sidepeek_content?.content?.embedded_itemsConnection.edges.forEach(
({ node }) => {
if (node.system) {
connections.push(node.system)
}
}
)
}
return connections
}
export async function getCityPageCount(lang: Lang) {
const getCityPageCountCounter = createCounter(

View File

@@ -6,21 +6,11 @@ import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { DestinationCountryPageEnum } from "../../../types/destinationCountryPage"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
accordionRefsSchema,
accordionSchema,
} from "../schemas/blocks/accordion"
import { contentRefsSchema, contentSchema } from "../schemas/blocks/content"
import {
destinationFiltersRefsSchema,
transformedDestinationFiltersSchema,
} from "../schemas/destinationFilters"
import { accordionSchema } from "../schemas/blocks/accordion"
import { contentSchema } from "../schemas/blocks/content"
import { transformedDestinationFiltersSchema } from "../schemas/destinationFilters"
import { mapLocationSchema } from "../schemas/mapLocation"
import {
linkRefsUnionSchema,
linkUnionSchema,
transformPageLink,
} from "../schemas/pageLinks"
import { linkUnionSchema, transformPageLink } from "../schemas/pageLinks"
import { systemSchema } from "../schemas/system"
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
@@ -131,47 +121,3 @@ export const countryPageUrlsSchema = z
.transform(
({ all_destination_country_page }) => all_destination_country_page.items
)
/** REFS */
const destinationCountryPageContentRefs = z
.object({
__typename: z.literal(
DestinationCountryPageEnum.ContentStack.blocks.Content
),
})
.merge(contentRefsSchema)
const destinationCountryPageAccordionRefs = z
.object({
__typename: z.literal(
DestinationCountryPageEnum.ContentStack.blocks.Accordion
),
})
.merge(accordionRefsSchema)
const blocksRefsSchema = z.discriminatedUnion("__typename", [
destinationCountryPageAccordionRefs,
destinationCountryPageContentRefs,
])
export const destinationCountryPageRefsSchema = z.object({
destination_country_page: z.object({
sidepeek_content: z
.object({
content: z
.object({
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
})
.nullish(),
})
.nullish(),
blocks: discriminatedUnionArray(blocksRefsSchema.options).nullable(),
seo_filters: destinationFiltersRefsSchema,
system: systemSchema,
}),
})

View File

@@ -2,73 +2,26 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "../../.."
import { notFoundError } from "../../../errors"
import {
GetDestinationCountryPage,
GetDestinationCountryPageRefs,
} from "../../../graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql"
import { GetDestinationCountryPage } from "../../../graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql"
import { request } from "../../../graphql/request"
import {
contentStackBaseWithServiceProcedure,
contentstackExtendedProcedureUID,
} from "../../../procedures"
import { ApiCountry } from "../../../types/country"
import { generateRefsResponseTag } from "../../../utils/generateTag"
import { generateTag } from "../../../utils/generateTag"
import { getCityPagesInput } from "./input"
import {
destinationCountryPageRefsSchema,
destinationCountryPageSchema,
} from "./output"
import { generatePageTags, getCityPages } from "./utils"
import { destinationCountryPageSchema } from "./output"
import { getCityPages } from "./utils"
import type {
GetDestinationCountryPageData,
GetDestinationCountryPageRefsSchema,
} from "../../../types/destinationCountryPage"
import type { GetDestinationCountryPageData } from "../../../types/destinationCountryPage"
import type { TrackingPageData } from "../../types"
export const destinationCountryPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
const getDestinationCountryPageRefsCounter = createCounter(
"trpc.contentstack.destinationCountryPage.get.refs"
)
const metricsGetDestinationCountryPageRefs =
getDestinationCountryPageRefsCounter.init({ lang, uid })
metricsGetDestinationCountryPageRefs.start()
const variables = { locale: lang, uid }
const refsResponse = await request<GetDestinationCountryPageRefsSchema>(
GetDestinationCountryPageRefs,
variables,
{
key: generateRefsResponseTag(lang, uid),
ttl: "max",
}
)
if (!refsResponse.data) {
metricsGetDestinationCountryPageRefs.noDataError()
throw notFoundError({
message: "GetDestinationCountryPageRefs returned no data",
errorDetails: variables,
})
}
const validatedRefsData = destinationCountryPageRefsSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
metricsGetDestinationCountryPageRefs.validationError(
validatedRefsData.error
)
return null
}
metricsGetDestinationCountryPageRefs.success()
const tags = generatePageTags(validatedRefsData.data, lang)
const cacheKey = generateTag(lang, uid)
const getDestinationCountryPageCounter = createCounter(
"trpc.contentstack.destinationCountryPage.get"
@@ -78,11 +31,12 @@ export const destinationCountryPageQueryRouter = router({
metricsGetDestinationCountryPage.start()
const variables = { locale: lang, uid }
const response = await request<GetDestinationCountryPageData>(
GetDestinationCountryPage,
variables,
{
key: tags,
key: `${cacheKey}:destinationCountryPage`,
ttl: "max",
}
)

View File

@@ -4,8 +4,7 @@ import { GetDestinationCityListData } from "../../../graphql/Query/DestinationCi
import { GetCountryPageUrls } from "../../../graphql/Query/DestinationCountryPage/DestinationCountryPageUrl.graphql"
import { request } from "../../../graphql/request"
import { ApiCountry } from "../../../types/country"
import { DestinationCountryPageEnum } from "../../../types/destinationCountryPage"
import { generateTag, generateTagsFromSystem } from "../../../utils/generateTag"
import { generateTag } from "../../../utils/generateTag"
import { getCitiesByCountry } from "../../hotels/services/getCitiesByCountry"
import { destinationCityListDataSchema } from "../destinationCityPage/output"
import { countryPageUrlsSchema } from "./output"
@@ -14,60 +13,7 @@ import type { Country } from "@scandic-hotels/common/constants/country"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { GetDestinationCityListDataResponse } from "../../../types/destinationCityPage"
import type {
DestinationCountryPageRefs,
GetCountryPageUrlsData,
} from "../../../types/destinationCountryPage"
import type { System } from "../schemas/system"
export function generatePageTags(
validatedData: DestinationCountryPageRefs,
lang: Lang
): string[] {
const connections = getConnections(validatedData)
return [
generateTagsFromSystem(lang, connections),
generateTag(lang, validatedData.destination_country_page.system.uid),
].flat()
}
export function getConnections({
destination_country_page,
}: DestinationCountryPageRefs) {
const connections: System["system"][] = [destination_country_page.system]
if (destination_country_page.blocks) {
destination_country_page.blocks.forEach((block) => {
switch (block.__typename) {
case DestinationCountryPageEnum.ContentStack.blocks.Accordion: {
if (block.accordion.length) {
connections.push(...block.accordion.filter((c) => !!c))
}
break
}
case DestinationCountryPageEnum.ContentStack.blocks.Content:
{
if (block?.content?.length) {
// TS has trouble infering the filtered types
// @ts-ignore
connections.push(...block.content)
}
}
break
}
})
}
if (destination_country_page.sidepeek_content) {
destination_country_page.sidepeek_content?.content?.embedded_itemsConnection.edges.forEach(
({ node }) => {
if (node.system) {
connections.push(node.system)
}
}
)
}
return connections
}
import type { GetCountryPageUrlsData } from "../../../types/destinationCountryPage"
export async function getCityListDataByCityIdentifier(
lang: Lang,

View File

@@ -2,10 +2,7 @@ import { z } from "zod"
import { DestinationOverviewPageEnum } from "../../../types/destinationOverviewPage"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
cardGalleryRefsSchema,
cardGallerySchema,
} from "../schemas/blocks/cardGallery"
import { cardGallerySchema } from "../schemas/blocks/cardGallery"
import { mapLocationSchema } from "../schemas/mapLocation"
import { systemSchema } from "../schemas/system"
@@ -37,23 +34,3 @@ export const destinationOverviewPageSchema = z.object({
url: z.string(),
}),
})
/** REFS */
const destinationOverviewPageCardGalleryRef = z
.object({
__typename: z.literal(
DestinationOverviewPageEnum.ContentStack.blocks.CardGallery
),
})
.merge(cardGalleryRefsSchema)
const blocksRefsSchema = z.discriminatedUnion("__typename", [
destinationOverviewPageCardGalleryRef,
])
export const destinationOverviewPageRefsSchema = z.object({
destination_overview_page: z.object({
blocks: discriminatedUnionArray(blocksRefsSchema.options).nullable(),
system: systemSchema,
}),
})

View File

@@ -3,37 +3,25 @@ import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import { router } from "../../.."
import { notFoundError } from "../../../errors"
import {
GetDestinationOverviewPage,
GetDestinationOverviewPageRefs,
} from "../../../graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql"
import { GetDestinationOverviewPage } from "../../../graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql"
import { request } from "../../../graphql/request"
import {
contentstackExtendedProcedureUID,
serviceProcedure,
} from "../../../procedures"
import { ApiCountry } from "../../../types/country"
import {
generateRefsResponseTag,
generateTag,
} from "../../../utils/generateTag"
import { generateTag } from "../../../utils/generateTag"
import { getCitiesByCountry } from "../../hotels/services/getCitiesByCountry"
import { getCountries } from "../../hotels/services/getCountries"
import { getHotelIdsByCityId } from "../../hotels/services/getHotelIdsByCityId"
import { getCityPageUrls } from "../destinationCityPage/utils"
import { getCountryPageUrls } from "../destinationCountryPage/utils"
import {
destinationOverviewPageRefsSchema,
destinationOverviewPageSchema,
} from "./output"
import { destinationOverviewPageSchema } from "./output"
import { getSortedDestinationsByLanguage } from "./utils"
import type { Country } from "@scandic-hotels/common/constants/country"
import type {
GetDestinationOverviewPageData,
GetDestinationOverviewPageRefsSchema,
} from "../../../types/destinationOverviewPage"
import type { GetDestinationOverviewPageData } from "../../../types/destinationOverviewPage"
import type { City, DestinationsData } from "../../../types/destinationsData"
import type { TrackingPageData } from "../../types"
@@ -41,43 +29,7 @@ export const destinationOverviewPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
const getDestinationOverviewPageRefsCounter = createCounter(
"trpc.contentstack.destinationOverviewPage.get.refs"
)
const metricsGetDestinationOverviewPageRefs =
getDestinationOverviewPageRefsCounter.init({ lang, uid })
metricsGetDestinationOverviewPageRefs.start()
const variables = { locale: lang, uid }
const refsResponse = await request<GetDestinationOverviewPageRefsSchema>(
GetDestinationOverviewPageRefs,
variables,
{
key: generateRefsResponseTag(lang, uid),
ttl: "max",
}
)
if (!refsResponse.data) {
metricsGetDestinationOverviewPageRefs.noDataError()
throw notFoundError({
message: "GetDestinationOverviewPageRefs returned no data",
errorDetails: variables,
})
}
const validatedRefsData = destinationOverviewPageRefsSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
metricsGetDestinationOverviewPageRefs.validationError(
validatedRefsData.error
)
return null
}
metricsGetDestinationOverviewPageRefs.success()
const cacheKey = generateTag(lang, uid)
const getDestinationOverviewPageCounter = createCounter(
"trpc.contentstack.destinationOverviewPage.get"
@@ -87,11 +39,12 @@ export const destinationOverviewPageQueryRouter = router({
metricsGetDestinationOverviewPage.start()
const variables = { locale: lang, uid }
const response = await request<GetDestinationOverviewPageData>(
GetDestinationOverviewPage,
variables,
{
key: generateTag(lang, uid),
key: `${cacheKey}:destinationOverviewPage`,
ttl: "max",
}
)

View File

@@ -5,12 +5,9 @@ import { nullableStringValidator } from "@scandic-hotels/common/utils/zod/string
import { HotelPageEnum } from "../../../types/hotelPageEnum"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
activitiesCardRefSchema,
activitiesCardSchema,
} from "../schemas/blocks/activitiesCard"
import { hotelFaqRefsSchema, hotelFaqSchema } from "../schemas/blocks/hotelFaq"
import { spaPageRefSchema, spaPageSchema } from "../schemas/blocks/spaPage"
import { activitiesCardSchema } from "../schemas/blocks/activitiesCard"
import { hotelFaqSchema } from "../schemas/blocks/hotelFaq"
import { spaPageSchema } from "../schemas/blocks/spaPage"
import { systemSchema } from "../schemas/system"
import type { ActivitiesCard, SpaPage } from "../../../types/hotelPage"
@@ -112,35 +109,6 @@ export const hotelPageSchema = z.object({
})),
})
/** REFS */
const hotelPageActivitiesCardRefs = z
.object({
__typename: z.literal(HotelPageEnum.ContentStack.blocks.ActivitiesCard),
})
.merge(activitiesCardRefSchema)
const hotelPageSpaPageRefs = z
.object({
__typename: z.literal(HotelPageEnum.ContentStack.blocks.SpaPage),
})
.merge(spaPageRefSchema)
const hotelPageBlockRefsItem = z.discriminatedUnion("__typename", [
hotelPageActivitiesCardRefs,
hotelPageSpaPageRefs,
])
export const hotelPageRefsSchema = z.object({
hotel_page: z.object({
content: discriminatedUnionArray(hotelPageBlockRefsItem.options).nullable(),
faq: hotelFaqRefsSchema.nullable(),
system: systemSchema,
}),
trackingProps: z.object({
url: z.string(),
}),
})
export const hotelPageUrlsSchema = z
.object({
all_hotel_page: z.object({

View File

@@ -4,106 +4,15 @@ import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/i
import { LoyaltyPageEnum } from "../../../enums/loyaltyPage"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
cardGridRefsSchema,
cardsGridSchema,
} from "../schemas/blocks/cardsGrid"
import {
contentRefsSchema as blockContentRefsSchema,
contentSchema as blockContentSchema,
} from "../schemas/blocks/content"
import {
dynamicContentRefsSchema,
dynamicContentSchema as blockDynamicContentSchema,
} from "../schemas/blocks/dynamicContent"
import {
shortcutsRefsSchema,
shortcutsSchema,
} from "../schemas/blocks/shortcuts"
import {
contentRefsSchema as sidebarContentRefsSchema,
contentSchema as sidebarContentSchema,
} from "../schemas/sidebar/content"
import { cardsGridSchema } from "../schemas/blocks/cardsGrid"
import { contentSchema as blockContentSchema } from "../schemas/blocks/content"
import { dynamicContentSchema as blockDynamicContentSchema } from "../schemas/blocks/dynamicContent"
import { shortcutsSchema } from "../schemas/blocks/shortcuts"
import { contentSchema as sidebarContentSchema } from "../schemas/sidebar/content"
import { dynamicContentSchema as sidebarDynamicContentSchema } from "../schemas/sidebar/dynamicContent"
import {
joinLoyaltyContactRefsSchema,
joinLoyaltyContactSchema,
} from "../schemas/sidebar/joinLoyaltyContact"
import { joinLoyaltyContactSchema } from "../schemas/sidebar/joinLoyaltyContact"
import { systemSchema } from "../schemas/system"
// LoyaltyPage Refs
const extendedCardGridRefsSchema = z
.object({
__typename: z.literal(LoyaltyPageEnum.ContentStack.blocks.CardsGrid),
})
.merge(cardGridRefsSchema)
const extendedContentRefsSchema = z
.object({
__typename: z.literal(LoyaltyPageEnum.ContentStack.blocks.Content),
})
.merge(blockContentRefsSchema)
const extendedDynamicContentRefsSchema = z
.object({
__typename: z.literal(LoyaltyPageEnum.ContentStack.blocks.DynamicContent),
})
.merge(dynamicContentRefsSchema)
const extendedShortcutsRefsSchema = z
.object({
__typename: z.literal(LoyaltyPageEnum.ContentStack.blocks.Shortcuts),
})
.merge(shortcutsRefsSchema)
const blocksRefsSchema = z.discriminatedUnion("__typename", [
extendedCardGridRefsSchema,
extendedContentRefsSchema,
extendedDynamicContentRefsSchema,
extendedShortcutsRefsSchema,
])
const contentSidebarRefsSchema = z
.object({
__typename: z.literal(LoyaltyPageEnum.ContentStack.sidebar.Content),
})
.merge(sidebarContentRefsSchema)
const extendedJoinLoyaltyContactRefsSchema = z
.object({
__typename: z.literal(
LoyaltyPageEnum.ContentStack.sidebar.JoinLoyaltyContact
),
})
.merge(joinLoyaltyContactRefsSchema)
const sidebarRefsSchema = z.discriminatedUnion("__typename", [
contentSidebarRefsSchema,
extendedJoinLoyaltyContactRefsSchema,
z.object({
__typename: z.literal(LoyaltyPageEnum.ContentStack.sidebar.DynamicContent),
}),
])
export const loyaltyPageRefsSchema = z.object({
loyalty_page: z.object({
blocks: discriminatedUnionArray(blocksRefsSchema.options).optional(),
sidebar: discriminatedUnionArray(sidebarRefsSchema.options)
.optional()
.transform((data) => {
if (data) {
return data.filter(
(block) =>
block.__typename !==
LoyaltyPageEnum.ContentStack.sidebar.DynamicContent
)
}
return data
}),
system: systemSchema,
}),
})
// LoyaltyPage
export const extendedCardsGridSchema = z
.object({

View File

@@ -2,78 +2,20 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "../../.."
import { notFoundError } from "../../../errors"
import {
GetLoyaltyPage,
GetLoyaltyPageRefs,
} from "../../../graphql/Query/LoyaltyPage/LoyaltyPage.graphql"
import { GetLoyaltyPage } from "../../../graphql/Query/LoyaltyPage/LoyaltyPage.graphql"
import { request } from "../../../graphql/request"
import { contentstackExtendedProcedureUID } from "../../../procedures"
import {
generateRefsResponseTag,
generateTag,
generateTagsFromAssetSystem,
generateTagsFromSystem,
} from "../../../utils/generateTag"
import { loyaltyPageRefsSchema, loyaltyPageSchema } from "./output"
import { getConnections } from "./utils"
import { generateTag } from "../../../utils/generateTag"
import { loyaltyPageSchema } from "./output"
import type {
GetLoyaltyPageRefsSchema,
GetLoyaltyPageSchema,
} from "../../../types/loyaltyPage"
import type { GetLoyaltyPageSchema } from "../../../types/loyaltyPage"
import type { TrackingPageData } from "../../types"
export const loyaltyPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
const getLoyaltyPageRefsCounter = createCounter(
"trpc.contentstack.loyaltyPage.get.refs"
)
const metricsGetLoyaltyPageRefs = getLoyaltyPageRefsCounter.init({
lang,
uid,
})
metricsGetLoyaltyPageRefs.start()
const variables = { locale: lang, uid }
const refsResponse = await request<GetLoyaltyPageRefsSchema>(
GetLoyaltyPageRefs,
variables,
{
key: generateRefsResponseTag(lang, uid),
ttl: "max",
}
)
if (!refsResponse.data) {
metricsGetLoyaltyPageRefs.noDataError()
throw notFoundError({
message: "GetLoyaltyPageRefs returned no data",
errorDetails: { ...variables },
})
}
const validatedLoyaltyPageRefs = loyaltyPageRefsSchema.safeParse(
refsResponse.data
)
if (!validatedLoyaltyPageRefs.success) {
metricsGetLoyaltyPageRefs.validationError(validatedLoyaltyPageRefs.error)
return null
}
metricsGetLoyaltyPageRefs.success()
const { connections, assetConnections } = getConnections(
validatedLoyaltyPageRefs.data
)
const tags = [
generateTagsFromSystem(lang, connections),
generateTagsFromAssetSystem(assetConnections),
generateTag(lang, validatedLoyaltyPageRefs.data.loyalty_page.system.uid),
].flat()
const cacheKey = generateTag(lang, uid)
const getLoyaltyPageCounter = createCounter(
"trpc.contentstack.loyaltyPage.get"
@@ -82,11 +24,12 @@ export const loyaltyPageQueryRouter = router({
metricsGetLoyaltyPage.start()
const variables = { locale: lang, uid }
const response = await request<GetLoyaltyPageSchema>(
GetLoyaltyPage,
variables,
{
key: tags,
key: `${cacheKey}:loyaltyPage`,
ttl: "max",
}
)

View File

@@ -1,72 +0,0 @@
import { LoyaltyPageEnum } from "../../../enums/loyaltyPage"
import type { LoyaltyPageRefs } from "../../../types/loyaltyPage"
import type { AssetSystem, System } from "../schemas/system"
export function getConnections({ loyalty_page }: LoyaltyPageRefs) {
const connections: System["system"][] = [loyalty_page.system]
const assetConnections: AssetSystem[] = []
if (loyalty_page.blocks) {
loyalty_page.blocks.forEach((block) => {
switch (block.__typename) {
case LoyaltyPageEnum.ContentStack.blocks.CardsGrid:
if (block.cards_grid.length) {
connections.push(...block.cards_grid)
}
break
case LoyaltyPageEnum.ContentStack.blocks.Content:
if (block?.content?.length) {
block.content.forEach((contentBlock) => {
if ("system" in contentBlock) {
assetConnections.push(contentBlock)
} else {
connections.push(contentBlock)
}
})
}
break
case LoyaltyPageEnum.ContentStack.blocks.DynamicContent:
if (block.dynamic_content.link) {
connections.push(block.dynamic_content.link)
}
break
case LoyaltyPageEnum.ContentStack.blocks.Shortcuts:
if (block.shortcuts.shortcuts.length) {
connections.push(...block.shortcuts.shortcuts.filter((c) => !!c))
}
break
default:
break
}
})
}
if (loyalty_page.sidebar) {
loyalty_page.sidebar.forEach((block) => {
switch (block?.__typename) {
case LoyaltyPageEnum.ContentStack.sidebar.Content:
if (block?.content?.length) {
block.content.forEach((contentBlock) => {
if ("system" in contentBlock) {
assetConnections.push(contentBlock)
} else {
connections.push(contentBlock)
}
})
}
break
case LoyaltyPageEnum.ContentStack.sidebar.JoinLoyaltyContact:
if (block.join_loyalty_contact?.button) {
connections.push(block.join_loyalty_contact.button)
}
break
default:
break
}
})
}
return { connections, assetConnections }
}

View File

@@ -6,14 +6,8 @@ import { nullableStringValidator } from "@scandic-hotels/common/utils/zod/string
import { PromoCampaignPageEnum } from "../../../types/promoCampaignPage"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
accordionRefsSchema,
accordionSchema,
} from "../schemas/blocks/accordion"
import {
contentRefsSchema as blockContentRefsSchema,
contentSchema as blockContentSchema,
} from "../schemas/blocks/content"
import { accordionSchema } from "../schemas/blocks/accordion"
import { contentSchema as blockContentSchema } from "../schemas/blocks/content"
import { systemSchema } from "../schemas/system"
export const promoCampaignPageContent = z
@@ -96,30 +90,3 @@ export const promoCampaignPageSchema = z
},
}
})
/** REFS */
const promoCampaignPageBlockContentRefs = z
.object({
__typename: z.literal(PromoCampaignPageEnum.ContentStack.blocks.Content),
})
.merge(blockContentRefsSchema)
const promoCampaignPageAccordionRefs = z
.object({
__typename: z.literal(PromoCampaignPageEnum.ContentStack.blocks.Accordion),
})
.merge(accordionRefsSchema)
const promoCampaignPageBlockRefsItem = z.discriminatedUnion("__typename", [
promoCampaignPageAccordionRefs,
promoCampaignPageBlockContentRefs,
])
export const promoCampaignPageRefsSchema = z.object({
promo_campaign_page: z.object({
blocks: discriminatedUnionArray(
promoCampaignPageBlockRefsItem.options
).nullable(),
system: systemSchema,
}),
})

View File

@@ -2,64 +2,20 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "../../.."
import { notFoundError } from "../../../errors"
import {
GetPromoCampaignPage,
GetPromoCampaignPageRefs,
} from "../../../graphql/Query/PromoCampaignPage/PromoCampaignPage.graphql"
import { GetPromoCampaignPage } from "../../../graphql/Query/PromoCampaignPage/PromoCampaignPage.graphql"
import { request } from "../../../graphql/request"
import { contentStackUidWithServiceProcedure } from "../../../procedures"
import { generateRefsResponseTag } from "../../../utils/generateTag"
import { promoCampaignPageRefsSchema, promoCampaignPageSchema } from "./output"
import { generatePageTags } from "./utils"
import { generateTag } from "../../../utils/generateTag"
import { promoCampaignPageSchema } from "./output"
import type {
GetPromoCampaignPageData,
GetPromoCampaignPageRefsData,
} from "../../../types/promoCampaignPage"
import type { GetPromoCampaignPageData } from "../../../types/promoCampaignPage"
import type { TrackingPageData } from "../../types"
export const promoCampaignPageQueryRouter = router({
get: contentStackUidWithServiceProcedure.query(async ({ ctx }) => {
const { lang, uid } = ctx
const getPromoCampaignPageRefsCounter = createCounter(
"trpc.contentstack.promoCampaignPage.get.refs"
)
const metricsGetPromoCampaignPageRefs =
getPromoCampaignPageRefsCounter.init({
lang,
uid,
})
metricsGetPromoCampaignPageRefs.start()
const refsResponse = await request<GetPromoCampaignPageRefsData>(
GetPromoCampaignPageRefs,
{ locale: lang, uid },
{
key: generateRefsResponseTag(lang, uid),
ttl: "max",
}
)
if (!refsResponse.data) {
metricsGetPromoCampaignPageRefs.noDataError()
throw notFoundError({
message: "GetPromoCampaignPageRefs returned no data",
errorDetails: { lang, uid },
})
}
const validatedRefsData = promoCampaignPageRefsSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
metricsGetPromoCampaignPageRefs.validationError(validatedRefsData.error)
return null
}
metricsGetPromoCampaignPageRefs.success()
const tags = generatePageTags(validatedRefsData.data, lang)
const cacheKey = generateTag(lang, uid)
const getPromoCampaignPageCounter = createCounter(
"trpc.contentstack.promoCampaignPage.get"
@@ -78,7 +34,7 @@ export const promoCampaignPageQueryRouter = router({
uid,
},
{
key: tags,
key: `${cacheKey}:promoCampaignPage`,
ttl: "max",
}
)

View File

@@ -1,54 +0,0 @@
import {
PromoCampaignPageEnum,
type PromoCampaignPageRefs,
} from "../../../types/promoCampaignPage"
import {
generateTag,
generateTagsFromAssetSystem,
generateTagsFromSystem,
} from "../../../utils/generateTag"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { AssetSystem, System } from "../schemas/system"
export function generatePageTags(
validatedData: PromoCampaignPageRefs,
lang: Lang
): string[] {
const { connections, assetConnections } = getConnections(validatedData)
return [
generateTagsFromSystem(lang, connections),
generateTagsFromAssetSystem(assetConnections),
generateTag(lang, validatedData.promo_campaign_page.system.uid),
].flat()
}
export function getConnections({ promo_campaign_page }: PromoCampaignPageRefs) {
const connections: System["system"][] = [promo_campaign_page.system]
const assetConnections: AssetSystem[] = []
if (promo_campaign_page.blocks) {
promo_campaign_page.blocks.forEach((block) => {
switch (block.__typename) {
case PromoCampaignPageEnum.ContentStack.blocks.Accordion:
if (block.accordion.length) {
connections.push(...block.accordion.filter((c) => !!c))
}
break
case PromoCampaignPageEnum.ContentStack.blocks.Content:
if (block?.content?.length) {
block.content.forEach((contentBlock) => {
if ("system" in contentBlock) {
assetConnections.push(contentBlock)
} else {
connections.push(contentBlock)
}
})
}
break
}
})
}
return { connections, assetConnections }
}

View File

@@ -2,12 +2,7 @@ import { z } from "zod"
import { MembershipLevelEnum } from "@scandic-hotels/common/constants/membershipLevels"
import {
linkRefsUnionSchema,
linkUnionSchema,
transformPageLink,
} from "../schemas/pageLinks"
import { systemSchema } from "../schemas/system"
import { linkUnionSchema, transformPageLink } from "../schemas/pageLinks"
export {
BenefitReward,
@@ -15,7 +10,6 @@ export {
CouponReward,
REDEEM_LOCATIONS,
REWARD_TYPES,
rewardRefsSchema,
validateApiAllTiersSchema,
validateCategorizedRewardsSchema,
validateCmsRewardsSchema,
@@ -65,31 +59,6 @@ const validateCmsRewardsSchema = z
})
.transform((data) => data.data.all_reward.items)
const rewardRefsSchema = z.object({
data: z.object({
all_reward: z.object({
items: z.array(
z.object({
redeem_description: z
.object({
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
})
// This is primarily added in order to handle a transition
// switching from string to RTE
.nullable(),
system: systemSchema,
})
),
}),
}),
})
const REDEEM_LOCATIONS = ["Non-redeemable", "On-site", "Online"] as const
const REWARD_CATEGORIES = [
"Restaurants",

View File

@@ -3,27 +3,14 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import * as api from "../../../api"
import { notFoundError } from "../../../errors"
import {
GetRewards as GetRewards,
GetRewardsRef as GetRewardsRef,
} from "../../../graphql/Query/RewardsWithRedeem.graphql"
import { GetRewards } from "../../../graphql/Query/RewardsWithRedeem.graphql"
import { request } from "../../../graphql/request"
import {
generateLoyaltyConfigTag,
generateRefsResponseTag,
} from "../../../utils/generateTag"
import {
rewardRefsSchema,
validateApiAllTiersSchema,
validateCmsRewardsSchema,
} from "./output"
import { generateLoyaltyConfigTag } from "../../../utils/generateTag"
import { validateApiAllTiersSchema, validateCmsRewardsSchema } from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type {
CMSRewardsResponse,
GetRewardRefsSchema,
} from "../../../types/reward"
import type { CMSRewardsResponse } from "../../../types/reward"
export function getUniqueRewardIds(rewardIds: string[]) {
const uniqueRewardIds = new Set(rewardIds)
@@ -88,44 +75,6 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) {
generateLoyaltyConfigTag(lang, "reward", id)
)
const getContentstackRewardAllRefsCounter = createCounter(
"trpc.contentstack.reward.all.refs"
)
const metricsGetContentstackRewardAllRefs =
getContentstackRewardAllRefsCounter.init({ lang, rewardIds })
metricsGetContentstackRewardAllRefs.start()
const refsResponse = await request<GetRewardRefsSchema>(
GetRewardsRef,
{
locale: lang,
rewardIds,
},
{
key: rewardIds.map((rewardId) => generateRefsResponseTag(lang, rewardId)),
ttl: "max",
}
)
if (!refsResponse.data) {
metricsGetContentstackRewardAllRefs.noDataError()
throw notFoundError({
message: "GetRewardsRef returned no data",
errorDetails: { lang, rewardIds },
})
}
const validatedRefsData = rewardRefsSchema.safeParse(refsResponse)
if (!validatedRefsData.success) {
metricsGetContentstackRewardAllRefs.validationError(validatedRefsData.error)
return null
}
metricsGetContentstackRewardAllRefs.success()
const getContentstackRewardAllCounter = createCounter(
"trpc.contentstack.reward.all"
)
@@ -134,6 +83,8 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) {
rewardIds,
})
metricsGetContentstackRewardAll.start()
const cmsRewardsResponse = await request<CMSRewardsResponse>(
GetRewards,
{

View File

@@ -1,11 +1,7 @@
import { z } from "zod"
import { BlocksEnums } from "../../../../types/blocksEnum"
import {
linkRefsUnionSchema,
linkUnionSchema,
transformPageLink,
} from "../pageLinks"
import { linkUnionSchema, transformPageLink } from "../pageLinks"
import { sysAssetSchema } from "./sysAsset"
export const embeddedContentSchema = z.union([linkUnionSchema, sysAssetSchema])
@@ -117,95 +113,3 @@ export const accordionSchema = z.object({
}
}),
})
export const globalAccordionConnectionRefs = z.object({
edges: z.array(
z.object({
node: z.object({
questions: z.array(
z.object({
answer: z.object({
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
}),
})
),
}),
})
),
})
export const specificAccordionConnectionRefs = z.object({
questions: z.array(
z.object({
answer: z.object({
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
}),
})
),
})
export const accordionRefsSchema = z.object({
accordion: z
.object({
accordions: z.array(
z.object({
__typename: z.nativeEnum(AccordionEnum),
global_accordion: z
.object({
global_accordionConnection: globalAccordionConnectionRefs,
})
.optional(),
specific_accordion: specificAccordionConnectionRefs.optional(),
})
),
})
.transform((data) => {
return data.accordions.flatMap((accordion) => {
switch (accordion.__typename) {
case AccordionEnum.CampaignPageBlocksAccordionBlockAccordionsGlobalAccordion:
case AccordionEnum.ContentPageBlocksAccordionBlockAccordionsGlobalAccordion:
case AccordionEnum.DestinationCityPageBlocksAccordionBlockAccordionsGlobalAccordion:
case AccordionEnum.DestinationCountryPageBlocksAccordionBlockAccordionsGlobalAccordion:
case AccordionEnum.DestinationFilterBlocksAccordionBlockAccordionsGlobalAccordion:
case AccordionEnum.PromoCampaignPageBlocksAccordionBlockAccordionsGlobalAccordion:
return (
accordion.global_accordion?.global_accordionConnection.edges.flatMap(
({ node: accordionConnection }) => {
return accordionConnection.questions.flatMap((question) =>
question.answer.embedded_itemsConnection.edges.flatMap(
({ node }) => node.system
)
)
}
) || []
)
case AccordionEnum.CampaignPageBlocksAccordionBlockAccordionsSpecificAccordion:
case AccordionEnum.DestinationCityPageBlocksAccordionBlockAccordionsSpecificAccordion:
case AccordionEnum.DestinationCountryPageBlocksAccordionBlockAccordionsSpecificAccordion:
case AccordionEnum.DestinationFilterBlocksAccordionBlockAccordionsSpecificAccordion:
case AccordionEnum.PromoCampaignPageBlocksAccordionBlockAccordionsSpecificAccordion:
return (
accordion.specific_accordion?.questions.flatMap((question) =>
question.answer.embedded_itemsConnection.edges.flatMap(
({ node }) => node.system
)
) || []
)
default:
return []
}
})
}),
})

View File

@@ -4,12 +4,7 @@ import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/i
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { HotelPageEnum } from "../../../../types/hotelPageEnum"
import {
collectionPageRefSchema,
collectionPageSchema,
contentPageRefSchema,
contentPageSchema,
} from "../pageLinks"
import { collectionPageSchema, contentPageSchema } from "../pageLinks"
export const activitiesCardSchema = z.object({
typename: z
@@ -70,26 +65,3 @@ export const activitiesCardSchema = z.object({
}
}),
})
export const activitiesCardRefSchema = z.object({
upcoming_activities_card: z
.object({
hotel_page_activities_content_pageConnection: z.object({
edges: z.array(
z.object({
node: z.discriminatedUnion("__typename", [
contentPageRefSchema,
collectionPageRefSchema,
]),
})
),
}),
})
.transform((data) => {
return (
data.hotel_page_activities_content_pageConnection.edges.flatMap(
({ node }) => node.system
) || []
)
}),
})

View File

@@ -4,7 +4,6 @@ import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/i
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { campaignPageRefSchema, promoCampaignPageRefSchema } from "../pageLinks"
export const campaignPageCardSchema = z.object({
image: transformedImageVaultAssetSchema,
@@ -66,18 +65,3 @@ export const allCampaignsSchema = z.object({
}
}),
})
export const allCampaignsRefsSchema = z.object({
all_campaigns: z.object({
campaignsConnection: z.object({
edges: z.array(
z.object({
node: z.discriminatedUnion("__typename", [
campaignPageRefSchema,
promoCampaignPageRefSchema,
]),
})
),
}),
}),
})

View File

@@ -1,13 +1,8 @@
import { z } from "zod"
import { BlocksEnums } from "../../../../types/blocksEnum"
import {
contentCardRefSchema,
contentCardSchema,
transformContentCard,
} from "./cards/contentCard"
import { contentCardSchema, transformContentCard } from "./cards/contentCard"
import { buttonSchema } from "./utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "./utils/linkConnection"
export const cardGallerySchema = z.object({
typename: z
@@ -77,24 +72,3 @@ export const cardGallerySchema = z.object({
}
}),
})
export const cardGalleryRefsSchema = z.object({
typename: z
.literal(BlocksEnums.block.CardGallery)
.optional()
.default(BlocksEnums.block.CardGallery),
card_gallery: z.object({
card_groups: z.array(
z.object({
cardsConnection: z.object({
edges: z.array(
z.object({
node: contentCardRefSchema,
})
),
}),
})
),
link: linkConnectionRefsSchema.optional(),
}),
})

View File

@@ -5,7 +5,6 @@ import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/i
import { CardsEnum } from "../../../../../types/cardsEnum"
import { systemSchema } from "../../system"
import { buttonSchema } from "../utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "../utils/linkConnection"
export const contentCardSchema = z.object({
__typename: z.literal(CardsEnum.ContentCard),
@@ -19,12 +18,6 @@ export const contentCardSchema = z.object({
system: systemSchema,
})
export const contentCardRefSchema = z.object({
__typename: z.literal(CardsEnum.ContentCard),
card_link: linkConnectionRefsSchema,
system: systemSchema,
})
export function transformContentCard(card: typeof contentCardSchema._type) {
// Return null if image or image URL is missing
if (!card.image?.url) return null

View File

@@ -6,7 +6,6 @@ import { CardsEnum } from "../../../../../types/cardsEnum"
import { systemSchema } from "../../system"
import { getInfoCardThemeFromDeprecatedCardTheme } from "../cardsGrid"
import { buttonSchema } from "../utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "../utils/linkConnection"
const INFO_CARD_WITH_IMAGE_THEMES = [
"one",
@@ -62,10 +61,3 @@ export function transformInfoCardWithImageBlock(
system: card.system,
}
}
export const infoCardWithImageBlockRefsSchema = z.object({
__typename: z.literal(CardsEnum.InfoCardWithImage),
primary_button: linkConnectionRefsSchema,
secondary_button: linkConnectionRefsSchema,
system: systemSchema,
})

View File

@@ -5,7 +5,6 @@ import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/i
import { CardsEnum } from "../../../../../types/cardsEnum"
import { systemSchema } from "../../system"
import { buttonSchema } from "../utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "../utils/linkConnection"
export const loyaltyCardBlockSchema = z.object({
__typename: z.literal(CardsEnum.LoyaltyCard),
@@ -17,9 +16,3 @@ export const loyaltyCardBlockSchema = z.object({
system: systemSchema,
title: z.string().optional(),
})
export const loyaltyCardBlockRefsSchema = z.object({
__typename: z.literal(CardsEnum.LoyaltyCard),
link: linkConnectionRefsSchema,
system: systemSchema,
})

View File

@@ -21,7 +21,6 @@ import { systemSchema } from "../../system"
import { imageContainerSchema } from "../imageContainer"
import { sysAssetSchema } from "../sysAsset"
import { buttonSchema } from "../utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "../utils/linkConnection"
export const teaserCardBlockSchema = z.object({
__typename: z.literal(CardsEnum.TeaserCard),
@@ -118,10 +117,3 @@ export function transformTeaserCardBlock(
system: card.system,
}
}
export const teaserCardBlockRefsSchema = z.object({
__typename: z.literal(CardsEnum.TeaserCard),
primary_button: linkConnectionRefsSchema,
secondary_button: linkConnectionRefsSchema,
system: systemSchema,
})

View File

@@ -10,21 +10,15 @@ import {
} from "../../../../types/cardsGridEnum"
import { systemSchema } from "../system"
import {
infoCardWithImageBlockRefsSchema,
infoCardWithImageBlockSchema,
transformInfoCardWithImageBlock,
} from "./cards/infoCardWithImage"
import { loyaltyCardBlockSchema } from "./cards/loyaltyCard"
import {
loyaltyCardBlockRefsSchema,
loyaltyCardBlockSchema,
} from "./cards/loyaltyCard"
import {
teaserCardBlockRefsSchema,
teaserCardBlockSchema,
transformTeaserCardBlock,
} from "./cards/teaserCard"
import { buttonSchema } from "./utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "./utils/linkConnection"
export const infoCardBlockSchema = z.object({
__typename: z.literal(CardsGridEnum.cards.InfoCard),
@@ -140,67 +134,6 @@ export const cardsGridSchema = z.object({
}),
})
export const infoCardBlockRefsSchema = z.object({
__typename: z.literal(CardsGridEnum.cards.InfoCard),
primary_button: linkConnectionRefsSchema,
secondary_button: linkConnectionRefsSchema,
system: systemSchema,
})
export function transformCardBlockRefs(
card:
| typeof infoCardBlockRefsSchema._type
| typeof teaserCardBlockRefsSchema._type
| typeof infoCardWithImageBlockRefsSchema._type
) {
const cards = [card.system]
if (card.primary_button) {
cards.push(card.primary_button)
}
if (card.secondary_button) {
cards.push(card.secondary_button)
}
return cards
}
export const cardGridRefsSchema = z.object({
cards_grid: z
.object({
cardConnection: z.object({
edges: z.array(
z.object({
node: z.discriminatedUnion("__typename", [
infoCardBlockRefsSchema,
loyaltyCardBlockRefsSchema,
teaserCardBlockRefsSchema,
infoCardWithImageBlockRefsSchema,
]),
})
),
}),
link: linkConnectionRefsSchema.nullish(),
})
.transform((data) => {
return data.cardConnection.edges
.map(({ node }) => {
if (
node.__typename === CardsGridEnum.cards.InfoCard ||
node.__typename === CardsGridEnum.cards.TeaserCard ||
node.__typename === CardsGridEnum.cards.InfoCardWithImage
) {
return transformCardBlockRefs(node)
} else {
const loyaltyCards = [node.system]
if (node.link) {
loyaltyCards.push(node.link)
}
return loyaltyCards
}
})
.flat()
}),
})
export function getInfoCardThemeFromDeprecatedCardTheme(theme?: string | null) {
if (!theme) {
return null

View File

@@ -1,13 +1,8 @@
import { z } from "zod"
import { BlocksEnums } from "../../../../types/blocksEnum"
import {
contentCardRefSchema,
contentCardSchema,
transformContentCard,
} from "./cards/contentCard"
import { contentCardSchema, transformContentCard } from "./cards/contentCard"
import { buttonSchema } from "./utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "./utils/linkConnection"
const commonFields = {
heading: z.string().optional(),
@@ -135,24 +130,3 @@ export const carouselCardsSchema = z.object({
}
}),
})
export const carouselCardsRefsSchema = z.object({
typename: z
.literal(BlocksEnums.block.CarouselCards)
.optional()
.default(BlocksEnums.block.CarouselCards),
carousel_cards: z.object({
card_groups: z.array(
z.object({
cardConnection: z.object({
edges: z.array(
z.object({
node: contentCardRefSchema,
})
),
}),
})
),
link: linkConnectionRefsSchema.nullish(),
}),
})

View File

@@ -1,17 +1,9 @@
import { z } from "zod"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { ContentEnum } from "../../../../types/content"
import {
rawLinkRefsUnionSchema,
rawLinkUnionSchema,
transformPageLink,
} from "../pageLinks"
import {
imageContainerRefsSchema,
imageContainerSchema,
} from "./imageContainer"
import { sysAssetRefsSchema, sysAssetSchema } from "./sysAsset"
import { rawLinkUnionSchema, transformPageLink } from "../pageLinks"
import { imageContainerSchema } from "./imageContainer"
import { sysAssetSchema } from "./sysAsset"
export const contentSchema = z.object({
typename: z
@@ -50,39 +42,3 @@ export const contentSchema = z.object({
return data?.content
}),
})
export const contentRefsSchema = z.object({
content: z
.object({
content: z
.object({
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: z.discriminatedUnion("__typename", [
sysAssetRefsSchema,
imageContainerRefsSchema,
...rawLinkRefsUnionSchema.options,
]),
})
),
}),
})
.nullish(),
})
.nullish()
.transform((data) => {
return data?.content?.embedded_itemsConnection.edges
.map(({ node }) => {
switch (node.__typename) {
case ContentEnum.blocks.SysAsset:
return node.system && (node.permanent_url || node.url)
? { system: node.system, url: node.permanent_url || node.url }
: null
default:
return node.system
}
})
.filter((node) => !!node)
}),
})

View File

@@ -2,11 +2,7 @@ import { z } from "zod"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { DynamicContentEnum } from "../../../../types/dynamicContent"
import {
linkRefsUnionSchema,
linkUnionSchema,
transformPageLink,
} from "../pageLinks"
import { linkUnionSchema, transformPageLink } from "../pageLinks"
export const dynamicContentSchema = z.object({
typename: z
@@ -47,24 +43,3 @@ export const dynamicContentSchema = z.object({
}),
}),
})
export const dynamicContentRefsSchema = z.object({
dynamic_content: z.object({
link: z
.object({
linkConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
})
.transform((data) => {
if (data.linkConnection?.edges.length) {
return data.linkConnection.edges[0].node.system
}
return null
}),
}),
})

View File

@@ -2,7 +2,6 @@ import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import * as pageLinks from "../../../../routers/contentstack/schemas/pageLinks"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { systemSchema } from "../system"
import { buttonSchema } from "./utils/buttonLinkSchema"
@@ -41,24 +40,3 @@ export const fullWidthCampaignBlockSchema = z
.default(BlocksEnums.block.FullWidthCampaign),
})
.merge(fullWidthCampaignSchema)
export const fullWidthCampaignBlockRefsSchema = z.object({
full_width_campaign: z.object({
full_width_campaignConnection: z.object({
edges: z.array(
z.object({
node: z.discriminatedUnion("__typename", [
pageLinks.accountPageRefSchema,
pageLinks.contentPageRefSchema,
pageLinks.loyaltyPageRefSchema,
pageLinks.collectionPageRefSchema,
pageLinks.hotelPageRefSchema,
pageLinks.destinationCityPageRefSchema,
pageLinks.destinationCountryPageRefSchema,
pageLinks.destinationOverviewPageRefSchema,
]),
})
),
}),
}),
})

View File

@@ -1,12 +1,7 @@
import { z } from "zod"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { HotelPageEnum } from "../../../../types/hotelPageEnum"
import {
accordionItemsSchema,
globalAccordionConnectionRefs,
specificAccordionConnectionRefs,
} from "./accordion"
import { accordionItemsSchema } from "./accordion"
export const hotelFaqSchema = z
.object({
@@ -43,33 +38,3 @@ export const hotelFaqSchema = z
array.push(data.specific_faq?.questions || [])
return { ...data, accordions: array.flat(2) }
})
export const hotelFaqRefsSchema = z
.object({
__typename: z
.literal(HotelPageEnum.ContentStack.blocks.Faq)
.optional()
.default(HotelPageEnum.ContentStack.blocks.Faq),
global_faqConnection: globalAccordionConnectionRefs.optional(),
specific_faq: specificAccordionConnectionRefs.optional().nullable(),
})
.transform((data) => {
const array = []
array.push(
data.global_faqConnection?.edges.flatMap(({ node: faqConnection }) => {
return faqConnection.questions.flatMap((question) =>
question.answer.embedded_itemsConnection.edges.flatMap(
({ node }) => node.system
)
)
}) || []
)
array.push(
data.specific_faq?.questions.flatMap((question) =>
question.answer.embedded_itemsConnection.edges.flatMap(
({ node }) => node.system
)
) || []
)
return array.flat(2)
})

View File

@@ -12,8 +12,3 @@ export const imageContainerSchema = z.object({
system: systemSchema,
title: z.string().optional(),
})
export const imageContainerRefsSchema = z.object({
__typename: z.literal(ContentEnum.blocks.ImageContainer),
system: systemSchema,
})

View File

@@ -4,7 +4,6 @@ import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/i
import { BlocksEnums } from "../../../../types/blocksEnum"
import { buttonSchema } from "./utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "./utils/linkConnection"
export const joinScandicFriendsSchema = z.object({
join_scandic_friends: z.object({
@@ -28,9 +27,3 @@ export const joinScandicFriendsBlockSchema = z
.default(BlocksEnums.block.JoinScandicFriends),
})
.merge(joinScandicFriendsSchema)
export const joinScandicFriendsBlockRefsSchema = z.object({
join_scandic_friends: z.object({
primary_button: linkConnectionRefsSchema,
}),
})

View File

@@ -1,7 +1,6 @@
import { z } from "zod"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { systemSchema } from "../system"
export const jotformSchema = z.object({
typename: z
@@ -24,19 +23,3 @@ export const jotformSchema = z.object({
return data.formConnection.edges[0]?.node || null
}),
})
export const jotformRefsSchema = z.object({
jotform: z
.object({
formConnection: z.object({
edges: z.array(
z.object({
node: z.object({ system: systemSchema }),
})
),
}),
})
.transform((data) => {
return data.formConnection.edges[0]?.node || null
}),
})

View File

@@ -1,11 +1,7 @@
import { z } from "zod"
import { BlocksEnums } from "../../../../types/blocksEnum"
import {
linkRefsUnionSchema,
linkUnionSchema,
transformPageLink,
} from "../pageLinks"
import { linkUnionSchema, transformPageLink } from "../pageLinks"
export const shortcutsBlockSchema = z.object({
shortcuts: z
@@ -91,27 +87,3 @@ export const shortcutsSchema = z
.default(BlocksEnums.block.Shortcuts),
})
.merge(shortcutsBlockSchema)
export const shortcutsRefsSchema = z.object({
shortcuts: z.object({
shortcuts: z
.array(
z.object({
linkConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
})
)
.transform((data) =>
data
.map((shortcut) => {
return shortcut.linkConnection.edges.map(({ node }) => node.system)
})
.flat()
),
}),
})

View File

@@ -3,7 +3,6 @@ import { z } from "zod"
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
import { HotelPageEnum } from "../../../../types/hotelPageEnum"
import { collectionPageRefSchema, contentPageRefSchema } from "../pageLinks"
export const spaPageSchema = z.object({
typename: z
@@ -46,22 +45,3 @@ export const spaPageSchema = z.object({
}
}),
})
export const spaPageRefSchema = z.object({
spa_page: z
.object({
pageConnection: z.object({
edges: z.array(
z.object({
node: z.discriminatedUnion("__typename", [
contentPageRefSchema,
collectionPageRefSchema,
]),
})
),
}),
})
.transform((data) => {
return data.pageConnection.edges.flatMap(({ node }) => node.system) || []
}),
})

View File

@@ -31,13 +31,3 @@ export const sysAssetSchema = z.object({
.nullish()
.transform((val) => (val === "Permanent URL Not Defined!" ? null : val)), // ContentStack returns this string when permanent_url is not defined
})
export const sysAssetRefsSchema = z.object({
__typename: z.literal(ContentEnum.blocks.SysAsset),
url: z.string().nullish(),
permanent_url: z
.string()
.nullish()
.transform((val) => (val === "Permanent URL Not Defined!" ? null : val)), // ContentStack returns this string when permanent_url is not defined
system: assetSystemSchema.nullish(),
})

View File

@@ -1,15 +1,8 @@
import { z } from "zod"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { ContentEnum } from "../../../../types/content"
import {
rawLinkRefsUnionSchema,
rawLinkUnionSchema,
transformPageLink,
} from "../pageLinks"
import { sysAssetRefsSchema, sysAssetSchema } from "./sysAsset"
import type { linkUnionSchema } from "../pageLinks"
import { rawLinkUnionSchema, transformPageLink } from "../pageLinks"
import { sysAssetSchema } from "./sysAsset"
export const textColsSchema = z.object({
typename: z
@@ -45,39 +38,3 @@ export const textColsSchema = z.object({
),
}),
})
type Refs = {
node: z.TypeOf<typeof linkUnionSchema>
}
export const textColsRefsSchema = z.object({
text_cols: z
.object({
columns: z.array(
z.object({
text: z.object({
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: z.discriminatedUnion("__typename", [
sysAssetRefsSchema,
...rawLinkRefsUnionSchema.options,
]),
})
),
}),
}),
})
),
})
.transform((data) => {
return data.columns
.map((column) => {
const filtered = column.text.embedded_itemsConnection.edges.filter(
(block) => block.node.__typename !== ContentEnum.blocks.SysAsset
) as unknown as Refs[] // TS issue with filtered out types
return filtered.map(({ node }) => node.system)
})
.flat()
}),
})

View File

@@ -2,11 +2,7 @@ import { z } from "zod"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { UspGridEnum } from "../../../../types/uspGrid"
import {
linkRefsUnionSchema,
linkUnionSchema,
transformPageLink,
} from "../pageLinks"
import { linkUnionSchema, transformPageLink } from "../pageLinks"
const uspCardSchema = z.object({
icon: UspGridEnum.uspIcons,
@@ -53,39 +49,3 @@ export const uspGridSchema = z.object({
}
}),
})
export const uspGridRefsSchema = z.object({
usp_grid: z
.object({
cardsConnection: z.object({
edges: z.array(
z.object({
node: z.object({
usp_card: z.array(
z.object({
text: z.object({
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
}),
})
),
}),
})
),
}),
})
.transform((data) => {
return data.cardsConnection.edges.flatMap(({ node }) =>
node.usp_card.flatMap((card) =>
card.text.embedded_itemsConnection.edges.map(
({ node }) => node.system
)
)
)
}),
})

View File

@@ -1,21 +0,0 @@
import { z } from "zod"
import { linkRefsUnionSchema } from "../../pageLinks"
export const linkConnectionRefsSchema = z
.object({
linkConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
})
.transform((data) => {
if (!data.linkConnection.edges.length) {
return null
}
return data.linkConnection.edges[0].node?.system
})

View File

@@ -1,14 +1,9 @@
import { z } from "zod"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { transformedVideoSchema, videoRefSchema } from "../video"
import { transformedVideoSchema } from "../video"
export const videoBlockSchema = z.object({
typename: z.literal(BlocksEnums.block.Video).default(BlocksEnums.block.Video),
video: transformedVideoSchema,
})
export const videoBlockRefsSchema = z.object({
typename: z.literal(BlocksEnums.block.Video).default(BlocksEnums.block.Video),
video: videoRefSchema,
})

View File

@@ -3,8 +3,7 @@ import { z } from "zod"
import { logger } from "@scandic-hotels/common/logger"
import { BlocksEnums } from "../../../../types/blocksEnum"
import { systemSchema } from "../system"
import { transformedVideoSchema, videoRefSchema } from "../video"
import { transformedVideoSchema } from "../video"
const cardStyleSchema = z
.string()
@@ -86,32 +85,3 @@ export const videoCardSchema = z.object({
}
}),
})
const videoCardRefSchema = z.object({
video: videoRefSchema,
system: systemSchema,
})
export const videoCardRefsSchema = z.object({
typename: z
.literal(BlocksEnums.block.VideoCard)
.default(BlocksEnums.block.VideoCard),
video_card: z
.object({
video_cardConnection: z.object({
edges: z.array(
z.object({
node: videoCardRefSchema,
})
),
}),
})
.transform((data) => {
const videoCard = data.video_cardConnection.edges[0]?.node
if (!videoCard?.video) {
return null
}
return videoCard
}),
})

View File

@@ -7,7 +7,6 @@ import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import { hotelFilterSchema } from "../../hotels/filters/output"
import { accordionSchema } from "./blocks/accordion"
import { contentSchema } from "./blocks/content"
import { systemSchema } from "./system"
export const destinationFilterBlockContent = z
.object({
@@ -50,22 +49,6 @@ export const transformedDestinationFiltersSchema =
transformDestinationFiltersResponse(data)
)
export const destinationFiltersRefsSchema = z
.array(
z.object({
filterConnection: z.object({
edges: z.array(
z.object({
node: z.object({
system: systemSchema,
}),
})
),
}),
})
)
.nullish()
export function transformDestinationFiltersResponse(
data: typeof destinationFiltersSchema._type
) {

View File

@@ -5,7 +5,3 @@ import { DynamicContentEnum } from "../../../../types/dynamicContent"
export const dynamicContentSchema = z.object({
component: z.enum(DynamicContentEnum.Headers.enums).nullish(),
})
export const dynamicContentRefsSchema = z.object({
component: z.enum(DynamicContentEnum.Headers.enums).nullish(),
})

View File

@@ -2,12 +2,7 @@ import { z } from "zod"
import { nullableStringValidator } from "@scandic-hotels/common/utils/zod/stringValidator"
import {
linkRefsUnionSchema,
linkUnionSchema,
transformPageLink,
transformPageLinkRef,
} from "./pageLinks"
import { linkUnionSchema, transformPageLink } from "./pageLinks"
const titleSchema = z.object({
title: nullableStringValidator,
@@ -45,28 +40,3 @@ export const linkAndTitleSchema = z.intersection(
linkConnectionSchema,
titleSchema
)
export const linkConnectionRefs = z
.object({
linkConnection: z.object({
edges: z.array(
z.object({
node: linkRefsUnionSchema,
})
),
}),
})
.transform((data) => {
if (data.linkConnection.edges.length) {
const linkNode = data.linkConnection.edges[0].node
if (linkNode) {
const link = transformPageLinkRef(linkNode)
if (link) {
return {
link,
}
}
}
}
return { link: null }
})

View File

@@ -19,11 +19,6 @@ export const accountPageSchema = z
})
.merge(pageLinkSchema)
export const accountPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.AccountPage),
system: systemSchema.nullable(),
})
export const extendedPageLinkSchema = pageLinkSchema.merge(
z.object({
web: z
@@ -40,121 +35,66 @@ export const campaignOverviewPageSchema = z
})
.merge(extendedPageLinkSchema)
export const campaignOverviewPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.CampaignOverviewPage),
system: systemSchema.nullable(),
})
export const collectionPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.CollectionPage),
})
.merge(extendedPageLinkSchema)
export const collectionPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.CollectionPage),
system: systemSchema.nullable(),
})
export const contentPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.ContentPage),
})
.merge(extendedPageLinkSchema)
export const contentPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.ContentPage),
system: systemSchema.nullable(),
})
export const destinationCityPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.DestinationCityPage),
})
.merge(pageLinkSchema)
export const destinationCityPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.DestinationCityPage),
system: systemSchema.nullable(),
})
export const campaignPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.CampaignPage),
})
.merge(pageLinkSchema)
export const campaignPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.CampaignPage),
system: systemSchema.nullable(),
})
export const destinationCountryPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.DestinationCountryPage),
})
.merge(pageLinkSchema)
export const destinationCountryPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.DestinationCountryPage),
system: systemSchema.nullable(),
})
export const destinationOverviewPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.DestinationOverviewPage),
})
.merge(pageLinkSchema)
export const destinationOverviewPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.DestinationOverviewPage),
system: systemSchema.nullable(),
})
export const hotelPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.HotelPage),
})
.merge(pageLinkSchema)
export const hotelPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.HotelPage),
system: systemSchema.nullable(),
})
export const loyaltyPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.LoyaltyPage),
})
.merge(extendedPageLinkSchema)
export const loyaltyPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.LoyaltyPage),
system: systemSchema.nullable(),
})
export const startPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.StartPage),
})
.merge(pageLinkSchema)
export const startPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.StartPage),
system: systemSchema.nullable(),
})
export const promoCampaignPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.PromoCampaignPage),
})
.merge(pageLinkSchema)
export const promoCampaignPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.PromoCampaignPage),
system: systemSchema.nullable(),
})
export const rawLinkUnionSchema = z.discriminatedUnion("__typename", [
accountPageSchema,
campaignOverviewPageSchema,
@@ -263,55 +203,3 @@ export const internalOrExternalLinkSchema = z
}
}
)
export const rawLinkRefsUnionSchema = z.discriminatedUnion("__typename", [
accountPageRefSchema,
campaignOverviewPageRefSchema,
campaignPageRefSchema,
collectionPageRefSchema,
contentPageRefSchema,
destinationCityPageRefSchema,
destinationCountryPageRefSchema,
destinationOverviewPageRefSchema,
hotelPageRefSchema,
loyaltyPageRefSchema,
startPageRefSchema,
promoCampaignPageRefSchema,
])
export const linkRefsUnionSchema = safeUnion(rawLinkRefsUnionSchema)
type RefData =
| z.output<typeof accountPageRefSchema>
| z.output<typeof campaignOverviewPageRefSchema>
| z.output<typeof campaignPageRefSchema>
| z.output<typeof collectionPageRefSchema>
| z.output<typeof contentPageRefSchema>
| z.output<typeof destinationCityPageRefSchema>
| z.output<typeof destinationCountryPageRefSchema>
| z.output<typeof destinationOverviewPageRefSchema>
| z.output<typeof hotelPageRefSchema>
| z.output<typeof loyaltyPageRefSchema>
| z.output<typeof startPageRefSchema>
| z.output<typeof promoCampaignPageRefSchema>
| Object
export function transformPageLinkRef(data: RefData) {
if (data && "__typename" in data) {
switch (data.__typename) {
case ContentEnum.blocks.AccountPage:
case ContentEnum.blocks.CampaignOverviewPage:
case ContentEnum.blocks.CampaignPage:
case ContentEnum.blocks.CollectionPage:
case ContentEnum.blocks.ContentPage:
case ContentEnum.blocks.DestinationCityPage:
case ContentEnum.blocks.DestinationCountryPage:
case ContentEnum.blocks.DestinationOverviewPage:
case ContentEnum.blocks.HotelPage:
case ContentEnum.blocks.LoyaltyPage:
case ContentEnum.blocks.StartPage:
case ContentEnum.blocks.PromoCampaignPage:
return data.system
}
}
}

View File

@@ -1,17 +1,9 @@
import { z } from "zod"
import { ContentEnum } from "../../../../types/content"
import { SidebarEnums } from "../../../../types/sidebar"
import {
imageContainerRefsSchema,
imageContainerSchema,
} from "../blocks/imageContainer"
import { sysAssetRefsSchema, sysAssetSchema } from "../blocks/sysAsset"
import {
rawLinkRefsUnionSchema,
rawLinkUnionSchema,
transformPageLink,
} from "../pageLinks"
import { imageContainerSchema } from "../blocks/imageContainer"
import { sysAssetSchema } from "../blocks/sysAsset"
import { rawLinkUnionSchema, transformPageLink } from "../pageLinks"
export const contentSchema = z.object({
typename: z
@@ -50,40 +42,3 @@ export const contentSchema = z.object({
}
}),
})
const actualRefs = z.discriminatedUnion("__typename", [
imageContainerRefsSchema,
...rawLinkRefsUnionSchema.options,
])
export const contentRefsSchema = z.object({
content: z
.object({
content: z.object({
embedded_itemsConnection: z.object({
edges: z.array(
z.object({
node: z.discriminatedUnion("__typename", [
sysAssetRefsSchema,
...actualRefs.options,
]),
})
),
}),
}),
})
.transform((data) => {
return data?.content?.embedded_itemsConnection.edges
.map(({ node }) => {
switch (node.__typename) {
case ContentEnum.blocks.SysAsset:
return node.system && (node.permanent_url || node.url)
? { system: node.system, url: node.permanent_url || node.url }
: null
default:
return node.system
}
})
.filter((node) => !!node)
}),
})

View File

@@ -3,7 +3,6 @@ import { z } from "zod"
import { JoinLoyaltyContactEnums } from "../../../../types/joinLoyaltyContact"
import { SidebarEnums } from "../../../../types/sidebar"
import { buttonSchema } from "../blocks/utils/buttonLinkSchema"
import { linkConnectionRefsSchema } from "../blocks/utils/linkConnection"
export const contactSchema = z.object({
contact: z.array(
@@ -49,9 +48,3 @@ export const joinLoyaltyContactSchema = z.object({
})
.merge(contactSchema),
})
export const joinLoyaltyContactRefsSchema = z.object({
join_loyalty_contact: z.object({
button: linkConnectionRefsSchema,
}),
})

View File

@@ -1,7 +1,7 @@
import { z } from "zod"
import { SidebarEnums } from "../../../../types/sidebar"
import { shortcutsBlockSchema, shortcutsRefsSchema } from "../blocks/shortcuts"
import { shortcutsBlockSchema } from "../blocks/shortcuts"
export const quickLinksSchema = z
.object({
@@ -11,5 +11,3 @@ export const quickLinksSchema = z
.default(SidebarEnums.blocks.QuickLinks),
})
.merge(shortcutsBlockSchema)
export const quickLinksRefschema = shortcutsRefsSchema

View File

@@ -4,9 +4,7 @@ import { scriptedCardThemeEnum } from "../../../../enums/scriptedCard"
import { SidebarEnums } from "../../../../types/sidebar"
import {
getInfoCardThemeFromDeprecatedCardTheme,
infoCardBlockRefsSchema,
infoCardBlockSchema,
transformCardBlockRefs,
transformInfoCardBlock,
} from "../blocks/cardsGrid"
@@ -41,25 +39,3 @@ export const scriptedCardsSchema = z.object({
}
}),
})
export const scriptedCardRefschema = z.object({
scripted_card: z
.object({
scripted_cardConnection: z.object({
edges: z.array(
z.object({
node: infoCardBlockRefsSchema,
})
),
}),
})
.transform((data) => {
let card = null
if (data.scripted_cardConnection.edges.length) {
card = transformCardBlockRefs(
data.scripted_cardConnection.edges[0].node
)
}
return card
}),
})

View File

@@ -2,11 +2,9 @@ import { z } from "zod"
import { SidebarEnums } from "../../../../types/sidebar"
import {
teaserCardBlockRefsSchema,
teaserCardBlockSchema,
transformTeaserCardBlock,
} from "../blocks/cards/teaserCard"
import { transformCardBlockRefs } from "../blocks/cardsGrid"
export const teaserCardsSchema = z.object({
typename: z
@@ -36,23 +34,3 @@ export const teaserCardsSchema = z.object({
}
}),
})
export const teaserCardRefschema = z.object({
teaser_card: z
.object({
teaser_cardConnection: z.object({
edges: z.array(
z.object({
node: teaserCardBlockRefsSchema,
})
),
}),
})
.transform((data) => {
let card = null
if (data.teaser_cardConnection.edges.length) {
card = transformCardBlockRefs(data.teaser_cardConnection.edges[0].node)
}
return card
}),
})

View File

@@ -3,26 +3,11 @@ import { z } from "zod"
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
cardGridRefsSchema,
cardsGridSchema,
} from "../schemas/blocks/cardsGrid"
import {
carouselCardsRefsSchema,
carouselCardsSchema,
} from "../schemas/blocks/carouselCards"
import {
fullWidthCampaignBlockRefsSchema,
fullWidthCampaignBlockSchema,
} from "../schemas/blocks/fullWidthCampaign"
import {
joinScandicFriendsBlockRefsSchema,
joinScandicFriendsBlockSchema,
} from "../schemas/blocks/joinScandicFriends"
import {
videoCardRefsSchema,
videoCardSchema,
} from "../schemas/blocks/videoCard"
import { cardsGridSchema } from "../schemas/blocks/cardsGrid"
import { carouselCardsSchema } from "../schemas/blocks/carouselCards"
import { fullWidthCampaignBlockSchema } from "../schemas/blocks/fullWidthCampaign"
import { joinScandicFriendsBlockSchema } from "../schemas/blocks/joinScandicFriends"
import { videoCardSchema } from "../schemas/blocks/videoCard"
import { systemSchema } from "../schemas/system"
import { StartPageEnum } from "./utils"
@@ -85,49 +70,3 @@ export const startPageSchema = z.object({
url: z.string(),
}),
})
/** REFS */
const startPageCardsRefs = z
.object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.CardsGrid),
})
.merge(cardGridRefsSchema)
const startPageFullWidthCampaignRef = z
.object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.FullWidthCampaign),
})
.merge(fullWidthCampaignBlockRefsSchema)
const startPageCarouselCardsRef = z
.object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.CarouselCards),
})
.merge(carouselCardsRefsSchema)
const startPageJoinScandicFriendsRef = z
.object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.JoinScandicFriends),
})
.merge(joinScandicFriendsBlockRefsSchema)
const startPageVideoCardRef = z
.object({
__typename: z.literal(StartPageEnum.ContentStack.blocks.VideoCard),
})
.merge(videoCardRefsSchema)
const startPageBlockRefsItem = z.discriminatedUnion("__typename", [
startPageCardsRefs,
startPageFullWidthCampaignRef,
startPageCarouselCardsRef,
startPageJoinScandicFriendsRef,
startPageVideoCardRef,
])
export const startPageRefsSchema = z.object({
start_page: z.object({
blocks: discriminatedUnionArray(startPageBlockRefsItem.options).nullable(),
system: systemSchema,
}),
})

View File

@@ -2,20 +2,11 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "../../.."
import { notFoundError } from "../../../errors"
import {
GetStartPage,
GetStartPageRefs,
} from "../../../graphql/Query/StartPage/StartPage.graphql"
import { GetStartPage } from "../../../graphql/Query/StartPage/StartPage.graphql"
import { request } from "../../../graphql/request"
import { contentstackExtendedProcedureUID } from "../../../procedures"
import {
generateRefsResponseTag,
generateTag,
generateTagsFromAssetSystem,
generateTagsFromSystem,
} from "../../../utils/generateTag"
import { startPageRefsSchema, startPageSchema } from "./output"
import { getConnections } from "./utils"
import { generateTag } from "../../../utils/generateTag"
import { startPageSchema } from "./output"
import type { z } from "zod"
@@ -25,66 +16,19 @@ import type { blocksSchema } from "./output"
export interface GetStartPageData extends z.input<typeof startPageSchema> {}
export interface StartPage extends z.output<typeof startPageSchema> {}
export interface GetStartPageRefsSchema extends z.input<
typeof startPageRefsSchema
> {}
export type Block = z.output<typeof blocksSchema>
export const startPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
const getStartPageRefsCounter = createCounter(
"trpc.contentstack.startPage.get.refs"
)
const metricsGetStartPageRefs = getStartPageRefsCounter.init({ lang, uid })
metricsGetStartPageRefs.start()
const refsResponse = await request<GetStartPageRefsSchema>(
GetStartPageRefs,
{
locale: lang,
uid,
},
{
key: generateRefsResponseTag(lang, uid),
ttl: "max",
}
)
if (!refsResponse.data) {
metricsGetStartPageRefs.noDataError()
throw notFoundError({
message: "StartPage refs returned no data",
errorDetails: { lang, uid },
})
}
const validatedRefsData = startPageRefsSchema.safeParse(refsResponse.data)
if (!validatedRefsData.success) {
metricsGetStartPageRefs.validationError(validatedRefsData.error)
return null
}
metricsGetStartPageRefs.success()
const cacheKey = generateTag(lang, uid)
const getStartPageCounter = createCounter("trpc.contentstack.startPage.get")
const metricsGetStartPage = getStartPageCounter.init({ lang, uid })
metricsGetStartPage.start()
const { connections, assetConnections } = getConnections(
validatedRefsData.data
)
const tags = [
generateTagsFromSystem(lang, connections),
generateTagsFromAssetSystem(assetConnections),
generateTag(lang, validatedRefsData.data.start_page.system.uid),
].flat()
const response = await request<GetStartPageData>(
GetStartPage,
{
@@ -92,7 +36,7 @@ export const startPageQueryRouter = router({
uid,
},
{
key: tags,
key: `${cacheKey}`,
ttl: "max",
}
)

View File

@@ -1,8 +1,3 @@
import type { z } from "zod"
import type { AssetSystem, System } from "../schemas/system"
import type { startPageRefsSchema } from "./output"
export namespace StartPageEnum {
export namespace ContentStack {
export const enum blocks {
@@ -14,60 +9,3 @@ export namespace StartPageEnum {
}
}
}
export interface StartPageRefs extends z.output<typeof startPageRefsSchema> {}
export function getConnections({ start_page }: StartPageRefs) {
const connections: System["system"][] = [start_page.system]
const assetConnections: AssetSystem[] = []
if (start_page.blocks) {
start_page.blocks.forEach((block) => {
const typeName = block.__typename
switch (typeName) {
case StartPageEnum.ContentStack.blocks.FullWidthCampaign:
block.full_width_campaign.full_width_campaignConnection.edges.forEach(
({ node }) => {
if (node.system) {
connections.push(node.system)
}
}
)
break
case StartPageEnum.ContentStack.blocks.CardsGrid:
block.cards_grid.forEach((card) => {
connections.push(card)
})
break
case StartPageEnum.ContentStack.blocks.CarouselCards:
block.carousel_cards.card_groups.forEach((group) => {
group.cardConnection.edges.forEach((node) => {
connections.push(node.node.system)
})
})
break
case StartPageEnum.ContentStack.blocks.JoinScandicFriends:
if (block.join_scandic_friends.primary_button) {
connections.push(block.join_scandic_friends.primary_button)
}
break
case StartPageEnum.ContentStack.blocks.VideoCard: {
if (block.video_card?.system) {
connections.push(block.video_card.system)
}
if (block.video_card?.video.sourceConnection.edges[0]) {
assetConnections.push(
block.video_card.video.sourceConnection.edges[0].node
)
}
break
}
default:
const _exhaustiveCheck: never = typeName
break
}
})
}
return { connections, assetConnections }
}