Merged in feat/sw-2859-set-up-shared-trpc-package (pull request #2319)

feat(SW-2859): Create trpc package

* Add isEdge, safeTry and dataCache to new common package

* Add eslint and move prettier config

* Clean up tests

* Create trpc package and move initialization

* Move errors and a few procedures

* Move telemetry to common package

* Move tokenManager to common package

* Add Sentry to procedures

* Clean up procedures

* Fix self-referencing imports

* Add exports to packages and lint rule to prevent relative imports

* Add env to trpc package

* Add eslint to trpc package

* Apply lint rules

* Use direct imports from trpc package

* Add lint-staged config to trpc

* Move lang enum to common

* Restructure trpc package folder structure

* Fix lang imports


Approved-by: Linus Flood
This commit is contained in:
Anton Gunnarsson
2025-06-18 12:14:20 +00:00
parent 2f38bdf0b1
commit 846fd904a6
211 changed files with 989 additions and 627 deletions

View File

@@ -1,72 +0,0 @@
import { cookies, headers } from "next/headers"
import { type Session } from "next-auth"
import { cache } from "react"
import { auth } from "@/auth"
import type { Lang } from "@/constants/languages"
typeof auth
type CreateContextOptions = {
auth: () => Promise<Session | null>
lang: Lang
pathname: string
uid?: string | null
url: string
webToken?: string
contentType?: string
}
/** Use this helper for:
* - testing, where we dont have to Mock Next.js' req/res
* - trpc's `createSSGHelpers` where we don't have req/res
**/
export function createContextInner(opts: CreateContextOptions) {
return {
auth: opts.auth,
lang: opts.lang,
pathname: opts.pathname,
uid: opts.uid,
url: opts.url,
webToken: opts.webToken,
contentType: opts.contentType,
}
}
/**
* This is the actual context you'll use in your router
* @link https://trpc.io/docs/context
**/
export const createContext = cache(async function () {
const headersList = await headers()
const cookie = await cookies()
const webviewTokenCookie = cookie.get("webviewToken")
const loginType = headersList.get("loginType")
return createContextInner({
auth: async () => {
const session = await auth()
const webToken = webviewTokenCookie?.value
if (!session?.token && !webToken) {
return null
}
return (
session ||
({
token: { access_token: webToken, loginType },
} as Session)
)
},
lang: headersList.get("x-lang") as Lang,
pathname: headersList.get("x-pathname")!,
uid: headersList.get("x-uid"),
url: headersList.get("x-url")!,
webToken: webviewTokenCookie?.value,
contentType: headersList.get("x-contenttype")!,
})
})
export type Context = Awaited<ReturnType<typeof createContext>>

View File

@@ -1,95 +0,0 @@
import { TRPCError } from "@trpc/server"
export function unauthorizedError(cause?: unknown) {
return new TRPCError({
code: "UNAUTHORIZED",
message: `Unauthorized`,
cause,
})
}
export function forbiddenError(cause?: unknown) {
return new TRPCError({
code: "FORBIDDEN",
message: `Forbidden`,
cause,
})
}
export function conflictError(cause?: unknown) {
return new TRPCError({
code: "CONFLICT",
message: `Conflict`,
cause,
})
}
export function badRequestError(cause?: unknown) {
return new TRPCError({
code: "BAD_REQUEST",
message: `Bad request`,
cause,
})
}
export function notFound(cause?: unknown) {
return new TRPCError({
code: "NOT_FOUND",
message: `Not found`,
cause,
})
}
export function unprocessableContent(cause?: unknown) {
return new TRPCError({
code: "UNPROCESSABLE_CONTENT",
message: "Unprocessable content",
cause,
})
}
export function internalServerError(cause?: unknown) {
return new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Internal Server Error`,
cause,
})
}
export const SESSION_EXPIRED = "SESSION_EXPIRED"
export class SessionExpiredError extends Error {}
export function sessionExpiredError() {
return new TRPCError({
code: "UNAUTHORIZED",
message: SESSION_EXPIRED,
cause: new SessionExpiredError(SESSION_EXPIRED),
})
}
export const PUBLIC_UNAUTHORIZED = "PUBLIC_UNAUTHORIZED"
export class PublicUnauthorizedError extends Error {}
export function publicUnauthorizedError() {
return new TRPCError({
code: "UNAUTHORIZED",
message: PUBLIC_UNAUTHORIZED,
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 409:
return conflictError(cause)
case 422:
return unprocessableContent(cause)
case 500:
default:
return internalServerError(cause)
}
}

View File

@@ -1,4 +1,6 @@
/** Routers */
import { router } from "@scandic-hotels/trpc"
import { autocompleteRouter } from "./routers/autocomplete"
import { bookingRouter } from "./routers/booking"
import { contentstackRouter } from "./routers/contentstack"
@@ -6,7 +8,6 @@ import { hotelsRouter } from "./routers/hotels"
import { navigationRouter } from "./routers/navigation"
import { partnerRouter } from "./routers/partners"
import { userRouter } from "./routers/user"
import { router } from "./trpc"
export const appRouter = router({
booking: bookingRouter,

View File

@@ -3,8 +3,8 @@ import { z } from "zod"
import { parseRefId } from "@/utils/refId"
import type { Meta } from "@/types/trpc/meta"
import type { Context } from "../context"
import type { Meta } from "@scandic-hotels/trpc"
import type { Context } from "@scandic-hotels/trpc/context"
export function createRefIdPlugin() {
const t = initTRPC.context<Context>().meta<Meta>().create()

View File

@@ -1,10 +1,10 @@
import { z } from "zod"
import { Lang } from "@scandic-hotels/common/constants/language"
import { getCacheClient } from "@scandic-hotels/common/dataCache"
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import { safeProtectedServiceProcedure } from "@scandic-hotels/trpc/procedures"
import { Lang } from "@/constants/languages"
import { safeProtectedServiceProcedure } from "@/server/trpc"
import { isDefined } from "@/server/utils"
import { getCityPageUrls } from "../contentstack/destinationCityPage/utils"

View File

@@ -1,4 +1,4 @@
import { router } from "@/server/trpc"
import { router } from "@scandic-hotels/trpc"
import { getDestinationsAutoCompleteRoute } from "./destinations"

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { bookingMutationRouter } from "./mutation"
import { bookingQueryRouter } from "./query"

View File

@@ -1,7 +1,9 @@
import { z } from "zod"
import { Lang } from "@scandic-hotels/common/constants/language"
import { ChildBedTypeEnum } from "@/constants/booking"
import { Lang, langToApiLang } from "@/constants/languages"
import { langToApiLang } from "@/constants/languages"
const roomsSchema = z
.array(

View File

@@ -1,8 +1,10 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { safeProtectedServiceProcedure } from "@scandic-hotels/trpc/procedures"
import * as api from "@/lib/api"
import { createRefIdPlugin } from "@/server/plugins/refIdToConfirmationNumber"
import { getMembershipNumber } from "@/server/routers/user/utils"
import { createCounter } from "@/server/telemetry"
import { router, safeProtectedServiceProcedure } from "@/server/trpc"
import { encrypt } from "@/utils/encryption"
import { isValidSession } from "@/utils/session"

View File

@@ -1,12 +1,16 @@
import * as api from "@/lib/api"
import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
import { createRefIdPlugin } from "@/server/plugins/refIdToConfirmationNumber"
import { createCounter } from "@/server/telemetry"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import {
badRequestError,
serverErrorByStatus,
} from "@scandic-hotels/trpc/errors"
import {
router,
safeProtectedServiceProcedure,
serviceProcedure,
} from "@/server/trpc"
} from "@scandic-hotels/trpc/procedures"
import * as api from "@/lib/api"
import { createRefIdPlugin } from "@/server/plugins/refIdToConfirmationNumber"
import { toApiLang } from "@/server/utils"
import { getBookedHotelRoom } from "@/utils/booking"

View File

@@ -1,11 +1,15 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import {
badRequestError,
serverErrorByStatus,
} from "@scandic-hotels/trpc/errors"
import * as api from "@/lib/api"
import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { toApiLang } from "@/server/utils"
import { bookingConfirmationSchema, createBookingSchema } from "./output"
import type { Lang } from "@/constants/languages"
import type { Lang } from "@scandic-hotels/common/constants/language"
export async function getBooking(
confirmationNumber: string,

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { accountPageQueryRouter } from "./query"

View File

@@ -1,11 +1,13 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { contentstackExtendedProcedureUID } from "@scandic-hotels/trpc/procedures"
import {
GetAccountPage,
GetAccountPageRefs,
} from "@/lib/graphql/Query/AccountPage/AccountPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import {
generateRefsResponseTag,

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { baseQueryRouter } from "./query"

View File

@@ -1,6 +1,7 @@
import { z, ZodError, ZodIssueCode } from "zod"
import { Lang } from "@/constants/languages"
import { Lang } from "@scandic-hotels/common/constants/language"
import { discriminatedUnion } from "@/lib/discriminatedUnion"
import {
cardBlockRefsSchema,

View File

@@ -1,5 +1,10 @@
import { cache } from "react"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { contentstackBaseProcedure } from "@scandic-hotels/trpc/procedures"
import { GetContactConfig } from "@/lib/graphql/Query/ContactConfig.graphql"
import {
GetCurrentFooter,
@@ -16,9 +21,6 @@ import {
GetSiteConfigRef,
} from "@/lib/graphql/Query/SiteConfig.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackBaseProcedure, router } from "@/server/trpc"
import { langInput } from "@/server/utils"
import {
@@ -51,6 +53,8 @@ import {
getSiteConfigConnections,
} from "./utils"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type {
FooterDataRaw,
FooterRefDataRaw,
@@ -63,7 +67,6 @@ import type {
GetSiteConfigData,
GetSiteConfigRefData,
} from "@/types/trpc/routers/contentstack/siteConfig"
import type { Lang } from "@/constants/languages"
const getContactConfig = cache(async (lang: Lang) => {
const getContactConfigCounter = createCounter(

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { breadcrumbsQueryRouter } from "./query"

View File

@@ -1,5 +1,10 @@
import { cache } from "react"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { contentstackExtendedProcedureUID } from "@scandic-hotels/trpc/procedures"
import {
GetMyPagesBreadcrumbs,
GetMyPagesBreadcrumbsRefs,
@@ -37,21 +42,19 @@ import {
GetLoyaltyPageBreadcrumbsRefs,
} from "@/lib/graphql/Query/Breadcrumbs/LoyaltyPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { generateRefsResponseTag } from "@/utils/generateTag"
import { breadcrumbsRefsSchema, breadcrumbsSchema } from "./output"
import { getTags } from "./utils"
import type { Lang } from "@scandic-hotels/common/constants/language"
import { PageContentTypeEnum } from "@/types/requests/contentType"
import type {
BreadcrumbsRefsSchema,
RawBreadcrumbsSchema,
} from "@/types/trpc/routers/contentstack/breadcrumbs"
import type { Lang } from "@/constants/languages"
interface BreadcrumbsPageData<T> {
dataKey: keyof T

View File

@@ -1,4 +1,4 @@
import { Lang } from "@/constants/languages"
import { Lang } from "@scandic-hotels/common/constants/language"
import { generateTag, generateTags } from "@/utils/generateTag"

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { campaignPageQueryRouter } from "./query"

View File

@@ -1,11 +1,13 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { contentStackUidWithServiceProcedure } from "@scandic-hotels/trpc/procedures"
import {
GetCampaignPage,
GetCampaignPageRefs,
} from "@/lib/graphql/Query/CampaignPage/CampaignPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentStackUidWithServiceProcedure, router } from "@/server/trpc"
import { generateRefsResponseTag } from "@/utils/generateTag"

View File

@@ -1,9 +1,10 @@
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
import type { Lang } from "@scandic-hotels/common/constants/language"
import { CampaignPageEnum } from "@/types/enums/campaignPage"
import type { System } from "@/types/requests/system"
import type { CampaignPageRefs } from "@/types/trpc/routers/contentstack/campaignPage"
import type { Lang } from "@/constants/languages"
export function generatePageTags(
validatedData: CampaignPageRefs,

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { collectionPageQueryRouter } from "./query"

View File

@@ -1,7 +1,9 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { contentstackExtendedProcedureUID } from "@scandic-hotels/trpc/procedures"
import { GetCollectionPage } from "@/lib/graphql/Query/CollectionPage/CollectionPage.graphql"
import { request } from "@/lib/graphql/request"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { collectionPageSchema } from "./output"
import {

View File

@@ -1,7 +1,8 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { notFound } from "@scandic-hotels/trpc/errors"
import { GetCollectionPageRefs } from "@/lib/graphql/Query/CollectionPage/CollectionPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import {
generateRefsResponseTag,
@@ -11,13 +12,14 @@ import {
import { collectionPageRefsSchema } from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import { CollectionPageEnum } from "@/types/enums/collectionPage"
import type { System } from "@/types/requests/system"
import type {
CollectionPageRefs,
GetCollectionPageRefsSchema,
} from "@/types/trpc/routers/contentstack/collectionPage"
import type { Lang } from "@/constants/languages"
export async function fetchCollectionPageRefs(lang: Lang, uid: string) {
const getCollectionPageRefsCounter = createCounter(

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { contentPageQueryRouter } from "./query"

View File

@@ -1,11 +1,13 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { contentstackExtendedProcedureUID } from "@scandic-hotels/trpc/procedures"
import { batchRequest } from "@/lib/graphql/batchRequest"
import {
GetContentPage,
GetContentPageBlocksBatch1,
GetContentPageBlocksBatch2,
} from "@/lib/graphql/Query/ContentPage/ContentPage.graphql"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { contentPageSchema } from "./output"
import {

View File

@@ -1,10 +1,11 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { notFound } from "@scandic-hotels/trpc/errors"
import { batchRequest } from "@/lib/graphql/batchRequest"
import {
GetContentPageBlocksRefs,
GetContentPageRefs,
} from "@/lib/graphql/Query/ContentPage/ContentPage.graphql"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import {
generateRefsResponseTag,
@@ -14,6 +15,8 @@ import {
import { contentPageRefsSchema } from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import { TrackingChannelEnum } from "@/types/components/tracking"
import { ContentPageEnum } from "@/types/enums/contentPage"
import type { System } from "@/types/requests/system"
@@ -21,7 +24,6 @@ import {
type ContentPageRefs,
type GetContentPageRefsSchema,
} from "@/types/trpc/routers/contentstack/contentPage"
import type { Lang } from "@/constants/languages"
export async function fetchContentPageRefs(lang: Lang, uid: string) {
const getContentPageRefsCounter = createCounter(

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { destinationCityPageQueryRouter } from "./query"

View File

@@ -1,11 +1,13 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { contentStackUidWithServiceProcedure } from "@scandic-hotels/trpc/procedures"
import {
GetDestinationCityPage,
GetDestinationCityPageRefs,
} from "@/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentStackUidWithServiceProcedure, router } from "@/server/trpc"
import { generateRefsResponseTag } from "@/utils/generateTag"

View File

@@ -1,12 +1,15 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { GetCityPageCount } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityPageCount.graphql"
import { GetCityPageUrls } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityPageUrl.graphql"
import { request } from "@/lib/graphql/request"
import { createCounter } from "@/server/telemetry"
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
import { batchedCityPageUrlsSchema, cityPageCountSchema } from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import { DestinationCityPageEnum } from "@/types/enums/destinationCityPage"
import type { System } from "@/types/requests/system"
import type {
@@ -14,7 +17,6 @@ import type {
GetCityPageCountData,
GetCityPageUrlsData,
} from "@/types/trpc/routers/contentstack/destinationCityPage"
import type { Lang } from "@/constants/languages"
export function generatePageTags(
validatedData: DestinationCityPageRefs,

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { destinationCountryPageQueryRouter } from "./query"

View File

@@ -1,15 +1,16 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import {
contentStackBaseWithServiceProcedure,
contentstackExtendedProcedureUID,
} from "@scandic-hotels/trpc/procedures"
import {
GetDestinationCountryPage,
GetDestinationCountryPageRefs,
} from "@/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import {
contentStackBaseWithServiceProcedure,
contentstackExtendedProcedureUID,
router,
} from "@/server/trpc"
import { generateRefsResponseTag } from "@/utils/generateTag"

View File

@@ -1,7 +1,8 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { GetDestinationCityListData } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityListData.graphql"
import { GetCountryPageUrls } from "@/lib/graphql/Query/DestinationCountryPage/DestinationCountryPageUrl.graphql"
import { request } from "@/lib/graphql/request"
import { createCounter } from "@/server/telemetry"
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
@@ -9,6 +10,8 @@ import { getCitiesByCountry } from "../../hotels/utils"
import { destinationCityListDataSchema } from "../destinationCityPage/output"
import { countryPageUrlsSchema } from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import { ApiCountry, type Country } from "@/types/enums/country"
import { DestinationCountryPageEnum } from "@/types/enums/destinationCountryPage"
import type { System } from "@/types/requests/system"
@@ -17,7 +20,6 @@ import type {
DestinationCountryPageRefs,
GetCountryPageUrlsData,
} from "@/types/trpc/routers/contentstack/destinationCountryPage"
import type { Lang } from "@/constants/languages"
export function generatePageTags(
validatedData: DestinationCountryPageRefs,

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { destinationOverviewPageQueryRouter } from "./query"

View File

@@ -1,18 +1,18 @@
import { Lang } from "@scandic-hotels/common/constants/language"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import {
contentstackExtendedProcedureUID,
serviceProcedure,
} from "@scandic-hotels/trpc/procedures"
import { Lang } from "@/constants/languages"
import {
GetDestinationOverviewPage,
GetDestinationOverviewPageRefs,
} from "@/lib/graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import {
contentstackExtendedProcedureUID,
router,
serviceProcedure,
} from "@/server/trpc"
import { generateRefsResponseTag, generateTag } from "@/utils/generateTag"

View File

@@ -1,4 +1,4 @@
import { Lang } from "@/constants/languages"
import { Lang } from "@scandic-hotels/common/constants/language"
import type { DestinationsData } from "@/types/components/destinationOverviewPage/destinationsList/destinationsData"
import { ApiCountry, Country } from "@/types/enums/country"

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { hotelPageQueryRouter } from "./query"

View File

@@ -1,8 +1,10 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { contentstackExtendedProcedureUID } from "@scandic-hotels/trpc/procedures"
import { GetHotelPage } from "@/lib/graphql/Query/HotelPage/HotelPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag"

View File

@@ -1,15 +1,17 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { GetHotelPageCount } from "@/lib/graphql/Query/HotelPage/HotelPageCount.graphql"
import { GetHotelPageUrls } from "@/lib/graphql/Query/HotelPage/HotelPageUrl.graphql"
import { request } from "@/lib/graphql/request"
import { createCounter } from "@/server/telemetry"
import { batchedHotelPageUrlsSchema, hotelPageCountSchema } from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type {
GetHotelPageCountData,
GetHotelPageUrlsData,
} from "@/types/trpc/routers/contentstack/hotelPage"
import type { Lang } from "@/constants/languages"
export async function getHotelPageCount(lang: Lang) {
const getHotelPageCountCounter = createCounter(

View File

@@ -1,4 +1,4 @@
import { router } from "@/server/trpc"
import { router } from "@scandic-hotels/trpc"
import { accountPageRouter } from "./accountPage"
import { baseRouter } from "./base"

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { languageSwitcherQueryRouter } from "./query"

View File

@@ -1,6 +1,6 @@
import { z } from "zod"
import { Lang } from "@/constants/languages"
import { Lang } from "@scandic-hotels/common/constants/language"
export const getLanguageSwitcherInput = z.object({
lang: z.nativeEnum(Lang),

View File

@@ -1,4 +1,5 @@
import { publicProcedure, router } from "@/server/trpc"
import { router } from "@scandic-hotels/trpc"
import { publicProcedure } from "@scandic-hotels/trpc/procedures"
import { getUidAndContentTypeByPath } from "@/services/cms/getUidAndContentTypeByPath"

View File

@@ -1,4 +1,7 @@
import { Lang } from "@/constants/languages"
import { Lang } from "@scandic-hotels/common/constants/language"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { internalServerError } from "@scandic-hotels/trpc/errors"
import { batchRequest } from "@/lib/graphql/batchRequest"
import {
GetDaDeEnUrlsAccountPage,
@@ -44,8 +47,6 @@ import {
GetDaDeEnUrlsStartPage,
GetFiNoSvUrlsStartPage,
} from "@/lib/graphql/Query/StartPage/StartPage.graphql"
import { internalServerError } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { generateTag } from "@/utils/generateTag"
import { removeTrailingSlash } from "@/utils/url"

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { loyaltyLevelQueryRouter } from "./query"

View File

@@ -1,6 +1,7 @@
import { z } from "zod"
import { Lang } from "@/constants/languages"
import { Lang } from "@scandic-hotels/common/constants/language"
import { MembershipLevelEnum } from "@/constants/membershipLevels"
export const loyaltyLevelInput = z.object({

View File

@@ -1,5 +1,10 @@
import { cache } from "react"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { contentstackBaseProcedure } from "@scandic-hotels/trpc/procedures"
import {
type MembershipLevel,
MembershipLevelEnum,
@@ -9,9 +14,6 @@ import {
GetLoyaltyLevel,
} from "@/lib/graphql/Query/LoyaltyLevels.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackBaseProcedure, router } from "@/server/trpc"
import { generateLoyaltyConfigTag } from "@/utils/generateTag"
@@ -22,9 +24,9 @@ import {
validateLoyaltyLevelsSchema,
} from "./output"
import type { Context } from "@/server/context"
import type { Lang } from "@scandic-hotels/common/constants/language"
export const getAllLoyaltyLevels = cache(async (ctx: Context) => {
export const getAllLoyaltyLevels = cache(async (lang: Lang) => {
const getLoyaltyLevelAllCounter = createCounter(
"trpc.contentstack",
"loyaltyLevel.all"
@@ -39,12 +41,12 @@ export const getAllLoyaltyLevels = cache(async (ctx: Context) => {
const allLevelIds = Object.values(MembershipLevelEnum)
const tags = allLevelIds.map((levelId) =>
generateLoyaltyConfigTag(ctx.lang, "loyalty_level", levelId)
generateLoyaltyConfigTag(lang, "loyalty_level", levelId)
)
const loyaltyLevelsConfigResponse = await request<LoyaltyLevelsResponse>(
GetAllLoyaltyLevels,
{ lang: ctx.lang, level_ids: allLevelIds },
{ lang, level_ids: allLevelIds },
{ key: tags, ttl: "max" }
)
@@ -68,13 +70,13 @@ export const getAllLoyaltyLevels = cache(async (ctx: Context) => {
})
export const getLoyaltyLevel = cache(
async (ctx: Context, level_id: MembershipLevel) => {
async (lang: Lang, level_id: MembershipLevel) => {
const getLoyaltyLevelCounter = createCounter(
"trpc.contentstack",
"loyaltyLevel.get"
)
const metricsGetLoyaltyLevel = getLoyaltyLevelCounter.init({
lang: ctx.lang,
lang,
level_id,
})
@@ -82,9 +84,9 @@ export const getLoyaltyLevel = cache(
const loyaltyLevelsConfigResponse = await request<LoyaltyLevelsResponse>(
GetLoyaltyLevel,
{ lang: ctx.lang, level_id },
{ lang, level_id },
{
key: generateLoyaltyConfigTag(ctx.lang, "loyalty_level", level_id),
key: generateLoyaltyConfigTag(lang, "loyalty_level", level_id),
ttl: "max",
}
)
@@ -116,9 +118,9 @@ export const loyaltyLevelQueryRouter = router({
byLevel: contentstackBaseProcedure
.input(loyaltyLevelInput)
.query(async function ({ ctx, input }) {
return getLoyaltyLevel(ctx, input.level)
return getLoyaltyLevel(ctx.lang, input.level)
}),
all: contentstackBaseProcedure.query(async function ({ ctx }) {
return getAllLoyaltyLevels(ctx)
return getAllLoyaltyLevels(ctx.lang)
}),
})

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { loyaltyPageQueryRouter } from "./query"

View File

@@ -1,11 +1,13 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { contentstackExtendedProcedureUID } from "@scandic-hotels/trpc/procedures"
import {
GetLoyaltyPage,
GetLoyaltyPageRefs,
} from "@/lib/graphql/Query/LoyaltyPage/LoyaltyPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import {
generateRefsResponseTag,

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { metadataQueryRouter } from "./query"

View File

@@ -15,13 +15,13 @@ import { getDescription } from "./utils/description"
import { getImage } from "./utils/image"
import { getTitle } from "./utils/title"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { Metadata } from "next"
import type { ImageVaultAsset } from "@/types/components/imageVault"
import { Country } from "@/types/enums/country"
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
import { RTETypeEnum } from "@/types/rte/enums"
import type { Lang } from "@/constants/languages"
const metaDataJsonSchema = z.object({
children: z.array(

View File

@@ -1,5 +1,10 @@
import { cache } from "react"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { contentStackUidWithServiceProcedure } from "@scandic-hotels/trpc/procedures"
import { env } from "@/env/server"
import { GetAccountPageMetadata } from "@/lib/graphql/Query/AccountPage/Metadata.graphql"
import { GetCampaignPageMetadata } from "@/lib/graphql/Query/CampaignPage/Metadata.graphql"
@@ -12,9 +17,6 @@ import { GetHotelPageMetadata } from "@/lib/graphql/Query/HotelPage/Metadata.gra
import { GetLoyaltyPageMetadata } from "@/lib/graphql/Query/LoyaltyPage/Metadata.graphql"
import { GetStartPageMetadata } from "@/lib/graphql/Query/StartPage/Metadata.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentStackUidWithServiceProcedure, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag"
@@ -24,12 +26,12 @@ import { getMetadataInput } from "./input"
import { getNonContentstackUrls, metadataSchema } from "./output"
import { affix, getCityData, getCountryData } from "./utils"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { Metadata } from "next"
import { PageContentTypeEnum } from "@/types/requests/contentType"
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
import type { RawMetadataSchema } from "@/types/trpc/routers/contentstack/metadata"
import type { Lang } from "@/constants/languages"
const fetchMetadata = cache(async function fetchMemoizedMetadata<T>(
query: string,

View File

@@ -1,4 +1,3 @@
import { type Lang } from "@/constants/languages"
import {
getFiltersFromHotels,
getSortedCities,
@@ -12,6 +11,8 @@ import {
} from "../../../hotels/utils"
import { getCityPages } from "../../destinationCountryPage/utils"
import type { Lang } from "@scandic-hotels/common/constants/language"
import { ApiCountry } from "@/types/enums/country"
import { SortOption } from "@/types/enums/destinationFilterAndSort"
import type {

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { pageSettingsQueryRouter } from "./query"

View File

@@ -1,5 +1,8 @@
import * as Sentry from "@sentry/nextjs"
import { router } from "@scandic-hotels/trpc"
import { contentstackBaseProcedure } from "@scandic-hotels/trpc/procedures"
import {
GetAccountPageSettings,
GetCampaignPageSettings,
@@ -14,7 +17,6 @@ import {
GetStartPageSettings,
} from "@/lib/graphql/Query/PageSettings.graphql"
import { request } from "@/lib/graphql/request"
import { contentstackBaseProcedure, router } from "@/server/trpc"
import { langInput } from "@/server/utils"
import { generateTag } from "@/utils/generateTag"

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { partnerQueryRouter } from "./query"

View File

@@ -1,32 +1,35 @@
import { cache } from "react"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { contentstackBaseProcedure } from "@scandic-hotels/trpc/procedures"
import { GetAllSasTierComparison } from "@/lib/graphql/Query/SASTierComparison.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackBaseProcedure, router } from "@/server/trpc"
import { validateSasTierComparisonSchema } from "./output"
import type { SasTierComparisonResponse } from "@/types/trpc/routers/contentstack/partner"
import type { Context } from "@/server/context"
import type { Lang } from "@scandic-hotels/common/constants/language"
export const getSasTierComparison = cache(async (ctx: Context) => {
import type { SasTierComparisonResponse } from "@/types/trpc/routers/contentstack/partner"
export const getSasTierComparison = cache(async (lang: Lang) => {
const getSasTierComparisonCounter = createCounter(
"trpc.contentstack",
"partner.getSasTierComparison"
)
const metricsGetSasTierComparison = getSasTierComparisonCounter.init({
lang: ctx.lang,
lang,
})
metricsGetSasTierComparison.start()
const tag = `${ctx.lang}:sas_tier_comparison`
const tag = `${lang}:sas_tier_comparison`
const sasTierComparisonConfigResponse =
await request<SasTierComparisonResponse>(
GetAllSasTierComparison,
{ lang: ctx.lang },
{ lang },
{
key: tag,
ttl: "max",
@@ -59,6 +62,6 @@ export const partnerQueryRouter = router({
getSasTierComparison: contentstackBaseProcedure.query(async function ({
ctx,
}) {
return getSasTierComparison(ctx)
return getSasTierComparison(ctx.lang)
}),
})

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { rewardQueryRouter } from "./query"

View File

@@ -1,12 +1,13 @@
import * as api from "@/lib/api"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import {
contentStackBaseWithProtectedProcedure,
contentStackBaseWithServiceProcedure,
protectedProcedure,
router,
} from "@/server/trpc"
} from "@scandic-hotels/trpc/procedures"
import * as api from "@/lib/api"
import { langInput } from "@/server/utils"
import {
@@ -64,7 +65,7 @@ export const rewardQueryRouter = router({
return []
}
const loyaltyLevelsConfig = await getAllLoyaltyLevels(ctx)
const loyaltyLevelsConfig = await getAllLoyaltyLevels(ctx.lang)
const levelsWithRewards = Object.entries(allApiRewards).map(
([level, rewards]) => {
const combinedRewards = rewards
@@ -150,7 +151,7 @@ export const rewardQueryRouter = router({
const [contentStackRewards, loyaltyLevelsConfig] = await Promise.all([
getCmsRewards(ctx.lang, rewardIds),
getLoyaltyLevel(ctx, input.level_id),
getLoyaltyLevel(ctx.lang, input.level_id),
])
if (!contentStackRewards) {

View File

@@ -1,4 +1,6 @@
import { getCacheClient } from "@scandic-hotels/common/dataCache"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { notFound } from "@scandic-hotels/trpc/errors"
import * as api from "@/lib/api"
import {
@@ -6,8 +8,6 @@ import {
GetRewardsRef as GetRewardsRef,
} from "@/lib/graphql/Query/RewardsWithRedeem.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import {
generateLoyaltyConfigTag,
@@ -20,11 +20,12 @@ import {
validateCmsRewardsSchema,
} from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type {
CMSRewardsResponse,
GetRewardRefsSchema,
} from "@/types/trpc/routers/contentstack/reward"
import type { Lang } from "@/constants/languages"
export function getUniqueRewardIds(rewardIds: string[]) {
const uniqueRewardIds = new Set(rewardIds)

View File

@@ -1,6 +1,6 @@
import { z } from "zod"
import { Lang } from "@/constants/languages"
import { Lang } from "@scandic-hotels/common/constants/language"
export const systemSchema = z.object({
content_type_uid: z.string(),

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { startPageQueryRouter } from "./query"

View File

@@ -1,11 +1,13 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { notFound } from "@scandic-hotels/trpc/errors"
import { contentstackExtendedProcedureUID } from "@scandic-hotels/trpc/procedures"
import {
GetStartPage,
GetStartPageRefs,
} from "@/lib/graphql/Query/StartPage/StartPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import {
generateRefsResponseTag,

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { hotelQueryRouter } from "./query"

View File

@@ -1,7 +1,7 @@
import dayjs from "dayjs"
import { z } from "zod"
import { Lang } from "@/constants/languages"
import { Lang } from "@scandic-hotels/common/constants/language"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"

View File

@@ -1,21 +1,21 @@
import { Lang } from "@scandic-hotels/common/constants/language"
import { getCacheClient } from "@scandic-hotels/common/dataCache"
import { REDEMPTION } from "@/constants/booking"
import { Lang } from "@/constants/languages"
import { env } from "@/env/server"
import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { badRequestError, unauthorizedError } from "@/server/errors/trpc"
import { getCityPageUrls } from "@/server/routers/contentstack/destinationCityPage/utils"
import { getVerifiedUser } from "@/server/routers/user/utils"
import { createCounter } from "@/server/telemetry"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { badRequestError, unauthorizedError } from "@scandic-hotels/trpc/errors"
import {
contentStackBaseWithServiceProcedure,
publicProcedure,
router,
safeProtectedServiceProcedure,
serviceProcedure,
} from "@/server/trpc"
} from "@scandic-hotels/trpc/procedures"
import { REDEMPTION } from "@/constants/booking"
import { env } from "@/env/server"
import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { getCityPageUrls } from "@/server/routers/contentstack/destinationCityPage/utils"
import { getVerifiedUser } from "@/server/routers/user/utils"
import { toApiLang } from "@/server/utils"
import { getHotelPageUrls } from "../contentstack/hotelPage/utils"

View File

@@ -1,15 +1,15 @@
import deepmerge from "deepmerge"
import stringify from "json-stable-stringify-without-jsonify"
import { Lang } from "@scandic-hotels/common/constants/language"
import { getCacheClient } from "@scandic-hotels/common/dataCache"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { badRequestError } from "@scandic-hotels/trpc/errors"
import { BookingErrorCodeEnum, REDEMPTION } from "@/constants/booking"
import { Lang } from "@/constants/languages"
import { selectRate } from "@/constants/routes/hotelReservation"
import { env } from "@/env/server"
import * as api from "@/lib/api"
import { badRequestError } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { toApiLang } from "@/server/utils"
import { generateChildrenString } from "@/components/HotelReservation/utils"

View File

@@ -1,4 +1,4 @@
import { router } from "@/server/trpc"
import { router } from "@scandic-hotels/trpc"
import { myPagesNavigation } from "./mypages/index"

View File

@@ -8,8 +8,9 @@ import { env } from "@/env/server"
import { getIntl } from "@/i18n"
import { getEurobonusMembership } from "@/utils/user"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { UserLoyalty } from "@/types/user"
import type { Lang } from "@/constants/languages"
import type { MyPagesLink } from "./MyPagesLink"
export const getPrimaryLinks = cache(

View File

@@ -2,7 +2,8 @@ import * as routes from "@/constants/routes/myPages"
import { getIntl } from "@/i18n"
import type { Lang } from "@/constants/languages"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { MyPagesLink } from "./MyPagesLink"
export async function getSecondaryLinks({

View File

@@ -1,9 +1,10 @@
import { TRPCError } from "@trpc/server"
import { z } from "zod"
import { Lang } from "@/constants/languages"
import { Lang } from "@scandic-hotels/common/constants/language"
import { safeProtectedProcedure } from "@scandic-hotels/trpc/procedures"
import { getVerifiedUser } from "@/server/routers/user/utils"
import { safeProtectedProcedure } from "@/server/trpc"
import { isValidSession } from "@/utils/session"

View File

@@ -1,4 +1,4 @@
import { router } from "@/server/trpc"
import { router } from "@scandic-hotels/trpc"
import { jobylonQueryRouter } from "./jobylon/query"
import { sasRouter } from "./sas"

View File

@@ -1,7 +1,7 @@
import { getCacheClient } from "@scandic-hotels/common/dataCache"
import { createCounter } from "@/server/telemetry"
import { publicProcedure, router } from "@/server/trpc"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { publicProcedure } from "@scandic-hotels/trpc/procedures"
import { jobylonFeedSchema } from "./output"

View File

@@ -1,4 +1,4 @@
import { router } from "@/server/trpc"
import { router } from "@scandic-hotels/trpc"
import { requestOtp } from "./otp/request/requestOtp"
import { verifyOtp } from "./otp/verify/verifyOtp"

View File

@@ -1,8 +1,9 @@
import * as Sentry from "@sentry/nextjs"
import { z } from "zod"
import { protectedProcedure } from "@scandic-hotels/trpc/procedures"
import * as api from "@/lib/api"
import { protectedProcedure } from "@/server/trpc"
import { getOTPState } from "./otp/getOTPState"
import { getSasToken } from "./getSasToken"

View File

@@ -4,8 +4,9 @@ import { cookies } from "next/headers"
import { v4 as uuidv4 } from "uuid"
import { z } from "zod"
import { protectedProcedure } from "@scandic-hotels/trpc/procedures"
import { env } from "@/env/server"
import { protectedProcedure } from "@/server/trpc"
import { getSasToken } from "../../getSasToken"
import { SAS_REQUEST_OTP_STATE_STORAGE_COOKIE_NAME } from "../constants"

View File

@@ -1,8 +1,9 @@
import { TRPCError } from "@trpc/server"
import { z } from "zod"
import { protectedProcedure } from "@scandic-hotels/trpc/procedures"
import { env } from "@/env/server"
import { protectedProcedure } from "@/server/trpc"
import { getSasToken } from "../../getSasToken"
import { getOTPState } from "../getOTPState"

View File

@@ -2,9 +2,10 @@ import * as Sentry from "@sentry/nextjs"
import { cookies } from "next/headers"
import { z } from "zod"
import { protectedProcedure } from "@scandic-hotels/trpc/procedures"
import { FriendsMembershipLevels } from "@/constants/membershipLevels"
import * as api from "@/lib/api"
import { protectedProcedure } from "@/server/trpc"
import { getUserSchema } from "../../user/output"
import { getVerifiedUser } from "../../user/utils"

View File

@@ -1,10 +1,12 @@
import * as Sentry from "@sentry/nextjs"
import { z } from "zod"
import { protectedProcedure } from "@scandic-hotels/trpc/procedures"
import * as api from "@/lib/api"
import { protectedProcedure } from "@/server/trpc"
import { getOTPState } from "./otp/getOTPState"
// import * as api from "@/lib/api"
import { getSasToken } from "./getSasToken"
const outputSchema = z.object({

View File

@@ -1,7 +1,8 @@
import { z } from "zod"
import { protectedProcedure } from "@scandic-hotels/trpc/procedures"
import * as api from "@/lib/api"
import { protectedProcedure } from "@/server/trpc"
import { getOTPState } from "./otp/getOTPState"
import { getSasToken } from "./getSasToken"

View File

@@ -1,4 +1,4 @@
import { mergeRouters } from "@/server/trpc"
import { mergeRouters } from "@scandic-hotels/trpc"
import { userMutationRouter } from "./mutation"
import { userQueryRouter } from "./query"

View File

@@ -1,6 +1,6 @@
import { z } from "zod"
import { Lang } from "@/constants/languages"
import { Lang } from "@scandic-hotels/common/constants/language"
import { signUpSchema } from "@/components/Forms/Signup/schema"

View File

@@ -1,13 +1,18 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import { serverErrorByStatus } from "@scandic-hotels/trpc/errors"
import {
protectedProcedure,
serviceProcedure,
} from "@scandic-hotels/trpc/procedures"
import { signupVerify } from "@/constants/routes/signup"
import { env } from "@/env/server"
import * as api from "@/lib/api"
import { serverErrorByStatus } from "@/server/errors/trpc"
import {
initiateSaveCardSchema,
subscriberIdSchema,
} from "@/server/routers/user/output"
import { createCounter } from "@/server/telemetry"
import { protectedProcedure, router, serviceProcedure } from "@/server/trpc"
import {
addCreditCardInput,

View File

@@ -1,11 +1,12 @@
import * as api from "@/lib/api"
import { createCounter } from "@/server/telemetry"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { router } from "@scandic-hotels/trpc"
import {
languageProtectedProcedure,
protectedProcedure,
router,
safeProtectedProcedure,
} from "@/server/trpc"
} from "@scandic-hotels/trpc/procedures"
import * as api from "@/lib/api"
import { toApiLang } from "@/server/utils"
import { isValidSession } from "@/utils/session"

View File

@@ -1,10 +1,11 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
import { countries } from "@/constants/countries"
import { myBookingPath } from "@/constants/myBooking"
import { myStay } from "@/constants/routes/myStay"
import { env } from "@/env/server"
import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { createCounter } from "@/server/telemetry"
import { toApiLang } from "@/server/utils"
import { cache } from "@/utils/cache"
@@ -22,10 +23,10 @@ import {
type Stay,
} from "./output"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { Session } from "next-auth"
import type { User } from "@/types/user"
import type { Lang } from "@/constants/languages"
export const getVerifiedUser = cache(
async ({

View File

@@ -1,135 +0,0 @@
import { describe, expect, test } from "@jest/globals"
import { sanitize } from "./"
describe("sanitize", () => {
test("should handle valid primitive attributes", () => {
const input = {
key1: "value1",
key2: 10,
key3: true,
}
const expected = {
key1: "value1",
key2: 10,
key3: true,
}
expect(sanitize(input)).toEqual(expected)
})
test("should handle valid array attributes", () => {
const input = {
key1: ["value1", "value2"],
key2: [1, 2, 3],
key3: [true, false, true],
key4: [null, undefined, "a", 1, true],
}
const expected = {
"key1.0": "value1",
"key1.1": "value2",
"key2.0": 1,
"key2.1": 2,
"key2.2": 3,
"key3.0": true,
"key3.1": false,
"key3.2": true,
"key4.0": null,
"key4.1": undefined,
"key4.2": "a",
"key4.3": 1,
"key4.4": true,
}
expect(sanitize(input)).toEqual(expected)
})
test("should stringify non-valid attributes", () => {
const input = {
key1: new Date("2024-08-08T12:00:00Z"),
key2: { nested: "object" },
}
const expected = {
key1: '"2024-08-08T12:00:00.000Z"',
"key2.nested": "object",
}
expect(sanitize(input)).toEqual(expected)
})
test("should handle nested valid attributes", () => {
const input = {
key1: "Example",
key2: 10,
nested: {
nestedKey1: "Value",
nestedKey2: {
nestedKey2Key1: true,
},
},
}
const expected = {
key1: "Example",
key2: 10,
"nested.nestedKey1": "Value",
"nested.nestedKey2.nestedKey2Key1": true,
}
expect(sanitize(input)).toEqual(expected)
})
test("should handle a mix of valid and non-valid nested attributes", () => {
const input = {
key1: "Example",
key2: 10,
nested: {
nestedKey1: "Value",
nestedKey2: {
nestedKey2Key1: true,
nestedKey2Key2: new Date("2024-08-08T12:00:00Z"),
},
nestedKey3: {
reallyNested: "hello",
},
},
nonPrimitive: new Date("2024-08-08T13:00:00Z"),
}
const expected = {
key1: "Example",
key2: 10,
"nested.nestedKey1": "Value",
"nested.nestedKey2.nestedKey2Key1": true,
"nested.nestedKey2.nestedKey2Key2": '"2024-08-08T12:00:00.000Z"',
"nested.nestedKey3.reallyNested": "hello",
nonPrimitive: '"2024-08-08T13:00:00.000Z"',
}
expect(sanitize(input)).toEqual(expected)
})
test("should throw an error when a function is passed", () => {
const input = {
key1: () => {},
}
expect(() => sanitize(input)).toThrowError("Cannot sanitize function")
})
test("should throw an error when input not an object", () => {
// @ts-expect-error: array not allowed. We do this here to make sure the
// function not only relies on TS but actively blocks arrays as input.
expect(() => sanitize(null)).toThrowError()
// @ts-expect-error: array not allowed. We do this here to make sure the
// function not only relies on TS but actively blocks arrays as input.
expect(() => sanitize(undefined)).toThrowError()
// @ts-expect-error: array not allowed. We do this here to make sure the
// function not only relies on TS but actively blocks arrays as input.
expect(() => sanitize("")).toThrowError()
// @ts-expect-error: array not allowed. We do this here to make sure the
// function not only relies on TS but actively blocks arrays as input.
expect(() => sanitize([1, 2, 3])).toThrowError()
})
test("should handle empty input", () => {
const input = {}
const expected = {}
expect(sanitize(input)).toEqual(expected)
})
})

View File

@@ -1,309 +0,0 @@
// Central place for telemetry
// TODO: Replace all of this with proper tracers and events
import {
type Attributes,
type AttributeValue,
metrics,
} from "@opentelemetry/api"
import deepmerge from "deepmerge"
import { flatten } from "flat"
import {
every,
isArray,
isBoolean,
isFunction,
isNull,
isNumber,
isObject,
isPlainObject,
isString,
isUndefined,
keys,
mapValues,
} from "lodash-es"
import type { ZodError } from "zod"
type AttributesInput = Record<string, unknown>
function isAttributesInput(value: unknown): value is AttributesInput {
return (
isObject(value) &&
!isArray(value) &&
!isNull(value) &&
keys(value).length > 0 &&
every(keys(value), isString)
)
}
/**
* Checks if a given value is a valid OpenTelemetry `AttributeValue`.
* An `AttributeValue` can be a `string`, `number`, `boolean`, or a homogenous
* array containing only `null`, `undefined`, `string`, `number`, or `boolean`.
*
* @param value The value to check.
* @returns `true` if the value is a valid `AttributeValue`, `false` otherwise.
*/
export function isValidAttributeValue(value: unknown): value is AttributeValue {
if (isString(value) || isNumber(value) || isBoolean(value)) {
return true
}
if (isArray(value)) {
return every(
value,
(item) =>
isNull(item) ||
isUndefined(item) ||
isString(item) ||
isNumber(item) ||
isBoolean(item)
)
}
return false
}
/**
* Sanitizes an input object, ensuring its values are valid OpenTelemetry
* `AttributeValue` or `JSON.stringify()` representations as a fallback.
* It recursively processes nested objects and flattens the final object to one
* level deep with dot delimited keys for nested values.
*
* @param data The input object to sanitize.
* @returns The resulting object.
*
* @example
* ```typescript
* import { sanitize } from '@/server/telemetry';
*
* const input = {
* key1: "Example",
* key2: 10,
* nested: {
* nestedKey1: "Value",
* nestedKey2: {
* nestedKey2Key1: true,
* },
* },
* };
*
* const sanitized = sanitize(input);
* console.log(sanitized);
* // {
* // key1: "Example",
* // key2: 10,
* // "nested.nestedKey1": "Value",
* // "nested.nestedKey2.nestedKey2Key1": true,
* // }
* ```
*/
export function sanitize(data: AttributesInput): Attributes {
if (!isPlainObject(data)) {
throw new Error(`Input must be an object, got ${JSON.stringify(data)}`)
}
return flatten(
mapValues(data, (value) => {
if (isFunction(value)) {
throw new Error("Cannot sanitize function")
} else if (isValidAttributeValue(value)) {
return value
} else if (isAttributesInput(value)) {
return sanitize(value)
}
return JSON.stringify(value)
})
)
}
/**
* Creates an object that holds three OpenTelemetry counter instruments. One
* that represents the counter itself, one for the success and one for any fail.
* The object contains an `init method that acts as a factory to create the a
* final object that holds methods to record different types of events one the
* appropriate counter.
*
* @param meterName The name of the OpenTelemetry meter to create.
* @param counterName The name of the counter instrument to create.
* @returns An object with an `init` method that returns an object
* with methods for recording counter events.
*
* @example
*
* See the codebase for reference usage.
*/
export function createCounter(meterName: string, counterName: string) {
const meter = metrics.getMeter(meterName)
const fullName = `${meterName}.${counterName}`
const counter = meter.createCounter(fullName)
const success = meter.createCounter(`${fullName}-success`)
const fail = meter.createCounter(`${fullName}-fail`)
return {
/**
* Initializes the counter event handlers with a set of base attributes.
* These attributes will be included in all recorded events.
*
* @param baseAttrs - The base attributes to associate with the counter. Defaults to an empty object.
* @returns An object with methods to record specific counter events.
*/
init(baseAttrs: AttributesInput = {}) {
return {
/**
* Records an event for the main counter.
*
* @param attrs - Additional attributes specific to this 'start' event. Defaults to an empty object.
*/
start(attrs: AttributesInput = {}) {
const mergedAttrs = deepmerge.all<AttributesInput>([baseAttrs, attrs])
const finalAttrs = sanitize(mergedAttrs)
counter.add(1, finalAttrs)
console.info(`[${fullName}] start:`, finalAttrs)
},
/**
* Records an event for the success counter.
*
* @param attrs - Additional attributes specific to this 'success' event. Defaults to an empty object.
*/
success(attrs: AttributesInput = {}) {
const mergedAttrs = deepmerge.all<AttributesInput>([baseAttrs, attrs])
const finalAttrs = sanitize(mergedAttrs)
success.add(1, finalAttrs)
console.info(`[${fullName}] success:`, finalAttrs)
},
/**
* Records an event of type `data_error` for the fail counter.
* Used when some dependent data could not be resolved during the
* operation. Note that "no data" also exists and might be more
* appropriate in certain situations.
*
* @param errorMsg - A message describing the data error.
* @param attrs - Additional attributes specific to this 'dataError' event. Defaults to an empty object.
*/
dataError(errorMsg: string, attrs: AttributesInput = {}) {
const mergedAttrs = deepmerge.all<AttributesInput>([
baseAttrs,
attrs,
{
error_type: "data_error",
error: errorMsg,
},
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
console.error(`[${fullName}] dataError:`, finalAttrs)
},
/**
* Records an event of type `not_found` for the fail counter.
* Used when some dependent data could not be found during the operation.
* Note that when there is an error resolving the data, the
* `dataError` method might be more appropriate.
*
* @param attrs - Additional attributes specific to this 'noDataError' event. Defaults to an empty object.
*/
noDataError(attrs: AttributesInput = {}) {
const mergedAttrs = deepmerge.all<AttributesInput>([
baseAttrs,
attrs,
{
error_type: "not_found",
},
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
console.error(`[${fullName}] noDataError:`, finalAttrs)
},
/**
* Records an event of type `validation_error` for the fail counter.
* Used when a Zod schema fails validation.
*
* @param zodError - The {@link ZodError} object representing the validation error.
*/
validationError(zodError: ZodError) {
const mergedAttrs = deepmerge.all<AttributesInput>([
baseAttrs,
{
error_type: "validation_error",
error: zodError.format(),
},
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
console.error(`[${fullName}] validationError:`, finalAttrs)
},
/**
* Records an event of type `http_error` for the fail counter.
* Used when a `fetch(...)` call fails. **Note**: This method must be
* `await`ed as it is asynchronous!
* The given {@link Response} must be unprocessed and will be cloned
* to avoid interfering with its consumption outside this function.
*
* @param response - The HTTP {@link Response} object.
*/
async httpError(response: Response) {
const res = response.clone()
const text = await res.text()
const mergedAttrs = deepmerge.all<AttributesInput>([
baseAttrs,
{
error_type: "http_error",
error: {
status: res.status,
statusText: res.statusText,
text,
},
},
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
console.error(`[${fullName}] httpError:`, finalAttrs)
},
/**
* Records an event of type `error` for the fail counter.
* Used when an error is thrown or an exception is caught, or as a
* general-purpose way to record an 'error' on the fail counter.
*
* @param err - An optional error object or message associated with the
* failure. Usually an instance of {@link Error} or a string.
*/
fail(err?: unknown) {
let msg = "unknown"
if (err && err instanceof Error) {
msg = err.message
} else if (typeof err === "string") {
msg = err
}
const mergedAttrs = deepmerge.all<AttributesInput>([
baseAttrs,
{
error_type: "error",
error: msg,
},
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
console.error(`[${fullName}] fail:`, finalAttrs)
},
}
},
}
}

View File

@@ -1,119 +0,0 @@
import { trace, type Tracer } from "@opentelemetry/api"
import { getCacheClient } from "@scandic-hotels/common/dataCache"
import { env } from "@/env/server"
import { createCounter } from "@/server/telemetry"
import type { ServiceTokenResponse } from "@/types/tokens"
export async function getServiceToken() {
const tracer = trace.getTracer("getServiceToken")
return await tracer.startActiveSpan("getServiceToken", async () => {
const scopes = ["profile", "hotel", "booking", "package", "availability"]
const cacheKey = getServiceTokenCacheKey(scopes)
const cacheClient = await getCacheClient()
const token = await getOrSetServiceTokenFromCache(cacheKey, scopes, tracer)
if (token.expiresAt < Date.now()) {
await cacheClient.deleteKey(cacheKey)
const newToken = await getOrSetServiceTokenFromCache(
cacheKey,
scopes,
tracer
)
return newToken.jwt
}
return token.jwt
})
}
async function getOrSetServiceTokenFromCache(
cacheKey: string,
scopes: string[],
tracer: Tracer
) {
const cacheClient = await getCacheClient()
const token = await cacheClient.cacheOrGet(
cacheKey,
async () => {
return await tracer.startActiveSpan("fetch new token", async () => {
const newToken = await getJwt(scopes)
return newToken
})
},
"1h"
)
return token
}
async function getJwt(scopes: string[]) {
const getJwtCounter = createCounter("tokenManager", "getJwt")
const metricsGetJwt = getJwtCounter.init({
scopes,
})
metricsGetJwt.start()
const jwt = await fetchServiceToken(scopes)
const expiresAt = Date.now() + jwt.expires_in * 1000
metricsGetJwt.success()
return { expiresAt, jwt }
}
async function fetchServiceToken(scopes: string[]) {
const fetchServiceTokenCounter = createCounter(
"tokenManager",
"fetchServiceToken"
)
const metricsFetchServiceToken = fetchServiceTokenCounter.init({
scopes,
})
metricsFetchServiceToken.start()
const response = await fetch(`${env.CURITY_ISSUER_USER}/oauth/v2/token`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: env.CURITY_CLIENT_ID_SERVICE,
client_secret: env.CURITY_CLIENT_SECRET_SERVICE,
scope: scopes.join(" "),
}),
signal: AbortSignal.timeout(15_000),
})
if (!response.ok) {
await metricsFetchServiceToken.httpError(response)
const text = await response.text()
throw new Error(
`[fetchServiceToken] Failed to obtain service token: ${JSON.stringify({
status: response.status,
statusText: response.statusText,
text,
})}`
)
}
const result = response.json() as Promise<ServiceTokenResponse>
metricsFetchServiceToken.success()
return result
}
function getServiceTokenCacheKey(scopes: string[]): string {
return `serviceToken:${scopes.join(",")}`
}

View File

@@ -1,3 +0,0 @@
import superjson from "superjson"
export const transformer = superjson

View File

@@ -1,211 +1,19 @@
import * as Sentry from "@sentry/nextjs"
import { initTRPC } from "@trpc/server"
import { experimental_nextAppDirCaller } from "@trpc/server/adapters/next-app-dir"
import { ZodError } from "zod"
import { env } from "@/env/server"
import {
badRequestError,
internalServerError,
sessionExpiredError,
unauthorizedError,
} from "./errors/trpc"
import { type Context, createContext } from "./context"
import { getServiceToken } from "./tokenManager"
import { transformer } from "./transformer"
import { langInput } from "./utils"
baseProcedure,
getProtectedServerActionProcedure,
} from "@scandic-hotels/trpc/procedures"
import type { Session } from "next-auth"
import { createAppContext } from "@/lib/trpc/server"
import type { Meta } from "@/types/trpc/meta"
const t = initTRPC
.context<Context>()
.meta<Meta>()
.create({
transformer,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
cause:
error.cause instanceof ZodError
? undefined
: JSON.parse(JSON.stringify(error.cause)),
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
}
},
})
const sentryMiddleware = t.middleware(
Sentry.trpcMiddleware({
attachRpcInput: true,
})
)
export const { createCallerFactory, mergeRouters, router } = t
const baseProcedure = t.procedure.use(sentryMiddleware)
export const publicProcedure = baseProcedure
export const languageProcedure = baseProcedure.use(async function (opts) {
if (!opts.ctx.lang) {
// When fetching data client side with TRPC we don't pass through middlewares and therefore do not get the lang through headers
// We can then pass lang as an input in the request and set it to the context in the procedure
const input = await opts.getRawInput()
const parsedInput = langInput.safeParse(input)
if (!parsedInput.success) {
throw badRequestError("Missing Lang in tRPC context")
}
return opts.next({
ctx: {
lang: parsedInput.data.lang,
},
})
}
return opts.next({
ctx: {
lang: opts.ctx.lang,
},
})
})
export const contentstackBaseProcedure = languageProcedure
export const contentstackExtendedProcedureUID = contentstackBaseProcedure.use(
async function (opts) {
if (!opts.ctx.uid) {
throw badRequestError("Missing UID in tRPC context")
}
return opts.next({
ctx: {
uid: opts.ctx.uid,
},
})
}
)
export const protectedProcedure = baseProcedure.use(async function (opts) {
const authRequired = opts.meta?.authRequired ?? true
const session = await opts.ctx.auth()
if (!authRequired && env.NODE_ENV === "development") {
console.info(
`❌❌❌❌ You are opting out of authorization, if its done on purpose maybe you should use the publicProcedure instead. ❌❌❌❌`
)
console.info(`path: ${opts.path} | type: ${opts.type}`)
}
if (!session) {
throw unauthorizedError()
}
if (session?.error === "RefreshAccessTokenError") {
throw sessionExpiredError()
}
return opts.next({
ctx: {
session,
},
})
})
export const safeProtectedProcedure = baseProcedure.use(async function (opts) {
const authRequired = opts.meta?.authRequired ?? true
let session: Session | null = await opts.ctx.auth()
if (!authRequired && env.NODE_ENV === "development") {
console.info(
`❌❌❌❌ You are opting out of authorization, if its done on purpose maybe you should use the publicProcedure instead. ❌❌❌❌`
)
console.info(`path: ${opts.path} | type: ${opts.type}`)
}
if (!session || session.error === "RefreshAccessTokenError") {
session = null
}
return opts.next({
ctx: {
session,
},
})
})
export const serviceProcedure = baseProcedure.use(async (opts) => {
const token = await getServiceToken()
const { access_token } = token
if (!access_token) {
throw internalServerError(`[serviceProcedure] No service token`)
}
return opts.next({
ctx: {
serviceToken: access_token,
},
})
})
export const serverActionProcedure = baseProcedure.experimental_caller(
const serverActionProcedure = baseProcedure.experimental_caller(
experimental_nextAppDirCaller({
createContext,
createContext: createAppContext,
normalizeFormData: true,
})
)
export const serviceServerActionProcedure = serverActionProcedure.use(
async (opts) => {
const { access_token } = await getServiceToken()
if (!access_token) {
throw internalServerError(
"[serviceServerActionProcedure]: No service token"
)
}
return opts.next({
ctx: {
serviceToken: access_token,
},
})
}
export const protectedServerActionProcedure = getProtectedServerActionProcedure(
serverActionProcedure
)
export const protectedServerActionProcedure = serverActionProcedure.use(
async (opts) => {
const session = await opts.ctx.auth()
if (!session) {
throw unauthorizedError()
}
if (session && session.error === "RefreshAccessTokenError") {
throw sessionExpiredError()
}
return opts.next({
ctx: {
...opts.ctx,
session,
},
})
}
)
export const contentStackUidWithServiceProcedure =
contentstackExtendedProcedureUID.concat(serviceProcedure)
export const contentStackBaseWithServiceProcedure =
contentstackBaseProcedure.concat(serviceProcedure)
export const contentStackBaseWithProtectedProcedure =
contentstackBaseProcedure.concat(protectedProcedure)
export const safeProtectedServiceProcedure =
safeProtectedProcedure.concat(serviceProcedure)
export const languageProtectedProcedure =
protectedProcedure.concat(languageProcedure)

View File

@@ -1,7 +1,8 @@
import { NextRequest } from "next/server"
import { z } from "zod"
import { Lang } from "@/constants/languages"
import { Lang } from "@scandic-hotels/common/constants/language"
import { env } from "@/env/server"
export function isDefined<T>(argument: T | undefined | null): argument is T {