From 35a527be05637e4de6152d2b60755942269b9e1f Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Fri, 8 Nov 2024 13:01:54 +0100 Subject: [PATCH] chore(SW-739): cleanup reward endpoint related code --- server/routers/contentstack/reward/query.ts | 403 ++++++-------------- server/routers/contentstack/reward/utils.ts | 168 ++++++++ 2 files changed, 292 insertions(+), 279 deletions(-) create mode 100644 server/routers/contentstack/reward/utils.ts diff --git a/server/routers/contentstack/reward/query.ts b/server/routers/contentstack/reward/query.ts index b4e0e54b2..0c1577182 100644 --- a/server/routers/contentstack/reward/query.ts +++ b/server/routers/contentstack/reward/query.ts @@ -1,10 +1,4 @@ -import { metrics } from "@opentelemetry/api" -import { unstable_cache } from "next/cache" - -import { Lang } from "@/constants/languages" import * as api from "@/lib/api" -import { GetRewards } from "@/lib/graphql/Query/Rewards.graphql" -import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" import { contentStackBaseWithProtectedProcedure, @@ -12,8 +6,6 @@ import { router, } from "@/server/trpc" -import { generateLoyaltyConfigTag } from "@/utils/generateTag" - import { getAllLoyaltyLevels, getLoyaltyLevel } from "../loyaltyLevel/query" import { rewardsAllInput, @@ -21,172 +13,135 @@ import { rewardsCurrentInput, rewardsUpdateInput, } from "./input" +import { Reward, SurpriseReward, validateApiRewardSchema } from "./output" import { - CmsRewardsResponse, - Reward, - SurpriseReward, - validateApiRewardSchema, - validateApiTierRewardsSchema, - validateCmsRewardsSchema, -} from "./output" + getAllCachedApiRewards, + getAllRewardCounter, + getAllRewardFailCounter, + getAllRewardSuccessCounter, + getByLevelRewardCounter, + getByLevelRewardFailCounter, + getByLevelRewardSuccessCounter, + getCmsRewards, + getCurrentRewardCounter, + getCurrentRewardFailCounter, + getCurrentRewardSuccessCounter, + getUniqueRewardIds, +} from "./utils" import { Surprise } from "@/types/components/blocks/surprises" -const meter = metrics.getMeter("trpc.reward") -// OpenTelemetry metrics: Reward - -const getCurrentRewardCounter = meter.createCounter( - "trpc.contentstack.reward.current" -) -const getCurrentRewardSuccessCounter = meter.createCounter( - "trpc.contentstack.reward.current-success" -) - -const getCurrentRewardFailCounter = meter.createCounter( - "trpc.contentstack.reward.current-fail" -) - -const getByLevelRewardCounter = meter.createCounter( - "trpc.contentstack.reward.byLevel" -) -const getByLevelRewardSuccessCounter = meter.createCounter( - "trpc.contentstack.reward.byLevel-success" -) - -const getByLevelRewardFailCounter = meter.createCounter( - "trpc.contentstack.reward.byLevel-fail" -) - -const getAllRewardCounter = meter.createCounter("trpc.contentstack.reward.all") - -const getAllRewardSuccessCounter = meter.createCounter( - "trpc.contentstack.reward.all-success" -) -const getAllRewardFailCounter = meter.createCounter( - "trpc.contentstack.reward.all-fail" -) - -const ONE_HOUR = 60 * 60 - -function getUniqueRewardIds(rewardIds: string[]) { - const uniqueRewardIds = new Set(rewardIds) - return Array.from(uniqueRewardIds) -} - -const getAllCachedApiRewards = unstable_cache( - async function (token) { - const apiResponse = await api.get(api.endpoints.v1.Profile.tierRewards, { - headers: { - Authorization: `Bearer ${token}`, - }, - }) - - if (!apiResponse.ok) { - const text = await apiResponse.text() - getCurrentRewardFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.rewards.tierRewards error ", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - - throw apiResponse - } - - const data = await apiResponse.json() - const validatedApiTierRewards = validateApiTierRewardsSchema.safeParse(data) - - if (!validatedApiTierRewards.success) { - getAllRewardFailCounter.add(1, { - error_type: "validation_error", - error: JSON.stringify(validatedApiTierRewards.error), - }) - console.error(validatedApiTierRewards.error) - console.error( - "api.rewards validation error", - JSON.stringify({ - error: validatedApiTierRewards.error, - }) - ) - throw validatedApiTierRewards.error - } - - return validatedApiTierRewards.data - }, - ["getAllApiRewards"], - { revalidate: ONE_HOUR } -) - -async function getCmsRewards(locale: Lang, rewardIds: string[]) { - const tags = rewardIds.map((id) => - generateLoyaltyConfigTag(locale, "reward", id) - ) - const cmsRewardsResponse = await request( - GetRewards, - { - locale: locale, - rewardIds, - }, - { next: { tags }, cache: "force-cache" } - ) - - if (!cmsRewardsResponse.data) { - getAllRewardFailCounter.add(1, { - lang: locale, - error_type: "validation_error", - error: JSON.stringify(cmsRewardsResponse.data), - }) - const notFoundError = notFound(cmsRewardsResponse) - console.error( - "contentstack.rewards not found error", - JSON.stringify({ - query: { - locale, - rewardIds, - }, - error: { code: notFoundError.code }, - }) - ) - throw notFoundError - } - - const validatedCmsRewards = - validateCmsRewardsSchema.safeParse(cmsRewardsResponse) - - if (!validatedCmsRewards.success) { - getAllRewardFailCounter.add(1, { - locale, - rewardIds, - error_type: "validation_error", - error: JSON.stringify(validatedCmsRewards.error), - }) - console.error(validatedCmsRewards.error) - console.error( - "contentstack.rewards validation error", - JSON.stringify({ - query: { locale, rewardIds }, - error: validatedCmsRewards.error, - }) - ) - return null - } - - return validatedCmsRewards.data -} - export const rewardQueryRouter = router({ + all: contentStackBaseWithServiceProcedure + .input(rewardsAllInput) + .query(async function ({ input, ctx }) { + getAllRewardCounter.add(1) + const allApiRewards = await getAllCachedApiRewards(ctx.serviceToken) + + if (!allApiRewards) { + return [] + } + + const rewardIds = Object.values(allApiRewards) + .flatMap((level) => level.map((reward) => reward?.rewardId)) + .filter((id): id is string => Boolean(id)) + + const contentStackRewards = await getCmsRewards( + ctx.lang, + getUniqueRewardIds(rewardIds) + ) + + if (!contentStackRewards) { + return [] + } + + const loyaltyLevelsConfig = await getAllLoyaltyLevels(ctx) + const levelsWithRewards = Object.entries(allApiRewards).map( + ([level, rewards]) => { + const combinedRewards = rewards + .filter((r) => (input.unique ? r?.rewardTierLevel === level : true)) + .map((reward) => { + const contentStackReward = contentStackRewards.find((r) => { + return r.reward_id === reward?.rewardId + }) + + if (contentStackReward) { + return contentStackReward + } else { + console.error("No contentStackReward found", reward?.rewardId) + } + }) + .filter((reward): reward is Reward => Boolean(reward)) + + const levelConfig = loyaltyLevelsConfig.find( + (l) => l.level_id === level + ) + + if (!levelConfig) { + getAllRewardFailCounter.add(1) + + console.error("contentstack.loyaltyLevels level not found") + throw notFound() + } + return { ...levelConfig, rewards: combinedRewards } + } + ) + + getAllRewardSuccessCounter.add(1) + return levelsWithRewards + }), + byLevel: contentStackBaseWithServiceProcedure + .input(rewardsByLevelInput) + .query(async function ({ input, ctx }) { + getByLevelRewardCounter.add(1) + const { level_id } = input + + const allUpcomingApiRewards = await getAllCachedApiRewards( + ctx.serviceToken + ) + + if (!allUpcomingApiRewards || !allUpcomingApiRewards[level_id]) { + getByLevelRewardFailCounter.add(1) + + return null + } + + let apiRewards = allUpcomingApiRewards[level_id]! + + if (input.unique) { + apiRewards = allUpcomingApiRewards[level_id]!.filter( + (reward) => reward?.rewardTierLevel === level_id + ) + } + + const rewardIds = apiRewards + .map((reward) => reward?.rewardId) + .filter((id): id is string => Boolean(id)) + + const contentStackRewards = await getCmsRewards(ctx.lang, rewardIds) + if (!contentStackRewards) { + return null + } + + const loyaltyLevelsConfig = await getLoyaltyLevel(ctx, input.level_id) + + const levelsWithRewards = apiRewards + .map((reward) => { + const contentStackReward = contentStackRewards.find((r) => { + return r.reward_id === reward?.rewardId + }) + + if (contentStackReward) { + return contentStackReward + } else { + console.error("No contentStackReward found", reward?.rewardId) + } + }) + .filter((reward): reward is Reward => Boolean(reward)) + + getByLevelRewardSuccessCounter.add(1) + return { level: loyaltyLevelsConfig, rewards: levelsWithRewards } + }), current: contentStackBaseWithProtectedProcedure .input(rewardsCurrentInput) .query(async function ({ input, ctx }) { @@ -280,116 +235,6 @@ export const rewardQueryRouter = router({ nextCursor, } }), - byLevel: contentStackBaseWithServiceProcedure - .input(rewardsByLevelInput) - .query(async function ({ input, ctx }) { - getByLevelRewardCounter.add(1) - const { level_id } = input - - const allUpcomingApiRewards = await getAllCachedApiRewards( - ctx.serviceToken - ) - - if (!allUpcomingApiRewards || !allUpcomingApiRewards[level_id]) { - getByLevelRewardFailCounter.add(1) - - return null - } - - let apiRewards = allUpcomingApiRewards[level_id]! - - if (input.unique) { - apiRewards = allUpcomingApiRewards[level_id]!.filter( - (reward) => reward?.rewardTierLevel === level_id - ) - } - - const rewardIds = apiRewards - .map((reward) => reward?.rewardId) - .filter((id): id is string => Boolean(id)) - - const contentStackRewards = await getCmsRewards(ctx.lang, rewardIds) - if (!contentStackRewards) { - return null - } - - const loyaltyLevelsConfig = await getLoyaltyLevel(ctx, input.level_id) - - const levelsWithRewards = apiRewards - .map((reward) => { - const contentStackReward = contentStackRewards.find((r) => { - return r.reward_id === reward?.rewardId - }) - - if (contentStackReward) { - return contentStackReward - } else { - console.error("No contentStackReward found", reward?.rewardId) - } - }) - .filter((reward): reward is Reward => Boolean(reward)) - - getByLevelRewardSuccessCounter.add(1) - return { level: loyaltyLevelsConfig, rewards: levelsWithRewards } - }), - all: contentStackBaseWithServiceProcedure - .input(rewardsAllInput) - .query(async function ({ input, ctx }) { - getAllRewardCounter.add(1) - const allApiRewards = await getAllCachedApiRewards(ctx.serviceToken) - - if (!allApiRewards) { - return [] - } - - const rewardIds = Object.values(allApiRewards) - .flatMap((level) => level.map((reward) => reward?.rewardId)) - .filter((id): id is string => Boolean(id)) - - const contentStackRewards = await getCmsRewards( - ctx.lang, - getUniqueRewardIds(rewardIds) - ) - - if (!contentStackRewards) { - return [] - } - - const loyaltyLevelsConfig = await getAllLoyaltyLevels(ctx) - const levelsWithRewards = Object.entries(allApiRewards).map( - ([level, rewards]) => { - const combinedRewards = rewards - .filter((r) => (input.unique ? r?.rewardTierLevel === level : true)) - .map((reward) => { - const contentStackReward = contentStackRewards.find((r) => { - return r.reward_id === reward?.rewardId - }) - - if (contentStackReward) { - return contentStackReward - } else { - console.error("No contentStackReward found", reward?.rewardId) - } - }) - .filter((reward): reward is Reward => Boolean(reward)) - - const levelConfig = loyaltyLevelsConfig.find( - (l) => l.level_id === level - ) - - if (!levelConfig) { - getAllRewardFailCounter.add(1) - - console.error("contentstack.loyaltyLevels level not found") - throw notFound() - } - return { ...levelConfig, rewards: combinedRewards } - } - ) - - getAllRewardSuccessCounter.add(1) - return levelsWithRewards - }), surprises: contentStackBaseWithProtectedProcedure.query(async ({ ctx }) => { getCurrentRewardCounter.add(1) diff --git a/server/routers/contentstack/reward/utils.ts b/server/routers/contentstack/reward/utils.ts new file mode 100644 index 000000000..8aab49955 --- /dev/null +++ b/server/routers/contentstack/reward/utils.ts @@ -0,0 +1,168 @@ +import { metrics } from "@opentelemetry/api" +import { unstable_cache } from "next/cache" + +import { Lang } from "@/constants/languages" +import * as api from "@/lib/api" +import { GetRewards } from "@/lib/graphql/Query/Rewards.graphql" +import { request } from "@/lib/graphql/request" +import { notFound } from "@/server/errors/trpc" + +import { generateLoyaltyConfigTag } from "@/utils/generateTag" + +import { + CmsRewardsResponse, + validateApiTierRewardsSchema, + validateCmsRewardsSchema, +} from "./output" + +const meter = metrics.getMeter("trpc.reward") +export const getAllRewardCounter = meter.createCounter( + "trpc.contentstack.reward.all" +) +export const getAllRewardFailCounter = meter.createCounter( + "trpc.contentstack.reward.all-fail" +) +export const getAllRewardSuccessCounter = meter.createCounter( + "trpc.contentstack.reward.all-success" +) +export const getCurrentRewardCounter = meter.createCounter( + "trpc.contentstack.reward.current" +) +export const getCurrentRewardFailCounter = meter.createCounter( + "trpc.contentstack.reward.current-fail" +) +export const getCurrentRewardSuccessCounter = meter.createCounter( + "trpc.contentstack.reward.current-success" +) +export const getByLevelRewardCounter = meter.createCounter( + "trpc.contentstack.reward.byLevel" +) +export const getByLevelRewardFailCounter = meter.createCounter( + "trpc.contentstack.reward.byLevel-fail" +) +export const getByLevelRewardSuccessCounter = meter.createCounter( + "trpc.contentstack.reward.byLevel-success" +) + +const ONE_HOUR = 60 * 60 + +export function getUniqueRewardIds(rewardIds: string[]) { + const uniqueRewardIds = new Set(rewardIds) + return Array.from(uniqueRewardIds) +} + +/** + * Cached for 1 hour. + */ +export const getAllCachedApiRewards = unstable_cache( + async function (token) { + const apiResponse = await api.get(api.endpoints.v1.Profile.tierRewards, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + + if (!apiResponse.ok) { + const text = await apiResponse.text() + getAllRewardFailCounter.add(1, { + error_type: "http_error", + error: JSON.stringify({ + status: apiResponse.status, + statusText: apiResponse.statusText, + text, + }), + }) + console.error( + "api.rewards.tierRewards error ", + JSON.stringify({ + error: { + status: apiResponse.status, + statusText: apiResponse.statusText, + text, + }, + }) + ) + + throw apiResponse + } + + const data = await apiResponse.json() + const validatedApiTierRewards = validateApiTierRewardsSchema.safeParse(data) + + if (!validatedApiTierRewards.success) { + getAllRewardFailCounter.add(1, { + error_type: "validation_error", + error: JSON.stringify(validatedApiTierRewards.error), + }) + console.error(validatedApiTierRewards.error) + console.error( + "api.rewards validation error", + JSON.stringify({ + error: validatedApiTierRewards.error, + }) + ) + throw validatedApiTierRewards.error + } + + return validatedApiTierRewards.data + }, + ["getAllApiRewards"], + { revalidate: ONE_HOUR } +) + +export async function getCmsRewards(locale: Lang, rewardIds: string[]) { + const tags = rewardIds.map((id) => + generateLoyaltyConfigTag(locale, "reward", id) + ) + const cmsRewardsResponse = await request( + GetRewards, + { + locale: locale, + rewardIds, + }, + { next: { tags }, cache: "force-cache" } + ) + + if (!cmsRewardsResponse.data) { + getAllRewardFailCounter.add(1, { + lang: locale, + error_type: "validation_error", + error: JSON.stringify(cmsRewardsResponse.data), + }) + const notFoundError = notFound(cmsRewardsResponse) + console.error( + "contentstack.rewards not found error", + JSON.stringify({ + query: { + locale, + rewardIds, + }, + error: { code: notFoundError.code }, + }) + ) + throw notFoundError + } + + const validatedCmsRewards = + validateCmsRewardsSchema.safeParse(cmsRewardsResponse) + + if (!validatedCmsRewards.success) { + getAllRewardFailCounter.add(1, { + locale, + rewardIds, + error_type: "validation_error", + error: JSON.stringify(validatedCmsRewards.error), + }) + console.error(validatedCmsRewards.error) + console.error( + "contentstack.rewards validation error", + JSON.stringify({ + query: { locale, rewardIds }, + error: validatedCmsRewards.error, + }) + ) + return null + } + + return validatedCmsRewards.data +}