diff --git a/.agents/skills/content-authoring/SKILL.md b/.agents/skills/content-authoring/SKILL.md index 159ce55..eaa3b1a 100644 --- a/.agents/skills/content-authoring/SKILL.md +++ b/.agents/skills/content-authoring/SKILL.md @@ -522,19 +522,14 @@ Test setup: ## API lookup für aufgelöste Referenzen -Beim Laden von Collections können Fremdschlüssel via `lookup`-Parameter automatisch aufgelöst werden. Der `lookup`-Parameter wird als 8. Argument an `getCachedEntries` übergeben: +Beim Laden von Collections können Fremdschlüssel via `lookup`-Parameter automatisch aufgelöst werden. Der `lookup`-Parameter wird einfach im Optionen-Objekt an `getCachedEntries` übergeben: ```ts -const products = await getCachedEntries<"machines">( - "machines", - { active: true, category: catId }, - "sortOrder", - undefined, - undefined, - undefined, - undefined, - "images:medialib" // lookup: "feld:collection" -) +const products = await getCachedEntries<"machines">("machines", { + filter: { active: true, category: catId }, + sort: "sortOrder", + lookup: "images:medialib", // lookup: "feld:collection" +}) ``` Das Format ist `"feldname:zielcollection"` (z.B. `"images:medialib"`). Die aufgelösten Daten landen in `entry._lookup.feldname` als Array der Ziel-Collection-Objekte. Ohne lookup bleiben `string[]`-Felder reine ID-Arrays. diff --git a/.agents/skills/media-seo-publishing/SKILL.md b/.agents/skills/media-seo-publishing/SKILL.md index 35ede1a..2d307ee 100644 --- a/.agents/skills/media-seo-publishing/SKILL.md +++ b/.agents/skills/media-seo-publishing/SKILL.md @@ -126,19 +126,14 @@ Typical usage: For repeated collection data such as galleries, teaser lists, or detail-page image arrays, also request the lookup instead of rendering from raw ID strings: ```ts -const entries = await getCachedEntries( - "your-collection", - { active: true }, - "sortOrder", - undefined, - undefined, - undefined, - undefined, - "imageField:medialib" -) +const entries = await getCachedEntries("your-collection", { + filter: { active: true }, + sort: "sortOrder", + lookup: "imageField:medialib", +}) ``` -`getCachedEntries()` expects `lookup` as the 8th argument and as a string, not as part of the `params` object. +`getCachedEntries()` expects `lookup` as an option in the second arguments object. Then consume the resolved entry from `_lookup`, for example `_lookup.imageField` or `_lookup.imageField?.[0]` depending on whether the schema stores one image or an array. diff --git a/AGENTS.md b/AGENTS.md index c89f204..631f21e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -136,20 +136,15 @@ Beim Laden von Collections können Fremdschlüssel via `lookup`-Parameter automa ```ts // entries mit aufgelösten medialib-Bildern laden -const entries = await getCachedEntries<"content">( - "content", - { active: true }, - "sort", - undefined, - undefined, - undefined, - undefined, - "blocks.heroImage.image:medialib" -) +const entries = await getCachedEntries<"content">("content", { + filter: { active: true }, + sort: "sort", + lookup: "blocks.heroImage.image:medialib" +}) // Ergebnis: entry._lookup enthält die aufgelösten Referenzen ``` -Der `lookup`-Parameter muss als 8. Argument an `getCachedEntries` übergeben werden. Ohne lookup bleiben Referenzfelder reine ID-Werte ohne `_lookup`. +Der `lookup`-Parameter wird im Optionen-Objekt an `getCachedEntries` übergeben. Ohne lookup bleiben Referenzfelder reine ID-Werte ohne `_lookup`. ## Tailwind CSS 4 diff --git a/api/config.yml.env b/api/config.yml.env index 25f3790..348a9db 100644 --- a/api/config.yml.env +++ b/api/config.yml.env @@ -1,2 +1,2 @@ ADMIN_TOKEN=5bdfjc78hdxn338cuhSJ -ADMIN_ASSET_VERSION=db968ab-dirty-1779027503096 +ADMIN_ASSET_VERSION=db968ab-dirty-1779028656102 diff --git a/api/hooks/lib/ssr-server.js b/api/hooks/lib/ssr-server.js index 0ea7cee..4ac4732 100644 --- a/api/hooks/lib/ssr-server.js +++ b/api/hooks/lib/ssr-server.js @@ -104,9 +104,10 @@ function ssrRequest(cacheKey, endpoint, query, options) { } } } - if (options && options.params && options.params.aggregate) { - const aggregates = typeof options.params.aggregate === "string" - ? options.params.aggregate.split(",") + const rawAggregate = (options && options.aggregate) || (options && options.params && options.params.aggregate); + if (rawAggregate) { + const aggregates = typeof rawAggregate === "string" + ? rawAggregate.split(",") : []; for (const a of aggregates) { // simple format: "collectionName:foreignField:..." diff --git a/api/hooks/lib/ssr.js b/api/hooks/lib/ssr.js index 650ffd2..247889b 100644 --- a/api/hooks/lib/ssr.js +++ b/api/hooks/lib/ssr.js @@ -72,6 +72,7 @@ function apiRequest(endpoint, options, body, sentry) { if (options?.offset) query += "&offset=" + options.offset if (options?.projection) query += "&projection=" + options.projection if (options?.lookup) query += "&lookup=" + options.lookup + if (options?.aggregate) query += "&aggregate=" + (typeof options.aggregate === 'string' ? options.aggregate : encodeURIComponent(JSON.stringify(options.aggregate))) if (options?.params) { Object.keys(options.params).forEach((p) => { diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index ba5614f..71dfd65 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -159,45 +159,30 @@ try { // Load navigation const [headerEntries, footerEntries] = await Promise.all([ - getCachedEntries<"navigation">( - "navigation", - { type: "header", language: lang }, - "sort", - 1, - undefined, - undefined, - undefined, - NAVIGATION_CONTENT_LOOKUP - ), - getCachedEntries<"navigation">( - "navigation", - { type: "footer", language: lang }, - "sort", - 1, - undefined, - undefined, - undefined, - NAVIGATION_CONTENT_LOOKUP - ), + getCachedEntries<"navigation">("navigation", { + filter: { type: "header", language: lang }, + sort: "sort", + limit: 1, + lookup: NAVIGATION_CONTENT_LOOKUP + }), + getCachedEntries<"navigation">("navigation", { + filter: { type: "footer", language: lang }, + sort: "sort", + limit: 1, + lookup: NAVIGATION_CONTENT_LOOKUP + }), ]) headerNav = headerEntries[0] || null footerNav = footerEntries[0] || null // Load content for current path. Limit 1 so SSR tracks content: instead of content:*. - const contentEntries = await getCachedEntries<"content">( - "content", - { - lang, - path: routePath, - active: true, - }, - "sort", - 1, - undefined, - undefined, - { aggregate: "comments:contentId:count" }, - CONTENT_MEDIA_LOOKUP - ) + const contentEntries = await getCachedEntries<"content">("content", { + filter: { lang, path: routePath, active: true }, + sort: "sort", + limit: 1, + aggregate: "comments:contentId:count", + lookup: CONTENT_MEDIA_LOOKUP + }) if (contentEntries.length > 0) { contentEntry = contentEntries[0] @@ -208,11 +193,10 @@ } try { - comments = await getCachedEntries( - "comments", - { active: true, contentId: contentEntry.id as string }, - "sort" - ) + comments = await getCachedEntries("comments", { + filter: { active: true, contentId: contentEntry.id as string }, + sort: "sort" + }) } catch (e) { console.error("Failed to load comments", e) comments = [] diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index c4bfdb6..0818f34 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -149,63 +149,37 @@ type EntryTypeSwitch = T extends "medialib" export async function getDBEntries( collectionName: T, - filter?: MongoFilter, - sort: string = "sort", - limit?: number, - offset?: number, - projection?: string, - params?: Record, - lookup?: string + options?: ApiOptions ): Promise[]> { - const c = await api[]>(collectionName, { - filter, - sort, - limit, - offset, - projection, - params, - lookup, - }) + const c = await api[]>(collectionName, options) return c.data } export async function getCachedEntries( collectionName: T, - filter?: MongoFilter, - sort: string = "sort", - limit?: number, - offset?: number, - projection?: string, - params?: Record, - lookup?: string + options?: ApiOptions ): Promise[]> { - const filterStr = obj2str({ collectionName, filter, sort, limit, offset, projection, params, lookup }) + const filterStr = obj2str({ collectionName, options }) if (cache[filterStr] && cache[filterStr].expire >= Date.now()) { return cache[filterStr].data as EntryTypeSwitch[] } - const entries = await getDBEntries(collectionName, filter, sort, limit, offset, projection, params, lookup) + const entries = await getDBEntries(collectionName, options) cache[filterStr] = { expire: Date.now() + CACHE_TTL, data: entries } return entries } export async function getDBEntry( collectionName: T, - filter: MongoFilter, - projection?: string, - params?: Record, - lookup?: string + options?: ApiOptions ): Promise | undefined> { - return (await getDBEntries(collectionName, filter, "_id", 1, undefined, projection, params, lookup))?.[0] + return (await getDBEntries(collectionName, { ...options, limit: 1 }))?.[0] } export async function getCachedEntry( collectionName: T, - filter: MongoFilter, - projection?: string, - params?: Record, - lookup?: string + options?: ApiOptions ): Promise | undefined> { - return (await getCachedEntries(collectionName, filter, "_id", 1, undefined, projection, params, lookup))?.[0] + return (await getCachedEntries(collectionName, { ...options, limit: 1 }))?.[0] } export async function postDBEntry( diff --git a/frontend/src/lib/mock.ts b/frontend/src/lib/mock.ts index 7d21669..51917b3 100644 --- a/frontend/src/lib/mock.ts +++ b/frontend/src/lib/mock.ts @@ -242,7 +242,7 @@ function cloneEntry(entry: T): T { } function applyAggregate(entry: Record, options?: ApiOptions): Record { - const rawAggregate = options?.params?.aggregate + const rawAggregate = options?.aggregate || options?.params?.aggregate if (!rawAggregate) return entry const aggregates = Array.isArray(rawAggregate) diff --git a/types/global.d.ts b/types/global.d.ts index 1de751e..3aa5e88 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -15,6 +15,7 @@ interface ApiOptions { filter?: MongoFilter sort?: string lookup?: string + aggregate?: string | Record limit?: number offset?: number projection?: string