import * as api from "@/lib/api" import { getMembershipNumber } from "@/server/routers/user/utils" import { createCounter } from "@/server/telemetry" import { getUserOrServiceToken } from "@/server/tokenManager" import { router, safeProtectedServiceProcedure } from "@/server/trpc" import { parseRefId } from "@/utils/refId" import { addPackageInput, cancelBookingsInput, createBookingInput, guaranteeBookingInput, priceChangeInput, removePackageInput, updateBookingInput, } from "./input" import { bookingSchema, createBookingSchema } from "./output" import { cancelBooking } from "./utils" 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 createBookingCounter = createCounter("trpc.booking", "create") const metricsCreateBooking = createBookingCounter.init({ membershipNumber: await getMembershipNumber(ctx.session), checkInDate, checkOutDate, hotelId, language, }) metricsCreateBooking.start() const headers = { Authorization: `Bearer ${accessToken}`, } const apiResponse = await api.post( api.endpoints.v1.Booking.bookings, { headers, body: inputWithoutLang, }, { language } ) if (!apiResponse.ok) { await metricsCreateBooking.httpError(apiResponse) const apiJson = await apiResponse.json() if ("errors" in apiJson && apiJson.errors.length) { const error = apiJson.errors[0] return { error: true, cause: error.code } as const } return null } const apiJson = await apiResponse.json() const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { metricsCreateBooking.validationError(verifiedData.error) return null } metricsCreateBooking.success() return verifiedData.data }), priceChange: safeProtectedServiceProcedure .input(priceChangeInput) .use(async ({ input, next }) => { const { confirmationNumber } = parseRefId(input.refId) return next({ ctx: { confirmationNumber, }, }) }) .mutation(async function ({ ctx, input }) { const { confirmationNumber } = ctx const priceChangeCounter = createCounter("trpc.booking", "price-change") const metricsPriceChange = priceChangeCounter.init({ confirmationNumber }) metricsPriceChange.start() const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const headers = { Authorization: `Bearer ${accessToken}`, } const apiResponse = await api.put( api.endpoints.v1.Booking.priceChange(confirmationNumber), { headers, body: input, } ) if (!apiResponse.ok) { await metricsPriceChange.httpError(apiResponse) return null } const apiJson = await apiResponse.json() const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { metricsPriceChange.validationError(verifiedData.error) return null } metricsPriceChange.success() return verifiedData.data.id }), cancel: safeProtectedServiceProcedure .input(cancelBookingsInput) .use(async ({ input, next }) => { const confirmationNumbers = input.refIds.map((refId) => { const { confirmationNumber } = parseRefId(refId) return confirmationNumber }) return next({ ctx: { confirmationNumbers, }, }) }) .mutation(async function ({ ctx, input }) { const { confirmationNumbers } = ctx const { lang } = input const responses = await Promise.allSettled( confirmationNumbers.map((confirmationNumber) => cancelBooking(confirmationNumber, lang) ) ) const cancelledRoomsSuccessfully = [] for (const [idx, response] of responses.entries()) { if (response.status === "fulfilled") { if (response.value) { cancelledRoomsSuccessfully.push(true) continue } } else { console.info( `Cancelling booking failed for confirmationNumber: ${confirmationNumbers[idx]}` ) console.error(response.reason) } cancelledRoomsSuccessfully.push(false) } return cancelledRoomsSuccessfully }), packages: safeProtectedServiceProcedure .input(addPackageInput) .use(async ({ input, next }) => { const { confirmationNumber } = parseRefId(input.refId) return next({ ctx: { confirmationNumber, }, }) }) .mutation(async function ({ ctx, input }) { const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const { refId, ...body } = input const { confirmationNumber } = ctx const addPackageCounter = createCounter("trpc.booking", "package.add") const metricsAddPackage = addPackageCounter.init({ confirmationNumber }) metricsAddPackage.start() const headers = { Authorization: `Bearer ${accessToken}`, } const apiResponse = await api.post( api.endpoints.v1.Booking.packages(confirmationNumber), { headers, body: body, } ) if (!apiResponse.ok) { await metricsAddPackage.httpError(apiResponse) return null } const apiJson = await apiResponse.json() const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { metricsAddPackage.validationError(verifiedData.error) return null } metricsAddPackage.success() return verifiedData.data }), guarantee: safeProtectedServiceProcedure .input(guaranteeBookingInput) .use(async ({ input, next }) => { const { confirmationNumber } = parseRefId(input.refId) return next({ ctx: { confirmationNumber, }, }) }) .mutation(async function ({ ctx, input }) { const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const { refId, language, ...body } = input const { confirmationNumber } = ctx const guaranteeBookingCounter = createCounter("trpc.booking", "guarantee") const metricsGuaranteeBooking = guaranteeBookingCounter.init({ confirmationNumber, }) metricsGuaranteeBooking.start() const headers = { Authorization: `Bearer ${accessToken}`, } const apiResponse = await api.put( api.endpoints.v1.Booking.guarantee(confirmationNumber), { headers, body: body, }, { language } ) if (!apiResponse.ok) { await metricsGuaranteeBooking.httpError(apiResponse) return null } const apiJson = await apiResponse.json() const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { metricsGuaranteeBooking.validationError(verifiedData.error) return null } metricsGuaranteeBooking.success() return verifiedData.data }), update: safeProtectedServiceProcedure .input(updateBookingInput) .use(async ({ input, next }) => { const { confirmationNumber } = parseRefId(input.refId) return next({ ctx: { confirmationNumber, }, }) }) .mutation(async function ({ ctx, input }) { const { confirmationNumber } = ctx const updateBookingCounter = createCounter("trpc.booking", "update") const metricsUpdateBooking = updateBookingCounter.init({ confirmationNumber, }) metricsUpdateBooking.start() const token = getUserOrServiceToken() const apiResponse = await api.put( api.endpoints.v1.Booking.booking(confirmationNumber), { body: { checkInDate: input.checkInDate, checkOutDate: input.checkOutDate, guest: input.guest, }, headers: { Authorization: `Bearer ${token}`, }, } ) if (!apiResponse.ok) { await metricsUpdateBooking.httpError(apiResponse) return null } const apiJson = await apiResponse.json() const verifiedData = bookingSchema.safeParse(apiJson) if (!verifiedData.success) { metricsUpdateBooking.validationError(verifiedData.error) return null } metricsUpdateBooking.success() return verifiedData.data.refId }), removePackage: safeProtectedServiceProcedure .input(removePackageInput) .use(async ({ input, next }) => { const { confirmationNumber } = parseRefId(input.refId) return next({ ctx: { confirmationNumber, }, }) }) .mutation(async function ({ ctx, input }) { const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const { codes, language } = input const { confirmationNumber } = ctx const removePackageCounter = createCounter( "trpc.booking", "package.remove" ) const metricsRemovePackage = removePackageCounter.init({ confirmationNumber, codes, language, }) metricsRemovePackage.start() const headers = { Authorization: `Bearer ${accessToken}`, } const apiResponse = await api.remove( api.endpoints.v1.Booking.packages(confirmationNumber), { headers, }, [["language", language], ...codes.map((code) => ["codes", code])] ) if (!apiResponse.ok) { await metricsRemovePackage.httpError(apiResponse) return false } metricsRemovePackage.success() return true }), })