From d651ea526cbcf92952311ecfedff84a3c59cc09c Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Wed, 15 May 2024 09:41:19 +0200 Subject: [PATCH 1/3] feat: add revlidation for loyalty page --- lib/graphql/Query/LoyaltyPage.graphql | 121 ++++++++++++++++++ .../contentstack/loyaltyPage/output.ts | 97 ++++++++++++++ .../routers/contentstack/loyaltyPage/query.ts | 71 +++++++++- .../routers/contentstack/loyaltyPage/utils.ts | 54 ++++++++ .../contentstack/myPages/navigation/output.ts | 2 +- types/requests/myPages/navigation.ts | 8 +- types/requests/pageLinks.ts | 5 + utils/contentType.ts | 23 ++++ 8 files changed, 368 insertions(+), 13 deletions(-) create mode 100644 server/routers/contentstack/loyaltyPage/utils.ts create mode 100644 types/requests/pageLinks.ts diff --git a/lib/graphql/Query/LoyaltyPage.graphql b/lib/graphql/Query/LoyaltyPage.graphql index b71b62c71..cd8327fcb 100644 --- a/lib/graphql/Query/LoyaltyPage.graphql +++ b/lib/graphql/Query/LoyaltyPage.graphql @@ -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 + } + } + } +} diff --git a/server/routers/contentstack/loyaltyPage/output.ts b/server/routers/contentstack/loyaltyPage/output.ts index 17f422477..8ccefb861 100644 --- a/server/routers/contentstack/loyaltyPage/output.ts +++ b/server/routers/contentstack/loyaltyPage/output.ts @@ -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 & { 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 +> diff --git a/server/routers/contentstack/loyaltyPage/query.ts b/server/routers/contentstack/loyaltyPage/query.ts index 70c14ba2f..dc934783d 100644 --- a/server/routers/contentstack/loyaltyPage/query.ts +++ b/server/routers/contentstack/loyaltyPage/query.ts @@ -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(GetLoyaltyPage, { - locale: input.locale, - url: input.href, - }) + const { locale } = input + + const refsResponse = await request( + 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( + GetLoyaltyPage, + { + locale, + url: input.href, + }, + { next: { tags } } + ) if (!loyaltyPageRes.data) { throw badRequestError() diff --git a/server/routers/contentstack/loyaltyPage/utils.ts b/server/routers/contentstack/loyaltyPage/utils.ts new file mode 100644 index 000000000..200b6c25d --- /dev/null +++ b/server/routers/contentstack/loyaltyPage/utils.ts @@ -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[] = [] + 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 +} diff --git a/server/routers/contentstack/myPages/navigation/output.ts b/server/routers/contentstack/myPages/navigation/output.ts index b4641c327..9785700ca 100644 --- a/server/routers/contentstack/myPages/navigation/output.ts +++ b/server/routers/contentstack/myPages/navigation/output.ts @@ -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( diff --git a/types/requests/myPages/navigation.ts b/types/requests/myPages/navigation.ts index 63728d0f2..ea1acfcdf 100644 --- a/types/requests/myPages/navigation.ts +++ b/types/requests/myPages/navigation.ts @@ -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 diff --git a/types/requests/pageLinks.ts b/types/requests/pageLinks.ts new file mode 100644 index 000000000..1e40320f3 --- /dev/null +++ b/types/requests/pageLinks.ts @@ -0,0 +1,5 @@ +export enum PageLinkEnum { + AccountPage = "AccountPage", + ContentPage = "ContentPage", + LoyaltyPage = "LoyaltyPage", +} diff --git a/utils/contentType.ts b/utils/contentType.ts index 656c9731c..2093b1a7d 100644 --- a/utils/contentType.ts +++ b/utils/contentType.ts @@ -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(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 +} From 863d99ad4414aca4dfff4b88746ffcc4b1672124 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Wed, 15 May 2024 11:06:52 +0200 Subject: [PATCH 2/3] fix: refs for account page --- lib/graphql/Query/AccountPage.graphql | 50 +++++++++++++ .../contentstack/accountPage/output.ts | 59 +++++++++++++++ .../routers/contentstack/accountPage/query.ts | 73 +++++++++++++++++-- .../routers/contentstack/accountPage/utils.ts | 33 +++++++++ .../routers/contentstack/loyaltyPage/query.ts | 2 +- 5 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 server/routers/contentstack/accountPage/utils.ts diff --git a/lib/graphql/Query/AccountPage.graphql b/lib/graphql/Query/AccountPage.graphql index 4119c943b..2bd12f4fa 100644 --- a/lib/graphql/Query/AccountPage.graphql +++ b/lib/graphql/Query/AccountPage.graphql @@ -2,6 +2,11 @@ #import "../Fragments/MyPages/AccountPage/AccountPageContentShortcuts.graphql" #import "../Fragments/MyPages/AccountPage/AccountPageContentTextContent.graphql" +#import "../Fragments/Refs/AccountPage.graphql" +#import "../Fragments/Refs/ContentPage.graphql" +#import "../Fragments/Refs/LoyaltyPage.graphql" +#import "../Fragments/Refs/System.graphql" + query GetAccountPage($locale: String!, $url: String!) { all_account_page(limit: 1, locale: $locale, where: { url: $url }) { items { @@ -17,3 +22,48 @@ query GetAccountPage($locale: String!, $url: String!) { total } } + +query GetAccountPageRefs($locale: String!, $url: String!) { + all_account_page(limit: 1, locale: $locale, where: { url: $url }) { + items { + content { + ... on AccountPageContentDynamicContent { + __typename + dynamic_content { + link { + linkConnection { + edges { + node { + __typename + ...AccountPageRef + ...LoyaltyPageRef + } + } + } + } + } + } + ... on AccountPageContentShortcuts { + __typename + shortcuts { + shortcuts { + linkConnection { + edges { + node { + __typename + ...AccountPageRef + ...ContentPageRef + ...LoyaltyPageRef + } + } + } + } + } + } + } + system { + ...System + } + } + } +} diff --git a/server/routers/contentstack/accountPage/output.ts b/server/routers/contentstack/accountPage/output.ts index a73757227..080e687e7 100644 --- a/server/routers/contentstack/accountPage/output.ts +++ b/server/routers/contentstack/accountPage/output.ts @@ -7,6 +7,7 @@ import { DynamicContentComponents, } from "@/types/components/myPages/myPage/enums" import { Embeds } from "@/types/requests/embeds" +import { PageLinkEnum } from "@/types/requests/pageLinks" import { Edges } from "@/types/requests/utils/edges" import { RTEDocument } from "@/types/rte/node" @@ -139,3 +140,61 @@ export type AccountPage = Omit & { title: string content: AccountPageContentItem[] } + +// 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 accountPageShortcutsRefs = z.object({ + __typename: z.literal(ContentEntries.AccountPageContentShortcuts), + shortcuts: z.object({ + shortcuts: z.array( + z.object({ + linkConnection: pageConnectionRefs, + }) + ), + }), +}) + +const accountPageDynamicContentRefs = z.object({ + __typename: z.literal(ContentEntries.AccountPageContentDynamicContent), + dynamic_content: z.object({ + link: z.object({ + linkConnection: pageConnectionRefs, + }), + }), +}) + +const accountPageContentItemRefs = z.discriminatedUnion("__typename", [ + accountPageDynamicContentRefs, + accountPageShortcutsRefs, +]) + +export const validateAccountPageRefsSchema = z.object({ + all_account_page: z.object({ + items: z.array( + z.object({ + content: z.array(accountPageContentItemRefs), + system: z.object({ + content_type_uid: z.string(), + uid: z.string(), + }), + }) + ), + }), +}) + +export type AccountPageRefsDataRaw = z.infer< + typeof validateAccountPageRefsSchema +> diff --git a/server/routers/contentstack/accountPage/query.ts b/server/routers/contentstack/accountPage/query.ts index 56a0ac2d3..5102e836c 100644 --- a/server/routers/contentstack/accountPage/query.ts +++ b/server/routers/contentstack/accountPage/query.ts @@ -1,10 +1,26 @@ -import GetAccountPage from "@/lib/graphql/Query/AccountPage.graphql" +import { + GetAccountPage, + GetAccountPageRefs, +} from "@/lib/graphql/Query/AccountPage.graphql" import { request } from "@/lib/graphql/request" 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 { getAccountPageInput } from "./input" -import { type AccountPage, validateAccountPageSchema } from "./output" +import { + type AccountPage, + AccountPageRefsDataRaw, + validateAccountPageRefsSchema, + validateAccountPageSchema, +} from "./output" +import { getConnections } from "./utils" import { ContentEntries } from "@/types/components/myPages/myPage/enums" import { Embeds } from "@/types/requests/embeds" @@ -14,10 +30,55 @@ import { RTEDocument } from "@/types/rte/node" export const accountPageQueryRouter = router({ get: publicProcedure.input(getAccountPageInput).query(async ({ input }) => { try { - const response = await request(GetAccountPage, { - locale: input.lang, - url: input.url, - }) + const { lang, url } = input + + const refsResponse = await request( + GetAccountPageRefs, + { + locale: lang, + url, + }, + { + next: { + tags: [generateRefsResponseTag(lang, "account_page")], + }, + } + ) + + if (!refsResponse.data) { + console.error("Bad response for `GetAccountPageRefs`") + console.error({ refsResponse }) + throw internalServerError() + } + + const cleanedData = removeEmptyObjects(refsResponse.data) + + const validatedAccountPageRefs = + validateAccountPageRefsSchema.safeParse(cleanedData) + if (!validatedAccountPageRefs.success) { + console.error("Bad validation for `GetAccountPageRefs`") + console.error(validatedAccountPageRefs.error) + throw badRequestError() + } + + const connections = getConnections(validatedAccountPageRefs.data) + + const tags = generateTags(lang, connections) + + tags.push( + generateTag( + lang, + validatedAccountPageRefs.data.all_account_page.items[0].system.uid + ) + ) + const response = await request( + GetAccountPage, + { + locale: lang, + url, + }, + { next: { tags } } + ) if (!response.data) { throw badRequestError() diff --git a/server/routers/contentstack/accountPage/utils.ts b/server/routers/contentstack/accountPage/utils.ts new file mode 100644 index 000000000..8a7e62a6d --- /dev/null +++ b/server/routers/contentstack/accountPage/utils.ts @@ -0,0 +1,33 @@ +import { AccountPageRefsDataRaw } from "./output" + +import { ContentEntries } from "@/types/components/myPages/myPage/enums" +import type { Edges } from "@/types/requests/utils/edges" +import type { NodeRefs } from "@/types/requests/utils/refs" + +export function getConnections(refs: AccountPageRefsDataRaw) { + const connections: Edges[] = [] + refs.all_account_page.items.forEach((ref) => { + if (ref.content) { + ref.content.forEach((item) => { + switch (item.__typename) { + case ContentEntries.AccountPageContentShortcuts: { + item.shortcuts.shortcuts.forEach((shortcut) => { + if (shortcut.linkConnection.edges.length) { + connections.push(shortcut.linkConnection) + } + }) + break + } + case ContentEntries.AccountPageContentDynamicContent: { + if (item.dynamic_content.link.linkConnection.edges.length) { + connections.push(item.dynamic_content.link.linkConnection) + } + break + } + } + }) + } + }) + + return connections +} diff --git a/server/routers/contentstack/loyaltyPage/query.ts b/server/routers/contentstack/loyaltyPage/query.ts index dc934783d..0466b46ca 100644 --- a/server/routers/contentstack/loyaltyPage/query.ts +++ b/server/routers/contentstack/loyaltyPage/query.ts @@ -51,7 +51,7 @@ export const loyaltyPageQueryRouter = router({ ) if (!refsResponse.data) { - console.error("Bad response for `GetNavigationMyPagesRefs`") + console.error("Bad response for `GetLoyaltyPageRefs`") console.error({ refsResponse }) throw internalServerError() } From cd33e177751a15b2835255571afb59ec08852599 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Wed, 15 May 2024 13:20:43 +0200 Subject: [PATCH 3/3] fix: install clean-deep --- package-lock.json | 29 +++++++++++++++++++ package.json | 3 +- .../routers/contentstack/accountPage/query.ts | 7 +++-- .../routers/contentstack/loyaltyPage/query.ts | 5 +++- server/routers/utils/index.ts | 15 ++++++++++ utils/contentType.ts | 23 --------------- 6 files changed, 55 insertions(+), 27 deletions(-) create mode 100644 server/routers/utils/index.ts diff --git a/package-lock.json b/package-lock.json index 3f28fd1bb..1c08c991b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@trpc/react-query": "^11.0.0-next-beta.318", "@trpc/server": "^11.0.0-next-beta.318", "class-variance-authority": "^0.7.0", + "clean-deep": "^3.4.0", "dayjs": "^1.11.10", "deepmerge": "^4.3.1", "graphql": "^16.8.1", @@ -3494,6 +3495,19 @@ "url": "https://joebell.co.uk" } }, + "node_modules/clean-deep": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/clean-deep/-/clean-deep-3.4.0.tgz", + "integrity": "sha512-Lo78NV5ItJL/jl+B5w0BycAisaieJGXK1qYi/9m4SjR8zbqmrUtO7Yhro40wEShGmmxs/aJLI/A+jNhdkXK8mw==", + "dependencies": { + "lodash.isempty": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.transform": "^4.6.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -7503,6 +7517,16 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true }, + "node_modules/lodash.isempty": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -7521,6 +7545,11 @@ "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", "dev": true }, + "node_modules/lodash.transform": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz", + "integrity": "sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ==" + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", diff --git a/package.json b/package.json index 1b57ae279..4560cf305 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@trpc/react-query": "^11.0.0-next-beta.318", "@trpc/server": "^11.0.0-next-beta.318", "class-variance-authority": "^0.7.0", + "clean-deep": "^3.4.0", "dayjs": "^1.11.10", "deepmerge": "^4.3.1", "graphql": "^16.8.1", @@ -73,4 +74,4 @@ "engines": { "node": "18" } -} \ No newline at end of file +} diff --git a/server/routers/contentstack/accountPage/query.ts b/server/routers/contentstack/accountPage/query.ts index 5102e836c..350dc6719 100644 --- a/server/routers/contentstack/accountPage/query.ts +++ b/server/routers/contentstack/accountPage/query.ts @@ -6,13 +6,13 @@ import { request } from "@/lib/graphql/request" 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 { removeEmptyObjects } from "../../utils" import { getAccountPageInput } from "./input" import { type AccountPage, @@ -51,6 +51,9 @@ export const accountPageQueryRouter = router({ throw internalServerError() } + // Remove empty objects from a fetched content type. Needed since + // Contentstack returns empty objects for all non queried blocks in modular blocks. + // This is an ongoing support case in Contentstack, ticker number #00031579 const cleanedData = removeEmptyObjects(refsResponse.data) const validatedAccountPageRefs = @@ -71,7 +74,7 @@ export const accountPageQueryRouter = router({ validatedAccountPageRefs.data.all_account_page.items[0].system.uid ) ) - const response = await request( + const response = await request( GetAccountPage, { locale: lang, diff --git a/server/routers/contentstack/loyaltyPage/query.ts b/server/routers/contentstack/loyaltyPage/query.ts index 0466b46ca..99521ba79 100644 --- a/server/routers/contentstack/loyaltyPage/query.ts +++ b/server/routers/contentstack/loyaltyPage/query.ts @@ -7,13 +7,13 @@ import { _ } from "@/lib/translation" 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 { removeEmptyObjects } from "../../utils" import { getLoyaltyPageInput } from "./input" import { type LoyaltyPage, @@ -56,6 +56,9 @@ export const loyaltyPageQueryRouter = router({ throw internalServerError() } + // Remove empty objects from a fetched content type. Needed since + // Contentstack returns empty objects for all non queried blocks in modular blocks. + // This is an ongoing support case in Contentstack, ticker number #00031579 const cleanedData = removeEmptyObjects(refsResponse.data) const validatedLoyaltyPageRefs = diff --git a/server/routers/utils/index.ts b/server/routers/utils/index.ts new file mode 100644 index 000000000..01019aa41 --- /dev/null +++ b/server/routers/utils/index.ts @@ -0,0 +1,15 @@ +import cleaner from "clean-deep" + +/** + * 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(obj: T) { + return cleaner(obj, { + emptyArrays: false, + emptyStrings: false, + nullValues: false, + undefinedValues: false, + }) +} diff --git a/utils/contentType.ts b/utils/contentType.ts index 2093b1a7d..656c9731c 100644 --- a/utils/contentType.ts +++ b/utils/contentType.ts @@ -53,26 +53,3 @@ 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(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 -}