diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx index f29e8e28c..32be4113d 100644 --- a/components/ContentType/HotelPage/index.tsx +++ b/components/ContentType/HotelPage/index.tsx @@ -1,7 +1,5 @@ import { serverClient } from "@/lib/trpc/server" -import { getLang } from "@/i18n/serverContext" - import { MOCK_FACILITIES } from "./Facilities/mockData" import AmenitiesList from "./AmenitiesList" import Facilities from "./Facilities" @@ -13,17 +11,7 @@ import TabNavigation from "./TabNavigation" import styles from "./hotelPage.module.css" export default async function HotelPage() { - const hotelPageIdentifierData = - await serverClient().contentstack.hotelPage.get() - - if (!hotelPageIdentifierData) { - return null - } - const lang = getLang() - const hotelData = await serverClient().hotel.get({ - hotelId: hotelPageIdentifierData.hotel_page_id, - language: lang, include: ["RoomCategories"], }) if (!hotelData) { diff --git a/server/errors/trpc.ts b/server/errors/trpc.ts index e9884d2d9..ac61d9015 100644 --- a/server/errors/trpc.ts +++ b/server/errors/trpc.ts @@ -59,3 +59,17 @@ export function publicUnauthorizedError() { cause: new PublicUnauthorizedError(PUBLIC_UNAUTHORIZED), }) } + +export function serverErrorByStatus(status: number, cause?: unknown) { + switch (status) { + case 401: + return unauthorizedError(cause) + case 403: + return forbiddenError(cause) + case 404: + return notFound(cause) + case 500: + default: + return internalServerError(cause) + } +} diff --git a/server/routers/hotels/input.ts b/server/routers/hotels/input.ts index 0b5d16b00..eb31e729d 100644 --- a/server/routers/hotels/input.ts +++ b/server/routers/hotels/input.ts @@ -1,10 +1,6 @@ import { z } from "zod" -import { Lang } from "@/constants/languages" - export const getHotelInputSchema = z.object({ - hotelId: z.string(), - language: z.nativeEnum(Lang), include: z .array(z.enum(["RoomCategories", "NearbyHotels", "Restaurants", "City"])) .optional(), diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index e7b85f7a3..9c55c56eb 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -233,7 +233,7 @@ const parkingPricingSchema = z.object({ localCurrency: z.object({ currency: z.string(), range: z.object({ - min: z.number(), + min: z.number().optional(), max: z.number().optional(), }), ordinary: z.array( @@ -284,8 +284,8 @@ const parkingSchema = z.object({ type: z.string(), name: z.string(), address: z.string(), - numberOfParkingSpots: z.number(), - numberOfChargingSpaces: z.number(), + numberOfParkingSpots: z.number().optional(), + numberOfChargingSpaces: z.number().optional(), distanceToHotel: z.number(), canMakeReservation: z.boolean(), pricing: parkingPricingSchema, diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index 404af8510..5397fc66d 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -1,10 +1,24 @@ import { metrics } from "@opentelemetry/api" import * as api from "@/lib/api" -import { badRequestError } from "@/server/errors/trpc" -import { publicProcedure, router, serviceProcedure } from "@/server/trpc" +import { GetHotelPage } from "@/lib/graphql/Query/HotelPage.graphql" +import { request } from "@/lib/graphql/request" +import { + badRequestError, + notFound, + serverErrorByStatus, +} from "@/server/errors/trpc" +import { + contentStackUidWithServiceProcedure, + publicProcedure, + router, +} from "@/server/trpc" import { toApiLang } from "@/server/utils" +import { + HotelPageDataRaw, + validateHotelPageSchema, +} from "../contentstack/hotelPage/output" import { getFiltersInputSchema, getHotelInputSchema, @@ -24,23 +38,54 @@ const getHotelCounter = meter.createCounter("trpc.hotel.get") const getHotelSuccessCounter = meter.createCounter("trpc.hotel.get-success") const getHotelFailCounter = meter.createCounter("trpc.hotel.get-fail") +const getHotelId = async (locale: string, uid: string | null | undefined) => { + const rawContentStackData = await request(GetHotelPage, { + locale, + uid, + }) + + if (!rawContentStackData.data) { + throw notFound(rawContentStackData) + } + + const hotelPageData = validateHotelPageSchema.safeParse( + rawContentStackData.data + ) + + if (!hotelPageData.success) { + console.error( + `Failed to validate Hotel Page - (uid: ${uid}, lang: ${locale})` + ) + console.error(hotelPageData.error) + return null + } + + return hotelPageData.data.hotel_page.hotel_page_id +} + export const hotelQueryRouter = router({ - get: serviceProcedure + get: contentStackUidWithServiceProcedure .input(getHotelInputSchema) .query(async ({ ctx, input }) => { - const { hotelId, language, include } = input - getHotelCounter.add(1, { hotelId, language, include }) + const { lang, uid } = ctx + const { include } = input + const hotelId = await getHotelId(lang, uid) - const apiLang = toApiLang(language) + if (!hotelId) { + throw notFound(`Hotel not found for uid: ${uid}`) + } + + const apiLang = toApiLang(lang) const params: Record = { hotelId, language: apiLang, } + if (include) { params.include = include.join(",") } - getHotelCounter.add(1, { hotelId, language, include }) + getHotelCounter.add(1, { hotelId, lang, include }) console.info( "api.hotels.hotel start", JSON.stringify({ @@ -62,7 +107,7 @@ export const hotelQueryRouter = router({ const text = await apiResponse.text() getHotelFailCounter.add(1, { hotelId, - language, + lang, include, error_type: "http_error", error: JSON.stringify({ @@ -82,7 +127,7 @@ export const hotelQueryRouter = router({ }, }) ) - return null + throw serverErrorByStatus(apiResponse.status, apiResponse) } const apiJson = await apiResponse.json() const validatedHotelData = getHotelDataSchema.safeParse(apiJson) @@ -90,7 +135,7 @@ export const hotelQueryRouter = router({ if (!validatedHotelData.success) { getHotelFailCounter.add(1, { hotelId, - language, + lang, include, error_type: "validation_error", error: JSON.stringify(validatedHotelData.error), @@ -116,7 +161,7 @@ export const hotelQueryRouter = router({ if (!validatedRoom.success) { getHotelFailCounter.add(1, { hotelId, - language, + lang, include, error_type: "validation_error", error: JSON.stringify( @@ -140,7 +185,7 @@ export const hotelQueryRouter = router({ }) : [] - getHotelSuccessCounter.add(1, { hotelId, language, include }) + getHotelSuccessCounter.add(1, { hotelId, lang, include }) console.info( "api.hotels.hotel success", JSON.stringify({ diff --git a/server/trpc.ts b/server/trpc.ts index 4f3a6f4f9..7a7a7f7ea 100644 --- a/server/trpc.ts +++ b/server/trpc.ts @@ -4,13 +4,13 @@ import { ZodError } from "zod" import { env } from "@/env/server" +import { type Context, createContext } from "./context" import { badRequestError, internalServerError, sessionExpiredError, unauthorizedError, } from "./errors/trpc" -import { type Context, createContext } from "./context" import { fetchServiceToken } from "./tokenManager" import { transformer } from "./transformer" @@ -146,3 +146,8 @@ export const protectedServerActionProcedure = serverActionProcedure.use( }) } ) + +// NOTE: This is actually save to use, just the implementation could change +// in minor version bumps. Please read: https://trpc.io/docs/faq#unstable +export const contentStackUidWithServiceProcedure = + contentstackExtendedProcedureUID.unstable_concat(serviceProcedure)