Merged in feat/SW-3549-pass-scandic-token (pull request #2989)
Feat/SW-3549 pass scandic token * WIP pass scandic token * pass scandic token when booking * Merge branch 'master' of bitbucket.org:scandic-swap/web into feat/SW-3549-pass-scandic-token * pass user token when doing availability search * undo changes * merge * Merged in fix/sw-3551-rsc-bookingflowconfig (pull request #2988) fix(SW-3551): Fix issue with BookingConfigProvider in RSC * wip move config to pages * Move config providing to pages * Merged in fix/update-promo-error-modal-text (pull request #2990) fix: update promo error modal text * fix: update promo error modal text Approved-by: Emma Zettervall * Merged in fix/sw-3514-missing-membership-input-for-multiroom (pull request #2991) fix(SW-3514): Show join Scandic Friends card for SAS multiroom * Show join card for room 2+ Approved-by: Hrishikesh Vaipurkar * Merged in feat/lokalise-rebuild (pull request #2993) Feat/lokalise rebuild * chore(lokalise): update translation ids * chore(lokalise): easier to switch between projects * chore(lokalise): update translation ids * . * . * . * . * . * . * chore(lokalise): update translation ids * chore(lokalise): update translation ids * . * . * . * chore(lokalise): update translation ids * chore(lokalise): update translation ids * . * . * chore(lokalise): update translation ids * chore(lokalise): update translation ids * chore(lokalise): new translations * merge * switch to errors for missing id's * merge * sync translations Approved-by: Linus Flood * Merged in feat/SW-3552-logout-from-social-session-when- (pull request #2994) feat(SW-3552): Removed scandic session on logout Approved-by: Joakim Jäderberg * merge * replace getRedemptionTokenSafely() with context based instead * Refactor user verification and error handling across multiple components; implement safeTry utility for safer async calls * Refactor user verification and error handling across multiple components; implement safeTry utility for safer async calls * merge * Merge branch 'master' of bitbucket.org:scandic-swap/web into feat/SW-3549-pass-scandic-token * add booking scope remove unused getMembershipNumber() Approved-by: Anton Gunnarsson Approved-by: Hrishikesh Vaipurkar
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
<p>
|
||||
@@ -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 (
|
||||
<p>
|
||||
{intl.formatMessage({
|
||||
@@ -52,7 +56,15 @@ export default async function Layout(
|
||||
})}
|
||||
</p>
|
||||
)
|
||||
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 (
|
||||
<p>
|
||||
{intl.formatMessage({
|
||||
@@ -61,10 +73,6 @@ export default async function Layout(
|
||||
})}
|
||||
</p>
|
||||
)
|
||||
default:
|
||||
const u: never = user
|
||||
logger.error("[webview:page] unhandled user loading error", u)
|
||||
Sentry.captureMessage("[webview:page] unhandled user loading error", u)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
<p>
|
||||
{intl.formatMessage({
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<Session | null> {
|
||||
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,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ export type SafeTryResult<T> = Promise<
|
||||
|
||||
export async function safeTry<T>(func: Promise<T>): SafeTryResult<T> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ReturnType<typeof getVerifiedUser>>
|
||||
type CreateContextOptions = {
|
||||
auth: () => Promise<Session | null>
|
||||
lang: Lang
|
||||
@@ -18,6 +21,9 @@ type CreateContextOptions = {
|
||||
webToken?: string
|
||||
contentType?: string
|
||||
app: "scandic-web" | "partner-sas"
|
||||
getScandicUserToken: () => Promise<string | null>
|
||||
getUserPointsBalance: () => Promise<number | null>
|
||||
getScandicUser: () => Promise<ScandicUser | null>
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -43,11 +43,18 @@ export const getDestinationsAutoCompleteRoute = safeProtectedServiceProcedure
|
||||
.input(destinationsAutoCompleteInputSchema)
|
||||
.query(async ({ ctx, input }): Promise<DestinationsAutoCompleteOutput> => {
|
||||
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 (
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<Context>({
|
||||
ctx: {
|
||||
token: token,
|
||||
userToken,
|
||||
userPoints: pointsValue,
|
||||
},
|
||||
input,
|
||||
@@ -52,19 +56,20 @@ export const enterDetails = safeProtectedServiceProcedure
|
||||
}
|
||||
throw unauthorizedError()
|
||||
}
|
||||
return next({
|
||||
return next<Context>({
|
||||
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,
|
||||
|
||||
@@ -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<Context>({
|
||||
ctx: {
|
||||
userToken,
|
||||
userPoints: pointsValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
throw unauthorizedError()
|
||||
}
|
||||
return next({
|
||||
|
||||
return next<Context>({
|
||||
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
|
||||
)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Context>({
|
||||
ctx: {
|
||||
userToken,
|
||||
userPoints: pointsValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
throw unauthorizedError()
|
||||
}
|
||||
return next({
|
||||
|
||||
return next<Context>({
|
||||
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,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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<Context>({
|
||||
ctx: {
|
||||
userToken,
|
||||
userPoints: pointsValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
throw unauthorizedError()
|
||||
}
|
||||
return next({
|
||||
|
||||
return next<Context>({
|
||||
ctx: {
|
||||
token: ctx.serviceToken,
|
||||
userToken,
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -54,7 +61,7 @@ export const myStay = safeProtectedServiceProcedure
|
||||
},
|
||||
lang: input.lang,
|
||||
},
|
||||
ctx.token,
|
||||
ctx.userToken,
|
||||
ctx.serviceToken,
|
||||
ctx.userPoints
|
||||
)
|
||||
|
||||
@@ -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<Context>({
|
||||
ctx: {
|
||||
userToken,
|
||||
userPoints: pointsValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
throw unauthorizedError()
|
||||
}
|
||||
return next({
|
||||
|
||||
return next<Context>({
|
||||
ctx: {
|
||||
token: ctx.serviceToken,
|
||||
userToken,
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -52,7 +59,7 @@ export const room = safeProtectedServiceProcedure
|
||||
},
|
||||
lang: input.lang,
|
||||
},
|
||||
ctx.token,
|
||||
ctx.userToken,
|
||||
ctx.serviceToken,
|
||||
ctx.userPoints
|
||||
)
|
||||
|
||||
@@ -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<Context>({
|
||||
ctx: {
|
||||
userToken,
|
||||
userPoints: pointsValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
throw unauthorizedError()
|
||||
}
|
||||
return next({
|
||||
|
||||
return next<Context>({
|
||||
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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<string, string | number> = {
|
||||
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<string, string | number>
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }),
|
||||
])
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<string | undefined> {
|
||||
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 = {
|
||||
|
||||
@@ -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<string | undefined> {
|
||||
if (!isValidSession(session)) return undefined
|
||||
|
||||
const verifiedUser = await getVerifiedUser({ session })
|
||||
if (!verifiedUser || "error" in verifiedUser) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return verifiedUser.data.membershipNumber
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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<number | undefined> {
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user