Include product category URLs in sitemaps and document by-slug lookup.
Adds /products/category/{slug} to default sitemap generation and a public categories/by-slug endpoint for storefront category pages.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
df17925d3a
commit
c4367d8eb0
@@ -23,6 +23,7 @@ const BASE_URL_PATTERN = /^https:\/\/[a-z0-9.-]+(?::\d+)?\/?$/i;
|
||||
|
||||
const TEMPLATE_KEYS = [
|
||||
'product',
|
||||
'productCategory',
|
||||
'blog',
|
||||
'portfolio',
|
||||
'storeItemByProduct',
|
||||
|
||||
@@ -11,7 +11,7 @@ export type ResolvedSitemapConfig = {
|
||||
templates: Required<
|
||||
Pick<
|
||||
import('../business-settings/business-settings.types').SitemapPathTemplatesConfig,
|
||||
'product' | 'blog' | 'portfolio'
|
||||
'product' | 'productCategory' | 'blog' | 'portfolio'
|
||||
>
|
||||
>;
|
||||
};
|
||||
|
||||
@@ -98,6 +98,9 @@ export function resolveSitemapConfig(
|
||||
templates: {
|
||||
product:
|
||||
stored?.templates?.product ?? DEFAULT_SITEMAP_PATH_TEMPLATES.product,
|
||||
productCategory:
|
||||
stored?.templates?.productCategory ??
|
||||
DEFAULT_SITEMAP_PATH_TEMPLATES.productCategory,
|
||||
blog: stored?.templates?.blog ?? DEFAULT_SITEMAP_PATH_TEMPLATES.blog,
|
||||
portfolio:
|
||||
stored?.templates?.portfolio ??
|
||||
|
||||
@@ -6,6 +6,7 @@ export const SITEMAP_URLSET_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9';
|
||||
/** Default storefront path templates (overridable via /meshkee/sitemap-config.json). */
|
||||
export const DEFAULT_SITEMAP_PATH_TEMPLATES = {
|
||||
product: '/products/{id}/{slug}',
|
||||
productCategory: '/products/category/{slug}',
|
||||
blog: '/blog/{id}/{slug}',
|
||||
portfolio: '/portfolios/{id}/{slug}',
|
||||
} as const;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ContentStatus, Prisma } from '@prisma/client';
|
||||
import { ContentStatus, MediaEntityType, Prisma } from '@prisma/client';
|
||||
import { normalizeBusinessSettings } from '../business-settings/business-settings.util';
|
||||
import type { BusinessModuleId } from '../business-settings/business-settings.types';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
@@ -119,6 +119,13 @@ export class SitemapService {
|
||||
}));
|
||||
|
||||
if (this.isModuleEnabled(enabledModules, 'products')) {
|
||||
entries.push(
|
||||
...(await this.loadProductCategories(
|
||||
businessId,
|
||||
config.baseUrl,
|
||||
config.templates.productCategory,
|
||||
)),
|
||||
);
|
||||
entries.push(
|
||||
...(await this.loadPublishedProducts(businessId, config.baseUrl, config.templates.product)),
|
||||
);
|
||||
@@ -143,6 +150,34 @@ export class SitemapService {
|
||||
return buildSitemapXml(entries);
|
||||
}
|
||||
|
||||
private async loadProductCategories(
|
||||
businessId: bigint,
|
||||
baseUrl: string,
|
||||
template: string,
|
||||
): Promise<SitemapUrlEntry[]> {
|
||||
const rows = await this.prisma.category.findMany({
|
||||
where: {
|
||||
businessId,
|
||||
entityType: MediaEntityType.product,
|
||||
isActive: true,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }],
|
||||
});
|
||||
|
||||
return rows.map((row) =>
|
||||
this.toEntry(baseUrl, template, {
|
||||
id: row.id,
|
||||
slug: row.slug,
|
||||
updatedAt: row.updatedAt,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async loadPublishedProducts(
|
||||
businessId: bigint,
|
||||
baseUrl: string,
|
||||
|
||||
Reference in New Issue
Block a user