diff --git a/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx b/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx index 528b7b211..4a94f599c 100644 --- a/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx @@ -10,7 +10,7 @@ import styles from "./page.module.css" import type { LangParams, PageArgs } from "@/types/params" -export { generateMetadataAccountPage as generateMetadata } from "@/utils/generateMetadata" +export { generateMetadata } from "@/utils/generateMetadata" export default async function MyPages({ params, diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/edit/page.tsx b/app/[lang]/(live)/(protected)/my-pages/profile/edit/page.tsx index dd8c6eb91..c2d24fe62 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/edit/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/profile/edit/page.tsx @@ -1,5 +1,5 @@ import ProfilePage from "../page" -export { generateMetadataAccountPage as generateMetadata } from "@/utils/generateMetadata" +export { generateMetadata } from "@/utils/generateMetadata" export default ProfilePage diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/page.tsx b/app/[lang]/(live)/(protected)/my-pages/profile/page.tsx index ee8e3ad34..749065fc5 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/profile/page.tsx @@ -7,7 +7,7 @@ import { setLang } from "@/i18n/serverContext" import { LangParams, PageArgs } from "@/types/params" -export { generateMetadataAccountPage as generateMetadata } from "@/utils/generateMetadata" +export { generateMetadata } from "@/utils/generateMetadata" export default async function ProfilePage({ params }: PageArgs) { setLang(params.lang) diff --git a/lib/graphql/Query/AccountPage/MetaData.graphql b/lib/graphql/Query/AccountPage/MetaData.graphql index 966f38fa1..f9b6ecfb4 100644 --- a/lib/graphql/Query/AccountPage/MetaData.graphql +++ b/lib/graphql/Query/AccountPage/MetaData.graphql @@ -1,7 +1,7 @@ #import "../../Fragments/Image.graphql" #import "../../Fragments/System.graphql" -query GetMyPagesMetaData($locale: String!, $uid: String!) { +query GetAccountPageMetaData($locale: String!, $uid: String!) { account_page(locale: $locale, uid: $uid) { system { ...System diff --git a/lib/graphql/Query/CollectionPage/Metadata.graphql b/lib/graphql/Query/CollectionPage/Metadata.graphql new file mode 100644 index 000000000..eee395a61 --- /dev/null +++ b/lib/graphql/Query/CollectionPage/Metadata.graphql @@ -0,0 +1,30 @@ +#import "../../Fragments/Image.graphql" +#import "../../Fragments/System.graphql" + +query GetCollectionPageMetaData($locale: String!, $uid: String!) { + collection_page(locale: $locale, uid: $uid) { + header { + heading + preamble + } + system { + ...System + } + web { + breadcrumbs { + title + } + seo_metadata { + description + title + imageConnection { + edges { + node { + ...Image + } + } + } + } + } + } +} diff --git a/lib/graphql/Query/ContentPage/Metadata.graphql b/lib/graphql/Query/ContentPage/Metadata.graphql new file mode 100644 index 000000000..4058fd646 --- /dev/null +++ b/lib/graphql/Query/ContentPage/Metadata.graphql @@ -0,0 +1,30 @@ +#import "../../Fragments/Image.graphql" +#import "../../Fragments/System.graphql" + +query GetContentPageMetaData($locale: String!, $uid: String!) { + content_page(locale: $locale, uid: $uid) { + header { + heading + preamble + } + system { + ...System + } + web { + breadcrumbs { + title + } + seo_metadata { + description + title + imageConnection { + edges { + node { + ...Image + } + } + } + } + } + } +} diff --git a/lib/graphql/Query/LoyaltyPage/MetaData.graphql b/lib/graphql/Query/LoyaltyPage/MetaData.graphql index a1233dc8c..45ff4161f 100644 --- a/lib/graphql/Query/LoyaltyPage/MetaData.graphql +++ b/lib/graphql/Query/LoyaltyPage/MetaData.graphql @@ -3,6 +3,8 @@ query GetLoyaltyPageMetaData($locale: String!, $uid: String!) { loyalty_page(locale: $locale, uid: $uid) { + heading + preamble system { ...System } diff --git a/server/routers/contentstack/accountPage/output.ts b/server/routers/contentstack/accountPage/output.ts index b0703784f..49a2f4383 100644 --- a/server/routers/contentstack/accountPage/output.ts +++ b/server/routers/contentstack/accountPage/output.ts @@ -11,7 +11,6 @@ import { shortcutsSchema, } from "../schemas/blocks/shortcuts" import { textContentSchema } from "../schemas/blocks/textContent" -import { page } from "../schemas/metadata" import { systemSchema } from "../schemas/system" import { AccountPageEnum } from "@/types/enums/accountPage" @@ -81,7 +80,3 @@ export const accountPageRefsSchema = z.object({ system: systemSchema, }), }) - -export const accountPageMetadataSchema = z.object({ - account_page: page, -}) diff --git a/server/routers/contentstack/accountPage/query.ts b/server/routers/contentstack/accountPage/query.ts index 612dd37c2..199d0c1a2 100644 --- a/server/routers/contentstack/accountPage/query.ts +++ b/server/routers/contentstack/accountPage/query.ts @@ -5,7 +5,6 @@ import { GetAccountPage, GetAccountPageRefs, } from "@/lib/graphql/Query/AccountPage/AccountPage.graphql" -import { GetMyPagesMetaData } from "@/lib/graphql/Query/AccountPage/MetaData.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" import { contentstackExtendedProcedureUID, router } from "@/server/trpc" @@ -16,13 +15,7 @@ import { generateTagsFromSystem, } from "@/utils/generateTag" -import { removeEmptyObjects } from "../../utils" -import { getMetaData, getResponse } from "../metadata/utils" -import { - accountPageMetadataSchema, - accountPageRefsSchema, - accountPageSchema, -} from "./output" +import { accountPageRefsSchema, accountPageSchema } from "./output" import { getConnections } from "./utils" import { @@ -30,7 +23,6 @@ import { type TrackingSDKPageData, } from "@/types/components/tracking" import type { - GetAccountpageMetadata, GetAccountPageRefsSchema, GetAccountPageSchema, } from "@/types/trpc/routers/contentstack/accountPage" @@ -203,30 +195,4 @@ export const accountPageQueryRouter = router({ tracking, } }), - metadata: router({ - get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { - const variables = { - locale: ctx.lang, - uid: ctx.uid, - } - const response = await getResponse( - GetMyPagesMetaData, - variables - ) - - const validatedMetadata = accountPageMetadataSchema.safeParse( - response.data - ) - - if (!validatedMetadata.success) { - console.error( - `Failed to validate My Page MetaData Data - (uid: ${variables.uid})` - ) - console.error(validatedMetadata.error) - return null - } - - return getMetaData(validatedMetadata.data.account_page) - }), - }), }) diff --git a/server/routers/contentstack/metadata/output.ts b/server/routers/contentstack/metadata/output.ts index e641a3d99..6bf5ee05e 100644 --- a/server/routers/contentstack/metadata/output.ts +++ b/server/routers/contentstack/metadata/output.ts @@ -1,11 +1,52 @@ import { z } from "zod" -import { page } from "../schemas/metadata" +import { getDescription, getImages, getTitle } from "./utils" -export const getLoyaltyPageMetadataSchema = z.object({ - loyalty_page: page, +export const rawMetaDataDataSchema = z.object({ + web: z.object({ + seo_metadata: z + .object({ + title: z.string().optional().nullable(), + description: z.string().optional().nullable(), + imageConnection: z + .object({ + edges: z.array( + z.object({ + node: z.object({ + url: z.string(), + }), + }) + ), + }) + .optional() + .nullable(), + }) + .optional() + .nullable(), + breadcrumbs: z + .object({ + title: z.string().optional().nullable(), + }) + .optional() + .nullable(), + }), + heading: z.string().optional().nullable(), + preamble: z.string().optional().nullable(), + header: z + .object({ + heading: z.string().optional().nullable(), + preamble: z.string().optional().nullable(), + }) + .optional() + .nullable(), }) -export type GetLoyaltyPageMetaDataData = z.infer< - typeof getLoyaltyPageMetadataSchema -> +export const metaDataSchema = rawMetaDataDataSchema.transform((data) => { + return { + title: getTitle(data), + description: getDescription(data), + openGraph: { + images: getImages(data), + }, + } +}) diff --git a/server/routers/contentstack/metadata/query.ts b/server/routers/contentstack/metadata/query.ts index 3de11938d..9b1d0dce7 100644 --- a/server/routers/contentstack/metadata/query.ts +++ b/server/routers/contentstack/metadata/query.ts @@ -1,45 +1,144 @@ +import { metrics } from "@opentelemetry/api" +import { cache } from "react" + +import { Lang } from "@/constants/languages" +import { GetAccountPageMetaData } from "@/lib/graphql/Query/AccountPage/MetaData.graphql" +import { GetCollectionPageMetaData } from "@/lib/graphql/Query/CollectionPage/MetaData.graphql" +import { GetContentPageMetaData } from "@/lib/graphql/Query/ContentPage/MetaData.graphql" import { GetLoyaltyPageMetaData } from "@/lib/graphql/Query/LoyaltyPage/MetaData.graphql" +import { request } from "@/lib/graphql/request" +import { notFound } from "@/server/errors/trpc" import { contentstackExtendedProcedureUID, router } from "@/server/trpc" -import { - type GetLoyaltyPageMetaDataData, - getLoyaltyPageMetadataSchema, -} from "./output" -import { getMetaData, getResponse, type Variables } from "./utils" +import { generateTag } from "@/utils/generateTag" + +import { metaDataSchema } from "./output" +import { affix } from "./utils" import { PageTypeEnum } from "@/types/requests/pageType" +import { RawMetaDataSchema } from "@/types/trpc/routers/contentstack/metadata" -async function getLoyaltyPageMetaData(variables: Variables) { - const response = await getResponse( - GetLoyaltyPageMetaData, - variables +const meter = metrics.getMeter("trpc.metaData") + +// OpenTelemetry metrics +const fetchMetaDataCounter = meter.createCounter( + "trpc.contentstack.metaData.get" +) +const fetchMetaDataSuccessCounter = meter.createCounter( + "trpc.contentstack.metaData.get-success" +) +const fetchMetaDataFailCounter = meter.createCounter( + "trpc.contentstack.metaData.get-fail" +) +const transformMetaDataCounter = meter.createCounter( + "trpc.contentstack.metaData.transform" +) +const transformMetaDataSuccessCounter = meter.createCounter( + "trpc.contentstack.metaData.transform-success" +) +const transformMetaDataFailCounter = meter.createCounter( + "trpc.contentstack.metaData.transform-fail" +) + +const fetchMetaData = cache(async function fetchMemoizedMetaData( + query: string, + { uid, lang }: { uid: string; lang: Lang } +) { + fetchMetaDataCounter.add(1, { lang, uid }) + console.info( + "contentstack.metaData fetch start", + JSON.stringify({ query: { lang, uid } }) ) - - const validatedMetadata = getLoyaltyPageMetadataSchema.safeParse( - response.data + const response = await request( + query, + { locale: lang, uid }, + { + cache: "force-cache", + next: { + tags: [generateTag(lang, uid, affix)], + }, + } ) - - if (!validatedMetadata.success) { + if (!response.data) { + const notFoundError = notFound(response) + fetchMetaDataFailCounter.add(1, { + lang, + uid, + error_type: "not_found", + error: JSON.stringify({ code: notFoundError.code }), + }) console.error( - `Failed to validate Loyaltypage MetaData Data - (uid: ${variables.uid})` + "contentstack.metaData fetch not found error", + JSON.stringify({ + query: { lang, uid }, + error: { code: notFoundError.code }, + }) + ) + throw notFoundError + } + + fetchMetaDataSuccessCounter.add(1, { lang, uid }) + console.info( + "contentstack.metaData fetch success", + JSON.stringify({ query: { lang, uid } }) + ) + + return response.data +}) + +function getTransformedMetaData(data: unknown) { + transformMetaDataCounter.add(1) + console.info("contentstack.metaData transform start") + const validatedMetaData = metaDataSchema.safeParse(data) + + if (!validatedMetaData.success) { + transformMetaDataFailCounter.add(1, { + error_type: "validation_error", + error: JSON.stringify(validatedMetaData.error), + }) + console.error( + "contentstack.metaData validation error", + JSON.stringify({ + error: validatedMetaData.error, + }) ) - console.error(validatedMetadata.error) return null } - return getMetaData(validatedMetadata.data.loyalty_page) + transformMetaDataSuccessCounter.add(1) + console.info("contentstack.metaData transform success") + + return validatedMetaData.data } export const metaDataQueryRouter = router({ get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { const variables = { - locale: ctx.lang, + lang: ctx.lang, uid: ctx.uid, } switch (ctx.contentType) { + case PageTypeEnum.accountPage: + const accountPageResponse = await fetchMetaData<{ + account_page: RawMetaDataSchema + }>(GetAccountPageMetaData, variables) + return getTransformedMetaData(accountPageResponse.account_page) + case PageTypeEnum.collectionPage: + const collectionPageResponse = await fetchMetaData<{ + collection_page: RawMetaDataSchema + }>(GetCollectionPageMetaData, variables) + return getTransformedMetaData(collectionPageResponse.collection_page) + case PageTypeEnum.contentPage: + const contentPageResponse = await fetchMetaData<{ + content_page: RawMetaDataSchema + }>(GetContentPageMetaData, variables) + return getTransformedMetaData(contentPageResponse.content_page) case PageTypeEnum.loyaltyPage: - return await getLoyaltyPageMetaData(variables) + const loyaltyPageResponse = await fetchMetaData<{ + loyalty_page: RawMetaDataSchema + }>(GetLoyaltyPageMetaData, variables) + return getTransformedMetaData(loyaltyPageResponse.loyalty_page) default: return null } diff --git a/server/routers/contentstack/metadata/utils.ts b/server/routers/contentstack/metadata/utils.ts index c1dc47a1d..ce44b0011 100644 --- a/server/routers/contentstack/metadata/utils.ts +++ b/server/routers/contentstack/metadata/utils.ts @@ -1,44 +1,44 @@ -import { Lang } from "@/constants/languages" -import { request } from "@/lib/graphql/request" -import { internalServerError, notFound } from "@/server/errors/trpc" - -import { generateTag } from "@/utils/generateTag" - -import { getMetaDataSchema, Page } from "../schemas/metadata" - -export type Variables = { - locale: Lang - uid: string -} +import { RawMetaDataSchema } from "@/types/trpc/routers/contentstack/metadata" export const affix = "metadata" -export async function getResponse(query: string, variables: Variables) { - const response = await request(query, variables, { - cache: "force-cache", - next: { - tags: [generateTag(variables.locale, variables.uid, affix)], - }, - }) - if (!response.data) { - throw notFound(response) +export function getTitle(data: RawMetaDataSchema) { + const metaData = data.web.seo_metadata + if (metaData?.title) { + return metaData.title } - - return response + if (data.web?.breadcrumbs?.title) { + return data.web.breadcrumbs.title + } + if (data.heading) { + return data.heading + } + if (data.header?.heading) { + return data.header.heading + } + return "" } -export function getMetaData(page: Page) { - const pageMetaData = { - breadcrumbsTitle: page.web.breadcrumbs.title, - title: page.web.seo_metadata.title, - description: page.web.seo_metadata.description, - imageConnection: page.web.seo_metadata.imageConnection, - uid: page.system.uid, +export function getDescription(data: RawMetaDataSchema) { + const metaData = data.web.seo_metadata + if (metaData?.description) { + return metaData.description } - const validatedMetaData = getMetaDataSchema.safeParse(pageMetaData) - if (!validatedMetaData.success) { - throw internalServerError(validatedMetaData.error) + if (data.preamble) { + return data.preamble } - - return validatedMetaData.data + if (data.header?.preamble) { + return data.header.preamble + } + return "" +} + +export function getImages(data: RawMetaDataSchema) { + const metaData = data.web.seo_metadata + if (metaData?.imageConnection) { + return metaData.imageConnection.edges.map((edge) => ({ + url: edge.node.url, + })) + } + return [] } diff --git a/server/routers/contentstack/schemas/metadata.ts b/server/routers/contentstack/schemas/metadata.ts deleted file mode 100644 index 9399f3a61..000000000 --- a/server/routers/contentstack/schemas/metadata.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { z } from "zod" - -import { systemSchema } from "./system" - -export const getMetaDataSchema = z.object({ - breadcrumbsTitle: z.string().optional(), - title: z.string().optional(), - description: z.string().optional(), - imageConnection: z - .object({ - edges: z.array( - z.object({ - node: z.object({ - url: z.string(), - }), - }) - ), - }) - .optional(), -}) - -export const page = z.object({ - web: z.object({ - seo_metadata: z.object({ - title: z.string().optional(), - description: z.string().optional(), - imageConnection: z - .object({ - edges: z.array( - z.object({ - node: z.object({ - url: z.string(), - }), - }) - ), - }) - .optional(), - }), - breadcrumbs: z.object({ - title: z.string(), - }), - }), - system: systemSchema, -}) - -export type Page = z.infer diff --git a/types/components/metadata/index.ts b/types/components/metadata/index.ts deleted file mode 100644 index 6c4eedc4a..000000000 --- a/types/components/metadata/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { z } from "zod" - -import { getMetaDataSchema } from "@/server/routers/contentstack/schemas/metadata" - -export interface MetaData extends z.infer {} diff --git a/types/trpc/routers/contentstack/accountPage.ts b/types/trpc/routers/contentstack/accountPage.ts index 4bdf2b490..703228d8f 100644 --- a/types/trpc/routers/contentstack/accountPage.ts +++ b/types/trpc/routers/contentstack/accountPage.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { - accountPageMetadataSchema, accountPageRefsSchema, accountPageSchema, blocksSchema, @@ -18,10 +17,4 @@ export interface GetAccountPageSchema export interface AccountPage extends z.output {} -export interface GetAccountpageMetadata - extends z.output {} - -export interface AccountPageMetadata - extends z.output {} - export type Block = z.output diff --git a/types/trpc/routers/contentstack/metadata.ts b/types/trpc/routers/contentstack/metadata.ts new file mode 100644 index 000000000..e9a71bec1 --- /dev/null +++ b/types/trpc/routers/contentstack/metadata.ts @@ -0,0 +1,10 @@ +import { z } from "zod" + +import { + metaDataSchema, + rawMetaDataDataSchema, +} from "@/server/routers/contentstack/metadata/output" + +export interface RawMetaDataSchema + extends z.input {} +export interface MetaDataSchema extends z.output {} diff --git a/utils/generateMetadata.ts b/utils/generateMetadata.ts index c75579587..db25914a0 100644 --- a/utils/generateMetadata.ts +++ b/utils/generateMetadata.ts @@ -1,59 +1,5 @@ import { serverClient } from "@/lib/trpc/server" export async function generateMetadata() { - const metaData = await serverClient().contentstack.metaData.get() - - if (!metaData) { - return { - title: "", - description: "", - openGraph: { - images: [], - }, - } - } - - const title = metaData?.breadcrumbsTitle ?? metaData?.title ?? "" - const description = metaData?.description ?? "" - const images = - metaData?.imageConnection?.edges?.map((edge) => ({ - url: edge.node.url, - })) || [] - - return { - title, - description, - openGraph: { - images, - }, - } -} - -export async function generateMetadataAccountPage() { - const metaData = await serverClient().contentstack.accountPage.metadata.get() - - if (!metaData) { - return { - title: "", - description: "", - openGraph: { - images: [], - }, - } - } - - const title = metaData?.breadcrumbsTitle ?? metaData?.title ?? "" - const description = metaData?.description ?? "" - const images = - metaData?.imageConnection?.edges?.map((edge) => ({ - url: edge.node.url, - })) || [] - - return { - title, - description, - openGraph: { - images, - }, - } + return await serverClient().contentstack.metaData.get() }