275 lines
7.2 KiB
TypeScript
275 lines
7.2 KiB
TypeScript
import { z } from "zod"
|
|
|
|
import { toLang } from "@/server/utils"
|
|
|
|
import { occupancySchema } from "./schemas/availability/occupancy"
|
|
import { productTypeSchema } from "./schemas/availability/productType"
|
|
import { citySchema } from "./schemas/city"
|
|
import {
|
|
attributesSchema,
|
|
includedSchema,
|
|
relationshipsSchema as hotelRelationshipsSchema,
|
|
} from "./schemas/hotel"
|
|
import { locationCitySchema } from "./schemas/location/city"
|
|
import { locationHotelSchema } from "./schemas/location/hotel"
|
|
import { breakfastPackageSchema, packageSchema } from "./schemas/packages"
|
|
import { rateSchema } from "./schemas/rate"
|
|
import { relationshipsSchema } from "./schemas/relationships"
|
|
import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration"
|
|
import { rateDefinitionSchema } from "./schemas/roomAvailability/rateDefinition"
|
|
|
|
import type {
|
|
AdditionalData,
|
|
City,
|
|
NearbyHotel,
|
|
Restaurant,
|
|
Room,
|
|
} from "@/types/hotel"
|
|
|
|
// NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html
|
|
export const hotelSchema = z
|
|
.object({
|
|
data: z.object({
|
|
attributes: attributesSchema,
|
|
id: z.string(),
|
|
language: z.string().transform((val) => {
|
|
const lang = toLang(val)
|
|
if (!lang) {
|
|
throw new Error("Invalid language")
|
|
}
|
|
return lang
|
|
}),
|
|
relationships: hotelRelationshipsSchema,
|
|
type: z.literal("hotels"), // No enum here but the standard return appears to be "hotels".
|
|
}),
|
|
// NOTE: We can pass an "include" param to the hotel API to retrieve
|
|
// additional data for an individual hotel.
|
|
included: includedSchema,
|
|
})
|
|
.transform(({ data: { attributes, ...data }, included }) => {
|
|
const additionalData =
|
|
included.find(
|
|
(inc): inc is AdditionalData => inc!.type === "additionalData"
|
|
) ?? ({} as AdditionalData)
|
|
const cities = included.filter((inc): inc is City => inc!.type === "cities")
|
|
const nearbyHotels = included.filter(
|
|
(inc): inc is NearbyHotel => inc!.type === "hotels"
|
|
)
|
|
const restaurants = included.filter(
|
|
(inc): inc is Restaurant => inc!.type === "restaurants"
|
|
)
|
|
const roomCategories = included.filter(
|
|
(inc): inc is Room => inc!.type === "roomcategories"
|
|
)
|
|
return {
|
|
additionalData,
|
|
cities,
|
|
hotel: {
|
|
...data,
|
|
...attributes,
|
|
},
|
|
nearbyHotels,
|
|
restaurants,
|
|
roomCategories,
|
|
}
|
|
})
|
|
|
|
export const hotelsAvailabilitySchema = z.object({
|
|
data: z.array(
|
|
z.object({
|
|
attributes: z.object({
|
|
checkInDate: z.string(),
|
|
checkOutDate: z.string(),
|
|
hotelId: z.number(),
|
|
occupancy: occupancySchema,
|
|
productType: productTypeSchema,
|
|
status: z.string(),
|
|
}),
|
|
relationships: relationshipsSchema.optional(),
|
|
type: z.string().optional(),
|
|
})
|
|
),
|
|
})
|
|
|
|
export const roomsAvailabilitySchema = z
|
|
.object({
|
|
data: z.object({
|
|
attributes: z.object({
|
|
checkInDate: z.string(),
|
|
checkOutDate: z.string(),
|
|
hotelId: z.number(),
|
|
mustBeGuaranteed: z.boolean().optional(),
|
|
occupancy: occupancySchema.optional(),
|
|
rateDefinitions: z.array(rateDefinitionSchema),
|
|
roomConfigurations: z.array(roomConfigurationSchema),
|
|
}),
|
|
relationships: relationshipsSchema.optional(),
|
|
type: z.string().optional(),
|
|
}),
|
|
})
|
|
.transform((o) => o.data.attributes)
|
|
|
|
export const ratesSchema = z.array(rateSchema)
|
|
|
|
export const citiesByCountrySchema = z.object({
|
|
data: z.array(
|
|
citySchema.transform((data) => {
|
|
return {
|
|
...data.attributes,
|
|
id: data.id,
|
|
type: data.type,
|
|
}
|
|
})
|
|
),
|
|
})
|
|
|
|
export const countriesSchema = z.object({
|
|
data: z
|
|
.array(
|
|
z.object({
|
|
attributes: z.object({
|
|
currency: z.string().default("N/A"),
|
|
name: z.string(),
|
|
}),
|
|
hotelInformationSystemId: z.number().optional(),
|
|
id: z.string().optional().default(""),
|
|
language: z.string().optional(),
|
|
type: z.literal("countries"),
|
|
})
|
|
)
|
|
.transform((data) => {
|
|
return data.map((country) => {
|
|
return {
|
|
...country.attributes,
|
|
hotelInformationSystemId: country.hotelInformationSystemId,
|
|
id: country.id,
|
|
language: country.language,
|
|
type: country.type,
|
|
}
|
|
})
|
|
}),
|
|
})
|
|
|
|
export const citiesSchema = z
|
|
.object({
|
|
data: z.array(citySchema),
|
|
})
|
|
.transform(({ data }) => {
|
|
if (data.length) {
|
|
const city = data[0]
|
|
return {
|
|
...city.attributes,
|
|
id: city.id,
|
|
type: city.type,
|
|
}
|
|
}
|
|
return null
|
|
})
|
|
|
|
export const locationsSchema = z.object({
|
|
data: z
|
|
.array(
|
|
z
|
|
.discriminatedUnion("type", [locationCitySchema, locationHotelSchema])
|
|
.transform((location) => {
|
|
if (location.type === "cities") {
|
|
return {
|
|
...location.attributes,
|
|
country: location?.country ?? "",
|
|
id: location.id,
|
|
type: location.type,
|
|
}
|
|
}
|
|
return {
|
|
...location.attributes,
|
|
id: location.id,
|
|
relationships: {
|
|
city: {
|
|
cityIdentifier: "",
|
|
ianaTimeZoneId: "",
|
|
id: "",
|
|
isPublished: false,
|
|
keywords: [],
|
|
name: "",
|
|
timeZoneId: "",
|
|
type: "cities",
|
|
url: location?.relationships?.city?.links?.related ?? "",
|
|
},
|
|
},
|
|
type: location.type,
|
|
}
|
|
})
|
|
)
|
|
.transform((data) =>
|
|
data
|
|
.filter((node) => !!node)
|
|
.sort((a, b) => {
|
|
if (a.type === b.type) {
|
|
return a.name.localeCompare(b.name)
|
|
} else {
|
|
return a.type === "cities" ? -1 : 1
|
|
}
|
|
})
|
|
),
|
|
})
|
|
|
|
export const breakfastPackagesSchema = z
|
|
.object({
|
|
data: z.object({
|
|
attributes: z.object({
|
|
hotelId: z.number(),
|
|
packages: z.array(breakfastPackageSchema),
|
|
}),
|
|
type: z.literal("breakfastpackage"),
|
|
}),
|
|
})
|
|
.transform(({ data }) =>
|
|
data.attributes.packages.filter((pkg) => pkg.code?.match(/^(BRF\d+)$/gm))
|
|
)
|
|
|
|
export const packagesSchema = z
|
|
.object({
|
|
data: z
|
|
.object({
|
|
attributes: z.object({
|
|
hotelId: z.number(),
|
|
packages: z.array(packageSchema).default([]),
|
|
}),
|
|
relationships: z
|
|
.object({
|
|
links: z.array(
|
|
z.object({
|
|
type: z.string(),
|
|
url: z.string(),
|
|
})
|
|
),
|
|
})
|
|
.optional(),
|
|
type: z.string(),
|
|
})
|
|
.optional(),
|
|
})
|
|
.transform(({ data }) => data?.attributes.packages)
|
|
|
|
export const getHotelIdsByCityIdSchema = z
|
|
.object({
|
|
data: z.array(
|
|
z.object({
|
|
// We only care about the hotel id
|
|
id: z.string(),
|
|
})
|
|
),
|
|
})
|
|
.transform((data) => data.data.map((hotel) => hotel.id))
|
|
|
|
export const getNearbyHotelIdsSchema = z
|
|
.object({
|
|
data: z.array(
|
|
z.object({
|
|
// We only care about the hotel id
|
|
id: z.string(),
|
|
})
|
|
),
|
|
})
|
|
.transform((data) => data.data.map((hotel) => hotel.id))
|