import { metrics } from "@opentelemetry/api" import * as api from "@/lib/api" import { dt } from "@/lib/dt" import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc" import { router, safeProtectedServiceProcedure, serviceProcedure, } from "@/server/trpc" import { getHotel } from "../hotels/query" import { bookingConfirmationInput, getBookingStatusInput } from "./input" import { bookingConfirmationSchema, createBookingSchema } from "./output" import { getBookedHotelRoom } from "./utils" const meter = metrics.getMeter("trpc.booking") const getBookingConfirmationCounter = meter.createCounter( "trpc.booking.confirmation" ) const getBookingConfirmationSuccessCounter = meter.createCounter( "trpc.booking.confirmation-success" ) const getBookingConfirmationFailCounter = meter.createCounter( "trpc.booking.confirmation-fail" ) const getBookingStatusCounter = meter.createCounter("trpc.booking.status") const getBookingStatusSuccessCounter = meter.createCounter( "trpc.booking.status-success" ) const getBookingStatusFailCounter = meter.createCounter( "trpc.booking.status-fail" ) export const bookingQueryRouter = router({ confirmation: safeProtectedServiceProcedure .input(bookingConfirmationInput) .query(async function ({ ctx, input: { confirmationNumber } }) { getBookingConfirmationCounter.add(1, { confirmationNumber }) const token = ctx.session?.token.access_token ?? ctx.serviceToken const apiResponse = await api.get( api.endpoints.v1.Booking.booking(confirmationNumber), { headers: { Authorization: `Bearer ${token}`, }, } ) if (!apiResponse.ok) { const responseMessage = await apiResponse.text() getBookingConfirmationFailCounter.add(1, { confirmationNumber, error_type: "http_error", error: responseMessage, }) console.error( "api.booking.confirmation error", JSON.stringify({ query: { confirmationNumber }, error: { status: apiResponse.status, statusText: apiResponse.statusText, text: responseMessage, }, }) ) // If the booking is not found, return null. // This scenario is expected to happen when a logged in user trying to access a booking that doesn't belong to them. if (apiResponse.status === 400) { return null } throw serverErrorByStatus(apiResponse.status, apiResponse) } const apiJson = await apiResponse.json() const booking = bookingConfirmationSchema.safeParse(apiJson) if (!booking.success) { getBookingConfirmationFailCounter.add(1, { confirmationNumber, error_type: "validation_error", error: JSON.stringify(booking.error), }) console.error( "api.booking.confirmation validation error", JSON.stringify({ query: { confirmationNumber }, error: booking.error, }) ) throw badRequestError() } const hotelData = await getHotel( { hotelId: booking.data.hotelId, isCardOnlyPayment: false, language: ctx.lang, }, ctx.serviceToken ) if (!hotelData) { getBookingConfirmationFailCounter.add(1, { confirmationNumber, hotelId: booking.data.hotelId, error_type: "http_error", error: "Couldn`t get hotel", }) console.error( "api.booking.confirmation error", JSON.stringify({ query: { confirmationNumber, hotelId: booking.data.hotelId }, error: { status: apiResponse.status, statusText: apiResponse.statusText, text: "Couldn`t get hotel", }, }) ) throw serverErrorByStatus(404) } getBookingConfirmationSuccessCounter.add(1, { confirmationNumber }) console.info( "api.booking.confirmation success", JSON.stringify({ query: { confirmationNumber }, }) ) /** * Add hotels check in and out times to booking check in and out date * as that is date only (YYYY-MM-DD) */ const checkInTime = hotelData.hotel.hotelFacts.checkin.checkInTime const [checkInHour, checkInMinute] = checkInTime.split(":") const checkIn = dt(booking.data.checkInDate) .set("hour", Number(checkInHour)) .set("minute", Number(checkInMinute)) const checkOutTime = hotelData.hotel.hotelFacts.checkin.checkOutTime const [checkOutHour, checkOutMinute] = checkOutTime.split(":") const checkOut = dt(booking.data.checkOutDate) .set("hour", Number(checkOutHour)) .set("minute", Number(checkOutMinute)) booking.data.checkInDate = checkIn.toDate() booking.data.checkOutDate = checkOut.toDate() return { ...hotelData, booking: booking.data, room: getBookedHotelRoom( hotelData.roomCategories, booking.data.roomTypeCode ), } }), status: serviceProcedure.input(getBookingStatusInput).query(async function ({ ctx, input, }) { const { confirmationNumber } = input getBookingStatusCounter.add(1, { confirmationNumber }) const apiResponse = await api.get( api.endpoints.v1.Booking.status(confirmationNumber), { headers: { Authorization: `Bearer ${ctx.serviceToken}`, }, } ) if (!apiResponse.ok) { const responseMessage = await apiResponse.text() getBookingStatusFailCounter.add(1, { confirmationNumber, error_type: "http_error", error: responseMessage, }) console.error( "api.booking.status error", JSON.stringify({ query: { confirmationNumber }, error: { status: apiResponse.status, statusText: apiResponse.statusText, text: responseMessage, }, }) ) throw serverErrorByStatus(apiResponse.status, apiResponse) } const apiJson = await apiResponse.json() const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { getBookingStatusFailCounter.add(1, { confirmationNumber, error_type: "validation_error", error: JSON.stringify(verifiedData.error), }) console.error( "api.booking.status validation error", JSON.stringify({ query: { confirmationNumber }, error: verifiedData.error, }) ) throw badRequestError() } getBookingStatusSuccessCounter.add(1, { confirmationNumber }) console.info( "api.booking.status success", JSON.stringify({ query: { confirmationNumber }, }) ) return verifiedData.data }), })