Merged in feat/sw-2863-move-contentstack-router-to-trpc-package (pull request #2389)

feat(SW-2863): Move contentstack router to trpc package

* Add exports to packages and lint rule to prevent relative imports

* Add env to trpc package

* Add eslint to trpc package

* Apply lint rules

* Use direct imports from trpc package

* Add lint-staged config to trpc

* Move lang enum to common

* Restructure trpc package folder structure

* WIP first step

* update internal imports in trpc

* Fix most errors in scandic-web

Just 100 left...

* Move Props type out of trpc

* Fix CategorizedFilters types

* Move more schemas in hotel router

* Fix deps

* fix getNonContentstackUrls

* Fix import error

* Fix entry error handling

* Fix generateMetadata metrics

* Fix alertType enum

* Fix duplicated types

* lint:fix

* Merge branch 'master' into feat/sw-2863-move-contentstack-router-to-trpc-package

* Fix broken imports

* Merge branch 'master' into feat/sw-2863-move-contentstack-router-to-trpc-package


Approved-by: Linus Flood
This commit is contained in:
Anton Gunnarsson
2025-06-26 07:53:01 +00:00
parent 0263ab8c87
commit 002d093af4
921 changed files with 3112 additions and 3008 deletions
@@ -0,0 +1,6 @@
import { mergeRouters } from "../../.."
import { campaignOverviewPageQueryRouter } from "./query"
export const campaignOverviewPageRouter = mergeRouters(
campaignOverviewPageQueryRouter
)
@@ -0,0 +1,115 @@
import { z } from "zod"
import { CampaignPageEnum } from "../../../types/campaignPage"
import { discriminatedUnionArray } from "../../../utils/discriminatedUnion"
import {
campaignPageHotelListing,
heroSchema,
includedHotelsSchema,
} from "../campaignPage/output"
import {
linkAndTitleSchema,
linkConnectionRefs,
} from "../schemas/linkConnection"
import { systemSchema } from "../schemas/system"
const navigationLinksSchema = z
.array(linkAndTitleSchema)
.nullable()
.transform((data) => {
if (!data) {
return null
}
return data
.filter((item) => !!item.link)
.map((item) => ({
url: item.link!.url,
title: item.title || item.link!.title,
}))
})
export const blocksSchema = z.discriminatedUnion("__typename", [
campaignPageHotelListing,
])
const topCampaignSchema = z
.object({
heading: z.string(),
hero: heroSchema,
included_hotels: includedHotelsSchema,
blocks: discriminatedUnionArray(blocksSchema.options),
url: z.string(),
})
.transform((data) => {
const { blocks, included_hotels, ...rest } = data
const hotelListingBlock = blocks.find(
(block) =>
block.__typename === CampaignPageEnum.ContentStack.blocks.HotelListing
)
return {
...rest,
hotel_listing: {
heading: hotelListingBlock?.hotel_listing.heading || "",
hotelIds: included_hotels,
},
}
})
export const campaignOverviewPageSchema = z.object({
campaign_overview_page: z
.object({
title: z.string(),
header: z.object({
heading: z.string(),
preamble: z.string(),
navigation_links: navigationLinksSchema,
}),
top_campaignConnection: z.object({
edges: z.array(
z.object({
node: topCampaignSchema,
})
),
}),
system: systemSchema.merge(
z.object({
created_at: z.string(),
updated_at: z.string(),
})
),
})
.transform((data) => {
const { top_campaignConnection, ...rest } = data
return {
...rest,
topCampaign: top_campaignConnection.edges.map(({ node }) => node)[0],
}
}),
trackingProps: z.object({
url: z.string(),
}),
})
/** REFS */
const campaignOverviewPageHeaderRefs = z.object({
navigation_links: z.array(linkConnectionRefs),
})
export const campaignOverviewPageRefsSchema = z.object({
campaign_overview_page: z.object({
header: campaignOverviewPageHeaderRefs,
top_campaignConnection: z.object({
edges: z.array(
z.object({
node: z.object({
system: systemSchema,
}),
})
),
}),
system: systemSchema,
}),
})
@@ -0,0 +1,130 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { request } from "@scandic-hotels/trpc/graphql/request"
import { contentStackUidWithServiceProcedure } from "@scandic-hotels/trpc/procedures"
import { generateRefsResponseTag } from "@scandic-hotels/trpc/utils/generateTag"
import {
GetCampaignOverviewPage,
GetCampaignOverviewPageRefs,
} from "../../../graphql/Query/CampaignOverviewPage/CampaignOverviewPage.graphql"
import {
campaignOverviewPageRefsSchema,
campaignOverviewPageSchema,
} from "./output"
import { generatePageTags } from "./utils"
import type {
GetCampaignOverviewPageData,
GetCampaignOverviewPageRefsData,
} 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 refsResponse = await request<GetCampaignOverviewPageRefsData>(
GetCampaignOverviewPageRefs,
{ locale: lang, uid },
{
key: generateRefsResponseTag(lang, uid),
ttl: "max",
}
)
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
metricsGetCampaignOverviewPageRefs.noDataError()
throw notFoundError
}
const validatedRefsData = campaignOverviewPageRefsSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
metricsGetCampaignOverviewPageRefs.validationError(
validatedRefsData.error
)
return null
}
metricsGetCampaignOverviewPageRefs.success()
const tags = generatePageTags(validatedRefsData.data, lang)
const getCampaignOverviewPageCounter = createCounter(
"trpc.contentstack",
"campaignOverviewPage.get"
)
const metricsGetCampaignOverviewPage = getCampaignOverviewPageCounter.init({
lang,
uid,
})
metricsGetCampaignOverviewPage.start()
const response = await request<GetCampaignOverviewPageData>(
GetCampaignOverviewPage,
{
locale: lang,
uid,
},
{
key: tags,
ttl: "max",
}
)
if (!response.data) {
const notFoundError = notFound(response)
metricsGetCampaignOverviewPage.noDataError()
throw notFoundError
}
const validatedResponse = campaignOverviewPageSchema.safeParse(
response.data
)
if (!validatedResponse.success) {
metricsGetCampaignOverviewPage.validationError(validatedResponse.error)
return null
}
const campaignOverviewPage = validatedResponse.data.campaign_overview_page
metricsGetCampaignOverviewPage.success()
const system = campaignOverviewPage.system
const pageName = `campaign-overview-page`
const tracking: TrackingPageData = {
pageId: system.uid,
domainLanguage: system.locale,
publishDate: system.updated_at,
createDate: system.created_at,
channel: "campaign-overview-page",
pageType: "campaign-overview-page",
pageName,
siteSections: pageName,
siteVersion: "new-web",
}
return {
campaignOverviewPage,
tracking,
}
}),
})
@@ -0,0 +1,41 @@
import {
generateTag,
generateTagsFromSystem,
} from "@scandic-hotels/trpc/utils/generateTag"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { System } from "@scandic-hotels/trpc/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_campaignConnection) {
campaign_overview_page.top_campaignConnection.edges.forEach(({ node }) => {
connections.push(node.system)
})
}
return connections
}