diff --git a/apps/partner-sas/auth/scandic/config.ts b/apps/partner-sas/auth/scandic/config.ts
index 5523c4463..671eb2f57 100644
--- a/apps/partner-sas/auth/scandic/config.ts
+++ b/apps/partner-sas/auth/scandic/config.ts
@@ -8,6 +8,6 @@ export const config = {
client_secret: env.CURITY_CLIENT_SECRET_USER,
redirect_uri: new URL("/api/web/auth/callback/curity", env.PUBLIC_URL).href,
acr_values: "urn:com:scandichotels:sas-eb",
- scope: "openid profile availability availability_whitelabel_get",
+ scope: "openid profile booking availability availability_whitelabel_get",
response_type: "code",
} as const
diff --git a/apps/partner-sas/lib/trpc/index.ts b/apps/partner-sas/lib/trpc/index.ts
index 4e99398f0..071a3e560 100644
--- a/apps/partner-sas/lib/trpc/index.ts
+++ b/apps/partner-sas/lib/trpc/index.ts
@@ -1,12 +1,16 @@
import { headers } from "next/headers"
+import { dt } from "@scandic-hotels/common/dt"
import { createContext } from "@scandic-hotels/trpc/context"
+import { getEuroBonusProfileData } from "@scandic-hotels/trpc/routers/partners/sas/getEuroBonusProfile"
+import { getVerifiedUser } from "@scandic-hotels/trpc/routers/user/utils/getVerifiedUser"
import {
appServerClient,
configureServerClient,
} from "@scandic-hotels/trpc/serverClient"
import { auth } from "@/auth"
+import { getSession } from "@/auth/scandic/session"
import type { Lang } from "@scandic-hotels/common/constants/language"
@@ -24,6 +28,34 @@ export async function createAppContext() {
const session = await auth()
return session
},
+ getScandicUserToken: async () => {
+ const session = await getSession()
+ return session?.access_token ?? null
+ },
+ getUserPointsBalance: async () => {
+ const session = await auth()
+ if (!session) return null
+
+ const euroBonusProfile = await getEuroBonusProfileData({
+ accessToken: session.token.access_token,
+ loginType: session.token.loginType,
+ })
+
+ if (!euroBonusProfile) return null
+
+ return euroBonusProfile.points.total
+ },
+ getScandicUser: async () => {
+ const session = await getSession()
+ if (!session) return null
+
+ return await getVerifiedUser({
+ token: {
+ expires_at: dt(session.expires_at).unix() * 1000,
+ access_token: session.access_token,
+ },
+ })
+ },
})
return ctx
diff --git a/apps/scandic-web/actions/editProfile.ts b/apps/scandic-web/actions/editProfile.ts
index 7a276c7da..97e092d1f 100644
--- a/apps/scandic-web/actions/editProfile.ts
+++ b/apps/scandic-web/actions/editProfile.ts
@@ -105,17 +105,17 @@ export const editProfile = protectedServerActionProcedure
if (typedKey === "address") {
if (
- (payload.data.address.city === profile.address.city ||
- (!payload.data.address.city && !profile.address.city)) &&
- (payload.data.address.countryCode === profile.address.countryCode ||
+ (payload.data.address.city === profile.address?.city ||
+ (!payload.data.address.city && !profile.address?.city)) &&
+ (payload.data.address.countryCode === profile.address?.countryCode ||
(!payload.data.address.countryCode &&
- !profile.address.countryCode)) &&
+ !profile.address?.countryCode)) &&
(payload.data.address.streetAddress ===
- profile.address.streetAddress ||
+ profile.address?.streetAddress ||
(!payload.data.address.streetAddress &&
- !profile.address.streetAddress)) &&
- (payload.data.address.zipCode === profile.address.zipCode ||
- (!payload.data.address.zipCode && !profile.address.zipCode))
+ !profile.address?.streetAddress)) &&
+ (payload.data.address.zipCode === profile.address?.zipCode ||
+ (!payload.data.address.zipCode && !profile.address?.zipCode))
) {
// untouched - noop
} else {
diff --git a/apps/scandic-web/app/[lang]/(partner)/(sas)/(protected)/sas-x-scandic/otp/OneTimePasswordForm.tsx b/apps/scandic-web/app/[lang]/(partner)/(sas)/(protected)/sas-x-scandic/otp/OneTimePasswordForm.tsx
index 6a839466a..659b4d95a 100644
--- a/apps/scandic-web/app/[lang]/(partner)/(sas)/(protected)/sas-x-scandic/otp/OneTimePasswordForm.tsx
+++ b/apps/scandic-web/app/[lang]/(partner)/(sas)/(protected)/sas-x-scandic/otp/OneTimePasswordForm.tsx
@@ -53,7 +53,7 @@ export default function OneTimePasswordForm({
}
if (requestOtp.isError) {
- const cause = requestOtp.error?.data?.cause as RequestOtpError
+ const cause = requestOtp.error?.data?.cause as unknown as RequestOtpError
const title = intl.formatMessage({
id: "linkEuroBonusAccount.oneTimePasswordGenericError",
diff --git a/apps/scandic-web/app/[lang]/webview/(views)/layout.tsx b/apps/scandic-web/app/[lang]/webview/(views)/layout.tsx
index 3809e22ec..3c53be0a7 100644
--- a/apps/scandic-web/app/[lang]/webview/(views)/layout.tsx
+++ b/apps/scandic-web/app/[lang]/webview/(views)/layout.tsx
@@ -1,8 +1,10 @@
import * as Sentry from "@sentry/nextjs"
+import { TRPCError } from "@trpc/server"
import { headers } from "next/headers"
import { redirect } from "next/navigation"
import { logger } from "@scandic-hotels/common/logger"
+import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import { getProfile } from "@/lib/trpc/memoizedRequests"
@@ -17,9 +19,9 @@ export default async function Layout(
const { children } = props
const intl = await getIntl()
- const user = await getProfile()
+ const [user, error] = await safeTry(getProfile())
- if (!user) {
+ if (!user && !error) {
logger.debug(`[webview:page] unable to load user`)
return (
@@ -31,19 +33,21 @@ export default async function Layout(
)
}
- if ("error" in user) {
- switch (user.cause) {
- case "unauthorized": // fall through
- case "forbidden": // fall through
- case "token_expired":
- const headersList = await headers()
- const returnURL = `/${params.lang}/webview${headersList.get("x-pathname")!}`
- const redirectURL = `/${params.lang}/webview/refresh?returnUrl=${encodeURIComponent(returnURL)}`
- logger.debug(
- `[webview:page] user error, redirecting to: ${redirectURL}`
- )
- redirect(redirectURL)
- case "notfound":
+ const notValidSession =
+ error instanceof TRPCError &&
+ (error.code === "UNAUTHORIZED" || error.code === "FORBIDDEN")
+
+ if (notValidSession) {
+ const headersList = await headers()
+ const returnURL = `/${params.lang}/webview${headersList.get("x-pathname")!}`
+ const redirectURL = `/${params.lang}/webview/refresh?returnUrl=${encodeURIComponent(returnURL)}`
+ logger.debug(`[webview:page] user error, redirecting to: ${redirectURL}`)
+ redirect(redirectURL)
+ }
+
+ if (error instanceof TRPCError) {
+ switch (error.code) {
+ case "NOT_FOUND":
return (
{intl.formatMessage({
@@ -52,7 +56,15 @@ export default async function Layout(
})}
)
- case "unknown":
+ default:
+ logger.error("[webview:page] unexpected error code loading user", error)
+ Sentry.captureException(error, {
+ data: {
+ errorCode: error.code,
+ message: "[webview:page] unexpected error code loading user",
+ },
+ })
+
return (
{intl.formatMessage({
@@ -61,10 +73,6 @@ export default async function Layout(
})}
)
- default:
- const u: never = user
- logger.error("[webview:page] unhandled user loading error", u)
- Sentry.captureMessage("[webview:page] unhandled user loading error", u)
}
}
diff --git a/apps/scandic-web/components/MyPages/Profile/index.tsx b/apps/scandic-web/components/MyPages/Profile/index.tsx
index 4fc387b53..8a7eff23c 100644
--- a/apps/scandic-web/components/MyPages/Profile/index.tsx
+++ b/apps/scandic-web/components/MyPages/Profile/index.tsx
@@ -30,11 +30,11 @@ export default async function Profile() {
const lang = await getLang()
const addressParts = []
- if (user.address.streetAddress) {
+ if (user.address?.streetAddress) {
addressParts.push(user.address.streetAddress)
}
- if (user.address.city) {
+ if (user.address?.city) {
addressParts.push(user.address.city)
}
@@ -43,8 +43,8 @@ export default async function Profile() {
region: new Intl.DisplayNames([lang], { type: "region" }),
}
- if (user.address.country) {
- const countryCode = isValidCountry(user.address.country)
+ if (user.address?.country) {
+ const countryCode = isValidCountry(user.address?.country)
? countriesMap[user.address.country]
: null
const localizedCountry = countryCode
diff --git a/apps/scandic-web/components/ProtectedLayout.tsx b/apps/scandic-web/components/ProtectedLayout.tsx
index 59e498bb6..030dc8e3f 100644
--- a/apps/scandic-web/components/ProtectedLayout.tsx
+++ b/apps/scandic-web/components/ProtectedLayout.tsx
@@ -1,8 +1,10 @@
+import { TRPCError } from "@trpc/server"
import { headers } from "next/headers"
import { redirect } from "next/navigation"
import { overview } from "@scandic-hotels/common/constants/routes/myPages"
import { logger } from "@scandic-hotels/common/logger"
+import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
import { getProfile } from "@/lib/trpc/memoizedRequests"
@@ -33,29 +35,9 @@ export async function ProtectedLayout({ children }: React.PropsWithChildren) {
redirect(redirectURL)
}
- const user = await getProfile()
+ const [user, error] = await safeTry(getProfile())
- if (user && "error" in user) {
- // redirect(redirectURL)
- logger.error("[layout:protected] error in user", user)
- switch (user.cause) {
- case "unauthorized": // fall through
- case "forbidden": // fall through
- case "token_expired":
- logger.error(
- `[layout:protected] user error, redirecting to: ${redirectURL}`
- )
- redirect(redirectURL)
- case "notfound":
- logger.error(`[layout:protected] notfound user loading error`)
- break
- case "unknown":
- logger.error(`[layout:protected] unknown user loading error`)
- break
- default:
- logger.error(`[layout:protected] unhandled user loading error`)
- break
- }
+ if (error instanceof TRPCError && error.code === "INTERNAL_SERVER_ERROR") {
return (
{intl.formatMessage({
diff --git a/apps/scandic-web/components/UserExists.tsx b/apps/scandic-web/components/UserExists.tsx
index 6abde0846..ba42ff785 100644
--- a/apps/scandic-web/components/UserExists.tsx
+++ b/apps/scandic-web/components/UserExists.tsx
@@ -16,9 +16,12 @@ export function UserExists() {
const isUserLoggedIn = isValidClientSession(session)
const lang = useLang()
- const { data, isLoading: isLoadingUser } = trpc.user.get.useQuery(undefined, {
- enabled: isUserLoggedIn,
- })
+ const { isLoading: isLoadingUser, error } = trpc.user.get.useQuery(
+ undefined,
+ {
+ enabled: isUserLoggedIn,
+ }
+ )
if (!isUserLoggedIn) {
return null
@@ -28,16 +31,12 @@ export function UserExists() {
return null
}
- if (data && "error" in data && data.error) {
- switch (data.cause) {
- case "notfound":
- redirect(
- `${logoutSafely[lang]}?redirectTo=${encodeURIComponent(userNotFound[lang])}`
- )
- default:
- break
- }
+ switch (error?.data?.code) {
+ case "NOT_FOUND":
+ redirect(
+ `${logoutSafely[lang]}?redirectTo=${encodeURIComponent(userNotFound[lang])}`
+ )
+ default:
+ return null
}
-
- return null
}
diff --git a/apps/scandic-web/lib/trpc/server.ts b/apps/scandic-web/lib/trpc/server.ts
index c6ad2d137..523638513 100644
--- a/apps/scandic-web/lib/trpc/server.ts
+++ b/apps/scandic-web/lib/trpc/server.ts
@@ -6,6 +6,7 @@ import { Lang } from "@scandic-hotels/common/constants/language"
import { login } from "@scandic-hotels/common/constants/routes/handleAuth"
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
import { createContext } from "@scandic-hotels/trpc/context"
+import { getVerifiedUser } from "@scandic-hotels/trpc/routers/user/utils/getVerifiedUser"
import {
appServerClient,
configureServerClient,
@@ -23,6 +24,21 @@ export async function createAppContext() {
const webviewTokenCookie = cookie.get("webviewToken")
const loginType = headersList.get("loginType")
+ async function getUserSession(): Promise {
+ const session = await auth()
+ const webToken = webviewTokenCookie?.value
+ if (!session?.token && !webToken) {
+ return null
+ }
+
+ return (
+ session ||
+ ({
+ token: { access_token: webToken, loginType },
+ } as Session)
+ )
+ }
+
const ctx = createContext({
app: "scandic-web",
lang: headersList.get("x-lang") as Lang,
@@ -31,19 +47,38 @@ export async function createAppContext() {
url: headersList.get("x-url")!,
webToken: webviewTokenCookie?.value,
contentType: headersList.get("x-contenttype")!,
- auth: async () => {
- const session = await auth()
- const webToken = webviewTokenCookie?.value
- if (!session?.token && !webToken) {
+ auth: async () => await getUserSession(),
+ getScandicUserToken: async () => {
+ const session = await getUserSession()
+ return session?.token?.access_token ?? null
+ },
+ getUserPointsBalance: async () => {
+ const session = await getUserSession()
+ if (!session) return null
+
+ const user = await getVerifiedUser({
+ token: {
+ expires_at: session.token.expires_at ?? 0,
+ access_token: session.token.access_token,
+ },
+ })
+
+ if (!user) {
return null
}
- return (
- session ||
- ({
- token: { access_token: webToken, loginType },
- } as Session)
- )
+ return user.membership?.currentPoints ?? 0
+ },
+ getScandicUser: async () => {
+ const session = await getUserSession()
+ if (!session) return null
+
+ return await getVerifiedUser({
+ token: {
+ expires_at: session.token.expires_at ?? 0,
+ access_token: session.token.access_token,
+ },
+ })
},
})
diff --git a/packages/common/utils/safeTry.ts b/packages/common/utils/safeTry.ts
index d3bb81596..5581fbe65 100644
--- a/packages/common/utils/safeTry.ts
+++ b/packages/common/utils/safeTry.ts
@@ -4,8 +4,8 @@ export type SafeTryResult = Promise<
export async function safeTry(func: Promise): SafeTryResult {
try {
- return [await func, undefined]
+ return [await func, undefined] as const
} catch (err) {
- return [undefined, err]
+ return [undefined, err instanceof Error ? err : (err as unknown)] as const
}
}
diff --git a/packages/trpc/lib/context.ts b/packages/trpc/lib/context.ts
index 36f38db1e..8760cfe3c 100644
--- a/packages/trpc/lib/context.ts
+++ b/packages/trpc/lib/context.ts
@@ -2,6 +2,8 @@ import type { Lang } from "@scandic-hotels/common/constants/language"
import type { User } from "next-auth"
import type { JWT } from "next-auth/jwt"
+import type { getVerifiedUser } from "./routers/user/utils/getVerifiedUser"
+
type Session = {
token: JWT
expires: string
@@ -9,6 +11,7 @@ type Session = {
error?: "RefreshAccessTokenError"
}
+type ScandicUser = Awaited>
type CreateContextOptions = {
auth: () => Promise
lang: Lang
@@ -18,6 +21,9 @@ type CreateContextOptions = {
webToken?: string
contentType?: string
app: "scandic-web" | "partner-sas"
+ getScandicUserToken: () => Promise
+ getUserPointsBalance: () => Promise
+ getScandicUser: () => Promise
}
export function createContext(opts: CreateContextOptions) {
@@ -30,6 +36,9 @@ export function createContext(opts: CreateContextOptions) {
webToken: opts.webToken,
contentType: opts.contentType,
app: opts.app,
+ getScandicUserToken: opts.getScandicUserToken,
+ getUserPointsBalance: opts.getUserPointsBalance,
+ getScandicUser: opts.getScandicUser,
}
}
diff --git a/packages/trpc/lib/index.ts b/packages/trpc/lib/index.ts
index 9074357e5..95ddfec95 100644
--- a/packages/trpc/lib/index.ts
+++ b/packages/trpc/lib/index.ts
@@ -19,10 +19,7 @@ const t = initTRPC
...shape,
data: {
...shape.data,
- cause:
- error.cause instanceof ZodError
- ? undefined
- : JSON.parse(JSON.stringify(error.cause)),
+ cause: error.cause instanceof ZodError ? undefined : error.cause,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
diff --git a/packages/trpc/lib/procedures.ts b/packages/trpc/lib/procedures.ts
index 896afb5e4..5d2816a05 100644
--- a/packages/trpc/lib/procedures.ts
+++ b/packages/trpc/lib/procedures.ts
@@ -124,6 +124,7 @@ export const serviceProcedure = baseProcedure.use(async (opts) => {
if (!access_token) {
throw internalServerError(`[serviceProcedure] No service token`)
}
+
return opts.next({
ctx: {
serviceToken: access_token,
diff --git a/packages/trpc/lib/routers/autocomplete/destinations.ts b/packages/trpc/lib/routers/autocomplete/destinations.ts
index 2df6f3cc9..86f5241b5 100644
--- a/packages/trpc/lib/routers/autocomplete/destinations.ts
+++ b/packages/trpc/lib/routers/autocomplete/destinations.ts
@@ -43,11 +43,18 @@ export const getDestinationsAutoCompleteRoute = safeProtectedServiceProcedure
.input(destinationsAutoCompleteInputSchema)
.query(async ({ ctx, input }): Promise => {
const lang = input.lang || ctx.lang
- const locations: AutoCompleteLocation[] =
- await getAutoCompleteDestinationsData({
+ const [locations, error] = await safeTry(
+ getAutoCompleteDestinationsData({
lang,
serviceToken: ctx.serviceToken,
})
+ )
+
+ if (error || !locations) {
+ throw new Error("Unable to fetch autocomplete destinations data", {
+ cause: error,
+ })
+ }
const hits = filterAndCategorizeAutoComplete({
locations: locations,
@@ -115,17 +122,31 @@ export async function getAutoCompleteDestinationsData({
}
const countryNames = countries.data.map((country) => country.name)
- const citiesByCountry = await getCitiesByCountry({
- countries: countryNames,
- serviceToken: serviceToken,
- lang,
- })
+ const [citiesByCountry, citiesByCountryError] = await safeTry(
+ getCitiesByCountry({
+ countries: countryNames,
+ serviceToken: serviceToken,
+ lang,
+ })
+ )
- const locations = await getLocations({
- lang: lang,
- serviceToken: serviceToken,
- citiesByCountry: citiesByCountry,
- })
+ if (citiesByCountryError || !citiesByCountry) {
+ autoCompleteLogger.error("Unable to fetch cities by country")
+ throw new Error("Unable to fetch cities by country")
+ }
+
+ const [locations, locationsError] = await safeTry(
+ getLocations({
+ lang: lang,
+ serviceToken: serviceToken,
+ citiesByCountry: citiesByCountry,
+ })
+ )
+
+ if (locationsError || !locations) {
+ autoCompleteLogger.error("Unable to fetch locations")
+ throw new Error("Unable to fetch locations")
+ }
const activeLocations = locations.filter((location) => {
return (
diff --git a/packages/trpc/lib/routers/booking/mutation/create/index.ts b/packages/trpc/lib/routers/booking/mutation/create/index.ts
index ec7234d89..77e924365 100644
--- a/packages/trpc/lib/routers/booking/mutation/create/index.ts
+++ b/packages/trpc/lib/routers/booking/mutation/create/index.ts
@@ -5,18 +5,12 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import * as api from "../../../../api"
import { safeProtectedServiceProcedure } from "../../../../procedures"
import { encrypt } from "../../../../utils/encryption"
-import { isValidSession } from "../../../../utils/session"
-import { getMembershipNumber } from "../../../user/utils"
-import { isPartnerLoggedInUser } from "../../utils"
import { createBookingInput, createBookingSchema } from "./schema"
export const create = safeProtectedServiceProcedure
.input(createBookingInput)
.use(async ({ ctx, next }) => {
- const token =
- isValidSession(ctx.session) && !isPartnerLoggedInUser(ctx.session)
- ? ctx.session.token.access_token
- : ctx.serviceToken
+ const token = await ctx.getScandicUserToken()
return next({
ctx: {
@@ -29,8 +23,9 @@ export const create = safeProtectedServiceProcedure
const { rooms, ...loggableInput } = inputWithoutLang
const createBookingCounter = createCounter("trpc.booking", "create")
+ const user = await ctx.getScandicUser()
const metricsCreateBooking = createBookingCounter.init({
- membershipNumber: await getMembershipNumber(ctx.session),
+ membershipNumber: user?.membershipNumber,
language,
...loggableInput,
rooms: inputWithoutLang.rooms.map(({ guest, ...room }) => {
@@ -40,9 +35,8 @@ export const create = safeProtectedServiceProcedure
})
metricsCreateBooking.start()
-
const headers = {
- Authorization: `Bearer ${ctx.token}`,
+ Authorization: `Bearer ${ctx.token || ctx.serviceToken}`,
}
const apiResponse = await api.post(
diff --git a/packages/trpc/lib/routers/hotels/availability/enterDetails.ts b/packages/trpc/lib/routers/hotels/availability/enterDetails.ts
index 4dc804958..cba490623 100644
--- a/packages/trpc/lib/routers/hotels/availability/enterDetails.ts
+++ b/packages/trpc/lib/routers/hotels/availability/enterDetails.ts
@@ -9,8 +9,7 @@ import { SEARCH_TYPE_REDEMPTION } from "../../../constants/booking"
import { AvailabilityEnum } from "../../../enums/selectHotel"
import { unauthorizedError } from "../../../errors"
import { safeProtectedServiceProcedure } from "../../../procedures"
-import { getRedemptionTokenSafely } from "../../../utils/getRedemptionTokenSafely"
-import { getUserPointsBalance } from "../../../utils/getUserPointsBalance"
+import { isValidSession } from "../../../utils/session"
import { baseBookingSchema, baseRoomSchema, selectedRoomSchema } from "../input"
import { getHotel } from "../services/getHotel"
import { getRoomsAvailability } from "../services/getRoomsAvailability"
@@ -32,18 +31,23 @@ export const enterDetailsRoomsAvailabilityInputSchema = z.object({
lang: z.nativeEnum(Lang),
})
+type Context = {
+ userToken: string | null
+ userPoints?: number
+}
+
const logger = createLogger("trpc:availability:enterDetails")
export const enterDetails = safeProtectedServiceProcedure
.input(enterDetailsRoomsAvailabilityInputSchema)
.use(async ({ ctx, input, next }) => {
+ const userToken = await ctx.getScandicUserToken()
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
- if (ctx.session?.token.access_token) {
- const pointsValue = await getUserPointsBalance(ctx.session)
- const token = getRedemptionTokenSafely(ctx.session, ctx.serviceToken)
- if (pointsValue !== undefined && token) {
- return next({
+ if (isValidSession(ctx.session)) {
+ const pointsValue = await ctx.getUserPointsBalance()
+ if (pointsValue && userToken) {
+ return next({
ctx: {
- token: token,
+ userToken,
userPoints: pointsValue,
},
input,
@@ -52,19 +56,20 @@ export const enterDetails = safeProtectedServiceProcedure
}
throw unauthorizedError()
}
- return next({
+ return next({
ctx: {
- token: ctx.serviceToken,
+ userToken,
},
})
})
.query(async function ({ ctx, input }) {
const availability = await getRoomsAvailability(
input,
- ctx.token,
+ ctx.userToken,
ctx.serviceToken,
ctx.userPoints
)
+
const hotelData = await getHotel(
{
hotelId: input.booking.hotelId,
diff --git a/packages/trpc/lib/routers/hotels/availability/hotelsByCity.ts b/packages/trpc/lib/routers/hotels/availability/hotelsByCity.ts
index 812d6eb10..abd83e220 100644
--- a/packages/trpc/lib/routers/hotels/availability/hotelsByCity.ts
+++ b/packages/trpc/lib/routers/hotels/availability/hotelsByCity.ts
@@ -6,9 +6,7 @@ import { getCacheClient } from "@scandic-hotels/common/dataCache"
import { env } from "../../../../env/server"
import { unauthorizedError } from "../../../errors"
import { safeProtectedServiceProcedure } from "../../../procedures"
-import { toApiLang } from "../../../utils"
-import { getRedemptionTokenSafely } from "../../../utils/getRedemptionTokenSafely"
-import { getUserPointsBalance } from "../../../utils/getUserPointsBalance"
+import { isValidSession } from "../../../utils/session"
import { getHotelsAvailabilityByCity } from "../services/getHotelsAvailabilityByCity"
export type HotelsAvailabilityInputSchema = z.output<
@@ -64,35 +62,40 @@ export const hotelsAvailabilityInputSchema = z
}
)
+type Context = {
+ userToken: string | null
+ userPoints?: number
+}
+
export const hotelsByCity = safeProtectedServiceProcedure
.input(hotelsAvailabilityInputSchema)
.use(async ({ ctx, input, next }) => {
+ const userToken = await ctx.getScandicUserToken()
+
if (input.redemption) {
- if (ctx.session?.token.access_token) {
- const pointsValue = await getUserPointsBalance(ctx.session)
- const token = getRedemptionTokenSafely(ctx.session, ctx.serviceToken)
- if (pointsValue !== undefined && token) {
- return next({
- ctx: {
- token: token,
- userPoints: pointsValue,
- },
- input,
- })
- }
+ const hasValidSession = isValidSession(ctx.session)
+ if (!hasValidSession) {
+ throw unauthorizedError()
+ }
+
+ const pointsValue = await ctx.getUserPointsBalance()
+ if (pointsValue && userToken) {
+ return next({
+ ctx: {
+ userToken,
+ userPoints: pointsValue,
+ },
+ })
}
- throw unauthorizedError()
}
- return next({
+
+ return next({
ctx: {
- token: ctx.serviceToken,
+ userToken,
},
- input,
})
})
.query(async ({ ctx, input }) => {
- const { lang } = ctx
- const apiLang = toApiLang(lang)
const {
cityId,
roomStayStartDate,
@@ -105,19 +108,26 @@ export const hotelsByCity = safeProtectedServiceProcedure
// In case of redemption do not cache result
if (redemption) {
- return getHotelsAvailabilityByCity(
+ return getHotelsAvailabilityByCity({
input,
- apiLang,
- ctx.token,
- ctx.userPoints
- )
+ lang: ctx.lang,
+ userToken: ctx.userToken,
+ serviceToken: ctx.serviceToken,
+ userPoints: ctx.userPoints,
+ })
}
const cacheClient = await getCacheClient()
return await cacheClient.cacheOrGet(
`${cityId}:${roomStayStartDate}:${roomStayEndDate}:${adults}:${children}:${bookingCode}`,
async () => {
- return getHotelsAvailabilityByCity(input, apiLang, ctx.token)
+ return getHotelsAvailabilityByCity({
+ input,
+ lang: ctx.lang,
+ userToken: undefined,
+ serviceToken: ctx.serviceToken,
+ userPoints: undefined,
+ })
},
env.CACHE_TIME_CITY_SEARCH
)
diff --git a/packages/trpc/lib/routers/hotels/availability/hotelsByCityWithBookingCode.ts b/packages/trpc/lib/routers/hotels/availability/hotelsByCityWithBookingCode.ts
index 8a86a319f..e342326c0 100644
--- a/packages/trpc/lib/routers/hotels/availability/hotelsByCityWithBookingCode.ts
+++ b/packages/trpc/lib/routers/hotels/availability/hotelsByCityWithBookingCode.ts
@@ -1,5 +1,4 @@
import { serviceProcedure } from "../../../procedures"
-import { toApiLang } from "../../../utils"
import { getHotelsAvailabilityByCity } from "../services/getHotelsAvailabilityByCity"
import { getHotelsAvailabilityByHotelIds } from "../services/getHotelsAvailabilityByHotelIds"
import { hotelsAvailabilityInputSchema } from "./hotelsByCity"
@@ -7,14 +6,13 @@ import { hotelsAvailabilityInputSchema } from "./hotelsByCity"
export const hotelsByCityWithBookingCode = serviceProcedure
.input(hotelsAvailabilityInputSchema)
.query(async ({ input, ctx }) => {
- const { lang } = ctx
- const apiLang = toApiLang(lang)
-
- const bookingCodeAvailabilityResponse = await getHotelsAvailabilityByCity(
+ const bookingCodeAvailabilityResponse = await getHotelsAvailabilityByCity({
input,
- apiLang,
- ctx.serviceToken
- )
+ lang: ctx.lang,
+ userToken: undefined,
+ serviceToken: ctx.serviceToken,
+ userPoints: undefined,
+ })
// Get regular availability of hotels which don't have availability with booking code.
const unavailableHotelIds = bookingCodeAvailabilityResponse.availability
@@ -36,11 +34,13 @@ export const hotelsByCityWithBookingCode = serviceProcedure
bookingCode: "",
hotelIds: unavailableHotelIds,
}
- const unavailableHotels = await getHotelsAvailabilityByHotelIds(
- unavailableHotelsInput,
- apiLang,
- ctx.serviceToken
- )
+ const unavailableHotels = await getHotelsAvailabilityByHotelIds({
+ input: unavailableHotelsInput,
+ lang: ctx.lang,
+ serviceToken: ctx.serviceToken,
+ userToken: undefined,
+ userPoints: undefined,
+ })
// No regular rates available due to network or API failure (no need to filter & merge).
if (!unavailableHotels) {
diff --git a/packages/trpc/lib/routers/hotels/availability/hotelsByHotelIds.ts b/packages/trpc/lib/routers/hotels/availability/hotelsByHotelIds.ts
index 6b834e425..da3fabc61 100644
--- a/packages/trpc/lib/routers/hotels/availability/hotelsByHotelIds.ts
+++ b/packages/trpc/lib/routers/hotels/availability/hotelsByHotelIds.ts
@@ -3,9 +3,7 @@ import { z } from "zod"
import { unauthorizedError } from "../../../errors"
import { safeProtectedServiceProcedure } from "../../../procedures"
-import { toApiLang } from "../../../utils"
-import { getRedemptionTokenSafely } from "../../../utils/getRedemptionTokenSafely"
-import { getUserPointsBalance } from "../../../utils/getUserPointsBalance"
+import { isValidSession } from "../../../utils/session"
import { getHotelsAvailabilityByHotelIds } from "../services/getHotelsAvailabilityByHotelIds"
export type HotelsByHotelIdsAvailabilityInputSchema = z.output<
@@ -60,40 +58,45 @@ export const getHotelsByHotelIdsAvailabilityInputSchema = z
message: "FROMDATE_CANNOT_BE_IN_THE_PAST",
}
)
+type Context = {
+ userToken: string | null
+ userPoints?: number
+}
export const hotelsByHotelIds = safeProtectedServiceProcedure
.input(getHotelsByHotelIdsAvailabilityInputSchema)
.use(async ({ ctx, input, next }) => {
+ const userToken = await ctx.getScandicUserToken()
+
if (input.redemption) {
- if (ctx.session?.token.access_token) {
- const pointsValue = await getUserPointsBalance(ctx.session)
- const token = getRedemptionTokenSafely(ctx.session, ctx.serviceToken)
- if (pointsValue !== undefined && token) {
- return next({
- ctx: {
- token: token,
- userPoints: pointsValue,
- },
- input,
- })
- }
+ const hasValidSession = isValidSession(ctx.session)
+ if (!hasValidSession) {
+ throw unauthorizedError()
+ }
+
+ const pointsValue = await ctx.getUserPointsBalance()
+ if (pointsValue && userToken) {
+ return next({
+ ctx: {
+ userToken,
+ userPoints: pointsValue,
+ },
+ })
}
- throw unauthorizedError()
}
- return next({
+
+ return next({
ctx: {
- token: ctx.serviceToken,
+ userToken,
},
- input,
})
})
.query(async ({ input, ctx }) => {
- const { lang } = ctx
- const apiLang = toApiLang(lang)
- return getHotelsAvailabilityByHotelIds(
+ return getHotelsAvailabilityByHotelIds({
input,
- apiLang,
- ctx.token,
- ctx.userPoints
- )
+ lang: ctx.lang,
+ userToken: ctx.userToken,
+ serviceToken: ctx.serviceToken,
+ userPoints: ctx.userPoints,
+ })
})
diff --git a/packages/trpc/lib/routers/hotels/availability/myStay.ts b/packages/trpc/lib/routers/hotels/availability/myStay.ts
index 0ca779d41..8115aef3c 100644
--- a/packages/trpc/lib/routers/hotels/availability/myStay.ts
+++ b/packages/trpc/lib/routers/hotels/availability/myStay.ts
@@ -6,8 +6,7 @@ import { createLogger } from "@scandic-hotels/common/logger/createLogger"
import { SEARCH_TYPE_REDEMPTION } from "../../../constants/booking"
import { unauthorizedError } from "../../../errors"
import { safeProtectedServiceProcedure } from "../../../procedures"
-import { getRedemptionTokenSafely } from "../../../utils/getRedemptionTokenSafely"
-import { getUserPointsBalance } from "../../../utils/getUserPointsBalance"
+import { isValidSession } from "../../../utils/session"
import { baseBookingSchema, baseRoomSchema, selectedRoomSchema } from "../input"
import { getRoomsAvailability } from "../services/getRoomsAvailability"
import { getSelectedRoomAvailability } from "../utils"
@@ -19,29 +18,37 @@ export const myStayRoomAvailabilityInputSchema = z.object({
lang: z.nativeEnum(Lang),
})
+type Context = {
+ userToken: string | null
+ userPoints?: number
+}
+
const logger = createLogger("trpc:availability:myStay")
export const myStay = safeProtectedServiceProcedure
.input(myStayRoomAvailabilityInputSchema)
.use(async ({ ctx, input, next }) => {
+ const userToken = await ctx.getScandicUserToken()
+
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
- if (ctx.session?.token.access_token) {
- const pointsValue = await getUserPointsBalance(ctx.session)
- const token = getRedemptionTokenSafely(ctx.session, ctx.serviceToken)
- if (pointsValue !== undefined && token) {
- return next({
- ctx: {
- token: token,
- userPoints: pointsValue,
- },
- input,
- })
- }
+ const hasValidSession = isValidSession(ctx.session)
+ if (!hasValidSession) {
+ throw unauthorizedError()
+ }
+
+ const pointsValue = await ctx.getUserPointsBalance()
+ if (pointsValue && userToken) {
+ return next({
+ ctx: {
+ userToken,
+ userPoints: pointsValue,
+ },
+ })
}
- throw unauthorizedError()
}
- return next({
+
+ return next({
ctx: {
- token: ctx.serviceToken,
+ userToken,
},
})
})
@@ -54,7 +61,7 @@ export const myStay = safeProtectedServiceProcedure
},
lang: input.lang,
},
- ctx.token,
+ ctx.userToken,
ctx.serviceToken,
ctx.userPoints
)
diff --git a/packages/trpc/lib/routers/hotels/availability/selectRate/room.ts b/packages/trpc/lib/routers/hotels/availability/selectRate/room.ts
index c343ee3df..2c2e1c642 100644
--- a/packages/trpc/lib/routers/hotels/availability/selectRate/room.ts
+++ b/packages/trpc/lib/routers/hotels/availability/selectRate/room.ts
@@ -5,8 +5,7 @@ import { Lang } from "@scandic-hotels/common/constants/language"
import { SEARCH_TYPE_REDEMPTION } from "../../../../constants/booking"
import { unauthorizedError } from "../../../../errors"
import { safeProtectedServiceProcedure } from "../../../../procedures"
-import { getRedemptionTokenSafely } from "../../../../utils/getRedemptionTokenSafely"
-import { getUserPointsBalance } from "../../../../utils/getUserPointsBalance"
+import { isValidSession } from "../../../../utils/session"
import { baseBookingSchema, baseRoomSchema } from "../../input"
import { getRoomsAvailability } from "../../services/getRoomsAvailability"
import { mergeRoomTypes } from "../../utils"
@@ -18,28 +17,36 @@ export const selectRateRoomAvailabilityInputSchema = z.object({
lang: z.nativeEnum(Lang),
})
+type Context = {
+ userToken: string | null
+ userPoints?: number
+}
+
export const room = safeProtectedServiceProcedure
.input(selectRateRoomAvailabilityInputSchema)
.use(async ({ ctx, input, next }) => {
+ const userToken = await ctx.getScandicUserToken()
+
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
- if (ctx.session?.token.access_token) {
- const pointsValue = await getUserPointsBalance(ctx.session)
- const token = getRedemptionTokenSafely(ctx.session, ctx.serviceToken)
- if (pointsValue !== undefined && token) {
- return next({
- ctx: {
- token: token,
- userPoints: pointsValue,
- },
- input,
- })
- }
+ const hasValidSession = isValidSession(ctx.session)
+ if (!hasValidSession) {
+ throw unauthorizedError()
+ }
+
+ const pointsValue = await ctx.getUserPointsBalance()
+ if (pointsValue && userToken) {
+ return next({
+ ctx: {
+ userToken,
+ userPoints: pointsValue,
+ },
+ })
}
- throw unauthorizedError()
}
- return next({
+
+ return next({
ctx: {
- token: ctx.serviceToken,
+ userToken,
},
})
})
@@ -52,7 +59,7 @@ export const room = safeProtectedServiceProcedure
},
lang: input.lang,
},
- ctx.token,
+ ctx.userToken,
ctx.serviceToken,
ctx.userPoints
)
diff --git a/packages/trpc/lib/routers/hotels/availability/selectRate/rooms/index.ts b/packages/trpc/lib/routers/hotels/availability/selectRate/rooms/index.ts
index de70cafd8..d398ce877 100644
--- a/packages/trpc/lib/routers/hotels/availability/selectRate/rooms/index.ts
+++ b/packages/trpc/lib/routers/hotels/availability/selectRate/rooms/index.ts
@@ -3,34 +3,41 @@ import "server-only"
import { SEARCH_TYPE_REDEMPTION } from "../../../../../constants/booking"
import { unauthorizedError } from "../../../../../errors"
import { safeProtectedServiceProcedure } from "../../../../../procedures"
-import { getRedemptionTokenSafely } from "../../../../../utils/getRedemptionTokenSafely"
-import { getUserPointsBalance } from "../../../../../utils/getUserPointsBalance"
+import { isValidSession } from "../../../../../utils/session"
import { getRoomsAvailability } from "../../../services/getRoomsAvailability"
import { mergeRoomTypes } from "../../../utils"
import { selectRateRoomsAvailabilityInputSchema } from "./schema"
+type Context = {
+ userToken: string | null
+ userPoints?: number
+}
+
export const rooms = safeProtectedServiceProcedure
.input(selectRateRoomsAvailabilityInputSchema)
.use(async ({ ctx, input, next }) => {
+ const userToken = await ctx.getScandicUserToken()
+
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
- if (ctx.session?.token.access_token) {
- const pointsValue = await getUserPointsBalance(ctx.session)
- const token = getRedemptionTokenSafely(ctx.session, ctx.serviceToken)
- if (pointsValue !== undefined && token) {
- return next({
- ctx: {
- token: token,
- userPoints: pointsValue,
- },
- input,
- })
- }
+ const hasValidSession = isValidSession(ctx.session)
+ if (!hasValidSession) {
+ throw unauthorizedError()
+ }
+
+ const pointsValue = await ctx.getUserPointsBalance()
+ if (pointsValue && userToken) {
+ return next({
+ ctx: {
+ userToken,
+ userPoints: pointsValue,
+ },
+ })
}
- throw unauthorizedError()
}
- return next({
+
+ return next({
ctx: {
- token: ctx.serviceToken,
+ userToken,
},
})
})
@@ -42,7 +49,7 @@ export const rooms = safeProtectedServiceProcedure
const availability = await getRoomsAvailability(
input,
- ctx.token,
+ ctx.userToken,
ctx.serviceToken,
ctx.userPoints
)
diff --git a/packages/trpc/lib/routers/hotels/services/getCountries.ts b/packages/trpc/lib/routers/hotels/services/getCountries.ts
index f60a2b3d1..dea57ba20 100644
--- a/packages/trpc/lib/routers/hotels/services/getCountries.ts
+++ b/packages/trpc/lib/routers/hotels/services/getCountries.ts
@@ -37,14 +37,26 @@ export async function getCountries({
)
if (!countryResponse.ok) {
- throw new Error("Unable to fetch countries")
+ logger.error("Unable to fetch countries", {
+ status: countryResponse.status,
+ statusText: countryResponse.statusText,
+ })
+
+ throw new Error("Unable to fetch countries", {
+ cause: {
+ status: countryResponse.status,
+ statusText: countryResponse.statusText,
+ },
+ })
}
const countriesJson = await countryResponse.json()
const countries = countriesSchema.safeParse(countriesJson)
if (!countries.success) {
logger.error(`Validation for countries failed`, countries.error)
- return null
+ throw new Error("Unable to parse country data", {
+ cause: countries.error,
+ })
}
return countries.data
diff --git a/packages/trpc/lib/routers/hotels/services/getHotelsAvailabilityByCity.ts b/packages/trpc/lib/routers/hotels/services/getHotelsAvailabilityByCity.ts
index 5e7e98fd5..342ec91e6 100644
--- a/packages/trpc/lib/routers/hotels/services/getHotelsAvailabilityByCity.ts
+++ b/packages/trpc/lib/routers/hotels/services/getHotelsAvailabilityByCity.ts
@@ -2,16 +2,26 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import * as api from "../../../api"
import { badRequestError } from "../../../errors"
+import { toApiLang } from "../../../utils"
import { hotelsAvailabilitySchema } from "../output"
+import type { Lang } from "@scandic-hotels/common/constants/language"
+
import type { HotelsAvailabilityInputSchema } from "../availability/hotelsByCity"
-export async function getHotelsAvailabilityByCity(
- input: HotelsAvailabilityInputSchema,
- apiLang: string,
- token: string, // Either service token or user access token in case of redemption search
- userPoints: number = 0
-) {
+export async function getHotelsAvailabilityByCity({
+ input,
+ lang,
+ userToken,
+ serviceToken,
+ userPoints = 0,
+}: {
+ input: HotelsAvailabilityInputSchema
+ lang: Lang
+ userToken: string | null | undefined
+ serviceToken: string
+ userPoints: number | undefined
+}) {
const {
cityId,
roomStayStartDate,
@@ -22,7 +32,9 @@ export async function getHotelsAvailabilityByCity(
redemption,
} = input
- const params: Record = {
+ const apiLang = toApiLang(lang)
+
+ const params = {
roomStayStartDate,
roomStayEndDate,
adults,
@@ -30,7 +42,7 @@ export async function getHotelsAvailabilityByCity(
...(bookingCode && { bookingCode }),
...(redemption ? { isRedemption: "true" } : {}),
language: apiLang,
- }
+ } satisfies Record
const getHotelsAvailabilityByCityCounter = createCounter(
"hotel",
@@ -54,7 +66,7 @@ export async function getHotelsAvailabilityByCity(
api.endpoints.v1.Availability.city(cityId),
{
headers: {
- Authorization: `Bearer ${token}`,
+ Authorization: `Bearer ${userToken ?? serviceToken}`,
},
},
params
diff --git a/packages/trpc/lib/routers/hotels/services/getHotelsAvailabilityByHotelIds.ts b/packages/trpc/lib/routers/hotels/services/getHotelsAvailabilityByHotelIds.ts
index 4251370a2..89a48e463 100644
--- a/packages/trpc/lib/routers/hotels/services/getHotelsAvailabilityByHotelIds.ts
+++ b/packages/trpc/lib/routers/hotels/services/getHotelsAvailabilityByHotelIds.ts
@@ -4,16 +4,26 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { env } from "../../../../env/server"
import * as api from "../../../api"
import { badRequestError } from "../../../errors"
+import { toApiLang } from "../../../utils"
import { hotelsAvailabilitySchema } from "../output"
+import type { Lang } from "@scandic-hotels/common/constants/language"
+
import type { HotelsByHotelIdsAvailabilityInputSchema } from "../availability/hotelsByHotelIds"
-export async function getHotelsAvailabilityByHotelIds(
- input: HotelsByHotelIdsAvailabilityInputSchema,
- apiLang: string,
- token: string,
- userPoints: number = 0
-) {
+export async function getHotelsAvailabilityByHotelIds({
+ input,
+ lang,
+ userToken,
+ serviceToken,
+ userPoints = 0,
+}: {
+ input: HotelsByHotelIdsAvailabilityInputSchema
+ lang: Lang
+ userToken: string | null | undefined
+ serviceToken: string
+ userPoints: number | undefined
+}) {
const {
hotelIds,
roomStayStartDate,
@@ -24,6 +34,8 @@ export async function getHotelsAvailabilityByHotelIds(
redemption,
} = input
+ const apiLang = toApiLang(lang)
+
const params = new URLSearchParams([
["roomStayStartDate", roomStayStartDate],
["roomStayEndDate", roomStayEndDate],
@@ -72,7 +84,7 @@ export async function getHotelsAvailabilityByHotelIds(
api.endpoints.v1.Availability.hotels(),
{
headers: {
- Authorization: `Bearer ${token}`,
+ Authorization: `Bearer ${userToken ?? serviceToken}`,
},
},
params
diff --git a/packages/trpc/lib/routers/hotels/services/getRoomsAvailability.ts b/packages/trpc/lib/routers/hotels/services/getRoomsAvailability.ts
index c39bec43b..8d651199d 100644
--- a/packages/trpc/lib/routers/hotels/services/getRoomsAvailability.ts
+++ b/packages/trpc/lib/routers/hotels/services/getRoomsAvailability.ts
@@ -17,7 +17,7 @@ import type { RoomsAvailabilityOutputSchema } from "../availability/selectRate/r
export async function getRoomsAvailability(
input: RoomsAvailabilityOutputSchema,
- token: string,
+ userToken: string | null | undefined,
serviceToken: string,
userPoints: number | undefined
) {
@@ -42,12 +42,12 @@ export async function getRoomsAvailability(
const apiLang = toApiLang(lang)
const baseCacheKey = {
- bookingCode,
- fromDate,
- hotelId,
lang,
- searchType,
+ hotelId,
+ fromDate,
toDate,
+ searchType,
+ bookingCode,
}
const cacheClient = await getCacheClient()
@@ -57,6 +57,11 @@ export async function getRoomsAvailability(
...baseCacheKey,
room,
}
+
+ const token = userToken
+ ? ({ type: "userToken", token: userToken } as const)
+ : ({ type: "serviceToken", token: serviceToken } as const)
+
const result = cacheClient.cacheOrGet(
stringify(cacheKey),
async function () {
@@ -78,7 +83,7 @@ export async function getRoomsAvailability(
{
cache: undefined, // overwrite default
headers: {
- Authorization: `Bearer ${token}`,
+ Authorization: `Bearer ${token.token}`,
},
},
params
@@ -181,7 +186,7 @@ export async function getRoomsAvailability(
return validateAvailabilityData.data
}
},
- "1m"
+ token.type === "userToken" ? "no cache" : "1m"
)
return result
diff --git a/packages/trpc/lib/routers/navigation/mypages/index.ts b/packages/trpc/lib/routers/navigation/mypages/index.ts
index a1f54718c..05f3a4bd4 100644
--- a/packages/trpc/lib/routers/navigation/mypages/index.ts
+++ b/packages/trpc/lib/routers/navigation/mypages/index.ts
@@ -2,6 +2,7 @@ import { TRPCError } from "@trpc/server"
import { z } from "zod"
import { Lang } from "@scandic-hotels/common/constants/language"
+import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import { safeProtectedProcedure } from "../../../procedures"
import { isValidSession } from "../../../utils/session"
@@ -38,13 +39,15 @@ export const myPagesNavigation = safeProtectedProcedure
})
}
- const user = await getVerifiedUser({ session: ctx.session })
- if (!user || user.error) {
+ const [user, error] = await safeTry(
+ getVerifiedUser({ token: ctx.session.token })
+ )
+ if (!user || error) {
return null
}
const [primaryLinks, secondaryLinks] = await Promise.all([
- getPrimaryLinks({ lang, userLoyalty: user.data.loyalty }),
+ getPrimaryLinks({ lang, userLoyalty: user.loyalty }),
getSecondaryLinks({ lang }),
])
diff --git a/packages/trpc/lib/routers/partners/sas/performLevelUpgrade.ts b/packages/trpc/lib/routers/partners/sas/performLevelUpgrade.ts
index 1ac59703c..cc24ca170 100644
--- a/packages/trpc/lib/routers/partners/sas/performLevelUpgrade.ts
+++ b/packages/trpc/lib/routers/partners/sas/performLevelUpgrade.ts
@@ -4,6 +4,7 @@ import { z } from "zod"
import { FriendsMembershipLevels } from "@scandic-hotels/common/constants/membershipLevels"
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
+import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import * as api from "../../../api"
import { protectedProcedure } from "../../../procedures"
@@ -31,11 +32,13 @@ export const performLevelUpgrade = protectedProcedure
return { tierMatchState: "cached" }
}
- const profile = await getVerifiedUser({ session: ctx.session })
- if (!profile || "error" in profile || !profile.data.membership) {
+ const [profile, error] = await safeTry(
+ getVerifiedUser({ token: ctx.session.token })
+ )
+ if (!profile?.membership || error) {
return { tierMatchState: "error" }
}
- const currentLevel = profile.data.membership.membershipLevel
+ const currentLevel = profile.membership.membershipLevel
sasLogger.debug("tier match started")
diff --git a/packages/trpc/lib/routers/user/query/index.ts b/packages/trpc/lib/routers/user/query/index.ts
index 19f5e72e6..755a7810c 100644
--- a/packages/trpc/lib/routers/user/query/index.ts
+++ b/packages/trpc/lib/routers/user/query/index.ts
@@ -1,8 +1,10 @@
import { createCounter } from "@scandic-hotels/common/telemetry"
+import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import { router } from "../../.."
import * as api from "../../../api"
import { Transactions } from "../../../enums/transactions"
+import { notFound } from "../../../errors"
import {
languageProtectedProcedure,
protectedProcedure,
@@ -42,30 +44,25 @@ export const userQueryRouter = router({
})
})
.query(async function getUser({ ctx }) {
- const data = await getVerifiedUser({ session: ctx.session })
-
- if (!data) {
- return null
+ const user = await ctx.getScandicUser()
+ if (!user) {
+ throw notFound()
}
- if ("error" in data && data.error) {
- return data
- }
-
- return parsedUser(data.data, !ctx.isMFA)
+ return parsedUser(user, !ctx.isMFA)
}),
getSafely: safeProtectedProcedure.query(async function getUser({ ctx }) {
if (!isValidSession(ctx.session)) {
return null
}
- const data = await getVerifiedUser({ session: ctx.session })
+ const user = await ctx.getScandicUser()
- if (!data || "error" in data) {
+ if (!user) {
return null
}
- return parsedUser(data.data, false)
+ return parsedUser(user, false)
}),
getWithExtendedPartnerData: safeProtectedProcedure.query(
async function getUser({ ctx }) {
@@ -73,60 +70,49 @@ export const userQueryRouter = router({
return null
}
- const data = await getVerifiedUser({
- session: ctx.session,
- includeExtendedPartnerData: true,
- })
+ const user = await ctx.getScandicUser()
- if (!data || "error" in data) {
+ if (!user) {
return null
}
- return parsedUser(data.data, false)
+ return parsedUser(user, false)
}
),
name: safeProtectedProcedure.query(async function ({ ctx }) {
if (!isValidSession(ctx.session)) {
return null
}
- const verifiedData = await getVerifiedUser({ session: ctx.session })
+ const user = await ctx.getScandicUser()
- if (!verifiedData || "error" in verifiedData) {
+ if (!user) {
return null
}
return {
- firstName: verifiedData.data.firstName,
- lastName: verifiedData.data.lastName,
+ firstName: user.firstName,
+ lastName: user.lastName,
}
}),
membershipLevel: protectedProcedure.query(async function ({ ctx }) {
- const verifiedData = await getVerifiedUser({ session: ctx.session })
- if (
- !verifiedData ||
- "error" in verifiedData ||
- !verifiedData.data.loyalty
- ) {
+ const user = await ctx.getScandicUser()
+ if (!user?.loyalty) {
return null
}
- const membershipLevel = getFriendsMembership(verifiedData.data.loyalty)
+ const membershipLevel = getFriendsMembership(user.loyalty)
return membershipLevel
}),
safeMembershipLevel: safeProtectedProcedure.query(async function ({ ctx }) {
if (!isValidSession(ctx.session)) {
return null
}
- const verifiedData = await getVerifiedUser({ session: ctx.session })
+ const user = await ctx.getScandicUser()
- if (
- !verifiedData ||
- "error" in verifiedData ||
- !verifiedData.data.loyalty
- ) {
+ if (!user?.loyalty) {
return null
}
- const membershipLevel = getFriendsMembership(verifiedData.data.loyalty)
+ const membershipLevel = getFriendsMembership(user.loyalty)
return membershipLevel
}),
userTrackingInfo,
@@ -327,12 +313,14 @@ export const userQueryRouter = router({
}),
membershipCards: protectedProcedure.query(async function ({ ctx }) {
- const userData = await getVerifiedUser({ session: ctx.session })
+ const [userData, error] = await safeTry(
+ getVerifiedUser({ token: ctx.session.token })
+ )
- if (!userData || "error" in userData || !userData.data.loyalty) {
+ if (!userData?.loyalty || error) {
return null
}
- return getMembershipCards(userData.data.loyalty)
+ return getMembershipCards(userData.loyalty)
}),
})
diff --git a/packages/trpc/lib/routers/user/query/userTrackingInfo.ts b/packages/trpc/lib/routers/user/query/userTrackingInfo.ts
index 4c73e2236..f56397d0f 100644
--- a/packages/trpc/lib/routers/user/query/userTrackingInfo.ts
+++ b/packages/trpc/lib/routers/user/query/userTrackingInfo.ts
@@ -47,13 +47,9 @@ async function getScandicFriendsUserTrackingData(session: Session | null) {
}
try {
- const verifiedUserData = await getVerifiedUser({ session: session })
+ const verifiedUserData = await getVerifiedUser({ token: session.token })
- if (
- !verifiedUserData ||
- "error" in verifiedUserData ||
- !verifiedUserData.data.loyalty
- ) {
+ if (!verifiedUserData || !verifiedUserData.loyalty) {
metricsUserTrackingInfo.success({
reason: "invalid user data",
data: notLoggedInUserTrackingData,
@@ -61,12 +57,12 @@ async function getScandicFriendsUserTrackingData(session: Session | null) {
return notLoggedInUserTrackingData
}
- const membership = getFriendsMembership(verifiedUserData.data.loyalty)
+ const membership = getFriendsMembership(verifiedUserData.loyalty)
const loggedInUserTrackingData: TrackingUserData = {
loginStatus: "logged in",
loginType: session.token.loginType as LoginType,
- memberId: verifiedUserData.data.profileId,
+ memberId: verifiedUserData.profileId,
membershipNumber: membership?.membershipNumber,
memberLevel: membership?.membershipLevel,
loginAction: "login success",
diff --git a/packages/trpc/lib/routers/user/utils.ts b/packages/trpc/lib/routers/user/utils.ts
index 7913e3d21..bfbbbf028 100644
--- a/packages/trpc/lib/routers/user/utils.ts
+++ b/packages/trpc/lib/routers/user/utils.ts
@@ -1,6 +1,7 @@
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
import { dt } from "@scandic-hotels/common/dt"
import { createCounter } from "@scandic-hotels/common/telemetry"
+import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import { env } from "../../../env/server"
import * as api from "../../api"
@@ -8,7 +9,6 @@ import { cache } from "../../DUPLICATED/cache"
import { creditCardsSchema } from "../../routers/user/output"
import { toApiLang } from "../../utils"
import { encrypt } from "../../utils/encryption"
-import { isValidSession } from "../../utils/session"
import { getVerifiedUser } from "./utils/getVerifiedUser"
import { type FriendTransaction, getStaysSchema, type Stay } from "./output"
@@ -16,19 +16,6 @@ import type { Lang } from "@scandic-hotels/common/constants/language"
import type { LangRoute } from "@scandic-hotels/common/constants/routes/langRoute"
import type { Session } from "next-auth"
-export async function getMembershipNumber(
- session: Session | null
-): Promise {
- if (!isValidSession(session)) return undefined
-
- const verifiedUser = await getVerifiedUser({ session })
- if (!verifiedUser || "error" in verifiedUser) {
- return undefined
- }
-
- return verifiedUser.data.membershipNumber
-}
-
export async function getPreviousStays(
accessToken: string,
limit: number = 10,
@@ -202,44 +189,45 @@ export async function updateStaysBookingUrl(
session: Session,
lang: Lang
) {
- const user = await getVerifiedUser({
- session,
- })
-
- if (user && !("error" in user)) {
- return data.map((d) => {
- const originalString =
- d.attributes.confirmationNumber.toString() + "," + user.data.lastName
- const encryptedBookingValue = encrypt(originalString)
-
- // Get base URL with fallback for ephemeral environments (like deploy previews).
- const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
-
- // Construct Booking URL.
- const bookingUrl = new URL(myStay[lang], baseUrl)
-
- // Add search parameters.
- if (encryptedBookingValue) {
- bookingUrl.searchParams.set("RefId", encryptedBookingValue)
- } else {
- bookingUrl.searchParams.set("lastName", user.data.lastName)
- bookingUrl.searchParams.set(
- "bookingId",
- d.attributes.confirmationNumber.toString()
- )
- }
-
- return {
- ...d,
- attributes: {
- ...d.attributes,
- bookingUrl: bookingUrl.toString(),
- },
- }
+ const [user, error] = await safeTry(
+ getVerifiedUser({
+ token: session.token,
})
- }
+ )
- return data
+ if (!user || error) {
+ return data
+ }
+ return data.map((d) => {
+ const originalString =
+ d.attributes.confirmationNumber.toString() + "," + user.lastName
+ const encryptedBookingValue = encrypt(originalString)
+
+ // Get base URL with fallback for ephemeral environments (like deploy previews).
+ const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
+
+ // Construct Booking URL.
+ const bookingUrl = new URL(myStay[lang], baseUrl)
+
+ // Add search parameters.
+ if (encryptedBookingValue) {
+ bookingUrl.searchParams.set("RefId", encryptedBookingValue)
+ } else {
+ bookingUrl.searchParams.set("lastName", user.lastName)
+ bookingUrl.searchParams.set(
+ "bookingId",
+ d.attributes.confirmationNumber.toString()
+ )
+ }
+
+ return {
+ ...d,
+ attributes: {
+ ...d.attributes,
+ bookingUrl: bookingUrl.toString(),
+ },
+ }
+ })
}
export const myBookingPath: LangRoute = {
diff --git a/packages/trpc/lib/routers/user/utils/getMemberShipNumber.ts b/packages/trpc/lib/routers/user/utils/getMemberShipNumber.ts
deleted file mode 100644
index 293526ac5..000000000
--- a/packages/trpc/lib/routers/user/utils/getMemberShipNumber.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { isValidSession } from "../../../utils/session"
-import { getVerifiedUser } from "./getVerifiedUser"
-
-import type { Session } from "next-auth"
-
-export async function getMembershipNumber(
- session: Session | null
-): Promise {
- if (!isValidSession(session)) return undefined
-
- const verifiedUser = await getVerifiedUser({ session })
- if (!verifiedUser || "error" in verifiedUser) {
- return undefined
- }
-
- return verifiedUser.data.membershipNumber
-}
diff --git a/packages/trpc/lib/routers/user/utils/getVerifiedUser.ts b/packages/trpc/lib/routers/user/utils/getVerifiedUser.ts
index db28811cd..46f7b343e 100644
--- a/packages/trpc/lib/routers/user/utils/getVerifiedUser.ts
+++ b/packages/trpc/lib/routers/user/utils/getVerifiedUser.ts
@@ -2,16 +2,19 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import * as api from "../../../api"
import { cache } from "../../../DUPLICATED/cache"
+import {
+ internalServerError,
+ serverErrorByStatus,
+ sessionExpiredError,
+} from "../../../errors"
import { getUserSchema } from "../output"
-import type { Session } from "next-auth"
-
export const getVerifiedUser = cache(
async ({
- session,
+ token,
includeExtendedPartnerData,
}: {
- session: Session
+ token: { expires_at?: number; access_token: string }
includeExtendedPartnerData?: boolean
}) => {
const getVerifiedUserCounter = createCounter("user", "getVerifiedUser")
@@ -20,16 +23,16 @@ export const getVerifiedUser = cache(
metricsGetVerifiedUser.start()
const now = Date.now()
- if (session.token.expires_at && session.token.expires_at < now) {
+ if (token.expires_at && token.expires_at < now) {
metricsGetVerifiedUser.dataError(`Token expired`)
- return { error: true, cause: "token_expired" } as const
+ throw sessionExpiredError()
}
const apiResponse = await api.get(
api.endpoints.v2.Profile.profile,
{
headers: {
- Authorization: `Bearer ${session.token.access_token}`,
+ Authorization: `Bearer ${token.access_token}`,
},
},
includeExtendedPartnerData
@@ -40,19 +43,7 @@ export const getVerifiedUser = cache(
if (!apiResponse.ok) {
await metricsGetVerifiedUser.httpError(apiResponse)
- if (apiResponse.status === 401) {
- return { error: true, cause: "unauthorized" } as const
- } else if (apiResponse.status === 403) {
- return { error: true, cause: "forbidden" } as const
- } else if (apiResponse.status === 404) {
- return { error: true, cause: "notfound" } as const
- }
-
- return {
- error: true,
- cause: "unknown",
- status: apiResponse.status,
- } as const
+ throw serverErrorByStatus(apiResponse.status, apiResponse)
}
const apiJson = await apiResponse.json()
@@ -63,17 +54,17 @@ export const getVerifiedUser = cache(
data: apiJson,
}
)
- return null
+ throw internalServerError("Missing data attributes in API response")
}
const verifiedData = getUserSchema.safeParse(apiJson)
if (!verifiedData.success) {
metricsGetVerifiedUser.validationError(verifiedData.error)
- return null
+ throw verifiedData.error
}
metricsGetVerifiedUser.success()
- return verifiedData
+ return verifiedData.data
}
)
diff --git a/packages/trpc/lib/routers/user/utils/updateStaysBookingUrl.ts b/packages/trpc/lib/routers/user/utils/updateStaysBookingUrl.ts
index 61047c105..7566539b4 100644
--- a/packages/trpc/lib/routers/user/utils/updateStaysBookingUrl.ts
+++ b/packages/trpc/lib/routers/user/utils/updateStaysBookingUrl.ts
@@ -1,6 +1,7 @@
import "server-only"
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
+import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import { env } from "../../../../env/server"
import { encrypt } from "../../../utils/encryption"
@@ -28,42 +29,47 @@ export async function updateStaysBookingUrl(
session: Session,
lang: Lang
) {
- const user = await getVerifiedUser({
- session,
- })
-
- if (user && !("error" in user)) {
- return data.map((d) => {
- const originalString =
- d.attributes.confirmationNumber.toString() + "," + user.data.lastName
- const encryptedBookingValue = encrypt(originalString)
-
- // Get base URL with fallback for ephemeral environments (like deploy previews).
- const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
-
- // Construct Booking URL.
- const bookingUrl = new URL(myStay[lang], baseUrl)
-
- // Add search parameters.
- if (encryptedBookingValue) {
- bookingUrl.searchParams.set("RefId", encryptedBookingValue)
- } else {
- bookingUrl.searchParams.set("lastName", user.data.lastName)
- bookingUrl.searchParams.set(
- "bookingId",
- d.attributes.confirmationNumber.toString()
- )
- }
-
- return {
- ...d,
- attributes: {
- ...d.attributes,
- bookingUrl: bookingUrl.toString(),
- },
- }
+ const [user, error] = await safeTry(
+ getVerifiedUser({
+ token: {
+ access_token: session.token.access_token,
+ expires_at: session.token.expires_at ?? 0,
+ },
})
+ )
+
+ if (error || !user) {
+ return data
}
- return data
+ return data.map((d) => {
+ const originalString =
+ d.attributes.confirmationNumber.toString() + "," + user.lastName
+ const encryptedBookingValue = encrypt(originalString)
+
+ // Get base URL with fallback for ephemeral environments (like deploy previews).
+ const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
+
+ // Construct Booking URL.
+ const bookingUrl = new URL(myStay[lang], baseUrl)
+
+ // Add search parameters.
+ if (encryptedBookingValue) {
+ bookingUrl.searchParams.set("RefId", encryptedBookingValue)
+ } else {
+ bookingUrl.searchParams.set("lastName", user.lastName)
+ bookingUrl.searchParams.set(
+ "bookingId",
+ d.attributes.confirmationNumber.toString()
+ )
+ }
+
+ return {
+ ...d,
+ attributes: {
+ ...d.attributes,
+ bookingUrl: bookingUrl.toString(),
+ },
+ }
+ })
}
diff --git a/packages/trpc/lib/utils/getRedemptionTokenSafely.ts b/packages/trpc/lib/utils/getRedemptionTokenSafely.ts
deleted file mode 100644
index 9f3e9c626..000000000
--- a/packages/trpc/lib/utils/getRedemptionTokenSafely.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { isValidSession } from "./session"
-
-import type { Session } from "next-auth"
-
-export function getRedemptionTokenSafely(
- session: Session,
- serviceToken: string
-): string | undefined {
- if (!isValidSession(session)) return undefined
-
- // ToDo- Get Curity based token when linked user is logged in
- // const token =
- // session.token.loginType === "eurobonus"
- // ? session.token.curity_access_token ?? serviceToken
- // : session.token.access_token
-
- const token =
- session.token.loginType === "eurobonus"
- ? serviceToken
- : session.token.access_token
-
- return token
-}
diff --git a/packages/trpc/lib/utils/getUserPointsBalance.ts b/packages/trpc/lib/utils/getUserPointsBalance.ts
deleted file mode 100644
index a01a47f57..000000000
--- a/packages/trpc/lib/utils/getUserPointsBalance.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { getEuroBonusProfileData } from "../routers/partners/sas/getEuroBonusProfile"
-import { getVerifiedUser } from "../routers/user/utils/getVerifiedUser"
-import { isValidSession } from "./session"
-
-import type { Session } from "next-auth"
-
-export async function getUserPointsBalance(
- session: Session | null
-): Promise {
- if (!isValidSession(session)) return undefined
-
- const verifiedUser =
- session.token.loginType === "eurobonus"
- ? await getEuroBonusProfileSafely(session)
- : await getVerifiedUser({ session })
-
- if (!verifiedUser || "error" in verifiedUser) {
- return undefined
- }
-
- const points =
- "points" in verifiedUser
- ? verifiedUser.points.total
- : verifiedUser.data.membership?.currentPoints
-
- return points ?? 0
-}
-
-async function getEuroBonusProfileSafely(session: Session) {
- try {
- return await getEuroBonusProfileData({
- accessToken: session.token.access_token,
- loginType: session.token.loginType,
- })
- } catch (_error) {
- return undefined
- }
-}