Merged in feat/sw-2864-move-hotels-router-to-trpc-package (pull request #2410)
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
This commit is contained in:
@@ -2,122 +2,6 @@ import { z } from "zod"
|
||||
|
||||
import { imageSchema } from "@scandic-hotels/trpc/routers/hotels/schemas/image"
|
||||
|
||||
import { countriesMap } from "@/constants/countries"
|
||||
|
||||
import { getFriendsMembership } from "@/utils/user"
|
||||
|
||||
const scandicFriendsTier = z.enum(["L1", "L2", "L3", "L4", "L5", "L6", "L7"])
|
||||
const sasEurobonusTier = z.enum(["EBB", "EBS", "EBG", "EBD", "EBP"])
|
||||
|
||||
const commonMembershipSchema = z.object({
|
||||
membershipNumber: z.string(),
|
||||
tierExpires: z.string().nullish().default(null),
|
||||
memberSince: z.string().nullish(),
|
||||
})
|
||||
|
||||
// This prevents validation errors if the API returns an unhandled membership type
|
||||
const otherMembershipSchema = z
|
||||
.object({
|
||||
// This ensures that `type` won't widen into "string", losing the literal types, when used in a union
|
||||
type: z.string().refine((val): val is string & {} => true),
|
||||
})
|
||||
.merge(commonMembershipSchema)
|
||||
|
||||
export const sasMembershipSchema = z
|
||||
.object({
|
||||
type: z.literal("SAS_EB"),
|
||||
tier: sasEurobonusTier,
|
||||
nextTier: sasEurobonusTier.nullish(),
|
||||
spendablePoints: z.number().nullish(),
|
||||
boostedByScandic: z.boolean().nullish(),
|
||||
boostedTier: sasEurobonusTier.nullish(),
|
||||
boostedTierExpires: z.string().nullish().default(null),
|
||||
})
|
||||
.merge(commonMembershipSchema)
|
||||
.transform((response) => {
|
||||
return {
|
||||
...response,
|
||||
tierExpires:
|
||||
// SAS API returns 1900-01-01 for non-expiring tiers
|
||||
response.tierExpires === "1900-01-01" ? null : response.tierExpires,
|
||||
}
|
||||
})
|
||||
|
||||
export const friendsMembershipSchema = z
|
||||
.object({
|
||||
type: z.literal("SCANDIC_NATIVE"),
|
||||
tier: scandicFriendsTier,
|
||||
nextTier: scandicFriendsTier.nullish(),
|
||||
pointsToNextTier: z.number().nullish(),
|
||||
nightsToTopTier: z.number().nullish(),
|
||||
})
|
||||
.merge(commonMembershipSchema)
|
||||
|
||||
export const membershipSchema = z.union([
|
||||
friendsMembershipSchema,
|
||||
sasMembershipSchema,
|
||||
otherMembershipSchema,
|
||||
])
|
||||
|
||||
const pointExpirationSchema = z.object({
|
||||
points: z.number().int(),
|
||||
expires: z.string(),
|
||||
})
|
||||
|
||||
export const userLoyaltySchema = z.object({
|
||||
memberships: z.array(membershipSchema),
|
||||
points: z.object({
|
||||
spendable: z.number().int(),
|
||||
earned: z.number().int(),
|
||||
spent: z.number().int(),
|
||||
}),
|
||||
tier: scandicFriendsTier,
|
||||
tierExpires: z.string(),
|
||||
tierBoostedBy: z.string().nullish(),
|
||||
pointExpirations: z.array(pointExpirationSchema),
|
||||
})
|
||||
|
||||
export const getUserSchema = z
|
||||
.object({
|
||||
data: z.object({
|
||||
attributes: z.object({
|
||||
dateOfBirth: z.string().optional().default("1900-01-01"),
|
||||
email: z.string().email(),
|
||||
firstName: z.string(),
|
||||
language: z
|
||||
.string()
|
||||
// Preserve Profile v1 formatting for now so it matches ApiLang enum
|
||||
.transform((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
||||
.optional(),
|
||||
lastName: z.string(),
|
||||
phoneNumber: z.string().optional(),
|
||||
profileId: z.string(),
|
||||
membershipNumber: z.string(),
|
||||
address: z
|
||||
.object({
|
||||
city: z.string().optional(),
|
||||
country: z.string().optional(),
|
||||
countryCode: z.nativeEnum(countriesMap).optional(),
|
||||
streetAddress: z.string().optional(),
|
||||
zipCode: z.string().optional(),
|
||||
})
|
||||
.optional()
|
||||
.nullable(),
|
||||
loyalty: userLoyaltySchema.optional(),
|
||||
}),
|
||||
type: z.string(),
|
||||
}),
|
||||
})
|
||||
.transform((apiResponse) => {
|
||||
return {
|
||||
...apiResponse.data.attributes,
|
||||
membership: apiResponse.data.attributes.loyalty
|
||||
? getFriendsMembership(apiResponse.data.attributes.loyalty)
|
||||
: null,
|
||||
name: `${apiResponse.data.attributes.firstName} ${apiResponse.data.attributes.lastName}`,
|
||||
}
|
||||
})
|
||||
|
||||
// Schema is the same for upcoming and previous stays endpoints
|
||||
export const getStaysSchema = z.object({
|
||||
data: z.array(
|
||||
@@ -234,35 +118,6 @@ type GetFriendTransactionsData = z.infer<typeof getFriendTransactionsSchema>
|
||||
|
||||
export type FriendTransaction = GetFriendTransactionsData["data"][number]
|
||||
|
||||
export const creditCardSchema = z
|
||||
.object({
|
||||
attribute: z.object({
|
||||
cardName: z.string().optional(),
|
||||
alias: z.string(),
|
||||
truncatedNumber: z.string().transform((s) => s.slice(-4)),
|
||||
expirationDate: z.string(),
|
||||
cardType: z
|
||||
.string()
|
||||
.transform((s) => s.charAt(0).toLowerCase() + s.slice(1)),
|
||||
}),
|
||||
id: z.string(),
|
||||
type: z.string(),
|
||||
})
|
||||
.transform((apiResponse) => {
|
||||
return {
|
||||
id: apiResponse.id,
|
||||
type: apiResponse.attribute.cardType,
|
||||
truncatedNumber: apiResponse.attribute.truncatedNumber,
|
||||
alias: apiResponse.attribute.alias,
|
||||
expirationDate: apiResponse.attribute.expirationDate,
|
||||
cardType: apiResponse.attribute.cardType,
|
||||
}
|
||||
})
|
||||
|
||||
export const creditCardsSchema = z.object({
|
||||
data: z.array(creditCardSchema),
|
||||
})
|
||||
|
||||
export const initiateSaveCardSchema = z.object({
|
||||
data: z.object({
|
||||
attribute: z.object({
|
||||
|
||||
@@ -6,10 +6,12 @@ import {
|
||||
protectedProcedure,
|
||||
safeProtectedProcedure,
|
||||
} from "@scandic-hotels/trpc/procedures"
|
||||
import { getFriendsMembership } from "@scandic-hotels/trpc/routers/user/helpers"
|
||||
import { getVerifiedUser } from "@scandic-hotels/trpc/routers/user/utils"
|
||||
import { toApiLang } from "@scandic-hotels/trpc/utils"
|
||||
|
||||
import { isValidSession } from "@/utils/session"
|
||||
import { getFriendsMembership, getMembershipCards } from "@/utils/user"
|
||||
import { getMembershipCards } from "@/utils/user"
|
||||
|
||||
import {
|
||||
friendTransactionsInput,
|
||||
@@ -22,13 +24,14 @@ import {
|
||||
getCreditCards,
|
||||
getPreviousStays,
|
||||
getUpcomingStays,
|
||||
getVerifiedUser,
|
||||
parsedUser,
|
||||
updateStaysBookingUrl,
|
||||
} from "./utils"
|
||||
|
||||
import type { LoginType } from "@scandic-hotels/trpc/types/loginType"
|
||||
|
||||
import type {
|
||||
LoginType,
|
||||
// LoginType,
|
||||
TrackingSDKUserData,
|
||||
} from "@/types/components/tracking"
|
||||
import { Transactions } from "@/types/enums/transactions"
|
||||
|
||||
@@ -2,9 +2,12 @@ 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 { countries } from "@/constants/countries"
|
||||
import { myBookingPath } from "@/constants/myBooking"
|
||||
import { env } from "@/env/server"
|
||||
|
||||
@@ -13,93 +16,13 @@ import { encrypt } from "@/utils/encryption"
|
||||
import * as maskValue from "@/utils/maskValue"
|
||||
import { isValidSession } from "@/utils/session"
|
||||
import { getCurrentWebUrl } from "@/utils/url"
|
||||
import { getFriendsMembership } from "@/utils/user"
|
||||
|
||||
import {
|
||||
creditCardsSchema,
|
||||
type FriendTransaction,
|
||||
getStaysSchema,
|
||||
getUserSchema,
|
||||
type Stay,
|
||||
} from "./output"
|
||||
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"
|
||||
|
||||
import type { User } from "@/types/user"
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
|
||||
export async function getMembershipNumber(
|
||||
session: Session | null
|
||||
): Promise<string | undefined> {
|
||||
|
||||
Reference in New Issue
Block a user