feat: add revlidation for loyalty page

This commit is contained in:
Christel Westerberg
2024-05-15 09:41:19 +02:00
parent a734ba848a
commit d651ea526c
8 changed files with 368 additions and 13 deletions

View File

@@ -3,6 +3,11 @@
#import "../Fragments/PageLink/ContentPageLink.graphql"
#import "../Fragments/PageLink/LoyaltyPageLink.graphql"
#import "../Fragments/Refs/AccountPage.graphql"
#import "../Fragments/Refs/ContentPage.graphql"
#import "../Fragments/Refs/LoyaltyPage.graphql"
#import "../Fragments/Refs/System.graphql"
query GetLoyaltyPage($locale: String!, $url: String!) {
all_loyalty_page(where: { url: $url }, locale: $locale) {
items {
@@ -145,3 +150,119 @@ query GetLoyaltyPage($locale: String!, $url: String!) {
}
}
}
query GetLoyaltyPageRefs($locale: String!, $url: String!) {
all_loyalty_page(where: { url: $url }, locale: $locale) {
items {
blocks {
... on LoyaltyPageBlocksShortcuts {
__typename
shortcuts {
shortcuts {
linkConnection {
edges {
node {
__typename
...AccountPageRef
...ContentPageRef
...LoyaltyPageRef
}
}
}
}
}
}
... on LoyaltyPageBlocksDynamicContent {
__typename
dynamic_content {
link {
pageConnection {
edges {
node {
__typename
...ContentPageRef
...LoyaltyPageRef
}
}
}
}
}
}
... on LoyaltyPageBlocksCardGrid {
__typename
card_grid {
cards {
referenceConnection {
edges {
node {
__typename
...AccountPageRef
...ContentPageRef
...LoyaltyPageRef
}
}
}
}
}
}
... on LoyaltyPageBlocksContent {
__typename
content {
content {
embedded_itemsConnection {
edges {
node {
# No fragments used since we want to include __typename for each type to avoid fetching SystemAsset
... on ContentPage {
__typename
system {
...System
}
}
... on LoyaltyPage {
__typename
system {
...System
}
}
}
}
}
}
}
}
}
sidebar {
... on LoyaltyPageSidebarContent {
__typename
content {
content {
embedded_itemsConnection {
edges {
node {
# No fragments used since we want to include __typename for each type to avoid fetching SystemAsset
... on ContentPage {
__typename
system {
...System
}
}
... on LoyaltyPage {
__typename
system {
...System
}
}
}
}
}
}
}
}
}
system {
...System
}
}
}
}

View File

@@ -9,6 +9,7 @@ import {
SidebarTypenameEnum,
} from "@/types/components/loyalty/enums"
import { Embeds } from "@/types/requests/embeds"
import { PageLinkEnum } from "@/types/requests/pageLinks"
import { EdgesWithTotalCount } from "@/types/requests/utils/edges"
import { RTEDocument } from "@/types/rte/node"
@@ -262,3 +263,99 @@ export type LoyaltyPage = Omit<LoyaltyPageRaw, "blocks" | "sidebar"> & {
blocks: Block[]
sidebar: Sidebar[]
}
// Refs types
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 loyaltyPageBlockCardGridRefs = z.object({
__typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardGrid),
card_grid: z.object({
cards: z.array(
z.object({
referenceConnection: pageConnectionRefs,
})
),
}),
})
const loyaltyPageDynamicContentRefs = z.object({
__typename: z.literal(
LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent
),
dynamic_content: z.object({
link: z.object({
pageConnection: pageConnectionRefs,
}),
}),
})
const loyaltyPageShortcutsRefs = z.object({
__typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts),
shortcuts: z.object({
shortcuts: z.array(
z.object({
linkConnection: pageConnectionRefs,
})
),
}),
})
const loyaltyPageBlockTextContentRefs = z.object({
__typename: z.literal(LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent),
content: z.object({
content: z.object({
embedded_itemsConnection: pageConnectionRefs,
}),
}),
})
const loyaltyPageBlocRefsItem = z.discriminatedUnion("__typename", [
loyaltyPageBlockCardGridRefs,
loyaltyPageDynamicContentRefs,
loyaltyPageBlockTextContentRefs,
loyaltyPageShortcutsRefs,
])
const loyaltyPageSidebarTextContentRef = z.object({
__typename: z.literal(SidebarTypenameEnum.LoyaltyPageSidebarContent),
content: z.object({
content: z.object({
embedded_itemsConnection: pageConnectionRefs,
}),
}),
})
const loyaltyPageSidebarRefsItem = z.discriminatedUnion("__typename", [
loyaltyPageSidebarTextContentRef,
])
export const validateLoyaltyPageRefsSchema = z.object({
all_loyalty_page: z.object({
items: z.array(
z.object({
blocks: z.array(loyaltyPageBlocRefsItem).nullable(),
sidebar: z.array(loyaltyPageSidebarRefsItem).nullable(),
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
})
),
}),
})
export type LoyaltyPageRefsDataRaw = z.infer<
typeof validateLoyaltyPageRefsSchema
>

View File

