perf: optimize public content caching
This commit is contained in:
@@ -3,6 +3,7 @@ import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { AboutContactTabs } from '@/components/site/about-contact-tabs'
|
||||
import { PageHero } from '@/components/site/page-hero'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { buildAbsoluteUrl, buildLocaleAlternates, extractRichTextPlainText, trimDescription } from '@/lib/seo'
|
||||
import { normalizeUploadUrl } from '@/lib/upload-url'
|
||||
@@ -15,13 +16,17 @@ function getAboutPageTitle(rawTitle: string | null | undefined) {
|
||||
return normalized
|
||||
}
|
||||
|
||||
async function getAboutPageData(locale: Locale) {
|
||||
const getAboutPageData = cacheCmsQuery(
|
||||
['about-page-global'],
|
||||
async (locale: Locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
return payload.findGlobal({
|
||||
slug: 'aboutPage',
|
||||
locale,
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { AboutContactTabs } from '@/components/site/about-contact-tabs'
|
||||
import { PageHero } from '@/components/site/page-hero'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { buildAbsoluteUrl, buildLocaleAlternates } from '@/lib/seo'
|
||||
import { normalizeUploadUrl } from '@/lib/upload-url'
|
||||
@@ -158,7 +159,9 @@ function normalizeContactGroups(groups: unknown, locale: Locale) {
|
||||
return normalized.length > 0 ? normalized : getDefaultContactGroups(locale)
|
||||
}
|
||||
|
||||
async function getContactUsPageData(locale: Locale) {
|
||||
const getContactUsPageData = cacheCmsQuery(
|
||||
['contact-us-page-global'],
|
||||
async (locale: Locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
return payload.findGlobal({
|
||||
@@ -166,7 +169,8 @@ async function getContactUsPageData(locale: Locale) {
|
||||
locale,
|
||||
slug: 'contactUsPage',
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { PageHero } from '@/components/site/page-hero'
|
||||
import { SiteIcon } from '@/components/site/site-icons'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { buildLocaleAlternates, extractRichTextPlainText, trimDescription } from '@/lib/seo'
|
||||
import { renderRichText } from '@/lib/render-rich-text'
|
||||
@@ -122,7 +123,7 @@ function hasRichTextContent(value: Download['notes'] | Download['changelog'] | n
|
||||
return Boolean(extractRichTextPlainText(value))
|
||||
}
|
||||
|
||||
async function getDownloadProductData(slug: string, locale: Locale) {
|
||||
async function getDownloadProductDataUncached(slug: string, locale: Locale) {
|
||||
const payload = await getPayloadClient()
|
||||
const products = await payload.find({
|
||||
collection: 'products',
|
||||
@@ -231,6 +232,11 @@ async function getDownloadProductData(slug: string, locale: Locale) {
|
||||
}
|
||||
}
|
||||
|
||||
const getDownloadProductData = cacheCmsQuery<
|
||||
[string, Locale],
|
||||
Awaited<ReturnType<typeof getDownloadProductDataUncached>>
|
||||
>(['download-product-data'], getDownloadProductDataUncached)
|
||||
|
||||
function getRelatedProducts(download: Download) {
|
||||
const multi = Array.isArray((download as Download & { products?: DownloadProductReference[] }).products)
|
||||
? ((download as Download & { products?: DownloadProductReference[] }).products as DownloadProductReference[])
|
||||
|
||||
@@ -4,6 +4,7 @@ import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { PageHero } from '@/components/site/page-hero'
|
||||
import { SiteIcon } from '@/components/site/site-icons'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { isTemplateProduct } from '@/lib/product-template-navigation'
|
||||
import { buildAbsoluteUrl, buildLocaleAlternates, extractRichTextPlainText } from '@/lib/seo'
|
||||
@@ -224,7 +225,7 @@ async function getSeedDownloadGroups(locale: Locale): Promise<DownloadGroup[]> {
|
||||
.sort((left, right) => left.productLabel.localeCompare(right.productLabel))
|
||||
}
|
||||
|
||||
async function getDownloadsPageData(locale: Locale) {
|
||||
async function getDownloadsPageDataUncached(locale: Locale) {
|
||||
const payload = await getPayloadClient()
|
||||
const downloadsResult = await payload.find({
|
||||
collection: 'downloads',
|
||||
@@ -337,7 +338,14 @@ async function getDownloadsPageData(locale: Locale) {
|
||||
return getSeedDownloadGroups(locale)
|
||||
}
|
||||
|
||||
async function getDownloadsPageSettings(locale: Locale): Promise<DownloadsPageSettings> {
|
||||
const getDownloadsPageData = cacheCmsQuery<[Locale], Awaited<ReturnType<typeof getDownloadsPageDataUncached>>>(
|
||||
['downloads-page-data'],
|
||||
getDownloadsPageDataUncached,
|
||||
)
|
||||
|
||||
const getDownloadsPageSettings = cacheCmsQuery<[Locale], DownloadsPageSettings>(
|
||||
['downloads-page-global'],
|
||||
async (locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
return (await payload.findGlobal({
|
||||
@@ -345,7 +353,8 @@ async function getDownloadsPageSettings(locale: Locale): Promise<DownloadsPageSe
|
||||
locale,
|
||||
slug: 'downloadsPage',
|
||||
})) as DownloadsPageSettings
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function getRelatedProducts(download: Download) {
|
||||
const multi = Array.isArray((download as Download & { products?: DownloadProductReference[] }).products)
|
||||
|
||||
@@ -3,6 +3,7 @@ import Link from 'next/link'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { renderRichText } from '@/lib/render-rich-text'
|
||||
import { buildAbsoluteUrl, buildLocaleAlternates, extractRichTextPlainText, trimDescription } from '@/lib/seo'
|
||||
@@ -21,7 +22,9 @@ function formatPublishedAt(value: string | null | undefined, locale: Locale) {
|
||||
}).format(new Date(value))
|
||||
}
|
||||
|
||||
async function getNewsArticle(locale: Locale, slug: string) {
|
||||
const getNewsArticle = cacheCmsQuery(
|
||||
['news-article'],
|
||||
async (locale: Locale, slug: string) => {
|
||||
const payload = await getPayloadClient()
|
||||
const result = await payload.find({
|
||||
collection: 'news',
|
||||
@@ -45,7 +48,8 @@ async function getNewsArticle(locale: Locale, slug: string) {
|
||||
})
|
||||
|
||||
return result.docs[0] ?? null
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function getRelatedProduct(product: number | Product) {
|
||||
return typeof product === 'object' ? product : null
|
||||
|
||||
@@ -3,6 +3,7 @@ import Link from 'next/link'
|
||||
import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { getBlogPosts } from '@/lib/blog-api'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { buildAbsoluteUrl, buildLocaleAlternates } from '@/lib/seo'
|
||||
import { normalizeUploadUrl } from '@/lib/upload-url'
|
||||
@@ -31,7 +32,9 @@ type NewsListItem = {
|
||||
title: string
|
||||
}
|
||||
|
||||
async function getNewsPageSettings() {
|
||||
const getNewsPageSettings = cacheCmsQuery(
|
||||
['news-page-global'],
|
||||
async () => {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
try {
|
||||
@@ -41,9 +44,12 @@ async function getNewsPageSettings() {
|
||||
} catch {
|
||||
return { newsSource: 'externalBlog' as const }
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
async function getLocalNewsPage(locale: Locale, page: number) {
|
||||
const getLocalNewsPage = cacheCmsQuery(
|
||||
['local-news-page'],
|
||||
async (locale: Locale, page: number) => {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
return payload.find({
|
||||
@@ -59,7 +65,8 @@ async function getLocalNewsPage(locale: Locale, page: number) {
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function getCoverImageUrl(article: News) {
|
||||
if (!article.coverImage || typeof article.coverImage === 'number') return null
|
||||
|
||||
@@ -9,6 +9,7 @@ import { HomeNews } from '@/components/home/home-news'
|
||||
import { HomeRecognition } from '@/components/home/home-recognition'
|
||||
import { HomeStatement } from '@/components/home/home-statement'
|
||||
import { getBlogPosts } from '@/lib/blog-api'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getSiteChromeSettings } from '@/lib/global-settings'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { VISIBLE_PRODUCT_STATUS } from '@/lib/product-status'
|
||||
@@ -110,16 +111,22 @@ function normalizeHomepageModuleOrder(items: Homepage['moduleOrder']): HomepageM
|
||||
return [...new Set([...ordered, ...validModules])]
|
||||
}
|
||||
|
||||
async function getHomepageData(locale: Locale) {
|
||||
const getHomepageData = cacheCmsQuery<[Locale], Homepage>(
|
||||
['homepage-global'],
|
||||
async (locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
return payload.findGlobal({
|
||||
depth: 2,
|
||||
slug: 'homepage',
|
||||
locale,
|
||||
})
|
||||
}
|
||||
}) as Promise<Homepage>
|
||||
},
|
||||
)
|
||||
|
||||
async function getPublishedProducts(locale: Locale) {
|
||||
const getPublishedProducts = cacheCmsQuery<[Locale], Product[]>(
|
||||
['published-products'],
|
||||
async (locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
const published = await payload.find({
|
||||
collection: 'products',
|
||||
@@ -139,8 +146,10 @@ async function getPublishedProducts(locale: Locale) {
|
||||
if (published.docs.length > 0) {
|
||||
return published.docs
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function getTemplateProductHrefs(products: Product[], locale: Locale) {
|
||||
return products
|
||||
@@ -148,7 +157,9 @@ function getTemplateProductHrefs(products: Product[], locale: Locale) {
|
||||
.map((product) => `/${locale}/products/${product.slug}`)
|
||||
}
|
||||
|
||||
async function getLatestLocalNews(locale: Locale) {
|
||||
const getLatestLocalNews = cacheCmsQuery<[Locale], News[]>(
|
||||
['latest-local-news'],
|
||||
async (locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
const result = await payload.find({
|
||||
collection: 'news',
|
||||
@@ -164,7 +175,8 @@ async function getLatestLocalNews(locale: Locale) {
|
||||
})
|
||||
|
||||
return result.docs
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function getLocalNewsHighlights(items: Homepage['newsHighlights']) {
|
||||
return (items || []).filter((item): item is News => typeof item === 'object')
|
||||
|
||||
@@ -3,6 +3,7 @@ import { notFound } from 'next/navigation'
|
||||
import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { ALL_PRODUCTS_FALLBACK } from '@/lib/fallback-data'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { ACCESSIBLE_PRODUCT_STATUSES } from '@/lib/product-status'
|
||||
import { getProductSeriesDefault } from '@/lib/product-series-defaults'
|
||||
@@ -336,7 +337,7 @@ function ProductSpecificationsSection({
|
||||
)
|
||||
}
|
||||
|
||||
async function getProductPageData(slug: string, locale: Locale) {
|
||||
async function getProductPageDataUncached(slug: string, locale: Locale) {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
const products = await payload.find({
|
||||
@@ -455,6 +456,11 @@ async function getProductPageData(slug: string, locale: Locale) {
|
||||
return { downloads, news: relatedNews, overviewMedia, product, reviews: reviews.docs, videos: videos.docs }
|
||||
}
|
||||
|
||||
const getProductPageData = cacheCmsQuery<[string, Locale], Awaited<ReturnType<typeof getProductPageDataUncached>>>(
|
||||
['product-page-data'],
|
||||
getProductPageDataUncached,
|
||||
)
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import Link from 'next/link'
|
||||
import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { PageHero } from '@/components/site/page-hero'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { getProductSeriesDefault } from '@/lib/product-series-defaults'
|
||||
import { VISIBLE_PRODUCT_STATUS } from '@/lib/product-status'
|
||||
@@ -100,7 +101,7 @@ function normalizeCategory(category: number | ProductCategory | null | undefined
|
||||
return getDefaultSeriesCopy(getCategoryKeyFromSlug(slug), locale)
|
||||
}
|
||||
|
||||
async function getProductsPageData(locale: Locale) {
|
||||
async function getProductsPageDataUncached(locale: Locale) {
|
||||
const payload = await getPayloadClient()
|
||||
const result = await payload.find({
|
||||
collection: 'products',
|
||||
@@ -162,6 +163,43 @@ async function getProductsPageData(locale: Locale) {
|
||||
}))
|
||||
}
|
||||
|
||||
const getProductsPageData = cacheCmsQuery<[Locale], Awaited<ReturnType<typeof getProductsPageDataUncached>>>(
|
||||
['products-page-data'],
|
||||
getProductsPageDataUncached,
|
||||
)
|
||||
|
||||
const getProductsPageSettings = cacheCmsQuery(
|
||||
['products-page-global'],
|
||||
async (locale: Locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
return payload.findGlobal({
|
||||
depth: 1,
|
||||
slug: 'productsPage',
|
||||
locale,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
const getProductCategories = cacheCmsQuery<[Locale], ProductCategory[]>(
|
||||
['product-categories'],
|
||||
async (locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
const categories = await payload.find({
|
||||
collection: 'productCategories',
|
||||
depth: 0,
|
||||
limit: 50,
|
||||
locale,
|
||||
sort: 'order',
|
||||
where: {
|
||||
and: [{ docLocale: { equals: locale } }, { name: { exists: true } }],
|
||||
},
|
||||
})
|
||||
|
||||
return categories.docs
|
||||
},
|
||||
)
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
@@ -187,25 +225,13 @@ export default async function ProductsPage({
|
||||
const { locale } = await params
|
||||
setRequestLocale(locale)
|
||||
|
||||
const products = await getProductsPageData(locale)
|
||||
const payload = await getPayloadClient()
|
||||
const page = await payload.findGlobal({
|
||||
depth: 1,
|
||||
slug: 'productsPage',
|
||||
locale,
|
||||
})
|
||||
const categories = await payload.find({
|
||||
collection: 'productCategories',
|
||||
depth: 0,
|
||||
limit: 50,
|
||||
locale,
|
||||
sort: 'order',
|
||||
where: {
|
||||
and: [{ docLocale: { equals: locale } }, { name: { exists: true } }],
|
||||
},
|
||||
})
|
||||
const [products, page, categories] = await Promise.all([
|
||||
getProductsPageData(locale),
|
||||
getProductsPageSettings(locale),
|
||||
getProductCategories(locale),
|
||||
])
|
||||
const categoryMap = new Map(
|
||||
categories.docs.map((category) => [category.slug, normalizeCategory(category, category.slug, locale)]),
|
||||
categories.map((category) => [category.slug, normalizeCategory(category, category.slug, locale)]),
|
||||
)
|
||||
const groups = new Map<string, { series: ProductSeries; items: ProductCard[] }>()
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { PageHero } from '@/components/site/page-hero'
|
||||
import { VideoModalGrid, type VideoModalGridItem } from '@/components/site/video-modal-grid'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { buildAbsoluteUrl, buildLocaleAlternates, trimDescription } from '@/lib/seo'
|
||||
import { normalizeUploadUrl } from '@/lib/upload-url'
|
||||
@@ -58,7 +59,7 @@ function truncateSourceName(value: string | null | undefined) {
|
||||
return chars.length > 20 ? `${chars.slice(0, 20).join('')}...` : source
|
||||
}
|
||||
|
||||
async function getReviewsPageData(locale: Locale) {
|
||||
async function getReviewsPageDataUncached(locale: Locale) {
|
||||
const payload = await getPayloadClient()
|
||||
const [videos, reviews] = await Promise.all([
|
||||
payload.find({
|
||||
@@ -95,7 +96,12 @@ async function getReviewsPageData(locale: Locale) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getReviewsPageSettings(locale: Locale) {
|
||||
const getReviewsPageData = cacheCmsQuery<[Locale], Awaited<ReturnType<typeof getReviewsPageDataUncached>>>(
|
||||
['reviews-page-data'],
|
||||
getReviewsPageDataUncached,
|
||||
)
|
||||
|
||||
async function getReviewsPageSettingsUncached(locale: Locale) {
|
||||
const payload = await getPayloadClient()
|
||||
const fallback = {
|
||||
description:
|
||||
@@ -141,6 +147,11 @@ async function getReviewsPageSettings(locale: Locale) {
|
||||
}
|
||||
}
|
||||
|
||||
const getReviewsPageSettings = cacheCmsQuery<[Locale], Awaited<ReturnType<typeof getReviewsPageSettingsUncached>>>(
|
||||
['reviews-page-global'],
|
||||
getReviewsPageSettingsUncached,
|
||||
)
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import Link from 'next/link'
|
||||
import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { PageHero } from '@/components/site/page-hero'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { VISIBLE_PRODUCT_STATUS } from '@/lib/product-status'
|
||||
import { renderRichText } from '@/lib/render-rich-text'
|
||||
@@ -13,7 +14,7 @@ import { SupportContactForm } from './support-contact-form'
|
||||
|
||||
export const revalidate = 300
|
||||
|
||||
async function getContactPageData(locale: Locale) {
|
||||
async function getContactPageDataUncached(locale: Locale) {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
const [page, products] = await Promise.all([
|
||||
@@ -40,6 +41,11 @@ async function getContactPageData(locale: Locale) {
|
||||
return { page, products: products.docs }
|
||||
}
|
||||
|
||||
const getContactPageData = cacheCmsQuery<[Locale], Awaited<ReturnType<typeof getContactPageDataUncached>>>(
|
||||
['support-contact-page-data'],
|
||||
getContactPageDataUncached,
|
||||
)
|
||||
|
||||
function getContactPageTitle(rawTitle: string | null | undefined, locale: Locale) {
|
||||
const normalized = rawTitle?.trim()
|
||||
if (!normalized) return ''
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Metadata } from 'next'
|
||||
import Link from 'next/link'
|
||||
import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { buildAbsoluteUrl, buildLocaleAlternates, extractRichTextPlainText } from '@/lib/seo'
|
||||
import { normalizeUploadUrl } from '@/lib/upload-url'
|
||||
@@ -125,7 +126,9 @@ function getFaqHeroImageUrl(settings: FaqPageSettings) {
|
||||
return normalizeUploadUrl(settings.hero?.imageUrl?.trim() || null)
|
||||
}
|
||||
|
||||
async function getFaqPageSettings(locale: Locale): Promise<FaqPageSettings> {
|
||||
const getFaqPageSettings = cacheCmsQuery<[Locale], FaqPageSettings>(
|
||||
['faq-page-global'],
|
||||
async (locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
try {
|
||||
@@ -138,9 +141,10 @@ async function getFaqPageSettings(locale: Locale): Promise<FaqPageSettings> {
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
async function getFaqPageData(locale: Locale) {
|
||||
async function getFaqPageDataUncached(locale: Locale) {
|
||||
const payload = await getPayloadClient()
|
||||
const result = await payload.find({
|
||||
collection: 'faqs',
|
||||
@@ -164,6 +168,11 @@ async function getFaqPageData(locale: Locale) {
|
||||
return [...groups.entries()].map(([group, faqs]) => ({ faqs, group }))
|
||||
}
|
||||
|
||||
const getFaqPageData = cacheCmsQuery<[Locale], Awaited<ReturnType<typeof getFaqPageDataUncached>>>(
|
||||
['faq-page-data'],
|
||||
getFaqPageDataUncached,
|
||||
)
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { PageHero } from '@/components/site/page-hero'
|
||||
import { SiteIcon } from '@/components/site/site-icons'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { renderRichText } from '@/lib/render-rich-text'
|
||||
import { buildAbsoluteUrl, buildLocaleAlternates, extractRichTextPlainText, trimDescription } from '@/lib/seo'
|
||||
@@ -17,14 +18,17 @@ type SupportEntry = NonNullable<SupportPageData['primaryEntries']>[number] & {
|
||||
iconKey?: string | null
|
||||
}
|
||||
|
||||
async function getSupportPageData(locale: Locale) {
|
||||
const getSupportPageData = cacheCmsQuery(
|
||||
['support-page-global'],
|
||||
async (locale: Locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
return payload.findGlobal({
|
||||
slug: 'supportPage',
|
||||
locale,
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function getSupportPageTitle(rawTitle: string | null | undefined, locale: Locale) {
|
||||
const normalized = rawTitle?.trim()
|
||||
|
||||
@@ -3,6 +3,7 @@ import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { PageHero } from '@/components/site/page-hero'
|
||||
import { VideoModalGrid, type VideoModalGridItem } from '@/components/site/video-modal-grid'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { buildAbsoluteUrl, buildLocaleAlternates, trimDescription } from '@/lib/seo'
|
||||
import { normalizeUploadUrl } from '@/lib/upload-url'
|
||||
@@ -54,7 +55,7 @@ const FALLBACK_VIDEOS: TutorialVideo[] = [
|
||||
},
|
||||
]
|
||||
|
||||
async function getTutorialPageData(locale: Locale) {
|
||||
async function getTutorialPageDataUncached(locale: Locale) {
|
||||
const payload = await getPayloadClient()
|
||||
const [page, videos] = await Promise.all([
|
||||
payload.findGlobal({
|
||||
@@ -87,6 +88,11 @@ async function getTutorialPageData(locale: Locale) {
|
||||
}
|
||||
}
|
||||
|
||||
const getTutorialPageData = cacheCmsQuery<[Locale], Awaited<ReturnType<typeof getTutorialPageDataUncached>>>(
|
||||
['support-tutorial-page-data'],
|
||||
getTutorialPageDataUncached,
|
||||
)
|
||||
|
||||
function getTutorialTitle(tutorial: TutorialSettings | null | undefined, locale: Locale) {
|
||||
return tutorial?.hero?.title?.trim() || (locale === 'zh' ? '视频教程' : 'Video Tutorials')
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import Link from 'next/link'
|
||||
import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { PageHero } from '@/components/site/page-hero'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { renderRichText } from '@/lib/render-rich-text'
|
||||
import { buildLocaleAlternates, extractRichTextPlainText, trimDescription } from '@/lib/seo'
|
||||
@@ -11,14 +12,17 @@ import type { WarrantyPage as WarrantyPageType } from '@/payload-types'
|
||||
|
||||
export const revalidate = 300
|
||||
|
||||
async function getWarrantyPageData(locale: Locale) {
|
||||
const getWarrantyPageData = cacheCmsQuery(
|
||||
['warranty-page-global'],
|
||||
async (locale: Locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
return payload.findGlobal({
|
||||
slug: 'warrantyPage',
|
||||
locale,
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function getWarrantyPageTitle(rawTitle: string | null | undefined, locale: Locale) {
|
||||
const normalized = rawTitle?.trim()
|
||||
|
||||
@@ -3,6 +3,7 @@ import Link from 'next/link'
|
||||
import { setRequestLocale } from 'next-intl/server'
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { PageHero } from '@/components/site/page-hero'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { buildAbsoluteUrl, buildLocaleAlternates } from '@/lib/seo'
|
||||
import { normalizeUploadUrl } from '@/lib/upload-url'
|
||||
@@ -292,7 +293,7 @@ function getMapPoints(dealers: Dealer[]) {
|
||||
return [...grouped.values()].sort((left, right) => left.region.localeCompare(right.region))
|
||||
}
|
||||
|
||||
async function getDealersPageSettings(locale: Locale) {
|
||||
async function getDealersPageSettingsUncached(locale: Locale) {
|
||||
const payload = await getPayloadClient()
|
||||
|
||||
try {
|
||||
@@ -348,7 +349,12 @@ async function getDealersPageSettings(locale: Locale) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getDealersPageData(locale: Locale) {
|
||||
const getDealersPageSettings = cacheCmsQuery<
|
||||
[Locale],
|
||||
Awaited<ReturnType<typeof getDealersPageSettingsUncached>>
|
||||
>(['dealers-page-global'], getDealersPageSettingsUncached)
|
||||
|
||||
async function getDealersPageDataUncached(locale: Locale) {
|
||||
const payload = await getPayloadClient()
|
||||
const result = await payload.find({
|
||||
collection: 'dealers',
|
||||
@@ -381,6 +387,11 @@ async function getDealersPageData(locale: Locale) {
|
||||
}
|
||||
}
|
||||
|
||||
const getDealersPageData = cacheCmsQuery<[Locale], Awaited<ReturnType<typeof getDealersPageDataUncached>>>(
|
||||
['dealers-page-data'],
|
||||
getDealersPageDataUncached,
|
||||
)
|
||||
|
||||
function DealerTabs({
|
||||
activeTab,
|
||||
locale,
|
||||
@@ -677,8 +688,10 @@ export default async function WhereToBuyPage({
|
||||
const query = await searchParams
|
||||
setRequestLocale(locale)
|
||||
|
||||
const { dealerDocs, dealerGroups, storeDocs } = await getDealersPageData(locale)
|
||||
const settings = await getDealersPageSettings(locale)
|
||||
const [{ dealerDocs, dealerGroups, storeDocs }, settings] = await Promise.all([
|
||||
getDealersPageData(locale),
|
||||
getDealersPageSettings(locale),
|
||||
])
|
||||
const hasDealers = dealerDocs.length > 0
|
||||
const hasStores = storeDocs.length > 0
|
||||
const shouldShowTabs = hasDealers && hasStores
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use server'
|
||||
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { revalidatePath, revalidateTag } from 'next/cache'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { CMS_CACHE_TAG } from '@/lib/cms-cache'
|
||||
|
||||
const CACHE_PATHS = [
|
||||
'/sitemap.xml',
|
||||
@@ -45,6 +46,8 @@ function normalizeReturnPath(value: FormDataEntryValue | null) {
|
||||
export async function clearFrontendCache(formData: FormData) {
|
||||
const returnTo = normalizeReturnPath(formData.get('returnTo'))
|
||||
|
||||
revalidateTag(CMS_CACHE_TAG)
|
||||
|
||||
for (const path of CACHE_PATHS) {
|
||||
revalidatePath(path)
|
||||
}
|
||||
@@ -59,6 +62,7 @@ export async function clearFrontendCache(formData: FormData) {
|
||||
export async function refreshSitemapCache(formData: FormData) {
|
||||
const returnTo = normalizeReturnPath(formData.get('returnTo'))
|
||||
|
||||
revalidateTag(CMS_CACHE_TAG)
|
||||
revalidatePath('/sitemap.xml')
|
||||
|
||||
redirect(`${returnTo}${returnTo.includes('?') ? '&' : '?'}sitemap=refreshed`)
|
||||
|
||||
+10
-1
@@ -1,9 +1,18 @@
|
||||
import type { MetadataRoute } from 'next'
|
||||
import { getSiteUrl } from '@/lib/seo'
|
||||
|
||||
function isPublicEversoloHost(siteUrl: string) {
|
||||
try {
|
||||
const hostname = new URL(siteUrl).hostname.toLowerCase()
|
||||
return hostname === 'eversolo.com' || hostname.endsWith('.eversolo.com')
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
const siteUrl = getSiteUrl()
|
||||
const isProduction = /^https?:\/\/(www\.)?eversolo\./i.test(siteUrl)
|
||||
const isProduction = isPublicEversoloHost(siteUrl)
|
||||
|
||||
return {
|
||||
host: siteUrl,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { unstable_cache } from 'next/cache'
|
||||
|
||||
export const CMS_CACHE_TAG = 'cms-public-content'
|
||||
export const PUBLIC_REVALIDATE_SECONDS = 300
|
||||
|
||||
export function cacheCmsQuery<Args extends unknown[], Result>(
|
||||
keyParts: string[],
|
||||
callback: (...args: Args) => Promise<Result>,
|
||||
): (...args: Args) => Promise<Result> {
|
||||
return unstable_cache(callback, ['cms', ...keyParts], {
|
||||
revalidate: PUBLIC_REVALIDATE_SECONDS,
|
||||
tags: [CMS_CACHE_TAG],
|
||||
}) as (...args: Args) => Promise<Result>
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { normalizeUploadUrl } from '@/lib/upload-url'
|
||||
import type { GlobalSetting, Media } from '@/payload-types'
|
||||
@@ -187,7 +188,9 @@ function ensureReviewsFooterLink(columns: FooterColumn[], locale: Locale): Foote
|
||||
)
|
||||
}
|
||||
|
||||
export async function getSiteChromeSettings(locale: Locale): Promise<SiteChromeSettings> {
|
||||
export const getSiteChromeSettings = cacheCmsQuery<[Locale], SiteChromeSettings>(
|
||||
['site-chrome-settings'],
|
||||
async (locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
const settings = (await payload.findGlobal({
|
||||
slug: 'globalSettings',
|
||||
@@ -246,4 +249,5 @@ export async function getSiteChromeSettings(locale: Locale): Promise<SiteChromeS
|
||||
siteName: cmsValue(settings.site?.name, fallback.siteName),
|
||||
siteTagline: cmsValue(settings.site?.tagline, fallback.siteTagline),
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Locale } from '@/i18n/config'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { getProductSeriesDefault } from '@/lib/product-series-defaults'
|
||||
import { VISIBLE_PRODUCT_STATUS } from '@/lib/product-status'
|
||||
@@ -74,7 +75,9 @@ function getCategorySeries(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSiteProductMenuData(locale: Locale): Promise<SiteProductMenuSeries[]> {
|
||||
export const getSiteProductMenuData = cacheCmsQuery<[Locale], SiteProductMenuSeries[]>(
|
||||
['site-product-menu'],
|
||||
async (locale) => {
|
||||
const payload = await getPayloadClient()
|
||||
const result = await payload.find({
|
||||
collection: 'products',
|
||||
@@ -120,4 +123,5 @@ export async function getSiteProductMenuData(locale: Locale): Promise<SiteProduc
|
||||
products: group.products.sort((left, right) => left.order - right.order),
|
||||
}))
|
||||
.sort((left, right) => left.order - right.order)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import { createReadStream } from 'node:fs'
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { Readable } from 'node:stream'
|
||||
|
||||
const IMMUTABLE_CACHE_CONTROL = 'public, max-age=31536000, immutable'
|
||||
|
||||
type ServeStaticFileOptions = {
|
||||
contentDisposition?: (filePath: string) => string | null
|
||||
contentType: (filePath: string) => string
|
||||
headOnly?: boolean
|
||||
parts: string[]
|
||||
request: Request
|
||||
root: string
|
||||
}
|
||||
|
||||
type ByteRange = {
|
||||
end: number
|
||||
start: number
|
||||
}
|
||||
|
||||
async function resolveExistingFile(root: string, requestedPath: string) {
|
||||
const normalizedRoot = path.resolve(root)
|
||||
const resolved = path.resolve(path.join(root, requestedPath))
|
||||
|
||||
if (!resolved.startsWith(`${normalizedRoot}${path.sep}`) && resolved !== normalizedRoot) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.access(resolved)
|
||||
return resolved
|
||||
} catch {
|
||||
const dir = path.dirname(resolved)
|
||||
const target = path.basename(resolved).toLowerCase()
|
||||
|
||||
try {
|
||||
const entries = await fs.readdir(dir)
|
||||
const match = entries.find((entry) => entry.toLowerCase() === target)
|
||||
return match ? path.join(dir, match) : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseRangeHeader(value: string | null, size: number): ByteRange | 'invalid' | null {
|
||||
if (!value) return null
|
||||
const match = /^bytes=(\d*)-(\d*)$/.exec(value.trim())
|
||||
if (!match) return 'invalid'
|
||||
|
||||
const [, rawStart, rawEnd] = match
|
||||
if (!rawStart && !rawEnd) return 'invalid'
|
||||
|
||||
if (!rawStart) {
|
||||
const suffixLength = Number(rawEnd)
|
||||
if (!Number.isFinite(suffixLength) || suffixLength <= 0) return 'invalid'
|
||||
return {
|
||||
end: size - 1,
|
||||
start: Math.max(size - suffixLength, 0),
|
||||
}
|
||||
}
|
||||
|
||||
const start = Number(rawStart)
|
||||
const end = rawEnd ? Number(rawEnd) : size - 1
|
||||
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end < start || start >= size) {
|
||||
return 'invalid'
|
||||
}
|
||||
|
||||
return {
|
||||
end: Math.min(end, size - 1),
|
||||
start,
|
||||
}
|
||||
}
|
||||
|
||||
function toWebStream(filePath: string, range?: ByteRange) {
|
||||
const stream = createReadStream(filePath, range)
|
||||
return Readable.toWeb(stream) as ReadableStream<Uint8Array>
|
||||
}
|
||||
|
||||
function createBaseHeaders(options: {
|
||||
contentDisposition?: string | null
|
||||
contentType: string
|
||||
etag: string
|
||||
lastModified: string
|
||||
}) {
|
||||
const headers = new Headers({
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Cache-Control': IMMUTABLE_CACHE_CONTROL,
|
||||
'Content-Type': options.contentType,
|
||||
ETag: options.etag,
|
||||
'Last-Modified': options.lastModified,
|
||||
})
|
||||
|
||||
if (options.contentDisposition) {
|
||||
headers.set('Content-Disposition', options.contentDisposition)
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
function isFresh(request: Request, etag: string, mtimeMs: number) {
|
||||
const ifNoneMatch = request.headers.get('if-none-match')
|
||||
if (ifNoneMatch?.split(',').map((value) => value.trim()).includes(etag)) return true
|
||||
|
||||
const ifModifiedSince = request.headers.get('if-modified-since')
|
||||
if (!ifModifiedSince) return false
|
||||
|
||||
const since = Date.parse(ifModifiedSince)
|
||||
return Number.isFinite(since) && since >= Math.floor(mtimeMs)
|
||||
}
|
||||
|
||||
export async function serveStaticFile({
|
||||
contentDisposition,
|
||||
contentType,
|
||||
headOnly = false,
|
||||
parts,
|
||||
request,
|
||||
root,
|
||||
}: ServeStaticFileOptions) {
|
||||
const requestedPath = parts.join('/')
|
||||
const filePath = await resolveExistingFile(root, requestedPath)
|
||||
if (!filePath) return new Response('Not Found', { status: 404 })
|
||||
|
||||
const stats = await fs.stat(filePath)
|
||||
if (!stats.isFile()) return new Response('Not Found', { status: 404 })
|
||||
|
||||
const etag = `W/"${stats.size}-${Math.floor(stats.mtimeMs)}"`
|
||||
const lastModified = stats.mtime.toUTCString()
|
||||
const headers = createBaseHeaders({
|
||||
contentDisposition: contentDisposition?.(filePath),
|
||||
contentType: contentType(filePath),
|
||||
etag,
|
||||
lastModified,
|
||||
})
|
||||
const range = parseRangeHeader(request.headers.get('range'), stats.size)
|
||||
|
||||
if (range === 'invalid') {
|
||||
return new Response(null, {
|
||||
headers: {
|
||||
'Content-Range': `bytes */${stats.size}`,
|
||||
},
|
||||
status: 416,
|
||||
})
|
||||
}
|
||||
|
||||
if (!range && isFresh(request, etag, stats.mtimeMs)) {
|
||||
return new Response(null, {
|
||||
headers,
|
||||
status: 304,
|
||||
})
|
||||
}
|
||||
|
||||
if (range) {
|
||||
headers.set('Content-Length', String(range.end - range.start + 1))
|
||||
headers.set('Content-Range', `bytes ${range.start}-${range.end}/${stats.size}`)
|
||||
|
||||
return new Response(headOnly ? null : toWebStream(filePath, range), {
|
||||
headers,
|
||||
status: 206,
|
||||
})
|
||||
}
|
||||
|
||||
headers.set('Content-Length', String(stats.size))
|
||||
|
||||
return new Response(headOnly ? null : toWebStream(filePath), {
|
||||
headers,
|
||||
status: 200,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { MigrateDownArgs, MigrateUpArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('public.products') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'products' AND column_name = 'doc_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'products' AND column_name = 'status')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'products' AND column_name = 'order')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "products_doc_locale_status_order_idx" ON "products" USING btree ("doc_locale", "status", "order")';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.products') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'products' AND column_name = 'doc_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'products' AND column_name = 'model')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "products_doc_locale_model_idx" ON "products" USING btree ("doc_locale", "model")';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.products_locales') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'products_locales' AND column_name = '_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'products_locales' AND column_name = 'slug')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'products_locales' AND column_name = '_parent_id')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "products_locales_locale_slug_parent_idx" ON "products_locales" USING btree ("_locale", "slug", "_parent_id")';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.product_categories') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'product_categories' AND column_name = 'doc_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'product_categories' AND column_name = 'order')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "product_categories_doc_locale_order_idx" ON "product_categories" USING btree ("doc_locale", "order")';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.downloads') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'downloads' AND column_name = 'doc_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'downloads' AND column_name = 'status')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'downloads' AND column_name = 'visibility_show_in_downloads_center')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'downloads' AND column_name = 'release_date')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "downloads_doc_locale_status_visibility_release_idx" ON "downloads" USING btree ("doc_locale", "status", "visibility_show_in_downloads_center", "release_date" DESC)';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.downloads_rels') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'downloads_rels' AND column_name = 'products_id')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'downloads_rels' AND column_name = 'parent_id')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "downloads_rels_products_parent_idx" ON "downloads_rels" USING btree ("products_id", "parent_id")';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.news') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'news' AND column_name = 'doc_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'news' AND column_name = 'published_at')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'news' AND column_name = 'updated_at')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "news_doc_locale_published_idx" ON "news" USING btree ("doc_locale", "published_at" DESC, "updated_at" DESC)';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.news_locales') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'news_locales' AND column_name = '_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'news_locales' AND column_name = 'slug')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'news_locales' AND column_name = '_parent_id')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "news_locales_locale_slug_parent_idx" ON "news_locales" USING btree ("_locale", "slug", "_parent_id")';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.news_rels') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'news_rels' AND column_name = 'products_id')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'news_rels' AND column_name = 'parent_id')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "news_rels_products_parent_idx" ON "news_rels" USING btree ("products_id", "parent_id")';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.dealers') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'dealers' AND column_name = 'doc_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'dealers' AND column_name = 'is_authorized')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'dealers' AND column_name = 'order')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "dealers_doc_locale_authorized_order_idx" ON "dealers" USING btree ("doc_locale", "is_authorized", "order")';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.faqs') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'faqs' AND column_name = 'doc_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'faqs' AND column_name = 'group')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'faqs' AND column_name = 'order')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "faqs_doc_locale_group_order_idx" ON "faqs" USING btree ("doc_locale", "group", "order")';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.faqs_locales') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'faqs_locales' AND column_name = '_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'faqs_locales' AND column_name = 'question')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'faqs_locales' AND column_name = '_parent_id')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "faqs_locales_locale_question_parent_idx" ON "faqs_locales" USING btree ("_locale", "question", "_parent_id")';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.product_videos') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'product_videos' AND column_name = 'doc_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'product_videos' AND column_name = 'product_id')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'product_videos' AND column_name = 'status')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'product_videos' AND column_name = 'sort_order')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'product_videos' AND column_name = 'published_at')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "product_videos_doc_locale_product_status_sort_idx" ON "product_videos" USING btree ("doc_locale", "product_id", "status", "sort_order", "published_at" DESC)';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.product_reviews') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'product_reviews' AND column_name = 'doc_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'product_reviews' AND column_name = 'product_id')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'product_reviews' AND column_name = 'status')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'product_reviews' AND column_name = 'sort_order')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'product_reviews' AND column_name = 'published_at')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "product_reviews_doc_locale_product_status_sort_idx" ON "product_reviews" USING btree ("doc_locale", "product_id", "status", "sort_order", "published_at" DESC)';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.support_tutorial_videos') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'support_tutorial_videos' AND column_name = 'doc_locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'support_tutorial_videos' AND column_name = 'status')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'support_tutorial_videos' AND column_name = 'sort_order')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'support_tutorial_videos' AND column_name = 'published_at')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "support_tutorial_videos_doc_locale_status_sort_idx" ON "support_tutorial_videos" USING btree ("doc_locale", "status", "sort_order", "published_at" DESC)';
|
||||
END IF;
|
||||
|
||||
IF to_regclass('public.redirects') IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'redirects' AND column_name = 'enabled')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'redirects' AND column_name = 'locale')
|
||||
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'redirects' AND column_name = 'from')
|
||||
THEN
|
||||
EXECUTE 'CREATE INDEX IF NOT EXISTS "redirects_enabled_locale_from_idx" ON "redirects" USING btree ("enabled", "locale", "from")';
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
}
|
||||
|
||||
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
DROP INDEX IF EXISTS "redirects_enabled_locale_from_idx";
|
||||
DROP INDEX IF EXISTS "support_tutorial_videos_doc_locale_status_sort_idx";
|
||||
DROP INDEX IF EXISTS "product_reviews_doc_locale_product_status_sort_idx";
|
||||
DROP INDEX IF EXISTS "product_videos_doc_locale_product_status_sort_idx";
|
||||
DROP INDEX IF EXISTS "faqs_locales_locale_question_parent_idx";
|
||||
DROP INDEX IF EXISTS "faqs_doc_locale_group_order_idx";
|
||||
DROP INDEX IF EXISTS "dealers_doc_locale_authorized_order_idx";
|
||||
DROP INDEX IF EXISTS "news_rels_products_parent_idx";
|
||||
DROP INDEX IF EXISTS "news_locales_locale_slug_parent_idx";
|
||||
DROP INDEX IF EXISTS "news_doc_locale_published_idx";
|
||||
DROP INDEX IF EXISTS "downloads_rels_products_parent_idx";
|
||||
DROP INDEX IF EXISTS "downloads_doc_locale_status_visibility_release_idx";
|
||||
DROP INDEX IF EXISTS "product_categories_doc_locale_order_idx";
|
||||
DROP INDEX IF EXISTS "products_locales_locale_slug_parent_idx";
|
||||
DROP INDEX IF EXISTS "products_doc_locale_model_idx";
|
||||
DROP INDEX IF EXISTS "products_doc_locale_status_order_idx";
|
||||
`)
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import * as migration_20260428_043000_add_sitemap_settings from './20260428_0430
|
||||
import * as migration_20260428_044000_add_homepage_hero_slide_visibility_overlay from './20260428_044000_add_homepage_hero_slide_visibility_overlay';
|
||||
import * as migration_20260428_045000_add_faq_page_global from './20260428_045000_add_faq_page_global';
|
||||
import * as migration_20260428_045500_add_faq_page_support_path from './20260428_045500_add_faq_page_support_path';
|
||||
import * as migration_20260429_070000_add_public_query_indexes from './20260429_070000_add_public_query_indexes';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
@@ -252,4 +253,9 @@ export const migrations = [
|
||||
down: migration_20260428_045500_add_faq_page_support_path.down,
|
||||
name: '20260428_045500_add_faq_page_support_path'
|
||||
},
|
||||
{
|
||||
up: migration_20260429_070000_add_public_query_indexes.up,
|
||||
down: migration_20260429_070000_add_public_query_indexes.down,
|
||||
name: '20260429_070000_add_public_query_indexes'
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user