@btst/stack 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.cjs +9 -1
- package/dist/api/index.d.cts +4 -4
- package/dist/api/index.d.mts +4 -4
- package/dist/api/index.d.ts +4 -4
- package/dist/api/index.mjs +9 -1
- package/dist/client/index.d.cts +2 -2
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/api/getters.cjs +42 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/getters.mjs +39 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +5 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +5 -0
- package/dist/packages/stack/src/plugins/blog/api/getters.cjs +131 -0
- package/dist/packages/stack/src/plugins/blog/api/getters.mjs +127 -0
- package/dist/packages/stack/src/plugins/blog/api/plugin.cjs +60 -107
- package/dist/packages/stack/src/plugins/blog/api/plugin.mjs +60 -107
- package/dist/packages/stack/src/plugins/blog/api/query-key-defs.cjs +18 -0
- package/dist/packages/stack/src/plugins/blog/api/query-key-defs.mjs +15 -0
- package/dist/packages/stack/src/plugins/blog/api/serializers.cjs +21 -0
- package/dist/packages/stack/src/plugins/blog/api/serializers.mjs +18 -0
- package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +16 -1
- package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +17 -2
- package/dist/packages/stack/src/plugins/cms/api/getters.cjs +156 -0
- package/dist/packages/stack/src/plugins/cms/api/getters.mjs +147 -0
- package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +624 -617
- package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +623 -616
- package/dist/packages/stack/src/plugins/cms/api/query-key-defs.cjs +29 -0
- package/dist/packages/stack/src/plugins/cms/api/query-key-defs.mjs +26 -0
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.internal.cjs +1 -1
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.internal.mjs +1 -1
- package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.cjs +6 -3
- package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.mjs +6 -3
- package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +15 -0
- package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +16 -1
- package/dist/packages/stack/src/plugins/form-builder/api/getters.cjs +120 -0
- package/dist/packages/stack/src/plugins/form-builder/api/getters.mjs +112 -0
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.cjs +75 -86
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.mjs +71 -82
- package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.cjs +37 -0
- package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.mjs +33 -0
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.cjs +1 -1
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.mjs +1 -1
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +15 -0
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +16 -1
- package/dist/packages/stack/src/plugins/kanban/api/getters.cjs +84 -0
- package/dist/packages/stack/src/plugins/kanban/api/getters.mjs +81 -0
- package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +37 -123
- package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +37 -123
- package/dist/packages/stack/src/plugins/kanban/api/query-key-defs.cjs +26 -0
- package/dist/packages/stack/src/plugins/kanban/api/query-key-defs.mjs +23 -0
- package/dist/packages/stack/src/plugins/kanban/api/serializers.cjs +30 -0
- package/dist/packages/stack/src/plugins/kanban/api/serializers.mjs +26 -0
- package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +11 -1
- package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +12 -2
- package/dist/packages/stack/src/plugins/utils.cjs +6 -0
- package/dist/packages/stack/src/plugins/utils.mjs +6 -1
- package/dist/plugins/ai-chat/api/index.cjs +3 -0
- package/dist/plugins/ai-chat/api/index.d.cts +27 -4
- package/dist/plugins/ai-chat/api/index.d.mts +27 -4
- package/dist/plugins/ai-chat/api/index.d.ts +27 -4
- package/dist/plugins/ai-chat/api/index.mjs +1 -0
- package/dist/plugins/ai-chat/client/hooks/index.d.cts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.mts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.ts +2 -2
- package/dist/plugins/ai-chat/query-keys.d.cts +9 -284
- package/dist/plugins/ai-chat/query-keys.d.mts +9 -284
- package/dist/plugins/ai-chat/query-keys.d.ts +9 -284
- package/dist/plugins/api/index.d.cts +4 -3
- package/dist/plugins/api/index.d.mts +4 -3
- package/dist/plugins/api/index.d.ts +4 -3
- package/dist/plugins/blog/api/index.cjs +9 -0
- package/dist/plugins/blog/api/index.d.cts +20 -4
- package/dist/plugins/blog/api/index.d.mts +20 -4
- package/dist/plugins/blog/api/index.d.ts +20 -4
- package/dist/plugins/blog/api/index.mjs +3 -0
- package/dist/plugins/blog/client/hooks/index.d.cts +5 -5
- package/dist/plugins/blog/client/hooks/index.d.mts +5 -5
- package/dist/plugins/blog/client/hooks/index.d.ts +5 -5
- package/dist/plugins/blog/client/index.d.cts +1 -1
- package/dist/plugins/blog/client/index.d.mts +1 -1
- package/dist/plugins/blog/client/index.d.ts +1 -1
- package/dist/plugins/blog/query-keys.cjs +13 -9
- package/dist/plugins/blog/query-keys.d.cts +8 -333
- package/dist/plugins/blog/query-keys.d.mts +8 -333
- package/dist/plugins/blog/query-keys.d.ts +8 -333
- package/dist/plugins/blog/query-keys.mjs +13 -9
- package/dist/plugins/client/index.cjs +1 -0
- package/dist/plugins/client/index.d.cts +10 -3
- package/dist/plugins/client/index.d.mts +10 -3
- package/dist/plugins/client/index.d.ts +10 -3
- package/dist/plugins/client/index.mjs +1 -1
- package/dist/plugins/cms/api/index.cjs +10 -0
- package/dist/plugins/cms/api/index.d.cts +7 -163
- package/dist/plugins/cms/api/index.d.mts +7 -163
- package/dist/plugins/cms/api/index.d.ts +7 -163
- package/dist/plugins/cms/api/index.mjs +2 -0
- package/dist/plugins/cms/client/hooks/index.d.cts +1 -1
- package/dist/plugins/cms/client/hooks/index.d.mts +1 -1
- package/dist/plugins/cms/client/hooks/index.d.ts +1 -1
- package/dist/plugins/cms/query-keys.cjs +2 -1
- package/dist/plugins/cms/query-keys.d.cts +6 -9
- package/dist/plugins/cms/query-keys.d.mts +6 -9
- package/dist/plugins/cms/query-keys.d.ts +6 -9
- package/dist/plugins/cms/query-keys.mjs +2 -1
- package/dist/plugins/form-builder/api/index.cjs +10 -0
- package/dist/plugins/form-builder/api/index.d.cts +7 -141
- package/dist/plugins/form-builder/api/index.d.mts +7 -141
- package/dist/plugins/form-builder/api/index.d.ts +7 -141
- package/dist/plugins/form-builder/api/index.mjs +2 -0
- package/dist/plugins/form-builder/client/components/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/components/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/components/index.d.ts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/form-builder/query-keys.cjs +3 -2
- package/dist/plugins/form-builder/query-keys.d.cts +7 -6
- package/dist/plugins/form-builder/query-keys.d.mts +7 -6
- package/dist/plugins/form-builder/query-keys.d.ts +7 -6
- package/dist/plugins/form-builder/query-keys.mjs +3 -2
- package/dist/plugins/kanban/api/index.cjs +9 -0
- package/dist/plugins/kanban/api/index.d.cts +17 -395
- package/dist/plugins/kanban/api/index.d.mts +17 -395
- package/dist/plugins/kanban/api/index.d.ts +17 -395
- package/dist/plugins/kanban/api/index.mjs +3 -0
- package/dist/plugins/kanban/client/components/index.d.cts +1 -1
- package/dist/plugins/kanban/client/components/index.d.mts +1 -1
- package/dist/plugins/kanban/client/components/index.d.ts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
- package/dist/plugins/kanban/client/index.d.cts +1 -1
- package/dist/plugins/kanban/client/index.d.mts +1 -1
- package/dist/plugins/kanban/client/index.d.ts +1 -1
- package/dist/plugins/kanban/query-keys.cjs +6 -12
- package/dist/plugins/kanban/query-keys.d.cts +5 -16
- package/dist/plugins/kanban/query-keys.d.mts +5 -16
- package/dist/plugins/kanban/query-keys.d.ts +5 -16
- package/dist/plugins/kanban/query-keys.mjs +6 -12
- package/dist/plugins/open-api/api/index.d.cts +2 -2
- package/dist/plugins/open-api/api/index.d.mts +2 -2
- package/dist/plugins/open-api/api/index.d.ts +2 -2
- package/dist/plugins/route-docs/client/index.d.cts +1 -1
- package/dist/plugins/route-docs/client/index.d.mts +1 -1
- package/dist/plugins/route-docs/client/index.d.ts +1 -1
- package/dist/plugins/ui-builder/index.d.cts +1 -1
- package/dist/plugins/ui-builder/index.d.mts +1 -1
- package/dist/plugins/ui-builder/index.d.ts +1 -1
- package/dist/shared/{stack.BoA0xkJv.d.cts → stack.7n9Y_u7N.d.cts} +33 -7
- package/dist/shared/{stack.BoA0xkJv.d.mts → stack.7n9Y_u7N.d.mts} +33 -7
- package/dist/shared/{stack.BoA0xkJv.d.ts → stack.7n9Y_u7N.d.ts} +33 -7
- package/dist/shared/stack.B1EeBt1b.d.ts +297 -0
- package/dist/shared/stack.BIXEI6v_.d.mts +419 -0
- package/dist/shared/stack.BKfolAyK.d.ts +419 -0
- package/dist/shared/stack.BeSm90va.d.ts +289 -0
- package/dist/shared/stack.BpolpQpf.d.cts +445 -0
- package/dist/shared/stack.C5dtIncc.d.mts +293 -0
- package/dist/shared/stack.CIP6QS9l.d.ts +293 -0
- package/dist/shared/stack.CMh_EdxW.d.cts +289 -0
- package/dist/shared/stack.CP68pFEH.d.mts +297 -0
- package/dist/shared/{stack.BsXokfNh.d.mts → stack.CVDTkMoO.d.cts} +8 -2
- package/dist/shared/{stack.BsXokfNh.d.ts → stack.CVDTkMoO.d.mts} +8 -2
- package/dist/shared/{stack.BsXokfNh.d.cts → stack.CVDTkMoO.d.ts} +8 -2
- package/dist/shared/{stack.DKDMI-QO.d.mts → stack.DJaKVY7v.d.cts} +7 -1
- package/dist/shared/{stack.DKDMI-QO.d.ts → stack.DJaKVY7v.d.mts} +7 -1
- package/dist/shared/{stack.DKDMI-QO.d.cts → stack.DJaKVY7v.d.ts} +7 -1
- package/dist/shared/{stack.DzH_wcvr.d.mts → stack.DdI5W6MB.d.cts} +9 -3
- package/dist/shared/{stack.DzH_wcvr.d.ts → stack.DdI5W6MB.d.mts} +9 -3
- package/dist/shared/{stack.DzH_wcvr.d.cts → stack.DdI5W6MB.d.ts} +9 -3
- package/dist/shared/stack.Dg09R0oB.d.mts +289 -0
- package/dist/shared/stack.Dw0Ly2TM.d.cts +293 -0
- package/dist/shared/stack.IdtKDRka.d.cts +297 -0
- package/dist/shared/stack.TIBF2AOx.d.ts +445 -0
- package/dist/shared/stack.rTy7-wQU.d.mts +445 -0
- package/dist/shared/stack.snB1EDP7.d.cts +419 -0
- package/package.json +3 -3
- package/src/__tests__/stack-api.test.ts +118 -0
- package/src/api/index.ts +15 -1
- package/src/plugins/ai-chat/__tests__/getters.test.ts +109 -0
- package/src/plugins/ai-chat/api/getters.ts +71 -0
- package/src/plugins/ai-chat/api/index.ts +1 -0
- package/src/plugins/ai-chat/api/plugin.ts +8 -0
- package/src/plugins/api/index.ts +3 -1
- package/src/plugins/blog/__tests__/getters.test.ts +540 -0
- package/src/plugins/blog/api/getters.ts +243 -0
- package/src/plugins/blog/api/index.ts +9 -0
- package/src/plugins/blog/api/plugin.ts +98 -141
- package/src/plugins/blog/api/query-key-defs.ts +46 -0
- package/src/plugins/blog/api/serializers.ts +27 -0
- package/src/plugins/blog/client/plugin.tsx +21 -1
- package/src/plugins/blog/query-keys.ts +21 -20
- package/src/plugins/client/index.ts +1 -1
- package/src/plugins/cms/__tests__/getters.test.ts +206 -0
- package/src/plugins/cms/api/getters.ts +268 -0
- package/src/plugins/cms/api/index.ts +15 -1
- package/src/plugins/cms/api/plugin.ts +151 -150
- package/src/plugins/cms/api/query-key-defs.ts +53 -0
- package/src/plugins/cms/api/serializers.ts +12 -0
- package/src/plugins/cms/client/components/pages/content-editor-page.internal.tsx +1 -1
- package/src/plugins/cms/client/hooks/cms-hooks.tsx +3 -0
- package/src/plugins/cms/client/plugin.tsx +19 -0
- package/src/plugins/cms/query-keys.ts +2 -1
- package/src/plugins/cms/types.ts +1 -1
- package/src/plugins/form-builder/__tests__/getters.test.ts +159 -0
- package/src/plugins/form-builder/api/getters.ts +226 -0
- package/src/plugins/form-builder/api/index.ts +15 -1
- package/src/plugins/form-builder/api/plugin.ts +107 -109
- package/src/plugins/form-builder/api/query-key-defs.ts +79 -0
- package/src/plugins/form-builder/api/serializers.ts +12 -0
- package/src/plugins/form-builder/client/components/pages/submissions-page.internal.tsx +1 -1
- package/src/plugins/form-builder/client/plugin.tsx +19 -0
- package/src/plugins/form-builder/query-keys.ts +6 -2
- package/src/plugins/form-builder/types.ts +2 -2
- package/src/plugins/kanban/__tests__/getters.test.ts +172 -0
- package/src/plugins/kanban/api/getters.ts +149 -0
- package/src/plugins/kanban/api/index.ts +4 -0
- package/src/plugins/kanban/api/plugin.ts +65 -146
- package/src/plugins/kanban/api/query-key-defs.ts +54 -0
- package/src/plugins/kanban/api/serializers.ts +49 -0
- package/src/plugins/kanban/client/plugin.tsx +15 -1
- package/src/plugins/kanban/query-keys.ts +10 -14
- package/src/plugins/utils.ts +19 -0
- package/src/types.ts +44 -5
- package/dist/shared/{stack.CbuN2zVV.d.cts → stack.CBON0dWL.d.cts} +7 -7
- package/dist/shared/{stack.CbuN2zVV.d.mts → stack.CBON0dWL.d.mts} +7 -7
- package/dist/shared/{stack.CbuN2zVV.d.ts → stack.CBON0dWL.d.ts} +7 -7
|
@@ -6,54 +6,9 @@ const schemaConverter = require('../../../../../ui/src/lib/schema-converter.cjs'
|
|
|
6
6
|
const db = require('../db.cjs');
|
|
7
7
|
const schemas = require('../schemas.cjs');
|
|
8
8
|
const utils = require('../utils.cjs');
|
|
9
|
+
const getters = require('./getters.cjs');
|
|
10
|
+
const queryKeyDefs = require('./query-key-defs.cjs');
|
|
9
11
|
|
|
10
|
-
function migrateToUnifiedSchema(jsonSchemaStr, fieldConfigStr) {
|
|
11
|
-
if (!fieldConfigStr) {
|
|
12
|
-
return jsonSchemaStr;
|
|
13
|
-
}
|
|
14
|
-
try {
|
|
15
|
-
const jsonSchema = JSON.parse(jsonSchemaStr);
|
|
16
|
-
const fieldConfig = JSON.parse(fieldConfigStr);
|
|
17
|
-
if (!jsonSchema.properties || typeof fieldConfig !== "object") {
|
|
18
|
-
return jsonSchemaStr;
|
|
19
|
-
}
|
|
20
|
-
for (const [key, config] of Object.entries(fieldConfig)) {
|
|
21
|
-
if (jsonSchema.properties[key] && typeof config === "object" && config !== null && "fieldType" in config) {
|
|
22
|
-
jsonSchema.properties[key].fieldType = config.fieldType;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return JSON.stringify(jsonSchema);
|
|
26
|
-
} catch {
|
|
27
|
-
return jsonSchemaStr;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function serializeContentType(ct) {
|
|
31
|
-
const needsMigration = !ct.autoFormVersion || ct.autoFormVersion < 2;
|
|
32
|
-
const migratedJsonSchema = needsMigration ? migrateToUnifiedSchema(ct.jsonSchema, ct.fieldConfig) : ct.jsonSchema;
|
|
33
|
-
return {
|
|
34
|
-
id: ct.id,
|
|
35
|
-
name: ct.name,
|
|
36
|
-
slug: ct.slug,
|
|
37
|
-
description: ct.description,
|
|
38
|
-
jsonSchema: migratedJsonSchema,
|
|
39
|
-
createdAt: ct.createdAt.toISOString(),
|
|
40
|
-
updatedAt: ct.updatedAt.toISOString()
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
function serializeContentItem(item) {
|
|
44
|
-
return {
|
|
45
|
-
...item,
|
|
46
|
-
createdAt: item.createdAt.toISOString(),
|
|
47
|
-
updatedAt: item.updatedAt.toISOString()
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function serializeContentItemWithType(item) {
|
|
51
|
-
return {
|
|
52
|
-
...serializeContentItem(item),
|
|
53
|
-
parsedData: JSON.parse(item.data),
|
|
54
|
-
contentType: item.contentType ? serializeContentType(item.contentType) : void 0
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
12
|
async function syncContentTypes(adapter, config) {
|
|
58
13
|
for (const ct of config.contentTypes) {
|
|
59
14
|
const jsonSchema = JSON.stringify(schemaConverter.zodToFormSchema(ct.schema));
|
|
@@ -271,275 +226,246 @@ async function populateRelations(adapter, item) {
|
|
|
271
226
|
join: { contentType: true }
|
|
272
227
|
});
|
|
273
228
|
if (relatedItem) {
|
|
274
|
-
relatedItems.push(serializeContentItemWithType(relatedItem));
|
|
229
|
+
relatedItems.push(getters.serializeContentItemWithType(relatedItem));
|
|
275
230
|
}
|
|
276
231
|
}
|
|
277
232
|
relations[fieldName] = relatedItems;
|
|
278
233
|
}
|
|
279
234
|
return relations;
|
|
280
235
|
}
|
|
281
|
-
const cmsBackendPlugin = (config) =>
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
syncPromise = syncContentTypes(adapter, config).catch((err) => {
|
|
289
|
-
syncPromise = null;
|
|
290
|
-
throw err;
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
await syncPromise;
|
|
294
|
-
};
|
|
295
|
-
const getContentType = async (slug) => {
|
|
296
|
-
await ensureSynced();
|
|
297
|
-
return adapter.findOne({
|
|
298
|
-
model: "contentType",
|
|
299
|
-
where: [{ field: "slug", value: slug, operator: "eq" }]
|
|
236
|
+
const cmsBackendPlugin = (config) => {
|
|
237
|
+
let syncPromise = null;
|
|
238
|
+
const ensureSynced = (adapter) => {
|
|
239
|
+
if (!syncPromise) {
|
|
240
|
+
syncPromise = syncContentTypes(adapter, config).catch((err) => {
|
|
241
|
+
syncPromise = null;
|
|
242
|
+
throw err;
|
|
300
243
|
});
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
sortBy: { field: "name", direction: "asc" }
|
|
244
|
+
}
|
|
245
|
+
return syncPromise;
|
|
246
|
+
};
|
|
247
|
+
const getContentTypesWithCounts = async (adapter) => {
|
|
248
|
+
const contentTypes = await getters.getAllContentTypes(adapter);
|
|
249
|
+
return Promise.all(
|
|
250
|
+
contentTypes.map(async (ct) => {
|
|
251
|
+
const count = await adapter.count({
|
|
252
|
+
model: "contentItem",
|
|
253
|
+
where: [
|
|
254
|
+
{ field: "contentTypeId", value: ct.id, operator: "eq" }
|
|
255
|
+
]
|
|
314
256
|
});
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
257
|
+
return { ...ct, itemCount: count };
|
|
258
|
+
})
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
const createCMSPrefetchForRoute = (adapter) => {
|
|
262
|
+
return async function prefetchForRoute(key, qc, params) {
|
|
263
|
+
await ensureSynced(adapter);
|
|
264
|
+
switch (key) {
|
|
265
|
+
case "dashboard":
|
|
266
|
+
case "newContent": {
|
|
267
|
+
const typesWithCounts = await getContentTypesWithCounts(adapter);
|
|
268
|
+
qc.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.typesList(), typesWithCounts);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case "contentList": {
|
|
272
|
+
const typeSlug = params?.typeSlug ?? "";
|
|
273
|
+
const [contentTypes, contentItems] = await Promise.all([
|
|
274
|
+
getContentTypesWithCounts(adapter),
|
|
275
|
+
getters.getAllContentItems(adapter, typeSlug, { limit: 20, offset: 0 })
|
|
276
|
+
]);
|
|
277
|
+
qc.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.typesList(), contentTypes);
|
|
278
|
+
qc.setQueryData(
|
|
279
|
+
queryKeyDefs.CMS_QUERY_KEYS.contentList({ typeSlug, limit: 20, offset: 0 }),
|
|
280
|
+
{
|
|
281
|
+
pages: [
|
|
320
282
|
{
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
283
|
+
items: contentItems.items,
|
|
284
|
+
total: contentItems.total,
|
|
285
|
+
limit: contentItems.limit ?? 20,
|
|
286
|
+
offset: contentItems.offset ?? 0
|
|
324
287
|
}
|
|
325
|
-
]
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const { slug } = ctx.params;
|
|
344
|
-
const contentType = await getContentType(slug);
|
|
345
|
-
if (!contentType) {
|
|
346
|
-
throw ctx.error(404, { message: "Content type not found" });
|
|
288
|
+
],
|
|
289
|
+
pageParams: [0]
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
case "editContent": {
|
|
295
|
+
const typeSlug = params?.typeSlug ?? "";
|
|
296
|
+
const id = params?.id ?? "";
|
|
297
|
+
const [contentTypes, item] = await Promise.all([
|
|
298
|
+
getContentTypesWithCounts(adapter),
|
|
299
|
+
id ? getters.getContentItemById(adapter, id) : Promise.resolve(null)
|
|
300
|
+
]);
|
|
301
|
+
qc.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.typesList(), contentTypes);
|
|
302
|
+
if (id) {
|
|
303
|
+
qc.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.contentDetail(typeSlug, id), item);
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
347
306
|
}
|
|
348
|
-
return serializeContentType(contentType);
|
|
349
307
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
308
|
+
};
|
|
309
|
+
};
|
|
310
|
+
return api.defineBackendPlugin({
|
|
311
|
+
name: "cms",
|
|
312
|
+
dbPlugin: db.cmsSchema,
|
|
313
|
+
api: (adapter) => ({
|
|
314
|
+
getAllContentTypes: async () => {
|
|
315
|
+
await ensureSynced(adapter);
|
|
316
|
+
return getters.getAllContentTypes(adapter);
|
|
357
317
|
},
|
|
358
|
-
async (
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const contentType = await getContentType(typeSlug);
|
|
362
|
-
if (!contentType) {
|
|
363
|
-
throw ctx.error(404, { message: "Content type not found" });
|
|
364
|
-
}
|
|
365
|
-
const whereConditions = [
|
|
366
|
-
{
|
|
367
|
-
field: "contentTypeId",
|
|
368
|
-
value: contentType.id,
|
|
369
|
-
operator: "eq"
|
|
370
|
-
}
|
|
371
|
-
];
|
|
372
|
-
if (slug) {
|
|
373
|
-
whereConditions.push({
|
|
374
|
-
field: "slug",
|
|
375
|
-
value: slug,
|
|
376
|
-
operator: "eq"
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
const allItems = await adapter.findMany({
|
|
380
|
-
model: "contentItem",
|
|
381
|
-
where: whereConditions
|
|
382
|
-
});
|
|
383
|
-
const total = allItems.length;
|
|
384
|
-
const items = await adapter.findMany({
|
|
385
|
-
model: "contentItem",
|
|
386
|
-
where: whereConditions,
|
|
387
|
-
limit,
|
|
388
|
-
offset,
|
|
389
|
-
sortBy: { field: "createdAt", direction: "desc" },
|
|
390
|
-
join: { contentType: true }
|
|
391
|
-
});
|
|
392
|
-
return {
|
|
393
|
-
items: items.map(serializeContentItemWithType),
|
|
394
|
-
total,
|
|
395
|
-
limit,
|
|
396
|
-
offset
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
);
|
|
400
|
-
const getContentItem = api.createEndpoint(
|
|
401
|
-
"/content/:typeSlug/:id",
|
|
402
|
-
{
|
|
403
|
-
method: "GET",
|
|
404
|
-
params: z.z.object({ typeSlug: z.z.string(), id: z.z.string() })
|
|
318
|
+
getAllContentItems: async (contentTypeSlug, params) => {
|
|
319
|
+
await ensureSynced(adapter);
|
|
320
|
+
return getters.getAllContentItems(adapter, contentTypeSlug, params);
|
|
405
321
|
},
|
|
406
|
-
async (
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (!contentType) {
|
|
410
|
-
throw ctx.error(404, { message: "Content type not found" });
|
|
411
|
-
}
|
|
412
|
-
const item = await adapter.findOne({
|
|
413
|
-
model: "contentItem",
|
|
414
|
-
where: [{ field: "id", value: id, operator: "eq" }],
|
|
415
|
-
join: { contentType: true }
|
|
416
|
-
});
|
|
417
|
-
if (!item || item.contentTypeId !== contentType.id) {
|
|
418
|
-
throw ctx.error(404, { message: "Content item not found" });
|
|
419
|
-
}
|
|
420
|
-
return serializeContentItemWithType(item);
|
|
421
|
-
}
|
|
422
|
-
);
|
|
423
|
-
const createContentItem = api.createEndpoint(
|
|
424
|
-
"/content/:typeSlug",
|
|
425
|
-
{
|
|
426
|
-
method: "POST",
|
|
427
|
-
params: z.z.object({ typeSlug: z.z.string() }),
|
|
428
|
-
body: z.z.object({
|
|
429
|
-
slug: z.z.string().min(1),
|
|
430
|
-
// Use passthrough object instead of z.record(z.unknown()) due to Zod v4 bug
|
|
431
|
-
data: z.z.object({}).passthrough()
|
|
432
|
-
})
|
|
322
|
+
getContentItemBySlug: async (contentTypeSlug, slug) => {
|
|
323
|
+
await ensureSynced(adapter);
|
|
324
|
+
return getters.getContentItemBySlug(adapter, contentTypeSlug, slug);
|
|
433
325
|
},
|
|
434
|
-
async (
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
throw ctx.error(404, { message: "Content type not found" });
|
|
447
|
-
}
|
|
448
|
-
const { processedData: dataWithResolvedRelations, relationIds } = await processRelationsInData(
|
|
449
|
-
adapter,
|
|
450
|
-
contentType,
|
|
451
|
-
data,
|
|
452
|
-
getContentType
|
|
453
|
-
);
|
|
454
|
-
const zodSchema = getContentTypeZodSchema(contentType);
|
|
455
|
-
const validation = zodSchema.safeParse(dataWithResolvedRelations);
|
|
456
|
-
if (!validation.success) {
|
|
457
|
-
throw ctx.error(400, {
|
|
458
|
-
message: "Validation failed",
|
|
459
|
-
errors: validation.error.issues
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
const existing = await adapter.findOne({
|
|
463
|
-
model: "contentItem",
|
|
464
|
-
where: [
|
|
465
|
-
{
|
|
466
|
-
field: "contentTypeId",
|
|
467
|
-
value: contentType.id,
|
|
468
|
-
operator: "eq"
|
|
469
|
-
},
|
|
470
|
-
{ field: "slug", value: slug, operator: "eq" }
|
|
471
|
-
]
|
|
326
|
+
getContentItemById: async (id) => {
|
|
327
|
+
await ensureSynced(adapter);
|
|
328
|
+
return getters.getContentItemById(adapter, id);
|
|
329
|
+
},
|
|
330
|
+
prefetchForRoute: createCMSPrefetchForRoute(adapter)
|
|
331
|
+
}),
|
|
332
|
+
routes: (adapter) => {
|
|
333
|
+
const getContentType = async (slug) => {
|
|
334
|
+
await ensureSynced(adapter);
|
|
335
|
+
return adapter.findOne({
|
|
336
|
+
model: "contentType",
|
|
337
|
+
where: [{ field: "slug", value: slug, operator: "eq" }]
|
|
472
338
|
});
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
339
|
+
};
|
|
340
|
+
const createContext = (typeSlug, headers) => ({
|
|
341
|
+
typeSlug,
|
|
342
|
+
headers
|
|
343
|
+
});
|
|
344
|
+
const listContentTypes = api.createEndpoint(
|
|
345
|
+
"/content-types",
|
|
346
|
+
{ method: "GET" },
|
|
347
|
+
async (ctx) => {
|
|
348
|
+
await ensureSynced(adapter);
|
|
349
|
+
const contentTypes = await adapter.findMany({
|
|
350
|
+
model: "contentType",
|
|
351
|
+
sortBy: { field: "name", direction: "asc" }
|
|
476
352
|
});
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
353
|
+
const typesWithCounts = await Promise.all(
|
|
354
|
+
contentTypes.map(async (ct) => {
|
|
355
|
+
const items = await adapter.findMany({
|
|
356
|
+
model: "contentItem",
|
|
357
|
+
where: [
|
|
358
|
+
{
|
|
359
|
+
field: "contentTypeId",
|
|
360
|
+
value: ct.id,
|
|
361
|
+
operator: "eq"
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
});
|
|
365
|
+
return {
|
|
366
|
+
...getters.serializeContentType(ct),
|
|
367
|
+
itemCount: items.length
|
|
368
|
+
};
|
|
369
|
+
})
|
|
483
370
|
);
|
|
484
|
-
|
|
485
|
-
|
|
371
|
+
return typesWithCounts;
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
const getContentTypeBySlug = api.createEndpoint(
|
|
375
|
+
"/content-types/:slug",
|
|
376
|
+
{
|
|
377
|
+
method: "GET",
|
|
378
|
+
params: z.z.object({ slug: z.z.string() })
|
|
379
|
+
},
|
|
380
|
+
async (ctx) => {
|
|
381
|
+
const { slug } = ctx.params;
|
|
382
|
+
const contentType = await getContentType(slug);
|
|
383
|
+
if (!contentType) {
|
|
384
|
+
throw ctx.error(404, { message: "Content type not found" });
|
|
486
385
|
}
|
|
386
|
+
return getters.serializeContentType(contentType);
|
|
487
387
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
388
|
+
);
|
|
389
|
+
const listContentItems = api.createEndpoint(
|
|
390
|
+
"/content/:typeSlug",
|
|
391
|
+
{
|
|
392
|
+
method: "GET",
|
|
393
|
+
params: z.z.object({ typeSlug: z.z.string() }),
|
|
394
|
+
query: schemas.listContentQuerySchema
|
|
395
|
+
},
|
|
396
|
+
async (ctx) => {
|
|
397
|
+
const { typeSlug } = ctx.params;
|
|
398
|
+
const { slug, limit, offset } = ctx.query;
|
|
399
|
+
const contentType = await getContentType(typeSlug);
|
|
400
|
+
if (!contentType) {
|
|
401
|
+
throw ctx.error(404, { message: "Content type not found" });
|
|
496
402
|
}
|
|
497
|
-
|
|
498
|
-
await syncRelations(adapter, item.id, relationIds);
|
|
499
|
-
const serialized = serializeContentItem(item);
|
|
500
|
-
if (config.hooks?.onAfterCreate) {
|
|
501
|
-
await config.hooks.onAfterCreate(serialized, context);
|
|
403
|
+
return getters.getAllContentItems(adapter, typeSlug, { slug, limit, offset });
|
|
502
404
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
async (ctx) => {
|
|
521
|
-
const { typeSlug, id } = ctx.params;
|
|
522
|
-
const { slug: rawSlug, data } = ctx.body;
|
|
523
|
-
const context = createContext(typeSlug, ctx.headers);
|
|
524
|
-
const slug = rawSlug ? utils.slugify(rawSlug) : void 0;
|
|
525
|
-
if (rawSlug && !slug) {
|
|
526
|
-
throw ctx.error(400, {
|
|
527
|
-
message: "Invalid slug: must contain at least one alphanumeric character"
|
|
405
|
+
);
|
|
406
|
+
const getContentItem = api.createEndpoint(
|
|
407
|
+
"/content/:typeSlug/:id",
|
|
408
|
+
{
|
|
409
|
+
method: "GET",
|
|
410
|
+
params: z.z.object({ typeSlug: z.z.string(), id: z.z.string() })
|
|
411
|
+
},
|
|
412
|
+
async (ctx) => {
|
|
413
|
+
const { typeSlug, id } = ctx.params;
|
|
414
|
+
const contentType = await getContentType(typeSlug);
|
|
415
|
+
if (!contentType) {
|
|
416
|
+
throw ctx.error(404, { message: "Content type not found" });
|
|
417
|
+
}
|
|
418
|
+
const item = await adapter.findOne({
|
|
419
|
+
model: "contentItem",
|
|
420
|
+
where: [{ field: "id", value: id, operator: "eq" }],
|
|
421
|
+
join: { contentType: true }
|
|
528
422
|
});
|
|
423
|
+
if (!item || item.contentTypeId !== contentType.id) {
|
|
424
|
+
throw ctx.error(404, { message: "Content item not found" });
|
|
425
|
+
}
|
|
426
|
+
return getters.serializeContentItemWithType(item);
|
|
529
427
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
428
|
+
);
|
|
429
|
+
const createContentItem = api.createEndpoint(
|
|
430
|
+
"/content/:typeSlug",
|
|
431
|
+
{
|
|
432
|
+
method: "POST",
|
|
433
|
+
params: z.z.object({ typeSlug: z.z.string() }),
|
|
434
|
+
body: z.z.object({
|
|
435
|
+
slug: z.z.string().min(1),
|
|
436
|
+
// Use passthrough object instead of z.record(z.unknown()) due to Zod v4 bug
|
|
437
|
+
data: z.z.object({}).passthrough()
|
|
438
|
+
})
|
|
439
|
+
},
|
|
440
|
+
async (ctx) => {
|
|
441
|
+
const { typeSlug } = ctx.params;
|
|
442
|
+
const { slug: rawSlug, data } = ctx.body;
|
|
443
|
+
const context = createContext(typeSlug, ctx.headers);
|
|
444
|
+
const slug = utils.slugify(rawSlug);
|
|
445
|
+
if (!slug) {
|
|
446
|
+
throw ctx.error(400, {
|
|
447
|
+
message: "Invalid slug: must contain at least one alphanumeric character"
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
const contentType = await getContentType(typeSlug);
|
|
451
|
+
if (!contentType) {
|
|
452
|
+
throw ctx.error(404, { message: "Content type not found" });
|
|
453
|
+
}
|
|
454
|
+
const { processedData: dataWithResolvedRelations, relationIds } = await processRelationsInData(
|
|
455
|
+
adapter,
|
|
456
|
+
contentType,
|
|
457
|
+
data,
|
|
458
|
+
getContentType
|
|
459
|
+
);
|
|
460
|
+
const zodSchema = getContentTypeZodSchema(contentType);
|
|
461
|
+
const validation = zodSchema.safeParse(dataWithResolvedRelations);
|
|
462
|
+
if (!validation.success) {
|
|
463
|
+
throw ctx.error(400, {
|
|
464
|
+
message: "Validation failed",
|
|
465
|
+
errors: validation.error.issues
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
const existing = await adapter.findOne({
|
|
543
469
|
model: "contentItem",
|
|
544
470
|
where: [
|
|
545
471
|
{
|
|
@@ -550,367 +476,448 @@ const cmsBackendPlugin = (config) => api.defineBackendPlugin({
|
|
|
550
476
|
{ field: "slug", value: slug, operator: "eq" }
|
|
551
477
|
]
|
|
552
478
|
});
|
|
553
|
-
if (
|
|
479
|
+
if (existing) {
|
|
554
480
|
throw ctx.error(409, {
|
|
555
481
|
message: "Content item with this slug already exists"
|
|
556
482
|
});
|
|
557
483
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
484
|
+
const processedData = validation.data;
|
|
485
|
+
if (config.hooks?.onBeforeCreate) {
|
|
486
|
+
const result = await config.hooks.onBeforeCreate(
|
|
487
|
+
processedData,
|
|
488
|
+
context
|
|
489
|
+
);
|
|
490
|
+
if (result === false) {
|
|
491
|
+
throw ctx.error(403, { message: "Create operation denied" });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const item = await adapter.create({
|
|
495
|
+
model: "contentItem",
|
|
496
|
+
data: {
|
|
497
|
+
contentTypeId: contentType.id,
|
|
498
|
+
slug,
|
|
499
|
+
data: JSON.stringify(processedData),
|
|
500
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
501
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
await syncRelations(adapter, item.id, relationIds);
|
|
505
|
+
const serialized = getters.serializeContentItem(item);
|
|
506
|
+
if (config.hooks?.onAfterCreate) {
|
|
507
|
+
await config.hooks.onAfterCreate(serialized, context);
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
...serialized,
|
|
511
|
+
parsedData: processedData
|
|
577
512
|
};
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
const updateContentItem = api.createEndpoint(
|
|
516
|
+
"/content/:typeSlug/:id",
|
|
517
|
+
{
|
|
518
|
+
method: "PUT",
|
|
519
|
+
params: z.z.object({ typeSlug: z.z.string(), id: z.z.string() }),
|
|
520
|
+
body: z.z.object({
|
|
521
|
+
slug: z.z.string().min(1).optional(),
|
|
522
|
+
// Use passthrough object instead of z.record(z.unknown()) due to Zod v4 bug
|
|
523
|
+
data: z.z.object({}).passthrough().optional()
|
|
524
|
+
})
|
|
525
|
+
},
|
|
526
|
+
async (ctx) => {
|
|
527
|
+
const { typeSlug, id } = ctx.params;
|
|
528
|
+
const { slug: rawSlug, data } = ctx.body;
|
|
529
|
+
const context = createContext(typeSlug, ctx.headers);
|
|
530
|
+
const slug = rawSlug ? utils.slugify(rawSlug) : void 0;
|
|
531
|
+
if (rawSlug && !slug) {
|
|
581
532
|
throw ctx.error(400, {
|
|
582
|
-
message: "
|
|
583
|
-
errors: validation.error.issues
|
|
533
|
+
message: "Invalid slug: must contain at least one alphanumeric character"
|
|
584
534
|
});
|
|
585
535
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
if (config.hooks?.onBeforeUpdate && validatedData) {
|
|
590
|
-
const result = await config.hooks.onBeforeUpdate(
|
|
591
|
-
id,
|
|
592
|
-
validatedData,
|
|
593
|
-
context
|
|
594
|
-
);
|
|
595
|
-
if (result === false) {
|
|
596
|
-
throw ctx.error(403, { message: "Update operation denied" });
|
|
536
|
+
const contentType = await getContentType(typeSlug);
|
|
537
|
+
if (!contentType) {
|
|
538
|
+
throw ctx.error(404, { message: "Content type not found" });
|
|
597
539
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
};
|
|
605
|
-
if (slug) updateData.slug = slug;
|
|
606
|
-
if (processedData) updateData.data = JSON.stringify(processedData);
|
|
607
|
-
await adapter.update({
|
|
608
|
-
model: "contentItem",
|
|
609
|
-
where: [{ field: "id", value: id, operator: "eq" }],
|
|
610
|
-
update: updateData
|
|
611
|
-
});
|
|
612
|
-
const updated = await adapter.findOne({
|
|
613
|
-
model: "contentItem",
|
|
614
|
-
where: [{ field: "id", value: id, operator: "eq" }],
|
|
615
|
-
join: { contentType: true }
|
|
616
|
-
});
|
|
617
|
-
if (!updated) {
|
|
618
|
-
throw ctx.error(500, { message: "Failed to fetch updated item" });
|
|
619
|
-
}
|
|
620
|
-
const serialized = serializeContentItem(updated);
|
|
621
|
-
if (config.hooks?.onAfterUpdate) {
|
|
622
|
-
await config.hooks.onAfterUpdate(serialized, context);
|
|
623
|
-
}
|
|
624
|
-
return serializeContentItemWithType(updated);
|
|
625
|
-
}
|
|
626
|
-
);
|
|
627
|
-
const deleteContentItem = api.createEndpoint(
|
|
628
|
-
"/content/:typeSlug/:id",
|
|
629
|
-
{
|
|
630
|
-
method: "DELETE",
|
|
631
|
-
params: z.z.object({ typeSlug: z.z.string(), id: z.z.string() })
|
|
632
|
-
},
|
|
633
|
-
async (ctx) => {
|
|
634
|
-
const { typeSlug, id } = ctx.params;
|
|
635
|
-
const context = createContext(typeSlug, ctx.headers);
|
|
636
|
-
const contentType = await getContentType(typeSlug);
|
|
637
|
-
if (!contentType) {
|
|
638
|
-
throw ctx.error(404, { message: "Content type not found" });
|
|
639
|
-
}
|
|
640
|
-
const existing = await adapter.findOne({
|
|
641
|
-
model: "contentItem",
|
|
642
|
-
where: [{ field: "id", value: id, operator: "eq" }]
|
|
643
|
-
});
|
|
644
|
-
if (!existing || existing.contentTypeId !== contentType.id) {
|
|
645
|
-
throw ctx.error(404, { message: "Content item not found" });
|
|
646
|
-
}
|
|
647
|
-
if (config.hooks?.onBeforeDelete) {
|
|
648
|
-
const canDelete = await config.hooks.onBeforeDelete(id, context);
|
|
649
|
-
if (!canDelete) {
|
|
650
|
-
throw ctx.error(403, { message: "Delete operation denied" });
|
|
540
|
+
const existing = await adapter.findOne({
|
|
541
|
+
model: "contentItem",
|
|
542
|
+
where: [{ field: "id", value: id, operator: "eq" }]
|
|
543
|
+
});
|
|
544
|
+
if (!existing || existing.contentTypeId !== contentType.id) {
|
|
545
|
+
throw ctx.error(404, { message: "Content item not found" });
|
|
651
546
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
});
|
|
716
|
-
const sourceIds = [
|
|
717
|
-
...new Set(contentRelations.map((r) => r.sourceId))
|
|
718
|
-
];
|
|
719
|
-
if (sourceIds.length === 0) {
|
|
720
|
-
return {
|
|
721
|
-
items: [],
|
|
722
|
-
total: 0,
|
|
723
|
-
limit,
|
|
724
|
-
offset
|
|
547
|
+
if (slug && slug !== existing.slug) {
|
|
548
|
+
const duplicate = await adapter.findOne({
|
|
549
|
+
model: "contentItem",
|
|
550
|
+
where: [
|
|
551
|
+
{
|
|
552
|
+
field: "contentTypeId",
|
|
553
|
+
value: contentType.id,
|
|
554
|
+
operator: "eq"
|
|
555
|
+
},
|
|
556
|
+
{ field: "slug", value: slug, operator: "eq" }
|
|
557
|
+
]
|
|
558
|
+
});
|
|
559
|
+
if (duplicate) {
|
|
560
|
+
throw ctx.error(409, {
|
|
561
|
+
message: "Content item with this slug already exists"
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
let dataWithResolvedRelations;
|
|
566
|
+
let relationIds;
|
|
567
|
+
if (data) {
|
|
568
|
+
const result = await processRelationsInData(
|
|
569
|
+
adapter,
|
|
570
|
+
contentType,
|
|
571
|
+
data,
|
|
572
|
+
getContentType
|
|
573
|
+
);
|
|
574
|
+
dataWithResolvedRelations = result.processedData;
|
|
575
|
+
relationIds = result.relationIds;
|
|
576
|
+
}
|
|
577
|
+
let validatedData = dataWithResolvedRelations;
|
|
578
|
+
if (dataWithResolvedRelations) {
|
|
579
|
+
const existingData = existing.data ? JSON.parse(existing.data) : {};
|
|
580
|
+
const mergedData = {
|
|
581
|
+
...existingData,
|
|
582
|
+
...dataWithResolvedRelations
|
|
583
|
+
};
|
|
584
|
+
const zodSchema = getContentTypeZodSchema(contentType);
|
|
585
|
+
const validation = zodSchema.safeParse(mergedData);
|
|
586
|
+
if (!validation.success) {
|
|
587
|
+
throw ctx.error(400, {
|
|
588
|
+
message: "Validation failed",
|
|
589
|
+
errors: validation.error.issues
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
validatedData = validation.data;
|
|
593
|
+
}
|
|
594
|
+
const processedData = validatedData;
|
|
595
|
+
if (config.hooks?.onBeforeUpdate && validatedData) {
|
|
596
|
+
const result = await config.hooks.onBeforeUpdate(
|
|
597
|
+
id,
|
|
598
|
+
validatedData,
|
|
599
|
+
context
|
|
600
|
+
);
|
|
601
|
+
if (result === false) {
|
|
602
|
+
throw ctx.error(403, { message: "Update operation denied" });
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (relationIds) {
|
|
606
|
+
await syncRelations(adapter, id, relationIds);
|
|
607
|
+
}
|
|
608
|
+
const updateData = {
|
|
609
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
725
610
|
};
|
|
611
|
+
if (slug) updateData.slug = slug;
|
|
612
|
+
if (processedData) updateData.data = JSON.stringify(processedData);
|
|
613
|
+
await adapter.update({
|
|
614
|
+
model: "contentItem",
|
|
615
|
+
where: [{ field: "id", value: id, operator: "eq" }],
|
|
616
|
+
update: updateData
|
|
617
|
+
});
|
|
618
|
+
const updated = await adapter.findOne({
|
|
619
|
+
model: "contentItem",
|
|
620
|
+
where: [{ field: "id", value: id, operator: "eq" }],
|
|
621
|
+
join: { contentType: true }
|
|
622
|
+
});
|
|
623
|
+
if (!updated) {
|
|
624
|
+
throw ctx.error(500, { message: "Failed to fetch updated item" });
|
|
625
|
+
}
|
|
626
|
+
const serialized = getters.serializeContentItem(updated);
|
|
627
|
+
if (config.hooks?.onAfterUpdate) {
|
|
628
|
+
await config.hooks.onAfterUpdate(serialized, context);
|
|
629
|
+
}
|
|
630
|
+
return getters.serializeContentItemWithType(updated);
|
|
726
631
|
}
|
|
727
|
-
|
|
728
|
-
|
|
632
|
+
);
|
|
633
|
+
const deleteContentItem = api.createEndpoint(
|
|
634
|
+
"/content/:typeSlug/:id",
|
|
635
|
+
{
|
|
636
|
+
method: "DELETE",
|
|
637
|
+
params: z.z.object({ typeSlug: z.z.string(), id: z.z.string() })
|
|
638
|
+
},
|
|
639
|
+
async (ctx) => {
|
|
640
|
+
const { typeSlug, id } = ctx.params;
|
|
641
|
+
const context = createContext(typeSlug, ctx.headers);
|
|
642
|
+
const contentType = await getContentType(typeSlug);
|
|
643
|
+
if (!contentType) {
|
|
644
|
+
throw ctx.error(404, { message: "Content type not found" });
|
|
645
|
+
}
|
|
646
|
+
const existing = await adapter.findOne({
|
|
647
|
+
model: "contentItem",
|
|
648
|
+
where: [{ field: "id", value: id, operator: "eq" }]
|
|
649
|
+
});
|
|
650
|
+
if (!existing || existing.contentTypeId !== contentType.id) {
|
|
651
|
+
throw ctx.error(404, { message: "Content item not found" });
|
|
652
|
+
}
|
|
653
|
+
if (config.hooks?.onBeforeDelete) {
|
|
654
|
+
const canDelete = await config.hooks.onBeforeDelete(id, context);
|
|
655
|
+
if (!canDelete) {
|
|
656
|
+
throw ctx.error(403, { message: "Delete operation denied" });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
await adapter.delete({
|
|
660
|
+
model: "contentItem",
|
|
661
|
+
where: [{ field: "id", value: id, operator: "eq" }]
|
|
662
|
+
});
|
|
663
|
+
if (config.hooks?.onAfterDelete) {
|
|
664
|
+
await config.hooks.onAfterDelete(id, context);
|
|
665
|
+
}
|
|
666
|
+
return { success: true };
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
const getContentItemPopulated = api.createEndpoint(
|
|
670
|
+
"/content/:typeSlug/:id/populated",
|
|
671
|
+
{
|
|
672
|
+
method: "GET",
|
|
673
|
+
params: z.z.object({ typeSlug: z.z.string(), id: z.z.string() })
|
|
674
|
+
},
|
|
675
|
+
async (ctx) => {
|
|
676
|
+
const { typeSlug, id } = ctx.params;
|
|
677
|
+
const contentType = await getContentType(typeSlug);
|
|
678
|
+
if (!contentType) {
|
|
679
|
+
throw ctx.error(404, { message: "Content type not found" });
|
|
680
|
+
}
|
|
729
681
|
const item = await adapter.findOne({
|
|
730
682
|
model: "contentItem",
|
|
731
|
-
where: [
|
|
732
|
-
{ field: "id", value: sourceId, operator: "eq" },
|
|
733
|
-
{
|
|
734
|
-
field: "contentTypeId",
|
|
735
|
-
value: contentType.id,
|
|
736
|
-
operator: "eq"
|
|
737
|
-
}
|
|
738
|
-
],
|
|
683
|
+
where: [{ field: "id", value: id, operator: "eq" }],
|
|
739
684
|
join: { contentType: true }
|
|
740
685
|
});
|
|
741
|
-
if (item) {
|
|
742
|
-
|
|
686
|
+
if (!item || item.contentTypeId !== contentType.id) {
|
|
687
|
+
throw ctx.error(404, { message: "Content item not found" });
|
|
743
688
|
}
|
|
689
|
+
const _relations = await populateRelations(adapter, item);
|
|
690
|
+
return {
|
|
691
|
+
...getters.serializeContentItemWithType(item),
|
|
692
|
+
_relations
|
|
693
|
+
};
|
|
744
694
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
695
|
+
);
|
|
696
|
+
const listContentByRelation = api.createEndpoint(
|
|
697
|
+
"/content/:typeSlug/by-relation",
|
|
698
|
+
{
|
|
699
|
+
method: "GET",
|
|
700
|
+
params: z.z.object({ typeSlug: z.z.string() }),
|
|
701
|
+
query: z.z.object({
|
|
702
|
+
field: z.z.string(),
|
|
703
|
+
targetId: z.z.string(),
|
|
704
|
+
limit: z.z.coerce.number().min(1).max(100).optional().default(20),
|
|
705
|
+
offset: z.z.coerce.number().min(0).optional().default(0)
|
|
706
|
+
})
|
|
707
|
+
},
|
|
708
|
+
async (ctx) => {
|
|
709
|
+
const { typeSlug } = ctx.params;
|
|
710
|
+
const { field, targetId, limit, offset } = ctx.query;
|
|
711
|
+
const contentType = await getContentType(typeSlug);
|
|
712
|
+
if (!contentType) {
|
|
713
|
+
throw ctx.error(404, { message: "Content type not found" });
|
|
714
|
+
}
|
|
715
|
+
const contentRelations = await adapter.findMany({
|
|
716
|
+
model: "contentRelation",
|
|
717
|
+
where: [
|
|
718
|
+
{ field: "targetId", value: targetId, operator: "eq" },
|
|
719
|
+
{ field: "fieldName", value: field, operator: "eq" }
|
|
720
|
+
]
|
|
721
|
+
});
|
|
722
|
+
const sourceIds = [
|
|
723
|
+
...new Set(contentRelations.map((r) => r.sourceId))
|
|
724
|
+
];
|
|
725
|
+
if (sourceIds.length === 0) {
|
|
726
|
+
return {
|
|
727
|
+
items: [],
|
|
728
|
+
total: 0,
|
|
729
|
+
limit,
|
|
730
|
+
offset
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
const allItems = [];
|
|
734
|
+
for (const sourceId of sourceIds) {
|
|
735
|
+
const item = await adapter.findOne({
|
|
736
|
+
model: "contentItem",
|
|
737
|
+
where: [
|
|
738
|
+
{ field: "id", value: sourceId, operator: "eq" },
|
|
739
|
+
{
|
|
740
|
+
field: "contentTypeId",
|
|
741
|
+
value: contentType.id,
|
|
742
|
+
operator: "eq"
|
|
743
|
+
}
|
|
744
|
+
],
|
|
745
|
+
join: { contentType: true }
|
|
746
|
+
});
|
|
747
|
+
if (item) {
|
|
748
|
+
allItems.push(item);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
allItems.sort(
|
|
752
|
+
(a, b) => b.createdAt.getTime() - a.createdAt.getTime()
|
|
753
|
+
);
|
|
754
|
+
const total = allItems.length;
|
|
755
|
+
const paginatedItems = allItems.slice(offset, offset + limit);
|
|
756
|
+
return {
|
|
757
|
+
items: paginatedItems.map(getters.serializeContentItemWithType),
|
|
758
|
+
total,
|
|
759
|
+
limit,
|
|
760
|
+
offset
|
|
761
|
+
};
|
|
774
762
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
)
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
763
|
+
);
|
|
764
|
+
const getInverseRelations = api.createEndpoint(
|
|
765
|
+
"/content-types/:slug/inverse-relations",
|
|
766
|
+
{
|
|
767
|
+
method: "GET",
|
|
768
|
+
params: z.z.object({ slug: z.z.string() }),
|
|
769
|
+
query: z.z.object({
|
|
770
|
+
itemId: z.z.string().optional()
|
|
771
|
+
})
|
|
772
|
+
},
|
|
773
|
+
async (ctx) => {
|
|
774
|
+
const { slug } = ctx.params;
|
|
775
|
+
const { itemId } = ctx.query;
|
|
776
|
+
await ensureSynced(adapter);
|
|
777
|
+
const targetContentType = await getContentType(slug);
|
|
778
|
+
if (!targetContentType) {
|
|
779
|
+
throw ctx.error(404, { message: "Content type not found" });
|
|
780
|
+
}
|
|
781
|
+
const allContentTypes = await adapter.findMany({
|
|
782
|
+
model: "contentType"
|
|
783
|
+
});
|
|
784
|
+
const inverseRelations = [];
|
|
785
|
+
for (const contentType of allContentTypes) {
|
|
786
|
+
const relationFields = extractRelationFields(contentType);
|
|
787
|
+
for (const [fieldName, relationConfig] of Object.entries(
|
|
788
|
+
relationFields
|
|
789
|
+
)) {
|
|
790
|
+
if (relationConfig.type === "belongsTo" && relationConfig.targetType === slug) {
|
|
791
|
+
let count = 0;
|
|
792
|
+
if (itemId) {
|
|
793
|
+
const relations = await adapter.findMany({
|
|
794
|
+
model: "contentRelation",
|
|
806
795
|
where: [
|
|
807
796
|
{
|
|
808
|
-
field: "
|
|
809
|
-
value:
|
|
797
|
+
field: "targetId",
|
|
798
|
+
value: itemId,
|
|
810
799
|
operator: "eq"
|
|
811
800
|
},
|
|
812
801
|
{
|
|
813
|
-
field: "
|
|
814
|
-
value:
|
|
802
|
+
field: "fieldName",
|
|
803
|
+
value: fieldName,
|
|
815
804
|
operator: "eq"
|
|
816
805
|
}
|
|
817
806
|
]
|
|
818
807
|
});
|
|
819
|
-
|
|
808
|
+
const itemIds = relations.map((r) => r.sourceId);
|
|
809
|
+
for (const sourceId of itemIds) {
|
|
810
|
+
const item = await adapter.findOne({
|
|
811
|
+
model: "contentItem",
|
|
812
|
+
where: [
|
|
813
|
+
{
|
|
814
|
+
field: "id",
|
|
815
|
+
value: sourceId,
|
|
816
|
+
operator: "eq"
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
field: "contentTypeId",
|
|
820
|
+
value: contentType.id,
|
|
821
|
+
operator: "eq"
|
|
822
|
+
}
|
|
823
|
+
]
|
|
824
|
+
});
|
|
825
|
+
if (item) count++;
|
|
826
|
+
}
|
|
820
827
|
}
|
|
828
|
+
inverseRelations.push({
|
|
829
|
+
sourceType: contentType.slug,
|
|
830
|
+
sourceTypeName: contentType.name,
|
|
831
|
+
fieldName,
|
|
832
|
+
count
|
|
833
|
+
});
|
|
821
834
|
}
|
|
822
|
-
inverseRelations.push({
|
|
823
|
-
sourceType: contentType.slug,
|
|
824
|
-
sourceTypeName: contentType.name,
|
|
825
|
-
fieldName,
|
|
826
|
-
count
|
|
827
|
-
});
|
|
828
835
|
}
|
|
829
836
|
}
|
|
837
|
+
return { inverseRelations };
|
|
830
838
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
const relations = await adapter.findMany({
|
|
862
|
-
model: "contentRelation",
|
|
863
|
-
where: [
|
|
864
|
-
{ field: "targetId", value: itemId, operator: "eq" },
|
|
865
|
-
{ field: "fieldName", value: fieldName, operator: "eq" }
|
|
866
|
-
]
|
|
867
|
-
});
|
|
868
|
-
const sourceIds = [...new Set(relations.map((r) => r.sourceId))];
|
|
869
|
-
const allItems = [];
|
|
870
|
-
for (const sourceId of sourceIds) {
|
|
871
|
-
const item = await adapter.findOne({
|
|
872
|
-
model: "contentItem",
|
|
839
|
+
);
|
|
840
|
+
const listInverseRelationItems = api.createEndpoint(
|
|
841
|
+
"/content-types/:slug/inverse-relations/:sourceType",
|
|
842
|
+
{
|
|
843
|
+
method: "GET",
|
|
844
|
+
params: z.z.object({
|
|
845
|
+
slug: z.z.string(),
|
|
846
|
+
sourceType: z.z.string()
|
|
847
|
+
}),
|
|
848
|
+
query: z.z.object({
|
|
849
|
+
itemId: z.z.string(),
|
|
850
|
+
fieldName: z.z.string(),
|
|
851
|
+
limit: z.z.coerce.number().min(1).max(100).optional().default(20),
|
|
852
|
+
offset: z.z.coerce.number().min(0).optional().default(0)
|
|
853
|
+
})
|
|
854
|
+
},
|
|
855
|
+
async (ctx) => {
|
|
856
|
+
const { slug, sourceType } = ctx.params;
|
|
857
|
+
const { itemId, fieldName, limit, offset } = ctx.query;
|
|
858
|
+
await ensureSynced(adapter);
|
|
859
|
+
const targetContentType = await getContentType(slug);
|
|
860
|
+
if (!targetContentType) {
|
|
861
|
+
throw ctx.error(404, { message: "Target content type not found" });
|
|
862
|
+
}
|
|
863
|
+
const sourceContentType = await getContentType(sourceType);
|
|
864
|
+
if (!sourceContentType) {
|
|
865
|
+
throw ctx.error(404, { message: "Source content type not found" });
|
|
866
|
+
}
|
|
867
|
+
const relations = await adapter.findMany({
|
|
868
|
+
model: "contentRelation",
|
|
873
869
|
where: [
|
|
874
|
-
{ field: "
|
|
875
|
-
{
|
|
876
|
-
|
|
877
|
-
value: sourceContentType.id,
|
|
878
|
-
operator: "eq"
|
|
879
|
-
}
|
|
880
|
-
],
|
|
881
|
-
join: { contentType: true }
|
|
870
|
+
{ field: "targetId", value: itemId, operator: "eq" },
|
|
871
|
+
{ field: "fieldName", value: fieldName, operator: "eq" }
|
|
872
|
+
]
|
|
882
873
|
});
|
|
883
|
-
|
|
884
|
-
|
|
874
|
+
const sourceIds = [...new Set(relations.map((r) => r.sourceId))];
|
|
875
|
+
const allItems = [];
|
|
876
|
+
for (const sourceId of sourceIds) {
|
|
877
|
+
const item = await adapter.findOne({
|
|
878
|
+
model: "contentItem",
|
|
879
|
+
where: [
|
|
880
|
+
{ field: "id", value: sourceId, operator: "eq" },
|
|
881
|
+
{
|
|
882
|
+
field: "contentTypeId",
|
|
883
|
+
value: sourceContentType.id,
|
|
884
|
+
operator: "eq"
|
|
885
|
+
}
|
|
886
|
+
],
|
|
887
|
+
join: { contentType: true }
|
|
888
|
+
});
|
|
889
|
+
if (item) {
|
|
890
|
+
allItems.push(item);
|
|
891
|
+
}
|
|
885
892
|
}
|
|
893
|
+
allItems.sort(
|
|
894
|
+
(a, b) => b.createdAt.getTime() - a.createdAt.getTime()
|
|
895
|
+
);
|
|
896
|
+
const total = allItems.length;
|
|
897
|
+
const paginatedItems = allItems.slice(offset, offset + limit);
|
|
898
|
+
return {
|
|
899
|
+
items: paginatedItems.map(getters.serializeContentItemWithType),
|
|
900
|
+
total,
|
|
901
|
+
limit,
|
|
902
|
+
offset
|
|
903
|
+
};
|
|
886
904
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
getContentItem,
|
|
905
|
-
createContentItem,
|
|
906
|
-
updateContentItem,
|
|
907
|
-
deleteContentItem,
|
|
908
|
-
getContentItemPopulated,
|
|
909
|
-
listContentByRelation,
|
|
910
|
-
getInverseRelations,
|
|
911
|
-
listInverseRelationItems
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
|
-
});
|
|
905
|
+
);
|
|
906
|
+
return {
|
|
907
|
+
listContentTypes,
|
|
908
|
+
getContentTypeBySlug,
|
|
909
|
+
listContentItems,
|
|
910
|
+
getContentItem,
|
|
911
|
+
createContentItem,
|
|
912
|
+
updateContentItem,
|
|
913
|
+
deleteContentItem,
|
|
914
|
+
getContentItemPopulated,
|
|
915
|
+
listContentByRelation,
|
|
916
|
+
getInverseRelations,
|
|
917
|
+
listInverseRelationItems
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
};
|
|
915
922
|
|
|
916
923
|
exports.cmsBackendPlugin = cmsBackendPlugin;
|