feat: revalidate my pages breadcrumbs on demand

This commit is contained in:
Simon Emanuelsson
2024-04-16 12:42:44 +02:00
committed by Michael Zetterberg
parent ba13a00b63
commit 0c4aa592cc
24 changed files with 322 additions and 57 deletions

View File

@@ -1,7 +1,7 @@
import { TRPCError } from "@trpc/server"
import {
TRPC_ERROR_CODES_BY_NUMBER,
TRPC_ERROR_CODES_BY_KEY,
TRPC_ERROR_CODES_BY_NUMBER,
} from "@trpc/server/rpc"
export function unauthorizedError() {

View File

@@ -1,10 +1,8 @@
import { z } from "zod";
import { z } from "zod"
import { Lang } from "@/constants/languages";
const langs = Object.keys(Lang) as [keyof typeof Lang]
import { Lang } from "@/constants/languages"
export const getBreadcrumbsInput = z.object({
href: z.string().min(1, { message: "href is required" }),
locale: z.enum(langs),
locale: z.nativeEnum(Lang),
})

View File

@@ -1,12 +1,40 @@
import { Lang } from "@/constants/languages"
import { z } from "zod"
export const validateBreadcrumbsRefsConstenstackSchema = z.object({
all_account_page: z.object({
items: z.array(
z.object({
breadcrumbs: z.object({
parentsConnection: z.object({
edges: z.array(
z.object({
node: z.object({
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
}),
})
),
}),
}),
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
})
),
}),
})
export const validateBreadcrumbsConstenstackSchema = z.object({
all_account_page: z.object({
items: z.array(
z.object({
breadcrumbs: z.object({
title: z.string(),
parents: z.object({
parentsConnection: z.object({
edges: z.array(
z.object({
node: z.object({
@@ -14,6 +42,7 @@ export const validateBreadcrumbsConstenstackSchema = z.object({
title: z.string(),
}),
system: z.object({
locale: z.nativeEnum(Lang),
uid: z.string(),
}),
url: z.string(),

View File

@@ -1,51 +1,120 @@
import { GetMyPagesBreadcrumbs } from "@/lib/graphql/Query/BreadcrumbsMyPages.graphql"
import {
GetMyPagesBreadcrumbs,
GetMyPagesBreadcrumbsRefs,
} from "@/lib/graphql/Query/BreadcrumbsMyPages.graphql"
import { request } from "@/lib/graphql/request"
import { badRequestError, internalServerError } from "@/server/errors/trpc"
import { publicProcedure, router } from "@/server/trpc"
import { getBreadcrumbsInput } from "./input"
import { validateBreadcrumbsConstenstackSchema } from "./output"
import {
generateRefsResponseTag,
generateTag,
generateTags,
} from "@/utils/generateTag"
import { removeMultipleSlashes } from "@/utils/url"
import { GetMyPagesBreadcrumbsData } from "@/types/requests/myPages/breadcrumbs"
import { getBreadcrumbsInput } from "./input"
import {
getBreadcrumbsSchema,
validateBreadcrumbsConstenstackSchema,
validateBreadcrumbsRefsConstenstackSchema,
} from "./output"
import { affix, getConnections, homeBreadcrumbs } from "./utils"
import type {
GetMyPagesBreadcrumbsData,
GetMyPagesBreadcrumbsRefsData,
} from "@/types/requests/myPages/breadcrumbs"
export const breadcrumbsQueryRouter = router({
get: publicProcedure.input(getBreadcrumbsInput).query(async ({ input }) => {
try {
const response = await request<GetMyPagesBreadcrumbsData>(
GetMyPagesBreadcrumbs,
{ locale: input.locale, url: input.href }
const refsResponse = await request<GetMyPagesBreadcrumbsRefsData>(
GetMyPagesBreadcrumbsRefs,
{ locale: input.locale, url: input.href },
{
next: {
tags: [generateRefsResponseTag(input.locale, input.href, affix)],
},
}
)
if (!response.data) {
if (!refsResponse.data) {
console.error("Bad response for `GetMyPagesBreadcrumbsRefs`")
console.error({ refsResponse })
throw internalServerError()
}
const validatedRefsData =
validateBreadcrumbsRefsConstenstackSchema.safeParse(refsResponse.data)
if (!validatedRefsData.success) {
console.info("Bad validation for `GetMyPagesBreadcrumbsRefs`")
console.error(validatedRefsData.error)
throw badRequestError()
}
const validatedBreadcrumbs =
const connections = getConnections(validatedRefsData.data)
const tags = generateTags(input.locale, connections)
const page = validatedRefsData.data.all_account_page.items[0]
tags.push(generateTag(input.locale, page.system.uid, affix))
const response = await request<GetMyPagesBreadcrumbsData>(
GetMyPagesBreadcrumbs,
{ locale: input.locale, url: input.href },
{ next: { tags } }
)
if (!response.data) {
console.error("Bad response for `GetMyPagesBreadcrumbs`")
console.error({ input })
console.error({ response })
throw internalServerError()
}
const validatedBreadcrumbsData =
validateBreadcrumbsConstenstackSchema.safeParse(response.data)
if (!validatedBreadcrumbs.success) {
if (!validatedBreadcrumbsData.success) {
console.error("Bad validation for `GetMyPagesBreadcrumbs`")
console.error(validatedBreadcrumbsData.error)
throw badRequestError()
}
const parentBreadcrumbs =
validatedBreadcrumbs.data.all_account_page.items[0].breadcrumbs.parents.edges.map(
validatedBreadcrumbsData.data.all_account_page.items[0].breadcrumbs.parentsConnection.edges.map(
(breadcrumb) => {
return {
href: breadcrumb.node.url,
href: removeMultipleSlashes(
`/${breadcrumb.node.system.locale}/${breadcrumb.node.url}`
),
title: breadcrumb.node.breadcrumbs.title,
uid: breadcrumb.node.system.uid,
}
}
)
const pageBreadcrumb =
validatedBreadcrumbs.data.all_account_page.items.map((breadcrumb) => {
return {
href: "",
title: breadcrumb.breadcrumbs.title,
uid: breadcrumb.system.uid,
}
})
const breadcrumbs = [parentBreadcrumbs, pageBreadcrumb].flat()
return breadcrumbs
const pageBreadcrumb =
validatedBreadcrumbsData.data.all_account_page.items.map(
(breadcrumb) => {
return {
title: breadcrumb.breadcrumbs.title,
uid: breadcrumb.system.uid,
}
}
)
const breadcrumbs = [
homeBreadcrumbs[input.locale],
parentBreadcrumbs,
pageBreadcrumb,
].flat()
const validatedBreadcrumbs = getBreadcrumbsSchema.safeParse(breadcrumbs)
if (!validatedBreadcrumbs.success) {
console.info("Bad validation for `validatedBreadcrumbs`")
console.error(validatedBreadcrumbs.error)
throw badRequestError()
}
return validatedBreadcrumbs.data
} catch (error) {
console.info(`Get My Pages Breadcrumbs Error`)
console.error(error)

View File

@@ -0,0 +1,50 @@
import { Lang } from "@/constants/languages"
import type { GetMyPagesBreadcrumbsRefsData } from "@/types/requests/myPages/breadcrumbs"
import type { Edges } from "@/types/requests/utils/edges"
import type { NodeRefs } from "@/types/requests/utils/refs"
export function getConnections(refs: GetMyPagesBreadcrumbsRefsData) {
const connections: Edges<NodeRefs>[] = []
refs.all_account_page.items.forEach((ref) => {
connections.push(ref.breadcrumbs.parentsConnection)
})
return connections
}
export const affix = "breadcrumbs"
// TODO: Make these editable in CMS?
export const homeBreadcrumbs = {
[Lang.da]: {
href: "/da",
title: "Hjem",
uid: "da",
},
[Lang.de]: {
href: "/de",
title: "Heim",
uid: "de",
},
[Lang.en]: {
href: "/en",
title: "Home",
uid: "en",
},
[Lang.fi]: {
href: "/fi",
title: "Koti",
uid: "fi",
},
[Lang.no]: {
href: "/no",
title: "Hjem",
uid: "no",
},
[Lang.sv]: {
href: "/sv",
title: "Hem",
uid: "sv",
},
}

View File

@@ -11,6 +11,7 @@ import {
generateTag,
generateTags,
} from "@/utils/generateTag"
import { removeMultipleSlashes } from "@/utils/url"
import { getNavigationInputSchema } from "./input"
import {
@@ -35,7 +36,7 @@ export function mapMenuItems(navigationItems: NavigationItem[]) {
lang: node.system.locale,
linkText: item.link_text || node.title,
uid: node.system.uid,
url: `/${node.system.locale}/${node.url}`.replaceAll(/\/\/+/g, "/"),
url: removeMultipleSlashes(`/${node.system.locale}/${node.url}`),
}
if ("sub_items" in item) {
@@ -54,12 +55,16 @@ export const navigationQueryRouter = router({
const refsResponse = await request<GetNavigationMyPagesRefsData>(
GetNavigationMyPagesRefs,
{ locale: lang },
{ tags: [generateRefsResponseTag(lang, "navigation_my_pages")] }
{
next: {
tags: [generateRefsResponseTag(lang, "navigation_my_pages")],
},
}
)
if (!refsResponse.data) {
console.error("Bad response for `GetNavigationMyPagesRefs`")
console.error(refsResponse)
console.error({ refsResponse })
throw internalServerError()
}
@@ -80,12 +85,13 @@ export const navigationQueryRouter = router({
const response = await request<GetNavigationMyPagesData>(
GetNavigationMyPages,
{ locale: lang },
{ tags }
{ next: { tags } }
)
if (!response.data) {
console.error("Bad response for `GetNavigationMyPages`")
console.error(response)
console.error({ input: lang })
console.error({ response })
throw internalServerError()
}

View File

@@ -31,7 +31,6 @@ export const userQueryRouter = router({
get: protectedProcedure.query(async function ({ ctx }) {
try {
const apiResponse = await api.get(api.endpoints.v0.profile, {
cache: "no-store",
headers: {
Authorization: `Bearer ${ctx.session.token.access_token}`,
},