493 lines
12 KiB
TypeScript
493 lines
12 KiB
TypeScript
import { z } from "zod"
|
|
|
|
import { toLang } from "@/server/utils"
|
|
|
|
const ratingsSchema = z
|
|
.object({
|
|
tripAdvisor: z.object({
|
|
numberOfReviews: z.number(),
|
|
rating: z.number(),
|
|
ratingImageUrl: z.string(),
|
|
webUrl: z.string(),
|
|
awards: z.array(
|
|
z.object({
|
|
displayName: z.string(),
|
|
images: z.object({
|
|
small: z.string(),
|
|
medium: z.string(),
|
|
large: z.string(),
|
|
}),
|
|
})
|
|
),
|
|
reviews: z
|
|
.object({
|
|
widgetHtmlTagId: z.string(),
|
|
widgetScriptEmbedUrlIframe: z.string(),
|
|
widgetScriptEmbedUrlJavaScript: z.string(),
|
|
})
|
|
.optional(),
|
|
}),
|
|
})
|
|
.optional()
|
|
|
|
const addressSchema = z.object({
|
|
streetAddress: z.string(),
|
|
city: z.string(),
|
|
zipCode: z.string(),
|
|
country: z.string(),
|
|
})
|
|
|
|
const contactInformationSchema = z.object({
|
|
phoneNumber: z.string(),
|
|
faxNumber: z.string().optional(),
|
|
email: z.string(),
|
|
websiteUrl: z.string(),
|
|
})
|
|
|
|
const checkinSchema = z.object({
|
|
checkInTime: z.string(),
|
|
checkOutTime: z.string(),
|
|
onlineCheckOutAvailableFrom: z.string().nullable().optional(),
|
|
onlineCheckout: z.boolean(),
|
|
})
|
|
|
|
const ecoLabelsSchema = z.object({
|
|
euEcoLabel: z.boolean(),
|
|
greenGlobeLabel: z.boolean(),
|
|
nordicEcoLabel: z.boolean(),
|
|
svanenEcoLabelCertificateNumber: z.string().optional(),
|
|
})
|
|
|
|
const hotelFacilityDetailSchema = z.object({
|
|
heading: z.string(),
|
|
description: z.string(),
|
|
})
|
|
|
|
const hotelFacilitySchema = z.object({
|
|
breakfast: hotelFacilityDetailSchema,
|
|
checkout: hotelFacilityDetailSchema,
|
|
gym: hotelFacilityDetailSchema,
|
|
internet: hotelFacilityDetailSchema,
|
|
laundry: hotelFacilityDetailSchema,
|
|
luggage: hotelFacilityDetailSchema,
|
|
shop: hotelFacilityDetailSchema,
|
|
telephone: hotelFacilityDetailSchema,
|
|
})
|
|
|
|
const hotelInformationDetailSchema = z.object({
|
|
heading: z.string(),
|
|
description: z.string(),
|
|
link: z.string().optional(),
|
|
})
|
|
|
|
const hotelInformationSchema = z.object({
|
|
accessibility: hotelInformationDetailSchema,
|
|
safety: hotelInformationDetailSchema,
|
|
sustainability: hotelInformationDetailSchema,
|
|
})
|
|
|
|
const interiorSchema = z.object({
|
|
numberOfBeds: z.number(),
|
|
numberOfCribs: z.number(),
|
|
numberOfFloors: z.number(),
|
|
numberOfRooms: z.object({
|
|
connected: z.number(),
|
|
forAllergics: z.number().optional(),
|
|
forDisabled: z.number(),
|
|
nonSmoking: z.number(),
|
|
pet: z.number(),
|
|
withExtraBeds: z.number(),
|
|
total: z.number(),
|
|
}),
|
|
})
|
|
|
|
const receptionHoursSchema = z.object({
|
|
alwaysOpen: z.boolean(),
|
|
isClosed: z.boolean(),
|
|
openingTime: z.string().optional(),
|
|
closingTime: z.string().optional(),
|
|
})
|
|
|
|
const locationSchema = z.object({
|
|
distanceToCentre: z.number(),
|
|
latitude: z.number(),
|
|
longitude: z.number(),
|
|
})
|
|
|
|
const imageMetaDataSchema = z.object({
|
|
title: z.string(),
|
|
altText: z.string(),
|
|
altText_En: z.string(),
|
|
copyRight: z.string(),
|
|
})
|
|
|
|
const imageSizesSchema = z.object({
|
|
tiny: z.string(),
|
|
small: z.string(),
|
|
medium: z.string(),
|
|
large: z.string(),
|
|
})
|
|
|
|
const hotelContentSchema = z.object({
|
|
images: z.object({
|
|
metaData: imageMetaDataSchema,
|
|
imageSizes: imageSizesSchema,
|
|
}),
|
|
texts: z.object({
|
|
facilityInformation: z.string(),
|
|
surroundingInformation: z.string(),
|
|
descriptions: z.object({
|
|
short: z.string(),
|
|
medium: z.string(),
|
|
}),
|
|
}),
|
|
restaurantsOverviewPage: z.object({
|
|
restaurantsOverviewPageLinkText: z.string(),
|
|
restaurantsOverviewPageLink: z.string(),
|
|
restaurantsContentDescriptionShort: z.string(),
|
|
restaurantsContentDescriptionMedium: z.string(),
|
|
}),
|
|
})
|
|
|
|
const detailedFacilitySchema = z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
code: z.string().optional(),
|
|
applyToAllHotels: z.boolean(),
|
|
public: z.boolean(),
|
|
icon: z.string(),
|
|
iconName: z.string().optional(),
|
|
sortOrder: z.number(),
|
|
})
|
|
|
|
const healthFacilitySchema = z.object({
|
|
type: z.string(),
|
|
content: z.object({
|
|
images: z.array(
|
|
z.object({
|
|
metaData: imageMetaDataSchema,
|
|
imageSizes: imageSizesSchema,
|
|
})
|
|
),
|
|
texts: z.object({
|
|
facilityInformation: z.string().optional(),
|
|
surroundingInformation: z.string().optional(),
|
|
descriptions: z.object({
|
|
short: z.string(),
|
|
medium: z.string(),
|
|
}),
|
|
}),
|
|
}),
|
|
openingDetails: z.object({
|
|
useManualOpeningHours: z.boolean(),
|
|
manualOpeningHours: z.string().optional(),
|
|
openingHours: z.object({
|
|
ordinary: z.object({
|
|
alwaysOpen: z.boolean(),
|
|
isClosed: z.boolean(),
|
|
openingTime: z.string().optional(),
|
|
closingTime: z.string().optional(),
|
|
sortOrder: z.number().optional(),
|
|
}),
|
|
weekends: z.object({
|
|
alwaysOpen: z.boolean(),
|
|
isClosed: z.boolean(),
|
|
openingTime: z.string().optional(),
|
|
closingTime: z.string().optional(),
|
|
sortOrder: z.number().optional(),
|
|
}),
|
|
}),
|
|
}),
|
|
details: z.array(
|
|
z.object({
|
|
name: z.string(),
|
|
type: z.string(),
|
|
value: z.string().optional(),
|
|
})
|
|
),
|
|
})
|
|
|
|
const rewardNightSchema = z.object({
|
|
points: z.number(),
|
|
campaign: z.object({
|
|
start: z.string(),
|
|
end: z.string(),
|
|
points: z.number(),
|
|
}),
|
|
})
|
|
|
|
const pointsOfInterestSchema = z.object({
|
|
name: z.string(),
|
|
distance: z.number(),
|
|
category: z.object({
|
|
name: z.string(),
|
|
group: z.string(),
|
|
}),
|
|
location: locationSchema,
|
|
isHighlighted: z.boolean(),
|
|
})
|
|
|
|
const parkingPricingSchema = z.object({
|
|
freeParking: z.boolean(),
|
|
paymentType: z.string(),
|
|
localCurrency: z.object({
|
|
currency: z.string(),
|
|
range: z.object({
|
|
min: z.number(),
|
|
max: z.number().optional(),
|
|
}),
|
|
ordinary: z.array(
|
|
z.object({
|
|
period: z.string(),
|
|
amount: z.number().optional(),
|
|
startTime: z.string(),
|
|
endTime: z.string(),
|
|
})
|
|
),
|
|
weekend: z.array(
|
|
z.object({
|
|
period: z.string(),
|
|
amount: z.number().optional(),
|
|
startTime: z.string(),
|
|
endTime: z.string(),
|
|
})
|
|
),
|
|
}),
|
|
requestedCurrency: z
|
|
.object({
|
|
currency: z.string(),
|
|
range: z.object({
|
|
min: z.number(),
|
|
max: z.number(),
|
|
}),
|
|
ordinary: z.array(
|
|
z.object({
|
|
period: z.string(),
|
|
amount: z.number(),
|
|
startTime: z.string(),
|
|
endTime: z.string(),
|
|
})
|
|
),
|
|
weekend: z.array(
|
|
z.object({
|
|
period: z.string(),
|
|
amount: z.number(),
|
|
startTime: z.string(),
|
|
endTime: z.string(),
|
|
})
|
|
),
|
|
})
|
|
.optional(),
|
|
})
|
|
|
|
const parkingSchema = z.object({
|
|
type: z.string(),
|
|
name: z.string(),
|
|
address: z.string(),
|
|
numberOfParkingSpots: z.number(),
|
|
numberOfChargingSpaces: z.number(),
|
|
distanceToHotel: z.number(),
|
|
canMakeReservation: z.boolean(),
|
|
pricing: parkingPricingSchema,
|
|
})
|
|
|
|
const specialNeedSchema = z.object({
|
|
name: z.string(),
|
|
details: z.string(),
|
|
})
|
|
|
|
const specialNeedGroupSchema = z.object({
|
|
name: z.string(),
|
|
specialNeeds: z.array(specialNeedSchema),
|
|
})
|
|
|
|
const socialMediaSchema = z.object({
|
|
instagram: z.string().optional(),
|
|
facebook: z.string().optional(),
|
|
})
|
|
|
|
const metaSpecialAlertSchema = z.object({
|
|
type: z.string(),
|
|
description: z.string().optional(),
|
|
displayInBookingFlow: z.boolean(),
|
|
startDate: z.string(),
|
|
endDate: z.string(),
|
|
})
|
|
|
|
const metaSchema = z.object({
|
|
specialAlerts: z.array(metaSpecialAlertSchema),
|
|
})
|
|
|
|
const relationshipsSchema = z.object({
|
|
restaurants: z.object({
|
|
links: z.object({
|
|
related: z.string(),
|
|
}),
|
|
}),
|
|
nearbyHotels: z.object({
|
|
links: z.object({
|
|
related: z.string(),
|
|
}),
|
|
}),
|
|
roomCategories: z.object({
|
|
links: z.object({
|
|
related: z.string(),
|
|
}),
|
|
}),
|
|
meetingRooms: z.object({
|
|
links: z.object({
|
|
related: z.string(),
|
|
}),
|
|
}),
|
|
})
|
|
|
|
const roomContentSchema = z.object({
|
|
images: z.array(
|
|
z.object({
|
|
metaData: imageMetaDataSchema,
|
|
imageSizes: imageSizesSchema,
|
|
})
|
|
),
|
|
texts: z.object({
|
|
descriptions: z.object({
|
|
short: z.string(),
|
|
medium: z.string(),
|
|
}),
|
|
}),
|
|
})
|
|
|
|
const roomTypesSchema = z.object({
|
|
name: z.string(),
|
|
description: z.string(),
|
|
code: z.string(),
|
|
roomCount: z.number(),
|
|
mainBed: z.object({
|
|
type: z.string(),
|
|
description: z.string(),
|
|
widthRange: z.object({
|
|
min: z.number(),
|
|
max: z.number(),
|
|
}),
|
|
}),
|
|
fixedExtraBed: z.object({
|
|
type: z.string(),
|
|
description: z.string().optional(),
|
|
widthRange: z.object({
|
|
min: z.number(),
|
|
max: z.number(),
|
|
}),
|
|
}),
|
|
roomSize: z.object({
|
|
min: z.number(),
|
|
max: z.number(),
|
|
}),
|
|
occupancy: z.object({
|
|
total: z.number(),
|
|
adults: z.number(),
|
|
children: z.number(),
|
|
}),
|
|
isLackingCribs: z.boolean(),
|
|
isLackingExtraBeds: z.boolean(),
|
|
})
|
|
|
|
const roomFacilitiesSchema = z.object({
|
|
availableInAllRooms: z.boolean(),
|
|
name: z.string(),
|
|
isUniqueSellingPoint: z.boolean(),
|
|
sortOrder: z.number(),
|
|
})
|
|
|
|
export const roomSchema = z.object({
|
|
attributes: z.object({
|
|
name: z.string(),
|
|
sortOrder: z.number(),
|
|
content: roomContentSchema,
|
|
roomTypes: z.array(roomTypesSchema),
|
|
roomFacilities: z.array(roomFacilitiesSchema),
|
|
occupancy: z.object({
|
|
total: z.number(),
|
|
adults: z.number(),
|
|
children: z.number(),
|
|
}),
|
|
roomSize: z.object({
|
|
min: z.number(),
|
|
max: z.number(),
|
|
}),
|
|
}),
|
|
id: z.string(),
|
|
type: z.enum(["roomcategories"]),
|
|
})
|
|
|
|
// NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html
|
|
export const getHotelDataSchema = z.object({
|
|
data: z.object({
|
|
id: z.string(),
|
|
type: z.string(), // No enum here but the standard return appears to be "hotels".
|
|
language: z.string().transform((val) => {
|
|
const lang = toLang(val)
|
|
if (!lang) {
|
|
throw new Error("Invalid language")
|
|
}
|
|
return lang
|
|
}),
|
|
attributes: z.object({
|
|
name: z.string(),
|
|
operaId: z.string(),
|
|
keywords: z.array(z.string()),
|
|
isPublished: z.boolean(),
|
|
cityId: z.string(),
|
|
cityName: z.string(),
|
|
ratings: ratingsSchema,
|
|
address: addressSchema,
|
|
contactInformation: contactInformationSchema,
|
|
hotelFacts: z.object({
|
|
checkin: checkinSchema,
|
|
ecoLabels: ecoLabelsSchema,
|
|
hotelFacilityDetail: hotelFacilitySchema,
|
|
hotelInformation: hotelInformationSchema,
|
|
interior: interiorSchema,
|
|
receptionHours: receptionHoursSchema,
|
|
yearBuilt: z.string(),
|
|
}),
|
|
location: locationSchema,
|
|
hotelContent: hotelContentSchema,
|
|
detailedFacilities: z.array(detailedFacilitySchema),
|
|
healthFacilities: z.array(healthFacilitySchema),
|
|
rewardNight: rewardNightSchema,
|
|
pointsOfInterest: z.array(pointsOfInterestSchema),
|
|
parking: z.array(parkingSchema),
|
|
specialNeedGroups: z.array(specialNeedGroupSchema),
|
|
socialMedia: socialMediaSchema,
|
|
meta: metaSchema.optional(),
|
|
isActive: z.boolean(),
|
|
}),
|
|
relationships: relationshipsSchema,
|
|
}),
|
|
// NOTE: We can pass an "include" param to the hotel API to retrieve
|
|
// additional data for an individual hotel.
|
|
included: z.array(roomSchema).optional(),
|
|
})
|
|
|
|
const rate = z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
description: z.string(),
|
|
size: z.string(),
|
|
pricePerNight: z.number(),
|
|
currency: z.string(),
|
|
imageSrc: z.string(),
|
|
})
|
|
|
|
export const getRatesSchema = z.array(rate)
|
|
|
|
export type Rate = z.infer<typeof rate>
|
|
|
|
const hotelFilter = z.object({
|
|
roomFacilities: z.array(z.string()),
|
|
hotelFacilities: z.array(z.string()),
|
|
hotelSurroundings: z.array(z.string()),
|
|
})
|
|
|
|
export const getFiltersSchema = hotelFilter
|
|
export type HotelFilter = z.infer<typeof hotelFilter>
|