feat (SW-2864): Move booking router to trpc package * Add env to trpc package * Add eslint to trpc package * Apply lint rules * Use direct imports from trpc package * Add lint-staged config to trpc * Move lang enum to common * Restructure trpc package folder structure * WIP first step * update internal imports in trpc * Fix most errors in scandic-web Just 100 left... * Move Props type out of trpc * Fix CategorizedFilters types * Move more schemas in hotel router * Fix deps * fix getNonContentstackUrls * Fix import error * Fix entry error handling * Fix generateMetadata metrics * Fix alertType enum * Fix duplicated types * lint:fix * Merge branch 'master' into feat/sw-2863-move-contentstack-router-to-trpc-package * Fix broken imports * Move booking router to trpc package * Merge branch 'master' into feat/sw-2864-move-hotels-router-to-trpc-package Approved-by: Linus Flood
305 lines
7.7 KiB
TypeScript
305 lines
7.7 KiB
TypeScript
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
|
|
import { dt } from "@scandic-hotels/common/dt"
|
|
import { createCounter } from "@scandic-hotels/common/telemetry"
|
|
import * as api from "@scandic-hotels/trpc/api"
|
|
import { countries } from "@scandic-hotels/trpc/constants/countries"
|
|
import { getFriendsMembership } from "@scandic-hotels/trpc/routers/user/helpers"
|
|
import { creditCardsSchema } from "@scandic-hotels/trpc/routers/user/output"
|
|
import { getVerifiedUser } from "@scandic-hotels/trpc/routers/user/utils"
|
|
import { toApiLang } from "@scandic-hotels/trpc/utils"
|
|
|
|
import { myBookingPath } from "@/constants/myBooking"
|
|
import { env } from "@/env/server"
|
|
|
|
import { cache } from "@/utils/cache"
|
|
import { encrypt } from "@/utils/encryption"
|
|
import * as maskValue from "@/utils/maskValue"
|
|
import { isValidSession } from "@/utils/session"
|
|
import { getCurrentWebUrl } from "@/utils/url"
|
|
|
|
import { type FriendTransaction, getStaysSchema, type Stay } from "./output"
|
|
|
|
import type { Lang } from "@scandic-hotels/common/constants/language"
|
|
import type { User } from "@scandic-hotels/trpc/types/user"
|
|
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,
|
|
language: Lang,
|
|
cursor?: string
|
|
) {
|
|
const getPreviousStaysCounter = createCounter("user", "getPreviousStays")
|
|
const metricsGetPreviousStays = getPreviousStaysCounter.init({
|
|
limit,
|
|
cursor,
|
|
language,
|
|
})
|
|
|
|
metricsGetPreviousStays.start()
|
|
|
|
const params: Record<string, string> = {
|
|
limit: String(limit),
|
|
language: toApiLang(language),
|
|
}
|
|
|
|
if (cursor) {
|
|
params.offset = cursor
|
|
}
|
|
|
|
const apiResponse = await api.get(
|
|
api.endpoints.v1.Booking.Stays.past,
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
},
|
|
},
|
|
params
|
|
)
|
|
|
|
if (!apiResponse.ok) {
|
|
await metricsGetPreviousStays.httpError(apiResponse)
|
|
return null
|
|
}
|
|
|
|
const apiJson = await apiResponse.json()
|
|
|
|
const verifiedData = getStaysSchema.safeParse(apiJson)
|
|
if (!verifiedData.success) {
|
|
metricsGetPreviousStays.validationError(verifiedData.error)
|
|
return null
|
|
}
|
|
|
|
metricsGetPreviousStays.success()
|
|
|
|
return verifiedData.data
|
|
}
|
|
|
|
export async function getUpcomingStays(
|
|
accessToken: string,
|
|
limit: number = 10,
|
|
language: Lang,
|
|
cursor?: string
|
|
) {
|
|
const getUpcomingStaysCounter = createCounter("user", "getUpcomingStays")
|
|
const metricsGetUpcomingStays = getUpcomingStaysCounter.init({
|
|
limit,
|
|
cursor,
|
|
language,
|
|
})
|
|
|
|
metricsGetUpcomingStays.start()
|
|
|
|
const params: Record<string, string> = {
|
|
limit: String(limit),
|
|
language: toApiLang(language),
|
|
}
|
|
|
|
if (cursor) {
|
|
params.offset = cursor
|
|
}
|
|
|
|
const apiResponse = await api.get(
|
|
api.endpoints.v1.Booking.Stays.future,
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
},
|
|
},
|
|
params
|
|
)
|
|
|
|
if (!apiResponse.ok) {
|
|
await metricsGetUpcomingStays.httpError(apiResponse)
|
|
return null
|
|
}
|
|
|
|
const apiJson = await apiResponse.json()
|
|
|
|
const verifiedData = getStaysSchema.safeParse(apiJson)
|
|
if (!verifiedData.success) {
|
|
metricsGetUpcomingStays.validationError(verifiedData.error)
|
|
return null
|
|
}
|
|
|
|
metricsGetUpcomingStays.success()
|
|
|
|
return verifiedData.data
|
|
}
|
|
|
|
export function parsedUser(data: User, isMFA: boolean) {
|
|
const country = countries.find((c) => c.code === data.address?.countryCode)
|
|
|
|
const user = {
|
|
address: {
|
|
city: data.address?.city,
|
|
country: country?.name ?? "",
|
|
countryCode: data.address?.countryCode,
|
|
streetAddress: data.address?.streetAddress,
|
|
zipCode: data.address?.zipCode,
|
|
},
|
|
dateOfBirth: data.dateOfBirth,
|
|
email: data.email,
|
|
firstName: data.firstName,
|
|
language: data.language,
|
|
lastName: data.lastName,
|
|
membershipNumber: data.membershipNumber,
|
|
membership: data.loyalty ? getFriendsMembership(data.loyalty) : null,
|
|
loyalty: data.loyalty,
|
|
name: `${data.firstName} ${data.lastName}`,
|
|
phoneNumber: data.phoneNumber,
|
|
profileId: data.profileId,
|
|
}
|
|
|
|
if (!isMFA) {
|
|
if (user.address.city) {
|
|
user.address.city = maskValue.text(user.address.city)
|
|
}
|
|
if (user.address.streetAddress) {
|
|
user.address.streetAddress = maskValue.text(user.address.streetAddress)
|
|
}
|
|
|
|
user.address.zipCode = data.address?.zipCode
|
|
? maskValue.text(data.address.zipCode)
|
|
: ""
|
|
|
|
user.dateOfBirth = maskValue.all(user.dateOfBirth)
|
|
|
|
user.email = maskValue.email(user.email)
|
|
|
|
user.phoneNumber = user.phoneNumber ? maskValue.phone(user.phoneNumber) : ""
|
|
}
|
|
|
|
return user
|
|
}
|
|
|
|
export const getCreditCards = cache(
|
|
async ({
|
|
session,
|
|
onlyNonExpired,
|
|
}: {
|
|
session: Session
|
|
onlyNonExpired?: boolean
|
|
}) => {
|
|
const getCreditCardsCounter = createCounter("user", "getCreditCards")
|
|
const metricsGetCreditCards = getCreditCardsCounter.init({
|
|
onlyNonExpired,
|
|
})
|
|
|
|
metricsGetCreditCards.start()
|
|
|
|
const apiResponse = await api.get(api.endpoints.v1.Profile.creditCards, {
|
|
headers: {
|
|
Authorization: `Bearer ${session.token.access_token}`,
|
|
},
|
|
})
|
|
|
|
if (!apiResponse.ok) {
|
|
await metricsGetCreditCards.httpError(apiResponse)
|
|
return null
|
|
}
|
|
|
|
const apiJson = await apiResponse.json()
|
|
const verifiedData = creditCardsSchema.safeParse(apiJson)
|
|
if (!verifiedData.success) {
|
|
metricsGetCreditCards.validationError(verifiedData.error)
|
|
return null
|
|
}
|
|
|
|
const result = verifiedData.data.data.filter((card) => {
|
|
if (onlyNonExpired) {
|
|
try {
|
|
const expirationDate = dt(card.expirationDate).startOf("day")
|
|
const currentDate = dt().startOf("day")
|
|
return expirationDate > currentDate
|
|
} catch (_) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
metricsGetCreditCards.success()
|
|
|
|
return result
|
|
}
|
|
)
|
|
|
|
export async function updateStaysBookingUrl(
|
|
data: Stay[],
|
|
session: Session,
|
|
lang: Lang
|
|
): Promise<Stay[]>
|
|
|
|
export async function updateStaysBookingUrl(
|
|
data: FriendTransaction[],
|
|
session: Session,
|
|
lang: Lang
|
|
): Promise<FriendTransaction[]>
|
|
|
|
export async function updateStaysBookingUrl(
|
|
data: Stay[] | FriendTransaction[],
|
|
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 = !env.isLangLive(lang)
|
|
? new URL(
|
|
getCurrentWebUrl({
|
|
path: myBookingPath[lang],
|
|
lang,
|
|
baseUrl,
|
|
})
|
|
)
|
|
: 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
|
|
}
|