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