Feature/wrap logging * feat: change all logging to go through our own logger function so that we can control log levels * move packages/trpc to using our own logger * merge Approved-by: Linus Flood
201 lines
6.1 KiB
TypeScript
201 lines
6.1 KiB
TypeScript
import { z } from "zod"
|
|
|
|
import { Lang } from "@scandic-hotels/common/constants/language"
|
|
import { getCacheClient } from "@scandic-hotels/common/dataCache"
|
|
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
|
import { isDefined } from "@scandic-hotels/common/utils/isDefined"
|
|
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
|
|
|
|
import { safeProtectedServiceProcedure } from "../../procedures"
|
|
import { getCityPageUrls } from "../../routers/contentstack/destinationCityPage/utils"
|
|
import { getCountryPageUrls } from "../../routers/contentstack/destinationCountryPage/utils"
|
|
import { getHotelPageUrls } from "../../routers/contentstack/hotelPage/utils"
|
|
import {
|
|
getCitiesByCountry,
|
|
getCountries,
|
|
getLocations,
|
|
} from "../../routers/hotels/utils"
|
|
import { ApiCountry, type Country } from "../../types/country"
|
|
import { filterAndCategorizeAutoComplete } from "./util/filterAndCategorizeAutoComplete"
|
|
import { mapLocationToAutoCompleteLocation } from "./util/mapLocationToAutoCompleteLocation"
|
|
|
|
import type { AutoCompleteLocation } from "./schema"
|
|
|
|
const destinationsAutoCompleteInputSchema = z.object({
|
|
query: z.string(),
|
|
selectedHotelId: z.string().optional(),
|
|
selectedCity: z.string().optional(),
|
|
lang: z.nativeEnum(Lang),
|
|
includeTypes: z.array(z.enum(["hotels", "cities", "countries"])),
|
|
})
|
|
|
|
type DestinationsAutoCompleteOutput = {
|
|
hits: {
|
|
hotels: AutoCompleteLocation[]
|
|
cities: AutoCompleteLocation[]
|
|
countries: AutoCompleteLocation[]
|
|
}
|
|
currentSelection: {
|
|
hotel: (AutoCompleteLocation & { type: "hotels" }) | null
|
|
city: (AutoCompleteLocation & { type: "cities" }) | null
|
|
}
|
|
}
|
|
|
|
export const getDestinationsAutoCompleteRoute = safeProtectedServiceProcedure
|
|
.input(destinationsAutoCompleteInputSchema)
|
|
.query(async ({ ctx, input }): Promise<DestinationsAutoCompleteOutput> => {
|
|
const lang = input.lang || ctx.lang
|
|
const locations: AutoCompleteLocation[] =
|
|
await getAutoCompleteDestinationsData({
|
|
lang,
|
|
serviceToken: ctx.serviceToken,
|
|
})
|
|
|
|
const hits = filterAndCategorizeAutoComplete({
|
|
locations: locations,
|
|
query: input.query,
|
|
includeTypes: input.includeTypes,
|
|
})
|
|
|
|
const selectedHotel = locations.find(
|
|
(location) =>
|
|
location.type === "hotels" && location.id === input.selectedHotelId
|
|
)
|
|
|
|
const selectedCity = locations.find(
|
|
(location) =>
|
|
location.type === "cities" &&
|
|
location.cityIdentifier === input.selectedCity
|
|
)
|
|
|
|
return {
|
|
hits: hits,
|
|
currentSelection: {
|
|
city: isCity(selectedCity) ? selectedCity : null,
|
|
hotel: isHotel(selectedHotel) ? selectedHotel : null,
|
|
},
|
|
}
|
|
})
|
|
|
|
function isHotel(
|
|
location: AutoCompleteLocation | null | undefined
|
|
): location is AutoCompleteLocation & { type: "hotels" } {
|
|
return !!location && location.type === "hotels"
|
|
}
|
|
|
|
function isCity(
|
|
location: AutoCompleteLocation | null | undefined
|
|
): location is AutoCompleteLocation & { type: "cities" } {
|
|
return !!location && location.type === "cities"
|
|
}
|
|
|
|
export async function getAutoCompleteDestinationsData({
|
|
lang,
|
|
serviceToken,
|
|
warmup = false,
|
|
}: {
|
|
lang: Lang
|
|
serviceToken: string
|
|
warmup?: boolean
|
|
}) {
|
|
const autoCompleteLogger = createLogger("autocomplete-destinations")
|
|
const cacheClient = await getCacheClient()
|
|
return await cacheClient.cacheOrGet(
|
|
`autocomplete:destinations:locations:${lang}`,
|
|
async () => {
|
|
const hotelUrlsPromise = safeTry(getHotelPageUrls(lang))
|
|
const cityUrlsPromise = safeTry(getCityPageUrls(lang))
|
|
const countryUrlsPromise = safeTry(getCountryPageUrls(lang))
|
|
const countries = await getCountries({
|
|
lang: lang,
|
|
serviceToken,
|
|
})
|
|
|
|
if (!countries) {
|
|
autoCompleteLogger.error("Unable to fetch countries")
|
|
throw new Error("Unable to fetch countries")
|
|
}
|
|
|
|
const countryNames = countries.data.map((country) => country.name)
|
|
const citiesByCountry = await getCitiesByCountry({
|
|
countries: countryNames,
|
|
serviceToken: serviceToken,
|
|
lang,
|
|
})
|
|
|
|
const locations = await getLocations({
|
|
lang: lang,
|
|
serviceToken: serviceToken,
|
|
citiesByCountry: citiesByCountry,
|
|
})
|
|
|
|
const activeLocations = locations.filter((location) => {
|
|
return (
|
|
location.type === "cities" ||
|
|
(location.type === "hotels" && location.isActive)
|
|
)
|
|
})
|
|
|
|
const [hotelUrls, hotelUrlsError] = await hotelUrlsPromise
|
|
const [cityUrls, cityUrlsError] = await cityUrlsPromise
|
|
const [countryUrls, countryUrlsError] = await countryUrlsPromise
|
|
|
|
if (
|
|
hotelUrlsError ||
|
|
cityUrlsError ||
|
|
countryUrlsError ||
|
|
!hotelUrls ||
|
|
!cityUrls ||
|
|
!countryUrls
|
|
) {
|
|
autoCompleteLogger.error("Unable to fetch location URLs")
|
|
throw new Error("Unable to fetch location URLs")
|
|
}
|
|
|
|
const hotelsAndCities = activeLocations
|
|
.map((location) => {
|
|
let url: string | undefined
|
|
|
|
if (location.type === "cities") {
|
|
url = cityUrls.find(
|
|
(c) =>
|
|
c.city &&
|
|
location.cityIdentifier &&
|
|
c.city === location.cityIdentifier
|
|
)?.url
|
|
}
|
|
|
|
if (location.type === "hotels") {
|
|
url = hotelUrls.find(
|
|
(h) => h.hotelId && location.id && h.hotelId === location.id
|
|
)?.url
|
|
}
|
|
|
|
return { ...location, url }
|
|
})
|
|
.map(mapLocationToAutoCompleteLocation)
|
|
.filter(isDefined)
|
|
|
|
const countryAutoCompleteLocations = countries.data.map((country) => {
|
|
const url = countryUrls.find(
|
|
(c) =>
|
|
c.country && ApiCountry[lang][c.country as Country] === country.name
|
|
)?.url
|
|
|
|
return {
|
|
id: country.id,
|
|
name: country.name,
|
|
type: "countries",
|
|
searchTokens: [country.name],
|
|
destination: "",
|
|
url,
|
|
} satisfies AutoCompleteLocation
|
|
})
|
|
|
|
return [...hotelsAndCities, ...countryAutoCompleteLocations]
|
|
},
|
|
"1d",
|
|
{ cacheStrategy: warmup ? "fetch-then-cache" : "cache-first" }
|
|
)
|
|
}
|