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,
|
client_secret: env.CURITY_CLIENT_SECRET_USER,
|
||||||
redirect_uri: new URL("/api/web/auth/callback/curity", env.PUBLIC_URL).href,
|
redirect_uri: new URL("/api/web/auth/callback/curity", env.PUBLIC_URL).href,
|
||||||
acr_values: "urn:com:scandichotels:sas-eb",
|
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",
|
response_type: "code",
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { headers } from "next/headers"
|
import { headers } from "next/headers"
|
||||||
|
|
||||||
|
import { dt } from "@scandic-hotels/common/dt"
|
||||||
import { createContext } from "@scandic-hotels/trpc/context"
|
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 {
|
import {
|
||||||
appServerClient,
|
appServerClient,
|
||||||
configureServerClient,
|
configureServerClient,
|
||||||
} from "@scandic-hotels/trpc/serverClient"
|
} from "@scandic-hotels/trpc/serverClient"
|
||||||
|
|
||||||
import { auth } from "@/auth"
|
import { auth } from "@/auth"
|
||||||
|
import { getSession } from "@/auth/scandic/session"
|
||||||
|
|
||||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
|
|
||||||
@@ -24,6 +28,34 @@ export async function createAppContext() {
|
|||||||
const session = await auth()
|
const session = await auth()
|
||||||
return session
|
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
|
return ctx
|
||||||
|
|||||||
@@ -105,17 +105,17 @@ export const editProfile = protectedServerActionProcedure
|
|||||||
|
|
||||||
if (typedKey === "address") {
|
if (typedKey === "address") {
|
||||||
if (
|
if (
|
||||||
(payload.data.address.city === profile.address.city ||
|
(payload.data.address.city === profile.address?.city ||
|
||||||
(!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 ||
|
||||||
(!payload.data.address.countryCode &&
|
(!payload.data.address.countryCode &&
|
||||||
!profile.address.countryCode)) &&
|
!profile.address?.countryCode)) &&
|
||||||
(payload.data.address.streetAddress ===
|
(payload.data.address.streetAddress ===
|
||||||
profile.address.streetAddress ||
|
profile.address?.streetAddress ||
|
||||||
(!payload.data.address.streetAddress &&
|
(!payload.data.address.streetAddress &&
|
||||||
!profile.address.streetAddress)) &&
|
!profile.address?.streetAddress)) &&
|
||||||
(payload.data.address.zipCode === profile.address.zipCode ||
|
(payload.data.address.zipCode === profile.address?.zipCode ||
|
||||||
(!payload.data.address.zipCode && !profile.address.zipCode))
|
(!payload.data.address.zipCode && !profile.address?.zipCode))
|
||||||
) {
|
) {
|
||||||
// untouched - noop
|
// untouched - noop
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default function OneTimePasswordForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (requestOtp.isError) {
|
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({
|
const title = intl.formatMessage({
|
||||||
id: "linkEuroBonusAccount.oneTimePasswordGenericError",
|
id: "linkEuroBonusAccount.oneTimePasswordGenericError",
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import * as Sentry from "@sentry/nextjs"
|
import * as Sentry from "@sentry/nextjs"
|
||||||
|
import { TRPCError } from "@trpc/server"
|
||||||
import { headers } from "next/headers"
|
import { headers } from "next/headers"
|
||||||
import { redirect } from "next/navigation"
|
import { redirect } from "next/navigation"
|
||||||
|
|
||||||
import { logger } from "@scandic-hotels/common/logger"
|
import { logger } from "@scandic-hotels/common/logger"
|
||||||
|
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
|
||||||
|
|
||||||
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
@@ -17,9 +19,9 @@ export default async function Layout(
|
|||||||
|
|
||||||
const { children } = props
|
const { children } = props
|
||||||
const intl = await getIntl()
|
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`)
|
logger.debug(`[webview:page] unable to load user`)
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
@@ -31,19 +33,21 @@ export default async function Layout(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("error" in user) {
|
const notValidSession =
|
||||||
switch (user.cause) {
|
error instanceof TRPCError &&
|
||||||
case "unauthorized": // fall through
|
(error.code === "UNAUTHORIZED" || error.code === "FORBIDDEN")
|
||||||
case "forbidden": // fall through
|
|
||||||
case "token_expired":
|
if (notValidSession) {
|
||||||
const headersList = await headers()
|
const headersList = await headers()
|
||||||
const returnURL = `/${params.lang}/webview${headersList.get("x-pathname")!}`
|
const returnURL = `/${params.lang}/webview${headersList.get("x-pathname")!}`
|
||||||
const redirectURL = `/${params.lang}/webview/refresh?returnUrl=${encodeURIComponent(returnURL)}`
|
const redirectURL = `/${params.lang}/webview/refresh?returnUrl=${encodeURIComponent(returnURL)}`
|
||||||
logger.debug(
|
logger.debug(`[webview:page] user error, redirecting to: ${redirectURL}`)
|
||||||
`[webview:page] user error, redirecting to: ${redirectURL}`
|
redirect(redirectURL)
|
||||||
)
|
}
|
||||||
redirect(redirectURL)
|
|
||||||
case "notfound":
|
if (error instanceof TRPCError) {
|
||||||
|
switch (error.code) {
|
||||||
|
case "NOT_FOUND":
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
@@ -52,7 +56,15 @@ export default async function Layout(
|
|||||||
})}
|
})}
|
||||||
</p>
|
</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 (
|
return (
|
||||||
<p>
|
<p>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
@@ -61,10 +73,6 @@ export default async function Layout(
|
|||||||
})}
|
})}
|
||||||
</p>
|
</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 lang = await getLang()
|
||||||
|
|
||||||
const addressParts = []
|
const addressParts = []
|
||||||
if (user.address.streetAddress) {
|
if (user.address?.streetAddress) {
|
||||||
addressParts.push(user.address.streetAddress)
|
addressParts.push(user.address.streetAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.address.city) {
|
if (user.address?.city) {
|
||||||
addressParts.push(user.address.city)
|
addressParts.push(user.address.city)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +43,8 @@ export default async function Profile() {
|
|||||||
region: new Intl.DisplayNames([lang], { type: "region" }),
|
region: new Intl.DisplayNames([lang], { type: "region" }),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.address.country) {
|
if (user.address?.country) {
|
||||||
const countryCode = isValidCountry(user.address.country)
|
const countryCode = isValidCountry(user.address?.country)
|
||||||
? countriesMap[user.address.country]
|
? countriesMap[user.address.country]
|
||||||
: null
|
: null
|
||||||
const localizedCountry = countryCode
|
const localizedCountry = countryCode
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import { TRPCError } from "@trpc/server"
|
||||||
import { headers } from "next/headers"
|
import { headers } from "next/headers"
|
||||||
import { redirect } from "next/navigation"
|
import { redirect } from "next/navigation"
|
||||||
|
|
||||||
import { overview } from "@scandic-hotels/common/constants/routes/myPages"
|
import { overview } from "@scandic-hotels/common/constants/routes/myPages"
|
||||||
import { logger } from "@scandic-hotels/common/logger"
|
import { logger } from "@scandic-hotels/common/logger"
|
||||||
|
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
|
||||||
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
|
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
|
||||||
|
|
||||||
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
||||||
@@ -33,29 +35,9 @@ export async function ProtectedLayout({ children }: React.PropsWithChildren) {
|
|||||||
redirect(redirectURL)
|
redirect(redirectURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await getProfile()
|
const [user, error] = await safeTry(getProfile())
|
||||||
|
|
||||||
if (user && "error" in user) {
|
if (error instanceof TRPCError && error.code === "INTERNAL_SERVER_ERROR") {
|
||||||
// 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
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
|
|||||||
@@ -16,9 +16,12 @@ export function UserExists() {
|
|||||||
const isUserLoggedIn = isValidClientSession(session)
|
const isUserLoggedIn = isValidClientSession(session)
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
|
|
||||||
const { data, isLoading: isLoadingUser } = trpc.user.get.useQuery(undefined, {
|
const { isLoading: isLoadingUser, error } = trpc.user.get.useQuery(
|
||||||
enabled: isUserLoggedIn,
|
undefined,
|
||||||
})
|
{
|
||||||
|
enabled: isUserLoggedIn,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (!isUserLoggedIn) {
|
if (!isUserLoggedIn) {
|
||||||
return null
|
return null
|
||||||
@@ -28,16 +31,12 @@ export function UserExists() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data && "error" in data && data.error) {
|
switch (error?.data?.code) {
|
||||||
switch (data.cause) {
|
case "NOT_FOUND":
|
||||||
case "notfound":
|
redirect(
|
||||||
redirect(
|
`${logoutSafely[lang]}?redirectTo=${encodeURIComponent(userNotFound[lang])}`
|
||||||
`${logoutSafely[lang]}?redirectTo=${encodeURIComponent(userNotFound[lang])}`
|
)
|
||||||
)
|
default:
|
||||||
default:
|
return null
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Lang } from "@scandic-hotels/common/constants/language"
|
|||||||
import { login } from "@scandic-hotels/common/constants/routes/handleAuth"
|
import { login } from "@scandic-hotels/common/constants/routes/handleAuth"
|
||||||
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
||||||
import { createContext } from "@scandic-hotels/trpc/context"
|
import { createContext } from "@scandic-hotels/trpc/context"
|
||||||
|
import { getVerifiedUser } from "@scandic-hotels/trpc/routers/user/utils/getVerifiedUser"
|
||||||
import {
|
import {
|
||||||
appServerClient,
|
appServerClient,
|
||||||
configureServerClient,
|
configureServerClient,
|
||||||
@@ -23,6 +24,21 @@ export async function createAppContext() {
|
|||||||
const webviewTokenCookie = cookie.get("webviewToken")
|
const webviewTokenCookie = cookie.get("webviewToken")
|
||||||
const loginType = headersList.get("loginType")
|
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({
|
const ctx = createContext({
|
||||||
app: "scandic-web",
|
app: "scandic-web",
|
||||||
lang: headersList.get("x-lang") as Lang,
|
lang: headersList.get("x-lang") as Lang,
|
||||||
@@ -31,19 +47,38 @@ export async function createAppContext() {
|
|||||||
url: headersList.get("x-url")!,
|
url: headersList.get("x-url")!,
|
||||||
webToken: webviewTokenCookie?.value,
|
webToken: webviewTokenCookie?.value,
|
||||||
contentType: headersList.get("x-contenttype")!,
|
contentType: headersList.get("x-contenttype")!,
|
||||||
auth: async () => {
|
auth: async () => await getUserSession(),
|
||||||
const session = await auth()
|
getScandicUserToken: async () => {
|
||||||
const webToken = webviewTokenCookie?.value
|
const session = await getUserSession()
|
||||||
if (!session?.token && !webToken) {
|
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 null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return user.membership?.currentPoints ?? 0
|
||||||
session ||
|
},
|
||||||
({
|
getScandicUser: async () => {
|
||||||
token: { access_token: webToken, loginType },
|
const session = await getUserSession()
|
||||||
} as Session)
|
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> {
|
export async function safeTry<T>(func: Promise<T>): SafeTryResult<T> {
|
||||||
try {
|
try {
|
||||||
return [await func, undefined]
|
return [await func, undefined] as const
|
||||||
} catch (err) {
|
} 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 { User } from "next-auth"
|
||||||
import type { JWT } from "next-auth/jwt"
|
import type { JWT } from "next-auth/jwt"
|
||||||
|
|
||||||
|
import type { getVerifiedUser } from "./routers/user/utils/getVerifiedUser"
|
||||||
|
|
||||||
type Session = {
|
type Session = {
|
||||||
token: JWT
|
token: JWT
|
||||||
expires: string
|
expires: string
|
||||||
@@ -9,6 +11,7 @@ type Session = {
|
|||||||
error?: "RefreshAccessTokenError"
|
error?: "RefreshAccessTokenError"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ScandicUser = Awaited<ReturnType<typeof getVerifiedUser>>
|
||||||
type CreateContextOptions = {
|
type CreateContextOptions = {
|
||||||
auth: () => Promise<Session | null>
|
auth: () => Promise<Session | null>
|
||||||
lang: Lang
|
lang: Lang
|
||||||
@@ -18,6 +21,9 @@ type CreateContextOptions = {
|
|||||||
webToken?: string
|
webToken?: string
|
||||||
contentType?: string
|
contentType?: string
|
||||||
app: "scandic-web" | "partner-sas"
|
app: "scandic-web" | "partner-sas"
|
||||||
|
getScandicUserToken: () => Promise<string | null>
|
||||||
|
getUserPointsBalance: () => Promise<number | null>
|
||||||
|
getScandicUser: () => Promise<ScandicUser | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createContext(opts: CreateContextOptions) {
|
export function createContext(opts: CreateContextOptions) {
|
||||||
@@ -30,6 +36,9 @@ export function createContext(opts: CreateContextOptions) {
|
|||||||
webToken: opts.webToken,
|
webToken: opts.webToken,
|
||||||
contentType: opts.contentType,
|
contentType: opts.contentType,
|
||||||
app: opts.app,
|
app: opts.app,
|
||||||
|
getScandicUserToken: opts.getScandicUserToken,
|
||||||
|
getUserPointsBalance: opts.getUserPointsBalance,
|
||||||
|
getScandicUser: opts.getScandicUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,7 @@ const t = initTRPC
|
|||||||
...shape,
|
...shape,
|
||||||
data: {
|
data: {
|
||||||
...shape.data,
|
...shape.data,
|
||||||
cause:
|
cause: error.cause instanceof ZodError ? undefined : error.cause,
|
||||||
error.cause instanceof ZodError
|
|
||||||
? undefined
|
|
||||||
: JSON.parse(JSON.stringify(error.cause)),
|
|
||||||
zodError:
|
zodError:
|
||||||
error.cause instanceof ZodError ? error.cause.flatten() : null,
|
error.cause instanceof ZodError ? error.cause.flatten() : null,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ export const serviceProcedure = baseProcedure.use(async (opts) => {
|
|||||||
if (!access_token) {
|
if (!access_token) {
|
||||||
throw internalServerError(`[serviceProcedure] No service token`)
|
throw internalServerError(`[serviceProcedure] No service token`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return opts.next({
|
return opts.next({
|
||||||
ctx: {
|
ctx: {
|
||||||
serviceToken: access_token,
|
serviceToken: access_token,
|
||||||
|
|||||||
@@ -43,11 +43,18 @@ export const getDestinationsAutoCompleteRoute = safeProtectedServiceProcedure
|
|||||||
.input(destinationsAutoCompleteInputSchema)
|
.input(destinationsAutoCompleteInputSchema)
|
||||||
.query(async ({ ctx, input }): Promise<DestinationsAutoCompleteOutput> => {
|
.query(async ({ ctx, input }): Promise<DestinationsAutoCompleteOutput> => {
|
||||||
const lang = input.lang || ctx.lang
|
const lang = input.lang || ctx.lang
|
||||||
const locations: AutoCompleteLocation[] =
|
const [locations, error] = await safeTry(
|
||||||
await getAutoCompleteDestinationsData({
|
getAutoCompleteDestinationsData({
|
||||||
lang,
|
lang,
|
||||||
serviceToken: ctx.serviceToken,
|
serviceToken: ctx.serviceToken,
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
if (error || !locations) {
|
||||||
|
throw new Error("Unable to fetch autocomplete destinations data", {
|
||||||
|
cause: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const hits = filterAndCategorizeAutoComplete({
|
const hits = filterAndCategorizeAutoComplete({
|
||||||
locations: locations,
|
locations: locations,
|
||||||
@@ -115,17 +122,31 @@ export async function getAutoCompleteDestinationsData({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const countryNames = countries.data.map((country) => country.name)
|
const countryNames = countries.data.map((country) => country.name)
|
||||||
const citiesByCountry = await getCitiesByCountry({
|
const [citiesByCountry, citiesByCountryError] = await safeTry(
|
||||||
countries: countryNames,
|
getCitiesByCountry({
|
||||||
serviceToken: serviceToken,
|
countries: countryNames,
|
||||||
lang,
|
serviceToken: serviceToken,
|
||||||
})
|
lang,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
const locations = await getLocations({
|
if (citiesByCountryError || !citiesByCountry) {
|
||||||
lang: lang,
|
autoCompleteLogger.error("Unable to fetch cities by country")
|
||||||
serviceToken: serviceToken,
|
throw new Error("Unable to fetch cities by country")
|
||||||
citiesByCountry: citiesByCountry,
|
}
|
||||||
})
|
|
||||||
|
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) => {
|
const activeLocations = locations.filter((location) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,18 +5,12 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
|
|||||||
import * as api from "../../../../api"
|
import * as api from "../../../../api"
|
||||||
import { safeProtectedServiceProcedure } from "../../../../procedures"
|
import { safeProtectedServiceProcedure } from "../../../../procedures"
|
||||||
import { encrypt } from "../../../../utils/encryption"
|
import { encrypt } from "../../../../utils/encryption"
|
||||||
import { isValidSession } from "../../../../utils/session"
|
|
||||||
import { getMembershipNumber } from "../../../user/utils"
|
|
||||||
import { isPartnerLoggedInUser } from "../../utils"
|
|
||||||
import { createBookingInput, createBookingSchema } from "./schema"
|
import { createBookingInput, createBookingSchema } from "./schema"
|
||||||
|
|
||||||
export const create = safeProtectedServiceProcedure
|
export const create = safeProtectedServiceProcedure
|
||||||
.input(createBookingInput)
|
.input(createBookingInput)
|
||||||
.use(async ({ ctx, next }) => {
|
.use(async ({ ctx, next }) => {
|
||||||
const token =
|
const token = await ctx.getScandicUserToken()
|
||||||
isValidSession(ctx.session) && !isPartnerLoggedInUser(ctx.session)
|
|
||||||
? ctx.session.token.access_token
|
|
||||||
: ctx.serviceToken
|
|
||||||
|
|
||||||
return next({
|
return next({
|
||||||
ctx: {
|
ctx: {
|
||||||
@@ -29,8 +23,9 @@ export const create = safeProtectedServiceProcedure
|
|||||||
const { rooms, ...loggableInput } = inputWithoutLang
|
const { rooms, ...loggableInput } = inputWithoutLang
|
||||||
|
|
||||||
const createBookingCounter = createCounter("trpc.booking", "create")
|
const createBookingCounter = createCounter("trpc.booking", "create")
|
||||||
|
const user = await ctx.getScandicUser()
|
||||||
const metricsCreateBooking = createBookingCounter.init({
|
const metricsCreateBooking = createBookingCounter.init({
|
||||||
membershipNumber: await getMembershipNumber(ctx.session),
|
membershipNumber: user?.membershipNumber,
|
||||||
language,
|
language,
|
||||||
...loggableInput,
|
...loggableInput,
|
||||||
rooms: inputWithoutLang.rooms.map(({ guest, ...room }) => {
|
rooms: inputWithoutLang.rooms.map(({ guest, ...room }) => {
|
||||||
@@ -40,9 +35,8 @@ export const create = safeProtectedServiceProcedure
|
|||||||
})
|
})
|
||||||
|
|
||||||
metricsCreateBooking.start()
|
metricsCreateBooking.start()
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
Authorization: `Bearer ${ctx.token}`,
|
Authorization: `Bearer ${ctx.token || ctx.serviceToken}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiResponse = await api.post(
|
const apiResponse = await api.post(
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import { SEARCH_TYPE_REDEMPTION } from "../../../constants/booking"
|
|||||||
import { AvailabilityEnum } from "../../../enums/selectHotel"
|
import { AvailabilityEnum } from "../../../enums/selectHotel"
|
||||||
import { unauthorizedError } from "../../../errors"
|
import { unauthorizedError } from "../../../errors"
|
||||||
import { safeProtectedServiceProcedure } from "../../../procedures"
|
import { safeProtectedServiceProcedure } from "../../../procedures"
|
||||||
import { getRedemptionTokenSafely } from "../../../utils/getRedemptionTokenSafely"
|
import { isValidSession } from "../../../utils/session"
|
||||||
import { getUserPointsBalance } from "../../../utils/getUserPointsBalance"
|
|
||||||
import { baseBookingSchema, baseRoomSchema, selectedRoomSchema } from "../input"
|
import { baseBookingSchema, baseRoomSchema, selectedRoomSchema } from "../input"
|
||||||
import { getHotel } from "../services/getHotel"
|
import { getHotel } from "../services/getHotel"
|
||||||
import { getRoomsAvailability } from "../services/getRoomsAvailability"
|
import { getRoomsAvailability } from "../services/getRoomsAvailability"
|
||||||
@@ -32,18 +31,23 @@ export const enterDetailsRoomsAvailabilityInputSchema = z.object({
|
|||||||
lang: z.nativeEnum(Lang),
|
lang: z.nativeEnum(Lang),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
userToken: string | null
|
||||||
|
userPoints?: number
|
||||||
|
}
|
||||||
|
|
||||||
const logger = createLogger("trpc:availability:enterDetails")
|
const logger = createLogger("trpc:availability:enterDetails")
|
||||||
export const enterDetails = safeProtectedServiceProcedure
|
export const enterDetails = safeProtectedServiceProcedure
|
||||||
.input(enterDetailsRoomsAvailabilityInputSchema)
|
.input(enterDetailsRoomsAvailabilityInputSchema)
|
||||||
.use(async ({ ctx, input, next }) => {
|
.use(async ({ ctx, input, next }) => {
|
||||||
|
const userToken = await ctx.getScandicUserToken()
|
||||||
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
|
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
|
||||||
if (ctx.session?.token.access_token) {
|
if (isValidSession(ctx.session)) {
|
||||||
const pointsValue = await getUserPointsBalance(ctx.session)
|
const pointsValue = await ctx.getUserPointsBalance()
|
||||||
const token = getRedemptionTokenSafely(ctx.session, ctx.serviceToken)
|
if (pointsValue && userToken) {
|
||||||
if (pointsValue !== undefined && token) {
|
return next<Context>({
|
||||||
return next({
|
|
||||||
ctx: {
|
ctx: {
|
||||||
token: token,
|
userToken,
|
||||||
userPoints: pointsValue,
|
userPoints: pointsValue,
|
||||||
},
|
},
|
||||||
input,
|
input,
|
||||||
@@ -52,19 +56,20 @@ export const enterDetails = safeProtectedServiceProcedure
|
|||||||
}
|
}
|
||||||
throw unauthorizedError()
|
throw unauthorizedError()
|
||||||
}
|
}
|
||||||
return next({
|
return next<Context>({
|
||||||
ctx: {
|
ctx: {
|
||||||
token: ctx.serviceToken,
|
userToken,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.query(async function ({ ctx, input }) {
|
.query(async function ({ ctx, input }) {
|
||||||
const availability = await getRoomsAvailability(
|
const availability = await getRoomsAvailability(
|
||||||
input,
|
input,
|
||||||
ctx.token,
|
ctx.userToken,
|
||||||
ctx.serviceToken,
|
ctx.serviceToken,
|
||||||
ctx.userPoints
|
ctx.userPoints
|
||||||
)
|
)
|
||||||
|
|
||||||
const hotelData = await getHotel(
|
const hotelData = await getHotel(
|
||||||
{
|
{
|
||||||
hotelId: input.booking.hotelId,
|
hotelId: input.booking.hotelId,
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import { getCacheClient } from "@scandic-hotels/common/dataCache"
|
|||||||
import { env } from "../../../../env/server"
|
import { env } from "../../../../env/server"
|
||||||
import { unauthorizedError } from "../../../errors"
|
import { unauthorizedError } from "../../../errors"
|
||||||
import { safeProtectedServiceProcedure } from "../../../procedures"
|
import { safeProtectedServiceProcedure } from "../../../procedures"
|
||||||
import { toApiLang } from "../../../utils"
|
import { isValidSession } from "../../../utils/session"
|
||||||
import { getRedemptionTokenSafely } from "../../../utils/getRedemptionTokenSafely"
|
|
||||||
import { getUserPointsBalance } from "../../../utils/getUserPointsBalance"
|
|
||||||
import { getHotelsAvailabilityByCity } from "../services/getHotelsAvailabilityByCity"
|
import { getHotelsAvailabilityByCity } from "../services/getHotelsAvailabilityByCity"
|
||||||
|
|
||||||
export type HotelsAvailabilityInputSchema = z.output<
|
export type HotelsAvailabilityInputSchema = z.output<
|
||||||
@@ -64,35 +62,40 @@ export const hotelsAvailabilityInputSchema = z
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
userToken: string | null
|
||||||
|
userPoints?: number
|
||||||
|
}
|
||||||
|
|
||||||
export const hotelsByCity = safeProtectedServiceProcedure
|
export const hotelsByCity = safeProtectedServiceProcedure
|
||||||
.input(hotelsAvailabilityInputSchema)
|
.input(hotelsAvailabilityInputSchema)
|
||||||
.use(async ({ ctx, input, next }) => {
|
.use(async ({ ctx, input, next }) => {
|
||||||
|
const userToken = await ctx.getScandicUserToken()
|
||||||
|
|
||||||
if (input.redemption) {
|
if (input.redemption) {
|
||||||
if (ctx.session?.token.access_token) {
|
const hasValidSession = isValidSession(ctx.session)
|
||||||
const pointsValue = await getUserPointsBalance(ctx.session)
|
if (!hasValidSession) {
|
||||||
const token = getRedemptionTokenSafely(ctx.session, ctx.serviceToken)
|
throw unauthorizedError()
|
||||||
if (pointsValue !== undefined && token) {
|
}
|
||||||
return next({
|
|
||||||
ctx: {
|
const pointsValue = await ctx.getUserPointsBalance()
|
||||||
token: token,
|
if (pointsValue && userToken) {
|
||||||
userPoints: pointsValue,
|
return next<Context>({
|
||||||
},
|
ctx: {
|
||||||
input,
|
userToken,
|
||||||
})
|
userPoints: pointsValue,
|
||||||
}
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
throw unauthorizedError()
|
|
||||||
}
|
}
|
||||||
return next({
|
|
||||||
|
return next<Context>({
|
||||||
ctx: {
|
ctx: {
|
||||||
token: ctx.serviceToken,
|
userToken,
|
||||||
},
|
},
|
||||||
input,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { lang } = ctx
|
|
||||||
const apiLang = toApiLang(lang)
|
|
||||||
const {
|
const {
|
||||||
cityId,
|
cityId,
|
||||||
roomStayStartDate,
|
roomStayStartDate,
|
||||||
@@ -105,19 +108,26 @@ export const hotelsByCity = safeProtectedServiceProcedure
|
|||||||
|
|
||||||
// In case of redemption do not cache result
|
// In case of redemption do not cache result
|
||||||
if (redemption) {
|
if (redemption) {
|
||||||
return getHotelsAvailabilityByCity(
|
return getHotelsAvailabilityByCity({
|
||||||
input,
|
input,
|
||||||
apiLang,
|
lang: ctx.lang,
|
||||||
ctx.token,
|
userToken: ctx.userToken,
|
||||||
ctx.userPoints
|
serviceToken: ctx.serviceToken,
|
||||||
)
|
userPoints: ctx.userPoints,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheClient = await getCacheClient()
|
const cacheClient = await getCacheClient()
|
||||||
return await cacheClient.cacheOrGet(
|
return await cacheClient.cacheOrGet(
|
||||||
`${cityId}:${roomStayStartDate}:${roomStayEndDate}:${adults}:${children}:${bookingCode}`,
|
`${cityId}:${roomStayStartDate}:${roomStayEndDate}:${adults}:${children}:${bookingCode}`,
|
||||||
async () => {
|
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
|
env.CACHE_TIME_CITY_SEARCH
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { serviceProcedure } from "../../../procedures"
|
import { serviceProcedure } from "../../../procedures"
|
||||||
import { toApiLang } from "../../../utils"
|
|
||||||
import { getHotelsAvailabilityByCity } from "../services/getHotelsAvailabilityByCity"
|
import { getHotelsAvailabilityByCity } from "../services/getHotelsAvailabilityByCity"
|
||||||
import { getHotelsAvailabilityByHotelIds } from "../services/getHotelsAvailabilityByHotelIds"
|
import { getHotelsAvailabilityByHotelIds } from "../services/getHotelsAvailabilityByHotelIds"
|
||||||
import { hotelsAvailabilityInputSchema } from "./hotelsByCity"
|
import { hotelsAvailabilityInputSchema } from "./hotelsByCity"
|
||||||
@@ -7,14 +6,13 @@ import { hotelsAvailabilityInputSchema } from "./hotelsByCity"
|
|||||||
export const hotelsByCityWithBookingCode = serviceProcedure
|
export const hotelsByCityWithBookingCode = serviceProcedure
|
||||||
.input(hotelsAvailabilityInputSchema)
|
.input(hotelsAvailabilityInputSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const { lang } = ctx
|
const bookingCodeAvailabilityResponse = await getHotelsAvailabilityByCity({
|
||||||
const apiLang = toApiLang(lang)
|
|
||||||
|
|
||||||
const bookingCodeAvailabilityResponse = await getHotelsAvailabilityByCity(
|
|
||||||
input,
|
input,
|
||||||
apiLang,
|
lang: ctx.lang,
|
||||||
ctx.serviceToken
|
userToken: undefined,
|
||||||
)
|
serviceToken: ctx.serviceToken,
|
||||||
|
userPoints: undefined,
|
||||||
|
})
|
||||||
|
|
||||||
// Get regular availability of hotels which don't have availability with booking code.
|
// Get regular availability of hotels which don't have availability with booking code.
|
||||||
const unavailableHotelIds = bookingCodeAvailabilityResponse.availability
|
const unavailableHotelIds = bookingCodeAvailabilityResponse.availability
|
||||||
@@ -36,11 +34,13 @@ export const hotelsByCityWithBookingCode = serviceProcedure
|
|||||||
bookingCode: "",
|
bookingCode: "",
|
||||||
hotelIds: unavailableHotelIds,
|
hotelIds: unavailableHotelIds,
|
||||||
}
|
}
|
||||||
const unavailableHotels = await getHotelsAvailabilityByHotelIds(
|
const unavailableHotels = await getHotelsAvailabilityByHotelIds({
|
||||||
unavailableHotelsInput,
|
input: unavailableHotelsInput,
|
||||||
apiLang,
|
lang: ctx.lang,
|
||||||
ctx.serviceToken
|
serviceToken: ctx.serviceToken,
|
||||||
)
|
userToken: undefined,
|
||||||
|
userPoints: undefined,
|
||||||
|
})
|
||||||
|
|
||||||
// No regular rates available due to network or API failure (no need to filter & merge).
|
// No regular rates available due to network or API failure (no need to filter & merge).
|
||||||
if (!unavailableHotels) {
|
if (!unavailableHotels) {
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import { unauthorizedError } from "../../../errors"
|
import { unauthorizedError } from "../../../errors"
|
||||||
import { safeProtectedServiceProcedure } from "../../../procedures"
|
import { safeProtectedServiceProcedure } from "../../../procedures"
|
||||||
import { toApiLang } from "../../../utils"
|
import { isValidSession } from "../../../utils/session"
|
||||||
import { getRedemptionTokenSafely } from "../../../utils/getRedemptionTokenSafely"
|
|
||||||
import { getUserPointsBalance } from "../../../utils/getUserPointsBalance"
|
|
||||||
import { getHotelsAvailabilityByHotelIds } from "../services/getHotelsAvailabilityByHotelIds"
|
import { getHotelsAvailabilityByHotelIds } from "../services/getHotelsAvailabilityByHotelIds"
|
||||||
|
|
||||||
export type HotelsByHotelIdsAvailabilityInputSchema = z.output<
|
export type HotelsByHotelIdsAvailabilityInputSchema = z.output<
|
||||||
@@ -60,40 +58,45 @@ export const getHotelsByHotelIdsAvailabilityInputSchema = z
|
|||||||
message: "FROMDATE_CANNOT_BE_IN_THE_PAST",
|
message: "FROMDATE_CANNOT_BE_IN_THE_PAST",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
type Context = {
|
||||||
|
userToken: string | null
|
||||||
|
userPoints?: number
|
||||||
|
}
|
||||||
|
|
||||||
export const hotelsByHotelIds = safeProtectedServiceProcedure
|
export const hotelsByHotelIds = safeProtectedServiceProcedure
|
||||||
.input(getHotelsByHotelIdsAvailabilityInputSchema)
|
.input(getHotelsByHotelIdsAvailabilityInputSchema)
|
||||||
.use(async ({ ctx, input, next }) => {
|
.use(async ({ ctx, input, next }) => {
|
||||||
|
const userToken = await ctx.getScandicUserToken()
|
||||||
|
|
||||||
if (input.redemption) {
|
if (input.redemption) {
|
||||||
if (ctx.session?.token.access_token) {
|
const hasValidSession = isValidSession(ctx.session)
|
||||||
const pointsValue = await getUserPointsBalance(ctx.session)
|
if (!hasValidSession) {
|
||||||
const token = getRedemptionTokenSafely(ctx.session, ctx.serviceToken)
|
throw unauthorizedError()
|
||||||
if (pointsValue !== undefined && token) {
|
}
|
||||||
return next({
|
|
||||||
ctx: {
|
const pointsValue = await ctx.getUserPointsBalance()
|
||||||
token: token,
|
if (pointsValue && userToken) {
|
||||||
userPoints: pointsValue,
|
return next<Context>({
|
||||||
},
|
ctx: {
|
||||||
input,
|
userToken,
|
||||||
})
|
userPoints: pointsValue,
|
||||||
}
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
throw unauthorizedError()
|
|
||||||
}
|
}
|
||||||
return next({
|
|
||||||
|
return next<Context>({
|
||||||
ctx: {
|
ctx: {
|
||||||
token: ctx.serviceToken,
|
userToken,
|
||||||
},
|
},
|
||||||
input,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const { lang } = ctx
|
return getHotelsAvailabilityByHotelIds({
|
||||||
const apiLang = toApiLang(lang)
|
|
||||||
return getHotelsAvailabilityByHotelIds(
|
|
||||||
input,
|
input,
|
||||||
apiLang,
|
lang: ctx.lang,
|
||||||
ctx.token,
|
userToken: ctx.userToken,
|
||||||
ctx.userPoints
|
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 { SEARCH_TYPE_REDEMPTION } from "../../../constants/booking"
|
||||||
import { unauthorizedError } from "../../../errors"
|
import { unauthorizedError } from "../../../errors"
|
||||||
import { safeProtectedServiceProcedure } from "../../../procedures"
|
import { safeProtectedServiceProcedure } from "../../../procedures"
|
||||||
import { getRedemptionTokenSafely } from "../../../utils/getRedemptionTokenSafely"
|
import { isValidSession } from "../../../utils/session"
|
||||||
import { getUserPointsBalance } from "../../../utils/getUserPointsBalance"
|
|
||||||
import { baseBookingSchema, baseRoomSchema, selectedRoomSchema } from "../input"
|
import { baseBookingSchema, baseRoomSchema, selectedRoomSchema } from "../input"
|
||||||
import { getRoomsAvailability } from "../services/getRoomsAvailability"
|
import { getRoomsAvailability } from "../services/getRoomsAvailability"
|
||||||
import { getSelectedRoomAvailability } from "../utils"
|
import { getSelectedRoomAvailability } from "../utils"
|
||||||
@@ -19,29 +18,37 @@ export const myStayRoomAvailabilityInputSchema = z.object({
|
|||||||
lang: z.nativeEnum(Lang),
|
lang: z.nativeEnum(Lang),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
userToken: string | null
|
||||||
|
userPoints?: number
|
||||||
|
}
|
||||||
|
|
||||||
const logger = createLogger("trpc:availability:myStay")
|
const logger = createLogger("trpc:availability:myStay")
|
||||||
export const myStay = safeProtectedServiceProcedure
|
export const myStay = safeProtectedServiceProcedure
|
||||||
.input(myStayRoomAvailabilityInputSchema)
|
.input(myStayRoomAvailabilityInputSchema)
|
||||||
.use(async ({ ctx, input, next }) => {
|
.use(async ({ ctx, input, next }) => {
|
||||||
|
const userToken = await ctx.getScandicUserToken()
|
||||||
|
|
||||||
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
|
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
|
||||||
if (ctx.session?.token.access_token) {
|
const hasValidSession = isValidSession(ctx.session)
|
||||||
const pointsValue = await getUserPointsBalance(ctx.session)
|
if (!hasValidSession) {
|
||||||
const token = getRedemptionTokenSafely(ctx.session, ctx.serviceToken)
|
throw unauthorizedError()
|
||||||
if (pointsValue !== undefined && token) {
|
}
|
||||||
return next({
|
|
||||||
ctx: {
|
const pointsValue = await ctx.getUserPointsBalance()
|
||||||
token: token,
|
if (pointsValue && userToken) {
|
||||||
userPoints: pointsValue,
|
return next<Context>({
|
||||||
},
|
ctx: {
|
||||||
input,
|
userToken,
|
||||||
})
|
userPoints: pointsValue,
|
||||||
}
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
throw unauthorizedError()
|
|
||||||
}
|
}
|
||||||
return next({
|
|
||||||
|
return next<Context>({
|
||||||
ctx: {
|
ctx: {
|
||||||
token: ctx.serviceToken,
|
userToken,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -54,7 +61,7 @@ export const myStay = safeProtectedServiceProcedure
|
|||||||
},
|
},
|
||||||
lang: input.lang,
|
lang: input.lang,
|
||||||
},
|
},
|
||||||
ctx.token,
|
ctx.userToken,
|
||||||
ctx.serviceToken,
|
ctx.serviceToken,
|
||||||
ctx.userPoints
|
ctx.userPoints
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import { Lang } from "@scandic-hotels/common/constants/language"
|
|||||||
import { SEARCH_TYPE_REDEMPTION } from "../../../../constants/booking"
|
import { SEARCH_TYPE_REDEMPTION } from "../../../../constants/booking"
|
||||||
import { unauthorizedError } from "../../../../errors"
|
import { unauthorizedError } from "../../../../errors"
|
||||||
import { safeProtectedServiceProcedure } from "../../../../procedures"
|
import { safeProtectedServiceProcedure } from "../../../../procedures"
|
||||||
import { getRedemptionTokenSafely } from "../../../../utils/getRedemptionTokenSafely"
|
import { isValidSession } from "../../../../utils/session"
|
||||||
import { getUserPointsBalance } from "../../../../utils/getUserPointsBalance"
|
|
||||||
import { baseBookingSchema, baseRoomSchema } from "../../input"
|
import { baseBookingSchema, baseRoomSchema } from "../../input"
|
||||||
import { getRoomsAvailability } from "../../services/getRoomsAvailability"
|
import { getRoomsAvailability } from "../../services/getRoomsAvailability"
|
||||||
import { mergeRoomTypes } from "../../utils"
|
import { mergeRoomTypes } from "../../utils"
|
||||||
@@ -18,28 +17,36 @@ export const selectRateRoomAvailabilityInputSchema = z.object({
|
|||||||
lang: z.nativeEnum(Lang),
|
lang: z.nativeEnum(Lang),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
userToken: string | null
|
||||||
|
userPoints?: number
|
||||||
|
}
|
||||||
|
|
||||||
export const room = safeProtectedServiceProcedure
|
export const room = safeProtectedServiceProcedure
|
||||||
.input(selectRateRoomAvailabilityInputSchema)
|
.input(selectRateRoomAvailabilityInputSchema)
|
||||||
.use(async ({ ctx, input, next }) => {
|
.use(async ({ ctx, input, next }) => {
|
||||||
|
const userToken = await ctx.getScandicUserToken()
|
||||||
|
|
||||||
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
|
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
|
||||||
if (ctx.session?.token.access_token) {
|
const hasValidSession = isValidSession(ctx.session)
|
||||||
const pointsValue = await getUserPointsBalance(ctx.session)
|
if (!hasValidSession) {
|
||||||
const token = getRedemptionTokenSafely(ctx.session, ctx.serviceToken)
|
throw unauthorizedError()
|
||||||
if (pointsValue !== undefined && token) {
|
}
|
||||||
return next({
|
|
||||||
ctx: {
|
const pointsValue = await ctx.getUserPointsBalance()
|
||||||
token: token,
|
if (pointsValue && userToken) {
|
||||||
userPoints: pointsValue,
|
return next<Context>({
|
||||||
},
|
ctx: {
|
||||||
input,
|
userToken,
|
||||||
})
|
userPoints: pointsValue,
|
||||||
}
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
throw unauthorizedError()
|
|
||||||
}
|
}
|
||||||
return next({
|
|
||||||
|
return next<Context>({
|
||||||
ctx: {
|
ctx: {
|
||||||
token: ctx.serviceToken,
|
userToken,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -52,7 +59,7 @@ export const room = safeProtectedServiceProcedure
|
|||||||
},
|
},
|
||||||
lang: input.lang,
|
lang: input.lang,
|
||||||
},
|
},
|
||||||
ctx.token,
|
ctx.userToken,
|
||||||
ctx.serviceToken,
|
ctx.serviceToken,
|
||||||
ctx.userPoints
|
ctx.userPoints
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,34 +3,41 @@ import "server-only"
|
|||||||
import { SEARCH_TYPE_REDEMPTION } from "../../../../../constants/booking"
|
import { SEARCH_TYPE_REDEMPTION } from "../../../../../constants/booking"
|
||||||
import { unauthorizedError } from "../../../../../errors"
|
import { unauthorizedError } from "../../../../../errors"
|
||||||
import { safeProtectedServiceProcedure } from "../../../../../procedures"
|
import { safeProtectedServiceProcedure } from "../../../../../procedures"
|
||||||
import { getRedemptionTokenSafely } from "../../../../../utils/getRedemptionTokenSafely"
|
import { isValidSession } from "../../../../../utils/session"
|
||||||
import { getUserPointsBalance } from "../../../../../utils/getUserPointsBalance"
|
|
||||||
import { getRoomsAvailability } from "../../../services/getRoomsAvailability"
|
import { getRoomsAvailability } from "../../../services/getRoomsAvailability"
|
||||||
import { mergeRoomTypes } from "../../../utils"
|
import { mergeRoomTypes } from "../../../utils"
|
||||||
import { selectRateRoomsAvailabilityInputSchema } from "./schema"
|
import { selectRateRoomsAvailabilityInputSchema } from "./schema"
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
userToken: string | null
|
||||||
|
userPoints?: number
|
||||||
|
}
|
||||||
|
|
||||||
export const rooms = safeProtectedServiceProcedure
|
export const rooms = safeProtectedServiceProcedure
|
||||||
.input(selectRateRoomsAvailabilityInputSchema)
|
.input(selectRateRoomsAvailabilityInputSchema)
|
||||||
.use(async ({ ctx, input, next }) => {
|
.use(async ({ ctx, input, next }) => {
|
||||||
|
const userToken = await ctx.getScandicUserToken()
|
||||||
|
|
||||||
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
|
if (input.booking.searchType === SEARCH_TYPE_REDEMPTION) {
|
||||||
if (ctx.session?.token.access_token) {
|
const hasValidSession = isValidSession(ctx.session)
|
||||||
const pointsValue = await getUserPointsBalance(ctx.session)
|
if (!hasValidSession) {
|
||||||
const token = getRedemptionTokenSafely(ctx.session, ctx.serviceToken)
|
throw unauthorizedError()
|
||||||
if (pointsValue !== undefined && token) {
|
}
|
||||||
return next({
|
|
||||||
ctx: {
|
const pointsValue = await ctx.getUserPointsBalance()
|
||||||
token: token,
|
if (pointsValue && userToken) {
|
||||||
userPoints: pointsValue,
|
return next<Context>({
|
||||||
},
|
ctx: {
|
||||||
input,
|
userToken,
|
||||||
})
|
userPoints: pointsValue,
|
||||||
}
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
throw unauthorizedError()
|
|
||||||
}
|
}
|
||||||
return next({
|
|
||||||
|
return next<Context>({
|
||||||
ctx: {
|
ctx: {
|
||||||
token: ctx.serviceToken,
|
userToken,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -42,7 +49,7 @@ export const rooms = safeProtectedServiceProcedure
|
|||||||
|
|
||||||
const availability = await getRoomsAvailability(
|
const availability = await getRoomsAvailability(
|
||||||
input,
|
input,
|
||||||
ctx.token,
|
ctx.userToken,
|
||||||
ctx.serviceToken,
|
ctx.serviceToken,
|
||||||
ctx.userPoints
|
ctx.userPoints
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -37,14 +37,26 @@ export async function getCountries({
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (!countryResponse.ok) {
|
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 countriesJson = await countryResponse.json()
|
||||||
const countries = countriesSchema.safeParse(countriesJson)
|
const countries = countriesSchema.safeParse(countriesJson)
|
||||||
if (!countries.success) {
|
if (!countries.success) {
|
||||||
logger.error(`Validation for countries failed`, countries.error)
|
logger.error(`Validation for countries failed`, countries.error)
|
||||||
return null
|
throw new Error("Unable to parse country data", {
|
||||||
|
cause: countries.error,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return countries.data
|
return countries.data
|
||||||
|
|||||||
@@ -2,16 +2,26 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
|
|||||||
|
|
||||||
import * as api from "../../../api"
|
import * as api from "../../../api"
|
||||||
import { badRequestError } from "../../../errors"
|
import { badRequestError } from "../../../errors"
|
||||||
|
import { toApiLang } from "../../../utils"
|
||||||
import { hotelsAvailabilitySchema } from "../output"
|
import { hotelsAvailabilitySchema } from "../output"
|
||||||
|
|
||||||
|
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
|
|
||||||
import type { HotelsAvailabilityInputSchema } from "../availability/hotelsByCity"
|
import type { HotelsAvailabilityInputSchema } from "../availability/hotelsByCity"
|
||||||
|
|
||||||
export async function getHotelsAvailabilityByCity(
|
export async function getHotelsAvailabilityByCity({
|
||||||
input: HotelsAvailabilityInputSchema,
|
input,
|
||||||
apiLang: string,
|
lang,
|
||||||
token: string, // Either service token or user access token in case of redemption search
|
userToken,
|
||||||
userPoints: number = 0
|
serviceToken,
|
||||||
) {
|
userPoints = 0,
|
||||||
|
}: {
|
||||||
|
input: HotelsAvailabilityInputSchema
|
||||||
|
lang: Lang
|
||||||
|
userToken: string | null | undefined
|
||||||
|
serviceToken: string
|
||||||
|
userPoints: number | undefined
|
||||||
|
}) {
|
||||||
const {
|
const {
|
||||||
cityId,
|
cityId,
|
||||||
roomStayStartDate,
|
roomStayStartDate,
|
||||||
@@ -22,7 +32,9 @@ export async function getHotelsAvailabilityByCity(
|
|||||||
redemption,
|
redemption,
|
||||||
} = input
|
} = input
|
||||||
|
|
||||||
const params: Record<string, string | number> = {
|
const apiLang = toApiLang(lang)
|
||||||
|
|
||||||
|
const params = {
|
||||||
roomStayStartDate,
|
roomStayStartDate,
|
||||||
roomStayEndDate,
|
roomStayEndDate,
|
||||||
adults,
|
adults,
|
||||||
@@ -30,7 +42,7 @@ export async function getHotelsAvailabilityByCity(
|
|||||||
...(bookingCode && { bookingCode }),
|
...(bookingCode && { bookingCode }),
|
||||||
...(redemption ? { isRedemption: "true" } : {}),
|
...(redemption ? { isRedemption: "true" } : {}),
|
||||||
language: apiLang,
|
language: apiLang,
|
||||||
}
|
} satisfies Record<string, string | number>
|
||||||
|
|
||||||
const getHotelsAvailabilityByCityCounter = createCounter(
|
const getHotelsAvailabilityByCityCounter = createCounter(
|
||||||
"hotel",
|
"hotel",
|
||||||
@@ -54,7 +66,7 @@ export async function getHotelsAvailabilityByCity(
|
|||||||
api.endpoints.v1.Availability.city(cityId),
|
api.endpoints.v1.Availability.city(cityId),
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${userToken ?? serviceToken}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
params
|
params
|
||||||
|
|||||||
@@ -4,16 +4,26 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
|
|||||||
import { env } from "../../../../env/server"
|
import { env } from "../../../../env/server"
|
||||||
import * as api from "../../../api"
|
import * as api from "../../../api"
|
||||||
import { badRequestError } from "../../../errors"
|
import { badRequestError } from "../../../errors"
|
||||||
|
import { toApiLang } from "../../../utils"
|
||||||
import { hotelsAvailabilitySchema } from "../output"
|
import { hotelsAvailabilitySchema } from "../output"
|
||||||
|
|
||||||
|
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
|
|
||||||
import type { HotelsByHotelIdsAvailabilityInputSchema } from "../availability/hotelsByHotelIds"
|
import type { HotelsByHotelIdsAvailabilityInputSchema } from "../availability/hotelsByHotelIds"
|
||||||
|
|
||||||
export async function getHotelsAvailabilityByHotelIds(
|
export async function getHotelsAvailabilityByHotelIds({
|
||||||
input: HotelsByHotelIdsAvailabilityInputSchema,
|
input,
|
||||||
apiLang: string,
|
lang,
|
||||||
token: string,
|
userToken,
|
||||||
userPoints: number = 0
|
serviceToken,
|
||||||
) {
|
userPoints = 0,
|
||||||
|
}: {
|
||||||
|
input: HotelsByHotelIdsAvailabilityInputSchema
|
||||||
|
lang: Lang
|
||||||
|
userToken: string | null | undefined
|
||||||
|
serviceToken: string
|
||||||
|
userPoints: number | undefined
|
||||||
|
}) {
|
||||||
const {
|
const {
|
||||||
hotelIds,
|
hotelIds,
|
||||||
roomStayStartDate,
|
roomStayStartDate,
|
||||||
@@ -24,6 +34,8 @@ export async function getHotelsAvailabilityByHotelIds(
|
|||||||
redemption,
|
redemption,
|
||||||
} = input
|
} = input
|
||||||
|
|
||||||
|
const apiLang = toApiLang(lang)
|
||||||
|
|
||||||
const params = new URLSearchParams([
|
const params = new URLSearchParams([
|
||||||
["roomStayStartDate", roomStayStartDate],
|
["roomStayStartDate", roomStayStartDate],
|
||||||
["roomStayEndDate", roomStayEndDate],
|
["roomStayEndDate", roomStayEndDate],
|
||||||
@@ -72,7 +84,7 @@ export async function getHotelsAvailabilityByHotelIds(
|
|||||||
api.endpoints.v1.Availability.hotels(),
|
api.endpoints.v1.Availability.hotels(),
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${userToken ?? serviceToken}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
params
|
params
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import type { RoomsAvailabilityOutputSchema } from "../availability/selectRate/r
|
|||||||
|
|
||||||
export async function getRoomsAvailability(
|
export async function getRoomsAvailability(
|
||||||
input: RoomsAvailabilityOutputSchema,
|
input: RoomsAvailabilityOutputSchema,
|
||||||
token: string,
|
userToken: string | null | undefined,
|
||||||
serviceToken: string,
|
serviceToken: string,
|
||||||
userPoints: number | undefined
|
userPoints: number | undefined
|
||||||
) {
|
) {
|
||||||
@@ -42,12 +42,12 @@ export async function getRoomsAvailability(
|
|||||||
const apiLang = toApiLang(lang)
|
const apiLang = toApiLang(lang)
|
||||||
|
|
||||||
const baseCacheKey = {
|
const baseCacheKey = {
|
||||||
bookingCode,
|
|
||||||
fromDate,
|
|
||||||
hotelId,
|
|
||||||
lang,
|
lang,
|
||||||
searchType,
|
hotelId,
|
||||||
|
fromDate,
|
||||||
toDate,
|
toDate,
|
||||||
|
searchType,
|
||||||
|
bookingCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheClient = await getCacheClient()
|
const cacheClient = await getCacheClient()
|
||||||
@@ -57,6 +57,11 @@ export async function getRoomsAvailability(
|
|||||||
...baseCacheKey,
|
...baseCacheKey,
|
||||||
room,
|
room,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const token = userToken
|
||||||
|
? ({ type: "userToken", token: userToken } as const)
|
||||||
|
: ({ type: "serviceToken", token: serviceToken } as const)
|
||||||
|
|
||||||
const result = cacheClient.cacheOrGet(
|
const result = cacheClient.cacheOrGet(
|
||||||
stringify(cacheKey),
|
stringify(cacheKey),
|
||||||
async function () {
|
async function () {
|
||||||
@@ -78,7 +83,7 @@ export async function getRoomsAvailability(
|
|||||||
{
|
{
|
||||||
cache: undefined, // overwrite default
|
cache: undefined, // overwrite default
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token.token}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
params
|
params
|
||||||
@@ -181,7 +186,7 @@ export async function getRoomsAvailability(
|
|||||||
return validateAvailabilityData.data
|
return validateAvailabilityData.data
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"1m"
|
token.type === "userToken" ? "no cache" : "1m"
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { TRPCError } from "@trpc/server"
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
|
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
|
||||||
|
|
||||||
import { safeProtectedProcedure } from "../../../procedures"
|
import { safeProtectedProcedure } from "../../../procedures"
|
||||||
import { isValidSession } from "../../../utils/session"
|
import { isValidSession } from "../../../utils/session"
|
||||||
@@ -38,13 +39,15 @@ export const myPagesNavigation = safeProtectedProcedure
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await getVerifiedUser({ session: ctx.session })
|
const [user, error] = await safeTry(
|
||||||
if (!user || user.error) {
|
getVerifiedUser({ token: ctx.session.token })
|
||||||
|
)
|
||||||
|
if (!user || error) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const [primaryLinks, secondaryLinks] = await Promise.all([
|
const [primaryLinks, secondaryLinks] = await Promise.all([
|
||||||
getPrimaryLinks({ lang, userLoyalty: user.data.loyalty }),
|
getPrimaryLinks({ lang, userLoyalty: user.loyalty }),
|
||||||
getSecondaryLinks({ lang }),
|
getSecondaryLinks({ lang }),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import { FriendsMembershipLevels } from "@scandic-hotels/common/constants/membershipLevels"
|
import { FriendsMembershipLevels } from "@scandic-hotels/common/constants/membershipLevels"
|
||||||
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
||||||
|
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
|
||||||
|
|
||||||
import * as api from "../../../api"
|
import * as api from "../../../api"
|
||||||
import { protectedProcedure } from "../../../procedures"
|
import { protectedProcedure } from "../../../procedures"
|
||||||
@@ -31,11 +32,13 @@ export const performLevelUpgrade = protectedProcedure
|
|||||||
return { tierMatchState: "cached" }
|
return { tierMatchState: "cached" }
|
||||||
}
|
}
|
||||||
|
|
||||||
const profile = await getVerifiedUser({ session: ctx.session })
|
const [profile, error] = await safeTry(
|
||||||
if (!profile || "error" in profile || !profile.data.membership) {
|
getVerifiedUser({ token: ctx.session.token })
|
||||||
|
)
|
||||||
|
if (!profile?.membership || error) {
|
||||||
return { tierMatchState: "error" }
|
return { tierMatchState: "error" }
|
||||||
}
|
}
|
||||||
const currentLevel = profile.data.membership.membershipLevel
|
const currentLevel = profile.membership.membershipLevel
|
||||||
|
|
||||||
sasLogger.debug("tier match started")
|
sasLogger.debug("tier match started")
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { createCounter } from "@scandic-hotels/common/telemetry"
|
import { createCounter } from "@scandic-hotels/common/telemetry"
|
||||||
|
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
|
||||||
|
|
||||||
import { router } from "../../.."
|
import { router } from "../../.."
|
||||||
import * as api from "../../../api"
|
import * as api from "../../../api"
|
||||||
import { Transactions } from "../../../enums/transactions"
|
import { Transactions } from "../../../enums/transactions"
|
||||||
|
import { notFound } from "../../../errors"
|
||||||
import {
|
import {
|
||||||
languageProtectedProcedure,
|
languageProtectedProcedure,
|
||||||
protectedProcedure,
|
protectedProcedure,
|
||||||
@@ -42,30 +44,25 @@ export const userQueryRouter = router({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.query(async function getUser({ ctx }) {
|
.query(async function getUser({ ctx }) {
|
||||||
const data = await getVerifiedUser({ session: ctx.session })
|
const user = await ctx.getScandicUser()
|
||||||
|
if (!user) {
|
||||||
if (!data) {
|
throw notFound()
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("error" in data && data.error) {
|
return parsedUser(user, !ctx.isMFA)
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedUser(data.data, !ctx.isMFA)
|
|
||||||
}),
|
}),
|
||||||
getSafely: safeProtectedProcedure.query(async function getUser({ ctx }) {
|
getSafely: safeProtectedProcedure.query(async function getUser({ ctx }) {
|
||||||
if (!isValidSession(ctx.session)) {
|
if (!isValidSession(ctx.session)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await getVerifiedUser({ session: ctx.session })
|
const user = await ctx.getScandicUser()
|
||||||
|
|
||||||
if (!data || "error" in data) {
|
if (!user) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsedUser(data.data, false)
|
return parsedUser(user, false)
|
||||||
}),
|
}),
|
||||||
getWithExtendedPartnerData: safeProtectedProcedure.query(
|
getWithExtendedPartnerData: safeProtectedProcedure.query(
|
||||||
async function getUser({ ctx }) {
|
async function getUser({ ctx }) {
|
||||||
@@ -73,60 +70,49 @@ export const userQueryRouter = router({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await getVerifiedUser({
|
const user = await ctx.getScandicUser()
|
||||||
session: ctx.session,
|
|
||||||
includeExtendedPartnerData: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!data || "error" in data) {
|
if (!user) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsedUser(data.data, false)
|
return parsedUser(user, false)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
name: safeProtectedProcedure.query(async function ({ ctx }) {
|
name: safeProtectedProcedure.query(async function ({ ctx }) {
|
||||||
if (!isValidSession(ctx.session)) {
|
if (!isValidSession(ctx.session)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const verifiedData = await getVerifiedUser({ session: ctx.session })
|
const user = await ctx.getScandicUser()
|
||||||
|
|
||||||
if (!verifiedData || "error" in verifiedData) {
|
if (!user) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
firstName: verifiedData.data.firstName,
|
firstName: user.firstName,
|
||||||
lastName: verifiedData.data.lastName,
|
lastName: user.lastName,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
membershipLevel: protectedProcedure.query(async function ({ ctx }) {
|
membershipLevel: protectedProcedure.query(async function ({ ctx }) {
|
||||||
const verifiedData = await getVerifiedUser({ session: ctx.session })
|
const user = await ctx.getScandicUser()
|
||||||
if (
|
if (!user?.loyalty) {
|
||||||
!verifiedData ||
|
|
||||||
"error" in verifiedData ||
|
|
||||||
!verifiedData.data.loyalty
|
|
||||||
) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const membershipLevel = getFriendsMembership(verifiedData.data.loyalty)
|
const membershipLevel = getFriendsMembership(user.loyalty)
|
||||||
return membershipLevel
|
return membershipLevel
|
||||||
}),
|
}),
|
||||||
safeMembershipLevel: safeProtectedProcedure.query(async function ({ ctx }) {
|
safeMembershipLevel: safeProtectedProcedure.query(async function ({ ctx }) {
|
||||||
if (!isValidSession(ctx.session)) {
|
if (!isValidSession(ctx.session)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const verifiedData = await getVerifiedUser({ session: ctx.session })
|
const user = await ctx.getScandicUser()
|
||||||
|
|
||||||
if (
|
if (!user?.loyalty) {
|
||||||
!verifiedData ||
|
|
||||||
"error" in verifiedData ||
|
|
||||||
!verifiedData.data.loyalty
|
|
||||||
) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const membershipLevel = getFriendsMembership(verifiedData.data.loyalty)
|
const membershipLevel = getFriendsMembership(user.loyalty)
|
||||||
return membershipLevel
|
return membershipLevel
|
||||||
}),
|
}),
|
||||||
userTrackingInfo,
|
userTrackingInfo,
|
||||||
@@ -327,12 +313,14 @@ export const userQueryRouter = router({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
membershipCards: protectedProcedure.query(async function ({ ctx }) {
|
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 null
|
||||||
}
|
}
|
||||||
|
|
||||||
return getMembershipCards(userData.data.loyalty)
|
return getMembershipCards(userData.loyalty)
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,13 +47,9 @@ async function getScandicFriendsUserTrackingData(session: Session | null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const verifiedUserData = await getVerifiedUser({ session: session })
|
const verifiedUserData = await getVerifiedUser({ token: session.token })
|
||||||
|
|
||||||
if (
|
if (!verifiedUserData || !verifiedUserData.loyalty) {
|
||||||
!verifiedUserData ||
|
|
||||||
"error" in verifiedUserData ||
|
|
||||||
!verifiedUserData.data.loyalty
|
|
||||||
) {
|
|
||||||
metricsUserTrackingInfo.success({
|
metricsUserTrackingInfo.success({
|
||||||
reason: "invalid user data",
|
reason: "invalid user data",
|
||||||
data: notLoggedInUserTrackingData,
|
data: notLoggedInUserTrackingData,
|
||||||
@@ -61,12 +57,12 @@ async function getScandicFriendsUserTrackingData(session: Session | null) {
|
|||||||
return notLoggedInUserTrackingData
|
return notLoggedInUserTrackingData
|
||||||
}
|
}
|
||||||
|
|
||||||
const membership = getFriendsMembership(verifiedUserData.data.loyalty)
|
const membership = getFriendsMembership(verifiedUserData.loyalty)
|
||||||
|
|
||||||
const loggedInUserTrackingData: TrackingUserData = {
|
const loggedInUserTrackingData: TrackingUserData = {
|
||||||
loginStatus: "logged in",
|
loginStatus: "logged in",
|
||||||
loginType: session.token.loginType as LoginType,
|
loginType: session.token.loginType as LoginType,
|
||||||
memberId: verifiedUserData.data.profileId,
|
memberId: verifiedUserData.profileId,
|
||||||
membershipNumber: membership?.membershipNumber,
|
membershipNumber: membership?.membershipNumber,
|
||||||
memberLevel: membership?.membershipLevel,
|
memberLevel: membership?.membershipLevel,
|
||||||
loginAction: "login success",
|
loginAction: "login success",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
|
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
|
||||||
import { dt } from "@scandic-hotels/common/dt"
|
import { dt } from "@scandic-hotels/common/dt"
|
||||||
import { createCounter } from "@scandic-hotels/common/telemetry"
|
import { createCounter } from "@scandic-hotels/common/telemetry"
|
||||||
|
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
|
||||||
|
|
||||||
import { env } from "../../../env/server"
|
import { env } from "../../../env/server"
|
||||||
import * as api from "../../api"
|
import * as api from "../../api"
|
||||||
@@ -8,7 +9,6 @@ import { cache } from "../../DUPLICATED/cache"
|
|||||||
import { creditCardsSchema } from "../../routers/user/output"
|
import { creditCardsSchema } from "../../routers/user/output"
|
||||||
import { toApiLang } from "../../utils"
|
import { toApiLang } from "../../utils"
|
||||||
import { encrypt } from "../../utils/encryption"
|
import { encrypt } from "../../utils/encryption"
|
||||||
import { isValidSession } from "../../utils/session"
|
|
||||||
import { getVerifiedUser } from "./utils/getVerifiedUser"
|
import { getVerifiedUser } from "./utils/getVerifiedUser"
|
||||||
import { type FriendTransaction, getStaysSchema, type Stay } from "./output"
|
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 { LangRoute } from "@scandic-hotels/common/constants/routes/langRoute"
|
||||||
import type { Session } from "next-auth"
|
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(
|
export async function getPreviousStays(
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
limit: number = 10,
|
limit: number = 10,
|
||||||
@@ -202,44 +189,45 @@ export async function updateStaysBookingUrl(
|
|||||||
session: Session,
|
session: Session,
|
||||||
lang: Lang
|
lang: Lang
|
||||||
) {
|
) {
|
||||||
const user = await getVerifiedUser({
|
const [user, error] = await safeTry(
|
||||||
session,
|
getVerifiedUser({
|
||||||
})
|
token: session.token,
|
||||||
|
|
||||||
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(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
)
|
||||||
|
|
||||||
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 = {
|
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 * as api from "../../../api"
|
||||||
import { cache } from "../../../DUPLICATED/cache"
|
import { cache } from "../../../DUPLICATED/cache"
|
||||||
|
import {
|
||||||
|
internalServerError,
|
||||||
|
serverErrorByStatus,
|
||||||
|
sessionExpiredError,
|
||||||
|
} from "../../../errors"
|
||||||
import { getUserSchema } from "../output"
|
import { getUserSchema } from "../output"
|
||||||
|
|
||||||
import type { Session } from "next-auth"
|
|
||||||
|
|
||||||
export const getVerifiedUser = cache(
|
export const getVerifiedUser = cache(
|
||||||
async ({
|
async ({
|
||||||
session,
|
token,
|
||||||
includeExtendedPartnerData,
|
includeExtendedPartnerData,
|
||||||
}: {
|
}: {
|
||||||
session: Session
|
token: { expires_at?: number; access_token: string }
|
||||||
includeExtendedPartnerData?: boolean
|
includeExtendedPartnerData?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const getVerifiedUserCounter = createCounter("user", "getVerifiedUser")
|
const getVerifiedUserCounter = createCounter("user", "getVerifiedUser")
|
||||||
@@ -20,16 +23,16 @@ export const getVerifiedUser = cache(
|
|||||||
metricsGetVerifiedUser.start()
|
metricsGetVerifiedUser.start()
|
||||||
|
|
||||||
const now = Date.now()
|
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`)
|
metricsGetVerifiedUser.dataError(`Token expired`)
|
||||||
return { error: true, cause: "token_expired" } as const
|
throw sessionExpiredError()
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiResponse = await api.get(
|
const apiResponse = await api.get(
|
||||||
api.endpoints.v2.Profile.profile,
|
api.endpoints.v2.Profile.profile,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${session.token.access_token}`,
|
Authorization: `Bearer ${token.access_token}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
includeExtendedPartnerData
|
includeExtendedPartnerData
|
||||||
@@ -40,19 +43,7 @@ export const getVerifiedUser = cache(
|
|||||||
if (!apiResponse.ok) {
|
if (!apiResponse.ok) {
|
||||||
await metricsGetVerifiedUser.httpError(apiResponse)
|
await metricsGetVerifiedUser.httpError(apiResponse)
|
||||||
|
|
||||||
if (apiResponse.status === 401) {
|
throw serverErrorByStatus(apiResponse.status, apiResponse)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiJson = await apiResponse.json()
|
const apiJson = await apiResponse.json()
|
||||||
@@ -63,17 +54,17 @@ export const getVerifiedUser = cache(
|
|||||||
data: apiJson,
|
data: apiJson,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return null
|
throw internalServerError("Missing data attributes in API response")
|
||||||
}
|
}
|
||||||
|
|
||||||
const verifiedData = getUserSchema.safeParse(apiJson)
|
const verifiedData = getUserSchema.safeParse(apiJson)
|
||||||
if (!verifiedData.success) {
|
if (!verifiedData.success) {
|
||||||
metricsGetVerifiedUser.validationError(verifiedData.error)
|
metricsGetVerifiedUser.validationError(verifiedData.error)
|
||||||
return null
|
throw verifiedData.error
|
||||||
}
|
}
|
||||||
|
|
||||||
metricsGetVerifiedUser.success()
|
metricsGetVerifiedUser.success()
|
||||||
|
|
||||||
return verifiedData
|
return verifiedData.data
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import "server-only"
|
import "server-only"
|
||||||
|
|
||||||
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
|
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
|
||||||
|
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
|
||||||
|
|
||||||
import { env } from "../../../../env/server"
|
import { env } from "../../../../env/server"
|
||||||
import { encrypt } from "../../../utils/encryption"
|
import { encrypt } from "../../../utils/encryption"
|
||||||
@@ -28,42 +29,47 @@ export async function updateStaysBookingUrl(
|
|||||||
session: Session,
|
session: Session,
|
||||||
lang: Lang
|
lang: Lang
|
||||||
) {
|
) {
|
||||||
const user = await getVerifiedUser({
|
const [user, error] = await safeTry(
|
||||||
session,
|
getVerifiedUser({
|
||||||
})
|
token: {
|
||||||
|
access_token: session.token.access_token,
|
||||||
if (user && !("error" in user)) {
|
expires_at: session.token.expires_at ?? 0,
|
||||||
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(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
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