import { metrics } from "@opentelemetry/api" import { signupVerify } from "@/constants/routes/signup" import { env } from "@/env/server" import * as api from "@/lib/api" import { serverErrorByStatus } from "@/server/errors/trpc" import { initiateSaveCardSchema, subscriberIdSchema, } from "@/server/routers/user/output" import { protectedProcedure, router, serviceProcedure } from "@/server/trpc" import { addCreditCardInput, deleteCreditCardInput, saveCreditCardInput, signupInput, } from "./input" const meter = metrics.getMeter("trpc.user") const generatePreferencesLinkCounter = meter.createCounter( "trpc.user.generatePreferencesLink" ) const generatePreferencesLinkSuccessCounter = meter.createCounter( "trpc.user.generatePreferencesLink-success" ) const generatePreferencesLinkFailCounter = meter.createCounter( "trpc.user.generatePreferencesLink-fail" ) const signupCounter = meter.createCounter("trpc.user.signup") const signupSuccessCounter = meter.createCounter("trpc.user.signup-success") const signupFailCounter = meter.createCounter("trpc.user.signup-fail") export const userMutationRouter = router({ creditCard: router({ add: protectedProcedure.input(addCreditCardInput).mutation(async function ({ ctx, input, }) { console.info( "api.user.creditCard.add start", JSON.stringify({ query: { language: input.language } }) ) const apiResponse = await api.post( api.endpoints.v1.Profile.CreditCards.initiateSaveCard, { headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, body: { language: input.language, mobileToken: false, redirectUrl: `api/web/add-card-callback/${input.language}`, }, } ) if (!apiResponse.ok) { const text = await apiResponse.text() console.error( "api.user.creditCard.add error", JSON.stringify({ query: { language: input.language }, error: { status: apiResponse.status, statusText: apiResponse.statusText, error: text, }, }) ) return null } const apiJson = await apiResponse.json() const verifiedData = initiateSaveCardSchema.safeParse(apiJson) if (!verifiedData.success) { console.error( "api.user.creditCard.add validation error", JSON.stringify({ query: { language: input.language }, error: verifiedData.error, }) ) return null } console.info( "api.user.creditCard.add success", JSON.stringify({ query: { language: input.language } }) ) return verifiedData.data.data }), save: protectedProcedure .input(saveCreditCardInput) .mutation(async function ({ ctx, input }) { console.info("api.user.creditCard.save start", JSON.stringify({})) const apiResponse = await api.post( api.endpoints.v1.Profile.CreditCards.transaction(input.transactionId), { headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, } ) if (!apiResponse.ok) { const text = await apiResponse.text() console.error( "api.user.creditCard.save error", JSON.stringify({ error: { status: apiResponse.status, statusText: apiResponse.statusText, text, }, }) ) return false } console.info("api.user.creditCard.save success", JSON.stringify({})) return true }), delete: protectedProcedure .input(deleteCreditCardInput) .mutation(async function ({ ctx, input }) { console.info( "api.user.creditCard.delete start", JSON.stringify({ query: {} }) ) const apiResponse = await api.remove( api.endpoints.v1.Profile.CreditCards.deleteCreditCard( input.creditCardId ), { headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, } ) if (!apiResponse.ok) { const text = await apiResponse.text() console.error( "api.user.creditCard.delete error", JSON.stringify({ error: { status: apiResponse.status, statusText: apiResponse.statusText, text, }, query: {}, }) ) return false } console.info("api.user.creditCard.delete success", JSON.stringify({})) return true }), }), generatePreferencesLink: protectedProcedure.mutation(async function ({ ctx, }) { generatePreferencesLinkCounter.add(1) const apiResponse = await api.get(api.endpoints.v1.Profile.subscriberId, { headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, }) if (!apiResponse.ok) { const text = await apiResponse.text() generatePreferencesLinkFailCounter.add(1, { error_type: "http_error", error: JSON.stringify({ status: apiResponse.status, statusText: apiResponse.statusText, text, }), }) console.error( "api.user.subscriberId error ", JSON.stringify({ error: { status: apiResponse.status, statusText: apiResponse.statusText, text, }, }) ) return null } const data = await apiResponse.json() const validatedData = subscriberIdSchema.safeParse(data) if (!validatedData.success) { generatePreferencesLinkSuccessCounter.add(1, { error_type: "validation_error", error: JSON.stringify(validatedData.error), }) console.error( "api.user.generatePreferencesLink validation error", JSON.stringify({ error: validatedData.error, }) ) console.error(validatedData.error.format()) return null } const preferencesLink = new URL(env.SALESFORCE_PREFERENCE_BASE_URL) preferencesLink.searchParams.set("subKey", validatedData.data.subscriberId) generatePreferencesLinkSuccessCounter.add(1) return preferencesLink.toString() }), signup: serviceProcedure.input(signupInput).mutation(async function ({ ctx, input, }) { signupCounter.add(1) const apiResponse = await api.post(api.endpoints.v1.Profile.profile, { body: input, headers: { Authorization: `Bearer ${ctx.serviceToken}`, }, }) if (!apiResponse.ok) { const text = await apiResponse.text() signupFailCounter.add(1, { error_type: "http_error", error: JSON.stringify({ status: apiResponse.status, statusText: apiResponse.statusText, error: text, }), }) console.error( "api.user.signup api error", JSON.stringify({ error: { status: apiResponse.status, statusText: apiResponse.statusText, error: text, }, }) ) throw serverErrorByStatus(apiResponse.status, text) } signupSuccessCounter.add(1) console.info("api.user.signup success") return { success: true, redirectUrl: signupVerify[input.language], } }), })