Files
web/apps/scandic-web/server/routers/booking/mutation.ts
Anton Gunnarsson c56a0b8ce9 Merged in feat/sw-1975-get-profile-v2 (pull request #1651)
Use get Profile V2 endpoint

Approved-by: Linus Flood
2025-04-08 06:26:00 +00:00

613 lines
16 KiB
TypeScript

import { metrics } from "@opentelemetry/api"
import sjson from "secure-json-parse"
import * as api from "@/lib/api"
import { getVerifiedUser } from "@/server/routers/user/query"
import { router, safeProtectedServiceProcedure } from "@/server/trpc"
import { isValidSession } from "@/utils/session"
import {
addPackageInput,
cancelBookingInput,
createBookingInput,
guaranteeBookingInput,
priceChangeInput,
removePackageInput,
updateBookingInput,
} from "./input"
import { bookingConfirmationSchema, 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"
)
const addPackageCounter = meter.createCounter("trpc.bookings.add-package")
const addPackageSuccessCounter = meter.createCounter(
"trpc.bookings.add-package-success"
)
const addPackageFailCounter = meter.createCounter(
"trpc.bookings.add-package-fail"
)
const guaranteeBookingCounter = meter.createCounter("trpc.bookings.guarantee")
const guaranteeBookingSuccessCounter = meter.createCounter(
"trpc.bookings.guarantee-success"
)
const guaranteeBookingFailCounter = meter.createCounter(
"trpc.bookings.guarantee-fail"
)
const updateBookingCounter = meter.createCounter("trpc.bookings.update-booking")
const updateBookingSuccessCounter = meter.createCounter(
"trpc.bookings.update-booking-success"
)
const updateBookingFailCounter = meter.createCounter(
"trpc.bookings.update-booking-fail"
)
const removePackageCounter = meter.createCounter("trpc.bookings.remove-package")
const removePackageSuccessCounter = meter.createCounter(
"trpc.bookings.remove-package-success"
)
const removePackageFailCounter = meter.createCounter(
"trpc.bookings.remove-package-fail"
)
async function getMembershipNumber(
session: Session | null
): Promise<string | undefined> {
if (!isValidSession(session)) return undefined
const verifiedUser = await getVerifiedUser({ session })
if (!verifiedUser || "error" in verifiedUser) {
return undefined
}
return verifiedUser.data.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,
text,
},
})
)
const apiJson = sjson.safeParse(text)
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) {
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
}),
packages: safeProtectedServiceProcedure
.input(addPackageInput)
.mutation(async function ({ ctx, input }) {
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
const { confirmationNumber, ...body } = input
addPackageCounter.add(1, { confirmationNumber })
const headers = {
Authorization: `Bearer ${accessToken}`,
}
const apiResponse = await api.post(
api.endpoints.v1.Booking.packages(confirmationNumber),
{
headers,
body: body,
}
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
addPackageFailCounter.add(1, {
confirmationNumber,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
}),
})
console.error(
"api.booking.addPackage 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) {
addPackageFailCounter.add(1, {
confirmationNumber,
error_type: "validation_error",
})
console.error(
"api.booking.addPackage validation error",
JSON.stringify({
query: { confirmationNumber },
error: verifiedData.error,
})
)
return null
}
addPackageSuccessCounter.add(1, { confirmationNumber })
return verifiedData.data
}),
guarantee: safeProtectedServiceProcedure
.input(guaranteeBookingInput)
.mutation(async function ({ ctx, input }) {
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
const { confirmationNumber, language, ...body } = input
guaranteeBookingCounter.add(1, { confirmationNumber })
const headers = {
Authorization: `Bearer ${accessToken}`,
}
const apiResponse = await api.put(
api.endpoints.v1.Booking.guarantee(confirmationNumber),
{
headers,
body: body,
},
{ language }
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
guaranteeBookingFailCounter.add(1, {
confirmationNumber,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
}),
})
console.error(
"api.booking.guarantee 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) {
guaranteeBookingFailCounter.add(1, {
confirmationNumber,
error_type: "validation_error",
})
console.error(
"api.booking.guarantee validation error",
JSON.stringify({
query: { confirmationNumber },
error: verifiedData.error,
})
)
return null
}
guaranteeBookingSuccessCounter.add(1, { confirmationNumber })
return verifiedData.data
}),
update: safeProtectedServiceProcedure
.input(updateBookingInput)
.mutation(async function ({ ctx, input }) {
const accessToken = ctx.session?.token.access_token || ctx.serviceToken
const { confirmationNumber, ...body } = input
updateBookingCounter.add(1, { confirmationNumber })
const apiResponse = await api.put(
api.endpoints.v1.Booking.booking(confirmationNumber),
{
body,
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
updateBookingFailCounter.add(1, {
confirmationNumber,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
}),
})
console.error(
"api.booking.updateBooking error",
JSON.stringify({
query: { confirmationNumber },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
error: text,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const verifiedData = bookingConfirmationSchema.safeParse(apiJson)
if (!verifiedData.success) {
updateBookingFailCounter.add(1, {
confirmationNumber,
error_type: "validation_error",
})
console.error(
"api.booking.updateBooking validation error",
JSON.stringify({
query: { confirmationNumber },
error: verifiedData.error,
})
)
return null
}
updateBookingSuccessCounter.add(1, { confirmationNumber })
return verifiedData.data
}),
removePackage: safeProtectedServiceProcedure
.input(removePackageInput)
.mutation(async function ({ ctx, input }) {
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
const { confirmationNumber, codes, language } = input
const headers = {
Authorization: `Bearer ${accessToken}`,
}
const loggingAttributes = {
confirmationNumber,
codes,
language,
}
removePackageCounter.add(1, loggingAttributes)
console.info(
"api.booking.remove-package start",
JSON.stringify({
request: loggingAttributes,
headers,
})
)
const apiResponse = await api.remove(
api.endpoints.v1.Booking.packages(confirmationNumber),
{
headers,
} as RequestInit,
[["language", language], ...codes.map((code) => ["codes", code])]
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
removePackageFailCounter.add(1, {
confirmationNumber,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
}),
})
console.error(
"api.booking.remove-package error",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
query: loggingAttributes,
})
)
return false
}
removePackageSuccessCounter.add(1, loggingAttributes)
console.info(
"api.booking.remove-package success",
JSON.stringify({
query: loggingAttributes,
})
)
return true
}),
})