@strapi/content-releases 0.0.0-next.56199ab7a5f3320e0debcbe4a24fe0b8cd599e21 → 0.0.0-next.615ae85762cbae9fc80af36685075ef25abd1c88
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/_chunks/{App-YFvVMqB8.js → App-1hHIqUoZ.js} +253 -191
- package/dist/_chunks/App-1hHIqUoZ.js.map +1 -0
- package/dist/_chunks/{App-8J9a-MD5.mjs → App-U6GbyLIE.mjs} +257 -195
- package/dist/_chunks/App-U6GbyLIE.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs +51 -0
- package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js +51 -0
- package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +1 -0
- package/dist/_chunks/{en-MyLPoISH.mjs → en-GqXgfmzl.mjs} +9 -3
- package/dist/_chunks/en-GqXgfmzl.mjs.map +1 -0
- package/dist/_chunks/{en-gYDqKYFd.js → en-bDhIlw-B.js} +9 -3
- package/dist/_chunks/en-bDhIlw-B.js.map +1 -0
- package/dist/_chunks/{index-ej8MzbQl.mjs → index-gkExFBa0.mjs} +92 -29
- package/dist/_chunks/index-gkExFBa0.mjs.map +1 -0
- package/dist/_chunks/{index-vxli-E-l.js → index-l-FvkQlQ.js} +91 -28
- package/dist/_chunks/index-l-FvkQlQ.js.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +324 -120
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +325 -121
- package/dist/server/index.mjs.map +1 -1
- package/package.json +10 -9
- package/dist/_chunks/App-8J9a-MD5.mjs.map +0 -1
- package/dist/_chunks/App-YFvVMqB8.js.map +0 -1
- package/dist/_chunks/en-MyLPoISH.mjs.map +0 -1
- package/dist/_chunks/en-gYDqKYFd.js.map +0 -1
- package/dist/_chunks/index-ej8MzbQl.mjs.map +0 -1
- package/dist/_chunks/index-vxli-E-l.js.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { setCreatorFields, errors, validateYupSchema, yup as yup$1
|
|
1
|
+
import { contentTypes as contentTypes$1, mapAsync, setCreatorFields, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
|
|
2
|
+
import { difference, keys } from "lodash";
|
|
2
3
|
import _ from "lodash/fp";
|
|
3
4
|
import EE from "@strapi/strapi/dist/utils/ee";
|
|
5
|
+
import { scheduleJob } from "node-schedule";
|
|
4
6
|
import * as yup from "yup";
|
|
5
7
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
6
8
|
const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
|
|
@@ -48,40 +50,50 @@ const ACTIONS = [
|
|
|
48
50
|
pluginName: "content-releases"
|
|
49
51
|
}
|
|
50
52
|
];
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
async function deleteActionsOnDisableDraftAndPublish({
|
|
54
|
+
oldContentTypes,
|
|
55
|
+
contentTypes: contentTypes2
|
|
56
|
+
}) {
|
|
57
|
+
if (!oldContentTypes) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
for (const uid in contentTypes2) {
|
|
61
|
+
if (!oldContentTypes[uid]) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const oldContentType = oldContentTypes[uid];
|
|
65
|
+
const contentType = contentTypes2[uid];
|
|
66
|
+
if (contentTypes$1.hasDraftAndPublish(oldContentType) && !contentTypes$1.hasDraftAndPublish(contentType)) {
|
|
67
|
+
await strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: uid }).execute();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
72
|
+
const deletedContentTypes = difference(keys(oldContentTypes), keys(contentTypes2)) ?? [];
|
|
73
|
+
if (deletedContentTypes.length) {
|
|
74
|
+
await mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
|
|
75
|
+
return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
54
79
|
const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
|
|
55
80
|
const register = async ({ strapi: strapi2 }) => {
|
|
56
|
-
if (features$2.isEnabled("cms-content-releases")
|
|
81
|
+
if (features$2.isEnabled("cms-content-releases")) {
|
|
57
82
|
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const destroyContentTypeUpdateListener = strapi2.eventHub.on(
|
|
61
|
-
"content-type.update",
|
|
62
|
-
async ({ contentType }) => {
|
|
63
|
-
if (contentType.schema.options.draftAndPublish === false) {
|
|
64
|
-
await releaseActionService.deleteManyForContentType(contentType.uid);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
);
|
|
68
|
-
eventManager.addDestroyListenerCallback(destroyContentTypeUpdateListener);
|
|
69
|
-
const destroyContentTypeDeleteListener = strapi2.eventHub.on(
|
|
70
|
-
"content-type.delete",
|
|
71
|
-
async ({ contentType }) => {
|
|
72
|
-
await releaseActionService.deleteManyForContentType(contentType.uid);
|
|
73
|
-
}
|
|
74
|
-
);
|
|
75
|
-
eventManager.addDestroyListenerCallback(destroyContentTypeDeleteListener);
|
|
83
|
+
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
|
|
84
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
|
|
76
85
|
}
|
|
77
86
|
};
|
|
87
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
88
|
+
return strapi2.plugin("content-releases").service(name);
|
|
89
|
+
};
|
|
78
90
|
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
79
91
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
80
|
-
if (features$1.isEnabled("cms-content-releases")
|
|
92
|
+
if (features$1.isEnabled("cms-content-releases")) {
|
|
81
93
|
strapi2.db.lifecycles.subscribe({
|
|
82
94
|
afterDelete(event) {
|
|
83
95
|
const { model, result } = event;
|
|
84
|
-
if (model.kind === "collectionType" && model.options
|
|
96
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
85
97
|
const { id } = result;
|
|
86
98
|
strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
87
99
|
where: {
|
|
@@ -97,7 +109,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
97
109
|
*/
|
|
98
110
|
async beforeDeleteMany(event) {
|
|
99
111
|
const { model, params } = event;
|
|
100
|
-
if (model.kind === "collectionType" && model.options
|
|
112
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
101
113
|
const { where } = params;
|
|
102
114
|
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
103
115
|
event.state.entriesToDelete = entriesToDelete;
|
|
@@ -122,6 +134,24 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
122
134
|
}
|
|
123
135
|
}
|
|
124
136
|
});
|
|
137
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
138
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
139
|
+
strapi2.log.error(
|
|
140
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
141
|
+
);
|
|
142
|
+
throw err;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
148
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
149
|
+
const scheduledJobs = getService("scheduling", {
|
|
150
|
+
strapi: strapi2
|
|
151
|
+
}).getAll();
|
|
152
|
+
for (const [, job] of scheduledJobs) {
|
|
153
|
+
job.cancel();
|
|
154
|
+
}
|
|
125
155
|
}
|
|
126
156
|
};
|
|
127
157
|
const schema$1 = {
|
|
@@ -150,6 +180,12 @@ const schema$1 = {
|
|
|
150
180
|
releasedAt: {
|
|
151
181
|
type: "datetime"
|
|
152
182
|
},
|
|
183
|
+
scheduledAt: {
|
|
184
|
+
type: "datetime"
|
|
185
|
+
},
|
|
186
|
+
timezone: {
|
|
187
|
+
type: "string"
|
|
188
|
+
},
|
|
153
189
|
actions: {
|
|
154
190
|
type: "relation",
|
|
155
191
|
relation: "oneToMany",
|
|
@@ -212,15 +248,6 @@ const contentTypes = {
|
|
|
212
248
|
release: release$1,
|
|
213
249
|
"release-action": releaseAction$1
|
|
214
250
|
};
|
|
215
|
-
const createReleaseActionService = ({ strapi: strapi2 }) => ({
|
|
216
|
-
async deleteManyForContentType(contentTypeUid) {
|
|
217
|
-
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
218
|
-
where: {
|
|
219
|
-
target_type: contentTypeUid
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
251
|
const getGroupName = (queryValue) => {
|
|
225
252
|
switch (queryValue) {
|
|
226
253
|
case "contentType":
|
|
@@ -236,10 +263,25 @@ const getGroupName = (queryValue) => {
|
|
|
236
263
|
const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
237
264
|
async create(releaseData, { user }) {
|
|
238
265
|
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
239
|
-
|
|
240
|
-
|
|
266
|
+
const {
|
|
267
|
+
validatePendingReleasesLimit,
|
|
268
|
+
validateUniqueNameForPendingRelease,
|
|
269
|
+
validateScheduledAtIsLaterThanNow
|
|
270
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
271
|
+
await Promise.all([
|
|
272
|
+
validatePendingReleasesLimit(),
|
|
273
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
274
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
275
|
+
]);
|
|
276
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
241
277
|
data: releaseWithCreatorFields
|
|
242
278
|
});
|
|
279
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
|
|
280
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
281
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
282
|
+
}
|
|
283
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
284
|
+
return release2;
|
|
243
285
|
},
|
|
244
286
|
async findOne(id, query = {}) {
|
|
245
287
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
@@ -258,51 +300,66 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
258
300
|
}
|
|
259
301
|
});
|
|
260
302
|
},
|
|
261
|
-
async
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// Find all Releases where the content type entry is present
|
|
268
|
-
actions: {
|
|
269
|
-
target_type: contentTypeUid,
|
|
270
|
-
target_id: entryId
|
|
271
|
-
}
|
|
272
|
-
} : {
|
|
273
|
-
// Find all Releases where the content type entry is not present
|
|
274
|
-
$or: [
|
|
275
|
-
{
|
|
276
|
-
$not: {
|
|
277
|
-
actions: {
|
|
278
|
-
target_type: contentTypeUid,
|
|
279
|
-
target_id: entryId
|
|
280
|
-
}
|
|
281
|
-
}
|
|
303
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
304
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
305
|
+
where: {
|
|
306
|
+
actions: {
|
|
307
|
+
target_type: contentTypeUid,
|
|
308
|
+
target_id: entryId
|
|
282
309
|
},
|
|
283
|
-
{
|
|
284
|
-
|
|
310
|
+
releasedAt: {
|
|
311
|
+
$null: true
|
|
285
312
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
313
|
+
},
|
|
314
|
+
populate: {
|
|
315
|
+
// Filter the action to get only the content type entry
|
|
316
|
+
actions: {
|
|
317
|
+
where: {
|
|
318
|
+
target_type: contentTypeUid,
|
|
319
|
+
target_id: entryId
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
return releases.map((release2) => {
|
|
325
|
+
if (release2.actions?.length) {
|
|
326
|
+
const [actionForEntry] = release2.actions;
|
|
327
|
+
delete release2.actions;
|
|
328
|
+
return {
|
|
329
|
+
...release2,
|
|
330
|
+
action: actionForEntry
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return release2;
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
337
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
338
|
+
where: {
|
|
339
|
+
releasedAt: {
|
|
340
|
+
$null: true
|
|
341
|
+
},
|
|
342
|
+
actions: {
|
|
292
343
|
target_type: contentTypeUid,
|
|
293
344
|
target_id: entryId
|
|
294
345
|
}
|
|
295
346
|
}
|
|
296
|
-
}
|
|
347
|
+
});
|
|
297
348
|
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
298
349
|
where: {
|
|
299
|
-
|
|
350
|
+
$or: [
|
|
351
|
+
{
|
|
352
|
+
id: {
|
|
353
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
actions: null
|
|
358
|
+
}
|
|
359
|
+
],
|
|
300
360
|
releasedAt: {
|
|
301
361
|
$null: true
|
|
302
362
|
}
|
|
303
|
-
},
|
|
304
|
-
populate: {
|
|
305
|
-
...populateAttachedAction
|
|
306
363
|
}
|
|
307
364
|
});
|
|
308
365
|
return releases.map((release2) => {
|
|
@@ -319,6 +376,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
319
376
|
},
|
|
320
377
|
async update(id, releaseData, { user }) {
|
|
321
378
|
const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(releaseData);
|
|
379
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
380
|
+
"release-validation",
|
|
381
|
+
{ strapi: strapi2 }
|
|
382
|
+
);
|
|
383
|
+
await Promise.all([
|
|
384
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
385
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
386
|
+
]);
|
|
322
387
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
323
388
|
if (!release2) {
|
|
324
389
|
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
@@ -334,6 +399,15 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
334
399
|
// @ts-expect-error see above
|
|
335
400
|
data: releaseWithCreatorFields
|
|
336
401
|
});
|
|
402
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
403
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
404
|
+
if (releaseData.scheduledAt) {
|
|
405
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
406
|
+
} else if (release2.scheduledAt) {
|
|
407
|
+
schedulingService.cancel(id);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
337
411
|
return updatedRelease;
|
|
338
412
|
},
|
|
339
413
|
async createAction(releaseId, action) {
|
|
@@ -491,6 +565,11 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
491
565
|
});
|
|
492
566
|
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
493
567
|
});
|
|
568
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
|
|
569
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
570
|
+
await schedulingService.cancel(release2.id);
|
|
571
|
+
}
|
|
572
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
494
573
|
return release2;
|
|
495
574
|
},
|
|
496
575
|
async publish(releaseId) {
|
|
@@ -501,7 +580,9 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
501
580
|
populate: {
|
|
502
581
|
actions: {
|
|
503
582
|
populate: {
|
|
504
|
-
entry:
|
|
583
|
+
entry: {
|
|
584
|
+
fields: ["id"]
|
|
585
|
+
}
|
|
505
586
|
}
|
|
506
587
|
}
|
|
507
588
|
}
|
|
@@ -516,30 +597,80 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
516
597
|
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
517
598
|
throw new errors.ValidationError("No entries to publish");
|
|
518
599
|
}
|
|
519
|
-
const
|
|
600
|
+
const collectionTypeActions = {};
|
|
601
|
+
const singleTypeActions = [];
|
|
520
602
|
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
521
603
|
const contentTypeUid = action.contentType;
|
|
522
|
-
if (
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
604
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
605
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
606
|
+
collectionTypeActions[contentTypeUid] = {
|
|
607
|
+
entriestoPublishIds: [],
|
|
608
|
+
entriesToUnpublishIds: []
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
if (action.type === "publish") {
|
|
612
|
+
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
613
|
+
} else {
|
|
614
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
615
|
+
}
|
|
530
616
|
} else {
|
|
531
|
-
|
|
617
|
+
singleTypeActions.push({
|
|
618
|
+
uid: contentTypeUid,
|
|
619
|
+
action: action.type,
|
|
620
|
+
id: action.entry.id
|
|
621
|
+
});
|
|
532
622
|
}
|
|
533
623
|
}
|
|
534
624
|
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
625
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
535
626
|
await strapi2.db.transaction(async () => {
|
|
536
|
-
for (const
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
627
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
628
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
629
|
+
const entry = await strapi2.entityService.findOne(uid, id, { populate });
|
|
630
|
+
try {
|
|
631
|
+
if (action === "publish") {
|
|
632
|
+
await entityManagerService.publish(entry, uid);
|
|
633
|
+
} else {
|
|
634
|
+
await entityManagerService.unpublish(entry, uid);
|
|
635
|
+
}
|
|
636
|
+
} catch (error) {
|
|
637
|
+
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
638
|
+
;
|
|
639
|
+
else {
|
|
640
|
+
throw error;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
645
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
646
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
647
|
+
const entriesToPublish = await strapi2.entityService.findMany(
|
|
648
|
+
contentTypeUid,
|
|
649
|
+
{
|
|
650
|
+
filters: {
|
|
651
|
+
id: {
|
|
652
|
+
$in: entriestoPublishIds
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
populate
|
|
656
|
+
}
|
|
657
|
+
);
|
|
658
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(
|
|
659
|
+
contentTypeUid,
|
|
660
|
+
{
|
|
661
|
+
filters: {
|
|
662
|
+
id: {
|
|
663
|
+
$in: entriesToUnpublishIds
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
populate
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
if (entriesToPublish.length > 0) {
|
|
670
|
+
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
540
671
|
}
|
|
541
|
-
if (
|
|
542
|
-
await entityManagerService.unpublishMany(
|
|
672
|
+
if (entriesToUnpublish.length > 0) {
|
|
673
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
|
|
543
674
|
}
|
|
544
675
|
}
|
|
545
676
|
});
|
|
@@ -552,6 +683,7 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
552
683
|
releasedAt: /* @__PURE__ */ new Date()
|
|
553
684
|
}
|
|
554
685
|
});
|
|
686
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
555
687
|
return release2;
|
|
556
688
|
},
|
|
557
689
|
async updateAction(actionId, releaseId, update) {
|
|
@@ -637,34 +769,94 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
637
769
|
if (pendingReleasesCount >= maximumPendingReleases) {
|
|
638
770
|
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
639
771
|
}
|
|
772
|
+
},
|
|
773
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
774
|
+
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
775
|
+
filters: {
|
|
776
|
+
releasedAt: {
|
|
777
|
+
$null: true
|
|
778
|
+
},
|
|
779
|
+
name,
|
|
780
|
+
...id && { id: { $ne: id } }
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
const isNameUnique = pendingReleases.length === 0;
|
|
784
|
+
if (!isNameUnique) {
|
|
785
|
+
throw new errors.ValidationError(`Release with name ${name} already exists`);
|
|
786
|
+
}
|
|
787
|
+
},
|
|
788
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
789
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
790
|
+
throw new errors.ValidationError("Scheduled at must be later than now");
|
|
791
|
+
}
|
|
640
792
|
}
|
|
641
793
|
});
|
|
642
|
-
const
|
|
643
|
-
const
|
|
644
|
-
destroyListenerCallbacks: []
|
|
645
|
-
};
|
|
794
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
795
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
646
796
|
return {
|
|
647
|
-
|
|
648
|
-
|
|
797
|
+
async set(releaseId, scheduleDate) {
|
|
798
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
799
|
+
if (!release2) {
|
|
800
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
801
|
+
}
|
|
802
|
+
const job = scheduleJob(scheduleDate, async () => {
|
|
803
|
+
try {
|
|
804
|
+
await getService("release").publish(releaseId);
|
|
805
|
+
} catch (error) {
|
|
806
|
+
}
|
|
807
|
+
this.cancel(releaseId);
|
|
808
|
+
});
|
|
809
|
+
if (scheduledJobs.has(releaseId)) {
|
|
810
|
+
this.cancel(releaseId);
|
|
811
|
+
}
|
|
812
|
+
scheduledJobs.set(releaseId, job);
|
|
813
|
+
return scheduledJobs;
|
|
649
814
|
},
|
|
650
|
-
|
|
651
|
-
if (
|
|
652
|
-
|
|
815
|
+
cancel(releaseId) {
|
|
816
|
+
if (scheduledJobs.has(releaseId)) {
|
|
817
|
+
scheduledJobs.get(releaseId).cancel();
|
|
818
|
+
scheduledJobs.delete(releaseId);
|
|
653
819
|
}
|
|
654
|
-
|
|
655
|
-
|
|
820
|
+
return scheduledJobs;
|
|
821
|
+
},
|
|
822
|
+
getAll() {
|
|
823
|
+
return scheduledJobs;
|
|
824
|
+
},
|
|
825
|
+
/**
|
|
826
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
827
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
828
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
829
|
+
*/
|
|
830
|
+
async syncFromDatabase() {
|
|
831
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
832
|
+
where: {
|
|
833
|
+
scheduledAt: {
|
|
834
|
+
$gte: /* @__PURE__ */ new Date()
|
|
835
|
+
},
|
|
836
|
+
releasedAt: null
|
|
837
|
+
}
|
|
656
838
|
});
|
|
839
|
+
for (const release2 of releases) {
|
|
840
|
+
this.set(release2.id, release2.scheduledAt);
|
|
841
|
+
}
|
|
842
|
+
return scheduledJobs;
|
|
657
843
|
}
|
|
658
844
|
};
|
|
659
845
|
};
|
|
660
846
|
const services = {
|
|
661
847
|
release: createReleaseService,
|
|
662
|
-
"release-action": createReleaseActionService,
|
|
663
848
|
"release-validation": createReleaseValidationService,
|
|
664
|
-
"
|
|
849
|
+
...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
|
|
665
850
|
};
|
|
666
851
|
const RELEASE_SCHEMA = yup.object().shape({
|
|
667
|
-
name: yup.string().trim().required()
|
|
852
|
+
name: yup.string().trim().required(),
|
|
853
|
+
// scheduledAt is a date, but we always receive strings from the client
|
|
854
|
+
scheduledAt: yup.string().nullable(),
|
|
855
|
+
timezone: yup.string().when("scheduledAt", {
|
|
856
|
+
is: (scheduledAt) => !!scheduledAt,
|
|
857
|
+
then: yup.string().required(),
|
|
858
|
+
otherwise: yup.string().nullable()
|
|
859
|
+
})
|
|
668
860
|
}).required().noUnknown();
|
|
669
861
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
670
862
|
const releaseController = {
|
|
@@ -681,9 +873,7 @@ const releaseController = {
|
|
|
681
873
|
const contentTypeUid = query.contentTypeUid;
|
|
682
874
|
const entryId = query.entryId;
|
|
683
875
|
const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
|
|
684
|
-
const data = await releaseService.
|
|
685
|
-
hasEntryAttached
|
|
686
|
-
});
|
|
876
|
+
const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
|
|
687
877
|
ctx.body = { data };
|
|
688
878
|
} else {
|
|
689
879
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
@@ -706,19 +896,18 @@ const releaseController = {
|
|
|
706
896
|
const id = ctx.params.id;
|
|
707
897
|
const releaseService = getService("release", { strapi });
|
|
708
898
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
});
|
|
713
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
899
|
+
if (!release2) {
|
|
900
|
+
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
901
|
+
}
|
|
714
902
|
const count = await releaseService.countActions({
|
|
715
903
|
filters: {
|
|
716
904
|
release: id
|
|
717
905
|
}
|
|
718
906
|
});
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
907
|
+
const sanitizedRelease = {
|
|
908
|
+
...release2,
|
|
909
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
910
|
+
};
|
|
722
911
|
const data = {
|
|
723
912
|
...sanitizedRelease,
|
|
724
913
|
actions: {
|
|
@@ -771,8 +960,27 @@ const releaseController = {
|
|
|
771
960
|
const id = ctx.params.id;
|
|
772
961
|
const releaseService = getService("release", { strapi });
|
|
773
962
|
const release2 = await releaseService.publish(id, { user });
|
|
963
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
964
|
+
releaseService.countActions({
|
|
965
|
+
filters: {
|
|
966
|
+
release: id,
|
|
967
|
+
type: "publish"
|
|
968
|
+
}
|
|
969
|
+
}),
|
|
970
|
+
releaseService.countActions({
|
|
971
|
+
filters: {
|
|
972
|
+
release: id,
|
|
973
|
+
type: "unpublish"
|
|
974
|
+
}
|
|
975
|
+
})
|
|
976
|
+
]);
|
|
774
977
|
ctx.body = {
|
|
775
|
-
data: release2
|
|
978
|
+
data: release2,
|
|
979
|
+
meta: {
|
|
980
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
981
|
+
totalPublishedEntries: countPublishActions,
|
|
982
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
983
|
+
}
|
|
776
984
|
};
|
|
777
985
|
}
|
|
778
986
|
};
|
|
@@ -1040,19 +1248,15 @@ const routes = {
|
|
|
1040
1248
|
};
|
|
1041
1249
|
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
1042
1250
|
const getPlugin = () => {
|
|
1043
|
-
if (features.isEnabled("cms-content-releases")
|
|
1251
|
+
if (features.isEnabled("cms-content-releases")) {
|
|
1044
1252
|
return {
|
|
1045
1253
|
register,
|
|
1046
1254
|
bootstrap,
|
|
1255
|
+
destroy,
|
|
1047
1256
|
contentTypes,
|
|
1048
1257
|
services,
|
|
1049
1258
|
controllers,
|
|
1050
|
-
routes
|
|
1051
|
-
destroy() {
|
|
1052
|
-
if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
|
|
1053
|
-
getService("event-manager").destroyAllListeners();
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1259
|
+
routes
|
|
1056
1260
|
};
|
|
1057
1261
|
}
|
|
1058
1262
|
return {
|