feat: extract shop from mp/shop — initial libreshop/shop
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.
This commit is contained in:
Michael Czechowski
2026-04-29 17:48:56 +02:00
commit 44107c0734
134 changed files with 19521 additions and 0 deletions

View File

@@ -0,0 +1,172 @@
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;
});