Merged in monorepo-step-1 (pull request #1080)

Migrate to a monorepo setup - step 1

* Move web to subfolder /apps/scandic-web

* Yarn + transitive deps

- Move to yarn
- design-system package removed for now since yarn doesn't
support the parameter for token (ie project currently broken)
- Add missing transitive dependencies as Yarn otherwise
prevents these imports
- VS Code doesn't pick up TS path aliases unless you open
/apps/scandic-web instead of root (will be fixed with monorepo)

* Pin framer-motion to temporarily fix typing issue

https://github.com/adobe/react-spectrum/issues/7494

* Pin zod to avoid typ error

There seems to have been a breaking change in the types
returned by zod where error is now returned as undefined
instead of missing in the type. We should just handle this
but to avoid merge conflicts just pin the dependency for
now.

* Pin react-intl version

Pin version of react-intl to avoid tiny type issue where formatMessage
does not accept a generic any more. This will be fixed in a future
commit, but to avoid merge conflicts just pin for now.

* Pin typescript version

Temporarily pin version as newer versions as stricter and results in
a type error. Will be fixed in future commit after merge.

* Setup workspaces

* Add design-system as a monorepo package

* Remove unused env var DESIGN_SYSTEM_ACCESS_TOKEN

* Fix husky for monorepo setup

* Update netlify.toml

* Add lint script to root package.json

* Add stub readme

* Fix react-intl formatMessage types

* Test netlify.toml in root

* Remove root toml

* Update netlify.toml publish path

* Remove package-lock.json

* Update build for branch/preview builds


Approved-by: Linus Flood
This commit is contained in:
Anton Gunnarsson
2025-02-26 10:36:17 +00:00
committed by Linus Flood
parent 667cab6fb6
commit 80100e7631
2731 changed files with 30986 additions and 23708 deletions

View File

@@ -0,0 +1,16 @@
import { notFound } from "next/navigation"
import { NextResponse } from "next/server"
import { env } from "@/env/server"
import { auth } from "@/auth"
export const GET = async () => {
if (env.NODE_ENV !== "development") {
return notFound()
}
const user = await auth()
console.log("[DEBUG] access-token", user?.token)
return NextResponse.json(user)
}

View File

@@ -0,0 +1,99 @@
import dayjs from "dayjs"
import { type NextRequest, NextResponse } from "next/server"
import { env } from "@/env/server"
import {
getEntries,
getSyncToken,
saveEntries,
saveLastUpdatedDate,
saveSitemapData,
saveSyncToken,
} from "@/utils/sitemap"
import { contentstackSync } from "./sync"
import {
generateSitemapCounter,
generateSitemapFailCounter,
generateSitemapSuccessCounter,
saveEntriesCounter,
saveSitemapDataCounter,
saveSyncTokenCounter,
} from "./telemetry"
import { mapEntriesToSitemapData, mergeEntries } from "./utils"
export const dynamic = "force-dynamic"
export async function GET(request: NextRequest) {
try {
generateSitemapCounter.add(1)
console.info("sitemap.generate start")
const headersList = request.headers
const secret = headersList.get("x-sitemap-sync-secret")
if (secret !== env.SITEMAP_SYNC_SECRET) {
throw Error(
`Can't sync and generate sitemap, invalid secret, received secret: ${secret}`
)
}
const syncToken = await getSyncToken()
const currentEntries = await getEntries()
const responseData = await contentstackSync(syncToken)
const mergedEntries = mergeEntries(currentEntries, responseData.entries)
saveEntriesCounter.add(1, { entriesCount: mergedEntries.length })
console.info(
"sitemap.entries.save",
JSON.stringify({ entriesCount: mergedEntries.length })
)
await saveEntries(mergedEntries)
const sitemapData = mapEntriesToSitemapData(mergedEntries)
const lastUpdated = dayjs.utc().format()
saveSitemapDataCounter.add(1, {
sitemapEntriesCount: sitemapData.length,
})
console.info(
"sitemap.data.save",
JSON.stringify({
sitemapEntriesCount: sitemapData.length,
lastUpdated,
})
)
await saveSitemapData(sitemapData)
await saveLastUpdatedDate(lastUpdated)
if (syncToken !== responseData.syncToken) {
saveSyncTokenCounter.add(1, {
syncToken: responseData.syncToken,
})
console.info(
"sitemap.synctoken.save",
JSON.stringify({ syncToken: responseData.syncToken })
)
await saveSyncToken(responseData.syncToken)
}
generateSitemapSuccessCounter.add(1)
return NextResponse.json({
message: "Sitemap data generated and stored successfully!",
now: dayjs.utc().format(),
})
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : JSON.stringify(error)
generateSitemapFailCounter.add(1, { error: errorMessage })
console.error("sitemap.generate.fail", errorMessage)
return NextResponse.json(
{
error: "Failed to generate sitemap",
now: dayjs.utc().format(),
},
{ status: 500, statusText: "Internal Server Error" }
)
}
}

View File

@@ -0,0 +1,112 @@
import { Region, Stack } from "contentstack"
import { env } from "@/env/server"
import {
syncEntriesCounter,
syncEntriesFailCounter,
syncEntriesPaginationCounter,
syncEntriesSuccessCounter,
} from "./telemetry"
import type { SyncResponse } from "@/types/sitemap"
const environment = env.CMS_ENVIRONMENT
const stack = Stack({
api_key: env.CMS_API_KEY,
delivery_token: env.CMS_ACCESS_TOKEN,
branch: env.CMS_BRANCH,
environment,
region: Region.EU,
})
export async function contentstackSync(syncToken: string | null) {
const entries = []
const syncOptions = syncToken ? { sync_token: syncToken } : { init: true }
syncEntriesCounter.add(1, {
environment,
...syncOptions,
})
console.info(
"sitemap.entries.sync start",
JSON.stringify({
environment,
...syncOptions,
})
)
try {
let syncResponse: SyncResponse = await stack.sync(syncOptions)
entries.push(...syncResponse.items)
// Check if there is a pagination token, and fetch more data if needed
while (syncResponse.pagination_token && !syncResponse.sync_token) {
syncEntriesPaginationCounter.add(1, {
environment,
paginationToken: syncResponse.pagination_token,
})
console.info(
"sitemap.entries.sync.pagination start",
JSON.stringify({
environment,
paginationToken: syncResponse.pagination_token,
})
)
syncResponse = await stack.sync({
pagination_token: syncResponse.pagination_token,
})
entries.push(...syncResponse.items)
syncEntriesPaginationCounter.add(1, {
environment,
paginationToken: syncResponse.pagination_token,
entriesCount: syncResponse.items.length,
})
console.info(
"sitemap.entries.sync.pagination success",
JSON.stringify({
environment,
paginationToken: syncResponse.pagination_token,
entriesCount: syncResponse.items.length,
})
)
}
if (syncResponse.sync_token) {
syncEntriesSuccessCounter.add(1, {
environment,
...syncOptions,
newSyncToken: syncResponse.sync_token,
entriesCount: entries.length,
})
console.info(
"sitemap.entries.sync success",
JSON.stringify({
environment,
...syncOptions,
newSyncToken: syncResponse.sync_token,
entriesCount: entries.length,
})
)
return {
syncToken: syncResponse.sync_token,
entries,
}
} else {
throw new Error("No sync token received, something went wrong")
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : JSON.stringify(error)
syncEntriesFailCounter.add(1, {
environment,
...syncOptions,
error: errorMessage,
})
console.error("sitemap.entries.sync error", errorMessage)
throw new Error("Failed to sync entries")
}
}

View File

@@ -0,0 +1,43 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("sitemap")
// OpenTelemetry metrics
export const generateSitemapCounter = meter.createCounter("sitemap.generate")
export const generateSitemapSuccessCounter = meter.createCounter(
"sitemap.generate-success"
)
export const generateSitemapFailCounter = meter.createCounter(
"sitemap.generate-fail"
)
export const syncEntriesCounter = meter.createCounter("sitemap.entries.sync")
export const syncEntriesSuccessCounter = meter.createCounter(
"sitemap.entries.sync-success"
)
export const syncEntriesFailCounter = meter.createCounter(
"sitemap.entries.sync-fail"
)
export const syncEntriesPaginationCounter = meter.createCounter(
"sitemap.entries.sync.pagination"
)
export const syncEntriesPaginationSuccessCounter = meter.createCounter(
"sitemap.entries.sync.pagination-success"
)
export const mergeEntriesCounter = meter.createCounter("sitemap.entries.merge")
export const mergeEntriesSuccessCounter = meter.createCounter(
"sitemap.entries.merge-success"
)
export const saveEntriesCounter = meter.createCounter("sitemap.entries.save")
export const transformEntriesCounter = meter.createCounter(
"sitemap.entries.transform"
)
export const transformEntriesSuccessCounter = meter.createCounter(
"sitemap.entries.transform-success"
)
export const saveSitemapDataCounter = meter.createCounter("sitemap.data.save")
export const saveSyncTokenCounter = meter.createCounter(
"sitemap.synctoken.save"
)

View File

@@ -0,0 +1,176 @@
import { Lang } from "@/constants/languages"
import { env } from "@/env/server"
import {
mergeEntriesCounter,
mergeEntriesSuccessCounter,
transformEntriesCounter,
transformEntriesSuccessCounter,
} from "./telemetry"
import type { SitemapEntry, SyncItem } from "@/types/sitemap"
export function mergeEntries(
currentEntries: SyncItem[],
newEntries: SyncItem[]
) {
mergeEntriesCounter.add(1, {
currentEntriesCount: currentEntries.length,
newEntriesCount: newEntries.length,
})
console.info(
"sitemap.entries.merge start",
JSON.stringify({
currentEntriesCount: currentEntries.length,
newEntriesCount: newEntries.length,
})
)
const entries = [...currentEntries]
newEntries.forEach((entry) => {
const index = entries.findIndex(
({ data }) =>
data.uid === entry.data.uid && data.locale === entry.data.locale
)
if (index > -1) {
entries[index] = entry
} else {
entries.push(entry)
}
})
mergeEntriesSuccessCounter.add(1, {
entriesCount: entries.length,
})
console.info(
"sitemap.entries.merge success",
JSON.stringify({
entriesCount: entries.length,
})
)
return entries
}
export function mapEntriesToSitemapData(entries: SyncItem[]) {
transformEntriesCounter.add(1, { entriesCount: entries.length })
console.info(
"sitemap.entries.transform start",
JSON.stringify({
entriesCount: entries.length,
})
)
const filteredEntries = filterEntriesToSitemapEntries(entries)
const entriesByUid = groupEntriesByUid(filteredEntries)
const sitemapEntries = Object.entries(entriesByUid)
.map(([_, entries]) => mapEntriesToSitemapEntry(entries))
.filter((entry): entry is SitemapEntry => !!entry)
transformEntriesSuccessCounter.add(1, {
entriesCount: entries.length,
sitemapEntriesCount: sitemapEntries.length,
})
console.info(
"sitemap.entries.transform success",
JSON.stringify({
entriesCount: entries.length,
sitemapEntriesCount: sitemapEntries.length,
})
)
return sitemapEntries
}
function filterEntriesToSitemapEntries(entries: SyncItem[]) {
return entries.filter((entry: SyncItem) => {
const shouldIndex = !entry.data.web?.seo_metadata?.noindex
return !!(entry.type === "entry_published" && entry.data.url && shouldIndex)
})
}
// We group the entries by UID because Contentstack has the same `uid` for an
// entry regardless of locale. We want to display each locale as an alternate
// in the sitemap, therefore we group them here by `uid`.
function groupEntriesByUid(entries: SyncItem[]) {
return entries.reduce<Record<string, SyncItem[]>>((acc, entry) => {
const uid = entry.data.uid
if (!acc[uid]) {
acc[uid] = []
}
acc[uid].push(entry)
return acc
}, {})
}
function mapEntriesToSitemapEntry(entries: SyncItem[]) {
// Main entry is always English. Without English, there can't be other pages in ContentStack.
const mainEntry = entries.find((entry) => entry.data.locale === Lang.en)
const alternates = getAlternates(entries)
const lastModified = getLastModified(entries)
const changeFrequency = getChangeFrequency(entries)
const priority = getPriority(entries)
if (mainEntry) {
const { locale, url } = mainEntry.data
const sitemapEntry: SitemapEntry = {
url: `${env.PUBLIC_URL}/${locale}${url}`,
lastModified,
changeFrequency,
priority,
}
if (alternates) {
sitemapEntry.alternates = alternates
}
return sitemapEntry
}
}
function getLastModified(entries: SyncItem[]) {
// Localized versions of the data can have a different last modified value.
// We make sure we take the latest.
return entries.reduce((latest, entry) => {
const entryDate = entry.data.updated_at
return entryDate > latest ? entryDate : latest
}, "")
}
function getChangeFrequency(entries: SyncItem[]) {
// Localized versions of the data can have a different changeFrequency value.
// We make sure we take the highest.
const changeFrequencyPriority: SitemapEntry["changeFrequency"][] = [
"never",
"yearly",
"monthly",
"weekly",
"daily",
"hourly",
"always",
]
return entries.reduce<SitemapEntry["changeFrequency"]>((highest, entry) => {
const changeFrequency =
entry.data.web?.seo_metadata?.sitemap?.change_frequency ?? "daily"
return changeFrequencyPriority.indexOf(changeFrequency) >
changeFrequencyPriority.indexOf(highest)
? changeFrequency
: highest
}, "never")
}
function getPriority(entries: SyncItem[]) {
// Localized versions of the data can have a different priority.
// We make sure we take the highest.
return entries.reduce((highest, entry) => {
const priority = entry.data.web?.seo_metadata?.sitemap?.priority ?? 0.5
return priority > highest ? priority : highest
}, 0.0)
}
function getAlternates(entries: SyncItem[]) {
return entries
.filter((entry) => entry.data.locale !== Lang.en)
.reduce<Partial<Record<Lang, string>>>((acc, { data }) => {
acc[data.locale] = `${env.PUBLIC_URL}/${data.locale}${data.url}`
return acc
}, {})
}

View File

@@ -0,0 +1,56 @@
import { Lang } from "@/constants/languages"
import { profile } from "@/constants/routes/myPages"
import { serverClient } from "@/lib/trpc/server"
import { getPublicURL } from "@/server/utils"
import type { NextRequest } from "next/server"
export async function GET(
request: NextRequest,
{ params }: { params: { lang: string } }
) {
const publicURL = getPublicURL(request)
console.log(`[add-card] callback started`)
const lang = params.lang as Lang
const returnUrl = new URL(`${publicURL}/${profile[lang ?? Lang.en]}`)
try {
const searchParams = request.nextUrl.searchParams
const success = searchParams.get("success")
const failure = searchParams.get("failure")
const cancel = searchParams.get("cancel")
const trxId = searchParams.get("datatransTrxId")
if (success) {
if (trxId) {
const saveCardSuccess = await serverClient().user.creditCard.save({
transactionId: trxId,
})
if (saveCardSuccess) {
console.log(`[add-card] planet success: card saved success`)
returnUrl.searchParams.set("success", "true")
} else {
console.log(`[add-card] planet success: card saved fail`)
returnUrl.searchParams.set("failure", "true")
}
} else {
console.log(`[add-card] planet success: missing datatransTrxId`)
returnUrl.searchParams.set("error", "true")
}
} else if (failure) {
console.log(`[add-card] planet fail`)
returnUrl.searchParams.set("failure", "true")
} else if (cancel) {
console.log(`[add-card] planet cancel`)
returnUrl.searchParams.set("cancel", "true")
}
} catch (e) {
console.error(`[add-card] error saving credit card`, e)
returnUrl.searchParams.set("error", "true")
}
console.log(`[add-card] redirecting to: ${returnUrl}`)
return Response.redirect(returnUrl)
}

View File

@@ -0,0 +1 @@
export { GET, POST } from "@/auth"

View File

@@ -0,0 +1,5 @@
import { type NextRequest, NextResponse } from "next/server"
export async function GET(request: NextRequest) {
return NextResponse.json(Object.fromEntries(request.headers.entries()))
}

View File

@@ -0,0 +1,73 @@
import { revalidateTag } from "next/cache"
import { headers } from "next/headers"
import { z } from "zod"
import { Lang } from "@/constants/languages"
import { env } from "@/env/server"
import { badRequest, internalServerError, notFound } from "@/server/errors/next"
import { generateHotelUrlTag } from "@/utils/generateTag"
import type { NextRequest } from "next/server"
const validateJsonBody = z.object({
data: z.object({
content_type: z.object({
uid: z.literal("hotel_page"),
}),
entry: z.object({
hotel_page_id: z.string(),
locale: z.nativeEnum(Lang),
publish_details: z.object({ locale: z.nativeEnum(Lang) }).optional(),
}),
}),
})
export async function POST(request: NextRequest) {
try {
const headersList = headers()
const secret = headersList.get("x-revalidate-secret")
if (secret !== env.REVALIDATE_SECRET) {
console.error(`Invalid Secret`)
console.error({ secret })
return badRequest({ revalidated: false, now: Date.now() })
}
const data = await request.json()
const validatedData = validateJsonBody.safeParse(data)
if (!validatedData.success) {
console.error("Bad validation for `validatedData` in hotel revalidation")
console.error(validatedData.error)
return internalServerError({ revalidated: false, now: Date.now() })
}
const {
data: {
data: { content_type, entry },
},
} = validatedData
// The publish_details.locale is the locale that the entry is published in, regardless if it is "localized" or not
const locale = entry.publish_details?.locale ?? entry.locale
let tag = ""
if (content_type.uid === "hotel_page") {
tag = generateHotelUrlTag(locale, entry.hotel_page_id)
} else {
console.error(
`Invalid content_type, received ${content_type.uid}, expected "hotel_page"`
)
return notFound({ revalidated: false, now: Date.now() })
}
console.info(`Revalidating hotel url tag: ${tag}`)
revalidateTag(tag)
return Response.json({ revalidated: true, now: Date.now() })
} catch (error) {
console.error("Failed to revalidate tag(s) for hotel")
console.error(error)
return internalServerError({ revalidated: false, now: Date.now() })
}
}

View File

@@ -0,0 +1,91 @@
import { revalidateTag } from "next/cache"
import { headers } from "next/headers"
import { z } from "zod"
import { Lang } from "@/constants/languages"
import { env } from "@/env/server"
import { badRequest, internalServerError, notFound } from "@/server/errors/next"
import { generateLoyaltyConfigTag } from "@/utils/generateTag"
import type { NextRequest } from "next/server"
enum LoyaltyConfigContentTypes {
loyalty_level = "loyalty_level",
reward = "reward",
}
const validateJsonBody = z.object({
data: z.object({
content_type: z.object({
uid: z.nativeEnum(LoyaltyConfigContentTypes),
}),
entry: z.object({
reward_id: z.string().optional(),
level_id: z.string().optional(),
locale: z.nativeEnum(Lang),
}),
}),
})
export async function POST(request: NextRequest) {
try {
const headersList = headers()
const secret = headersList.get("x-revalidate-secret")
if (secret !== env.REVALIDATE_SECRET) {
console.error(`Invalid Secret`)
console.error({ secret })
return badRequest({ revalidated: false, now: Date.now() })
}
const data = await request.json()
const validatedData = validateJsonBody.safeParse(data)
if (!validatedData.success) {
console.error(
"Bad validation for `validatedData` in loyaltyConfig revalidation"
)
console.error(validatedData.error)
return internalServerError({ revalidated: false, now: Date.now() })
}
const {
data: {
data: { content_type, entry },
},
} = validatedData
let tag = ""
if (
content_type.uid === LoyaltyConfigContentTypes.loyalty_level &&
entry.level_id
) {
tag = generateLoyaltyConfigTag(
entry.locale,
content_type.uid,
entry.level_id
)
} else if (
content_type.uid === LoyaltyConfigContentTypes.reward &&
entry.reward_id
) {
tag = generateLoyaltyConfigTag(
entry.locale,
content_type.uid,
entry.reward_id
)
} else {
console.error("Invalid content_type")
return notFound({ revalidated: false, now: Date.now() })
}
console.info(`Revalidating loyalty config tag: ${tag}`)
revalidateTag(tag)
return Response.json({ revalidated: true, now: Date.now() })
} catch (error) {
console.error("Failed to revalidate tag(s) for loyalty config")
console.error(error)
return internalServerError({ revalidated: false, now: Date.now() })
}
}

View File

@@ -0,0 +1,61 @@
import { revalidateTag } from "next/cache"
import { headers } from "next/headers"
import { env } from "@/env/server"
import { badRequest, internalServerError } from "@/server/errors/next"
import { generateTag } from "@/utils/generateTag"
import type { Lang } from "@/constants/languages"
// This file is primarily to be used locally to test
// purging your cache for new (and old) requests
export async function POST() {
try {
const headersList = headers()
const secret = headersList.get("x-revalidate-secret")
if (secret !== env.REVALIDATE_SECRET) {
console.error(`Invalid Secret`)
console.error({ secret })
return badRequest({
now: Date.now(),
revalidated: false,
})
}
const affix = headersList.get("x-affix")
const identifier = headersList.get("x-identifier")
const lang = headersList.get("x-lang")
if (lang && identifier) {
if (affix) {
const tag = generateTag(lang as Lang, identifier, affix)
console.info(
`Revalidated tag for [lang: ${lang}, identifier: ${identifier}, affix: ${affix}]`
)
console.info(`Tag: ${tag}`)
revalidateTag(tag)
} else {
const tag = generateTag(lang as Lang, identifier)
console.info(
`Revalidated tag for [lang: ${lang}, identifier: ${identifier}]`
)
console.info(`Tag: ${tag}`)
revalidateTag(tag)
}
} else {
console.info(`Missing lang and/or identifier`)
console.info(`lang: ${lang}, identifier: ${identifier}`)
return badRequest({
now: Date.now(),
revalidated: false,
})
}
return Response.json({ revalidated: true, now: Date.now() })
} catch (error) {
console.error("Failed to revalidate tag(s)")
console.error(error)
return internalServerError({ revalidated: false, now: Date.now() })
}
}

View File

@@ -0,0 +1,144 @@
import { revalidateTag } from "next/cache"
import { headers } from "next/headers"
import { z } from "zod"
import { Lang } from "@/constants/languages"
import { env } from "@/env/server"
import { badRequest, internalServerError } from "@/server/errors/next"
import { affix as breadcrumbsAffix } from "@/server/routers/contentstack/breadcrumbs/utils"
import { languageSwitcherAffix } from "@/server/routers/contentstack/languageSwitcher/utils"
import { affix as metadataAffix } from "@/server/routers/contentstack/metadata/utils"
import { affix as pageSettingsAffix } from "@/server/routers/contentstack/pageSettings/utils"
import {
generateRefsResponseTag,
generateRefTag,
generateTag,
} from "@/utils/generateTag"
import type { NextRequest } from "next/server"
const validateJsonBody = z.object({
data: z.object({
content_type: z.object({
uid: z.string(),
}),
entry: z.object({
breadcrumbs: z
.object({
title: z.string(),
})
.optional(),
locale: z.nativeEnum(Lang),
publish_details: z.object({ locale: z.nativeEnum(Lang) }).optional(),
uid: z.string(),
url: z.string().optional(),
page_settings: z
.object({
hide_booking_widget: z.boolean(),
})
.optional(),
}),
}),
})
export async function POST(request: NextRequest) {
try {
const headersList = headers()
const secret = headersList.get("x-revalidate-secret")
if (secret !== env.REVALIDATE_SECRET) {
console.error(`Invalid Secret`)
console.error({ secret })
return badRequest({
now: Date.now(),
revalidated: false,
})
}
const data = await request.json()
const validatedData = validateJsonBody.safeParse(data)
if (!validatedData.success) {
console.error("Bad validation for `validatedData`")
console.error(validatedData.error)
return internalServerError({ revalidated: false, now: Date.now() })
}
const {
data: {
data: { content_type, entry },
},
} = validatedData
// The publish_details.locale is the locale that the entry is published in, regardless if it is "localized" or not
const entryLocale = entry.publish_details?.locale ?? entry.locale
const refsTag = generateRefsResponseTag(entryLocale, entry.uid)
const contentEntryTag = generateRefsResponseTag(
entryLocale,
content_type.uid
)
const refTag = generateRefTag(entryLocale, content_type.uid, entry.uid)
const tag = generateTag(entryLocale, entry.uid)
const languageSwitcherTag = generateTag(
entryLocale,
entry.uid,
languageSwitcherAffix
)
const metadataTag = generateTag(entryLocale, entry.uid, metadataAffix)
console.info(`Revalidating refsTag: ${refsTag}`)
revalidateTag(refsTag)
console.info(`Revalidating refTag: ${refTag}`)
revalidateTag(refTag)
console.info(`Revalidating tag: ${tag}`)
revalidateTag(tag)
console.info(`Revalidating language switcher tag: ${languageSwitcherTag}`)
revalidateTag(languageSwitcherTag)
console.info(`Revalidating metadataTag: ${metadataTag}`)
revalidateTag(metadataTag)
console.info(`Revalidating contentEntryTag: ${contentEntryTag}`)
revalidateTag(contentEntryTag)
if (entry.breadcrumbs) {
const breadcrumbsRefsTag = generateRefsResponseTag(
entryLocale,
entry.uid,
breadcrumbsAffix
)
const breadcrumbsTag = generateTag(
entryLocale,
entry.uid,
breadcrumbsAffix
)
console.info(`Revalidating breadcrumbsRefsTag: ${breadcrumbsRefsTag}`)
revalidateTag(breadcrumbsRefsTag)
console.info(`Revalidating breadcrumbsTag: ${breadcrumbsTag}`)
revalidateTag(breadcrumbsTag)
}
if (entry.page_settings) {
const pageSettingsTag = generateTag(
entryLocale,
entry.uid,
pageSettingsAffix
)
console.info(`Revalidating pageSettingsTag: ${pageSettingsTag}`)
revalidateTag(pageSettingsTag)
}
return Response.json({ revalidated: true, now: Date.now() })
} catch (error) {
console.error("Failed to revalidate tag(s)")
console.error(error)
return internalServerError({ revalidated: false, now: Date.now() })
}
}

View File

@@ -0,0 +1,15 @@
import { fetchRequestHandler } from "@trpc/server/adapters/fetch"
import { appRouter } from "@/server"
import { createContext } from "@/server/context"
async function handler(req: Request) {
return fetchRequestHandler({
createContext,
endpoint: "/api/web/trpc",
req,
router: appRouter,
})
}
export { handler as GET, handler as POST }