Merged in feat/SW-550-sitemap (pull request #981)
feat(SW-550): added sync functionality and sitemap generation * feat(SW-550): added sync functionality and sitemap generation * feat(SW-550): Added support for splitting and saving multiple sitemaps when there are 50000+ urls * feat(SW-550): Updates after PR * feat(SW-550): Added locale to sitemap data * feat(SW-550): Added support for locale based sitemapData * feat(SW-550): Saving alternates of sitemap entries * feat(SW-550): Refactoring to use sitemap utils file * feat(SW-550): Using Netlify.env to get environment variables * feat(SW-550): clarify use of functions Approved-by: Michael Zetterberg
This commit is contained in:
@@ -20,6 +20,7 @@ DESIGN_SYSTEM_ACCESS_TOKEN=""
|
||||
NEXTAUTH_REDIRECT_PROXY_URL="http://localhost:3000/api/web/auth"
|
||||
NEXTAUTH_SECRET=""
|
||||
REVALIDATE_SECRET=""
|
||||
SITEMAP_SYNC_SECRET=""
|
||||
SALESFORCE_PREFERENCE_BASE_URL="https://cloud.emails.scandichotels.com/preference-center"
|
||||
|
||||
SEAMLESS_LOGIN_DA="http://www.example.dk/updatelogin"
|
||||
|
||||
99
app/api/sitemap/route.ts
Normal file
99
app/api/sitemap/route.ts
Normal 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" }
|
||||
)
|
||||
}
|
||||
}
|
||||
112
app/api/sitemap/sync.ts
Normal file
112
app/api/sitemap/sync.ts
Normal 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")
|
||||
}
|
||||
}
|
||||
43
app/api/sitemap/telemetry.ts
Normal file
43
app/api/sitemap/telemetry.ts
Normal 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"
|
||||
)
|
||||
176
app/api/sitemap/utils.ts
Normal file
176
app/api/sitemap/utils.ts
Normal 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
|
||||
}, {})
|
||||
}
|
||||
2
env/server.ts
vendored
2
env/server.ts
vendored
@@ -57,6 +57,7 @@ export const env = createEnv({
|
||||
.default("false"),
|
||||
PUBLIC_URL: z.string().default(""),
|
||||
REVALIDATE_SECRET: z.string(),
|
||||
SITEMAP_SYNC_SECRET: z.string(),
|
||||
SALESFORCE_PREFERENCE_BASE_URL: z.string(),
|
||||
SEAMLESS_LOGIN_DA: z
|
||||
.string()
|
||||
@@ -220,6 +221,7 @@ export const env = createEnv({
|
||||
PRINT_QUERY: process.env.PRINT_QUERY,
|
||||
PUBLIC_URL: process.env.PUBLIC_URL,
|
||||
REVALIDATE_SECRET: process.env.REVALIDATE_SECRET,
|
||||
SITEMAP_SYNC_SECRET: process.env.SITEMAP_SYNC_SECRET,
|
||||
SALESFORCE_PREFERENCE_BASE_URL: process.env.SALESFORCE_PREFERENCE_BASE_URL,
|
||||
SEAMLESS_LOGIN_DA:
|
||||
process.env.SEAMLESS_LOGIN || process.env.SEAMLESS_LOGIN_DA,
|
||||
|
||||
@@ -33,3 +33,6 @@ remote_images = [
|
||||
"https://imagevault-stage.scandichotels.com.*",
|
||||
"https://imagevault.scandichotels.com.*",
|
||||
]
|
||||
|
||||
[functions."sitemap"]
|
||||
schedule = "@daily"
|
||||
|
||||
17
netlify/functions/sitemap/index.mts
Normal file
17
netlify/functions/sitemap/index.mts
Normal file
@@ -0,0 +1,17 @@
|
||||
/* eslint-disable import/no-anonymous-default-export */
|
||||
import type { Context } from "@netlify/functions"
|
||||
|
||||
export default async (request: Request, context: Context) => {
|
||||
const { next_run } = await request.json()
|
||||
const SITEMAP_SYNC_SECRET = Netlify.env.get("SITEMAP_SYNC_SECRET")
|
||||
const PUBLIC_URL = Netlify.env.get("PUBLIC_URL")
|
||||
console.info(
|
||||
`Started sitemap sync at: ${new Date().toISOString()}! Next invocation at: ${next_run}`
|
||||
)
|
||||
const headers = new Headers()
|
||||
headers.set("x-sitemap-sync-secret", SITEMAP_SYNC_SECRET!)
|
||||
|
||||
fetch(`${PUBLIC_URL}/api/sitemap`, {
|
||||
headers,
|
||||
})
|
||||
}
|
||||
354
package-lock.json
generated
354
package-lock.json
generated
@@ -13,6 +13,8 @@
|
||||
"@contentstack/live-preview-utils": "^3.0.0",
|
||||
"@hookform/error-message": "^2.0.1",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@netlify/blobs": "^8.1.0",
|
||||
"@netlify/functions": "^3.0.0",
|
||||
"@netlify/plugin-nextjs": "^5.9.4",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/sdk-metrics": "^1.25.1",
|
||||
@@ -36,6 +38,7 @@
|
||||
"@vis.gl/react-google-maps": "^1.2.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clean-deep": "^3.4.0",
|
||||
"contentstack": "^3.23.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"deepmerge": "^4.3.1",
|
||||
"downshift": "^9.0.8",
|
||||
@@ -138,15 +141,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@auth/core": {
|
||||
"version": "0.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.32.0.tgz",
|
||||
"integrity": "sha512-3+ssTScBd+1fd0/fscAyQN1tSygXzuhysuVVzB942ggU4mdfiTbv36P0ccVnExKWYJKvu3E2r3/zxXCCAmTOrg==",
|
||||
"version": "0.37.2",
|
||||
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.37.2.tgz",
|
||||
"integrity": "sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==",
|
||||
"dependencies": {
|
||||
"@panva/hkdf": "^1.1.1",
|
||||
"@panva/hkdf": "^1.2.1",
|
||||
"@types/cookie": "0.6.0",
|
||||
"cookie": "0.6.0",
|
||||
"jose": "^5.1.3",
|
||||
"oauth4webapi": "^2.9.0",
|
||||
"cookie": "0.7.1",
|
||||
"jose": "^5.9.3",
|
||||
"oauth4webapi": "^3.0.0",
|
||||
"preact": "10.11.3",
|
||||
"preact-render-to-string": "5.2.3"
|
||||
},
|
||||
@@ -2518,6 +2521,11 @@
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/@contentstack/utils": {
|
||||
"version": "1.3.17",
|
||||
"resolved": "https://registry.npmjs.org/@contentstack/utils/-/utils-1.3.17.tgz",
|
||||
"integrity": "sha512-RwuhAUa28wTuHoDkzMFFcookangKr3gJVzyk9nd48RO+8WHSboFGVy4NCI8Zx2BiqbhjcYGtPyMZrLiIuNtyVQ=="
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
@@ -3430,10 +3438,50 @@
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@netlify/blobs": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@netlify/blobs/-/blobs-8.1.1.tgz",
|
||||
"integrity": "sha512-7Dg3PzArvQ0Owq4wpkLECC9iaDBOxuWUN2uwbQtUF6tZssyez2QN+eO0CjuIhhZUivbw+X9bwsyiEjWkdJnv/A==",
|
||||
"engines": {
|
||||
"node": "^14.16.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@netlify/functions": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-3.0.0.tgz",
|
||||
"integrity": "sha512-XXf9mNw4+fkxUzukDpJtzc32bl1+YlXZwEhc5ZgMcTbJPLpgRLDs5WWSPJ4eY/Mv1ZFvtxmMwmfgoQYVt68Qog==",
|
||||
"dependencies": {
|
||||
"@netlify/serverless-functions-api": "1.30.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@netlify/node-cookies": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@netlify/node-cookies/-/node-cookies-0.1.0.tgz",
|
||||
"integrity": "sha512-OAs1xG+FfLX0LoRASpqzVntVV/RpYkgpI0VrUnw2u0Q1qiZUzcPffxRK8HF3gc4GjuhG5ahOEMJ9bswBiZPq0g==",
|
||||
"engines": {
|
||||
"node": "^14.16.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@netlify/plugin-nextjs": {
|
||||
"version": "5.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@netlify/plugin-nextjs/-/plugin-nextjs-5.9.4.tgz",
|
||||
"integrity": "sha512-Q9qyhGUxFuM3kuWmoaj4yHPUfHhOsZmwdsQv4kIXkBWVu8FOe0RFHRP0A0EXciSVFGwq+XAo3H5jEtVSANAOWw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@netlify/serverless-functions-api": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.30.1.tgz",
|
||||
"integrity": "sha512-JkbaWFeydQdeDHz1mAy4rw+E3bl9YtbCgkntfTxq+IlNX/aIMv2/b1kZnQZcil4/sPoZGL831Dq6E374qRpU1A==",
|
||||
"dependencies": {
|
||||
"@netlify/node-cookies": "^0.1.0",
|
||||
"urlpattern-polyfill": "8.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
@@ -8501,6 +8549,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="
|
||||
},
|
||||
"node_modules/@types/glob-to-regexp": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob-to-regexp/-/glob-to-regexp-0.4.4.tgz",
|
||||
"integrity": "sha512-nDKoaKJYbnn1MZxUY0cA1bPmmgZbg0cTq7Rh13d0KWYNOiKbqoR+2d89SnRPszGh7ROzSwZ/GOjZ4jPbmmZ6Eg=="
|
||||
},
|
||||
"node_modules/@types/google.maps": {
|
||||
"version": "3.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.0.tgz",
|
||||
@@ -10438,6 +10491,33 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz",
|
||||
"integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"get-intrinsic": "^1.2.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -11074,6 +11154,35 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/contentstack": {
|
||||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/contentstack/-/contentstack-3.24.1.tgz",
|
||||
"integrity": "sha512-HG8YkyG2VxN70d30ygfd38wXHA94qnGsXnIc3K9F8dAeUTx5jIIqPXahvg4B0yotr0OyoOaxuR0zUhyO1hku9Q==",
|
||||
"dependencies": {
|
||||
"@contentstack/utils": "^1.3.15",
|
||||
"es6-promise": "^4.2.8",
|
||||
"fetch-mock": "^12.2.0",
|
||||
"localStorage": "1.0.4",
|
||||
"qs": "^6.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/contentstack/node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
@@ -11081,9 +11190,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -11690,7 +11799,6 @@
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -11935,6 +12043,19 @@
|
||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/duplexer": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
|
||||
@@ -12172,13 +12293,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -12187,7 +12304,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -12227,7 +12343,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
|
||||
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
@@ -12275,6 +12390,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-promise": {
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
@@ -13109,6 +13229,21 @@
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-mock": {
|
||||
"version": "12.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-12.3.0.tgz",
|
||||
"integrity": "sha512-+ZHzLuzrKpP3u5PZo8ghFP1Kr3UJUTZ5PT/uQZtLv7UagDCVRt1bSzVg6MoTFdjQ0GXsx/crq2t0tGabkbH2yA==",
|
||||
"dependencies": {
|
||||
"@types/glob-to-regexp": "^0.4.4",
|
||||
"dequal": "^2.0.3",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"is-subset-of": "^3.1.10",
|
||||
"regexparam": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-retry": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-6.0.0.tgz",
|
||||
@@ -13441,16 +13576,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"dev": true,
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
|
||||
"integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.0",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -13476,6 +13615,18 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stdin": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz",
|
||||
@@ -13596,8 +13747,7 @@
|
||||
"node_modules/glob-to-regexp": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
|
||||
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
|
||||
"peer": true
|
||||
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
@@ -13721,12 +13871,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -13860,10 +14009,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"dev": true,
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -14710,6 +14858,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-subset-of": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/is-subset-of/-/is-subset-of-3.1.10.tgz",
|
||||
"integrity": "sha512-avvaYgVmYWyaZ1NDFiv4y9JGkrE2je3op1Po4VYKKJKR8H2qVPsg1GZuuXl5elCTxTlwAIsrAjWAs4BVrISFRw==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||
"dependencies": {
|
||||
"typedescriptor": "3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/is-symbol": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
|
||||
@@ -17239,6 +17396,14 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/localStorage": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/localStorage/-/localStorage-1.0.4.tgz",
|
||||
"integrity": "sha512-r35zrihcDiX+dqWlJSeIwS9nrF95OQTgqMFm3FB2D/+XgdmZtcutZOb7t0xXkhOEM8a9kpuu7cc28g1g36I5DQ==",
|
||||
"engines": {
|
||||
"node": ">= v0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
@@ -17707,6 +17872,14 @@
|
||||
"integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.30",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
|
||||
@@ -18111,12 +18284,12 @@
|
||||
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.19.tgz",
|
||||
"integrity": "sha512-YHu1igcAxZPh8ZB7GIM93dqgY6gcAzq66FOhQFheAdOx1raxNcApt05nNyNCSB6NegSiyJ4XOPsaNow4pfDmsg==",
|
||||
"dependencies": {
|
||||
"@auth/core": "0.32.0"
|
||||
"@auth/core": "0.37.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@simplewebauthn/browser": "^9.0.1",
|
||||
"@simplewebauthn/server": "^9.0.2",
|
||||
"next": "^14 || ^15.0.0-0",
|
||||
"next": "^14.0.0-0 || ^15.0.0-0",
|
||||
"nodemailer": "^6.6.5",
|
||||
"react": "^18.2.0 || ^19.0.0-0"
|
||||
},
|
||||
@@ -18246,9 +18419,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/oauth4webapi": {
|
||||
"version": "2.17.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.17.0.tgz",
|
||||
"integrity": "sha512-lbC0Z7uzAFNFyzEYRIC+pkSVvDHJTbEW+dYlSBAlCYDe6RxUkJ26bClhk8ocBZip1wfI9uKTe0fm4Ib4RHn6uQ==",
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.4.tgz",
|
||||
"integrity": "sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
@@ -18262,10 +18435,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
||||
"dev": true,
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@@ -19960,6 +20135,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/regexparam": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz",
|
||||
"integrity": "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/regexpu-core": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz",
|
||||
@@ -20601,15 +20784,65 @@
|
||||
"integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||
"dev": true,
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-list": "^1.0.0",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -21939,6 +22172,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/typedescriptor": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typedescriptor/-/typedescriptor-3.0.2.tgz",
|
||||
"integrity": "sha512-hyVbaCUd18UiXk656g/imaBLMogpdijIEpnhWYrSda9rhvO4gOU16n2nh7xG5lv/rjumnZzGOdz0CEGTmFe0fQ==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.4.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
||||
@@ -22295,6 +22534,11 @@
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/urlpattern-polyfill": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz",
|
||||
"integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ=="
|
||||
},
|
||||
"node_modules/use-callback-ref": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz",
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
"@contentstack/live-preview-utils": "^3.0.0",
|
||||
"@hookform/error-message": "^2.0.1",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@netlify/blobs": "^8.1.0",
|
||||
"@netlify/functions": "^3.0.0",
|
||||
"@netlify/plugin-nextjs": "^5.9.4",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/sdk-metrics": "^1.25.1",
|
||||
@@ -51,6 +53,7 @@
|
||||
"@vis.gl/react-google-maps": "^1.2.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clean-deep": "^3.4.0",
|
||||
"contentstack": "^3.23.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"deepmerge": "^4.3.1",
|
||||
"downshift": "^9.0.8",
|
||||
|
||||
@@ -25,6 +25,12 @@
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"netlify/functions/**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
50
types/sitemap.ts
Normal file
50
types/sitemap.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { SyncResult } from "contentstack"
|
||||
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
export type ChangeFrequency =
|
||||
| "always"
|
||||
| "hourly"
|
||||
| "daily"
|
||||
| "weekly"
|
||||
| "monthly"
|
||||
| "yearly"
|
||||
| "never"
|
||||
|
||||
export interface SitemapEntry {
|
||||
url: string
|
||||
lastModified: string
|
||||
changeFrequency: ChangeFrequency
|
||||
priority: number
|
||||
alternates?: Partial<Record<Lang, string>>
|
||||
}
|
||||
|
||||
export type SitemapData = SitemapEntry[]
|
||||
|
||||
export interface SyncItemData {
|
||||
uid: string
|
||||
locale: Lang
|
||||
url?: string
|
||||
updated_at: string
|
||||
web?: {
|
||||
seo_metadata?: {
|
||||
noindex?: boolean | null
|
||||
sitemap?: {
|
||||
change_frequency: SitemapEntry["changeFrequency"]
|
||||
priority: SitemapEntry["priority"]
|
||||
} | null
|
||||
}
|
||||
}
|
||||
}
|
||||
export interface SyncItem {
|
||||
type: string
|
||||
data: SyncItemData
|
||||
}
|
||||
export interface SyncResponse extends Omit<SyncResult, "items"> {
|
||||
items: SyncItem[]
|
||||
}
|
||||
|
||||
export type SyncItemsByUid = {
|
||||
mainEntry: SyncItem
|
||||
alternates: SyncItem[]
|
||||
}
|
||||
75
utils/sitemap.ts
Normal file
75
utils/sitemap.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { getStore } from "@netlify/blobs"
|
||||
|
||||
import { env } from "@/env/server"
|
||||
|
||||
import type { SitemapData, SyncItem } from "@/types/sitemap"
|
||||
|
||||
const branch = env.CMS_BRANCH
|
||||
const environment = env.CMS_ENVIRONMENT
|
||||
const entriesKey = `${environment}/${branch}/entries`
|
||||
const syncTokenKey = `${environment}/${branch}/syncToken`
|
||||
const sitemapDataKey = `${environment}/${branch}/sitemapData`
|
||||
const lastUpdatedKey = `${environment}/${branch}/lastUpdated`
|
||||
const MAX_ENTRIES_PER_SITEMAP = 50000
|
||||
|
||||
// We need to wrap `getStore` because calling it in the root of the file causes
|
||||
// it to be executed during build time. This is not supported by Netlify.
|
||||
function store() {
|
||||
return getStore("sitemap")
|
||||
}
|
||||
|
||||
export async function saveEntries(entries: SyncItem[]) {
|
||||
await store().setJSON(entriesKey, entries)
|
||||
}
|
||||
|
||||
export async function saveSitemapData(sitemapData: SitemapData) {
|
||||
await store().setJSON(sitemapDataKey, sitemapData)
|
||||
}
|
||||
|
||||
export async function saveLastUpdatedDate(lastUpdated: string) {
|
||||
await store().set(lastUpdatedKey, lastUpdated)
|
||||
}
|
||||
|
||||
export async function saveSyncToken(syncToken: string) {
|
||||
await store().set(syncTokenKey, syncToken)
|
||||
}
|
||||
|
||||
export async function getSyncToken() {
|
||||
return await store().get(syncTokenKey)
|
||||
}
|
||||
|
||||
export async function getEntries() {
|
||||
const entries: SyncItem[] = await store().get(entriesKey, {
|
||||
type: "json",
|
||||
})
|
||||
|
||||
return entries || []
|
||||
}
|
||||
|
||||
export async function getLastUpdated() {
|
||||
return await store().get(lastUpdatedKey)
|
||||
}
|
||||
|
||||
export async function getSitemapData() {
|
||||
const sitemapData: SitemapData | null = await store().get(sitemapDataKey, {
|
||||
type: "json",
|
||||
})
|
||||
|
||||
return sitemapData || []
|
||||
}
|
||||
|
||||
export async function getSitemapDataById(id: number) {
|
||||
const sitemapData = await getSitemapData()
|
||||
const index = id - 1
|
||||
const start = index * MAX_ENTRIES_PER_SITEMAP
|
||||
const end = start + MAX_ENTRIES_PER_SITEMAP
|
||||
return sitemapData.slice(start, end)
|
||||
}
|
||||
|
||||
export async function getSitemapIds() {
|
||||
const sitemapData = await getSitemapData()
|
||||
const numberOfSitemaps = Math.ceil(
|
||||
sitemapData.length / MAX_ENTRIES_PER_SITEMAP
|
||||
)
|
||||
return Array.from({ length: numberOfSitemaps }, (_, index) => index + 1)
|
||||
}
|
||||
Reference in New Issue
Block a user