import { Lang } from "@/constants/languages" import { env } from "@/env/server" import * as api from "@/lib/api" import { protectedProcedure, router, safeProtectedProcedure, } from "@/server/trpc" import { countries } from "@/components/TempDesignSystem/Form/Country/countries" import * as maskValue from "@/utils/maskValue" import { getMembership, getMembershipCards } from "@/utils/user" import encryptValue from "../utils/encryptValue" import { getUserInputSchema, staysInput } from "./input" import { getCreditCardsSchema, getFriendTransactionsSchema, getMembershipCardsSchema, getStaysSchema, getUserSchema, Stay, } from "./output" import { benefits, extendedUser, nextLevelPerks } from "./temp" import type { Session } from "next-auth" import type { LoginType, TrackingSDKUserData, } from "@/types/components/tracking" async function getVerifiedUser({ session }: { session: Session }) { const apiResponse = await api.get(api.endpoints.v1.profile, { cache: "no-store", headers: { Authorization: `Bearer ${session.token.access_token}`, }, }) if (!apiResponse.ok) { return null } const apiJson = await apiResponse.json() if (!apiJson.data?.attributes) { console.error(`User has no data - (user: ${JSON.stringify(session.user)})`) return null } const verifiedData = getUserSchema.safeParse(apiJson.data.attributes) if (!verifiedData.success) { console.error( `Failed to validate User - (User: ${JSON.stringify(session.user)})` ) console.error(verifiedData.error) return null } return verifiedData } function fakingRequest(payload: T): Promise { return new Promise((resolve) => { setTimeout(() => { resolve(payload) }, 1500) }) } const updateStaysBookingUrl = async ( data: Stay[], token: string, lang: Lang ) => { // Tenporary API call needed till we have user name in ctx session data const apiResponse = await api.get(api.endpoints.v1.profile, { cache: "no-store", headers: { Authorization: `Bearer ${token}`, }, }) // Temporary Url, domain and lang support for current web let localeDomain = env.PUBLIC_URL let fullBookingUrl = localeDomain + "/hotelreservation/my-booking" switch (lang) { case Lang.sv: localeDomain = localeDomain?.replace(".com", ".se") fullBookingUrl = localeDomain + "/hotelreservation/din-bokning" break case Lang.no: localeDomain = localeDomain?.replace(".com", ".no") fullBookingUrl = localeDomain + "/hotelreservation/my-booking" break case Lang.da: localeDomain = localeDomain?.replace(".com", ".dk") fullBookingUrl = localeDomain + "/hotelreservation/min-booking" break case Lang.fi: localeDomain = localeDomain?.replace(".com", ".fi") fullBookingUrl = localeDomain + "/varaa-hotelli/varauksesi" break case Lang.de: localeDomain = localeDomain?.replace(".com", ".de") fullBookingUrl = localeDomain + "/hotelreservation/my-booking" break default: break } if (apiResponse.ok) { const apiJson = await apiResponse.json() if (apiJson.data?.attributes) { return data.map((stay: Stay) => { const originalString = stay.attributes.confirmationNumber.toString() + "," + apiJson.data.attributes.lastName const encryptedBookingValue = encryptValue(originalString) const bookingUrl = !!encryptedBookingValue ? fullBookingUrl + "?RefId=" + encryptedBookingValue : fullBookingUrl + "?lastName=" + apiJson.data.attributes.lastName + "&bookingId=" + stay.attributes.confirmationNumber return { ...stay, attributes: { ...stay.attributes, bookingUrl: bookingUrl, }, } }) } } return data } export const userQueryRouter = router({ get: protectedProcedure .input(getUserInputSchema) .query(async function getUser({ ctx, input }) { const verifiedData = await getVerifiedUser({ session: ctx.session }) if (!verifiedData) { return null } const country = countries.find( (c) => c.code === verifiedData.data.address.countryCode ) const user = { ...extendedUser, address: { city: verifiedData.data.address.city, country: country?.name ?? "", countryCode: verifiedData.data.address.countryCode, streetAddress: verifiedData.data.address.streetAddress, zipCode: verifiedData.data.address.zipCode, }, dateOfBirth: verifiedData.data.dateOfBirth, email: verifiedData.data.email, firstName: verifiedData.data.firstName, language: verifiedData.data.language, lastName: verifiedData.data.lastName, memberships: verifiedData.data.memberships, name: `${verifiedData.data.firstName} ${verifiedData.data.lastName}`, phoneNumber: verifiedData.data.phoneNumber, profileId: verifiedData.data.profileId, } if (input.mask) { 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 = verifiedData.data.address?.zipCode ? maskValue.text(verifiedData.data.address.zipCode) : "" user.email = maskValue.email(user.email) user.phoneNumber = user.phoneNumber ? maskValue.phone(user.phoneNumber) : "" } return user }), name: safeProtectedProcedure.query(async function ({ ctx }) { if (!ctx.session) { return null } const verifiedData = await getVerifiedUser({ session: ctx.session }) if (!verifiedData) { return null } return { firstName: verifiedData.data.firstName, lastName: verifiedData.data.lastName, } }), membershipLevel: safeProtectedProcedure.query(async function ({ ctx }) { if (!ctx.session) { return null } const verifiedData = await getVerifiedUser({ session: ctx.session }) if (!verifiedData) { return null } const membershipLevel = getMembership(verifiedData.data.memberships) return membershipLevel }), tracking: safeProtectedProcedure.query(async function ({ ctx }) { const notLoggedInUserTrackingData: TrackingSDKUserData = { loginStatus: "Non-logged in", } if (!ctx.session) { return notLoggedInUserTrackingData } const verifiedUserData = await getVerifiedUser({ session: ctx.session }) if (!verifiedUserData) { return notLoggedInUserTrackingData } const params = new URLSearchParams() params.set("limit", "1") const previousStaysResponse = await api.get( api.endpoints.v1.previousStays, { headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, }, params ) if (!previousStaysResponse.ok) { console.error( `API Response Failed - Getting Previous Stays for tracking user` ) console.error(previousStaysResponse) return notLoggedInUserTrackingData } const previousStaysApiJson = await previousStaysResponse.json() const verifiedPreviousStaysData = getStaysSchema.safeParse(previousStaysApiJson) if (!verifiedPreviousStaysData.success) { console.error(`Failed to validate Previous Stays Data for tracking user`) console.error(verifiedPreviousStaysData.error) return notLoggedInUserTrackingData } const membership = getMembership(verifiedUserData.data.memberships) const loggedInUserTrackingData: TrackingSDKUserData = { loginStatus: "logged in", loginType: ctx.session.token.loginType as LoginType, memberId: membership?.membershipNumber, memberLevel: membership?.membershipLevel, noOfNightsStayed: verifiedPreviousStaysData.data.links?.totalCount ?? 0, totalPointsAvailableToSpend: membership?.currentPoints, loginAction: "login success", } return loggedInUserTrackingData }), benefits: router({ current: protectedProcedure.query(async function (opts) { // TODO: Make request to get user data from Scandic API return await fakingRequest(benefits) }), nextLevel: protectedProcedure.query(async function (opts) { // TODO: Make request to get user data from Scandic API return await fakingRequest(nextLevelPerks) }), }), stays: router({ previous: protectedProcedure .input(staysInput) .query(async ({ ctx, input }) => { const { limit, cursor } = input const params = new URLSearchParams() params.set("limit", limit.toString()) if (cursor) { params.set("offset", cursor.toString()) } const apiResponse = await api.get( api.endpoints.v1.previousStays, { headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, }, params ) if (!apiResponse.ok) { // switch (apiResponse.status) { // case 400: // throw badRequestError(apiResponse) // case 401: // throw unauthorizedError(apiResponse) // case 403: // throw forbiddenError(apiResponse) // default: // throw internalServerError(apiResponse) // } console.error(`API Response Failed - Getting Previous Stays`) console.error(`User: (${JSON.stringify(ctx.session.user)})`) console.error(apiResponse) return null } const apiJson = await apiResponse.json() const verifiedData = getStaysSchema.safeParse(apiJson) if (!verifiedData.success) { console.error(`Failed to validate Previous Stays Data`) console.error(`User: (${JSON.stringify(ctx.session.user)})`) console.error(verifiedData.error) return null } const nextCursor = verifiedData.data.links && verifiedData.data.links.offset < verifiedData.data.links.totalCount ? verifiedData.data.links.offset : undefined const updatedData = await updateStaysBookingUrl( verifiedData.data.data, ctx.session.token.access_token, ctx.lang ) return { data: updatedData, nextCursor, } }), upcoming: protectedProcedure .input(staysInput) .query(async ({ ctx, input }) => { const { limit, cursor } = input const params = new URLSearchParams() params.set("limit", limit.toString()) if (cursor) { params.set("offset", cursor.toString()) } const apiResponse = await api.get( api.endpoints.v1.upcomingStays, { headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, }, params ) if (!apiResponse.ok) { // switch (apiResponse.status) { // case 400: // throw badRequestError(apiResponse) // case 401: // throw unauthorizedError(apiResponse) // case 403: // throw forbiddenError(apiResponse) // default: // throw internalServerError(apiResponse) // } console.error(`API Response Failed - Getting Upcoming Stays`) console.error(`User: (${JSON.stringify(ctx.session.user)})`) console.error(apiResponse) return null } const apiJson = await apiResponse.json() const verifiedData = getStaysSchema.safeParse(apiJson) if (!verifiedData.success) { console.error(`Failed to validate Upcoming Stays Data`) console.error(`User: (${JSON.stringify(ctx.session.user)})`) console.error(verifiedData.error) return null } const nextCursor = verifiedData.data.links && verifiedData.data.links.offset < verifiedData.data.links.totalCount ? verifiedData.data.links.offset : undefined const updatedData = await updateStaysBookingUrl( verifiedData.data.data, ctx.session.token.access_token, ctx.lang ) return { data: updatedData, nextCursor, } }), }), transaction: router({ friendTransactions: protectedProcedure.query(async (opts) => { const apiResponse = await api.get(api.endpoints.v1.friendTransactions, { headers: { Authorization: `Bearer ${opts.ctx.session.token.access_token}`, }, }) if (!apiResponse.ok) { // switch (apiResponse.status) { // case 400: // throw badRequestError() // case 401: // throw unauthorizedError() // case 403: // throw forbiddenError() // default: // throw internalServerError() // } console.error(`API Response Failed - Getting Friend Transactions`) console.error(`User: (${JSON.stringify(opts.ctx.session.user)})`) console.error(apiResponse) return null } const apiJson = await apiResponse.json() const verifiedData = getFriendTransactionsSchema.safeParse(apiJson) if (!verifiedData.success) { console.error(`Failed to validate Friend Transactions Data`) console.error(`User: (${JSON.stringify(opts.ctx.session.user)})`) console.error(verifiedData.error) return null } return { data: verifiedData.data.data.map(({ attributes }) => { return { awardPoints: attributes.awardPoints, checkinDate: attributes.checkinDate, checkoutDate: attributes.checkoutDate, city: attributes.hotelInformation?.city, confirmationNumber: attributes.confirmationNumber, hotelName: attributes.hotelInformation?.name, nights: attributes.nights, } }), } }), }), creditCards: protectedProcedure.query(async function ({ ctx }) { const apiResponse = await api.get(api.endpoints.v1.creditCards, { cache: "no-store", headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, }) if (!apiResponse.ok) { console.error(`API Response Failed - Getting Creadit Cards`) console.error(`User: (${JSON.stringify(ctx.session.user)})`) console.error(apiResponse) return null } const apiJson = await apiResponse.json() const verifiedData = getCreditCardsSchema.safeParse(apiJson) if (!verifiedData.success) { console.error(`Failed to validate Credit Cards Data`) console.error(`User: (${JSON.stringify(ctx.session.user)})`) console.error(verifiedData.error) return null } return verifiedData.data.data }), membershipCards: protectedProcedure.query(async function ({ ctx }) { const apiResponse = await api.get(api.endpoints.v1.profile, { cache: "no-store", headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, }) if (!apiResponse.ok) { // switch (apiResponse.status) { // case 400: // throw badRequestError() // case 401: // throw unauthorizedError() // case 403: // throw forbiddenError() // default: // throw internalServerError() // } console.error(`API Response Failed - Getting Membership Cards`) console.error(`User: (${JSON.stringify(ctx.session.user)})`) console.error(apiResponse) } const apiJson = await apiResponse.json() const verifiedData = getMembershipCardsSchema.safeParse( apiJson.data.attributes.memberships ) if (!verifiedData.success) { console.error(`Failed to validate Memberships Cards Data`) console.error(`User: (${JSON.stringify(ctx.session.user)})`) console.error(verifiedData.error) return null } const cards = getMembershipCards(verifiedData.data) return cards }), })