feat: bedtypes is selectable again

This commit is contained in:
Simon Emanuelsson
2025-04-07 13:43:52 +02:00
committed by Michael Zetterberg
parent f62723c6e5
commit afb37d0cc5
69 changed files with 2135 additions and 2349 deletions

View File

@@ -1,14 +1,19 @@
import deepmerge from "deepmerge"
import stringify from "json-stable-stringify-without-jsonify"
import { REDEMPTION } from "@/constants/booking"
import { Lang } from "@/constants/languages"
import { env } from "@/env/server"
import * as api from "@/lib/api"
import { badRequestError } from "@/server/errors/trpc"
import { toApiLang } from "@/server/utils"
import { generateChildrenString } from "@/components/HotelReservation/utils"
import { getCacheClient } from "@/services/dataCache"
import { cache } from "@/utils/cache"
import { getHotelPageUrls } from "../contentstack/hotelPage/utils"
import { type RoomFeaturesInput, roomPackagesInputSchema } from "./input"
import { metrics } from "./metrics"
import {
type Cities,
@@ -16,15 +21,31 @@ import {
citiesSchema,
countriesSchema,
getHotelIdsSchema,
hotelsAvailabilitySchema,
hotelSchema,
locationsSchema,
packagesSchema,
roomFeaturesSchema,
roomsAvailabilitySchema,
} from "./output"
import { getHotel } from "./query"
import type { z } from "zod"
import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType"
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { HotelTypeEnum } from "@/types/enums/hotelType"
import { PointOfInterestGroupEnum } from "@/types/enums/pointOfInterest"
import type { DestinationPagesHotelData } from "@/types/hotel"
import type {
DestinationPagesHotelData,
Room as RoomCategory,
} from "@/types/hotel"
import type { PackagesInput } from "@/types/requests/packages"
import type {
HotelsAvailabilityInputSchema,
HotelsByHotelIdsAvailabilityInputSchema,
RoomsAvailabilityInputRoom,
RoomsAvailabilityInputSchema,
} from "@/types/trpc/routers/hotel/availability"
import type { HotelInput } from "@/types/trpc/routers/hotel/hotel"
import type {
CitiesGroupedByCountry,
CityLocation,
@@ -34,9 +55,9 @@ import type {
Products,
RateDefinition,
RedemptionsProduct,
RoomConfiguration,
} from "@/types/trpc/routers/hotel/roomAvailability"
import type { Endpoint } from "@/lib/api/endpoints"
import type { selectedRoomAvailabilityInputSchema } from "./input"
export function getPoiGroupByCategoryName(category: string | undefined) {
if (!category) return PointOfInterestGroupEnum.LOCATION
@@ -608,51 +629,184 @@ function findProduct(product: Products, rateDefinition: RateDefinition) {
}
}
export async function getSelectedRoomAvailability(
input: z.input<typeof selectedRoomAvailabilityInputSchema>,
lang: string,
serviceToken: string,
userPoints?: number
export const getHotel = cache(
async (input: HotelInput, serviceToken: string) => {
const callable = async function (
hotelId: HotelInput["hotelId"],
language: HotelInput["language"],
isCardOnlyPayment?: HotelInput["isCardOnlyPayment"]
) {
/**
* Since API expects the params appended and not just
* a comma separated string we need to initialize the
* SearchParams with a sequence of pairs
* (include=City&include=NearbyHotels&include=Restaurants etc.)
**/
const params = new URLSearchParams([
["include", "AdditionalData"],
["include", "City"],
["include", "NearbyHotels"],
["include", "Restaurants"],
["include", "RoomCategories"],
["language", toApiLang(language)],
])
metrics.hotel.counter.add(1, {
hotelId,
language,
})
console.info(
"api.hotels.hotelData start",
JSON.stringify({ query: { hotelId, params: params.toString() } })
)
const apiResponse = await api.get(
api.endpoints.v1.Hotel.Hotels.hotel(hotelId),
{
headers: {
Authorization: `Bearer ${serviceToken}`,
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.hotel.fail.add(1, {
hotelId,
language,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.hotelData error",
JSON.stringify({
query: { hotelId, params: params.toString() },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const validateHotelData = hotelSchema.safeParse(apiJson)
if (!validateHotelData.success) {
metrics.hotel.fail.add(1, {
hotelId,
language,
error_type: "validation_error",
error: JSON.stringify(validateHotelData.error),
})
console.error(
"api.hotels.hotelData validation error",
JSON.stringify({
query: { hotelId, params: params.toString() },
error: validateHotelData.error,
})
)
throw badRequestError()
}
metrics.hotel.success.add(1, {
hotelId,
language,
})
console.info(
"api.hotels.hotelData success",
JSON.stringify({
query: { hotelId, params: params.toString() },
})
)
const hotelData = validateHotelData.data
if (isCardOnlyPayment) {
hotelData.hotel.merchantInformationData.alternatePaymentOptions = []
}
const gallery = hotelData.additionalData?.gallery
if (gallery) {
const smallerImages = gallery.smallerImages
const hotelGalleryImages =
hotelData.hotel.hotelType === HotelTypeEnum.Signature
? smallerImages.slice(0, 10)
: smallerImages.slice(0, 6)
hotelData.hotel.galleryImages = hotelGalleryImages
}
return hotelData
}
const cacheClient = await getCacheClient()
return await cacheClient.cacheOrGet(
`${input.language}:hotel:${input.hotelId}:${!!input.isCardOnlyPayment}`,
async () => {
return callable(input.hotelId, input.language, input.isCardOnlyPayment)
},
env.CACHE_TIME_HOTELS
)
}
)
export async function getHotelsAvailabilityByCity(
input: HotelsAvailabilityInputSchema,
apiLang: string,
token: string, // Either service token or user access token in case of redemption search
userPoints: number = 0
) {
const {
adults,
bookingCode,
children,
hotelId,
roomStayEndDate,
cityId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
redemption,
} = input
const params: Record<string, string | number | undefined> = {
const params: Record<string, string | number> = {
roomStayStartDate,
roomStayEndDate,
adults,
...(children && { children }),
...(bookingCode && { bookingCode }),
...(redemption && { isRedemption: "true" }),
language: lang,
...(redemption ? { isRedemption: "true" } : {}),
language: apiLang,
}
metrics.selectedRoomAvailability.counter.add(1, input)
metrics.hotelsAvailability.counter.add(1, {
cityId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
redemption,
})
console.info(
"api.hotels.selectedRoomAvailability start",
JSON.stringify({ query: { hotelId: input.hotelId, params } })
"api.hotels.hotelsAvailability start",
JSON.stringify({ query: { cityId, params } })
)
const apiResponseAvailability = await api.get(
api.endpoints.v1.Availability.hotel(hotelId.toString()),
const apiResponse = await api.get(
api.endpoints.v1.Availability.city(cityId),
{
headers: {
Authorization: `Bearer ${serviceToken}`,
Authorization: `Bearer ${token}`,
},
},
params
)
if (!apiResponseAvailability.ok) {
const text = await apiResponseAvailability.text()
metrics.selectedRoomAvailability.fail.add(1, {
hotelId,
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.hotelsAvailability.fail.add(1, {
cityId,
roomStayStartDate,
roomStayEndDate,
adults,
@@ -660,61 +814,611 @@ export async function getSelectedRoomAvailability(
bookingCode,
error_type: "http_error",
error: JSON.stringify({
status: apiResponseAvailability.status,
statusText: apiResponseAvailability.statusText,
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.selectedRoomAvailability error",
"api.hotels.hotelsAvailability error",
JSON.stringify({
query: { hotelId, params },
query: { cityId, params },
error: {
status: apiResponseAvailability.status,
statusText: apiResponseAvailability.statusText,
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
throw new Error("Failed to fetch selected room availability")
throw new Error("Failed to fetch hotels availability by city")
}
const apiJsonAvailability = await apiResponseAvailability.json()
const validateAvailabilityData =
roomsAvailabilitySchema.safeParse(apiJsonAvailability)
const apiJson = await apiResponse.json()
const validateAvailabilityData = hotelsAvailabilitySchema.safeParse(apiJson)
if (!validateAvailabilityData.success) {
metrics.selectedRoomAvailability.fail.add(1, {
hotelId,
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.selectedRoomAvailability validation error",
"api.hotels.hotelsAvailability validation error",
JSON.stringify({
query: { hotelId, params },
query: { cityId, params },
error: validateAvailabilityData.error,
})
)
throw badRequestError()
}
const { rateDefinitions, roomConfigurations } = validateAvailabilityData.data
const rateDefinition = rateDefinitions.find(
(rd) => rd.rateCode === input.rateCode
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) {
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 async function getHotelsAvailabilityByHotelIds(
input: HotelsByHotelIdsAvailabilityInputSchema,
apiLang: string,
serviceToken: string
) {
const {
hotelIds,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
} = input
const params = new URLSearchParams([
["roomStayStartDate", roomStayStartDate],
["roomStayEndDate", roomStayEndDate],
["adults", adults.toString()],
["children", children ?? ""],
["bookingCode", bookingCode],
["language", apiLang],
])
const cacheClient = await getCacheClient()
return cacheClient.cacheOrGet(
`${apiLang}:hotels:availability:${hotelIds.join(",")}:${roomStayStartDate}:${roomStayEndDate}:${adults}:${children}:${bookingCode}`,
async () => {
/**
* Since API expects the params appended and not just
* a comma separated string we need to initialize the
* SearchParams with a sequence of pairs
* (hotelIds=810&hotelIds=879&hotelIds=222 etc.)
**/
hotelIds.forEach((hotelId) =>
params.append("hotelIds", hotelId.toString())
)
metrics.hotelsByHotelIdAvailability.counter.add(1, {
hotelIds,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
})
console.info(
"api.hotels.hotelsByHotelIdAvailability start",
JSON.stringify({ query: { params } })
)
const apiResponse = await api.get(
api.endpoints.v1.Availability.hotels(),
{
headers: {
Authorization: `Bearer ${serviceToken}`,
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.hotelsByHotelIdAvailability.fail.add(1, {
hotelIds,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.hotelsByHotelIdAvailability error",
JSON.stringify({
query: { params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
throw new Error("Failed to fetch hotels availability by hotelIds")
}
const apiJson = await apiResponse.json()
const validateAvailabilityData =
hotelsAvailabilitySchema.safeParse(apiJson)
if (!validateAvailabilityData.success) {
metrics.hotelsByHotelIdAvailability.fail.add(1, {
hotelIds,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
error_type: "validation_error",
error: JSON.stringify(validateAvailabilityData.error),
})
console.error(
"api.hotels.hotelsByHotelIdAvailability validation error",
JSON.stringify({
query: { params },
error: validateAvailabilityData.error,
})
)
throw badRequestError()
}
metrics.hotelsByHotelIdAvailability.success.add(1, {
hotelIds,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
})
console.info(
"api.hotels.hotelsByHotelIdAvailability success",
JSON.stringify({
query: { params },
})
)
return {
availability: validateAvailabilityData.data.data.flatMap(
(hotels) => hotels.attributes
),
}
},
env.CACHE_TIME_CITY_SEARCH
)
}
async function getRoomFeaturesInventory(
input: RoomFeaturesInput,
token: string
) {
const {
adults,
childrenInRoom,
endDate,
hotelId,
roomFeatureCodes,
startDate,
} = input
const cacheClient = await getCacheClient()
return cacheClient.cacheOrGet(
stringify(input),
async function () {
const params = {
adults,
hotelId,
roomFeatureCode: roomFeatureCodes,
roomStayEndDate: endDate,
roomStayStartDate: startDate,
...(childrenInRoom?.length && {
children: generateChildrenString(childrenInRoom),
}),
}
metrics.roomFeatures.counter.add(1, params)
const apiResponse = await api.get(
api.endpoints.v1.Availability.roomFeatures(hotelId),
{
headers: {
Authorization: `Bearer ${token}`,
},
},
params
)
if (!apiResponse.ok) {
const text = apiResponse.text()
console.error(
"api.availability.roomfeature error",
JSON.stringify({
query: { hotelId, params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
metrics.roomFeatures.fail.add(1, params)
return null
}
const data = await apiResponse.json()
const validatedRoomFeaturesData = roomFeaturesSchema.safeParse(data)
if (!validatedRoomFeaturesData.success) {
console.error(
"api.availability.roomfeature error",
JSON.stringify({
query: { hotelId, params },
error: validatedRoomFeaturesData.error,
})
)
return null
}
metrics.roomFeatures.success.add(1, params)
return validatedRoomFeaturesData.data
},
"5m"
)
}
export async function getPackages(
rawInput: PackagesInput,
serviceToken: string
) {
const parsedInput = roomPackagesInputSchema.safeParse(rawInput)
if (!parsedInput.success) {
console.info(`Failed to parse input for Get Packages`)
console.error(parsedInput.error)
return null
}
const input = parsedInput.data
const cacheClient = await getCacheClient()
return cacheClient.cacheOrGet(
stringify(input),
async function () {
const {
adults,
children,
endDate,
hotelId,
lang,
packageCodes,
startDate,
} = input
const apiLang = toApiLang(lang)
const searchParams = new URLSearchParams({
adults: adults.toString(),
children: children.toString(),
endDate,
language: apiLang,
startDate,
})
packageCodes.forEach((code) => {
searchParams.append("packageCodes", code)
})
const params = searchParams.toString()
metrics.packages.counter.add(1, {
hotelId,
})
console.info(
"api.hotels.packages start",
JSON.stringify({ query: { hotelId, params } })
)
const apiResponse = await api.get(
api.endpoints.v1.Package.Packages.hotel(hotelId),
{
headers: {
Authorization: `Bearer ${serviceToken}`,
},
},
searchParams
)
if (!apiResponse.ok) {
metrics.packages.fail.add(1, {
hotelId,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
}),
})
console.error(
"api.hotels.packages error",
JSON.stringify({ query: { hotelId, params } })
)
return null
}
const apiJson = await apiResponse.json()
const validatedPackagesData = packagesSchema.safeParse(apiJson)
if (!validatedPackagesData.success) {
metrics.packages.fail.add(1, {
hotelId,
error_type: "validation_error",
error: JSON.stringify(validatedPackagesData.error),
})
console.error(
"api.hotels.packages validation error",
JSON.stringify({
query: { hotelId, params },
error: validatedPackagesData.error,
})
)
return null
}
metrics.packages.success.add(1, {
hotelId,
})
console.info(
"api.hotels.packages success",
JSON.stringify({ query: { hotelId, params: params } })
)
return validatedPackagesData.data
},
"3h"
)
}
export async function getRoomsAvailability(
input: RoomsAvailabilityInputSchema,
token: string,
serviceToken: string,
userPoints: number | undefined
) {
const {
booking: { bookingCode, fromDate, hotelId, rooms, searchType, toDate },
lang,
} = input
const redemption = searchType === REDEMPTION
const apiLang = toApiLang(lang)
const kids = rooms
.map((r) => r.childrenInRoom)
.filter(Boolean)
.map((kid) => JSON.stringify(kid))
const metricsData = {
adultsCount: rooms.map((r) => r.adults),
bookingCode,
childArray: kids.length ? kids : undefined,
hotelId,
roomStayEndDate: toDate,
roomStayStartDate: fromDate,
}
metrics.roomsAvailability.counter.add(1, metricsData)
console.info(
"api.hotels.roomsAvailability start",
JSON.stringify({ query: { hotelId, params: metricsData } })
)
const baseCacheKey = {
bookingCode,
fromDate,
hotelId,
lang,
searchType,
toDate,
}
const cacheClient = await getCacheClient()
const availabilityResponses = await Promise.allSettled(
rooms.map(async (room: RoomsAvailabilityInputRoom) => {
const cacheKey = {
...baseCacheKey,
room,
}
return await cacheClient.cacheOrGet(
stringify(cacheKey),
async function () {
{
const params = {
adults: room.adults,
language: apiLang,
roomStayStartDate: fromDate,
roomStayEndDate: toDate,
...(room.childrenInRoom?.length && {
children: generateChildrenString(room.childrenInRoom),
}),
...(room.bookingCode && { bookingCode: room.bookingCode }),
...(redemption && { isRedemption: "true" }),
}
const apiResponse = await api.get(
api.endpoints.v1.Availability.hotel(hotelId),
{
headers: {
Authorization: `Bearer ${token}`,
},
next: {
revalidate: 60,
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.roomsAvailability.fail.add(1, metricsData)
console.error("Failed API call", { params, text })
return { error: "http_error", details: text }
}
const apiJson = await apiResponse.json()
const validateAvailabilityData =
roomsAvailabilitySchema.safeParse(apiJson)
if (!validateAvailabilityData.success) {
console.error("Validation error", {
params,
error: validateAvailabilityData.error,
})
metrics.roomsAvailability.fail.add(1, metricsData)
return {
error: "validation_error",
details: validateAvailabilityData.error,
}
}
if (redemption) {
for (const roomConfig of validateAvailabilityData.data
.roomConfigurations) {
for (const product of roomConfig.redemptions) {
if (userPoints) {
product.redemption.hasEnoughPoints =
userPoints >= product.redemption.localPrice.pointsPerStay
}
}
}
}
const roomFeatures = await getPackages(
{
adults: room.adults,
children: room.childrenInRoom?.length,
endDate: input.booking.toDate,
hotelId: input.booking.hotelId,
lang,
packageCodes: [
RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
RoomPackageCodeEnum.ALLERGY_ROOM,
RoomPackageCodeEnum.PET_ROOM,
],
startDate: input.booking.fromDate,
},
serviceToken
)
if (roomFeatures) {
validateAvailabilityData.data.packages = roomFeatures
}
// Fetch packages
if (room.packages?.length) {
const roomFeaturesInventory = await getRoomFeaturesInventory(
{
adults: room.adults,
childrenInRoom: room.childrenInRoom,
endDate: input.booking.toDate,
hotelId: input.booking.hotelId,
lang,
roomFeatureCodes: room.packages,
startDate: input.booking.fromDate,
},
serviceToken
)
if (roomFeaturesInventory) {
const features = roomFeaturesInventory.reduce<
Record<string, number>
>((fts, feat) => {
fts[feat.roomTypeCode] = feat.features?.[0]?.inventory ?? 0
return fts
}, {})
const updatedRoomConfigurations =
validateAvailabilityData.data.roomConfigurations
// This filter is needed since we can get availability
// back from roomFeatures yet the availability call
// says there are no rooms left...
.filter((rc) => rc.roomsLeft)
.filter((rc) => features?.[rc.roomTypeCode])
.map((rc) => ({
...rc,
roomsLeft: features[rc.roomTypeCode],
status: AvailabilityEnum.Available,
}))
validateAvailabilityData.data.roomConfigurations =
updatedRoomConfigurations
}
}
return validateAvailabilityData.data
}
},
"1m"
)
})
)
metrics.roomsAvailability.success.add(1, metricsData)
const data = availabilityResponses.map((availability) => {
if (availability.status === "fulfilled") {
return availability.value
}
return {
details: availability.reason,
error: "request_failure",
}
})
return data
}
export function getSelectedRoomAvailability(
rateCode: string,
rateDefinitions: RateDefinition[],
roomConfigurations: RoomConfiguration[],
roomTypeCode: string,
userPoints: number | undefined
) {
const rateDefinition = rateDefinitions.find((rd) => rd.rateCode === rateCode)
if (!rateDefinition) {
return null
}
const selectedRoom = roomConfigurations.find(
(room) =>
room.roomTypeCode === input.roomTypeCode &&
room.roomTypeCode === roomTypeCode &&
room.products.find((product) => findProduct(product, rateDefinition))
)
@@ -753,3 +1457,90 @@ export async function getSelectedRoomAvailability(
selectedRoom,
}
}
// Used to ensure `Available` rooms
// are shown before all `NotAvailable`
const statusLookup = {
[AvailabilityEnum.Available]: 1,
[AvailabilityEnum.NotAvailable]: 2,
}
export function sortRoomConfigs(a: RoomConfiguration, b: RoomConfiguration) {
// @ts-expect-error - array indexing
return statusLookup[a.status] - statusLookup[b.status]
}
export function getBedTypes(
rooms: RoomConfiguration[],
roomType: string,
roomCategories?: RoomCategory[]
) {
if (!roomCategories) {
return []
}
return rooms
.filter((room) => room.roomType === roomType)
.map((availRoom) => {
const matchingRoom = roomCategories
?.find((room) =>
room.roomTypes
.map((roomType) => roomType.code)
.includes(availRoom.roomTypeCode)
)
?.roomTypes.find((roomType) => roomType.code === availRoom.roomTypeCode)
if (matchingRoom) {
return {
description: matchingRoom.description,
size: matchingRoom.mainBed.widthRange,
value: matchingRoom.code,
type: matchingRoom.mainBed.type,
extraBed: matchingRoom.fixedExtraBed
? {
type: matchingRoom.fixedExtraBed.type,
description: matchingRoom.fixedExtraBed.description,
}
: undefined,
}
}
})
.filter((bed): bed is BedTypeSelection => Boolean(bed))
}
export function mergeRoomTypes(roomConfigurations: RoomConfiguration[]) {
// Initial sort to guarantee if one bed is NotAvailable and whereas
// the other is Available to make sure data is added to the correct
// roomConfig
roomConfigurations.sort(sortRoomConfigs)
const roomConfigs = new Map<string, RoomConfiguration>()
for (const roomConfig of roomConfigurations) {
if (roomConfigs.has(roomConfig.roomType)) {
const currentRoomConf = roomConfigs.get(roomConfig.roomType)
if (currentRoomConf) {
currentRoomConf.features = roomConfig.features.reduce(
(feats, feature) => {
const currentFeatureIndex = feats.findIndex(
(f) => f.code === feature.code
)
if (currentFeatureIndex !== -1) {
feats[currentFeatureIndex].inventory =
feats[currentFeatureIndex].inventory + feature.inventory
} else {
feats.push(feature)
}
return feats
},
currentRoomConf.features
)
currentRoomConf.roomsLeft =
currentRoomConf.roomsLeft + roomConfig.roomsLeft
roomConfigs.set(currentRoomConf.roomType, currentRoomConf)
}
} else {
roomConfigs.set(roomConfig.roomType, roomConfig)
}
}
return Array.from(roomConfigs.values())
}