Files
web/apps/scandic-web/server/routers/booking/utils.ts
Linus Flood f450f2a5db Merge master
2025-05-02 14:01:50 +02:00

219 lines
5.5 KiB
TypeScript

import * as api from "@/lib/api"
import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { getUserOrServiceToken } from "@/server/tokenManager"
import { toApiLang } from "@/server/utils"
import { getCacheClient } from "@/services/dataCache"
import { bookingSchema, createBookingSchema } from "./output"
import type { BookingSchema } from "@/types/trpc/routers/booking/confirmation"
import type { Lang } from "@/constants/languages"
export async function getBooking(confirmationNumber: string, lang: Lang) {
const getBookingCounter = createCounter("booking", "get")
const metricsGetBooking = getBookingCounter.init({ confirmationNumber })
metricsGetBooking.start()
const cacheKey = `${lang}:booking:${confirmationNumber}`
const cache = await getCacheClient()
const result: BookingSchema | null = await cache.cacheOrGet(
cacheKey,
async () => {
const token = getUserOrServiceToken()
const apiResponse = await api.get(
api.endpoints.v1.Booking.booking(confirmationNumber),
{
headers: {
Authorization: `Bearer ${token}`,
},
},
{ language: toApiLang(lang) }
)
if (!apiResponse.ok) {
await metricsGetBooking.httpError(apiResponse)
// 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 = bookingSchema.safeParse(apiJson)
if (!booking.success) {
metricsGetBooking.validationError(booking.error)
throw badRequestError()
}
return booking.data
},
"1h"
)
metricsGetBooking.success()
return result
}
export async function getBookings(confirmationNumbers: string[], lang: Lang) {
const results = await Promise.allSettled(
confirmationNumbers.map((confirmationNumber) => {
return getBooking(confirmationNumber, lang)
})
)
return results.map((result) => {
if (result.status === "fulfilled" && result.value) {
return result.value
}
return null
})
}
export async function getLinkedReservations(
confirmationNumber: string,
lang: Lang
) {
const booking = await getBooking(confirmationNumber, lang)
if (!booking) {
return null
}
if (booking.linkedReservations.length > 0) {
const confirmationNumbers = booking.linkedReservations.map(
(linkedReservation) => {
return linkedReservation.confirmationNumber
}
)
const bookings = await getBookings(confirmationNumbers, lang)
const linkedReservations = bookings.map((booking, i) => {
if (booking === null) {
return {
confirmationNumber: confirmationNumbers[i],
error: true,
} as const
}
return booking
})
return linkedReservations
}
return []
}
export async function cancelBooking(confirmationNumber: string, lang: Lang) {
const cancelBookingCounter = createCounter("booking", "cancel")
const metricsCancelBooking = cancelBookingCounter.init({
confirmationNumber,
lang,
})
metricsCancelBooking.start()
const token = getUserOrServiceToken()
const headers = {
Authorization: `Bearer ${token}`,
}
const booking = await getBooking(confirmationNumber, lang)
if (!booking) {
metricsCancelBooking.noDataError({ confirmationNumber })
return null
}
const { firstName, lastName, email } = booking.guest
const apiResponse = await api.remove(
api.endpoints.v1.Booking.cancel(confirmationNumber),
{
headers,
body: { firstName, lastName, email },
},
{ language: toApiLang(lang) }
)
if (!apiResponse.ok) {
await metricsCancelBooking.httpError(apiResponse)
return null
}
const apiJson = await apiResponse.json()
const verifiedData = createBookingSchema.safeParse(apiJson)
if (!verifiedData.success) {
metricsCancelBooking.validationError(verifiedData.error)
return null
}
metricsCancelBooking.success()
return verifiedData.data
}
export async function findBooking(
confirmationNumber: string,
lang: Lang,
token: string,
lastName?: string,
firstName?: string,
email?: string
) {
const findBookingCounter = createCounter("booking", "find")
const metricsGetBooking = findBookingCounter.init({
confirmationNumber,
lastName,
firstName,
email,
})
metricsGetBooking.start()
const apiResponse = await api.post(
api.endpoints.v1.Booking.find(confirmationNumber),
{
headers: {
Authorization: `Bearer ${token}`,
},
body: {
lastName,
firstName,
email,
},
},
{ language: toApiLang(lang) }
)
if (!apiResponse.ok) {
await metricsGetBooking.httpError(apiResponse)
// 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 = bookingSchema.safeParse(apiJson)
if (!booking.success) {
metricsGetBooking.validationError(booking.error)
throw badRequestError()
}
metricsGetBooking.success()
return booking.data
}