Some checks failed
Build and publish / build (push) Failing after 19s
Source moved verbatim from mp/shop/ on 2026-04-29; mp was the first concrete adapter consuming the libreshop toolkit. Builds and publishes git.librete.ch/libreshop/shop on every main / v* push via the standard .gitea/workflows/build.yml shared across libreshop components.
173 lines
4.5 KiB
TypeScript
173 lines
4.5 KiB
TypeScript
import { fetchCms } from "~/server/utils/cmsApi";
|
|
|
|
function slugify(text: string): string {
|
|
return text
|
|
.toLowerCase()
|
|
.replace(/\s+/g, "-")
|
|
.replace(/ü/g, "ue")
|
|
.replace(/ä/g, "ae")
|
|
.replace(/ö/g, "oe")
|
|
.replace(/ß/g, "ss");
|
|
}
|
|
|
|
interface ProductData {
|
|
id: number;
|
|
slug?: string;
|
|
updatedAt?: string;
|
|
cover?: { id: number } | { data: { id: number } };
|
|
attributes?: {
|
|
slug: string;
|
|
updatedAt: string;
|
|
cover?: { data: { id: number } };
|
|
};
|
|
}
|
|
|
|
interface CoverData {
|
|
id: number;
|
|
name?: string;
|
|
sort?: number;
|
|
updatedAt?: string;
|
|
attributes?: {
|
|
name: string;
|
|
sort?: number;
|
|
updatedAt: string;
|
|
};
|
|
}
|
|
|
|
interface ApiResponse<T> {
|
|
data: T[];
|
|
meta?: {
|
|
pagination?: {
|
|
total: number;
|
|
pageSize: number;
|
|
pageCount: number;
|
|
};
|
|
};
|
|
}
|
|
|
|
export default defineSitemapEventHandler(async (event) => {
|
|
const config = useRuntimeConfig(event);
|
|
const siteUrl = config.siteUrl || "https://muellerprints-paperwork.com";
|
|
const urls: { loc: string; lastmod?: string; priority?: number }[] = [];
|
|
const allPageSize = 20; // must match notebooks/index.vue pageSize
|
|
const coverPageSize = 24; // must match notebooks/[cover].vue pageSize
|
|
|
|
try {
|
|
// Fetch all products for detail page URLs
|
|
const products = await fetchCms<ApiResponse<ProductData>>("/products", {
|
|
query: {
|
|
"pagination[pageSize]": "1000",
|
|
"fields[0]": "slug",
|
|
"fields[1]": "updatedAt",
|
|
"populate[cover][fields][0]": "id"
|
|
}
|
|
});
|
|
|
|
// Product detail pages
|
|
for (const product of products.data || []) {
|
|
const slug = product.slug || product.attributes?.slug;
|
|
const updatedAt = product.updatedAt || product.attributes?.updatedAt;
|
|
|
|
if (slug) {
|
|
urls.push({
|
|
loc: `${siteUrl}/details/${slug}`,
|
|
lastmod: updatedAt,
|
|
priority: 0.8
|
|
});
|
|
}
|
|
}
|
|
|
|
// Fetch promo products count for pagination (matches what the page actually shows)
|
|
const promoProducts = await fetchCms<ApiResponse<ProductData>>("/promo-products", {
|
|
query: {
|
|
"pagination[pageSize]": "1",
|
|
"pagination[page]": "1"
|
|
}
|
|
});
|
|
const totalPromoProducts = promoProducts.meta?.pagination?.total || 0;
|
|
const totalNotebooksPages = Math.ceil(totalPromoProducts / allPageSize);
|
|
|
|
// Add /notebooks pagination pages
|
|
for (let page = 2; page <= totalNotebooksPages; page++) {
|
|
urls.push({
|
|
loc: `${siteUrl}/notebooks?page=${page}`,
|
|
priority: 0.7
|
|
});
|
|
}
|
|
|
|
// Fetch all covers
|
|
const covers = await fetchCms<ApiResponse<CoverData>>("/product-covers", {
|
|
query: {
|
|
"fields[0]": "name",
|
|
"fields[1]": "sort",
|
|
"fields[2]": "updatedAt"
|
|
}
|
|
});
|
|
|
|
// Sort covers by sort field
|
|
const sortedCovers = (covers.data || []).sort((a, b) => {
|
|
const sortA = a.sort ?? a.attributes?.sort ?? 0;
|
|
const sortB = b.sort ?? b.attributes?.sort ?? 0;
|
|
return sortA - sortB;
|
|
});
|
|
|
|
// Cover category pages with slugs and pagination
|
|
for (const cover of sortedCovers) {
|
|
const name = cover.name || cover.attributes?.name;
|
|
const updatedAt = cover.updatedAt || cover.attributes?.updatedAt;
|
|
|
|
if (name) {
|
|
const slug = slugify(name);
|
|
|
|
// Main category page
|
|
urls.push({
|
|
loc: `${siteUrl}/notebooks/${slug}`,
|
|
lastmod: updatedAt,
|
|
priority: 0.7
|
|
});
|
|
|
|
// Fetch promo product count for this cover (matches page display)
|
|
const coverId = cover.id;
|
|
const coverPromo = await fetchCms<ApiResponse<ProductData>>("/promo-products", {
|
|
query: {
|
|
"filters[cover]": String(coverId),
|
|
"pagination[pageSize]": "1",
|
|
"pagination[page]": "1"
|
|
}
|
|
});
|
|
const coverTotal = coverPromo.meta?.pagination?.total || 0;
|
|
const coverPageCount = Math.ceil(coverTotal / coverPageSize);
|
|
|
|
// Add pagination pages for this category
|
|
for (let page = 2; page <= coverPageCount; page++) {
|
|
urls.push({
|
|
loc: `${siteUrl}/notebooks/${slug}?page=${page}`,
|
|
priority: 0.6
|
|
});
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching sitemap data:", error);
|
|
}
|
|
|
|
// Static pages
|
|
urls.push(
|
|
{ loc: `${siteUrl}/`, priority: 1.0 },
|
|
{ loc: `${siteUrl}/notebooks`, priority: 0.9 },
|
|
// Info pages
|
|
{ loc: `${siteUrl}/about`, priority: 0.6 },
|
|
{ loc: `${siteUrl}/kontakt`, priority: 0.5 },
|
|
{ loc: `${siteUrl}/anfahrt`, priority: 0.4 },
|
|
{ loc: `${siteUrl}/oeffnungszeiten`, priority: 0.4 },
|
|
// Legal pages
|
|
{ loc: `${siteUrl}/impressum`, priority: 0.2 },
|
|
{ loc: `${siteUrl}/datenschutz`, priority: 0.2 },
|
|
{ loc: `${siteUrl}/agb`, priority: 0.2 },
|
|
{ loc: `${siteUrl}/versand`, priority: 0.3 },
|
|
{ loc: `${siteUrl}/zahlung`, priority: 0.3 }
|
|
);
|
|
|
|
return urls;
|
|
});
|