import { metrics } from "@opentelemetry/api" import * as api from "@/lib/api" import { getVerifiedUser } from "@/server/routers/user/query" import { router, safeProtectedServiceProcedure } from "@/server/trpc" import { getMembership } from "@/utils/user" import { cancelBookingInput, createBookingInput, priceChangeInput, } from "./input" import { createBookingSchema } from "./output" import type { Session } from "next-auth" const meter = metrics.getMeter("trpc.bookings") const createBookingCounter = meter.createCounter("trpc.bookings.create") const createBookingSuccessCounter = meter.createCounter( "trpc.bookings.create-success" ) const createBookingFailCounter = meter.createCounter( "trpc.bookings.create-fail" ) const priceChangeCounter = meter.createCounter("trpc.bookings.price-change") const priceChangeSuccessCounter = meter.createCounter( "trpc.bookings.price-change-success" ) const priceChangeFailCounter = meter.createCounter( "trpc.bookings.price-change-fail" ) const cancelBookingCounter = meter.createCounter("trpc.bookings.cancel") const cancelBookingSuccessCounter = meter.createCounter( "trpc.bookings.cancel-success" ) const cancelBookingFailCounter = meter.createCounter( "trpc.bookings.cancel-fail" ) async function getMembershipNumber( session: Session | null ): Promise { if (!session) return undefined const verifiedUser = await getVerifiedUser({ session }) if (!verifiedUser || "error" in verifiedUser) { return undefined } const membership = getMembership(verifiedUser.data.memberships) return membership?.membershipNumber } export const bookingMutationRouter = router({ create: safeProtectedServiceProcedure .input(createBookingInput) .mutation(async function ({ ctx, input }) { const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const { language, ...inputWithoutLang } = input const { hotelId, checkInDate, checkOutDate } = inputWithoutLang const loggingAttributes = { membershipNumber: await getMembershipNumber(ctx.session), checkInDate, checkOutDate, hotelId, language, } createBookingCounter.add(1, loggingAttributes) console.info( "api.booking.create start", JSON.stringify({ query: loggingAttributes, }) ) const headers = { Authorization: `Bearer ${accessToken}`, } const apiResponse = await api.post( api.endpoints.v1.Booking.bookings, { headers, body: inputWithoutLang, }, { language } ) if (!apiResponse.ok) { const text = await apiResponse.text() createBookingFailCounter.add(1, { hotelId, checkInDate, checkOutDate, error_type: "http_error", error: JSON.stringify({ status: apiResponse.status, }), }) console.error( "api.booking.create error", JSON.stringify({ query: loggingAttributes, error: { status: apiResponse.status, statusText: apiResponse.statusText, error: text, }, }) ) return null } const apiJson = await apiResponse.json() const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { createBookingFailCounter.add(1, { hotelId, checkInDate, checkOutDate, error_type: "validation_error", }) console.error( "api.booking.create validation error", JSON.stringify({ query: loggingAttributes, error: verifiedData.error, }) ) return null } createBookingSuccessCounter.add(1, { hotelId, checkInDate, checkOutDate, }) console.info( "api.booking.create success", JSON.stringify({ query: loggingAttributes, }) ) return verifiedData.data }), priceChange: safeProtectedServiceProcedure .input(priceChangeInput) .mutation(async function ({ ctx, input }) { const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const { confirmationNumber } = input priceChangeCounter.add(1, { confirmationNumber }) const headers = { Authorization: `Bearer ${accessToken}`, } const apiResponse = await api.put( api.endpoints.v1.Booking.priceChange(confirmationNumber), { headers, body: input, } ) if (!apiResponse.ok) { const text = await apiResponse.text() priceChangeFailCounter.add(1, { confirmationNumber, error_type: "http_error", error: JSON.stringify({ status: apiResponse.status, }), }) console.error( "api.booking.priceChange error", JSON.stringify({ query: { confirmationNumber }, error: { status: apiResponse.status, statusText: apiResponse.statusText, error: text, }, }) ) return null } const apiJson = await apiResponse.json() const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { priceChangeFailCounter.add(1, { confirmationNumber, error_type: "validation_error", }) console.error( "api.booking.priceChange validation error", JSON.stringify({ query: { confirmationNumber }, error: verifiedData.error, }) ) return null } priceChangeSuccessCounter.add(1, { confirmationNumber }) return verifiedData.data }), cancel: safeProtectedServiceProcedure .input(cancelBookingInput) .mutation(async function ({ ctx, input }) { const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const { confirmationNumber, language } = input const headers = { Authorization: `Bearer ${accessToken}`, } const cancellationReason = { reasonCode: "WEB-CANCEL", reason: "WEB-CANCEL", } const loggingAttributes = { confirmationNumber, language, } cancelBookingCounter.add(1, loggingAttributes) console.info( "api.booking.cancel start", JSON.stringify({ request: loggingAttributes, headers, }) ) const apiResponse = await api.remove( api.endpoints.v1.Booking.cancel(confirmationNumber), { headers, body: JSON.stringify(cancellationReason), } as RequestInit, { language } ) if (!apiResponse.ok) { const text = await apiResponse.text() cancelBookingFailCounter.add(1, { confirmationNumber, error_type: "http_error", error: JSON.stringify({ status: apiResponse.status, }), }) console.error( "api.booking.cancel error", JSON.stringify({ error: { status: apiResponse.status, statusText: apiResponse.statusText, text, }, query: loggingAttributes, }) ) return false } const apiJson = await apiResponse.json() const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { cancelBookingFailCounter.add(1, { confirmationNumber, error_type: "validation_error", }) console.error( "api.booking.cancel validation error", JSON.stringify({ query: loggingAttributes, error: verifiedData.error, }) ) return null } cancelBookingSuccessCounter.add(1, loggingAttributes) console.info( "api.booking.cancel success", JSON.stringify({ query: loggingAttributes, }) ) return verifiedData.data }), })