From 48af26a772c7d78ac57db688190486bbb4fe6a76 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Fri, 8 Nov 2024 14:50:18 +0100 Subject: [PATCH] feat(SW-739): use new allTiers endpoint and add feature flag --- .env.local.example | 1 + .env.test | 1 + env/server.ts | 8 +++ lib/api/endpoints.ts | 10 ++- server/routers/contentstack/reward/output.ts | 16 +++++ server/routers/contentstack/reward/query.ts | 13 ++-- server/routers/contentstack/reward/utils.ts | 66 +++++++++++++++++++- 7 files changed, 107 insertions(+), 8 deletions(-) diff --git a/.env.local.example b/.env.local.example index ab0b7ee72..e1b8e182f 100644 --- a/.env.local.example +++ b/.env.local.example @@ -52,3 +52,4 @@ GOOGLE_STATIC_MAP_ID="" GOOGLE_DYNAMIC_MAP_ID="" HIDE_FOR_NEXT_RELEASE="true" +USE_NEW_REWARDS_ENDPOINT="true" diff --git a/.env.test b/.env.test index ce2b20278..f651cbe63 100644 --- a/.env.test +++ b/.env.test @@ -43,3 +43,4 @@ GOOGLE_STATIC_MAP_ID="test" GOOGLE_DYNAMIC_MAP_ID="test" HIDE_FOR_NEXT_RELEASE="true" SALESFORCE_PREFERENCE_BASE_URL="test" +USE_NEW_REWARDS_ENDPOINT="true" diff --git a/env/server.ts b/env/server.ts index c4b9355b7..1f81557ce 100644 --- a/env/server.ts +++ b/env/server.ts @@ -72,6 +72,13 @@ export const env = createEnv({ .refine((s) => s === "true" || s === "false") // transform to boolean .transform((s) => s === "true"), + USE_NEW_REWARDS_ENDPOINT: z + .string() + // only allow "true" or "false" + .refine((s) => s === "true" || s === "false") + // transform to boolean + .transform((s) => s === "true") + .default("false"), }, emptyStringAsUndefined: true, runtimeEnv: { @@ -126,5 +133,6 @@ export const env = createEnv({ GOOGLE_STATIC_MAP_ID: process.env.GOOGLE_STATIC_MAP_ID, GOOGLE_DYNAMIC_MAP_ID: process.env.GOOGLE_DYNAMIC_MAP_ID, HIDE_FOR_NEXT_RELEASE: process.env.HIDE_FOR_NEXT_RELEASE, + USE_NEW_REWARDS_ENDPOINT: process.env.USE_NEW_REWARDS_ENDPOINT, }, }) diff --git a/lib/api/endpoints.ts b/lib/api/endpoints.ts index 100fb7518..4e5258db0 100644 --- a/lib/api/endpoints.ts +++ b/lib/api/endpoints.ts @@ -151,8 +151,10 @@ export namespace endpoints { export const invalidateSessions = `${base.path.profile}/${version}/${base.enitity.Profile}/invalidateSessions` export const membership = `${base.path.profile}/${version}/${base.enitity.Profile}/membership` export const profile = `${base.path.profile}/${version}/${base.enitity.Profile}` - export const reward = `${base.path.profile}/${version}/${base.enitity.Profile}/reward` export const subscriberId = `${base.path.profile}/${version}/${base.enitity.Profile}/SubscriberId` + + // TODO: Remove once new endpoints are out in production. + export const reward = `${base.path.profile}/${version}/${base.enitity.Profile}/reward` export const tierRewards = `${base.path.profile}/${version}/${base.enitity.Profile}/tierRewards` export function deleteProfile(profileId: string) { @@ -172,9 +174,11 @@ export namespace endpoints { } export namespace Reward { - export const allTiers = `${base.path.profile}/${version}/${base.enitity.Reward}/AllTiers` + export const allTiers = `${base.path.profile}/${version}/${base.enitity.Reward}/allTiers` export const reward = `${base.path.profile}/${version}/${base.enitity.Reward}` - export const unwrap = `${base.path.profile}/${version}/${base.enitity.Reward}/Unwrap` + export const redeem = `${base.path.profile}/${version}/${base.enitity.Reward}/redeem` + export const unwrap = `${base.path.profile}/${version}/${base.enitity.Reward}/unwrap` + // TODO: add surprise endpoint once available. export function claim(rewardId: string) { return `${base.path.profile}/${version}/${base.enitity.Reward}/Claim/${rewardId}` diff --git a/server/routers/contentstack/reward/output.ts b/server/routers/contentstack/reward/output.ts index 5bd2c7d75..a6687b92a 100644 --- a/server/routers/contentstack/reward/output.ts +++ b/server/routers/contentstack/reward/output.ts @@ -91,6 +91,22 @@ export const validateApiTierRewardsSchema = z.record( ) ) +export const validateApiAllTiersSchema = z.record( + z.nativeEnum(TierKey).transform((data) => { + return TierKey[data as unknown as Key] + }), + z.array( + z.object({ + id: z.string().optional(), + status: z.string().optional(), + rewardId: z.string().optional(), + rewardTierLevel: z.string().optional(), + rewardType: z.string().optional(), + title: z.string().optional(), + }) + ) +) + export const validateCmsRewardsSchema = z .object({ data: z.object({ diff --git a/server/routers/contentstack/reward/query.ts b/server/routers/contentstack/reward/query.ts index 0c1577182..b09cb320e 100644 --- a/server/routers/contentstack/reward/query.ts +++ b/server/routers/contentstack/reward/query.ts @@ -1,3 +1,4 @@ +import { env } from "@/env/server" import * as api from "@/lib/api" import { notFound } from "@/server/errors/trpc" import { @@ -22,6 +23,7 @@ import { getByLevelRewardCounter, getByLevelRewardFailCounter, getByLevelRewardSuccessCounter, + getCachedAllTierRewards, getCmsRewards, getCurrentRewardCounter, getCurrentRewardFailCounter, @@ -36,7 +38,10 @@ export const rewardQueryRouter = router({ .input(rewardsAllInput) .query(async function ({ input, ctx }) { getAllRewardCounter.add(1) - const allApiRewards = await getAllCachedApiRewards(ctx.serviceToken) + + const allApiRewards = !!env.USE_NEW_REWARDS_ENDPOINT + ? await getCachedAllTierRewards(ctx.serviceToken) + : await getAllCachedApiRewards(ctx.serviceToken) if (!allApiRewards) { return [] @@ -96,9 +101,9 @@ export const rewardQueryRouter = router({ getByLevelRewardCounter.add(1) const { level_id } = input - const allUpcomingApiRewards = await getAllCachedApiRewards( - ctx.serviceToken - ) + const allUpcomingApiRewards = !!env.USE_NEW_REWARDS_ENDPOINT + ? await getCachedAllTierRewards(ctx.serviceToken) + : await getAllCachedApiRewards(ctx.serviceToken) if (!allUpcomingApiRewards || !allUpcomingApiRewards[level_id]) { getByLevelRewardFailCounter.add(1) diff --git a/server/routers/contentstack/reward/utils.ts b/server/routers/contentstack/reward/utils.ts index 8aab49955..8f1e50343 100644 --- a/server/routers/contentstack/reward/utils.ts +++ b/server/routers/contentstack/reward/utils.ts @@ -11,6 +11,7 @@ import { generateLoyaltyConfigTag } from "@/utils/generateTag" import { CmsRewardsResponse, + validateApiAllTiersSchema, validateApiTierRewardsSchema, validateCmsRewardsSchema, } from "./output" @@ -52,7 +53,8 @@ export function getUniqueRewardIds(rewardIds: string[]) { } /** - * Cached for 1 hour. + * Uses profile/v1/Profile/tierRewards. + * Will be removed when new endpoint is out in production. */ export const getAllCachedApiRewards = unstable_cache( async function (token) { @@ -110,6 +112,68 @@ export const getAllCachedApiRewards = unstable_cache( { revalidate: ONE_HOUR } ) +/** + * Cached for 1 hour. + */ +export const getCachedAllTierRewards = unstable_cache( + async function (token) { + const apiResponse = await api.get( + api.endpoints.v1.Profile.Reward.allTiers, + { + 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.allTiers error ", + JSON.stringify({ + error: { + status: apiResponse.status, + statusText: apiResponse.statusText, + text, + }, + }) + ) + + throw apiResponse + } + + const data = await apiResponse.json() + const validatedApiAllTierRewards = validateApiAllTiersSchema.safeParse(data) + + if (!validatedApiAllTierRewards.success) { + getAllRewardFailCounter.add(1, { + error_type: "validation_error", + error: JSON.stringify(validatedApiAllTierRewards.error), + }) + console.error(validatedApiAllTierRewards.error) + console.error( + "api.rewards validation error", + JSON.stringify({ + error: validatedApiAllTierRewards.error, + }) + ) + throw validatedApiAllTierRewards.error + } + + return validatedApiAllTierRewards.data + }, + ["getApiAllTierRewards"], + { revalidate: ONE_HOUR } +) + export async function getCmsRewards(locale: Lang, rewardIds: string[]) { const tags = rewardIds.map((id) => generateLoyaltyConfigTag(locale, "reward", id)