@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.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const utils = require("@strapi/utils");
|
|
3
|
+
const lodash = require("lodash");
|
|
3
4
|
const _ = require("lodash/fp");
|
|
4
5
|
const EE = require("@strapi/strapi/dist/utils/ee");
|
|
6
|
+
const nodeSchedule = require("node-schedule");
|
|
5
7
|
const yup = require("yup");
|
|
6
8
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
7
9
|
function _interopNamespace(e) {
|
|
@@ -71,40 +73,50 @@ const ACTIONS = [
|
|
|
71
73
|
pluginName: "content-releases"
|
|
72
74
|
}
|
|
73
75
|
];
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
async function deleteActionsOnDisableDraftAndPublish({
|
|
77
|
+
oldContentTypes,
|
|
78
|
+
contentTypes: contentTypes2
|
|
79
|
+
}) {
|
|
80
|
+
if (!oldContentTypes) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
for (const uid in contentTypes2) {
|
|
84
|
+
if (!oldContentTypes[uid]) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const oldContentType = oldContentTypes[uid];
|
|
88
|
+
const contentType = contentTypes2[uid];
|
|
89
|
+
if (utils.contentTypes.hasDraftAndPublish(oldContentType) && !utils.contentTypes.hasDraftAndPublish(contentType)) {
|
|
90
|
+
await strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: uid }).execute();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
95
|
+
const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
|
|
96
|
+
if (deletedContentTypes.length) {
|
|
97
|
+
await utils.mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
|
|
98
|
+
return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
77
102
|
const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
|
|
78
103
|
const register = async ({ strapi: strapi2 }) => {
|
|
79
|
-
if (features$2.isEnabled("cms-content-releases")
|
|
104
|
+
if (features$2.isEnabled("cms-content-releases")) {
|
|
80
105
|
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const destroyContentTypeUpdateListener = strapi2.eventHub.on(
|
|
84
|
-
"content-type.update",
|
|
85
|
-
async ({ contentType }) => {
|
|
86
|
-
if (contentType.schema.options.draftAndPublish === false) {
|
|
87
|
-
await releaseActionService.deleteManyForContentType(contentType.uid);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
);
|
|
91
|
-
eventManager.addDestroyListenerCallback(destroyContentTypeUpdateListener);
|
|
92
|
-
const destroyContentTypeDeleteListener = strapi2.eventHub.on(
|
|
93
|
-
"content-type.delete",
|
|
94
|
-
async ({ contentType }) => {
|
|
95
|
-
await releaseActionService.deleteManyForContentType(contentType.uid);
|
|
96
|
-
}
|
|
97
|
-
);
|
|
98
|
-
eventManager.addDestroyListenerCallback(destroyContentTypeDeleteListener);
|
|
106
|
+
strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
|
|
107
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
|
|
99
108
|
}
|
|
100
109
|
};
|
|
110
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
111
|
+
return strapi2.plugin("content-releases").service(name);
|
|
112
|
+
};
|
|
101
113
|
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
102
114
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
103
|
-
if (features$1.isEnabled("cms-content-releases")
|
|
115
|
+
if (features$1.isEnabled("cms-content-releases")) {
|
|
104
116
|
strapi2.db.lifecycles.subscribe({
|
|
105
117
|
afterDelete(event) {
|
|
106
118
|
const { model, result } = event;
|
|
107
|
-
if (model.kind === "collectionType" && model.options
|
|
119
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
108
120
|
const { id } = result;
|
|
109
121
|
strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
110
122
|
where: {
|
|
@@ -120,7 +132,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
120
132
|
*/
|
|
121
133
|
async beforeDeleteMany(event) {
|
|
122
134
|
const { model, params } = event;
|
|
123
|
-
if (model.kind === "collectionType" && model.options
|
|
135
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
124
136
|
const { where } = params;
|
|
125
137
|
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
126
138
|
event.state.entriesToDelete = entriesToDelete;
|
|
@@ -145,6 +157,24 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
145
157
|
}
|
|
146
158
|
}
|
|
147
159
|
});
|
|
160
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
161
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
162
|
+
strapi2.log.error(
|
|
163
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
164
|
+
);
|
|
165
|
+
throw err;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
171
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
172
|
+
const scheduledJobs = getService("scheduling", {
|
|
173
|
+
strapi: strapi2
|
|
174
|
+
}).getAll();
|
|
175
|
+
for (const [, job] of scheduledJobs) {
|
|
176
|
+
job.cancel();
|
|
177
|
+
}
|
|
148
178
|
}
|
|
149
179
|
};
|
|
150
180
|
const schema$1 = {
|
|
@@ -173,6 +203,12 @@ const schema$1 = {
|
|
|
173
203
|
releasedAt: {
|
|
174
204
|
type: "datetime"
|
|
175
205
|
},
|
|
206
|
+
scheduledAt: {
|
|
207
|
+
type: "datetime"
|
|
208
|
+
},
|
|
209
|
+
timezone: {
|
|
210
|
+
type: "string"
|
|
211
|
+
},
|
|
176
212
|
actions: {
|
|
177
213
|
type: "relation",
|
|
178
214
|
relation: "oneToMany",
|
|
@@ -235,15 +271,6 @@ const contentTypes = {
|
|
|
235
271
|
release: release$1,
|
|
236
272
|
"release-action": releaseAction$1
|
|
237
273
|
};
|
|
238
|
-
const createReleaseActionService = ({ strapi: strapi2 }) => ({
|
|
239
|
-
async deleteManyForContentType(contentTypeUid) {
|
|
240
|
-
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
241
|
-
where: {
|
|
242
|
-
target_type: contentTypeUid
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
274
|
const getGroupName = (queryValue) => {
|
|
248
275
|
switch (queryValue) {
|
|
249
276
|
case "contentType":
|
|
@@ -259,10 +286,25 @@ const getGroupName = (queryValue) => {
|
|
|
259
286
|
const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
260
287
|
async create(releaseData, { user }) {
|
|
261
288
|
const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
|
|
262
|
-
|
|
263
|
-
|
|
289
|
+
const {
|
|
290
|
+
validatePendingReleasesLimit,
|
|
291
|
+
validateUniqueNameForPendingRelease,
|
|
292
|
+
validateScheduledAtIsLaterThanNow
|
|
293
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
294
|
+
await Promise.all([
|
|
295
|
+
validatePendingReleasesLimit(),
|
|
296
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
297
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
298
|
+
]);
|
|
299
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
264
300
|
data: releaseWithCreatorFields
|
|
265
301
|
});
|
|
302
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
|
|
303
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
304
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
305
|
+
}
|
|
306
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
307
|
+
return release2;
|
|
266
308
|
},
|
|
267
309
|
async findOne(id, query = {}) {
|
|
268
310
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
@@ -281,51 +323,66 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
281
323
|
}
|
|
282
324
|
});
|
|
283
325
|
},
|
|
284
|
-
async
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
// Find all Releases where the content type entry is present
|
|
291
|
-
actions: {
|
|
292
|
-
target_type: contentTypeUid,
|
|
293
|
-
target_id: entryId
|
|
294
|
-
}
|
|
295
|
-
} : {
|
|
296
|
-
// Find all Releases where the content type entry is not present
|
|
297
|
-
$or: [
|
|
298
|
-
{
|
|
299
|
-
$not: {
|
|
300
|
-
actions: {
|
|
301
|
-
target_type: contentTypeUid,
|
|
302
|
-
target_id: entryId
|
|
303
|
-
}
|
|
304
|
-
}
|
|
326
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
327
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
328
|
+
where: {
|
|
329
|
+
actions: {
|
|
330
|
+
target_type: contentTypeUid,
|
|
331
|
+
target_id: entryId
|
|
305
332
|
},
|
|
306
|
-
{
|
|
307
|
-
|
|
333
|
+
releasedAt: {
|
|
334
|
+
$null: true
|
|
308
335
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
336
|
+
},
|
|
337
|
+
populate: {
|
|
338
|
+
// Filter the action to get only the content type entry
|
|
339
|
+
actions: {
|
|
340
|
+
where: {
|
|
341
|
+
target_type: contentTypeUid,
|
|
342
|
+
target_id: entryId
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
return releases.map((release2) => {
|
|
348
|
+
if (release2.actions?.length) {
|
|
349
|
+
const [actionForEntry] = release2.actions;
|
|
350
|
+
delete release2.actions;
|
|
351
|
+
return {
|
|
352
|
+
...release2,
|
|
353
|
+
action: actionForEntry
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
return release2;
|
|
357
|
+
});
|
|
358
|
+
},
|
|
359
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
360
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
361
|
+
where: {
|
|
362
|
+
releasedAt: {
|
|
363
|
+
$null: true
|
|
364
|
+
},
|
|
365
|
+
actions: {
|
|
315
366
|
target_type: contentTypeUid,
|
|
316
367
|
target_id: entryId
|
|
317
368
|
}
|
|
318
369
|
}
|
|
319
|
-
}
|
|
370
|
+
});
|
|
320
371
|
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
321
372
|
where: {
|
|
322
|
-
|
|
373
|
+
$or: [
|
|
374
|
+
{
|
|
375
|
+
id: {
|
|
376
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
actions: null
|
|
381
|
+
}
|
|
382
|
+
],
|
|
323
383
|
releasedAt: {
|
|
324
384
|
$null: true
|
|
325
385
|
}
|
|
326
|
-
},
|
|
327
|
-
populate: {
|
|
328
|
-
...populateAttachedAction
|
|
329
386
|
}
|
|
330
387
|
});
|
|
331
388
|
return releases.map((release2) => {
|
|
@@ -342,6 +399,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
342
399
|
},
|
|
343
400
|
async update(id, releaseData, { user }) {
|
|
344
401
|
const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(releaseData);
|
|
402
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
403
|
+
"release-validation",
|
|
404
|
+
{ strapi: strapi2 }
|
|
405
|
+
);
|
|
406
|
+
await Promise.all([
|
|
407
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
408
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
409
|
+
]);
|
|
345
410
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
346
411
|
if (!release2) {
|
|
347
412
|
throw new utils.errors.NotFoundError(`No release found for id ${id}`);
|
|
@@ -357,6 +422,15 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
357
422
|
// @ts-expect-error see above
|
|
358
423
|
data: releaseWithCreatorFields
|
|
359
424
|
});
|
|
425
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
426
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
427
|
+
if (releaseData.scheduledAt) {
|
|
428
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
429
|
+
} else if (release2.scheduledAt) {
|
|
430
|
+
schedulingService.cancel(id);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
360
434
|
return updatedRelease;
|
|
361
435
|
},
|
|
362
436
|
async createAction(releaseId, action) {
|
|
@@ -514,6 +588,11 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
514
588
|
});
|
|
515
589
|
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
516
590
|
});
|
|
591
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
|
|
592
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
593
|
+
await schedulingService.cancel(release2.id);
|
|
594
|
+
}
|
|
595
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
517
596
|
return release2;
|
|
518
597
|
},
|
|
519
598
|
async publish(releaseId) {
|
|
@@ -524,7 +603,9 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
524
603
|
populate: {
|
|
525
604
|
actions: {
|
|
526
605
|
populate: {
|
|
527
|
-
entry:
|
|
606
|
+
entry: {
|
|
607
|
+
fields: ["id"]
|
|
608
|
+
}
|
|
528
609
|
}
|
|
529
610
|
}
|
|
530
611
|
}
|
|
@@ -539,30 +620,80 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
539
620
|
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
540
621
|
throw new utils.errors.ValidationError("No entries to publish");
|
|
541
622
|
}
|
|
542
|
-
const
|
|
623
|
+
const collectionTypeActions = {};
|
|
624
|
+
const singleTypeActions = [];
|
|
543
625
|
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
544
626
|
const contentTypeUid = action.contentType;
|
|
545
|
-
if (
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
627
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
628
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
629
|
+
collectionTypeActions[contentTypeUid] = {
|
|
630
|
+
entriestoPublishIds: [],
|
|
631
|
+
entriesToUnpublishIds: []
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
if (action.type === "publish") {
|
|
635
|
+
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
636
|
+
} else {
|
|
637
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
638
|
+
}
|
|
553
639
|
} else {
|
|
554
|
-
|
|
640
|
+
singleTypeActions.push({
|
|
641
|
+
uid: contentTypeUid,
|
|
642
|
+
action: action.type,
|
|
643
|
+
id: action.entry.id
|
|
644
|
+
});
|
|
555
645
|
}
|
|
556
646
|
}
|
|
557
647
|
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
648
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
558
649
|
await strapi2.db.transaction(async () => {
|
|
559
|
-
for (const
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
650
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
651
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
652
|
+
const entry = await strapi2.entityService.findOne(uid, id, { populate });
|
|
653
|
+
try {
|
|
654
|
+
if (action === "publish") {
|
|
655
|
+
await entityManagerService.publish(entry, uid);
|
|
656
|
+
} else {
|
|
657
|
+
await entityManagerService.unpublish(entry, uid);
|
|
658
|
+
}
|
|
659
|
+
} catch (error) {
|
|
660
|
+
if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
661
|
+
;
|
|
662
|
+
else {
|
|
663
|
+
throw error;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
668
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
669
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
670
|
+
const entriesToPublish = await strapi2.entityService.findMany(
|
|
671
|
+
contentTypeUid,
|
|
672
|
+
{
|
|
673
|
+
filters: {
|
|
674
|
+
id: {
|
|
675
|
+
$in: entriestoPublishIds
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
populate
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(
|
|
682
|
+
contentTypeUid,
|
|
683
|
+
{
|
|
684
|
+
filters: {
|
|
685
|
+
id: {
|
|
686
|
+
$in: entriesToUnpublishIds
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
populate
|
|
690
|
+
}
|
|
691
|
+
);
|
|
692
|
+
if (entriesToPublish.length > 0) {
|
|
693
|
+
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
563
694
|
}
|
|
564
|
-
if (
|
|
565
|
-
await entityManagerService.unpublishMany(
|
|
695
|
+
if (entriesToUnpublish.length > 0) {
|
|
696
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
|
|
566
697
|
}
|
|
567
698
|
}
|
|
568
699
|
});
|
|
@@ -575,6 +706,7 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
575
706
|
releasedAt: /* @__PURE__ */ new Date()
|
|
576
707
|
}
|
|
577
708
|
});
|
|
709
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
578
710
|
return release2;
|
|
579
711
|
},
|
|
580
712
|
async updateAction(actionId, releaseId, update) {
|
|
@@ -660,34 +792,94 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
660
792
|
if (pendingReleasesCount >= maximumPendingReleases) {
|
|
661
793
|
throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
|
|
662
794
|
}
|
|
795
|
+
},
|
|
796
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
797
|
+
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
798
|
+
filters: {
|
|
799
|
+
releasedAt: {
|
|
800
|
+
$null: true
|
|
801
|
+
},
|
|
802
|
+
name,
|
|
803
|
+
...id && { id: { $ne: id } }
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
const isNameUnique = pendingReleases.length === 0;
|
|
807
|
+
if (!isNameUnique) {
|
|
808
|
+
throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
812
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
813
|
+
throw new utils.errors.ValidationError("Scheduled at must be later than now");
|
|
814
|
+
}
|
|
663
815
|
}
|
|
664
816
|
});
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
destroyListenerCallbacks: []
|
|
668
|
-
};
|
|
817
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
818
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
669
819
|
return {
|
|
670
|
-
|
|
671
|
-
|
|
820
|
+
async set(releaseId, scheduleDate) {
|
|
821
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
822
|
+
if (!release2) {
|
|
823
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
824
|
+
}
|
|
825
|
+
const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
|
|
826
|
+
try {
|
|
827
|
+
await getService("release").publish(releaseId);
|
|
828
|
+
} catch (error) {
|
|
829
|
+
}
|
|
830
|
+
this.cancel(releaseId);
|
|
831
|
+
});
|
|
832
|
+
if (scheduledJobs.has(releaseId)) {
|
|
833
|
+
this.cancel(releaseId);
|
|
834
|
+
}
|
|
835
|
+
scheduledJobs.set(releaseId, job);
|
|
836
|
+
return scheduledJobs;
|
|
672
837
|
},
|
|
673
|
-
|
|
674
|
-
if (
|
|
675
|
-
|
|
838
|
+
cancel(releaseId) {
|
|
839
|
+
if (scheduledJobs.has(releaseId)) {
|
|
840
|
+
scheduledJobs.get(releaseId).cancel();
|
|
841
|
+
scheduledJobs.delete(releaseId);
|
|
676
842
|
}
|
|
677
|
-
|
|
678
|
-
|
|
843
|
+
return scheduledJobs;
|
|
844
|
+
},
|
|
845
|
+
getAll() {
|
|
846
|
+
return scheduledJobs;
|
|
847
|
+
},
|
|
848
|
+
/**
|
|
849
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
850
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
851
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
852
|
+
*/
|
|
853
|
+
async syncFromDatabase() {
|
|
854
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
855
|
+
where: {
|
|
856
|
+
scheduledAt: {
|
|
857
|
+
$gte: /* @__PURE__ */ new Date()
|
|
858
|
+
},
|
|
859
|
+
releasedAt: null
|
|
860
|
+
}
|
|
679
861
|
});
|
|
862
|
+
for (const release2 of releases) {
|
|
863
|
+
this.set(release2.id, release2.scheduledAt);
|
|
864
|
+
}
|
|
865
|
+
return scheduledJobs;
|
|
680
866
|
}
|
|
681
867
|
};
|
|
682
868
|
};
|
|
683
869
|
const services = {
|
|
684
870
|
release: createReleaseService,
|
|
685
|
-
"release-action": createReleaseActionService,
|
|
686
871
|
"release-validation": createReleaseValidationService,
|
|
687
|
-
"
|
|
872
|
+
...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
|
|
688
873
|
};
|
|
689
874
|
const RELEASE_SCHEMA = yup__namespace.object().shape({
|
|
690
|
-
name: yup__namespace.string().trim().required()
|
|
875
|
+
name: yup__namespace.string().trim().required(),
|
|
876
|
+
// scheduledAt is a date, but we always receive strings from the client
|
|
877
|
+
scheduledAt: yup__namespace.string().nullable(),
|
|
878
|
+
timezone: yup__namespace.string().when("scheduledAt", {
|
|
879
|
+
is: (scheduledAt) => !!scheduledAt,
|
|
880
|
+
then: yup__namespace.string().required(),
|
|
881
|
+
otherwise: yup__namespace.string().nullable()
|
|
882
|
+
})
|
|
691
883
|
}).required().noUnknown();
|
|
692
884
|
const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
|
|
693
885
|
const releaseController = {
|
|
@@ -704,9 +896,7 @@ const releaseController = {
|
|
|
704
896
|
const contentTypeUid = query.contentTypeUid;
|
|
705
897
|
const entryId = query.entryId;
|
|
706
898
|
const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
|
|
707
|
-
const data = await releaseService.
|
|
708
|
-
hasEntryAttached
|
|
709
|
-
});
|
|
899
|
+
const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
|
|
710
900
|
ctx.body = { data };
|
|
711
901
|
} else {
|
|
712
902
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
@@ -729,19 +919,18 @@ const releaseController = {
|
|
|
729
919
|
const id = ctx.params.id;
|
|
730
920
|
const releaseService = getService("release", { strapi });
|
|
731
921
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
});
|
|
736
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
922
|
+
if (!release2) {
|
|
923
|
+
throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
|
|
924
|
+
}
|
|
737
925
|
const count = await releaseService.countActions({
|
|
738
926
|
filters: {
|
|
739
927
|
release: id
|
|
740
928
|
}
|
|
741
929
|
});
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
930
|
+
const sanitizedRelease = {
|
|
931
|
+
...release2,
|
|
932
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
933
|
+
};
|
|
745
934
|
const data = {
|
|
746
935
|
...sanitizedRelease,
|
|
747
936
|
actions: {
|
|
@@ -794,8 +983,27 @@ const releaseController = {
|
|
|
794
983
|
const id = ctx.params.id;
|
|
795
984
|
const releaseService = getService("release", { strapi });
|
|
796
985
|
const release2 = await releaseService.publish(id, { user });
|
|
986
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
987
|
+
releaseService.countActions({
|
|
988
|
+
filters: {
|
|
989
|
+
release: id,
|
|
990
|
+
type: "publish"
|
|
991
|
+
}
|
|
992
|
+
}),
|
|
993
|
+
releaseService.countActions({
|
|
994
|
+
filters: {
|
|
995
|
+
release: id,
|
|
996
|
+
type: "unpublish"
|
|
997
|
+
}
|
|
998
|
+
})
|
|
999
|
+
]);
|
|
797
1000
|
ctx.body = {
|
|
798
|
-
data: release2
|
|
1001
|
+
data: release2,
|
|
1002
|
+
meta: {
|
|
1003
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1004
|
+
totalPublishedEntries: countPublishActions,
|
|
1005
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1006
|
+
}
|
|
799
1007
|
};
|
|
800
1008
|
}
|
|
801
1009
|
};
|
|
@@ -1063,19 +1271,15 @@ const routes = {
|
|
|
1063
1271
|
};
|
|
1064
1272
|
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
1065
1273
|
const getPlugin = () => {
|
|
1066
|
-
if (features.isEnabled("cms-content-releases")
|
|
1274
|
+
if (features.isEnabled("cms-content-releases")) {
|
|
1067
1275
|
return {
|
|
1068
1276
|
register,
|
|
1069
1277
|
bootstrap,
|
|
1278
|
+
destroy,
|
|
1070
1279
|
contentTypes,
|
|
1071
1280
|
services,
|
|
1072
1281
|
controllers,
|
|
1073
|
-
routes
|
|
1074
|
-
destroy() {
|
|
1075
|
-
if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
|
|
1076
|
-
getService("event-manager").destroyAllListeners();
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1282
|
+
routes
|
|
1079
1283
|
};
|
|
1080
1284
|
}
|
|
1081
1285
|
return {
|