feat(SW-739): use new allTiers endpoint and add feature flag

This commit is contained in:
Chuma McPhoy
2024-11-08 14:50:18 +01:00
parent 35a527be05
commit 48af26a772
7 changed files with 107 additions and 8 deletions

View File

@@ -52,3 +52,4 @@ GOOGLE_STATIC_MAP_ID=""
GOOGLE_DYNAMIC_MAP_ID="" GOOGLE_DYNAMIC_MAP_ID=""
HIDE_FOR_NEXT_RELEASE="true" HIDE_FOR_NEXT_RELEASE="true"
USE_NEW_REWARDS_ENDPOINT="true"

View File

@@ -43,3 +43,4 @@ GOOGLE_STATIC_MAP_ID="test"
GOOGLE_DYNAMIC_MAP_ID="test" GOOGLE_DYNAMIC_MAP_ID="test"
HIDE_FOR_NEXT_RELEASE="true" HIDE_FOR_NEXT_RELEASE="true"
SALESFORCE_PREFERENCE_BASE_URL="test" SALESFORCE_PREFERENCE_BASE_URL="test"
USE_NEW_REWARDS_ENDPOINT="true"

8
env/server.ts vendored
View File

@@ -72,6 +72,13 @@ export const env = createEnv({
.refine((s) => s === "true" || s === "false") .refine((s) => s === "true" || s === "false")
// transform to boolean // transform to boolean
.transform((s) => s === "true"), .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, emptyStringAsUndefined: true,
runtimeEnv: { runtimeEnv: {
@@ -126,5 +133,6 @@ export const env = createEnv({
GOOGLE_STATIC_MAP_ID: process.env.GOOGLE_STATIC_MAP_ID, GOOGLE_STATIC_MAP_ID: process.env.GOOGLE_STATIC_MAP_ID,
GOOGLE_DYNAMIC_MAP_ID: process.env.GOOGLE_DYNAMIC_MAP_ID, GOOGLE_DYNAMIC_MAP_ID: process.env.GOOGLE_DYNAMIC_MAP_ID,
HIDE_FOR_NEXT_RELEASE: process.env.HIDE_FOR_NEXT_RELEASE, HIDE_FOR_NEXT_RELEASE: process.env.HIDE_FOR_NEXT_RELEASE,
USE_NEW_REWARDS_ENDPOINT: process.env.USE_NEW_REWARDS_ENDPOINT,
}, },
}) })

View File

@@ -151,8 +151,10 @@ export namespace endpoints {
export const invalidateSessions = `${base.path.profile}/${version}/${base.enitity.Profile}/invalidateSessions` export const invalidateSessions = `${base.path.profile}/${version}/${base.enitity.Profile}/invalidateSessions`
export const membership = `${base.path.profile}/${version}/${base.enitity.Profile}/membership` export const membership = `${base.path.profile}/${version}/${base.enitity.Profile}/membership`
export const profile = `${base.path.profile}/${version}/${base.enitity.Profile}` 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` 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 const tierRewards = `${base.path.profile}/${version}/${base.enitity.Profile}/tierRewards`
export function deleteProfile(profileId: string) { export function deleteProfile(profileId: string) {
@@ -172,9 +174,11 @@ export namespace endpoints {
} }
export namespace Reward { 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 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) { export function claim(rewardId: string) {
return `${base.path.profile}/${version}/${base.enitity.Reward}/Claim/${rewardId}` return `${base.path.profile}/${version}/${base.enitity.Reward}/Claim/${rewardId}`

View File

@@ -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 export const validateCmsRewardsSchema = z
.object({ .object({
data: z.object({ data: z.object({

View File

@@ -1,3 +1,4 @@
import { env } from "@/env/server"
import * as api from "@/lib/api" import * as api from "@/lib/api"
import { notFound } from "@/server/errors/trpc" import { notFound } from "@/server/errors/trpc"
import { import {
@@ -22,6 +23,7 @@ import {
getByLevelRewardCounter, getByLevelRewardCounter,
getByLevelRewardFailCounter, getByLevelRewardFailCounter,
getByLevelRewardSuccessCounter, getByLevelRewardSuccessCounter,
getCachedAllTierRewards,
getCmsRewards, getCmsRewards,
getCurrentRewardCounter, getCurrentRewardCounter,
getCurrentRewardFailCounter, getCurrentRewardFailCounter,
@@ -36,7 +38,10 @@ export const rewardQueryRouter = router({
.input(rewardsAllInput) .input(rewardsAllInput)
.query(async function ({ input, ctx }) { .query(async function ({ input, ctx }) {
getAllRewardCounter.add(1) 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) { if (!allApiRewards) {
return [] return []
@@ -96,9 +101,9 @@ export const rewardQueryRouter = router({
getByLevelRewardCounter.add(1) getByLevelRewardCounter.add(1)
const { level_id } = input const { level_id } = input
const allUpcomingApiRewards = await getAllCachedApiRewards( const allUpcomingApiRewards = !!env.USE_NEW_REWARDS_ENDPOINT
ctx.serviceToken ? await getCachedAllTierRewards(ctx.serviceToken)
) : await getAllCachedApiRewards(ctx.serviceToken)
if (!allUpcomingApiRewards || !allUpcomingApiRewards[level_id]) { if (!allUpcomingApiRewards || !allUpcomingApiRewards[level_id]) {
getByLevelRewardFailCounter.add(1) getByLevelRewardFailCounter.add(1)

View File

@@ -11,6 +11,7 @@ import { generateLoyaltyConfigTag } from "@/utils/generateTag"
import { import {
CmsRewardsResponse, CmsRewardsResponse,
validateApiAllTiersSchema,
validateApiTierRewardsSchema, validateApiTierRewardsSchema,
validateCmsRewardsSchema, validateCmsRewardsSchema,
} from "./output" } 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( export const getAllCachedApiRewards = unstable_cache(
async function (token) { async function (token) {
@@ -110,6 +112,68 @@ export const getAllCachedApiRewards = unstable_cache(
{ revalidate: ONE_HOUR } { 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[]) { export async function getCmsRewards(locale: Lang, rewardIds: string[]) {
const tags = rewardIds.map((id) => const tags = rewardIds.map((id) =>
generateLoyaltyConfigTag(locale, "reward", id) generateLoyaltyConfigTag(locale, "reward", id)