import { metrics } from "@opentelemetry/api" import { unstable_cache } from "next/cache" import { env } from "@/env/server" import * as api from "@/lib/api" import { GetRewards } from "@/lib/graphql/Query/Rewards.graphql" import { GetRewards as GetRewardsWithReedem } from "@/lib/graphql/Query/RewardsWithRedeem.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" import { generateLoyaltyConfigTag } from "@/utils/generateTag" import { type CmsRewardsResponse, type CmsRewardsWithRedeemResponse, validateApiAllTiersSchema, validateApiTierRewardsSchema, validateCmsRewardsSchema, validateCmsRewardsWithRedeemSchema, } from "./output" import type { Lang } from "@/constants/languages" 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" ) export const getUnwrapSurpriseCounter = meter.createCounter( "trpc.contentstack.reward.unwrap" ) export const getUnwrapSurpriseFailCounter = meter.createCounter( "trpc.contentstack.reward.unwrap-fail" ) export const getUnwrapSurpriseSuccessCounter = meter.createCounter( "trpc.contentstack.reward.unwrap-success" ) export const getRedeemCounter = meter.createCounter( "trpc.contentstack.reward.redeem" ) export const getRedeemFailCounter = meter.createCounter( "trpc.contentstack.reward.redeem-fail" ) export const getRedeemSuccessCounter = meter.createCounter( "trpc.contentstack.reward.redeem-success" ) const ONE_HOUR = 60 * 60 export function getUniqueRewardIds(rewardIds: string[]) { const uniqueRewardIds = new Set(rewardIds) return Array.from(uniqueRewardIds) } /** * 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) { 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 } ) /** * 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) ) const cmsRewardsResponse = env.USE_NEW_REWARD_MODEL ? await request( GetRewardsWithReedem, { locale: locale, rewardIds, }, { next: { tags }, cache: "force-cache" } ) : 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 = env.USE_NEW_REWARD_MODEL ? validateCmsRewardsWithRedeemSchema.safeParse(cmsRewardsResponse) : 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 }