Merged in feat/sw-2863-move-contentstack-router-to-trpc-package (pull request #2389)
feat(SW-2863): Move contentstack router to trpc package * Add exports to packages and lint rule to prevent relative imports * 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 * Merge branch 'master' into feat/sw-2863-move-contentstack-router-to-trpc-package Approved-by: Linus Flood
This commit is contained in:
20
packages/trpc/lib/utils/contactConfig.ts
Normal file
20
packages/trpc/lib/utils/contactConfig.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type {
|
||||
ContactConfig,
|
||||
ContactFieldGroups,
|
||||
} from "../routers/contentstack/base/output"
|
||||
|
||||
export function getValueFromContactConfig(
|
||||
keyString: string,
|
||||
data: ContactConfig
|
||||
): string | undefined {
|
||||
const [groupName, key] = keyString.split(".") as [
|
||||
ContactFieldGroups,
|
||||
keyof ContactConfig[ContactFieldGroups],
|
||||
]
|
||||
if (data[groupName]) {
|
||||
const fieldGroup = data[groupName]
|
||||
|
||||
return fieldGroup[key]
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
57
packages/trpc/lib/utils/discriminatedUnion.ts
Normal file
57
packages/trpc/lib/utils/discriminatedUnion.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import type { ZodDiscriminatedUnionOption, ZodError } from "zod"
|
||||
|
||||
export interface DiscriminatedUnionError {
|
||||
error: ZodError<unknown>
|
||||
}
|
||||
|
||||
export interface Option extends ZodDiscriminatedUnionOption<"__typename"> {}
|
||||
|
||||
/**
|
||||
* This file is created to handle our discriminated unions
|
||||
* validations primarily for union returns from Contentstacks
|
||||
* GraphQL server.
|
||||
*
|
||||
* In the case of a new block being added to the union in Contenstack,
|
||||
* Zod will throw because that typename is not expected ("invalid_union_discriminator").
|
||||
* Therefore we add a safety that we return everytime we stumble upon
|
||||
* the issue and then we filter it out in the transform.
|
||||
*
|
||||
* This replaces the `cleanEmptyObjects` function that would require
|
||||
* everyone to never make a mistake in adding __typename to root
|
||||
* anywhere (or any other potentially global fields in case the return type
|
||||
* is an Interface e.g).
|
||||
*/
|
||||
|
||||
export function discriminatedUnion<R>(options: Option[]) {
|
||||
return z
|
||||
.discriminatedUnion("__typename", [
|
||||
z.object({ __typename: z.literal(undefined) }),
|
||||
...options,
|
||||
])
|
||||
.catch(({ error }: DiscriminatedUnionError) => {
|
||||
if (
|
||||
error.issues.find(
|
||||
(issue) => issue.code === "invalid_union_discriminator"
|
||||
)
|
||||
) {
|
||||
return { __typename: undefined }
|
||||
}
|
||||
throw new Error(error.message)
|
||||
})
|
||||
.transform((data) => {
|
||||
if (data.__typename === "undefined" || data.__typename === undefined) {
|
||||
return null
|
||||
}
|
||||
return data as R
|
||||
})
|
||||
}
|
||||
|
||||
export function discriminatedUnionArray<T extends Option>(options: T[]) {
|
||||
return z
|
||||
.array(discriminatedUnion(options))
|
||||
.transform((blocks) =>
|
||||
blocks.filter((block) => !!block)
|
||||
) as unknown as z.ZodEffects<z.ZodArray<T>>
|
||||
}
|
||||
61
packages/trpc/lib/utils/entry.ts
Normal file
61
packages/trpc/lib/utils/entry.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
import { batchRequest } from "../graphql/batchRequest"
|
||||
import {
|
||||
EntryByUrlBatch1,
|
||||
EntryByUrlBatch2,
|
||||
} from "../graphql/Query/ResolveEntry.graphql"
|
||||
import { validateEntryResolveSchema } from "../types/entry"
|
||||
|
||||
export function resolveEntryCacheKey(lang: Lang, url: string) {
|
||||
return `${lang}:${url}:resolveentry`
|
||||
}
|
||||
export async function resolve(url: string, lang = Lang.en) {
|
||||
const variables = { locale: lang, url: url || "/" }
|
||||
|
||||
const cacheKey = resolveEntryCacheKey(variables.locale, variables.url)
|
||||
|
||||
// The maximum amount of content types you can query is 6, therefor more
|
||||
// than that is being batched
|
||||
const response = await batchRequest([
|
||||
{
|
||||
document: EntryByUrlBatch1,
|
||||
variables,
|
||||
cacheOptions: {
|
||||
ttl: "max",
|
||||
key: cacheKey,
|
||||
},
|
||||
},
|
||||
{
|
||||
document: EntryByUrlBatch2,
|
||||
variables,
|
||||
cacheOptions: {
|
||||
ttl: "max",
|
||||
key: cacheKey,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const validatedData = validateEntryResolveSchema.safeParse(response.data)
|
||||
|
||||
if (!validatedData.success) {
|
||||
return {
|
||||
error: validatedData.error,
|
||||
}
|
||||
}
|
||||
|
||||
for (const value of Object.values(validatedData.data)) {
|
||||
if (value.total) {
|
||||
const { content_type_uid, uid } = value.items[0].system
|
||||
return {
|
||||
contentType: content_type_uid,
|
||||
uid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
contentType: null,
|
||||
uid: null,
|
||||
}
|
||||
}
|
||||
113
packages/trpc/lib/utils/generateTag.ts
Normal file
113
packages/trpc/lib/utils/generateTag.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import type { Lang } from "@scandic-hotels/common/constants/language"
|
||||
|
||||
import type { System } from "../routers/contentstack/schemas/system"
|
||||
import type { Edges } from "../types/edges"
|
||||
import type { NodeRefs } from "../types/refs"
|
||||
|
||||
/**
|
||||
* Function to generate tag for initial refs request
|
||||
*
|
||||
* @param lang Lang
|
||||
* @param identifier Should be uid for all pages and content_type_uid for
|
||||
* everything else
|
||||
* @param affix possible extra value to add to string, e.g lang:identifier:breadcrumbs:refs
|
||||
* as it is the same entity as the actual page tag otherwise
|
||||
* @returns string
|
||||
*/
|
||||
export function generateRefsResponseTag(
|
||||
lang: Lang,
|
||||
identifier: string,
|
||||
affix?: string
|
||||
) {
|
||||
if (affix) {
|
||||
return `${lang}:${identifier}:${affix}:refs`
|
||||
}
|
||||
return `${lang}:${identifier}:refs`
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to generate all tags to references on entity
|
||||
*
|
||||
* @param lang Lang
|
||||
* @param contentTypeUid content_type_uid of reference
|
||||
* @param uid system.uid of reference
|
||||
* @returns string
|
||||
*/
|
||||
export function generateRefTag(
|
||||
lang: Lang,
|
||||
contentTypeUid: string,
|
||||
uid: string
|
||||
) {
|
||||
return `${lang}:ref:${contentTypeUid}:${uid}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to generate tag for entity being requested
|
||||
*
|
||||
* @param lang Lang
|
||||
* @param uid system.uid of entity
|
||||
* @param affix possible extra value to add to string, e.g lang:uid:breadcrumbs
|
||||
* as it is the same entity as the actual page tag otherwise
|
||||
* @returns string
|
||||
*/
|
||||
export function generateTag(lang: Lang, uid: string, affix?: string | null) {
|
||||
if (affix) {
|
||||
return `${lang}:${uid}:${affix}`
|
||||
}
|
||||
|
||||
return `${lang}:${uid}`
|
||||
}
|
||||
|
||||
export function generateTags(lang: Lang, connections: Edges<NodeRefs>[]) {
|
||||
return connections
|
||||
.map((connection) => {
|
||||
return connection.edges.map(({ node }) => {
|
||||
return generateRefTag(
|
||||
lang,
|
||||
node.system.content_type_uid,
|
||||
node.system.uid
|
||||
)
|
||||
})
|
||||
})
|
||||
.flat()
|
||||
}
|
||||
|
||||
export function generateTagsFromSystem(
|
||||
lang: Lang,
|
||||
connections: System["system"][]
|
||||
) {
|
||||
return connections.map((system) => {
|
||||
return generateRefTag(
|
||||
system.locale ?? lang,
|
||||
system.content_type_uid,
|
||||
system.uid
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to generate tags for loyalty configuration models
|
||||
*
|
||||
* @param lang Lang
|
||||
* @param contentTypeUid content_type_uid of reference
|
||||
* @param id system shared identifier, e.g reward_id, level_id
|
||||
* @returns string
|
||||
*/
|
||||
export function generateLoyaltyConfigTag(
|
||||
lang: Lang,
|
||||
contentTypeUid: string,
|
||||
id: string
|
||||
) {
|
||||
return `${lang}:loyalty_config:${contentTypeUid}:${id}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to generate tags for hotel page urls
|
||||
*
|
||||
* @param lang Lang
|
||||
* @param hotelId hotelId of reference
|
||||
* @returns string
|
||||
*/
|
||||
export function generateHotelUrlTag(lang: Lang, hotelId: string) {
|
||||
return `${lang}:hotel_page_url:${hotelId}`
|
||||
}
|
||||
34
packages/trpc/lib/utils/getSortedCities.ts
Normal file
34
packages/trpc/lib/utils/getSortedCities.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { SortOption } from "../enums/destinationFilterAndSort"
|
||||
|
||||
import type { DestinationCityListItem } from "../types/destinationCityPage"
|
||||
|
||||
const CITY_SORTING_STRATEGIES: Partial<
|
||||
Record<
|
||||
SortOption,
|
||||
(a: DestinationCityListItem, b: DestinationCityListItem) => number
|
||||
>
|
||||
> = {
|
||||
[SortOption.Name]: function (a, b) {
|
||||
return a.cityName.localeCompare(b.cityName)
|
||||
},
|
||||
[SortOption.Recommended]: function (a, b) {
|
||||
if (a.sort_order === null && b.sort_order === null) {
|
||||
return a.cityName.localeCompare(b.cityName)
|
||||
}
|
||||
if (a.sort_order === null) {
|
||||
return 1
|
||||
}
|
||||
if (b.sort_order === null) {
|
||||
return -1
|
||||
}
|
||||
return b.sort_order - a.sort_order
|
||||
},
|
||||
}
|
||||
|
||||
export function getSortedCities(
|
||||
cities: DestinationCityListItem[],
|
||||
sortOption: SortOption
|
||||
) {
|
||||
const sortFn = CITY_SORTING_STRATEGIES[sortOption]
|
||||
return sortFn ? cities.sort(sortFn) : cities
|
||||
}
|
||||
38
packages/trpc/lib/utils/imageVault.ts
Normal file
38
packages/trpc/lib/utils/imageVault.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type {
|
||||
ImageVaultAsset,
|
||||
ImageVaultAssetResponse,
|
||||
} from "../types/imageVault"
|
||||
|
||||
export function insertResponseToImageVaultAsset(
|
||||
response: ImageVaultAssetResponse
|
||||
): ImageVaultAsset {
|
||||
const alt = response.Metadata?.find((meta) =>
|
||||
meta.Name.includes("AltText_")
|
||||
)?.Value
|
||||
|
||||
const caption = response.Metadata?.find((meta) =>
|
||||
meta.Name.includes("Title_")
|
||||
)?.Value
|
||||
|
||||
const mediaConversion = response.MediaConversions[0]
|
||||
const aspectRatio =
|
||||
mediaConversion.FormatAspectRatio ||
|
||||
mediaConversion.AspectRatio ||
|
||||
mediaConversion.Width / mediaConversion.Height
|
||||
|
||||
return {
|
||||
url: mediaConversion.Url,
|
||||
id: response.Id,
|
||||
meta: {
|
||||
alt,
|
||||
caption,
|
||||
},
|
||||
title: response.Name,
|
||||
dimensions: {
|
||||
width: mediaConversion.Width,
|
||||
height: mediaConversion.Height,
|
||||
aspectRatio,
|
||||
},
|
||||
focalPoint: response.FocalPoint || { x: 50, y: 50 },
|
||||
}
|
||||
}
|
||||
15
packages/trpc/lib/utils/sortRoomConfigs.ts
Normal file
15
packages/trpc/lib/utils/sortRoomConfigs.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { AvailabilityEnum } from "../enums/selectHotel"
|
||||
|
||||
import type { RoomConfiguration } from "../types/roomAvailability"
|
||||
|
||||
// Used to ensure `Available` rooms
|
||||
// are shown before all `NotAvailable`
|
||||
const statusLookup = {
|
||||
[AvailabilityEnum.Available]: 1,
|
||||
[AvailabilityEnum.NotAvailable]: 2,
|
||||
}
|
||||
|
||||
export function sortRoomConfigs(a: RoomConfiguration, b: RoomConfiguration) {
|
||||
// @ts-expect-error - array indexing
|
||||
return statusLookup[a.status] - statusLookup[b.status]
|
||||
}
|
||||
Reference in New Issue
Block a user