Merged in chore/remove-enrichHotel-call (pull request #3244)
Chore/remove enrichHotel call * chore: remove enrichHotel api call * include cityId when errors happen * remove unused funciton and cleanup code * tests no longer expect us to have called a function that is not used * remove commented code Approved-by: Linus Flood
This commit is contained in:
@@ -66,6 +66,7 @@ export async function GET(
|
|||||||
(x) =>
|
(x) =>
|
||||||
x.isActive &&
|
x.isActive &&
|
||||||
x.isPublished &&
|
x.isPublished &&
|
||||||
|
x.relationships.city.name &&
|
||||||
equalsIgnoreCaseAndAccents(x.relationships.city.name, cityParam)
|
equalsIgnoreCaseAndAccents(x.relationships.city.name, cityParam)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function createDataResponse(
|
|||||||
hotels: Array<{
|
hotels: Array<{
|
||||||
name: string
|
name: string
|
||||||
distanceToCentre?: number | undefined
|
distanceToCentre?: number | undefined
|
||||||
relationships: { city: { name: string } }
|
relationships: { city: { name: string | undefined } }
|
||||||
images?: { large?: string } | undefined
|
images?: { large?: string } | undefined
|
||||||
}>
|
}>
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -63,8 +63,10 @@ export async function GET(
|
|||||||
(x) =>
|
(x) =>
|
||||||
x.isActive &&
|
x.isActive &&
|
||||||
x.isPublished &&
|
x.isPublished &&
|
||||||
cities.some((c) =>
|
cities.some(
|
||||||
equalsIgnoreCaseAndAccents(x.relationships.city.name, c.name)
|
(c) =>
|
||||||
|
x.relationships.city.name &&
|
||||||
|
equalsIgnoreCaseAndAccents(x.relationships.city.name, c.name)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const autoCompleteLocationSchema = z.object({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
type: z.enum(["cities", "hotels", "countries"]),
|
type: z.enum(["cities", "hotels", "countries"]),
|
||||||
searchTokens: z.array(z.string()),
|
searchTokens: z.array(z.string()),
|
||||||
destination: z.string(),
|
destination: z.string().optional(),
|
||||||
url: z.string().optional(),
|
url: z.string().optional(),
|
||||||
cityIdentifier: z.string().optional(),
|
cityIdentifier: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function filterAutoCompleteLocations<T extends AutoCompleteLocation>(
|
|||||||
const searchable = locations.map((x) => ({
|
const searchable = locations.map((x) => ({
|
||||||
...x,
|
...x,
|
||||||
nameTokens: extractTokens(x.name),
|
nameTokens: extractTokens(x.name),
|
||||||
destinationTokens: extractTokens(x.destination),
|
destinationTokens: extractTokens(x.destination ?? ""),
|
||||||
}))
|
}))
|
||||||
fuseConfig.setCollection(searchable)
|
fuseConfig.setCollection(searchable)
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import { addressSchema } from "./schemas/hotel/address"
|
|||||||
import { detailedFacilitiesSchema } from "./schemas/hotel/detailedFacility"
|
import { detailedFacilitiesSchema } from "./schemas/hotel/detailedFacility"
|
||||||
import { locationSchema } from "./schemas/hotel/location"
|
import { locationSchema } from "./schemas/hotel/location"
|
||||||
import { imageSchema } from "./schemas/image"
|
import { imageSchema } from "./schemas/image"
|
||||||
import { locationCitySchema } from "./schemas/location/city"
|
|
||||||
import { locationHotelSchema } from "./schemas/location/hotel"
|
|
||||||
import { relationshipsSchema } from "./schemas/relationships"
|
import { relationshipsSchema } from "./schemas/relationships"
|
||||||
import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration"
|
import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration"
|
||||||
import { rateDefinitionSchema } from "./schemas/roomAvailability/rateDefinition"
|
import { rateDefinitionSchema } from "./schemas/roomAvailability/rateDefinition"
|
||||||
@@ -429,66 +427,6 @@ export const citiesSchema = z
|
|||||||
return null
|
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.attributes.countryName || "",
|
|
||||||
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,
|
|
||||||
operaId: location.attributes.operaId ?? "",
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.transform((data) =>
|
|
||||||
data
|
|
||||||
.filter((node) => !!node && node.isPublished)
|
|
||||||
.filter((node) => {
|
|
||||||
if (node.type === "hotels") {
|
|
||||||
if (!node.operaId) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!node.cityIdentifier) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
|
||||||
if (a.type === b.type) {
|
|
||||||
return a.name.localeCompare(b.name)
|
|
||||||
} else {
|
|
||||||
return a.type === "cities" ? -1 : 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
export type BreakfastPackages = z.output<typeof breakfastPackagesSchema>
|
export type BreakfastPackages = z.output<typeof breakfastPackagesSchema>
|
||||||
export const breakfastPackagesSchema = z
|
export const breakfastPackagesSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -42,14 +42,18 @@ export async function getHotelIdsByCityId({
|
|||||||
|
|
||||||
if (!apiResponse.ok) {
|
if (!apiResponse.ok) {
|
||||||
await metricsGetHotelIdsByCityId.httpError(apiResponse)
|
await metricsGetHotelIdsByCityId.httpError(apiResponse)
|
||||||
throw new Error("Unable to fetch hotelIds by cityId")
|
throw new Error(`Unable to fetch hotelIds by cityId`, {
|
||||||
|
cause: { cityId },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiJson = await apiResponse.json()
|
const apiJson = await apiResponse.json()
|
||||||
const validatedHotelIds = getHotelIdsSchema.safeParse(apiJson)
|
const validatedHotelIds = getHotelIdsSchema.safeParse(apiJson)
|
||||||
if (!validatedHotelIds.success) {
|
if (!validatedHotelIds.success) {
|
||||||
metricsGetHotelIdsByCityId.validationError(validatedHotelIds.error)
|
metricsGetHotelIdsByCityId.validationError(validatedHotelIds.error)
|
||||||
throw new Error("Unable to parse data for hotelIds by cityId")
|
throw new Error(`Unable to parse data for hotelIds by cityId`, {
|
||||||
|
cause: { cityId, errors: validatedHotelIds.error },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return validatedHotelIds.data
|
return validatedHotelIds.data
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Lang } from "@scandic-hotels/common/constants/language"
|
|||||||
import { getCacheClient } from "@scandic-hotels/common/dataCache"
|
import { getCacheClient } from "@scandic-hotels/common/dataCache"
|
||||||
|
|
||||||
import * as api from "../../../api"
|
import * as api from "../../../api"
|
||||||
import { getCity } from "./getCity"
|
// import { getCity } from "./getCity"
|
||||||
import { getLocationsByCountries } from "./getLocationsByCountries"
|
import { getLocationsByCountries } from "./getLocationsByCountries"
|
||||||
|
|
||||||
import type { CitiesGroupedByCountry } from "../../../types/locations"
|
import type { CitiesGroupedByCountry } from "../../../types/locations"
|
||||||
@@ -35,15 +35,16 @@ vi.mock("./getCity", () => {
|
|||||||
vi.mock("@scandic-hotels/common/logger/createLogger", () => {
|
vi.mock("@scandic-hotels/common/logger/createLogger", () => {
|
||||||
return {
|
return {
|
||||||
createLogger: () => ({
|
createLogger: () => ({
|
||||||
error: vi.fn(),
|
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
|
warn: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const mockedGetCacheClient = getCacheClient as unknown as Mock
|
const mockedGetCacheClient = getCacheClient as unknown as Mock
|
||||||
const mockedApiGet = api.get as unknown as Mock
|
const mockedApiGet = api.get as unknown as Mock
|
||||||
const mockedGetCity = getCity as unknown as Mock
|
// const mockedGetCity = getCity as unknown as Mock
|
||||||
|
|
||||||
describe("getLocationsByCountries", () => {
|
describe("getLocationsByCountries", () => {
|
||||||
const mockedCacheClient = {
|
const mockedCacheClient = {
|
||||||
@@ -125,20 +126,6 @@ describe("getLocationsByCountries", () => {
|
|||||||
json: async () => apiPayload,
|
json: async () => apiPayload,
|
||||||
})
|
})
|
||||||
|
|
||||||
// getCity returns enriched city object for hotel relationship
|
|
||||||
const mockedCity: Awaited<ReturnType<typeof getCity>> = {
|
|
||||||
cityIdentifier: "remote-ci-1",
|
|
||||||
ianaTimeZoneId: "Europe/Stockholm",
|
|
||||||
id: "remote-city-id",
|
|
||||||
isPublished: true,
|
|
||||||
keywords: [],
|
|
||||||
name: "RemoteCity",
|
|
||||||
timeZoneId: "Europe/Stockholm",
|
|
||||||
type: "cities",
|
|
||||||
}
|
|
||||||
|
|
||||||
mockedGetCity.mockResolvedValueOnce(mockedCity)
|
|
||||||
|
|
||||||
const citiesByCountry = {
|
const citiesByCountry = {
|
||||||
CountryX: [{ name: "CityAA" }],
|
CountryX: [{ name: "CityAA" }],
|
||||||
} as unknown as CitiesGroupedByCountry
|
} as unknown as CitiesGroupedByCountry
|
||||||
@@ -164,16 +151,13 @@ describe("getLocationsByCountries", () => {
|
|||||||
expect(cityNode!.country).toBe("CountryX") // country assigned based on citiesByCountry
|
expect(cityNode!.country).toBe("CountryX") // country assigned based on citiesByCountry
|
||||||
|
|
||||||
expect(hotelNode).toBeDefined()
|
expect(hotelNode).toBeDefined()
|
||||||
expect(mockedGetCity).toHaveBeenCalledWith({
|
|
||||||
cityUrl: "https://api/cities/city1",
|
|
||||||
serviceToken: "token",
|
|
||||||
})
|
|
||||||
// hotel relationships.city should be the object returned by getCity (merged)
|
// hotel relationships.city should be the object returned by getCity (merged)
|
||||||
expect(hotelNode?.relationships).toBeDefined()
|
expect(hotelNode?.relationships).toBeDefined()
|
||||||
expect(hotelNode?.relationships.city).toEqual(
|
expect(hotelNode?.relationships.city).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: mockedCity.id,
|
id: "city1",
|
||||||
name: mockedCity.name,
|
name: "City1",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -192,19 +176,6 @@ describe("getLocationsByCountries", () => {
|
|||||||
json: async () => apiPayload,
|
json: async () => apiPayload,
|
||||||
})
|
})
|
||||||
|
|
||||||
// getCity returns enriched city object for hotel relationship
|
|
||||||
const mockedCity: Awaited<ReturnType<typeof getCity>> = {
|
|
||||||
cityIdentifier: "remote-ci-1",
|
|
||||||
ianaTimeZoneId: "Europe/Stockholm",
|
|
||||||
id: "remote-city-id",
|
|
||||||
isPublished: true,
|
|
||||||
keywords: [],
|
|
||||||
name: "RemoteCity",
|
|
||||||
timeZoneId: "Europe/Stockholm",
|
|
||||||
type: "cities",
|
|
||||||
}
|
|
||||||
mockedGetCity.mockResolvedValue(mockedCity)
|
|
||||||
|
|
||||||
const citiesByCountry = {
|
const citiesByCountry = {
|
||||||
CountryX: [{ name: "CityAA" }],
|
CountryX: [{ name: "CityAA" }],
|
||||||
} as unknown as CitiesGroupedByCountry
|
} as unknown as CitiesGroupedByCountry
|
||||||
@@ -230,17 +201,13 @@ describe("getLocationsByCountries", () => {
|
|||||||
.filter((n) => n.type === "hotels")
|
.filter((n) => n.type === "hotels")
|
||||||
.find((n) => n.name === "Hotel1")
|
.find((n) => n.name === "Hotel1")
|
||||||
expect(hotel1).toBeDefined()
|
expect(hotel1).toBeDefined()
|
||||||
expect(mockedGetCity).toHaveBeenCalledWith({
|
|
||||||
cityUrl: "https://api/cities/city1",
|
|
||||||
serviceToken: "token",
|
|
||||||
})
|
|
||||||
|
|
||||||
// hotel relationships.city should be the object returned by getCity (merged)
|
// hotel relationships.city should be the object returned by getCity (merged)
|
||||||
expect(hotel1?.relationships).toBeDefined()
|
expect(hotel1?.relationships).toBeDefined()
|
||||||
expect(hotel1?.relationships.city).toEqual(
|
expect(hotel1?.relationships.city).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: mockedCity.id,
|
id: "city1",
|
||||||
name: mockedCity.name,
|
name: "City1",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -248,20 +215,15 @@ describe("getLocationsByCountries", () => {
|
|||||||
.filter((n) => n.type === "hotels")
|
.filter((n) => n.type === "hotels")
|
||||||
.find((n) => n.name === "Hotel2")
|
.find((n) => n.name === "Hotel2")
|
||||||
expect(hotel2).toBeDefined()
|
expect(hotel2).toBeDefined()
|
||||||
expect(mockedGetCity).toHaveBeenCalledWith({
|
|
||||||
cityUrl: "https://api/cities/city2",
|
|
||||||
serviceToken: "token",
|
|
||||||
})
|
|
||||||
// hotel relationships.city should be the object returned by getCity (merged)
|
// hotel relationships.city should be the object returned by getCity (merged)
|
||||||
expect(hotel2?.relationships).toBeDefined()
|
expect(hotel2?.relationships).toBeDefined()
|
||||||
expect(hotel2?.relationships.city).toEqual(
|
expect(hotel2?.relationships.city).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: mockedCity.id,
|
id: "city2",
|
||||||
name: mockedCity.name,
|
name: "City2",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(mockedGetCity).toHaveBeenCalledTimes(2)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("filters out unpublished cities", async () => {
|
it("filters out unpublished cities", async () => {
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import deepmerge from "deepmerge"
|
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { getCacheClient } from "@scandic-hotels/common/dataCache"
|
import { getCacheClient } from "@scandic-hotels/common/dataCache"
|
||||||
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
||||||
import { chunk } from "@scandic-hotels/common/utils/chunk"
|
|
||||||
|
|
||||||
import * as api from "../../../api"
|
import * as api from "../../../api"
|
||||||
import { serverErrorByStatus } from "../../../errors"
|
import { serverErrorByStatus } from "../../../errors"
|
||||||
import { toApiLang } from "../../../utils"
|
import { toApiLang } from "../../../utils"
|
||||||
import { locationCitySchema } from "../schemas/location/city"
|
import { locationCitySchema } from "../schemas/location/city"
|
||||||
import { locationHotelSchema } from "../schemas/location/hotel"
|
import { locationHotelSchema } from "../schemas/location/hotel"
|
||||||
import { getCity } from "./getCity"
|
|
||||||
|
|
||||||
import type { Country } from "@scandic-hotels/common/constants/country"
|
import type { Country } from "@scandic-hotels/common/constants/country"
|
||||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
@@ -21,6 +18,14 @@ type CitiesNamesByCountry = Record<
|
|||||||
Country | (string & {}),
|
Country | (string & {}),
|
||||||
Array<{ name: string }>
|
Array<{ name: string }>
|
||||||
> | null
|
> | null
|
||||||
|
type Hotel = Extract<
|
||||||
|
z.infer<typeof locationsSchema>["data"][number],
|
||||||
|
{ type: "hotels" }
|
||||||
|
>
|
||||||
|
type City = Extract<
|
||||||
|
z.infer<typeof locationsSchema>["data"][number],
|
||||||
|
{ type: "cities" }
|
||||||
|
>
|
||||||
|
|
||||||
export async function getLocationsByCountries({
|
export async function getLocationsByCountries({
|
||||||
lang,
|
lang,
|
||||||
@@ -73,21 +78,11 @@ export async function getLocationsByCountries({
|
|||||||
const data = cleanData(verifiedLocations.data.data)
|
const data = cleanData(verifiedLocations.data.data)
|
||||||
const cities = data
|
const cities = data
|
||||||
.filter((x) => x.type === "cities")
|
.filter((x) => x.type === "cities")
|
||||||
.map((x) => enrichCity(x, citiesByCountry))
|
.map((city) => addCountryDataToCity(city, citiesByCountry))
|
||||||
|
|
||||||
const chunkedHotels = chunk(
|
const hotels = data
|
||||||
data.filter((x) => x.type === "hotels"),
|
.filter((x) => x.type === "hotels")
|
||||||
10
|
.map((hotel) => addCityDataToHotel(hotel, cities))
|
||||||
)
|
|
||||||
const hotels = (
|
|
||||||
await Promise.all(
|
|
||||||
chunkedHotels.flatMap(async (chunk) => {
|
|
||||||
return await Promise.all(
|
|
||||||
chunk.flatMap(async (hotel) => enrichHotel(hotel, serviceToken))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
).flat()
|
|
||||||
|
|
||||||
let locations: z.infer<typeof locationsSchema>["data"] = [
|
let locations: z.infer<typeof locationsSchema>["data"] = [
|
||||||
...cities,
|
...cities,
|
||||||
@@ -100,48 +95,32 @@ export async function getLocationsByCountries({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function enrichHotel(
|
function addCityDataToHotel(hotel: Hotel, cities: City[]) {
|
||||||
hotel: Extract<
|
const city = cities.find((c) => c.id === hotel.relationships.city.id)
|
||||||
z.infer<typeof locationsSchema>["data"][number],
|
|
||||||
{ type: "hotels" }
|
|
||||||
>,
|
|
||||||
serviceToken: string
|
|
||||||
): Promise<
|
|
||||||
Extract<z.infer<typeof locationsSchema>["data"][number], { type: "hotels" }>
|
|
||||||
> {
|
|
||||||
if (hotel.type !== "hotels") {
|
|
||||||
return hotel
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hotel.relationships.city?.url) {
|
|
||||||
return hotel
|
|
||||||
}
|
|
||||||
const city = await getCity({
|
|
||||||
cityUrl: hotel.relationships.city.url,
|
|
||||||
serviceToken,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!city) {
|
if (!city) {
|
||||||
|
hotelUtilsLogger.warn(
|
||||||
|
`City with id ${hotel.relationships.city.id} not found for hotel ${hotel.id}`
|
||||||
|
)
|
||||||
return hotel
|
return hotel
|
||||||
}
|
}
|
||||||
|
|
||||||
return deepmerge(hotel, {
|
return {
|
||||||
|
...hotel,
|
||||||
relationships: {
|
relationships: {
|
||||||
city,
|
city: {
|
||||||
|
...city,
|
||||||
|
cityIdentifier: city.cityIdentifier,
|
||||||
|
url: hotel.relationships.city.url,
|
||||||
|
keywords: city.keyWords ?? [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
} satisfies Hotel
|
||||||
}
|
}
|
||||||
|
|
||||||
function enrichCity(
|
function addCountryDataToCity(
|
||||||
city: Extract<
|
city: City,
|
||||||
z.infer<typeof locationsSchema>["data"][number],
|
|
||||||
{ type: "cities" }
|
|
||||||
>,
|
|
||||||
citiesByCountry: CitiesNamesByCountry | null
|
citiesByCountry: CitiesNamesByCountry | null
|
||||||
): Extract<
|
): City {
|
||||||
z.infer<typeof locationsSchema>["data"][number],
|
|
||||||
{ type: "cities" }
|
|
||||||
> {
|
|
||||||
if (!citiesByCountry) {
|
if (!citiesByCountry) {
|
||||||
return city
|
return city
|
||||||
}
|
}
|
||||||
@@ -206,13 +185,13 @@ export const locationsSchema = z.object({
|
|||||||
id: location.id,
|
id: location.id,
|
||||||
relationships: {
|
relationships: {
|
||||||
city: {
|
city: {
|
||||||
cityIdentifier: "",
|
cityIdentifier: undefined as string | undefined,
|
||||||
ianaTimeZoneId: "",
|
id: extractCityId(
|
||||||
id: "",
|
location.relationships?.city?.links?.related ?? ""
|
||||||
|
),
|
||||||
isPublished: false,
|
isPublished: false,
|
||||||
keywords: [],
|
keywords: [] as string[],
|
||||||
name: "",
|
name: undefined as string | undefined,
|
||||||
timeZoneId: "",
|
|
||||||
type: "cities",
|
type: "cities",
|
||||||
url: location?.relationships?.city?.links?.related ?? "",
|
url: location?.relationships?.city?.links?.related ?? "",
|
||||||
},
|
},
|
||||||
@@ -223,3 +202,18 @@ export const locationsSchema = z.object({
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function extractCityId(cityUrl: string): string | null {
|
||||||
|
try {
|
||||||
|
const url = new URL(cityUrl)
|
||||||
|
if (!url.pathname.toLowerCase().includes("/cities/")) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = url.pathname.split("/").at(-1)
|
||||||
|
|
||||||
|
return id ?? null
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import type { z } from "zod"
|
|||||||
import type {
|
import type {
|
||||||
citiesByCountrySchema,
|
citiesByCountrySchema,
|
||||||
countriesSchema,
|
countriesSchema,
|
||||||
locationsSchema,
|
|
||||||
} from "../routers/hotels/output"
|
} from "../routers/hotels/output"
|
||||||
|
import type { locationsSchema } from "../routers/hotels/services/getLocationsByCountries"
|
||||||
|
|
||||||
export interface LocationSchema extends z.output<typeof locationsSchema> {}
|
export interface LocationSchema extends z.output<typeof locationsSchema> {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user