@@ -1,15 +1,28 @@
import GetLoyaltyPage from "@/lib/graphql/Query/LoyaltyPage.graphql"
import {
GetLoyaltyPage,
GetLoyaltyPageRefs,
} from "@/lib/graphql/Query/LoyaltyPage.graphql"
import { request } from "@/lib/graphql/request"
import { _ } from "@/lib/translation"
import { badRequestError } from "@/server/errors/trpc"
import { badRequestError, internalServerError } from "@/server/errors/trpc"
import { publicProcedure, router } from "@/server/trpc"
import { removeEmptyObjects } from "@/utils/contentType"
import {
generateRefsResponseTag,
generateTag,
generateTags,
} from "@/utils/generateTag"
import { getLoyaltyPageInput } from "./input"
import {
type LoyaltyPage,
type LoyaltyPageDataRaw,
type LoyaltyPageRefsDataRaw,
validateLoyaltyPageRefsSchema,
validateLoyaltyPageSchema,
} from "./output"
import { getConnections } from "./utils"
import {
LoyaltyBlocksTypenameEnum,
@@ -22,10 +35,56 @@ import { RTEDocument } from "@/types/rte/node"
export const loyaltyPageQueryRouter = router({
get: publicProcedure.input(getLoyaltyPageInput).query(async ({ input }) => {
try {
const loyaltyPageRes = await request<LoyaltyPageDataRaw>(GetLoyaltyPage, {
locale: input.locale,
url: input.href,
})
const { locale } = input
const refsResponse = await request<LoyaltyPageRefsDataRaw>(
GetLoyaltyPageRefs,
{
locale,
url: input.href,
},
{
next: {
tags: [generateRefsResponseTag(locale, "loyalty_page")],
},
}
)
if (!refsResponse.data) {
console.error("Bad response for `GetNavigationMyPagesRefs`")
console.error({ refsResponse })
throw internalServerError()
}
const cleanedData = removeEmptyObjects(refsResponse.data)
const validatedLoyaltyPageRefs =
validateLoyaltyPageRefsSchema.safeParse(cleanedData)
if (!validatedLoyaltyPageRefs.success) {
console.error("Bad validation for `GetLoyaltyPageRefs`")
console.error(validatedLoyaltyPageRefs.error)
throw badRequestError()
}
const connections = getConnections(validatedLoyaltyPageRefs.data)
const tags = generateTags(locale, connections)
tags.push(
generateTag(
locale,
validatedLoyaltyPageRefs.data.all_loyalty_page.items[0].system.uid
)
)
const loyaltyPageRes = await request<LoyaltyPageDataRaw>(
GetLoyaltyPage,
{
locale,
url: input.href,
},
{ next: { tags } }
)
if (!loyaltyPageRes.data) {
throw badRequestError()

View File

@@ -0,0 +1,54 @@
import { LoyaltyPageRefsDataRaw } from "./output"
import { LoyaltyBlocksTypenameEnum } from "@/types/components/loyalty/enums"
import type { Edges } from "@/types/requests/utils/edges"
import type { NodeRefs } from "@/types/requests/utils/refs"
export function getConnections(refs: LoyaltyPageRefsDataRaw) {
const connections: Edges<NodeRefs>[] = []
refs.all_loyalty_page.items.forEach((ref) => {
if (ref.blocks) {
ref.blocks.forEach((item) => {
switch (item.__typename) {
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent: {
if (item.content.content.embedded_itemsConnection.edges.length) {
connections.push(item.content.content.embedded_itemsConnection)
}
break
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardGrid: {
item.card_grid.cards.forEach((card) => {
if (card.referenceConnection.edges.length) {
connections.push(card.referenceConnection)
}
})
break
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts: {
item.shortcuts.shortcuts.forEach((shortcut) => {
if (shortcut.linkConnection.edges.length) {
connections.push(shortcut.linkConnection)
}
})
break
}
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent: {
if (item.dynamic_content.link.pageConnection.edges.length) {
connections.push(item.dynamic_content.link.pageConnection)
}
break
}
}
})
}
if (ref.sidebar) {
ref.sidebar?.forEach((item) => {
if (item.content.content.embedded_itemsConnection.edges.length) {
connections.push(item.content.content.embedded_itemsConnection)
}
})
}
})
return connections
}

View File

@@ -2,7 +2,7 @@ import { z } from "zod"
import { Lang } from "@/constants/languages"
import { PageLinkEnum } from "@/types/requests/myPages/navigation"
import { PageLinkEnum } from "@/types/requests/pageLinks"
const pageConnection = z.object({
edges: z.array(

View File

@@ -1,15 +1,11 @@
import { PageLinkEnum } from "../pageLinks"
import type { Lang } from "@/constants/languages"
import type { System } from "../system"
import type { AllRequestResponse } from "../utils/all"
import type { Edges } from "../utils/edges"
import type { TypenameInterface } from "../utils/typename"
export enum PageLinkEnum {
AccountPage = "AccountPage",
ContentPage = "ContentPage",
LoyaltyPage = "LoyaltyPage",
}
export type MenuItem = {
lang: Lang
linkText: string

View File

@@ -0,0 +1,5 @@
export enum PageLinkEnum {
AccountPage = "AccountPage",
ContentPage = "ContentPage",
LoyaltyPage = "LoyaltyPage",
}

View File

@@ -53,3 +53,26 @@ export async function getContentTypeByPathName(
return PageTypeEnum.CurrentBlocksPage
}
}
/**
* Function to remove empty objects from a fetched content type.
* Used since Contentstack returns empty objects for all non
* queried in modular blocks.
*/
export function removeEmptyObjects<T>(obj: T): T {
const copy = obj as any
for (let key in copy) {
if (typeof copy[key] === "object" && copy[key] !== null) {
copy[key] = removeEmptyObjects(copy[key])
if (Object.keys(copy[key]).length === 0 && !Array.isArray(copy[key])) {
delete copy[key]
}
}
}
if (Array.isArray(copy)) {
return copy.filter((item) => item != null).map(removeEmptyObjects) as T
}
return copy as T
}