@btst/plugin-cms 1.0.0 → 1.2.0-beta.1
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/getters.cjs +37 -25
- package/dist/api/getters.mjs +37 -25
- package/dist/api/index.cjs +1 -0
- package/dist/api/index.d.cts +17 -8
- package/dist/api/index.d.mts +17 -8
- package/dist/api/index.d.ts +17 -8
- package/dist/api/index.mjs +1 -1
- package/dist/api/mutations.cjs +46 -19
- package/dist/api/mutations.mjs +46 -20
- package/dist/api/operations.cjs +586 -177
- package/dist/api/operations.mjs +585 -178
- package/dist/api/plugin.cjs +56 -7
- package/dist/api/plugin.mjs +57 -8
- package/dist/api/publication.cjs +8 -0
- package/dist/api/publication.mjs +6 -0
- package/dist/client/components/forms/content-form.cjs +107 -1
- package/dist/client/components/forms/content-form.mjs +108 -2
- package/dist/client/components/pages/content-editor-page.internal.cjs +23 -5
- package/dist/client/components/pages/content-editor-page.internal.mjs +23 -5
- package/dist/client/components/pages/content-list-page.internal.cjs +3 -0
- package/dist/client/components/pages/content-list-page.internal.mjs +3 -0
- package/dist/client/components/pages/dashboard-page.internal.cjs +10 -30
- package/dist/client/components/pages/dashboard-page.internal.mjs +10 -30
- package/dist/client/components/revision-history.cjs +161 -0
- package/dist/client/components/revision-history.mjs +159 -0
- package/dist/client/components/shared/content-type-card.cjs +56 -0
- package/dist/client/components/shared/content-type-card.mjs +54 -0
- package/dist/client/components/shared/empty-state.cjs +18 -1
- package/dist/client/components/shared/empty-state.mjs +19 -2
- package/dist/client/hooks/cms-hooks.cjs +18 -2
- package/dist/client/hooks/cms-hooks.mjs +17 -3
- package/dist/client/hooks/index.cjs +2 -0
- package/dist/client/hooks/index.d.cts +306 -2
- package/dist/client/hooks/index.d.mts +306 -2
- package/dist/client/hooks/index.d.ts +306 -2
- package/dist/client/hooks/index.mjs +1 -1
- package/dist/client/index.d.cts +55 -2
- package/dist/client/index.d.mts +55 -2
- package/dist/client/index.d.ts +55 -2
- package/dist/db.cjs +25 -0
- package/dist/db.mjs +25 -0
- package/dist/permissions.cjs +2 -0
- package/dist/permissions.d.cts +1 -0
- package/dist/permissions.d.mts +1 -0
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.mjs +2 -0
- package/dist/query-keys.cjs +31 -1
- package/dist/query-keys.d.cts +1 -1
- package/dist/query-keys.d.mts +1 -1
- package/dist/query-keys.d.ts +1 -1
- package/dist/query-keys.mjs +31 -1
- package/dist/shared/{plugin-cms.W4Le8D91.d.mts → plugin-cms.DAAUZ41w.d.mts} +377 -144
- package/dist/shared/{plugin-cms.c-GeWvTf.d.cts → plugin-cms.DJ_293QU.d.cts} +377 -144
- package/dist/shared/{plugin-cms.DQLAoogy.d.ts → plugin-cms.DlUSv7wz.d.ts} +377 -144
- package/dist/types.d.cts +38 -2
- package/dist/types.d.mts +38 -2
- package/dist/types.d.ts +38 -2
- package/dist/ui/src/components/auto-form/config.cjs +2 -0
- package/dist/ui/src/components/auto-form/config.mjs +2 -0
- package/dist/ui/src/components/auto-form/fields/file.cjs +68 -0
- package/dist/ui/src/components/auto-form/fields/file.mjs +63 -0
- package/dist/ui/src/components/auto-form/fields/object.cjs +9 -2
- package/dist/ui/src/components/auto-form/fields/object.mjs +9 -2
- package/dist/ui/src/components/auto-form/helpers.cjs +9 -0
- package/dist/ui/src/components/auto-form/helpers.mjs +9 -0
- package/dist/ui/src/components/auto-form/index.cjs +29 -4
- package/dist/ui/src/components/auto-form/index.mjs +31 -6
- package/dist/ui/src/components/auto-form/stepped-auto-form.cjs +34 -29
- package/dist/ui/src/components/auto-form/stepped-auto-form.mjs +35 -30
- package/dist/ui/src/components/sheet.cjs +126 -0
- package/dist/ui/src/components/sheet.mjs +105 -0
- package/dist/ui/src/lib/form-visibility.cjs +20 -0
- package/dist/ui/src/lib/form-visibility.mjs +18 -0
- package/dist/ui/src/lib/schema-converter.cjs +101 -0
- package/dist/ui/src/lib/schema-converter.mjs +100 -1
- package/package.json +5 -4
package/dist/api/operations.mjs
CHANGED
|
@@ -6,7 +6,16 @@ import { createListContentQuerySchema, DEFAULT_MAX_PAGE_SIZE, contentReadOptions
|
|
|
6
6
|
import { slugify } from '../utils.mjs';
|
|
7
7
|
import { serializeContentType, getAllContentItems, getContentItemBySlug, applyContentReadOptions, serializeContentItemWithType, getContentItemsByRelationForType, validateContentReadOptions, CMSContentSelectionError } from './getters.mjs';
|
|
8
8
|
import { syncRelations, extractRelationFields, isNewRelationValue, isExistingRelationValue } from './relations.mjs';
|
|
9
|
+
import { recordContentRevision } from './mutations.mjs';
|
|
10
|
+
import { isPublishedContent } from './publication.mjs';
|
|
9
11
|
|
|
12
|
+
const dispatchQueues = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
function freezeCMSOperationData(value) {
|
|
14
|
+
if (value && typeof value === "object") {
|
|
15
|
+
Object.values(value).forEach(freezeCMSOperationData);
|
|
16
|
+
Object.freeze(value);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
10
19
|
function operationContentType(contentType) {
|
|
11
20
|
return { ...contentType };
|
|
12
21
|
}
|
|
@@ -55,13 +64,22 @@ const CMSContentItemParamsSchema = z.object({
|
|
|
55
64
|
typeSlug: z.string().min(1),
|
|
56
65
|
id: z.string().min(1)
|
|
57
66
|
});
|
|
67
|
+
const PublishAtSchema = z.iso.datetime({ offset: true }).nullable().optional();
|
|
68
|
+
const CMSRevisionParamsSchema = CMSContentItemParamsSchema.extend({
|
|
69
|
+
revisionId: z.string().min(1)
|
|
70
|
+
});
|
|
71
|
+
const CMSRevisionsQuerySchema = z.object({
|
|
72
|
+
offset: z.coerce.number().int().min(0).default(0)
|
|
73
|
+
});
|
|
58
74
|
const CMSCreateContentItemBodySchema = z.object({
|
|
59
75
|
slug: z.string().min(1),
|
|
60
|
-
data: CMSDataSchema
|
|
76
|
+
data: CMSDataSchema,
|
|
77
|
+
publishAt: PublishAtSchema
|
|
61
78
|
});
|
|
62
79
|
const CMSUpdateContentItemBodySchema = z.object({
|
|
63
80
|
slug: z.string().min(1).optional(),
|
|
64
|
-
data: CMSDataSchema.optional()
|
|
81
|
+
data: CMSDataSchema.optional(),
|
|
82
|
+
publishAt: PublishAtSchema
|
|
65
83
|
});
|
|
66
84
|
const ListContentTypesInputSchema = EmptyInputSchema;
|
|
67
85
|
const GetContentTypeInputSchema = CMSContentTypeParamsSchema;
|
|
@@ -194,7 +212,7 @@ async function getRecordOrThrow(adapter, ensureSynced, typeSlug, id) {
|
|
|
194
212
|
}
|
|
195
213
|
return item;
|
|
196
214
|
}
|
|
197
|
-
function recordFacts(item) {
|
|
215
|
+
function recordFacts(item, view) {
|
|
198
216
|
if (!item.contentType) {
|
|
199
217
|
throw new CMSOperationError(
|
|
200
218
|
500,
|
|
@@ -204,13 +222,14 @@ function recordFacts(item) {
|
|
|
204
222
|
}
|
|
205
223
|
return {
|
|
206
224
|
contentType: item.contentType.slug,
|
|
225
|
+
...view ? { view } : {},
|
|
207
226
|
scope: "record",
|
|
208
227
|
recordId: item.id,
|
|
209
228
|
...item.authorId ? { authorId: item.authorId } : {}
|
|
210
229
|
};
|
|
211
230
|
}
|
|
212
|
-
function collectionFacts(contentType) {
|
|
213
|
-
return { contentType, scope: "collection" };
|
|
231
|
+
function collectionFacts(contentType, view) {
|
|
232
|
+
return { contentType, scope: "collection", ...view ? { view } : {} };
|
|
214
233
|
}
|
|
215
234
|
function assertRecordFacts(item, facts) {
|
|
216
235
|
if (!facts.recordId || item.id !== facts.recordId || item.contentType?.slug !== facts.contentType || (item.authorId ?? void 0) !== facts.authorId) {
|
|
@@ -258,7 +277,7 @@ function staleRecordError() {
|
|
|
258
277
|
);
|
|
259
278
|
}
|
|
260
279
|
function assertRecordSnapshot(current, expected) {
|
|
261
|
-
if (current.id !== expected.id || current.contentTypeId !== expected.contentTypeId || current.contentType?.slug !== expected.contentType?.slug || (current.authorId ?? void 0) !== (expected.authorId ?? void 0) || current.slug !== expected.slug || current.data !== expected.data || current.updatedAt.getTime() !== expected.updatedAt.getTime()) {
|
|
280
|
+
if (current.id !== expected.id || current.contentTypeId !== expected.contentTypeId || current.contentType?.slug !== expected.contentType?.slug || (current.authorId ?? void 0) !== (expected.authorId ?? void 0) || current.slug !== expected.slug || current.data !== expected.data || current.publishAt?.getTime() !== expected.publishAt?.getTime() || current.updatedAt.getTime() !== expected.updatedAt.getTime()) {
|
|
262
281
|
throw staleRecordError();
|
|
263
282
|
}
|
|
264
283
|
}
|
|
@@ -399,8 +418,10 @@ async function applyRelationWritePlan(adapter, plan) {
|
|
|
399
418
|
if (existing) throw staleRecordError();
|
|
400
419
|
await adapter.create({
|
|
401
420
|
model: "contentItem",
|
|
402
|
-
data: item
|
|
421
|
+
data: item,
|
|
422
|
+
forceAllowId: true
|
|
403
423
|
});
|
|
424
|
+
await recordContentRevision(adapter, item, item.authorId);
|
|
404
425
|
}
|
|
405
426
|
}
|
|
406
427
|
function relationFilterSnapshot(sourceContentType, fieldName, relation, targetContentType) {
|
|
@@ -636,6 +657,7 @@ async function deriveInverseSourceAuthorization(adapter, ensureSynced, targetTyp
|
|
|
636
657
|
}
|
|
637
658
|
function createCMSOperations(adapter, options) {
|
|
638
659
|
const { ensureSynced, getContentTypesWithCounts, hooks } = options;
|
|
660
|
+
const publicationFor = (typeSlug, view) => view === "content" ? options.contentTypes?.find((type) => type.slug === typeSlug)?.publication : void 0;
|
|
639
661
|
const populatedRelationSnapshots = /* @__PURE__ */ new WeakMap();
|
|
640
662
|
const relationWriteAuthorizationSnapshots = /* @__PURE__ */ new WeakMap();
|
|
641
663
|
const relationFilterAuthorizationSnapshots = /* @__PURE__ */ new WeakMap();
|
|
@@ -887,7 +909,10 @@ function createCMSOperations(adapter, options) {
|
|
|
887
909
|
input.typeSlug
|
|
888
910
|
);
|
|
889
911
|
if (!input.query.slug) {
|
|
890
|
-
return collectionFacts(
|
|
912
|
+
return collectionFacts(
|
|
913
|
+
contentType.slug,
|
|
914
|
+
publicationFor(contentType.slug, input.query.view) ? "content" : void 0
|
|
915
|
+
);
|
|
891
916
|
}
|
|
892
917
|
const item = await getContentItemBySlug(
|
|
893
918
|
adapter,
|
|
@@ -896,6 +921,7 @@ function createCMSOperations(adapter, options) {
|
|
|
896
921
|
);
|
|
897
922
|
const facts = {
|
|
898
923
|
contentType: contentType.slug,
|
|
924
|
+
...publicationFor(contentType.slug, input.query.view) ? { view: "content" } : {},
|
|
899
925
|
scope: "record",
|
|
900
926
|
...item ? {
|
|
901
927
|
recordId: item.id,
|
|
@@ -914,11 +940,16 @@ function createCMSOperations(adapter, options) {
|
|
|
914
940
|
input.query
|
|
915
941
|
);
|
|
916
942
|
}
|
|
917
|
-
const result = await getAllContentItems(
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
943
|
+
const result = await getAllContentItems(
|
|
944
|
+
adapter,
|
|
945
|
+
facts.contentType,
|
|
946
|
+
{
|
|
947
|
+
...input.query,
|
|
948
|
+
view: void 0,
|
|
949
|
+
fields: void 0
|
|
950
|
+
},
|
|
951
|
+
publicationFor(facts.contentType, input.query.view)
|
|
952
|
+
);
|
|
922
953
|
if (facts.scope === "record") {
|
|
923
954
|
if (!facts.recordId) {
|
|
924
955
|
if (result.items.length > 0) throw staleRecordError();
|
|
@@ -956,7 +987,8 @@ function createCMSOperations(adapter, options) {
|
|
|
956
987
|
input: GetContentItemInputSchema,
|
|
957
988
|
permission: cmsPermissions.record.read,
|
|
958
989
|
facts: async ({ input }) => recordFacts(
|
|
959
|
-
await getRecordOrThrow(adapter, ensureSynced, input.typeSlug, input.id)
|
|
990
|
+
await getRecordOrThrow(adapter, ensureSynced, input.typeSlug, input.id),
|
|
991
|
+
publicationFor(input.typeSlug, input.view) ? "content" : void 0
|
|
960
992
|
),
|
|
961
993
|
additionalPermissions: ({ facts }) => [
|
|
962
994
|
cmsPermissions.contentType.read({ contentType: facts.contentType })
|
|
@@ -969,6 +1001,14 @@ function createCMSOperations(adapter, options) {
|
|
|
969
1001
|
facts.recordId ?? ""
|
|
970
1002
|
);
|
|
971
1003
|
assertRecordFacts(item, facts);
|
|
1004
|
+
const publication = publicationFor(facts.contentType, input.view);
|
|
1005
|
+
if (publication && !isPublishedContent(item, publication)) {
|
|
1006
|
+
throw new CMSOperationError(
|
|
1007
|
+
404,
|
|
1008
|
+
"Content item not found",
|
|
1009
|
+
"RECORD_NOT_FOUND"
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
972
1012
|
validateOperationContentReadOptions(item.contentType, input);
|
|
973
1013
|
return applyContentReadOptions(
|
|
974
1014
|
operationContentItem(serializeContentItemWithType(item)),
|
|
@@ -1093,12 +1133,14 @@ function createCMSOperations(adapter, options) {
|
|
|
1093
1133
|
contentTypeId: contentType.id,
|
|
1094
1134
|
slug,
|
|
1095
1135
|
data: JSON.stringify(validatedData),
|
|
1136
|
+
publishAt: context.input.body.publishAt ? new Date(context.input.body.publishAt) : null,
|
|
1096
1137
|
...context.identity ? { authorId: context.identity.id } : {},
|
|
1097
1138
|
createdAt: /* @__PURE__ */ new Date(),
|
|
1098
1139
|
updatedAt: /* @__PURE__ */ new Date()
|
|
1099
1140
|
}
|
|
1100
1141
|
});
|
|
1101
1142
|
await syncRelations(tx, item.id, relationPlan.relationIds);
|
|
1143
|
+
await recordContentRevision(tx, item, context.identity?.id);
|
|
1102
1144
|
const joined = await getRecordOrThrow(
|
|
1103
1145
|
tx,
|
|
1104
1146
|
ensureSynced,
|
|
@@ -1120,68 +1162,103 @@ function createCMSOperations(adapter, options) {
|
|
|
1120
1162
|
await notifyError(error, "create", createContext(context));
|
|
1121
1163
|
}
|
|
1122
1164
|
});
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1165
|
+
const executeUpdateContentItem = async (context, replaceData = false) => {
|
|
1166
|
+
const lifecycleContext = updateContext(context);
|
|
1167
|
+
const contentType = await getContentTypeOrThrow(
|
|
1168
|
+
adapter,
|
|
1169
|
+
ensureSynced,
|
|
1170
|
+
context.facts.contentType
|
|
1171
|
+
);
|
|
1172
|
+
const rawSlug = context.input.body.slug;
|
|
1173
|
+
const slug = rawSlug ? slugify(rawSlug) : void 0;
|
|
1174
|
+
if (rawSlug && !slug) {
|
|
1175
|
+
throw new CMSOperationError(
|
|
1176
|
+
400,
|
|
1177
|
+
"Invalid slug: must contain at least one alphanumeric character",
|
|
1178
|
+
"INVALID_SLUG"
|
|
1132
1179
|
);
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1180
|
+
}
|
|
1181
|
+
const bodyData = context.input.body.data;
|
|
1182
|
+
const relationAuthorization = bodyData ? await recheckRelationWriteAuthorization(
|
|
1183
|
+
context.input,
|
|
1184
|
+
contentType.slug,
|
|
1185
|
+
bodyData
|
|
1186
|
+
) : void 0;
|
|
1187
|
+
const authorizedRecord = await getRecordOrThrow(
|
|
1188
|
+
adapter,
|
|
1189
|
+
ensureSynced,
|
|
1190
|
+
context.facts.contentType,
|
|
1191
|
+
context.facts.recordId
|
|
1192
|
+
);
|
|
1193
|
+
assertRecordFacts(authorizedRecord, context.facts);
|
|
1194
|
+
if (slug && slug !== authorizedRecord.slug) {
|
|
1195
|
+
const duplicate = await adapter.findOne({
|
|
1196
|
+
model: "contentItem",
|
|
1197
|
+
where: [
|
|
1198
|
+
{
|
|
1199
|
+
field: "contentTypeId",
|
|
1200
|
+
value: contentType.id,
|
|
1201
|
+
operator: "eq"
|
|
1202
|
+
},
|
|
1203
|
+
{ field: "slug", value: slug, operator: "eq" }
|
|
1204
|
+
]
|
|
1143
1205
|
});
|
|
1144
|
-
if (
|
|
1145
|
-
const authorization = await deriveRelationWriteAuthorization(
|
|
1146
|
-
adapter,
|
|
1147
|
-
await getContentTypeOrThrow(adapter, ensureSynced, facts.contentType),
|
|
1148
|
-
input.body.data,
|
|
1149
|
-
ensureSynced
|
|
1150
|
-
);
|
|
1151
|
-
relationWriteAuthorizationSnapshots.set(input, authorization);
|
|
1152
|
-
return [contentTypePermission, ...authorization.permissions];
|
|
1153
|
-
},
|
|
1154
|
-
execute: async (context) => {
|
|
1155
|
-
const lifecycleContext = updateContext(context);
|
|
1156
|
-
const contentType = await getContentTypeOrThrow(
|
|
1157
|
-
adapter,
|
|
1158
|
-
ensureSynced,
|
|
1159
|
-
context.facts.contentType
|
|
1160
|
-
);
|
|
1161
|
-
const rawSlug = context.input.body.slug;
|
|
1162
|
-
const slug = rawSlug ? slugify(rawSlug) : void 0;
|
|
1163
|
-
if (rawSlug && !slug) {
|
|
1206
|
+
if (duplicate) {
|
|
1164
1207
|
throw new CMSOperationError(
|
|
1165
|
-
|
|
1166
|
-
"
|
|
1167
|
-
"
|
|
1208
|
+
409,
|
|
1209
|
+
"Content item with this slug already exists",
|
|
1210
|
+
"DUPLICATE_SLUG"
|
|
1168
1211
|
);
|
|
1169
1212
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1213
|
+
}
|
|
1214
|
+
const relationPlan = bodyData ? await planRelationsInData(
|
|
1215
|
+
adapter,
|
|
1216
|
+
contentType,
|
|
1217
|
+
bodyData,
|
|
1218
|
+
ensureSynced,
|
|
1219
|
+
context.identity?.id
|
|
1220
|
+
) : void 0;
|
|
1221
|
+
const currentData = authorizedRecord.data ? JSON.parse(authorizedRecord.data) : {};
|
|
1222
|
+
const validation = getContentTypeZodSchema(contentType).safeParse({
|
|
1223
|
+
...replaceData ? {} : currentData,
|
|
1224
|
+
...relationPlan?.processedData ?? {}
|
|
1225
|
+
});
|
|
1226
|
+
if (!validation.success) {
|
|
1227
|
+
throw new CMSOperationError(
|
|
1228
|
+
400,
|
|
1229
|
+
"Validation failed",
|
|
1230
|
+
"RECORD_VALIDATION_FAILED",
|
|
1231
|
+
validation.error.issues
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
const mergedData = validation.data;
|
|
1235
|
+
await runBeforeHook(
|
|
1236
|
+
() => hooks?.onBeforeUpdateContent?.(
|
|
1237
|
+
context.facts.recordId,
|
|
1238
|
+
mergedData,
|
|
1239
|
+
lifecycleContext
|
|
1240
|
+
),
|
|
1241
|
+
"Update operation denied"
|
|
1242
|
+
);
|
|
1243
|
+
return adapter.transaction(async (tx) => {
|
|
1244
|
+
if (bodyData && relationAuthorization) {
|
|
1245
|
+
await assertCurrentRelationWriteAuthorization(
|
|
1246
|
+
tx,
|
|
1247
|
+
relationAuthorization,
|
|
1248
|
+
contentType.slug,
|
|
1249
|
+
bodyData
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
const current = await getRecordOrThrow(
|
|
1253
|
+
tx,
|
|
1178
1254
|
ensureSynced,
|
|
1179
1255
|
context.facts.contentType,
|
|
1180
1256
|
context.facts.recordId
|
|
1181
1257
|
);
|
|
1182
|
-
assertRecordFacts(
|
|
1183
|
-
|
|
1184
|
-
|
|
1258
|
+
assertRecordFacts(current, context.facts);
|
|
1259
|
+
assertRecordSnapshot(current, authorizedRecord);
|
|
1260
|
+
if (slug && slug !== current.slug) {
|
|
1261
|
+
const duplicate = await tx.findOne({
|
|
1185
1262
|
model: "contentItem",
|
|
1186
1263
|
where: [
|
|
1187
1264
|
{
|
|
@@ -1200,132 +1277,453 @@ function createCMSOperations(adapter, options) {
|
|
|
1200
1277
|
);
|
|
1201
1278
|
}
|
|
1202
1279
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1280
|
+
if (relationPlan) await applyRelationWritePlan(tx, relationPlan);
|
|
1281
|
+
const update = {
|
|
1282
|
+
updatedAt: new Date(
|
|
1283
|
+
Math.max(Date.now(), current.updatedAt.getTime() + 1)
|
|
1284
|
+
)
|
|
1285
|
+
};
|
|
1286
|
+
if (context.input.body.publishAt !== void 0) {
|
|
1287
|
+
update.publishAt = context.input.body.publishAt ? new Date(context.input.body.publishAt) : null;
|
|
1288
|
+
}
|
|
1289
|
+
if (slug) update.slug = slug;
|
|
1290
|
+
if (bodyData) update.data = JSON.stringify(mergedData);
|
|
1291
|
+
const matched = await tx.updateMany({
|
|
1292
|
+
model: "contentItem",
|
|
1293
|
+
where: [
|
|
1294
|
+
{
|
|
1295
|
+
field: "id",
|
|
1296
|
+
value: context.facts.recordId,
|
|
1297
|
+
operator: "eq"
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
field: "contentTypeId",
|
|
1301
|
+
value: contentType.id,
|
|
1302
|
+
operator: "eq"
|
|
1303
|
+
},
|
|
1304
|
+
{
|
|
1305
|
+
field: "authorId",
|
|
1306
|
+
value: context.facts.authorId ?? null,
|
|
1307
|
+
operator: "eq"
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
field: "updatedAt",
|
|
1311
|
+
value: current.updatedAt,
|
|
1312
|
+
operator: "eq"
|
|
1313
|
+
}
|
|
1314
|
+
],
|
|
1315
|
+
update
|
|
1214
1316
|
});
|
|
1215
|
-
if (!
|
|
1216
|
-
throw
|
|
1217
|
-
400,
|
|
1218
|
-
"Validation failed",
|
|
1219
|
-
"RECORD_VALIDATION_FAILED",
|
|
1220
|
-
validation.error.issues
|
|
1221
|
-
);
|
|
1317
|
+
if (!didUpdateContentItem(matched, context.facts.recordId)) {
|
|
1318
|
+
throw staleRecordError();
|
|
1222
1319
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1320
|
+
if (replaceData) {
|
|
1321
|
+
await tx.deleteMany({
|
|
1322
|
+
model: "contentRelation",
|
|
1323
|
+
where: [{ field: "sourceId", value: current.id, operator: "eq" }]
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
if (relationPlan) {
|
|
1327
|
+
await syncRelations(
|
|
1328
|
+
tx,
|
|
1226
1329
|
context.facts.recordId,
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1330
|
+
relationPlan.relationIds
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
const updated = await getRecordOrThrow(
|
|
1334
|
+
tx,
|
|
1335
|
+
ensureSynced,
|
|
1336
|
+
context.facts.contentType,
|
|
1337
|
+
context.facts.recordId
|
|
1231
1338
|
);
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1339
|
+
await recordContentRevision(tx, updated, context.identity?.id);
|
|
1340
|
+
return operationContentItem(serializeContentItemWithType(updated));
|
|
1341
|
+
});
|
|
1342
|
+
};
|
|
1343
|
+
const authorizeUpdateRelations = async ({
|
|
1344
|
+
input,
|
|
1345
|
+
facts
|
|
1346
|
+
}) => {
|
|
1347
|
+
const contentTypePermission = cmsPermissions.contentType.read({
|
|
1348
|
+
contentType: facts.contentType
|
|
1349
|
+
});
|
|
1350
|
+
if (!input.body.data) return [contentTypePermission];
|
|
1351
|
+
const authorization = await deriveRelationWriteAuthorization(
|
|
1352
|
+
adapter,
|
|
1353
|
+
await getContentTypeOrThrow(adapter, ensureSynced, facts.contentType),
|
|
1354
|
+
input.body.data,
|
|
1355
|
+
ensureSynced
|
|
1356
|
+
);
|
|
1357
|
+
relationWriteAuthorizationSnapshots.set(input, authorization);
|
|
1358
|
+
return [contentTypePermission, ...authorization.permissions];
|
|
1359
|
+
};
|
|
1360
|
+
const updateFacts = async ({
|
|
1361
|
+
input
|
|
1362
|
+
}) => {
|
|
1363
|
+
const item = await getRecordOrThrow(
|
|
1364
|
+
adapter,
|
|
1365
|
+
ensureSynced,
|
|
1366
|
+
input.typeSlug,
|
|
1367
|
+
input.id
|
|
1368
|
+
);
|
|
1369
|
+
const facts = recordFacts(item);
|
|
1370
|
+
return {
|
|
1371
|
+
contentType: facts.contentType,
|
|
1372
|
+
recordId: facts.recordId ?? input.id,
|
|
1373
|
+
...facts.authorId ? { authorId: facts.authorId } : {}
|
|
1374
|
+
};
|
|
1375
|
+
};
|
|
1376
|
+
const afterUpdateContentItem = async (context) => {
|
|
1377
|
+
const base = updateContext(context);
|
|
1378
|
+
const afterUpdateContent = hooks?.onAfterUpdateContent;
|
|
1379
|
+
await afterUpdateContent?.(
|
|
1380
|
+
context.result,
|
|
1381
|
+
Object.freeze({ ...base, result: context.result })
|
|
1382
|
+
);
|
|
1383
|
+
};
|
|
1384
|
+
const updateContentItem = defineOperation({
|
|
1385
|
+
input: UpdateContentItemInputSchema,
|
|
1386
|
+
permission: cmsPermissions.record.update,
|
|
1387
|
+
facts: updateFacts,
|
|
1388
|
+
additionalPermissions: authorizeUpdateRelations,
|
|
1389
|
+
execute: (context) => executeUpdateContentItem(context),
|
|
1390
|
+
after: afterUpdateContentItem,
|
|
1391
|
+
onError: async ({ error, ...context }) => {
|
|
1392
|
+
await notifyError(error, "update", updateContext(context));
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
const dispatchDueContent = defineOperation({
|
|
1396
|
+
input: z.object({
|
|
1397
|
+
limit: z.number().int().min(1).max(100).default(20),
|
|
1398
|
+
after: z.string().min(1).optional()
|
|
1399
|
+
}),
|
|
1400
|
+
permission: cmsPermissions.contentType.read,
|
|
1401
|
+
facts: async ({ request }) => {
|
|
1402
|
+
if (request)
|
|
1403
|
+
throw new CMSOperationError(
|
|
1404
|
+
403,
|
|
1405
|
+
"Publication dispatch requires trusted execution",
|
|
1406
|
+
"TRUSTED_OPERATION_REQUIRED"
|
|
1407
|
+
);
|
|
1408
|
+
if (adapter.id !== "memory" && typeof adapter.options?.adapterConfig.transaction !== "function") {
|
|
1409
|
+
throw new CMSOperationError(
|
|
1410
|
+
500,
|
|
1411
|
+
"Publication requires an adapter with isolated transaction support",
|
|
1412
|
+
"ATOMIC_TRANSACTION_REQUIRED"
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
await ensureSynced();
|
|
1416
|
+
return {};
|
|
1417
|
+
},
|
|
1418
|
+
execute: async ({ input }) => {
|
|
1419
|
+
const run = async () => {
|
|
1420
|
+
const result = {
|
|
1421
|
+
scanned: 0,
|
|
1422
|
+
published: [],
|
|
1423
|
+
skipped: [],
|
|
1424
|
+
failures: [],
|
|
1425
|
+
nextCursor: null
|
|
1426
|
+
};
|
|
1427
|
+
const now = /* @__PURE__ */ new Date();
|
|
1428
|
+
const types = /* @__PURE__ */ new Map();
|
|
1429
|
+
for (const definition of options.contentTypes ?? []) {
|
|
1430
|
+
if (!definition.publication) continue;
|
|
1431
|
+
const type = await getContentTypeOrThrow(
|
|
1432
|
+
adapter,
|
|
1433
|
+
ensureSynced,
|
|
1434
|
+
definition.slug
|
|
1239
1435
|
);
|
|
1436
|
+
types.set(type.id, { type, policy: definition.publication });
|
|
1240
1437
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1438
|
+
if (!types.size) return result;
|
|
1439
|
+
const dueWhere = [
|
|
1440
|
+
{
|
|
1441
|
+
field: "contentTypeId",
|
|
1442
|
+
value: [...types.keys()],
|
|
1443
|
+
operator: "in"
|
|
1444
|
+
},
|
|
1445
|
+
{ field: "publishAt", value: null, operator: "ne" },
|
|
1446
|
+
{ field: "publishAt", value: now, operator: "lte" }
|
|
1447
|
+
];
|
|
1448
|
+
const memory = adapter.id === "memory" || adapter.options?.adapterConfig.adapterId === "memory";
|
|
1449
|
+
let offset = 0;
|
|
1450
|
+
if (memory && input.after) {
|
|
1451
|
+
let upper = await adapter.count({
|
|
1251
1452
|
model: "contentItem",
|
|
1252
|
-
where:
|
|
1253
|
-
{
|
|
1254
|
-
field: "contentTypeId",
|
|
1255
|
-
value: contentType.id,
|
|
1256
|
-
operator: "eq"
|
|
1257
|
-
},
|
|
1258
|
-
{ field: "slug", value: slug, operator: "eq" }
|
|
1259
|
-
]
|
|
1453
|
+
where: dueWhere
|
|
1260
1454
|
});
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1455
|
+
while (offset < upper) {
|
|
1456
|
+
const middle = Math.floor((offset + upper) / 2);
|
|
1457
|
+
const [probe] = await adapter.findMany({
|
|
1458
|
+
model: "contentItem",
|
|
1459
|
+
where: dueWhere,
|
|
1460
|
+
select: ["id"],
|
|
1461
|
+
sortBy: { field: "id", direction: "asc" },
|
|
1462
|
+
limit: 1,
|
|
1463
|
+
offset: middle
|
|
1464
|
+
});
|
|
1465
|
+
if (probe && probe.id.localeCompare(input.after) <= 0)
|
|
1466
|
+
offset = middle + 1;
|
|
1467
|
+
else upper = middle;
|
|
1267
1468
|
}
|
|
1268
1469
|
}
|
|
1269
|
-
|
|
1270
|
-
const update = { updatedAt: /* @__PURE__ */ new Date() };
|
|
1271
|
-
if (slug) update.slug = slug;
|
|
1272
|
-
if (bodyData) update.data = JSON.stringify(mergedData);
|
|
1273
|
-
const matched = await tx.updateMany({
|
|
1470
|
+
const candidates = await adapter.findMany({
|
|
1274
1471
|
model: "contentItem",
|
|
1275
1472
|
where: [
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
value: context.facts.recordId,
|
|
1279
|
-
operator: "eq"
|
|
1280
|
-
},
|
|
1281
|
-
{
|
|
1282
|
-
field: "contentTypeId",
|
|
1283
|
-
value: contentType.id,
|
|
1284
|
-
operator: "eq"
|
|
1285
|
-
},
|
|
1286
|
-
{
|
|
1287
|
-
field: "authorId",
|
|
1288
|
-
value: context.facts.authorId ?? null,
|
|
1289
|
-
operator: "eq"
|
|
1290
|
-
},
|
|
1291
|
-
{
|
|
1292
|
-
field: "updatedAt",
|
|
1293
|
-
value: current.updatedAt,
|
|
1294
|
-
operator: "eq"
|
|
1295
|
-
}
|
|
1473
|
+
...dueWhere,
|
|
1474
|
+
...!memory && input.after ? [{ field: "id", value: input.after, operator: "gt" }] : []
|
|
1296
1475
|
],
|
|
1297
|
-
|
|
1476
|
+
limit: input.limit,
|
|
1477
|
+
offset,
|
|
1478
|
+
sortBy: { field: "id", direction: "asc" }
|
|
1298
1479
|
});
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1480
|
+
result.nextCursor = candidates.length === input.limit ? candidates.at(-1).id : null;
|
|
1481
|
+
for (const candidate of candidates) {
|
|
1482
|
+
const { type, policy } = types.get(candidate.contentTypeId);
|
|
1483
|
+
result.scanned += 1;
|
|
1484
|
+
let committed = false;
|
|
1485
|
+
let lifecycle;
|
|
1486
|
+
try {
|
|
1487
|
+
if (!candidate.publishAt || candidate.publishAt > now || JSON.parse(candidate.data)[policy.stateField] !== policy.scheduledValue) {
|
|
1488
|
+
result.skipped.push(candidate.id);
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
const data = {
|
|
1492
|
+
...JSON.parse(candidate.data),
|
|
1493
|
+
[policy.stateField]: policy.publishedValue
|
|
1494
|
+
};
|
|
1495
|
+
const context = {
|
|
1496
|
+
input: {
|
|
1497
|
+
typeSlug: type.slug,
|
|
1498
|
+
id: candidate.id,
|
|
1499
|
+
body: { data, publishAt: null }
|
|
1500
|
+
},
|
|
1501
|
+
facts: {
|
|
1502
|
+
contentType: type.slug,
|
|
1503
|
+
recordId: candidate.id,
|
|
1504
|
+
...candidate.authorId ? { authorId: candidate.authorId } : {}
|
|
1505
|
+
},
|
|
1506
|
+
identity: null
|
|
1507
|
+
};
|
|
1508
|
+
freezeCMSOperationData(context.input);
|
|
1509
|
+
freezeCMSOperationData(context.facts);
|
|
1510
|
+
lifecycle = context;
|
|
1511
|
+
const validation = getContentTypeZodSchema(type).safeParse(data);
|
|
1512
|
+
if (!validation.success)
|
|
1513
|
+
throw new CMSOperationError(
|
|
1514
|
+
400,
|
|
1515
|
+
"Validation failed",
|
|
1516
|
+
"RECORD_VALIDATION_FAILED",
|
|
1517
|
+
validation.error.issues
|
|
1518
|
+
);
|
|
1519
|
+
await deriveRelationWriteAuthorization(
|
|
1520
|
+
adapter,
|
|
1521
|
+
type,
|
|
1522
|
+
data,
|
|
1523
|
+
ensureSynced
|
|
1524
|
+
);
|
|
1525
|
+
await runBeforeHook(
|
|
1526
|
+
() => hooks?.onBeforeUpdateContent?.(
|
|
1527
|
+
candidate.id,
|
|
1528
|
+
validation.data,
|
|
1529
|
+
updateContext(context)
|
|
1530
|
+
),
|
|
1531
|
+
"Publication denied"
|
|
1532
|
+
);
|
|
1533
|
+
const item = await adapter.transaction(async (tx) => {
|
|
1534
|
+
const current = await getRecordOrThrow(
|
|
1535
|
+
tx,
|
|
1536
|
+
ensureSynced,
|
|
1537
|
+
type.slug,
|
|
1538
|
+
candidate.id
|
|
1539
|
+
);
|
|
1540
|
+
assertRecordSnapshot(current, {
|
|
1541
|
+
...candidate,
|
|
1542
|
+
contentType: type
|
|
1543
|
+
});
|
|
1544
|
+
await deriveRelationWriteAuthorization(
|
|
1545
|
+
tx,
|
|
1546
|
+
current.contentType,
|
|
1547
|
+
data,
|
|
1548
|
+
ensureSynced
|
|
1549
|
+
);
|
|
1550
|
+
const currentValidation = getContentTypeZodSchema(
|
|
1551
|
+
current.contentType
|
|
1552
|
+
).safeParse(data);
|
|
1553
|
+
if (!currentValidation.success)
|
|
1554
|
+
throw new CMSOperationError(
|
|
1555
|
+
400,
|
|
1556
|
+
"Validation failed",
|
|
1557
|
+
"RECORD_VALIDATION_FAILED",
|
|
1558
|
+
currentValidation.error.issues
|
|
1559
|
+
);
|
|
1560
|
+
const matched = await tx.updateMany({
|
|
1561
|
+
model: "contentItem",
|
|
1562
|
+
where: [
|
|
1563
|
+
{ field: "id", value: candidate.id, operator: "eq" },
|
|
1564
|
+
{ field: "contentTypeId", value: type.id, operator: "eq" },
|
|
1565
|
+
{ field: "data", value: candidate.data, operator: "eq" },
|
|
1566
|
+
{
|
|
1567
|
+
field: "publishAt",
|
|
1568
|
+
value: candidate.publishAt,
|
|
1569
|
+
operator: "eq"
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
field: "updatedAt",
|
|
1573
|
+
value: candidate.updatedAt,
|
|
1574
|
+
operator: "eq"
|
|
1575
|
+
}
|
|
1576
|
+
],
|
|
1577
|
+
update: {
|
|
1578
|
+
data: JSON.stringify(currentValidation.data),
|
|
1579
|
+
publishAt: null,
|
|
1580
|
+
updatedAt: new Date(
|
|
1581
|
+
Math.max(Date.now(), current.updatedAt.getTime() + 1)
|
|
1582
|
+
)
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
if (!didUpdateContentItem(matched, candidate.id))
|
|
1586
|
+
throw staleRecordError();
|
|
1587
|
+
const updated = await getRecordOrThrow(
|
|
1588
|
+
tx,
|
|
1589
|
+
ensureSynced,
|
|
1590
|
+
type.slug,
|
|
1591
|
+
candidate.id
|
|
1592
|
+
);
|
|
1593
|
+
await recordContentRevision(tx, updated);
|
|
1594
|
+
return operationContentItem(
|
|
1595
|
+
serializeContentItemWithType(updated)
|
|
1596
|
+
);
|
|
1597
|
+
});
|
|
1598
|
+
committed = true;
|
|
1599
|
+
freezeCMSOperationData(item);
|
|
1600
|
+
result.published.push(candidate.id);
|
|
1601
|
+
await afterUpdateContentItem({ ...context, result: item });
|
|
1602
|
+
const afterPublish = hooks?.onAfterPublishContent;
|
|
1603
|
+
await afterPublish?.(
|
|
1604
|
+
item,
|
|
1605
|
+
Object.freeze({ ...updateContext(context), result: item })
|
|
1606
|
+
);
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
if (!committed && error instanceof CMSOperationError && (error.code === "RECORD_STATE_CHANGED" || error.code === "RECORD_NOT_FOUND")) {
|
|
1609
|
+
result.skipped.push(candidate.id);
|
|
1610
|
+
} else {
|
|
1611
|
+
result.failures.push({
|
|
1612
|
+
id: candidate.id,
|
|
1613
|
+
message: normalizeOperationError(error, "Publication failed").message,
|
|
1614
|
+
published: committed
|
|
1615
|
+
});
|
|
1616
|
+
if (lifecycle) {
|
|
1617
|
+
try {
|
|
1618
|
+
await notifyError(error, "update", updateContext(lifecycle));
|
|
1619
|
+
} catch {
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1308
1624
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1625
|
+
return result;
|
|
1626
|
+
};
|
|
1627
|
+
const previous = dispatchQueues.get(adapter) ?? Promise.resolve();
|
|
1628
|
+
const pending = previous.catch(() => {
|
|
1629
|
+
}).then(run);
|
|
1630
|
+
dispatchQueues.set(adapter, pending);
|
|
1631
|
+
try {
|
|
1632
|
+
return await pending;
|
|
1633
|
+
} finally {
|
|
1634
|
+
if (dispatchQueues.get(adapter) === pending)
|
|
1635
|
+
dispatchQueues.delete(adapter);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
});
|
|
1639
|
+
const listContentRevisions = defineOperation({
|
|
1640
|
+
input: CMSContentItemParamsSchema.extend(CMSRevisionsQuerySchema.shape),
|
|
1641
|
+
permission: cmsPermissions.record.read,
|
|
1642
|
+
facts: async ({ input }) => recordFacts(
|
|
1643
|
+
await getRecordOrThrow(adapter, ensureSynced, input.typeSlug, input.id)
|
|
1644
|
+
),
|
|
1645
|
+
additionalPermissions: ({ facts }) => [
|
|
1646
|
+
cmsPermissions.contentType.read({ contentType: facts.contentType })
|
|
1647
|
+
],
|
|
1648
|
+
execute: async ({ input, facts }) => {
|
|
1649
|
+
assertRecordFacts(
|
|
1650
|
+
await getRecordOrThrow(adapter, ensureSynced, input.typeSlug, input.id),
|
|
1651
|
+
facts
|
|
1652
|
+
);
|
|
1653
|
+
const where = [
|
|
1654
|
+
{ field: "contentItemId", value: input.id, operator: "eq" }
|
|
1655
|
+
];
|
|
1656
|
+
const revisions = await adapter.findMany({
|
|
1657
|
+
model: "contentRevision",
|
|
1658
|
+
where,
|
|
1659
|
+
sortBy: { field: "version", direction: "desc" },
|
|
1660
|
+
limit: 20,
|
|
1661
|
+
offset: input.offset
|
|
1662
|
+
});
|
|
1663
|
+
return {
|
|
1664
|
+
items: revisions.map((revision) => ({
|
|
1665
|
+
...revision,
|
|
1666
|
+
createdAt: revision.createdAt.toISOString(),
|
|
1667
|
+
publishAt: revision.publishAt?.toISOString() ?? null
|
|
1668
|
+
})),
|
|
1669
|
+
total: await adapter.count({ model: "contentRevision", where })
|
|
1670
|
+
};
|
|
1671
|
+
},
|
|
1672
|
+
onError: ({ error, ...context }) => notifyError(
|
|
1673
|
+
error,
|
|
1674
|
+
"list",
|
|
1675
|
+
genericContext(context, context.facts.contentType)
|
|
1676
|
+
)
|
|
1677
|
+
});
|
|
1678
|
+
const restoreInputs = /* @__PURE__ */ new WeakMap();
|
|
1679
|
+
const restoreContentRevision = defineOperation({
|
|
1680
|
+
input: CMSRevisionParamsSchema,
|
|
1681
|
+
permission: cmsPermissions.record.update,
|
|
1682
|
+
facts: updateFacts,
|
|
1683
|
+
additionalPermissions: async ({ input, facts }) => {
|
|
1684
|
+
const revision = await adapter.findOne({
|
|
1685
|
+
model: "contentRevision",
|
|
1686
|
+
where: [
|
|
1687
|
+
{ field: "id", value: input.revisionId, operator: "eq" },
|
|
1688
|
+
{ field: "contentItemId", value: input.id, operator: "eq" }
|
|
1689
|
+
]
|
|
1690
|
+
});
|
|
1691
|
+
if (!revision)
|
|
1692
|
+
throw new CMSOperationError(
|
|
1693
|
+
404,
|
|
1694
|
+
"Revision not found",
|
|
1695
|
+
"REVISION_NOT_FOUND"
|
|
1314
1696
|
);
|
|
1315
|
-
|
|
1697
|
+
const updateInput = UpdateContentItemInputSchema.parse({
|
|
1698
|
+
typeSlug: input.typeSlug,
|
|
1699
|
+
id: input.id,
|
|
1700
|
+
body: {
|
|
1701
|
+
slug: revision.slug,
|
|
1702
|
+
data: JSON.parse(revision.data),
|
|
1703
|
+
publishAt: revision.publishAt?.toISOString() ?? null
|
|
1704
|
+
}
|
|
1316
1705
|
});
|
|
1706
|
+
freezeCMSOperationData(updateInput);
|
|
1707
|
+
restoreInputs.set(input, updateInput);
|
|
1708
|
+
return [
|
|
1709
|
+
cmsPermissions.record.read({ ...facts, scope: "record" }),
|
|
1710
|
+
...await authorizeUpdateRelations({ input: updateInput, facts })
|
|
1711
|
+
];
|
|
1317
1712
|
},
|
|
1713
|
+
execute: async (context) => executeUpdateContentItem(
|
|
1714
|
+
{ ...context, input: restoreInputs.get(context.input) },
|
|
1715
|
+
true
|
|
1716
|
+
),
|
|
1318
1717
|
after: async (context) => {
|
|
1319
|
-
const
|
|
1320
|
-
|
|
1321
|
-
await
|
|
1322
|
-
context.result,
|
|
1323
|
-
Object.freeze({ ...base, result: context.result })
|
|
1324
|
-
);
|
|
1718
|
+
const input = restoreInputs.get(context.input);
|
|
1719
|
+
restoreInputs.delete(context.input);
|
|
1720
|
+
await afterUpdateContentItem({ ...context, input });
|
|
1325
1721
|
},
|
|
1326
|
-
onError:
|
|
1327
|
-
|
|
1328
|
-
|
|
1722
|
+
onError: ({ error, ...context }) => notifyError(
|
|
1723
|
+
error,
|
|
1724
|
+
"update",
|
|
1725
|
+
genericContext(context, context.facts.contentType)
|
|
1726
|
+
)
|
|
1329
1727
|
});
|
|
1330
1728
|
const deleteContentItem = defineOperation({
|
|
1331
1729
|
input: DeleteContentItemInputSchema,
|
|
@@ -1492,7 +1890,8 @@ function createCMSOperations(adapter, options) {
|
|
|
1492
1890
|
input: ListContentByRelationInputSchema,
|
|
1493
1891
|
permission: cmsPermissions.record.read,
|
|
1494
1892
|
facts: async ({ input }) => collectionFacts(
|
|
1495
|
-
(await getContentTypeOrThrow(adapter, ensureSynced, input.typeSlug)).slug
|
|
1893
|
+
(await getContentTypeOrThrow(adapter, ensureSynced, input.typeSlug)).slug,
|
|
1894
|
+
publicationFor(input.typeSlug, input.query.view) ? "content" : void 0
|
|
1496
1895
|
),
|
|
1497
1896
|
additionalPermissions: async ({ input, facts }) => {
|
|
1498
1897
|
const sourceContentType = await getContentTypeOrThrow(
|
|
@@ -1524,7 +1923,8 @@ function createCMSOperations(adapter, options) {
|
|
|
1524
1923
|
const result = await getContentItemsByRelationForType(
|
|
1525
1924
|
adapter,
|
|
1526
1925
|
sourceContentType,
|
|
1527
|
-
input.query
|
|
1926
|
+
input.query,
|
|
1927
|
+
publicationFor(facts.contentType, input.query.view)
|
|
1528
1928
|
);
|
|
1529
1929
|
return {
|
|
1530
1930
|
...result,
|
|
@@ -1661,7 +2061,10 @@ function createCMSOperations(adapter, options) {
|
|
|
1661
2061
|
ensureSynced,
|
|
1662
2062
|
input.sourceType
|
|
1663
2063
|
);
|
|
1664
|
-
return collectionFacts(
|
|
2064
|
+
return collectionFacts(
|
|
2065
|
+
sourceType.slug,
|
|
2066
|
+
publicationFor(sourceType.slug, input.query.view) ? "content" : void 0
|
|
2067
|
+
);
|
|
1665
2068
|
},
|
|
1666
2069
|
additionalPermissions: async ({ input, facts }) => {
|
|
1667
2070
|
const authorization = await deriveInverseSourceAuthorization(
|
|
@@ -1726,7 +2129,8 @@ function createCMSOperations(adapter, options) {
|
|
|
1726
2129
|
targetId: input.query.itemId,
|
|
1727
2130
|
limit: input.query.limit,
|
|
1728
2131
|
offset: input.query.offset
|
|
1729
|
-
}
|
|
2132
|
+
},
|
|
2133
|
+
publicationFor(facts.contentType, input.query.view)
|
|
1730
2134
|
);
|
|
1731
2135
|
return {
|
|
1732
2136
|
...result,
|
|
@@ -1751,6 +2155,9 @@ function createCMSOperations(adapter, options) {
|
|
|
1751
2155
|
getContentItem,
|
|
1752
2156
|
createContentItem,
|
|
1753
2157
|
updateContentItem,
|
|
2158
|
+
dispatchDueContent,
|
|
2159
|
+
listContentRevisions,
|
|
2160
|
+
restoreContentRevision,
|
|
1754
2161
|
deleteContentItem,
|
|
1755
2162
|
getContentItemPopulated,
|
|
1756
2163
|
listContentByRelation,
|
|
@@ -1759,4 +2166,4 @@ function createCMSOperations(adapter, options) {
|
|
|
1759
2166
|
};
|
|
1760
2167
|
}
|
|
1761
2168
|
|
|
1762
|
-
export { CMSContentItemParamsSchema, CMSContentTypeParamsSchema, CMSCreateContentItemBodySchema, CMSOperationError, CMSUpdateContentItemBodySchema, createCMSOperations };
|
|
2169
|
+
export { CMSContentItemParamsSchema, CMSContentTypeParamsSchema, CMSCreateContentItemBodySchema, CMSOperationError, CMSRevisionParamsSchema, CMSRevisionsQuerySchema, CMSUpdateContentItemBodySchema, createCMSOperations };
|