diff --git a/server/routers/contentstack/reward/output.ts b/server/routers/contentstack/reward/output.ts index a6687b92a..8e954e656 100644 --- a/server/routers/contentstack/reward/output.ts +++ b/server/routers/contentstack/reward/output.ts @@ -91,22 +91,6 @@ 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({ @@ -138,3 +122,61 @@ export type SurpriseReward = z.output export type CmsRewardsResponse = z.input export type Reward = z.output[0] + +// New endpoint related types and schemas. + +const BenefitReward = z.object({ + title: z.string().optional(), + id: z.string().optional(), + status: z.string().optional(), + rewardId: z.string().optional(), + rewardType: z.string().optional(), + rewardTierLevel: z.string().optional(), +}) + +const CouponState = z.enum(["claimed", "redeemed", "viewed"]) +const CouponData = z.object({ + couponCode: z.string().optional(), + unwrapped: z.boolean().default(false), + state: CouponState, + expiresAt: z.string().datetime({ offset: true }).optional(), +}) + +const CouponReward = z.object({ + title: z.string().optional(), + id: z.string().optional(), + rewardId: z.string().optional(), + rewardType: z.string().optional(), + status: z.string().optional(), + coupon: z.array(CouponData).optional(), +}) + +/** + * Schema for the new /profile/v1/Reward endpoint. + * + * TODO: Once we fully migrate to the new endpoint: + * 1. Remove the data transform and use the categorized structure directly. + * 2. Simplify surprise filtering in the query. + */ +export const validateCategorizedRewardsSchema = z + .object({ + benefits: z.array(BenefitReward), + coupons: z.array(CouponReward), + }) + .transform((data) => [ + ...data.benefits.map((benefit) => ({ + ...benefit, + type: "custom" as const, // Added for legacy compatibility. + })), + ...data.coupons.map((coupon) => ({ + ...coupon, + type: "coupon" as const, // Added for legacy compatibility. + })), + ]) + +export const validateApiAllTiersSchema = z.record( + z.nativeEnum(TierKey).transform((data) => { + return TierKey[data as unknown as Key] + }), + z.array(BenefitReward) +) diff --git a/server/routers/contentstack/reward/query.ts b/server/routers/contentstack/reward/query.ts index b09cb320e..565ead0a5 100644 --- a/server/routers/contentstack/reward/query.ts +++ b/server/routers/contentstack/reward/query.ts @@ -14,7 +14,12 @@ import { rewardsCurrentInput, rewardsUpdateInput, } from "./input" -import { Reward, SurpriseReward, validateApiRewardSchema } from "./output" +import { + Reward, + SurpriseReward, + validateApiRewardSchema, + validateCategorizedRewardsSchema, +} from "./output" import { getAllCachedApiRewards, getAllRewardCounter, @@ -33,6 +38,8 @@ import { import { Surprise } from "@/types/components/blocks/surprises" +const ONE_HOUR = 60 * 60 + export const rewardQueryRouter = router({ all: contentStackBaseWithServiceProcedure .input(rewardsAllInput) @@ -154,12 +161,17 @@ export const rewardQueryRouter = router({ const { limit, cursor } = input - const apiResponse = await api.get(api.endpoints.v1.Profile.reward, { + const isNewEndpoint = !!env.USE_NEW_REWARDS_ENDPOINT + const endpoint = isNewEndpoint + ? api.endpoints.v1.Profile.Reward.reward + : api.endpoints.v1.Profile.reward + + const apiResponse = await api.get(endpoint, { cache: undefined, // override defaultOptions headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, - next: { revalidate: 60 * 60 }, + next: { revalidate: ONE_HOUR }, }) if (!apiResponse.ok) { @@ -186,8 +198,9 @@ export const rewardQueryRouter = router({ } const data = await apiResponse.json() - - const validatedApiRewards = validateApiRewardSchema.safeParse(data) + const validatedApiRewards = isNewEndpoint + ? validateCategorizedRewardsSchema.safeParse(data) + : validateApiRewardSchema.safeParse(data) if (!validatedApiRewards.success) { getCurrentRewardFailCounter.add(1, { @@ -243,12 +256,17 @@ export const rewardQueryRouter = router({ surprises: contentStackBaseWithProtectedProcedure.query(async ({ ctx }) => { getCurrentRewardCounter.add(1) - const apiResponse = await api.get(api.endpoints.v1.Profile.reward, { - cache: undefined, // override defaultOptions + const isNewEndpoint = !!env.USE_NEW_REWARDS_ENDPOINT + const endpoint = isNewEndpoint + ? api.endpoints.v1.Profile.Reward.reward + : api.endpoints.v1.Profile.reward + + const apiResponse = await api.get(endpoint, { + cache: undefined, headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, - next: { revalidate: 60 * 60 }, + next: { revalidate: ONE_HOUR }, }) if (!apiResponse.ok) { @@ -275,8 +293,9 @@ export const rewardQueryRouter = router({ } const data = await apiResponse.json() - - const validatedApiRewards = validateApiRewardSchema.safeParse(data) + const validatedApiRewards = isNewEndpoint + ? validateCategorizedRewardsSchema.safeParse(data) + : validateApiRewardSchema.safeParse(data) if (!validatedApiRewards.success) { getCurrentRewardFailCounter.add(1, { diff --git a/server/routers/contentstack/reward/utils.ts b/server/routers/contentstack/reward/utils.ts index 8f1e50343..d06f2666b 100644 --- a/server/routers/contentstack/reward/utils.ts +++ b/server/routers/contentstack/reward/utils.ts @@ -53,8 +53,8 @@ export function getUniqueRewardIds(rewardIds: string[]) { } /** - * Uses profile/v1/Profile/tierRewards. - * Will be removed when new endpoint is out in production. + * Uses the legacy profile/v1/Profile/tierRewards endpoint. + * TODO: Delete when the new endpoint is out in production. */ export const getAllCachedApiRewards = unstable_cache( async function (token) {