feat: SW-2028 Fixed review comments
This commit is contained in:
@@ -9,6 +9,8 @@ import {
|
||||
nullableStringValidator,
|
||||
} from "@/utils/zod/stringValidator"
|
||||
|
||||
import { CurrencyEnum } from "@/types/enums/currency"
|
||||
|
||||
const guestSchema = z.object({
|
||||
email: nullableStringEmailValidator,
|
||||
firstName: nullableStringValidator,
|
||||
@@ -97,7 +99,7 @@ export const packageSchema = z
|
||||
unitPrice: z.number(),
|
||||
totalPrice: z.number().nullish(),
|
||||
totalUnit: z.number().int().nullish(),
|
||||
currency: z.string().default(""),
|
||||
currency: z.nativeEnum(CurrencyEnum).default(CurrencyEnum.Unknown),
|
||||
points: nullableIntValidator,
|
||||
}),
|
||||
comment: z.string().nullish(),
|
||||
@@ -218,7 +220,7 @@ export const bookingConfirmationSchema = z
|
||||
computedReservationStatus: z.string().nullable().default(""),
|
||||
confirmationNumber: nullableStringValidator,
|
||||
createDateTime: z.date({ coerce: true }),
|
||||
currencyCode: z.string(),
|
||||
currencyCode: z.nativeEnum(CurrencyEnum),
|
||||
guest: guestSchema,
|
||||
linkedReservations: nullableArrayObjectValidator(
|
||||
linkedReservationSchema
|
||||
|
||||
@@ -208,12 +208,12 @@ export const getHotel = cache(
|
||||
}
|
||||
)
|
||||
|
||||
export const getHotelsAvailabilityByCity = async (
|
||||
async function getHotelsAvailabilityByCity(
|
||||
input: HotelsAvailabilityInputSchema,
|
||||
apiLang: string,
|
||||
token: string, // Either service token or user access token in case of redemption search
|
||||
session?: Session
|
||||
) => {
|
||||
) {
|
||||
const {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
@@ -223,131 +223,124 @@ export const getHotelsAvailabilityByCity = async (
|
||||
bookingCode,
|
||||
redemption,
|
||||
} = input
|
||||
const cacheClient = await getCacheClient()
|
||||
return await cacheClient.cacheOrGet(
|
||||
`${cityId}:${roomStayStartDate}:${roomStayEndDate}:${adults}:${children}:${bookingCode}:${redemption ? "isRedemption" : ""}`,
|
||||
async () => {
|
||||
const params: Record<string, string | number> = {
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
...(children && { children }),
|
||||
...(bookingCode && { bookingCode }),
|
||||
...(redemption ? { isRedemption: "true" } : {}),
|
||||
language: apiLang,
|
||||
}
|
||||
metrics.hotelsAvailability.counter.add(1, {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
redemption,
|
||||
})
|
||||
console.info(
|
||||
"api.hotels.hotelsAvailability start",
|
||||
JSON.stringify({ query: { cityId, params } })
|
||||
)
|
||||
const apiResponse = await api.get(
|
||||
api.endpoints.v1.Availability.city(cityId),
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
},
|
||||
params
|
||||
)
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
metrics.hotelsAvailability.fail.add(1, {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.hotels.hotelsAvailability error",
|
||||
JSON.stringify({
|
||||
query: { cityId, params },
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
throw new Error("Failed to fetch hotels availability by city")
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
const validateAvailabilityData =
|
||||
hotelsAvailabilitySchema.safeParse(apiJson)
|
||||
if (!validateAvailabilityData.success) {
|
||||
metrics.hotelsAvailability.fail.add(1, {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
redemption,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validateAvailabilityData.error),
|
||||
})
|
||||
console.error(
|
||||
"api.hotels.hotelsAvailability validation error",
|
||||
JSON.stringify({
|
||||
query: { cityId, params },
|
||||
error: validateAvailabilityData.error,
|
||||
})
|
||||
)
|
||||
throw badRequestError()
|
||||
}
|
||||
metrics.hotelsAvailability.success.add(1, {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
redemption,
|
||||
})
|
||||
console.info(
|
||||
"api.hotels.hotelsAvailability success",
|
||||
JSON.stringify({
|
||||
query: { cityId, params: params },
|
||||
})
|
||||
)
|
||||
if (redemption && session) {
|
||||
const verifiedUser = await getVerifiedUser({ session })
|
||||
if (!verifiedUser?.error) {
|
||||
const userPoints = verifiedUser?.data.membership?.currentPoints ?? 0
|
||||
validateAvailabilityData.data.data.forEach((data) => {
|
||||
data.attributes.productType?.redemptions?.forEach((r) => {
|
||||
r.hasEnoughPoints = userPoints >= r.localPrice.pointsPerStay
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
availability: validateAvailabilityData.data.data.flatMap(
|
||||
(hotels) => hotels.attributes
|
||||
),
|
||||
}
|
||||
},
|
||||
env.CACHE_TIME_CITY_SEARCH
|
||||
const params: Record<string, string | number> = {
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
...(children && { children }),
|
||||
...(bookingCode && { bookingCode }),
|
||||
...(redemption ? { isRedemption: "true" } : {}),
|
||||
language: apiLang,
|
||||
}
|
||||
metrics.hotelsAvailability.counter.add(1, {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
redemption,
|
||||
})
|
||||
console.info(
|
||||
"api.hotels.hotelsAvailability start",
|
||||
JSON.stringify({ query: { cityId, params } })
|
||||
)
|
||||
const apiResponse = await api.get(
|
||||
api.endpoints.v1.Availability.city(cityId),
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
},
|
||||
params
|
||||
)
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
metrics.hotelsAvailability.fail.add(1, {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.hotels.hotelsAvailability error",
|
||||
JSON.stringify({
|
||||
query: { cityId, params },
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
throw new Error("Failed to fetch hotels availability by city")
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
const validateAvailabilityData = hotelsAvailabilitySchema.safeParse(apiJson)
|
||||
if (!validateAvailabilityData.success) {
|
||||
metrics.hotelsAvailability.fail.add(1, {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
redemption,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validateAvailabilityData.error),
|
||||
})
|
||||
console.error(
|
||||
"api.hotels.hotelsAvailability validation error",
|
||||
JSON.stringify({
|
||||
query: { cityId, params },
|
||||
error: validateAvailabilityData.error,
|
||||
})
|
||||
)
|
||||
throw badRequestError()
|
||||
}
|
||||
metrics.hotelsAvailability.success.add(1, {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
redemption,
|
||||
})
|
||||
console.info(
|
||||
"api.hotels.hotelsAvailability success",
|
||||
JSON.stringify({
|
||||
query: { cityId, params: params },
|
||||
})
|
||||
)
|
||||
if (redemption && session) {
|
||||
const verifiedUser = await getVerifiedUser({ session })
|
||||
if (!verifiedUser?.error) {
|
||||
const userPoints = verifiedUser?.data.membership?.currentPoints ?? 0
|
||||
validateAvailabilityData.data.data.forEach((data) => {
|
||||
data.attributes.productType?.redemptions?.forEach((r) => {
|
||||
r.hasEnoughPoints = userPoints >= r.localPrice.pointsPerStay
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
availability: validateAvailabilityData.data.data.flatMap(
|
||||
(hotels) => hotels.attributes
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
export const getHotelsAvailabilityByHotelIds = async (
|
||||
@@ -492,7 +485,23 @@ export const hotelQueryRouter = router({
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { lang } = ctx
|
||||
const apiLang = toApiLang(lang)
|
||||
return getHotelsAvailabilityByCity(input, apiLang, ctx.serviceToken)
|
||||
const {
|
||||
cityId,
|
||||
roomStayStartDate,
|
||||
roomStayEndDate,
|
||||
adults,
|
||||
children,
|
||||
bookingCode,
|
||||
} = input
|
||||
const cacheClient = await getCacheClient()
|
||||
|
||||
return await cacheClient.cacheOrGet(
|
||||
`${cityId}:${roomStayStartDate}:${roomStayEndDate}:${adults}:${children}:${bookingCode}`,
|
||||
async () => {
|
||||
return getHotelsAvailabilityByCity(input, apiLang, ctx.serviceToken)
|
||||
},
|
||||
env.CACHE_TIME_CITY_SEARCH
|
||||
)
|
||||
}),
|
||||
hotelsByCityWithRedemption: protectedProcedure
|
||||
.input(hotelsAvailabilityInputSchema)
|
||||
@@ -503,7 +512,7 @@ export const hotelQueryRouter = router({
|
||||
input,
|
||||
apiLang,
|
||||
ctx.session.token.access_token,
|
||||
ctx.session,
|
||||
ctx.session
|
||||
)
|
||||
}),
|
||||
hotelsByHotelIds: serviceProcedure
|
||||
@@ -519,12 +528,16 @@ export const hotelQueryRouter = router({
|
||||
.use(async ({ ctx, input, next }) => {
|
||||
if (input.redemption) {
|
||||
if (ctx.session?.token.access_token) {
|
||||
return next({
|
||||
ctx: {
|
||||
token: ctx.session.token.access_token,
|
||||
},
|
||||
input,
|
||||
})
|
||||
const verifiedUser = await getVerifiedUser({ session: ctx.session })
|
||||
if (!verifiedUser?.error) {
|
||||
return next({
|
||||
ctx: {
|
||||
token: ctx.session.token.access_token,
|
||||
userPoints: verifiedUser?.data.membership?.currentPoints ?? 0,
|
||||
},
|
||||
input,
|
||||
})
|
||||
}
|
||||
}
|
||||
throw unauthorizedError()
|
||||
}
|
||||
@@ -622,16 +635,15 @@ export const hotelQueryRouter = router({
|
||||
)?.mustBeGuaranteed
|
||||
}
|
||||
|
||||
if (redemption && ctx.session) {
|
||||
const verifiedUser = await getVerifiedUser({ session: ctx.session })
|
||||
if (!verifiedUser?.error) {
|
||||
const userPoints = verifiedUser?.data.membership?.currentPoints ?? 0
|
||||
validateAvailabilityData.data.roomConfigurations.forEach((data) => {
|
||||
data.redemptions?.forEach(r => {
|
||||
r.redemption.hasEnoughPoints = userPoints >= r.redemption.localPrice.pointsPerStay
|
||||
if (redemption) {
|
||||
validateAvailabilityData.data.roomConfigurations.forEach(
|
||||
(data) => {
|
||||
data.redemptions?.forEach((r) => {
|
||||
r.redemption.hasEnoughPoints =
|
||||
ctx.userPoints >= r.redemption.localPrice.pointsPerStay
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return validateAvailabilityData.data
|
||||
|
||||
@@ -4,18 +4,19 @@ import { imageSizesSchema } from "./image"
|
||||
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||||
import { CurrencyEnum } from "@/types/enums/currency"
|
||||
import { PackageTypeEnum } from "@/types/enums/packages"
|
||||
|
||||
// TODO: Remove optional and default when the API change has been deployed
|
||||
export const packagePriceSchema = z
|
||||
.object({
|
||||
currency: z.string().default("N/A"),
|
||||
currency: z.nativeEnum(CurrencyEnum).default(CurrencyEnum.Unknown),
|
||||
price: z.number(),
|
||||
totalPrice: z.number(),
|
||||
})
|
||||
.optional()
|
||||
.default({
|
||||
currency: "N/A",
|
||||
currency: CurrencyEnum.Unknown,
|
||||
price: 0,
|
||||
totalPrice: 0,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user