feat(SW-187): Footer data from contentstack

This commit is contained in:
Pontus Dreij
2024-08-28 08:30:45 +02:00
parent 2fcbaa62e4
commit da51bd88fe
14 changed files with 370 additions and 21 deletions

View File

@@ -1,6 +1,7 @@
import { z } from "zod"
import { Image } from "@/types/image"
import { PageLinkEnum } from "@/types/requests/pageLinks"
// Help me write this zod schema based on the type ContactConfig
export const validateContactConfigSchema = z.object({
@@ -165,7 +166,7 @@ const validateNavigationItem = z.object({
export type NavigationItem = z.infer<typeof validateNavigationItem>
export const validateFooterConfigSchema = z.object({
export const validateCurrentFooterConfigSchema = z.object({
all_current_footer: z.object({
items: z.array(
z.object({
@@ -232,16 +233,18 @@ export const validateFooterConfigSchema = z.object({
}),
})
export type FooterDataRaw = z.infer<typeof validateFooterConfigSchema>
export type CurrentFooterDataRaw = z.infer<
typeof validateCurrentFooterConfigSchema
>
export type FooterData = Omit<
FooterDataRaw["all_current_footer"]["items"][0],
export type CurrentFooterData = Omit<
CurrentFooterDataRaw["all_current_footer"]["items"][0],
"logoConnection"
> & {
logo: Image
}
const validateFooterRefConfigSchema = z.object({
const validateCurrentFooterRefConfigSchema = z.object({
all_current_footer: z.object({
items: z.array(
z.object({
@@ -254,4 +257,106 @@ const validateFooterRefConfigSchema = z.object({
}),
})
export type CurrentFooterRefDataRaw = z.infer<
typeof validateCurrentFooterRefConfigSchema
>
const validateExternalLink = z
.object({
href: z.string(),
title: z.string(),
})
.optional()
const validateInternalLink = z
.object({
edges: z.array(
z.object({
node: z.object({
title: z.string(),
url: z.string(),
}),
})
),
})
.optional()
const validateLinkItem = z.object({
title: z.string(),
open_in_new_tab: z.boolean(),
link: validateExternalLink,
pageConnection: validateInternalLink,
})
export type FooterLinkItem = z.infer<typeof validateLinkItem>
export const validateFooterConfigSchema = z.object({
all_footer: z.object({
items: z.array(
z.object({
main_links: z.array(validateLinkItem),
app_downloads: z.object({
title: z.string(),
links: z.array(
z.object({
type: z.string(),
href: validateExternalLink,
})
),
}),
secondary_links: z.array(
z.object({
title: z.string(),
links: z.array(validateLinkItem),
})
),
})
),
}),
})
export type FooterDataRaw = z.infer<typeof validateFooterConfigSchema>
export type FooterData = FooterDataRaw["all_footer"]["items"][0]
const pageConnectionRefs = z.object({
edges: z.array(
z.object({
node: z.object({
__typename: z.nativeEnum(PageLinkEnum),
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
}),
})
),
})
const validateFooterRefConfigSchema = z.object({
all_footer: z.object({
items: z.array(
z.object({
main_links: z.array(
z.object({
pageConnection: pageConnectionRefs,
})
),
secondary_links: z.array(
z.object({
links: z.array(
z.object({
pageConnection: pageConnectionRefs,
})
),
})
),
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
})
),
}),
})
export type FooterRefDataRaw = z.infer<typeof validateFooterRefConfigSchema>

View File

@@ -9,6 +9,7 @@ import {
GetCurrentHeader,
GetCurrentHeaderRef,
} from "@/lib/graphql/Query/CurrentHeader.graphql"
import { GetFooter, GetFooterRef } from "@/lib/graphql/Query/Footer.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { contentstackBaseProcedure, router } from "@/server/trpc"
@@ -18,15 +19,19 @@ import { generateRefsResponseTag, generateTag } from "@/utils/generateTag"
import { langInput } from "./input"
import {
type ContactConfigData,
CurrentFooterDataRaw,
CurrentFooterRefDataRaw,
FooterDataRaw,
FooterRefDataRaw,
HeaderData,
HeaderDataRaw,
HeaderRefDataRaw,
validateContactConfigSchema,
validateCurrentFooterConfigSchema,
validateFooterConfigSchema,
validateHeaderConfigSchema,
} from "./output"
import { transformPageConnectionLinks } from "./utils"
const meter = metrics.getMeter("trpc.contentstack.base")
// OpenTelemetry metrics: ContactConfig
@@ -236,6 +241,87 @@ export const baseQueryRouter = router({
logo,
} as HeaderData
}),
currentFooter: contentstackBaseProcedure
.input(langInput)
.query(async ({ input }) => {
getFooterRefCounter.add(1, { lang: input.lang })
console.info(
"contentstack.footer.ref start",
JSON.stringify({ query: { lang: input.lang } })
)
const responseRef = await request<CurrentFooterRefDataRaw>(
GetCurrentFooterRef,
{
locale: input.lang,
}
)
// There's currently no error handling/validation for the responseRef, should it be added?
getFooterCounter.add(1, { lang: input.lang })
console.info(
"contentstack.footer start",
JSON.stringify({
query: {
lang: input.lang,
},
})
)
const response = await request<CurrentFooterDataRaw>(
GetCurrentFooter,
{
locale: input.lang,
},
{
next: {
tags: [generateRefsResponseTag(input.lang, "current_footer")],
},
}
)
if (!response.data) {
const notFoundError = notFound(response)
getFooterFailCounter.add(1, {
lang: input.lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.footer not found error",
JSON.stringify({
query: {
lang: input.lang,
},
error: { code: notFoundError.code },
})
)
throw notFoundError
}
const validatedFooterConfig = validateCurrentFooterConfigSchema.safeParse(
response.data
)
if (!validatedFooterConfig.success) {
getFooterFailCounter.add(1, {
lang: input.lang,
error_type: "validation_error",
error: JSON.stringify(validatedFooterConfig.error),
})
console.error(
"contentstack.footer validation error",
JSON.stringify({
query: { lang: input.lang },
error: validatedFooterConfig.error,
})
)
return null
}
getFooterSuccessCounter.add(1, { lang: input.lang })
console.info(
"contentstack.footer success",
JSON.stringify({ query: { lang: input.lang } })
)
return validatedFooterConfig.data.all_current_footer.items[0]
}),
footer: contentstackBaseProcedure
.input(langInput)
.query(async ({ input }) => {
@@ -252,7 +338,7 @@ export const baseQueryRouter = router({
{
cache: "force-cache",
next: {
tags: [generateRefsResponseTag(input.lang, "current_footer")],
tags: [generateRefsResponseTag(input.lang, "footer")],
},
}
)
@@ -266,10 +352,9 @@ export const baseQueryRouter = router({
},
})
)
const currentFooterUID =
responseRef.data.all_current_footer.items[0].system.uid
const currentFooterUID = responseRef.data.all_footer.items[0].system.uid
const response = await request<FooterDataRaw>(
GetCurrentFooter,
GetFooter,
{
locale: input.lang,
},
@@ -324,6 +409,15 @@ export const baseQueryRouter = router({
"contentstack.footer success",
JSON.stringify({ query: { lang: input.lang } })
)
return validatedFooterConfig.data.all_current_footer.items[0]
const validatedFooterData = validatedFooterConfig.data.all_footer.items[0]
const mainLinks = transformPageConnectionLinks(
validatedFooterData.main_links
)
return {
mainLinks: mainLinks,
appDownloads: validatedFooterData.app_downloads,
secondaryLinks: validatedFooterData.secondary_links,
}
}),
})

View File

@@ -0,0 +1,10 @@
import type { FooterLinkItem } from "./output"
export function transformPageConnectionLinks(links: FooterLinkItem[]) {
return links.flatMap((link) =>
link.pageConnection?.edges.map((edge) => ({
title: edge.node.title,
url: edge.node.url,
}))
)
}