import { getPayload } from 'payload'; import configPromise from '@payload-config'; import { mapSlugToFileSlug } from './slugs'; export interface ProductFrontmatter { title: string; sku: string; description: string; categories: string[]; images: string[]; locale: string; isFallback?: boolean; } export interface ProductMdx { slug: string; frontmatter: ProductFrontmatter; content: any; // Lexical AST from Payload } export async function getProductMetadata( slug: string, locale: string, ): Promise | null> { const payload = await getPayload({ config: configPromise }); const fileSlug = await mapSlugToFileSlug(slug, locale); let result = await payload.find({ collection: 'products', where: { and: [{ slug: { equals: fileSlug } }, { locale: { equals: locale } }], }, depth: 1, // To auto-resolve Media relation (images array) limit: 1, }); let isFallback = false; if (result.docs.length === 0 && locale !== 'en') { // Fallback to English result = await payload.find({ collection: 'products', where: { and: [{ slug: { equals: fileSlug } }, { locale: { equals: 'en' } }], }, depth: 1, limit: 1, }); if (result.docs.length > 0) { isFallback = true; } } if (result.docs.length > 0) { const doc = result.docs[0]; // Process Images const resolvedImages = ((doc.images as any[]) || []) .map((img) => (typeof img === 'string' ? img : img.url)) .filter(Boolean); if (resolvedImages.length === 0) return null; // Original logic skipped docs without images return { slug: doc.slug, frontmatter: { title: doc.title, sku: doc.sku, description: doc.description, categories: Array.isArray(doc.categories) ? doc.categories.map((c: any) => c.category) : [], images: resolvedImages, locale: doc.locale, isFallback, }, }; } return null; } export async function getProductBySlug(slug: string, locale: string): Promise { const payload = await getPayload({ config: configPromise }); const fileSlug = await mapSlugToFileSlug(slug, locale); let result = await payload.find({ collection: 'products', where: { and: [{ slug: { equals: fileSlug } }, { locale: { equals: locale } }], }, depth: 1, // Auto-resolve Media logic limit: 1, }); let isFallback = false; if (result.docs.length === 0 && locale !== 'en') { // Fallback to English result = await payload.find({ collection: 'products', where: { and: [{ slug: { equals: fileSlug } }, { locale: { equals: 'en' } }], }, depth: 1, limit: 1, }); if (result.docs.length > 0) { isFallback = true; } } if (result.docs.length > 0) { const doc = result.docs[0]; // Map Images correctly from resolved Media docs const resolvedImages = ((doc.images as any[]) || []) .map((img) => (typeof img === 'string' ? img : img.url)) .filter(Boolean); if (resolvedImages.length === 0) return null; return { slug: doc.slug, frontmatter: { title: doc.title, sku: doc.sku, description: doc.description, categories: Array.isArray(doc.categories) ? doc.categories.map((c: any) => c.category) : [], images: resolvedImages, locale: doc.locale, isFallback, }, content: doc.content, // Lexical payload instead of raw MDX String }; } return null; } export async function getAllProductSlugs(locale: string): Promise { const payload = await getPayload({ config: configPromise }); const result = await payload.find({ collection: 'products', where: { locale: { equals: locale, }, }, pagination: false, // get all docs }); return result.docs.map((doc) => doc.slug); } export async function getAllProducts(locale: string): Promise { // Fetch ALL products in a single query to avoid N+1 getPayload() calls const payload = await getPayload({ config: configPromise }); const selectFields = { title: true, slug: true, sku: true, description: true, categories: true, images: true, locale: true, }; // Get products for this locale const result = await payload.find({ collection: 'products', where: { locale: { equals: locale } }, depth: 1, pagination: false, select: selectFields, }); let products: ProductMdx[] = result.docs .filter((doc) => { const resolvedImages = ((doc.images as any[]) || []) .map((img) => (typeof img === 'string' ? img : img.url)) .filter(Boolean); return resolvedImages.length > 0; }) .map((doc) => ({ slug: doc.slug, frontmatter: { title: doc.title, sku: doc.sku || '', description: doc.description || '', categories: Array.isArray(doc.categories) ? doc.categories.map((c: any) => c.category) : [], images: ((doc.images as any[]) || []) .map((img) => (typeof img === 'string' ? img : img.url)) .filter(Boolean), locale: doc.locale, }, content: null, // Avoided loading into memory! })); // Also include English fallbacks for slugs not in this locale if (locale !== 'en') { const localeSlugs = new Set(products.map((p) => p.slug)); const enResult = await payload.find({ collection: 'products', where: { locale: { equals: 'en' } }, depth: 1, pagination: false, select: selectFields, }); const fallbacks = enResult.docs .filter((doc) => !localeSlugs.has(doc.slug)) .filter((doc) => { const resolvedImages = ((doc.images as any[]) || []) .map((img) => (typeof img === 'string' ? img : img.url)) .filter(Boolean); return resolvedImages.length > 0; }) .map((doc) => ({ slug: doc.slug, frontmatter: { title: doc.title, sku: doc.sku || '', description: doc.description || '', categories: Array.isArray(doc.categories) ? doc.categories.map((c: any) => c.category) : [], images: ((doc.images as any[]) || []) .map((img) => (typeof img === 'string' ? img : img.url)) .filter(Boolean), locale: doc.locale, isFallback: true, }, content: null, // Avoided loading into memory! })); products = [...products, ...fallbacks]; } return products; } export async function getAllProductsMetadata(locale: string): Promise[]> { // Reuse getAllProducts to avoid separate N+1 queries const products = await getAllProducts(locale); return products.map((p) => ({ slug: p.slug, frontmatter: p.frontmatter, })); }