Merged in chore/refactor-hotel-trpc-routes (pull request #2891)
Chore/refactor hotel trpc routes * chore(SW-3519): refactor trpc hotel routers * chore(SW-3519): refactor trpc hotel routers * refactor * merge * Merge branch 'master' of bitbucket.org:scandic-swap/web into chore/refactor-hotel-trpc-routes Approved-by: Linus Flood
This commit is contained in:
@@ -12,7 +12,6 @@ import {
|
||||
getFriendsMembership,
|
||||
getMembershipCards,
|
||||
} from "../../../routers/user/helpers"
|
||||
import { getVerifiedUser } from "../../../routers/user/utils"
|
||||
import { toApiLang } from "../../../utils"
|
||||
import { isValidSession } from "../../../utils/session"
|
||||
import {
|
||||
@@ -21,13 +20,12 @@ import {
|
||||
staysInput,
|
||||
} from "../input"
|
||||
import { getFriendTransactionsSchema } from "../output"
|
||||
import {
|
||||
getCreditCards,
|
||||
getPreviousStays,
|
||||
getUpcomingStays,
|
||||
parsedUser,
|
||||
updateStaysBookingUrl,
|
||||
} from "../utils"
|
||||
import { getCreditCards } from "../services/getCreditCards"
|
||||
import { getPreviousStays } from "../services/getPreviousStays"
|
||||
import { getUpcomingStays } from "../services/getUpcomingStays"
|
||||
import { getVerifiedUser } from "../utils/getVerifiedUser"
|
||||
import { parsedUser } from "../utils/parsedUser"
|
||||
import { updateStaysBookingUrl } from "../utils/updateStaysBookingUrl"
|
||||
import { userTrackingInfo } from "./userTrackingInfo"
|
||||
|
||||
export const userQueryRouter = router({
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
|
||||
import { safeProtectedProcedure } from "../../../procedures"
|
||||
import { isValidSession } from "../../../utils/session"
|
||||
import { getFriendsMembership } from "../helpers"
|
||||
import { getVerifiedUser } from "../utils"
|
||||
import { getVerifiedUser } from "../utils/getVerifiedUser"
|
||||
|
||||
import type { LoginType } from "@scandic-hotels/common/constants/loginType"
|
||||
|
||||
|
||||
60
packages/trpc/lib/routers/user/services/getCreditCards.ts
Normal file
60
packages/trpc/lib/routers/user/services/getCreditCards.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { dt } from "@scandic-hotels/common/dt"
|
||||
import { createCounter } from "@scandic-hotels/common/telemetry"
|
||||
|
||||
import * as api from "../../../api"
|
||||
import { cache } from "../../../DUPLICATED/cache"
|
||||
import { creditCardsSchema } from "../output"
|
||||
|
||||
import type { Session } from "next-auth"
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
59
packages/trpc/lib/routers/user/services/getPreviousStays.ts
Normal file
59
packages/trpc/lib/routers/user/services/getPreviousStays.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { createCounter } from "@scandic-hotels/common/telemetry"
|
||||
|
||||
import * as api from "../../../api"
|
||||
import { toApiLang } from "../../../utils"
|
||||
import { getStaysSchema } from "../output"
|
||||
|
||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
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
|
||||
}
|
||||
59
packages/trpc/lib/routers/user/services/getUpcomingStays.ts
Normal file
59
packages/trpc/lib/routers/user/services/getUpcomingStays.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { createCounter } from "@scandic-hotels/common/telemetry"
|
||||
|
||||
import * as api from "../../../api"
|
||||
import { toApiLang } from "../../../utils"
|
||||
import { getStaysSchema } from "../output"
|
||||
|
||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
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
|
||||
}
|
||||
17
packages/trpc/lib/routers/user/utils/getMemberShipNumber.ts
Normal file
17
packages/trpc/lib/routers/user/utils/getMemberShipNumber.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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
|
||||
}
|
||||
79
packages/trpc/lib/routers/user/utils/getVerifiedUser.ts
Normal file
79
packages/trpc/lib/routers/user/utils/getVerifiedUser.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { createCounter } from "@scandic-hotels/common/telemetry"
|
||||
|
||||
import * as api from "../../../api"
|
||||
import { cache } from "../../../DUPLICATED/cache"
|
||||
import { getUserSchema } from "../output"
|
||||
|
||||
import type { Session } from "next-auth"
|
||||
|
||||
export const getVerifiedUser = cache(
|
||||
async ({
|
||||
session,
|
||||
includeExtendedPartnerData,
|
||||
}: {
|
||||
session: Session
|
||||
includeExtendedPartnerData?: boolean
|
||||
}) => {
|
||||
const getVerifiedUserCounter = createCounter("user", "getVerifiedUser")
|
||||
const metricsGetVerifiedUser = getVerifiedUserCounter.init()
|
||||
|
||||
metricsGetVerifiedUser.start()
|
||||
|
||||
const now = Date.now()
|
||||
if (session.token.expires_at && session.token.expires_at < now) {
|
||||
metricsGetVerifiedUser.dataError(`Token expired`)
|
||||
return { error: true, cause: "token_expired" } as const
|
||||
}
|
||||
|
||||
const apiResponse = await api.get(
|
||||
api.endpoints.v2.Profile.profile,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.token.access_token}`,
|
||||
},
|
||||
},
|
||||
includeExtendedPartnerData
|
||||
? { includes: "extendedPartnerInformation" }
|
||||
: {}
|
||||
)
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
await metricsGetVerifiedUser.httpError(apiResponse)
|
||||
|
||||
if (apiResponse.status === 401) {
|
||||
return { error: true, cause: "unauthorized" } as const
|
||||
} else if (apiResponse.status === 403) {
|
||||
return { error: true, cause: "forbidden" } as const
|
||||
} else if (apiResponse.status === 404) {
|
||||
return { error: true, cause: "notfound" } as const
|
||||
}
|
||||
|
||||
return {
|
||||
error: true,
|
||||
cause: "unknown",
|
||||
status: apiResponse.status,
|
||||
} as const
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
if (!apiJson.data?.attributes) {
|
||||
metricsGetVerifiedUser.dataError(
|
||||
`Missing data attributes in API response`,
|
||||
{
|
||||
data: apiJson,
|
||||
}
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const verifiedData = getUserSchema.safeParse(apiJson)
|
||||
if (!verifiedData.success) {
|
||||
metricsGetVerifiedUser.validationError(verifiedData.error)
|
||||
return null
|
||||
}
|
||||
|
||||
metricsGetVerifiedUser.success()
|
||||
|
||||
return verifiedData
|
||||
}
|
||||
)
|
||||
54
packages/trpc/lib/routers/user/utils/parsedUser.ts
Normal file
54
packages/trpc/lib/routers/user/utils/parsedUser.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as maskValue from "@scandic-hotels/common/utils/maskValue"
|
||||
|
||||
import { countries } from "../../../constants/countries"
|
||||
import { getFriendsMembership } from "../helpers"
|
||||
|
||||
import type { User } from "../../../types/user"
|
||||
|
||||
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,
|
||||
employmentDetails: data.employmentDetails,
|
||||
firstName: data.firstName,
|
||||
language: data.language,
|
||||
lastName: data.lastName,
|
||||
loyalty: data.loyalty,
|
||||
membershipNumber: data.membershipNumber,
|
||||
membership: data.loyalty ? getFriendsMembership(data.loyalty) : null,
|
||||
name: `${data.firstName} ${data.lastName}`,
|
||||
phoneNumber: data.phoneNumber,
|
||||
profileId: data.profileId,
|
||||
promotions: data.promotions || null,
|
||||
} satisfies User
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import "server-only"
|
||||
|
||||
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
|
||||
|
||||
import { env } from "../../../../env/server"
|
||||
import { encrypt } from "../../../utils/encryption"
|
||||
import { getVerifiedUser } from "./getVerifiedUser"
|
||||
|
||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||
import type { Session } from "next-auth"
|
||||
|
||||
import type { FriendTransaction, Stay } from "../output"
|
||||
|
||||
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 = 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
|
||||
}
|
||||
Reference in New Issue
Block a user