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