Merged in feature/redis (pull request #1478)
Distributed cache * cache deleteKey now uses an options object instead of a lonely argument variable fuzzy * merge * remove debug logs and cleanup * cleanup * add fault handling * add fault handling * add pid when logging redis client creation * add identifier when logging redis client creation * cleanup * feat: add redis-api as it's own app * feature: use http wrapper for redis * feat: add the possibility to fallback to unstable_cache * Add error handling if redis cache is unresponsive * add logging for unstable_cache * merge * don't cache errors * fix: metadatabase on branchdeploys * Handle when /en/destinations throws add ErrorBoundary * Add sentry-logging when ErrorBoundary catches exception * Fix error handling for distributed cache * cleanup code * Added Application Insights back * Update generateApiKeys script and remove duplicate * Merge branch 'feature/redis' of bitbucket.org:scandic-swap/web into feature/redis * merge Approved-by: Linus Flood
This commit is contained in:
committed by
Linus Flood
parent
a8304e543e
commit
fa63b20ed0
@@ -25,7 +25,6 @@ import type {
|
||||
GetAccountPageRefsSchema,
|
||||
GetAccountPageSchema,
|
||||
} from "@/types/trpc/routers/contentstack/accountPage"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
const meter = metrics.getMeter("trpc.accountPage")
|
||||
|
||||
@@ -64,10 +63,8 @@ export const accountPageQueryRouter = router({
|
||||
uid,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(lang, uid)],
|
||||
},
|
||||
key: generateRefsResponseTag(lang, uid),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -128,10 +125,8 @@ export const accountPageQueryRouter = router({
|
||||
uid,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags,
|
||||
},
|
||||
key: tags,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { notFound } from "@/server/errors/trpc"
|
||||
import { contentstackBaseProcedure, router } from "@/server/trpc"
|
||||
import { langInput } from "@/server/utils"
|
||||
|
||||
import { getCacheClient } from "@/services/dataCache"
|
||||
import {
|
||||
generateRefsResponseTag,
|
||||
generateTag,
|
||||
@@ -107,10 +108,8 @@ const getContactConfig = cache(async (lang: Lang) => {
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [`${lang}:contact`],
|
||||
},
|
||||
key: `${lang}:contact`,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -176,10 +175,8 @@ export const baseQueryRouter = router({
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(lang, "header")],
|
||||
},
|
||||
key: generateRefsResponseTag(lang, "header"),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -244,7 +241,7 @@ export const baseQueryRouter = router({
|
||||
const response = await request<GetHeaderData>(
|
||||
GetHeader,
|
||||
{ locale: lang },
|
||||
{ cache: "force-cache", next: { tags } }
|
||||
{ key: tags, ttl: "max" }
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
@@ -305,10 +302,8 @@ export const baseQueryRouter = router({
|
||||
locale: input.lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(input.lang, "current_header")],
|
||||
},
|
||||
key: generateRefsResponseTag(input.lang, "current_header"),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
getCurrentHeaderCounter.add(1, { lang: input.lang })
|
||||
@@ -326,10 +321,8 @@ export const baseQueryRouter = router({
|
||||
GetCurrentHeader,
|
||||
{ locale: input.lang },
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(input.lang, currentHeaderUID)],
|
||||
},
|
||||
key: generateTag(input.lang, currentHeaderUID),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -397,10 +390,8 @@ export const baseQueryRouter = router({
|
||||
locale: input.lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(input.lang, "current_footer")],
|
||||
},
|
||||
key: generateRefsResponseTag(input.lang, "current_footer"),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
// There's currently no error handling/validation for the responseRef, should it be added?
|
||||
@@ -422,10 +413,8 @@ export const baseQueryRouter = router({
|
||||
locale: input.lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(input.lang, currentFooterUID)],
|
||||
},
|
||||
key: generateTag(input.lang, currentFooterUID),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -486,10 +475,8 @@ export const baseQueryRouter = router({
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(lang, "footer")],
|
||||
},
|
||||
key: generateRefsResponseTag(lang, "footer"),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -563,10 +550,8 @@ export const baseQueryRouter = router({
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags,
|
||||
},
|
||||
key: tags,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -620,157 +605,164 @@ export const baseQueryRouter = router({
|
||||
.input(langInput)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const lang = input.lang ?? ctx.lang
|
||||
|
||||
getSiteConfigRefCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig.ref start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const responseRef = await request<GetSiteConfigRefData>(
|
||||
GetSiteConfigRef,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(lang, "site_config")],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!responseRef.data) {
|
||||
const notFoundError = notFound(responseRef)
|
||||
getSiteConfigRefFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig.refs not found error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
lang,
|
||||
const cacheClient = await getCacheClient()
|
||||
return await cacheClient.cacheOrGet(
|
||||
generateRefsResponseTag(lang, "site_config", "root"),
|
||||
async () => {
|
||||
getSiteConfigRefCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig.ref start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const responseRef = await request<GetSiteConfigRefData>(
|
||||
GetSiteConfigRef,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
error: { code: notFoundError.code },
|
||||
})
|
||||
)
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedSiteConfigRef = siteConfigRefSchema.safeParse(
|
||||
responseRef.data
|
||||
)
|
||||
|
||||
if (!validatedSiteConfigRef.success) {
|
||||
getSiteConfigRefFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedSiteConfigRef.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig.refs validation error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
lang,
|
||||
},
|
||||
error: validatedSiteConfigRef.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const connections = getSiteConfigConnections(validatedSiteConfigRef.data)
|
||||
const siteConfigUid = responseRef.data.all_site_config.items[0].system.uid
|
||||
|
||||
const tags = [
|
||||
generateTagsFromSystem(lang, connections),
|
||||
generateTag(lang, siteConfigUid),
|
||||
].flat()
|
||||
|
||||
getSiteConfigRefSuccessCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig.refs success",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
|
||||
getSiteConfigCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const [siteConfigResponse, contactConfig] = await Promise.all([
|
||||
request<GetSiteConfigData>(
|
||||
GetSiteConfig,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: { tags },
|
||||
}
|
||||
),
|
||||
getContactConfig(lang),
|
||||
])
|
||||
|
||||
if (!siteConfigResponse.data) {
|
||||
const notFoundError = notFound(siteConfigResponse)
|
||||
|
||||
getSiteConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
|
||||
console.error(
|
||||
"contentstack.siteConfig not found error",
|
||||
JSON.stringify({
|
||||
query: { lang },
|
||||
error: { code: notFoundError.code },
|
||||
})
|
||||
)
|
||||
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedSiteConfig = siteConfigSchema.safeParse(
|
||||
siteConfigResponse.data
|
||||
)
|
||||
|
||||
if (!validatedSiteConfig.success) {
|
||||
getSiteConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedSiteConfig.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig validation error",
|
||||
JSON.stringify({
|
||||
query: { lang },
|
||||
error: validatedSiteConfig.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
getSiteConfigSuccessCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig success",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
|
||||
const { sitewideAlert } = validatedSiteConfig.data
|
||||
|
||||
return {
|
||||
...validatedSiteConfig.data,
|
||||
sitewideAlert: sitewideAlert
|
||||
? {
|
||||
...sitewideAlert,
|
||||
phoneContact: contactConfig
|
||||
? getAlertPhoneContactData(sitewideAlert, contactConfig)
|
||||
: null,
|
||||
{
|
||||
key: generateRefsResponseTag(lang, "site_config"),
|
||||
ttl: "max",
|
||||
}
|
||||
: null,
|
||||
}
|
||||
)
|
||||
|
||||
if (!responseRef.data) {
|
||||
const notFoundError = notFound(responseRef)
|
||||
getSiteConfigRefFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig.refs not found error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
lang,
|
||||
},
|
||||
error: { code: notFoundError.code },
|
||||
})
|
||||
)
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedSiteConfigRef = siteConfigRefSchema.safeParse(
|
||||
responseRef.data
|
||||
)
|
||||
|
||||
if (!validatedSiteConfigRef.success) {
|
||||
getSiteConfigRefFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedSiteConfigRef.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig.refs validation error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
lang,
|
||||
},
|
||||
error: validatedSiteConfigRef.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const connections = getSiteConfigConnections(
|
||||
validatedSiteConfigRef.data
|
||||
)
|
||||
const siteConfigUid =
|
||||
responseRef.data.all_site_config.items[0].system.uid
|
||||
|
||||
const tags = [
|
||||
generateTagsFromSystem(lang, connections),
|
||||
generateTag(lang, siteConfigUid),
|
||||
].flat()
|
||||
|
||||
getSiteConfigRefSuccessCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig.refs success",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
|
||||
getSiteConfigCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const [siteConfigResponse, contactConfig] = await Promise.all([
|
||||
request<GetSiteConfigData>(
|
||||
GetSiteConfig,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
key: tags,
|
||||
ttl: "max",
|
||||
}
|
||||
),
|
||||
getContactConfig(lang),
|
||||
])
|
||||
|
||||
if (!siteConfigResponse.data) {
|
||||
const notFoundError = notFound(siteConfigResponse)
|
||||
|
||||
getSiteConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
|
||||
console.error(
|
||||
"contentstack.siteConfig not found error",
|
||||
JSON.stringify({
|
||||
query: { lang },
|
||||
error: { code: notFoundError.code },
|
||||
})
|
||||
)
|
||||
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedSiteConfig = siteConfigSchema.safeParse(
|
||||
siteConfigResponse.data
|
||||
)
|
||||
|
||||
if (!validatedSiteConfig.success) {
|
||||
getSiteConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedSiteConfig.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig validation error",
|
||||
JSON.stringify({
|
||||
query: { lang },
|
||||
error: validatedSiteConfig.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
getSiteConfigSuccessCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig success",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
|
||||
const { sitewideAlert } = validatedSiteConfig.data
|
||||
|
||||
return {
|
||||
...validatedSiteConfig.data,
|
||||
sitewideAlert: sitewideAlert
|
||||
? {
|
||||
...sitewideAlert,
|
||||
phoneContact: contactConfig
|
||||
? getAlertPhoneContactData(sitewideAlert, contactConfig)
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
},
|
||||
"max"
|
||||
)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -37,6 +37,8 @@ import { request } from "@/lib/graphql/request"
|
||||
import { notFound } from "@/server/errors/trpc"
|
||||
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
|
||||
|
||||
import { generateRefsResponseTag } from "@/utils/generateTag"
|
||||
|
||||
import { breadcrumbsRefsSchema, breadcrumbsSchema } from "./output"
|
||||
import { getTags } from "./utils"
|
||||
|
||||
@@ -46,7 +48,6 @@ import type {
|
||||
RawBreadcrumbsSchema,
|
||||
} from "@/types/trpc/routers/contentstack/breadcrumbs"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
import { generateRefsResponseTag } from "@/utils/generateTag"
|
||||
|
||||
const meter = metrics.getMeter("trpc.breadcrumbs")
|
||||
|
||||
@@ -89,8 +90,8 @@ const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs<T>(
|
||||
refQuery,
|
||||
{ locale: lang, uid },
|
||||
{
|
||||
cache: `force-cache`,
|
||||
next: { tags: [generateRefsResponseTag(lang, uid)] },
|
||||
key: generateRefsResponseTag(lang, uid, "breadcrumbs"),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -129,8 +130,8 @@ const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs<T>(
|
||||
query,
|
||||
{ locale: lang, uid },
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: { tags },
|
||||
key: tags,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
type TrackingSDKPageData,
|
||||
} from "@/types/components/tracking"
|
||||
import type { GetCollectionPageSchema } from "@/types/trpc/routers/contentstack/collectionPage"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
export const collectionPageQueryRouter = router({
|
||||
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
|
||||
@@ -45,10 +44,8 @@ export const collectionPageQueryRouter = router({
|
||||
GetCollectionPage,
|
||||
{ locale: lang, uid },
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags,
|
||||
},
|
||||
key: tags,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { metrics } from "@opentelemetry/api"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { GetCollectionPageRefs } from "@/lib/graphql/Query/CollectionPage/CollectionPage.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { notFound } from "@/server/errors/trpc"
|
||||
|
||||
import { getCacheClient } from "@/services/dataCache"
|
||||
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
|
||||
|
||||
import { collectionPageRefsSchema } from "./output"
|
||||
|
||||
import { CollectionPageEnum } from "@/types/enums/collectionPage"
|
||||
import { System } from "@/types/requests/system"
|
||||
import {
|
||||
import type { System } from "@/types/requests/system"
|
||||
import type {
|
||||
CollectionPageRefs,
|
||||
GetCollectionPageRefsSchema,
|
||||
} from "@/types/trpc/routers/contentstack/collectionPage"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
const meter = metrics.getMeter("trpc.collectionPage")
|
||||
// OpenTelemetry metrics: CollectionPage
|
||||
@@ -41,15 +42,17 @@ export async function fetchCollectionPageRefs(lang: Lang, uid: string) {
|
||||
query: { lang, uid },
|
||||
})
|
||||
)
|
||||
const refsResponse = await request<GetCollectionPageRefsSchema>(
|
||||
GetCollectionPageRefs,
|
||||
{ locale: lang, uid },
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid)],
|
||||
},
|
||||
}
|
||||
|
||||
const cacheClient = await getCacheClient()
|
||||
const cacheKey = generateTag(lang, uid)
|
||||
const refsResponse = await cacheClient.cacheOrGet(
|
||||
cacheKey,
|
||||
async () =>
|
||||
await request<GetCollectionPageRefsSchema>(GetCollectionPageRefs, {
|
||||
locale: lang,
|
||||
uid,
|
||||
}),
|
||||
"max"
|
||||
)
|
||||
|
||||
if (!refsResponse.data) {
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
|
||||
import type { TrackingSDKPageData } from "@/types/components/tracking"
|
||||
import type { GetContentPageSchema } from "@/types/trpc/routers/contentstack/contentPage"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
export const contentPageQueryRouter = router({
|
||||
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
|
||||
@@ -43,33 +42,27 @@ export const contentPageQueryRouter = router({
|
||||
{
|
||||
document: GetContentPage,
|
||||
variables: { locale: lang, uid },
|
||||
options: {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags,
|
||||
},
|
||||
cacheOptions: {
|
||||
key: `${tags.join(",")}:contentPage`,
|
||||
ttl: "max",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
document: GetContentPageBlocksBatch1,
|
||||
variables: { locale: lang, uid },
|
||||
options: {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags,
|
||||
},
|
||||
cacheOptions: {
|
||||
key: `${tags.join(",")}:contentPageBlocksBatch1`,
|
||||
ttl: "max",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
document: GetContentPageBlocksBatch2,
|
||||
variables: { locale: lang, uid },
|
||||
options: {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags,
|
||||
},
|
||||
cacheOptions: {
|
||||
key: `${tags.join(",")}:contentPageBlocksBatch2`,
|
||||
ttl: "max",
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
@@ -49,21 +49,17 @@ export async function fetchContentPageRefs(lang: Lang, uid: string) {
|
||||
{
|
||||
document: GetContentPageRefs,
|
||||
variables: { locale: lang, uid },
|
||||
options: {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid)],
|
||||
},
|
||||
cacheOptions: {
|
||||
key: generateTag(lang, uid),
|
||||
ttl: "max",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: GetContentPageBlocksRefs,
|
||||
variables: { locale: lang, uid },
|
||||
options: {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid + 1)],
|
||||
},
|
||||
cacheOptions: {
|
||||
key: generateTag(lang, uid + 1),
|
||||
ttl: "max",
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
@@ -46,10 +46,8 @@ export const destinationCityPageQueryRouter = router({
|
||||
GetDestinationCityPageRefs,
|
||||
{ locale: lang, uid },
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid)],
|
||||
},
|
||||
key: generateTag(lang, uid),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -109,10 +107,8 @@ export const destinationCityPageQueryRouter = router({
|
||||
uid,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags,
|
||||
},
|
||||
key: tags,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
if (!response.data) {
|
||||
@@ -153,7 +149,11 @@ export const destinationCityPageQueryRouter = router({
|
||||
}
|
||||
const destinationCityPage = validatedResponse.data.destination_city_page
|
||||
const cityIdentifier = destinationCityPage.destination_settings.city
|
||||
const city = await getCityByCityIdentifier(cityIdentifier, serviceToken)
|
||||
const city = await getCityByCityIdentifier({
|
||||
cityIdentifier,
|
||||
lang,
|
||||
serviceToken,
|
||||
})
|
||||
|
||||
if (!city) {
|
||||
getDestinationCityPageFailCounter.add(1, {
|
||||
|
||||
@@ -14,8 +14,6 @@ import {
|
||||
getCityPageUrlsSuccessCounter,
|
||||
} from "./telemetry"
|
||||
|
||||
import type { BatchRequestDocument } from "graphql-request"
|
||||
|
||||
import { DestinationCityPageEnum } from "@/types/enums/destinationCityPage"
|
||||
import type { System } from "@/types/requests/system"
|
||||
import type {
|
||||
@@ -78,17 +76,15 @@ export async function getCityPageCount(lang: Lang) {
|
||||
"contentstack.cityPageCount start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const tags = [`${lang}:city_page_count`]
|
||||
|
||||
const response = await request<GetCityPageCountData>(
|
||||
GetCityPageCount,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags,
|
||||
},
|
||||
key: `${lang}:city_page_count`,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
if (!response.data) {
|
||||
@@ -148,21 +144,14 @@ export async function getCityPageUrls(lang: Lang) {
|
||||
// The `batchRequest` function is not working here, because the arrayMerge is
|
||||
// used for other purposes.
|
||||
const amountOfRequests = Math.ceil(count / 100)
|
||||
const requests: (BatchRequestDocument & { options?: RequestInit })[] =
|
||||
Array.from({ length: amountOfRequests }).map((_, i) => ({
|
||||
document: GetCityPageUrls,
|
||||
variables: { locale: lang, skip: i * 100 },
|
||||
options: {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [`${lang}:city_page_urls_batch_${i}`],
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const batchedResponse = await Promise.all(
|
||||
requests.map((req) =>
|
||||
request<GetCityPageUrlsData>(req.document, req.variables, req.options)
|
||||
Array.from({ length: amountOfRequests }).map((_, i) =>
|
||||
request<GetCityPageUrlsData>(
|
||||
GetCityPageUrls,
|
||||
{ locale: lang, skip: i * 100 },
|
||||
{ key: `${lang}:city_page_urls_batch_${i}`, ttl: "max" }
|
||||
)
|
||||
)
|
||||
)
|
||||
const validatedResponse = batchedCityPageUrlsSchema.safeParse(batchedResponse)
|
||||
|
||||
@@ -51,10 +51,8 @@ export const destinationCountryPageQueryRouter = router({
|
||||
GetDestinationCountryPageRefs,
|
||||
{ locale: lang, uid },
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid)],
|
||||
},
|
||||
key: generateTag(lang, uid),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -114,10 +112,8 @@ export const destinationCountryPageQueryRouter = router({
|
||||
uid,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags,
|
||||
},
|
||||
key: tags,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
if (!response.data) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { env } from "@/env/server"
|
||||
import { GetDestinationCityListData } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityListData.graphql"
|
||||
import { GetCountryPageUrls } from "@/lib/graphql/Query/DestinationCountryPage/DestinationCountryPageUrl.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { toApiLang } from "@/server/utils"
|
||||
|
||||
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
|
||||
|
||||
@@ -20,7 +18,6 @@ import {
|
||||
|
||||
import { ApiCountry, type Country } from "@/types/enums/country"
|
||||
import { DestinationCountryPageEnum } from "@/types/enums/destinationCountryPage"
|
||||
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
||||
import type { System } from "@/types/requests/system"
|
||||
import type { GetDestinationCityListDataResponse } from "@/types/trpc/routers/contentstack/destinationCityPage"
|
||||
import type {
|
||||
@@ -85,7 +82,7 @@ export async function getCityListDataByCityIdentifier(
|
||||
"contentstack.cityListData start",
|
||||
JSON.stringify({ query: { lang, cityIdentifier } })
|
||||
)
|
||||
const tag = `${lang}:city_list_data:${cityIdentifier}`
|
||||
|
||||
const response = await request<GetDestinationCityListDataResponse>(
|
||||
GetDestinationCityListData,
|
||||
{
|
||||
@@ -93,10 +90,8 @@ export async function getCityListDataByCityIdentifier(
|
||||
cityIdentifier,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [tag],
|
||||
},
|
||||
key: `${lang}:city_list_data:${cityIdentifier}`,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -148,23 +143,12 @@ export async function getCityPages(
|
||||
serviceToken: string,
|
||||
country: Country
|
||||
) {
|
||||
const apiLang = toApiLang(lang)
|
||||
const params = new URLSearchParams({
|
||||
language: apiLang,
|
||||
})
|
||||
const options: RequestOptionsWithOutBody = {
|
||||
// needs to clear default option as only
|
||||
// cache or next.revalidate is permitted
|
||||
cache: undefined,
|
||||
headers: {
|
||||
Authorization: `Bearer ${serviceToken}`,
|
||||
},
|
||||
next: {
|
||||
revalidate: env.CACHE_TIME_HOTELS,
|
||||
},
|
||||
}
|
||||
const apiCountry = ApiCountry[lang][country]
|
||||
const cities = await getCitiesByCountry([apiCountry], options, params, lang)
|
||||
const cities = await getCitiesByCountry({
|
||||
countries: [apiCountry],
|
||||
lang,
|
||||
serviceToken,
|
||||
})
|
||||
|
||||
const publishedCities = cities[apiCountry].filter((city) => city.isPublished)
|
||||
|
||||
@@ -201,10 +185,8 @@ export async function getCountryPageUrls(lang: Lang) {
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [tag],
|
||||
},
|
||||
key: tag,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { env } from "@/env/server"
|
||||
import {
|
||||
GetDestinationOverviewPage,
|
||||
GetDestinationOverviewPageRefs,
|
||||
@@ -10,9 +9,9 @@ import {
|
||||
router,
|
||||
serviceProcedure,
|
||||
} from "@/server/trpc"
|
||||
import { toApiLang } from "@/server/utils"
|
||||
|
||||
import { generateTag } from "@/utils/generateTag"
|
||||
import { safeTry } from "@/utils/safeTry"
|
||||
|
||||
import {
|
||||
getCitiesByCountry,
|
||||
@@ -42,7 +41,6 @@ import {
|
||||
TrackingChannelEnum,
|
||||
type TrackingSDKPageData,
|
||||
} from "@/types/components/tracking"
|
||||
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
||||
import type {
|
||||
GetDestinationOverviewPageData,
|
||||
GetDestinationOverviewPageRefsSchema,
|
||||
@@ -66,10 +64,8 @@ export const destinationOverviewPageQueryRouter = router({
|
||||
uid,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid)],
|
||||
},
|
||||
key: generateTag(lang, uid),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
if (!refsResponse.data) {
|
||||
@@ -133,10 +129,8 @@ export const destinationOverviewPageQueryRouter = router({
|
||||
uid,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid)],
|
||||
},
|
||||
key: generateTag(lang, uid),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
if (!response.data) {
|
||||
@@ -207,23 +201,11 @@ export const destinationOverviewPageQueryRouter = router({
|
||||
}),
|
||||
destinations: router({
|
||||
get: serviceProcedure.query(async function ({ ctx }) {
|
||||
const apiLang = toApiLang(ctx.lang)
|
||||
const params = new URLSearchParams({
|
||||
language: apiLang,
|
||||
const countries = await getCountries({
|
||||
lang: ctx.lang,
|
||||
serviceToken: ctx.serviceToken,
|
||||
})
|
||||
|
||||
const options: RequestOptionsWithOutBody = {
|
||||
// needs to clear default option as only
|
||||
// cache or next.revalidate is permitted
|
||||
cache: undefined,
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.serviceToken}`,
|
||||
},
|
||||
next: {
|
||||
revalidate: env.CACHE_TIME_HOTELS,
|
||||
},
|
||||
}
|
||||
const countries = await getCountries(options, params, ctx.lang)
|
||||
const countryPages = await getCountryPageUrls(ctx.lang)
|
||||
|
||||
if (!countries) {
|
||||
@@ -232,13 +214,12 @@ export const destinationOverviewPageQueryRouter = router({
|
||||
|
||||
const countryNames = countries.data.map((country) => country.name)
|
||||
|
||||
const citiesByCountry = await getCitiesByCountry(
|
||||
countryNames,
|
||||
options,
|
||||
params,
|
||||
ctx.lang,
|
||||
true
|
||||
)
|
||||
const citiesByCountry = await getCitiesByCountry({
|
||||
lang: ctx.lang,
|
||||
countries: countryNames,
|
||||
serviceToken: ctx.serviceToken,
|
||||
onlyPublished: true,
|
||||
})
|
||||
|
||||
const cityPages = await getCityPageUrls(ctx.lang)
|
||||
|
||||
@@ -246,15 +227,11 @@ export const destinationOverviewPageQueryRouter = router({
|
||||
Object.entries(citiesByCountry).map(async ([country, cities]) => {
|
||||
const citiesWithHotelCount = await Promise.all(
|
||||
cities.map(async (city) => {
|
||||
const hotelIdsParams = new URLSearchParams({
|
||||
language: apiLang,
|
||||
city: city.id,
|
||||
})
|
||||
|
||||
const hotels = await getHotelIdsByCityId(
|
||||
city.id,
|
||||
options,
|
||||
hotelIdsParams
|
||||
const [hotels] = await safeTry(
|
||||
getHotelIdsByCityId({
|
||||
cityId: city.id,
|
||||
serviceToken: ctx.serviceToken,
|
||||
})
|
||||
)
|
||||
|
||||
const cityPage = cityPages.find(
|
||||
@@ -268,7 +245,7 @@ export const destinationOverviewPageQueryRouter = router({
|
||||
return {
|
||||
id: city.id,
|
||||
name: city.name,
|
||||
hotelIds: hotels,
|
||||
hotelIds: hotels || [],
|
||||
hotelCount: hotels?.length ?? 0,
|
||||
url: cityPage.url,
|
||||
}
|
||||
|
||||
@@ -31,10 +31,8 @@ export const hotelPageQueryRouter = router({
|
||||
uid,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid)],
|
||||
},
|
||||
key: generateTag(lang, uid),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
if (!response.data) {
|
||||
|
||||
@@ -23,8 +23,6 @@ import {
|
||||
getHotelPageUrlsSuccessCounter,
|
||||
} from "./telemetry"
|
||||
|
||||
import type { BatchRequestDocument } from "graphql-request"
|
||||
|
||||
import { HotelPageEnum } from "@/types/enums/hotelPage"
|
||||
import type { System } from "@/types/requests/system"
|
||||
import type {
|
||||
@@ -48,10 +46,8 @@ export async function fetchHotelPageRefs(lang: Lang, uid: string) {
|
||||
GetHotelPageRefs,
|
||||
{ locale: lang, uid },
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid)],
|
||||
},
|
||||
key: generateTag(lang, uid),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
if (!refsResponse.data) {
|
||||
@@ -149,17 +145,14 @@ export async function getHotelPageCount(lang: Lang) {
|
||||
"contentstack.hotelPageCount start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const tags = [`${lang}:hotel_page_count`]
|
||||
const response = await request<GetHotelPageCountData>(
|
||||
GetHotelPageCount,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags,
|
||||
},
|
||||
key: `${lang}:hotel_page_count`,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -220,21 +213,18 @@ export async function getHotelPageUrls(lang: Lang) {
|
||||
// The `batchRequest` function is not working here, because the arrayMerge is
|
||||
// used for other purposes.
|
||||
const amountOfRequests = Math.ceil(count / 100)
|
||||
const requests: (BatchRequestDocument & { options?: RequestInit })[] =
|
||||
Array.from({ length: amountOfRequests }).map((_, i) => ({
|
||||
document: GetHotelPageUrls,
|
||||
variables: { locale: lang, skip: i * 100 },
|
||||
options: {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [`${lang}:hotel_page_urls_batch_${i}`],
|
||||
},
|
||||
},
|
||||
}))
|
||||
const requests = Array.from({ length: amountOfRequests }).map((_, i) => ({
|
||||
document: GetHotelPageUrls,
|
||||
variables: { locale: lang, skip: i * 100 },
|
||||
cacheKey: `${lang}:hotel_page_urls_batch_${i}`,
|
||||
}))
|
||||
|
||||
const batchedResponse = await Promise.all(
|
||||
requests.map((req) =>
|
||||
request<GetHotelPageUrlsData>(req.document, req.variables, req.options)
|
||||
request<GetHotelPageUrlsData>(req.document, req.variables, {
|
||||
key: req.cacheKey,
|
||||
ttl: "max",
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -149,21 +149,17 @@ export async function getUrlsOfAllLanguages(
|
||||
{
|
||||
document: daDeEnDocument,
|
||||
variables,
|
||||
options: {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: tagsDaDeEn,
|
||||
},
|
||||
cacheOptions: {
|
||||
ttl: "max",
|
||||
key: tagsDaDeEn,
|
||||
},
|
||||
},
|
||||
{
|
||||
document: fiNoSvDocument,
|
||||
variables,
|
||||
options: {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: tagsFiNoSv,
|
||||
},
|
||||
cacheOptions: {
|
||||
ttl: "max",
|
||||
key: tagsFiNoSv,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
@@ -63,7 +63,7 @@ export const getAllLoyaltyLevels = cache(async (ctx: Context) => {
|
||||
const loyaltyLevelsConfigResponse = await request<LoyaltyLevelsResponse>(
|
||||
GetAllLoyaltyLevels,
|
||||
{ lang: ctx.lang, level_ids: allLevelIds },
|
||||
{ next: { tags }, cache: "force-cache" }
|
||||
{ key: tags, ttl: "max" }
|
||||
)
|
||||
|
||||
if (!loyaltyLevelsConfigResponse.data) {
|
||||
@@ -113,10 +113,8 @@ export const getLoyaltyLevel = cache(
|
||||
GetLoyaltyLevel,
|
||||
{ lang: ctx.lang, level_id },
|
||||
{
|
||||
next: {
|
||||
tags: [generateLoyaltyConfigTag(ctx.lang, "loyalty_level", level_id)],
|
||||
},
|
||||
cache: "force-cache",
|
||||
key: generateLoyaltyConfigTag(ctx.lang, "loyalty_level", level_id),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
if (
|
||||
|
||||
@@ -25,7 +25,6 @@ import type {
|
||||
GetLoyaltyPageRefsSchema,
|
||||
GetLoyaltyPageSchema,
|
||||
} from "@/types/trpc/routers/contentstack/loyaltyPage"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
const meter = metrics.getMeter("trpc.loyaltyPage")
|
||||
// OpenTelemetry metrics: LoyaltyPage
|
||||
@@ -64,10 +63,8 @@ export const loyaltyPageQueryRouter = router({
|
||||
GetLoyaltyPageRefs,
|
||||
variables,
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(lang, uid)],
|
||||
},
|
||||
key: generateRefsResponseTag(lang, uid),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -133,8 +130,8 @@ export const loyaltyPageQueryRouter = router({
|
||||
GetLoyaltyPage,
|
||||
variables,
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: { tags },
|
||||
key: tags,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -64,10 +64,8 @@ const fetchMetadata = cache(async function fetchMemoizedMetadata<T>(
|
||||
query,
|
||||
{ locale: lang, uid },
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid, affix)],
|
||||
},
|
||||
key: generateTag(lang, uid, affix),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
if (!response.data) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ApiLang, type Lang } from "@/constants/languages"
|
||||
import { env } from "@/env/server"
|
||||
import { type Lang } from "@/constants/languages"
|
||||
import { getFiltersFromHotels } from "@/stores/destination-data/helper"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
@@ -12,7 +11,6 @@ import {
|
||||
} from "../../hotels/utils"
|
||||
|
||||
import { ApiCountry } from "@/types/enums/country"
|
||||
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
||||
import { RTETypeEnum } from "@/types/rte/enums"
|
||||
import type {
|
||||
MetadataInputSchema,
|
||||
@@ -218,17 +216,19 @@ export async function getCityData(
|
||||
const cityIdentifier = cities[0]
|
||||
|
||||
if (cityIdentifier) {
|
||||
const cityData = await getCityByCityIdentifier(
|
||||
const cityData = await getCityByCityIdentifier({
|
||||
cityIdentifier,
|
||||
serviceToken
|
||||
)
|
||||
serviceToken,
|
||||
lang,
|
||||
})
|
||||
const hotelIds = await getHotelIdsByCityIdentifier(
|
||||
cityIdentifier,
|
||||
serviceToken
|
||||
)
|
||||
const hotels = await getHotelsByHotelIds(hotelIds, lang, serviceToken)
|
||||
let filterType
|
||||
|
||||
const hotels = await getHotelsByHotelIds({ hotelIds, lang, serviceToken })
|
||||
|
||||
let filterType
|
||||
if (filter) {
|
||||
const allFilters = getFiltersFromHotels(hotels)
|
||||
const facilityFilter = allFilters.facilityFilters.find(
|
||||
@@ -264,28 +264,12 @@ export async function getCountryData(
|
||||
const translatedCountry = ApiCountry[lang][country]
|
||||
let filterType
|
||||
|
||||
const options: RequestOptionsWithOutBody = {
|
||||
// needs to clear default option as only
|
||||
// cache or next.revalidate is permitted
|
||||
cache: undefined,
|
||||
headers: {
|
||||
Authorization: `Bearer ${serviceToken}`,
|
||||
},
|
||||
next: {
|
||||
revalidate: env.CACHE_TIME_HOTELS,
|
||||
},
|
||||
}
|
||||
const hotelIdsParams = new URLSearchParams({
|
||||
language: ApiLang.En,
|
||||
const hotelIds = await getHotelIdsByCountry({
|
||||
country,
|
||||
serviceToken,
|
||||
})
|
||||
const hotelIds = await getHotelIdsByCountry(
|
||||
country,
|
||||
options,
|
||||
hotelIdsParams
|
||||
)
|
||||
|
||||
const hotels = await getHotelsByHotelIds(hotelIds, lang, serviceToken)
|
||||
const hotels = await getHotelsByHotelIds({ hotelIds, lang, serviceToken })
|
||||
|
||||
if (filter) {
|
||||
const allFilters = getFiltersFromHotels(hotels)
|
||||
|
||||
@@ -84,10 +84,8 @@ export const pageSettingsQueryRouter = router({
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid, affix)],
|
||||
},
|
||||
key: generateTag(lang, uid, affix),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -32,10 +32,8 @@ export const getSasTierComparison = cache(async (ctx: Context) => {
|
||||
GetAllSasTierComparison,
|
||||
{ lang: ctx.lang },
|
||||
{
|
||||
next: {
|
||||
tags: [tag],
|
||||
},
|
||||
cache: "force-cache",
|
||||
key: tag,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from "@/server/trpc"
|
||||
import { langInput } from "@/server/utils"
|
||||
|
||||
import { getCacheClient } from "@/services/dataCache"
|
||||
|
||||
import { getAllLoyaltyLevels, getLoyaltyLevel } from "../loyaltyLevel/query"
|
||||
import {
|
||||
rewardsAllInput,
|
||||
@@ -46,8 +48,6 @@ import {
|
||||
getUnwrapSurpriseSuccessCounter,
|
||||
} from "./utils"
|
||||
|
||||
const ONE_HOUR = 60 * 60
|
||||
|
||||
export const rewardQueryRouter = router({
|
||||
all: contentStackBaseWithServiceProcedure
|
||||
.input(rewardsAllInput)
|
||||
@@ -174,131 +174,139 @@ export const rewardQueryRouter = router({
|
||||
? api.endpoints.v1.Profile.Reward.reward
|
||||
: api.endpoints.v1.Profile.reward
|
||||
|
||||
const apiResponse = await api.get(endpoint, {
|
||||
cache: undefined, // override defaultOptions
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
||||
},
|
||||
next: { revalidate: ONE_HOUR },
|
||||
})
|
||||
const cacheClient = await getCacheClient()
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.reward error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
return cacheClient.cacheOrGet(
|
||||
endpoint,
|
||||
async () => {
|
||||
const apiResponse = await api.get(endpoint, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
||||
},
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const data = await apiResponse.json()
|
||||
|
||||
const validatedApiRewards = isNewEndpoint
|
||||
? validateCategorizedRewardsSchema.safeParse(data)
|
||||
: validateApiRewardSchema.safeParse(data)
|
||||
|
||||
if (!validatedApiRewards.success) {
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
locale: ctx.lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedApiRewards.error),
|
||||
})
|
||||
console.error(validatedApiRewards.error)
|
||||
console.error(
|
||||
"contentstack.rewards validation error",
|
||||
JSON.stringify({
|
||||
query: { locale: ctx.lang },
|
||||
error: validatedApiRewards.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const rewardIds = getNonRedeemedRewardIds(validatedApiRewards.data)
|
||||
|
||||
const cmsRewards = await getCmsRewards(ctx.lang, rewardIds)
|
||||
|
||||
if (!cmsRewards) {
|
||||
return null
|
||||
}
|
||||
|
||||
const wrappedSurprisesIds = validatedApiRewards.data
|
||||
.filter(
|
||||
(reward) =>
|
||||
reward.type === "coupon" &&
|
||||
reward.rewardType === "Surprise" &&
|
||||
"coupon" in reward &&
|
||||
reward.coupon.some(({ unwrapped }) => !unwrapped)
|
||||
)
|
||||
.map(({ rewardId }) => rewardId)
|
||||
|
||||
const rewards = cmsRewards
|
||||
.filter(
|
||||
(cmsReward) => !wrappedSurprisesIds.includes(cmsReward.reward_id)
|
||||
)
|
||||
.map((cmsReward) => {
|
||||
const apiReward = validatedApiRewards.data.find(
|
||||
({ rewardId }) => rewardId === cmsReward.reward_id
|
||||
)
|
||||
|
||||
const redeemableCoupons =
|
||||
(apiReward &&
|
||||
"coupon" in apiReward &&
|
||||
apiReward.coupon.filter(
|
||||
(coupon) => coupon.state !== "redeemed" && coupon.unwrapped
|
||||
)) ||
|
||||
[]
|
||||
|
||||
const firstRedeemableCouponToExpire = redeemableCoupons.reduce(
|
||||
(earliest, coupon) => {
|
||||
if (dt(coupon.expiresAt).isBefore(dt(earliest.expiresAt))) {
|
||||
return coupon
|
||||
}
|
||||
return earliest
|
||||
},
|
||||
redeemableCoupons[0]
|
||||
)?.couponCode
|
||||
|
||||
return {
|
||||
...cmsReward,
|
||||
id: apiReward?.id,
|
||||
rewardType: apiReward?.rewardType,
|
||||
redeemLocation: apiReward?.redeemLocation,
|
||||
rewardTierLevel:
|
||||
apiReward && "rewardTierLevel" in apiReward
|
||||
? apiReward.rewardTierLevel
|
||||
: undefined,
|
||||
operaRewardId:
|
||||
apiReward && "operaRewardId" in apiReward
|
||||
? apiReward.operaRewardId
|
||||
: "",
|
||||
categories:
|
||||
apiReward && "categories" in apiReward
|
||||
? apiReward.categories || []
|
||||
: [],
|
||||
couponCode: firstRedeemableCouponToExpire,
|
||||
coupons:
|
||||
apiReward && "coupon" in apiReward ? apiReward.coupon || [] : [],
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.reward error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
getCurrentRewardSuccessCounter.add(1)
|
||||
const data = await apiResponse.json()
|
||||
|
||||
return { rewards }
|
||||
const validatedApiRewards = isNewEndpoint
|
||||
? validateCategorizedRewardsSchema.safeParse(data)
|
||||
: validateApiRewardSchema.safeParse(data)
|
||||
|
||||
if (!validatedApiRewards.success) {
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
locale: ctx.lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedApiRewards.error),
|
||||
})
|
||||
console.error(validatedApiRewards.error)
|
||||
console.error(
|
||||
"contentstack.rewards validation error",
|
||||
JSON.stringify({
|
||||
query: { locale: ctx.lang },
|
||||
error: validatedApiRewards.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const rewardIds = getNonRedeemedRewardIds(validatedApiRewards.data)
|
||||
|
||||
const cmsRewards = await getCmsRewards(ctx.lang, rewardIds)
|
||||
|
||||
if (!cmsRewards) {
|
||||
return null
|
||||
}
|
||||
|
||||
const wrappedSurprisesIds = validatedApiRewards.data
|
||||
.filter(
|
||||
(reward) =>
|
||||
reward.type === "coupon" &&
|
||||
reward.rewardType === "Surprise" &&
|
||||
"coupon" in reward &&
|
||||
reward.coupon.some(({ unwrapped }) => !unwrapped)
|
||||
)
|
||||
.map(({ rewardId }) => rewardId)
|
||||
|
||||
const rewards = cmsRewards
|
||||
.filter(
|
||||
(cmsReward) => !wrappedSurprisesIds.includes(cmsReward.reward_id)
|
||||
)
|
||||
.map((cmsReward) => {
|
||||
const apiReward = validatedApiRewards.data.find(
|
||||
({ rewardId }) => rewardId === cmsReward.reward_id
|
||||
)
|
||||
|
||||
const redeemableCoupons =
|
||||
(apiReward &&
|
||||
"coupon" in apiReward &&
|
||||
apiReward.coupon.filter(
|
||||
(coupon) => coupon.state !== "redeemed" && coupon.unwrapped
|
||||
)) ||
|
||||
[]
|
||||
|
||||
const firstRedeemableCouponToExpire = redeemableCoupons.reduce(
|
||||
(earliest, coupon) => {
|
||||
if (dt(coupon.expiresAt).isBefore(dt(earliest.expiresAt))) {
|
||||
return coupon
|
||||
}
|
||||
return earliest
|
||||
},
|
||||
redeemableCoupons[0]
|
||||
)?.couponCode
|
||||
|
||||
return {
|
||||
...cmsReward,
|
||||
id: apiReward?.id,
|
||||
rewardType: apiReward?.rewardType,
|
||||
redeemLocation: apiReward?.redeemLocation,
|
||||
rewardTierLevel:
|
||||
apiReward && "rewardTierLevel" in apiReward
|
||||
? apiReward.rewardTierLevel
|
||||
: undefined,
|
||||
operaRewardId:
|
||||
apiReward && "operaRewardId" in apiReward
|
||||
? apiReward.operaRewardId
|
||||
: "",
|
||||
categories:
|
||||
apiReward && "categories" in apiReward
|
||||
? apiReward.categories || []
|
||||
: [],
|
||||
couponCode: firstRedeemableCouponToExpire,
|
||||
coupons:
|
||||
apiReward && "coupon" in apiReward
|
||||
? apiReward.coupon || []
|
||||
: [],
|
||||
}
|
||||
})
|
||||
|
||||
getCurrentRewardSuccessCounter.add(1)
|
||||
|
||||
return { rewards }
|
||||
},
|
||||
"1h"
|
||||
)
|
||||
}),
|
||||
surprises: contentStackBaseWithProtectedProcedure
|
||||
.input(langInput.optional()) // lang is required for client, but not for server
|
||||
@@ -310,114 +318,120 @@ export const rewardQueryRouter = router({
|
||||
? api.endpoints.v1.Profile.Reward.reward
|
||||
: api.endpoints.v1.Profile.reward
|
||||
|
||||
const apiResponse = await api.get(endpoint, {
|
||||
cache: undefined,
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
||||
},
|
||||
next: { revalidate: ONE_HOUR },
|
||||
})
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.reward error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
const cacheClient = await getCacheClient()
|
||||
return await cacheClient.cacheOrGet(
|
||||
endpoint,
|
||||
async () => {
|
||||
const apiResponse = await api.get(endpoint, {
|
||||
cache: undefined,
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
||||
},
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const data = await apiResponse.json()
|
||||
const validatedApiRewards = isNewEndpoint
|
||||
? validateCategorizedRewardsSchema.safeParse(data)
|
||||
: validateApiRewardSchema.safeParse(data)
|
||||
|
||||
if (!validatedApiRewards.success) {
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
locale: ctx.lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedApiRewards.error),
|
||||
})
|
||||
console.error(validatedApiRewards.error)
|
||||
console.error(
|
||||
"contentstack.surprises validation error",
|
||||
JSON.stringify({
|
||||
query: { locale: ctx.lang },
|
||||
error: validatedApiRewards.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const rewardIds = validatedApiRewards.data
|
||||
.map((reward) => reward?.rewardId)
|
||||
.filter((rewardId): rewardId is string => !!rewardId)
|
||||
.sort()
|
||||
|
||||
const cmsRewards = await getCmsRewards(ctx.lang, rewardIds)
|
||||
|
||||
if (!cmsRewards) {
|
||||
return null
|
||||
}
|
||||
|
||||
getCurrentRewardSuccessCounter.add(1)
|
||||
|
||||
const surprises: Surprise[] = validatedApiRewards.data
|
||||
// TODO: Add predicates once legacy endpoints are removed
|
||||
.filter((reward) => {
|
||||
if (reward?.rewardType !== "Surprise") {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!("coupon" in reward)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const unwrappedCoupons =
|
||||
reward.coupon.filter((coupon) => !coupon.unwrapped) || []
|
||||
if (unwrappedCoupons.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
.map((surprise) => {
|
||||
const reward = cmsRewards.find(
|
||||
({ reward_id }) => surprise.rewardId === reward_id
|
||||
)
|
||||
|
||||
if (!reward) {
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.reward error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
...reward,
|
||||
id: surprise.id,
|
||||
rewardType: surprise.rewardType,
|
||||
rewardTierLevel: undefined,
|
||||
redeemLocation: surprise.redeemLocation,
|
||||
categories:
|
||||
"categories" in surprise ? surprise.categories || [] : [],
|
||||
coupons: "coupon" in surprise ? surprise.coupon || [] : [],
|
||||
}
|
||||
})
|
||||
.flatMap((surprises) => (surprises ? [surprises] : []))
|
||||
const data = await apiResponse.json()
|
||||
const validatedApiRewards = isNewEndpoint
|
||||
? validateCategorizedRewardsSchema.safeParse(data)
|
||||
: validateApiRewardSchema.safeParse(data)
|
||||
|
||||
return surprises
|
||||
if (!validatedApiRewards.success) {
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
locale: ctx.lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedApiRewards.error),
|
||||
})
|
||||
console.error(validatedApiRewards.error)
|
||||
console.error(
|
||||
"contentstack.surprises validation error",
|
||||
JSON.stringify({
|
||||
query: { locale: ctx.lang },
|
||||
error: validatedApiRewards.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const rewardIds = validatedApiRewards.data
|
||||
.map((reward) => reward?.rewardId)
|
||||
.filter((rewardId): rewardId is string => !!rewardId)
|
||||
.sort()
|
||||
|
||||
const cmsRewards = await getCmsRewards(ctx.lang, rewardIds)
|
||||
|
||||
if (!cmsRewards) {
|
||||
return null
|
||||
}
|
||||
|
||||
getCurrentRewardSuccessCounter.add(1)
|
||||
|
||||
const surprises: Surprise[] = validatedApiRewards.data
|
||||
// TODO: Add predicates once legacy endpoints are removed
|
||||
.filter((reward) => {
|
||||
if (reward?.rewardType !== "Surprise") {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!("coupon" in reward)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const unwrappedCoupons =
|
||||
reward.coupon.filter((coupon) => !coupon.unwrapped) || []
|
||||
if (unwrappedCoupons.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
.map((surprise) => {
|
||||
const reward = cmsRewards.find(
|
||||
({ reward_id }) => surprise.rewardId === reward_id
|
||||
)
|
||||
|
||||
if (!reward) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
...reward,
|
||||
id: surprise.id,
|
||||
rewardType: surprise.rewardType,
|
||||
rewardTierLevel: undefined,
|
||||
redeemLocation: surprise.redeemLocation,
|
||||
coupons: "coupon" in surprise ? surprise.coupon || [] : [],
|
||||
categories:
|
||||
"categories" in surprise ? surprise.categories || [] : [],
|
||||
}
|
||||
})
|
||||
.flatMap((surprises) => (surprises ? [surprises] : []))
|
||||
|
||||
return surprises
|
||||
},
|
||||
"1h"
|
||||
)
|
||||
}),
|
||||
unwrap: protectedProcedure
|
||||
.input(rewardsUpdateInput)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { metrics } from "@opentelemetry/api"
|
||||
import { unstable_cache } from "next/cache"
|
||||
|
||||
import { env } from "@/env/server"
|
||||
import * as api from "@/lib/api"
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { notFound } from "@/server/errors/trpc"
|
||||
|
||||
import { getCacheClient } from "@/services/dataCache"
|
||||
import { generateLoyaltyConfigTag, generateTag } from "@/utils/generateTag"
|
||||
|
||||
import {
|
||||
@@ -85,8 +85,6 @@ export const getAllCMSRewardRefsSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.reward.all-success"
|
||||
)
|
||||
|
||||
const ONE_HOUR = 60 * 60
|
||||
|
||||
export function getUniqueRewardIds(rewardIds: string[]) {
|
||||
const uniqueRewardIds = new Set(rewardIds)
|
||||
return Array.from(uniqueRewardIds)
|
||||
@@ -96,123 +94,133 @@ export function getUniqueRewardIds(rewardIds: string[]) {
|
||||
* Uses the legacy profile/v1/Profile/tierRewards endpoint.
|
||||
* TODO: Delete when the new endpoint is out in production.
|
||||
*/
|
||||
export const getAllCachedApiRewards = unstable_cache(
|
||||
async function (token) {
|
||||
const apiResponse = await api.get(api.endpoints.v1.Profile.tierRewards, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
export async function getAllCachedApiRewards(token: string) {
|
||||
const cacheClient = await getCacheClient()
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getAllRewardFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
return await cacheClient.cacheOrGet(
|
||||
"getAllApiRewards",
|
||||
async () => {
|
||||
const apiResponse = await api.get(api.endpoints.v1.Profile.tierRewards, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
console.error(
|
||||
"api.rewards.tierRewards error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getAllRewardFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
}),
|
||||
})
|
||||
)
|
||||
console.error(
|
||||
"api.rewards.tierRewards error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
throw apiResponse
|
||||
}
|
||||
throw apiResponse
|
||||
}
|
||||
|
||||
const data = await apiResponse.json()
|
||||
const validatedApiTierRewards = validateApiTierRewardsSchema.safeParse(data)
|
||||
const data = await apiResponse.json()
|
||||
const validatedApiTierRewards =
|
||||
validateApiTierRewardsSchema.safeParse(data)
|
||||
|
||||
if (!validatedApiTierRewards.success) {
|
||||
getAllRewardFailCounter.add(1, {
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedApiTierRewards.error),
|
||||
})
|
||||
console.error(validatedApiTierRewards.error)
|
||||
console.error(
|
||||
"api.rewards validation error",
|
||||
JSON.stringify({
|
||||
error: validatedApiTierRewards.error,
|
||||
if (!validatedApiTierRewards.success) {
|
||||
getAllRewardFailCounter.add(1, {
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedApiTierRewards.error),
|
||||
})
|
||||
)
|
||||
throw validatedApiTierRewards.error
|
||||
}
|
||||
console.error(validatedApiTierRewards.error)
|
||||
console.error(
|
||||
"api.rewards validation error",
|
||||
JSON.stringify({
|
||||
error: validatedApiTierRewards.error,
|
||||
})
|
||||
)
|
||||
throw validatedApiTierRewards.error
|
||||
}
|
||||
|
||||
return validatedApiTierRewards.data
|
||||
},
|
||||
["getAllApiRewards"],
|
||||
{ revalidate: ONE_HOUR }
|
||||
)
|
||||
return validatedApiTierRewards.data
|
||||
},
|
||||
"1h"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached for 1 hour.
|
||||
*/
|
||||
export const getCachedAllTierRewards = unstable_cache(
|
||||
async function (token) {
|
||||
const apiResponse = await api.get(
|
||||
api.endpoints.v1.Profile.Reward.allTiers,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
export async function getCachedAllTierRewards(token: string) {
|
||||
const cacheClient = await getCacheClient()
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getAllRewardFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.rewards.allTiers error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
return await cacheClient.cacheOrGet(
|
||||
"getAllTierRewards",
|
||||
async () => {
|
||||
const apiResponse = await api.get(
|
||||
api.endpoints.v1.Profile.Reward.allTiers,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getAllRewardFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
}),
|
||||
})
|
||||
)
|
||||
console.error(
|
||||
"api.rewards.allTiers error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
throw apiResponse
|
||||
}
|
||||
throw apiResponse
|
||||
}
|
||||
|
||||
const data = await apiResponse.json()
|
||||
const validatedApiAllTierRewards = validateApiAllTiersSchema.safeParse(data)
|
||||
const data = await apiResponse.json()
|
||||
const validatedApiAllTierRewards =
|
||||
validateApiAllTiersSchema.safeParse(data)
|
||||
|
||||
if (!validatedApiAllTierRewards.success) {
|
||||
getAllRewardFailCounter.add(1, {
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedApiAllTierRewards.error),
|
||||
})
|
||||
console.error(validatedApiAllTierRewards.error)
|
||||
console.error(
|
||||
"api.rewards validation error",
|
||||
JSON.stringify({
|
||||
error: validatedApiAllTierRewards.error,
|
||||
if (!validatedApiAllTierRewards.success) {
|
||||
getAllRewardFailCounter.add(1, {
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedApiAllTierRewards.error),
|
||||
})
|
||||
)
|
||||
throw validatedApiAllTierRewards.error
|
||||
}
|
||||
console.error(validatedApiAllTierRewards.error)
|
||||
console.error(
|
||||
"api.rewards validation error",
|
||||
JSON.stringify({
|
||||
error: validatedApiAllTierRewards.error,
|
||||
})
|
||||
)
|
||||
throw validatedApiAllTierRewards.error
|
||||
}
|
||||
|
||||
return validatedApiAllTierRewards.data
|
||||
},
|
||||
["getApiAllTierRewards"],
|
||||
{ revalidate: ONE_HOUR }
|
||||
)
|
||||
return validatedApiAllTierRewards.data
|
||||
},
|
||||
"1h"
|
||||
)
|
||||
}
|
||||
|
||||
export async function getCmsRewards(lang: Lang, rewardIds: string[]) {
|
||||
const tags = rewardIds.map((id) =>
|
||||
@@ -235,10 +243,8 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) {
|
||||
rewardIds,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: rewardIds.map((rewardId) => generateTag(lang, rewardId)),
|
||||
},
|
||||
key: rewardIds.map((rewardId) => generateTag(lang, rewardId)),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
if (!refsResponse.data) {
|
||||
@@ -292,7 +298,10 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) {
|
||||
locale: lang,
|
||||
rewardIds,
|
||||
},
|
||||
{ next: { tags }, cache: "force-cache" }
|
||||
{
|
||||
key: tags,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
} else {
|
||||
cmsRewardsResponse = await request<CmsRewardsResponse>(
|
||||
@@ -301,7 +310,7 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) {
|
||||
locale: lang,
|
||||
rewardIds,
|
||||
},
|
||||
{ next: { tags }, cache: "force-cache" }
|
||||
{ key: tags, ttl: "max" }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -46,10 +46,8 @@ export const startPageQueryRouter = router({
|
||||
uid,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid)],
|
||||
},
|
||||
key: generateTag(lang, uid),
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
if (!refsResponse.data) {
|
||||
@@ -118,10 +116,8 @@ export const startPageQueryRouter = router({
|
||||
uid,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags,
|
||||
},
|
||||
key: tags,
|
||||
ttl: "max",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -269,6 +269,7 @@ export const countriesSchema = z.object({
|
||||
}),
|
||||
})
|
||||
|
||||
export type Cities = z.infer<typeof citiesSchema>
|
||||
export const citiesSchema = z
|
||||
.object({
|
||||
data: z.array(citySchema),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
productTypePriceSchema,
|
||||
productTypePointsSchema,
|
||||
productTypePriceSchema,
|
||||
} from "../productTypePrice"
|
||||
|
||||
export const productTypeSchema = z
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import deepmerge from "deepmerge"
|
||||
import { unstable_cache } from "next/cache"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { env } from "@/env/server"
|
||||
import * as api from "@/lib/api"
|
||||
import { toApiLang } from "@/server/utils"
|
||||
|
||||
import { getCacheClient } from "@/services/dataCache"
|
||||
|
||||
import { getHotelPageUrls } from "../contentstack/hotelPage/utils"
|
||||
import { metrics } from "./metrics"
|
||||
import {
|
||||
type Cities,
|
||||
citiesByCountrySchema,
|
||||
citiesSchema,
|
||||
countriesSchema,
|
||||
@@ -18,12 +20,10 @@ import {
|
||||
import { getHotel } from "./query"
|
||||
|
||||
import { PointOfInterestGroupEnum } from "@/types/enums/pointOfInterest"
|
||||
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
||||
import type { HotelDataWithUrl } from "@/types/hotel"
|
||||
import type {
|
||||
CitiesGroupedByCountry,
|
||||
CityLocation,
|
||||
HotelLocation,
|
||||
} from "@/types/trpc/routers/hotel/locations"
|
||||
import type { Endpoint } from "@/lib/api/endpoints"
|
||||
|
||||
@@ -58,18 +58,21 @@ export function getPoiGroupByCategoryName(category: string | undefined) {
|
||||
export const locationsAffix = "locations"
|
||||
|
||||
export const TWENTYFOUR_HOURS = 60 * 60 * 24
|
||||
export async function getCity(
|
||||
cityUrl: string,
|
||||
options: RequestOptionsWithOutBody,
|
||||
lang: Lang,
|
||||
relationshipCity: HotelLocation["relationships"]["city"]
|
||||
) {
|
||||
return unstable_cache(
|
||||
async function (locationCityUrl: string) {
|
||||
const url = new URL(locationCityUrl)
|
||||
export async function getCity({
|
||||
cityUrl,
|
||||
serviceToken,
|
||||
}: {
|
||||
cityUrl: string
|
||||
serviceToken: string
|
||||
}): Promise<Cities> {
|
||||
const cacheClient = await getCacheClient()
|
||||
return await cacheClient.cacheOrGet(
|
||||
cityUrl,
|
||||
async () => {
|
||||
const url = new URL(cityUrl)
|
||||
const cityResponse = await api.get(
|
||||
url.pathname as Endpoint,
|
||||
options,
|
||||
{ headers: { Authorization: `Bearer ${serviceToken}` } },
|
||||
url.searchParams
|
||||
)
|
||||
|
||||
@@ -81,33 +84,44 @@ export async function getCity(
|
||||
const city = citiesSchema.safeParse(cityJson)
|
||||
if (!city.success) {
|
||||
console.info(`Validation of city failed`)
|
||||
console.info(`cityUrl: ${locationCityUrl}`)
|
||||
console.info(`cityUrl: ${cityUrl}`)
|
||||
console.error(city.error)
|
||||
return null
|
||||
}
|
||||
|
||||
return city.data
|
||||
},
|
||||
[cityUrl, `${lang}:${relationshipCity}`],
|
||||
{ revalidate: TWENTYFOUR_HOURS }
|
||||
)(cityUrl)
|
||||
"1d"
|
||||
)
|
||||
}
|
||||
|
||||
export async function getCountries(
|
||||
options: RequestOptionsWithOutBody,
|
||||
params: URLSearchParams,
|
||||
export async function getCountries({
|
||||
lang,
|
||||
serviceToken,
|
||||
}: {
|
||||
lang: Lang
|
||||
) {
|
||||
return unstable_cache(
|
||||
async function (searchParams) {
|
||||
serviceToken: string
|
||||
}) {
|
||||
const cacheClient = await getCacheClient()
|
||||
return await cacheClient.cacheOrGet(
|
||||
`${lang}:${locationsAffix}:countries`,
|
||||
async () => {
|
||||
const params = new URLSearchParams({
|
||||
language: toApiLang(lang),
|
||||
})
|
||||
|
||||
const countryResponse = await api.get(
|
||||
api.endpoints.v1.Hotel.countries,
|
||||
options,
|
||||
searchParams
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${serviceToken}`,
|
||||
},
|
||||
},
|
||||
params
|
||||
)
|
||||
|
||||
if (!countryResponse.ok) {
|
||||
return null
|
||||
throw new Error("Unable to fetch countries")
|
||||
}
|
||||
|
||||
const countriesJson = await countryResponse.json()
|
||||
@@ -120,114 +134,128 @@ export async function getCountries(
|
||||
|
||||
return countries.data
|
||||
},
|
||||
[`${lang}:${locationsAffix}:countries`, params.toString()],
|
||||
{ revalidate: TWENTYFOUR_HOURS }
|
||||
)(params)
|
||||
"1d"
|
||||
)
|
||||
}
|
||||
|
||||
export async function getCitiesByCountry(
|
||||
countries: string[],
|
||||
options: RequestOptionsWithOutBody,
|
||||
params: URLSearchParams,
|
||||
lang: Lang,
|
||||
onlyPublished = false, // false by default as it might be used in other places
|
||||
affix: string = locationsAffix
|
||||
) {
|
||||
return unstable_cache(
|
||||
async function (
|
||||
searchParams: URLSearchParams,
|
||||
searchedCountries: string[]
|
||||
) {
|
||||
const citiesGroupedByCountry: CitiesGroupedByCountry = {}
|
||||
|
||||
await Promise.all(
|
||||
searchedCountries.map(async (country) => {
|
||||
export async function getCitiesByCountry({
|
||||
countries,
|
||||
lang,
|
||||
onlyPublished = false,
|
||||
affix = locationsAffix,
|
||||
serviceToken,
|
||||
}: {
|
||||
countries: string[]
|
||||
lang: Lang
|
||||
onlyPublished?: boolean // false by default as it might be used in other places
|
||||
affix?: string
|
||||
serviceToken: string
|
||||
}): Promise<CitiesGroupedByCountry> {
|
||||
const cacheClient = await getCacheClient()
|
||||
const allCitiesByCountries = await Promise.all(
|
||||
countries.map(async (country) => {
|
||||
return cacheClient.cacheOrGet(
|
||||
`${lang}:${affix}:cities-by-country:${country}`,
|
||||
async () => {
|
||||
const params = new URLSearchParams({
|
||||
language: toApiLang(lang),
|
||||
})
|
||||
const countryResponse = await api.get(
|
||||
api.endpoints.v1.Hotel.Cities.country(country),
|
||||
options,
|
||||
searchParams
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${serviceToken}`,
|
||||
},
|
||||
},
|
||||
params
|
||||
)
|
||||
|
||||
if (!countryResponse.ok) {
|
||||
return null
|
||||
throw new Error(`Unable to fetch cities by country ${country}`)
|
||||
}
|
||||
|
||||
const countryJson = await countryResponse.json()
|
||||
const citiesByCountry = citiesByCountrySchema.safeParse(countryJson)
|
||||
if (!citiesByCountry.success) {
|
||||
console.info(`Failed to validate Cities by Country payload`)
|
||||
console.error(`Unable to parse cities by country ${country}`)
|
||||
console.error(citiesByCountry.error)
|
||||
return null
|
||||
throw new Error(`Unable to parse cities by country ${country}`)
|
||||
}
|
||||
|
||||
const cities = onlyPublished
|
||||
? citiesByCountry.data.data.filter((city) => city.isPublished)
|
||||
: citiesByCountry.data.data
|
||||
citiesGroupedByCountry[country] = cities
|
||||
return true
|
||||
})
|
||||
return { ...citiesByCountry.data, country }
|
||||
},
|
||||
"1d"
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
return citiesGroupedByCountry
|
||||
},
|
||||
[
|
||||
`${lang}:${affix}:cities-by-country`,
|
||||
params.toString(),
|
||||
JSON.stringify(countries),
|
||||
],
|
||||
{ revalidate: TWENTYFOUR_HOURS }
|
||||
)(params, countries)
|
||||
const filteredCitiesByCountries = allCitiesByCountries.map((country) => ({
|
||||
...country,
|
||||
data: onlyPublished
|
||||
? country.data.filter((city) => city.isPublished)
|
||||
: country.data,
|
||||
}))
|
||||
|
||||
const groupedCitiesByCountry: CitiesGroupedByCountry =
|
||||
filteredCitiesByCountries.reduce((acc, { country, data }) => {
|
||||
acc[country] = data
|
||||
return acc
|
||||
}, {} as CitiesGroupedByCountry)
|
||||
|
||||
return groupedCitiesByCountry
|
||||
}
|
||||
|
||||
export async function getLocations(
|
||||
lang: Lang,
|
||||
options: RequestOptionsWithOutBody,
|
||||
params: URLSearchParams,
|
||||
export async function getLocations({
|
||||
lang,
|
||||
citiesByCountry,
|
||||
serviceToken,
|
||||
}: {
|
||||
lang: Lang
|
||||
citiesByCountry: CitiesGroupedByCountry | null
|
||||
) {
|
||||
return unstable_cache(
|
||||
async function (
|
||||
searchParams: URLSearchParams,
|
||||
groupedCitiesByCountry: CitiesGroupedByCountry | null
|
||||
) {
|
||||
serviceToken: string
|
||||
}) {
|
||||
const cacheClient = await getCacheClient()
|
||||
|
||||
return await cacheClient.cacheOrGet(
|
||||
`${lang}:locations`.toLowerCase(),
|
||||
async () => {
|
||||
const params = new URLSearchParams({
|
||||
language: toApiLang(lang),
|
||||
})
|
||||
|
||||
const apiResponse = await api.get(
|
||||
api.endpoints.v1.Hotel.locations,
|
||||
options,
|
||||
searchParams
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${serviceToken}`,
|
||||
},
|
||||
},
|
||||
params
|
||||
)
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
if (apiResponse.status === 401) {
|
||||
return { error: true, cause: "unauthorized" } as const
|
||||
throw new Error("unauthorized")
|
||||
} else if (apiResponse.status === 403) {
|
||||
return { error: true, cause: "forbidden" } as const
|
||||
throw new Error("forbidden")
|
||||
}
|
||||
return null
|
||||
throw new Error("downstream error")
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
const verifiedLocations = locationsSchema.safeParse(apiJson)
|
||||
if (!verifiedLocations.success) {
|
||||
console.info(`Locations Verification Failed`)
|
||||
console.error(verifiedLocations.error)
|
||||
return null
|
||||
throw new Error("Unable to parse locations")
|
||||
}
|
||||
|
||||
return await Promise.all(
|
||||
verifiedLocations.data.data.map(async (location) => {
|
||||
if (location.type === "cities") {
|
||||
if (groupedCitiesByCountry) {
|
||||
const country = Object.keys(groupedCitiesByCountry).find(
|
||||
(country) => {
|
||||
if (
|
||||
groupedCitiesByCountry[country].find(
|
||||
(loc) => loc.name === location.name
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (citiesByCountry) {
|
||||
const country = Object.keys(citiesByCountry).find((country) =>
|
||||
citiesByCountry[country].find(
|
||||
(loc) => loc.name === location.name
|
||||
)
|
||||
)
|
||||
if (country) {
|
||||
return {
|
||||
@@ -243,12 +271,10 @@ export async function getLocations(
|
||||
}
|
||||
} else if (location.type === "hotels") {
|
||||
if (location.relationships.city?.url) {
|
||||
const city = await getCity(
|
||||
location.relationships.city.url,
|
||||
options,
|
||||
lang,
|
||||
location.relationships.city
|
||||
)
|
||||
const city = await getCity({
|
||||
cityUrl: location.relationships.city.url,
|
||||
serviceToken,
|
||||
})
|
||||
if (city) {
|
||||
return deepmerge(location, {
|
||||
relationships: {
|
||||
@@ -263,44 +289,51 @@ export async function getLocations(
|
||||
})
|
||||
)
|
||||
},
|
||||
[
|
||||
`${lang}:${locationsAffix}`,
|
||||
params.toString(),
|
||||
JSON.stringify(citiesByCountry),
|
||||
],
|
||||
{ revalidate: TWENTYFOUR_HOURS }
|
||||
)(params, citiesByCountry)
|
||||
"1d"
|
||||
)
|
||||
}
|
||||
|
||||
export async function getHotelIdsByCityId(
|
||||
cityId: string,
|
||||
options: RequestOptionsWithOutBody,
|
||||
params: URLSearchParams
|
||||
) {
|
||||
return unstable_cache(
|
||||
async function (params: URLSearchParams) {
|
||||
metrics.hotelIds.counter.add(1, { params: params.toString() })
|
||||
export async function getHotelIdsByCityId({
|
||||
cityId,
|
||||
serviceToken,
|
||||
}: {
|
||||
cityId: string
|
||||
serviceToken: string
|
||||
}) {
|
||||
const cacheClient = await getCacheClient()
|
||||
return await cacheClient.cacheOrGet(
|
||||
`${cityId}:hotelsByCityId`,
|
||||
async () => {
|
||||
const searchParams = new URLSearchParams({
|
||||
city: cityId,
|
||||
})
|
||||
metrics.hotelIds.counter.add(1, { params: searchParams.toString() })
|
||||
console.info(
|
||||
"api.hotel.hotel-ids start",
|
||||
JSON.stringify({ params: params.toString() })
|
||||
JSON.stringify({ params: searchParams.toString() })
|
||||
)
|
||||
|
||||
const apiResponse = await api.get(
|
||||
api.endpoints.v1.Hotel.hotels,
|
||||
options,
|
||||
params
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${serviceToken}`,
|
||||
},
|
||||
},
|
||||
searchParams
|
||||
)
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const responseMessage = await apiResponse.text()
|
||||
metrics.hotelIds.fail.add(1, {
|
||||
params: params.toString(),
|
||||
params: searchParams.toString(),
|
||||
error_type: "http_error",
|
||||
error: responseMessage,
|
||||
})
|
||||
console.error(
|
||||
"api.hotel.hotel-ids fetch error",
|
||||
JSON.stringify({
|
||||
params: params.toString(),
|
||||
params: searchParams.toString(),
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
@@ -309,59 +342,73 @@ export async function getHotelIdsByCityId(
|
||||
})
|
||||
)
|
||||
|
||||
return []
|
||||
throw new Error("Unable to fetch hotelIds by cityId")
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
const validatedHotelIds = getHotelIdsSchema.safeParse(apiJson)
|
||||
if (!validatedHotelIds.success) {
|
||||
metrics.hotelIds.fail.add(1, {
|
||||
params: params.toString(),
|
||||
params: searchParams.toString(),
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedHotelIds.error),
|
||||
})
|
||||
console.error(
|
||||
"api.hotel.hotel-ids validation error",
|
||||
JSON.stringify({
|
||||
params: params.toString(),
|
||||
params: searchParams.toString(),
|
||||
error: validatedHotelIds.error,
|
||||
})
|
||||
)
|
||||
return []
|
||||
|
||||
throw new Error("Unable to parse data for hotelIds by cityId")
|
||||
}
|
||||
|
||||
metrics.hotelIds.success.add(1, { cityId })
|
||||
console.info(
|
||||
"api.hotel.hotel-ids success",
|
||||
JSON.stringify({
|
||||
params: params.toString(),
|
||||
params: searchParams.toString(),
|
||||
response: validatedHotelIds.data,
|
||||
})
|
||||
)
|
||||
|
||||
return validatedHotelIds.data
|
||||
},
|
||||
[`hotelsByCityId`, params.toString()],
|
||||
{ revalidate: env.CACHE_TIME_HOTELS }
|
||||
)(params)
|
||||
env.CACHE_TIME_HOTELS
|
||||
)
|
||||
}
|
||||
|
||||
export async function getHotelIdsByCountry(
|
||||
country: string,
|
||||
options: RequestOptionsWithOutBody,
|
||||
params: URLSearchParams
|
||||
) {
|
||||
return unstable_cache(
|
||||
async function (params: URLSearchParams) {
|
||||
export async function getHotelIdsByCountry({
|
||||
country,
|
||||
serviceToken,
|
||||
}: {
|
||||
country: string
|
||||
serviceToken: string
|
||||
}) {
|
||||
const cacheClient = await getCacheClient()
|
||||
|
||||
return await cacheClient.cacheOrGet(
|
||||
`${country}:hotelsByCountry`,
|
||||
async () => {
|
||||
metrics.hotelIds.counter.add(1, { country })
|
||||
console.info(
|
||||
"api.hotel.hotel-ids start",
|
||||
JSON.stringify({ query: { country } })
|
||||
)
|
||||
|
||||
const hotelIdsParams = new URLSearchParams({
|
||||
country,
|
||||
})
|
||||
|
||||
const apiResponse = await api.get(
|
||||
api.endpoints.v1.Hotel.hotels,
|
||||
options,
|
||||
params
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${serviceToken}`,
|
||||
},
|
||||
},
|
||||
hotelIdsParams
|
||||
)
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
@@ -383,7 +430,7 @@ export async function getHotelIdsByCountry(
|
||||
})
|
||||
)
|
||||
|
||||
return []
|
||||
throw new Error("Unable to fetch hotelIds by country")
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
@@ -401,7 +448,7 @@ export async function getHotelIdsByCountry(
|
||||
error: validatedHotelIds.error,
|
||||
})
|
||||
)
|
||||
return []
|
||||
throw new Error("Unable to parse hotelIds by country")
|
||||
}
|
||||
|
||||
metrics.hotelIds.success.add(1, { country })
|
||||
@@ -412,62 +459,45 @@ export async function getHotelIdsByCountry(
|
||||
|
||||
return validatedHotelIds.data
|
||||
},
|
||||
[`hotelsByCountry`, params.toString()],
|
||||
{ revalidate: env.CACHE_TIME_HOTELS }
|
||||
)(params)
|
||||
env.CACHE_TIME_HOTELS
|
||||
)
|
||||
}
|
||||
|
||||
export async function getHotelIdsByCityIdentifier(
|
||||
cityIdentifier: string,
|
||||
serviceToken: string
|
||||
) {
|
||||
const apiLang = toApiLang(Lang.en)
|
||||
const city = await getCityByCityIdentifier(cityIdentifier, serviceToken)
|
||||
const city = await getCityByCityIdentifier({
|
||||
cityIdentifier,
|
||||
lang: Lang.en,
|
||||
serviceToken,
|
||||
})
|
||||
|
||||
if (!city) {
|
||||
return []
|
||||
}
|
||||
|
||||
const hotelIdsParams = new URLSearchParams({
|
||||
language: apiLang,
|
||||
city: city.id,
|
||||
const hotelIds = await getHotelIdsByCityId({
|
||||
cityId: city.id,
|
||||
serviceToken,
|
||||
})
|
||||
const options: RequestOptionsWithOutBody = {
|
||||
// needs to clear default option as only
|
||||
// cache or next.revalidate is permitted
|
||||
cache: undefined,
|
||||
headers: {
|
||||
Authorization: `Bearer ${serviceToken}`,
|
||||
},
|
||||
next: {
|
||||
revalidate: env.CACHE_TIME_HOTELS,
|
||||
},
|
||||
}
|
||||
const hotelIds = await getHotelIdsByCityId(city.id, options, hotelIdsParams)
|
||||
return hotelIds
|
||||
}
|
||||
|
||||
export async function getCityByCityIdentifier(
|
||||
cityIdentifier: string,
|
||||
export async function getCityByCityIdentifier({
|
||||
cityIdentifier,
|
||||
lang,
|
||||
serviceToken,
|
||||
}: {
|
||||
cityIdentifier: string
|
||||
lang: Lang
|
||||
serviceToken: string
|
||||
) {
|
||||
const lang = Lang.en
|
||||
const apiLang = toApiLang(lang)
|
||||
const options: RequestOptionsWithOutBody = {
|
||||
// needs to clear default option as only
|
||||
// cache or next.revalidate is permitted
|
||||
cache: undefined,
|
||||
headers: {
|
||||
Authorization: `Bearer ${serviceToken}`,
|
||||
},
|
||||
next: {
|
||||
revalidate: env.CACHE_TIME_HOTELS,
|
||||
},
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
language: apiLang,
|
||||
}) {
|
||||
const locations = await getLocations({
|
||||
lang,
|
||||
citiesByCountry: null,
|
||||
serviceToken,
|
||||
})
|
||||
const locations = await getLocations(lang, options, params, null)
|
||||
if (!locations || "error" in locations) {
|
||||
return null
|
||||
}
|
||||
@@ -479,11 +509,15 @@ export async function getCityByCityIdentifier(
|
||||
return city ?? null
|
||||
}
|
||||
|
||||
export async function getHotelsByHotelIds(
|
||||
hotelIds: string[],
|
||||
lang: Lang,
|
||||
export async function getHotelsByHotelIds({
|
||||
hotelIds,
|
||||
lang,
|
||||
serviceToken,
|
||||
}: {
|
||||
hotelIds: string[]
|
||||
lang: Lang
|
||||
serviceToken: string
|
||||
) {
|
||||
}) {
|
||||
const hotelPages = await getHotelPageUrls(lang)
|
||||
const hotels = await Promise.all(
|
||||
hotelIds.map(async (hotelId) => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { publicProcedure, router } from "@/server/trpc"
|
||||
|
||||
import { getCacheClient } from "@/services/dataCache"
|
||||
|
||||
import { jobylonFeedSchema } from "./output"
|
||||
import {
|
||||
getJobylonFeedCounter,
|
||||
@@ -29,66 +31,74 @@ export const jobylonQueryRouter = router({
|
||||
JSON.stringify({ query: { url: urlString } })
|
||||
)
|
||||
|
||||
const response = await fetch(url, {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
revalidate: TWENTYFOUR_HOURS,
|
||||
const cacheClient = await getCacheClient()
|
||||
return await cacheClient.cacheOrGet(
|
||||
"jobylon:feed",
|
||||
async () => {
|
||||
const response = await fetch(url, {
|
||||
cache: "no-cache",
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text()
|
||||
const error = {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
text,
|
||||
}
|
||||
getJobylonFeedFailCounter.add(1, {
|
||||
url: urlString,
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify(error),
|
||||
})
|
||||
console.error(
|
||||
"jobylon.feed error",
|
||||
JSON.stringify({
|
||||
query: { url: urlString },
|
||||
error,
|
||||
})
|
||||
)
|
||||
|
||||
throw new Error(
|
||||
`Failed to fetch Jobylon feed: ${JSON.stringify(error)}`
|
||||
)
|
||||
}
|
||||
|
||||
const responseJson = await response.json()
|
||||
const validatedResponse = jobylonFeedSchema.safeParse(responseJson)
|
||||
|
||||
if (!validatedResponse.success) {
|
||||
getJobylonFeedFailCounter.add(1, {
|
||||
urlString,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedResponse.error),
|
||||
})
|
||||
|
||||
const errorData = JSON.stringify({
|
||||
query: { url: urlString },
|
||||
error: validatedResponse.error,
|
||||
})
|
||||
|
||||
console.error("jobylon.feed error", errorData)
|
||||
throw new Error(
|
||||
`Failed to parse Jobylon feed: ${JSON.stringify(errorData)}`
|
||||
)
|
||||
}
|
||||
|
||||
getJobylonFeedSuccessCounter.add(1, {
|
||||
url: urlString,
|
||||
})
|
||||
console.info(
|
||||
"jobylon.feed success",
|
||||
JSON.stringify({
|
||||
query: { url: urlString },
|
||||
})
|
||||
)
|
||||
|
||||
return validatedResponse.data
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text()
|
||||
const error = {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
text,
|
||||
}
|
||||
getJobylonFeedFailCounter.add(1, {
|
||||
url: urlString,
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify(error),
|
||||
})
|
||||
console.error(
|
||||
"jobylon.feed error",
|
||||
JSON.stringify({
|
||||
query: { url: urlString },
|
||||
error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const responseJson = await response.json()
|
||||
const validatedResponse = jobylonFeedSchema.safeParse(responseJson)
|
||||
|
||||
if (!validatedResponse.success) {
|
||||
getJobylonFeedFailCounter.add(1, {
|
||||
urlString,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedResponse.error),
|
||||
})
|
||||
|
||||
console.error(
|
||||
"jobylon.feed error",
|
||||
JSON.stringify({
|
||||
query: { url: urlString },
|
||||
error: validatedResponse.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
getJobylonFeedSuccessCounter.add(1, {
|
||||
url: urlString,
|
||||
})
|
||||
console.info(
|
||||
"jobylon.feed success",
|
||||
JSON.stringify({
|
||||
query: { url: urlString },
|
||||
})
|
||||
"1d"
|
||||
)
|
||||
|
||||
return validatedResponse.data
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -641,11 +641,9 @@ export const userQueryRouter = router({
|
||||
const apiResponse = await api.get(
|
||||
api.endpoints.v1.Profile.Transaction.friendTransactions,
|
||||
{
|
||||
cache: undefined, // override defaultOptions
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
||||
},
|
||||
next: { revalidate: 30 * 60 * 1000 },
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user