@strapi/content-releases 0.0.0-next.56199ab7a5f3320e0debcbe4a24fe0b8cd599e21 → 0.0.0-next.677a639124e715b4bd6bb862d1ef6536358bed8b
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-8J9a-MD5.mjs → App-8FCxPK8-.mjs} +252 -195
- package/dist/_chunks/App-8FCxPK8-.mjs.map +1 -0
- package/dist/_chunks/{App-YFvVMqB8.js → App-pspKUC-W.js} +248 -191
- package/dist/_chunks/App-pspKUC-W.js.map +1 -0
- package/dist/_chunks/{en-MyLPoISH.mjs → en-m9eTk4UF.mjs} +7 -3
- package/dist/_chunks/en-m9eTk4UF.mjs.map +1 -0
- package/dist/_chunks/{en-gYDqKYFd.js → en-r9YocBH0.js} +7 -3
- package/dist/_chunks/en-r9YocBH0.js.map +1 -0
- package/dist/_chunks/{index-ej8MzbQl.mjs → index-8aK7GzI5.mjs} +78 -29
- package/dist/_chunks/index-8aK7GzI5.mjs.map +1 -0
- package/dist/_chunks/{index-vxli-E-l.js → index-nGaPcY9m.js} +77 -28
- package/dist/_chunks/index-nGaPcY9m.js.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +308 -120
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +309 -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,9 @@ const schema$1 = {
|
|
|
173
203
|
releasedAt: {
|
|
174
204
|
type: "datetime"
|
|
175
205
|
},
|
|
206
|
+
scheduledAt: {
|
|
207
|
+
type: "datetime"
|
|
208
|
+
},
|
|
176
209
|
actions: {
|
|
177
210
|
type: "relation",
|
|
178
211
|
relation: "oneToMany",
|
|
@@ -235,15 +268,6 @@ const contentTypes = {
|
|
|
235
268
|
release: release$1,
|
|
236
269
|
"release-action": releaseAction$1
|
|
237
270
|
};
|
|
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
271
|
const getGroupName = (queryValue) => {
|
|
248
272
|
switch (queryValue) {
|
|
249
273
|
case "contentType":
|
|
@@ -259,10 +283,24 @@ const getGroupName = (queryValue) => {
|
|
|
259
283
|
const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
260
284
|
async create(releaseData, { user }) {
|
|
261
285
|
const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
|
|
262
|
-
|
|
263
|
-
|
|
286
|
+
const {
|
|
287
|
+
validatePendingReleasesLimit,
|
|
288
|
+
validateUniqueNameForPendingRelease,
|
|
289
|
+
validateScheduledAtIsLaterThanNow
|
|
290
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
291
|
+
await Promise.all([
|
|
292
|
+
validatePendingReleasesLimit(),
|
|
293
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
294
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
295
|
+
]);
|
|
296
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
264
297
|
data: releaseWithCreatorFields
|
|
265
298
|
});
|
|
299
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
|
|
300
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
301
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
302
|
+
}
|
|
303
|
+
return release2;
|
|
266
304
|
},
|
|
267
305
|
async findOne(id, query = {}) {
|
|
268
306
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
@@ -281,51 +319,66 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
281
319
|
}
|
|
282
320
|
});
|
|
283
321
|
},
|
|
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
|
-
}
|
|
322
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
323
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
324
|
+
where: {
|
|
325
|
+
actions: {
|
|
326
|
+
target_type: contentTypeUid,
|
|
327
|
+
target_id: entryId
|
|
305
328
|
},
|
|
306
|
-
{
|
|
307
|
-
|
|
329
|
+
releasedAt: {
|
|
330
|
+
$null: true
|
|
308
331
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
332
|
+
},
|
|
333
|
+
populate: {
|
|
334
|
+
// Filter the action to get only the content type entry
|
|
335
|
+
actions: {
|
|
336
|
+
where: {
|
|
337
|
+
target_type: contentTypeUid,
|
|
338
|
+
target_id: entryId
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
return releases.map((release2) => {
|
|
344
|
+
if (release2.actions?.length) {
|
|
345
|
+
const [actionForEntry] = release2.actions;
|
|
346
|
+
delete release2.actions;
|
|
347
|
+
return {
|
|
348
|
+
...release2,
|
|
349
|
+
action: actionForEntry
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return release2;
|
|
353
|
+
});
|
|
354
|
+
},
|
|
355
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
356
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
357
|
+
where: {
|
|
358
|
+
releasedAt: {
|
|
359
|
+
$null: true
|
|
360
|
+
},
|
|
361
|
+
actions: {
|
|
315
362
|
target_type: contentTypeUid,
|
|
316
363
|
target_id: entryId
|
|
317
364
|
}
|
|
318
365
|
}
|
|
319
|
-
}
|
|
366
|
+
});
|
|
320
367
|
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
321
368
|
where: {
|
|
322
|
-
|
|
369
|
+
$or: [
|
|
370
|
+
{
|
|
371
|
+
id: {
|
|
372
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
actions: null
|
|
377
|
+
}
|
|
378
|
+
],
|
|
323
379
|
releasedAt: {
|
|
324
380
|
$null: true
|
|
325
381
|
}
|
|
326
|
-
},
|
|
327
|
-
populate: {
|
|
328
|
-
...populateAttachedAction
|
|
329
382
|
}
|
|
330
383
|
});
|
|
331
384
|
return releases.map((release2) => {
|
|
@@ -342,6 +395,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
342
395
|
},
|
|
343
396
|
async update(id, releaseData, { user }) {
|
|
344
397
|
const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(releaseData);
|
|
398
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
399
|
+
"release-validation",
|
|
400
|
+
{ strapi: strapi2 }
|
|
401
|
+
);
|
|
402
|
+
await Promise.all([
|
|
403
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
404
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
405
|
+
]);
|
|
345
406
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
346
407
|
if (!release2) {
|
|
347
408
|
throw new utils.errors.NotFoundError(`No release found for id ${id}`);
|
|
@@ -357,6 +418,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
357
418
|
// @ts-expect-error see above
|
|
358
419
|
data: releaseWithCreatorFields
|
|
359
420
|
});
|
|
421
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
422
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
423
|
+
if (releaseData.scheduledAt) {
|
|
424
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
425
|
+
} else if (release2.scheduledAt) {
|
|
426
|
+
schedulingService.cancel(id);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
360
429
|
return updatedRelease;
|
|
361
430
|
},
|
|
362
431
|
async createAction(releaseId, action) {
|
|
@@ -524,7 +593,9 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
524
593
|
populate: {
|
|
525
594
|
actions: {
|
|
526
595
|
populate: {
|
|
527
|
-
entry:
|
|
596
|
+
entry: {
|
|
597
|
+
fields: ["id"]
|
|
598
|
+
}
|
|
528
599
|
}
|
|
529
600
|
}
|
|
530
601
|
}
|
|
@@ -539,30 +610,80 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
539
610
|
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
540
611
|
throw new utils.errors.ValidationError("No entries to publish");
|
|
541
612
|
}
|
|
542
|
-
const
|
|
613
|
+
const collectionTypeActions = {};
|
|
614
|
+
const singleTypeActions = [];
|
|
543
615
|
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
544
616
|
const contentTypeUid = action.contentType;
|
|
545
|
-
if (
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
617
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
618
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
619
|
+
collectionTypeActions[contentTypeUid] = {
|
|
620
|
+
entriestoPublishIds: [],
|
|
621
|
+
entriesToUnpublishIds: []
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
if (action.type === "publish") {
|
|
625
|
+
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
626
|
+
} else {
|
|
627
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
628
|
+
}
|
|
553
629
|
} else {
|
|
554
|
-
|
|
630
|
+
singleTypeActions.push({
|
|
631
|
+
uid: contentTypeUid,
|
|
632
|
+
action: action.type,
|
|
633
|
+
id: action.entry.id
|
|
634
|
+
});
|
|
555
635
|
}
|
|
556
636
|
}
|
|
557
637
|
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
638
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
558
639
|
await strapi2.db.transaction(async () => {
|
|
559
|
-
for (const
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
640
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
641
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
642
|
+
const entry = await strapi2.entityService.findOne(uid, id, { populate });
|
|
643
|
+
try {
|
|
644
|
+
if (action === "publish") {
|
|
645
|
+
await entityManagerService.publish(entry, uid);
|
|
646
|
+
} else {
|
|
647
|
+
await entityManagerService.unpublish(entry, uid);
|
|
648
|
+
}
|
|
649
|
+
} catch (error) {
|
|
650
|
+
if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
651
|
+
;
|
|
652
|
+
else {
|
|
653
|
+
throw error;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
658
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
659
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
660
|
+
const entriesToPublish = await strapi2.entityService.findMany(
|
|
661
|
+
contentTypeUid,
|
|
662
|
+
{
|
|
663
|
+
filters: {
|
|
664
|
+
id: {
|
|
665
|
+
$in: entriestoPublishIds
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
populate
|
|
669
|
+
}
|
|
670
|
+
);
|
|
671
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(
|
|
672
|
+
contentTypeUid,
|
|
673
|
+
{
|
|
674
|
+
filters: {
|
|
675
|
+
id: {
|
|
676
|
+
$in: entriesToUnpublishIds
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
populate
|
|
680
|
+
}
|
|
681
|
+
);
|
|
682
|
+
if (entriesToPublish.length > 0) {
|
|
683
|
+
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
563
684
|
}
|
|
564
|
-
if (
|
|
565
|
-
await entityManagerService.unpublishMany(
|
|
685
|
+
if (entriesToUnpublish.length > 0) {
|
|
686
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
|
|
566
687
|
}
|
|
567
688
|
}
|
|
568
689
|
});
|
|
@@ -660,34 +781,89 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
660
781
|
if (pendingReleasesCount >= maximumPendingReleases) {
|
|
661
782
|
throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
|
|
662
783
|
}
|
|
784
|
+
},
|
|
785
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
786
|
+
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
787
|
+
filters: {
|
|
788
|
+
releasedAt: {
|
|
789
|
+
$null: true
|
|
790
|
+
},
|
|
791
|
+
name,
|
|
792
|
+
...id && { id: { $ne: id } }
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
const isNameUnique = pendingReleases.length === 0;
|
|
796
|
+
if (!isNameUnique) {
|
|
797
|
+
throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
|
|
798
|
+
}
|
|
799
|
+
},
|
|
800
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
801
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
802
|
+
throw new utils.errors.ValidationError("Scheduled at must be later than now");
|
|
803
|
+
}
|
|
663
804
|
}
|
|
664
805
|
});
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
destroyListenerCallbacks: []
|
|
668
|
-
};
|
|
806
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
807
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
669
808
|
return {
|
|
670
|
-
|
|
671
|
-
|
|
809
|
+
async set(releaseId, scheduleDate) {
|
|
810
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
811
|
+
if (!release2) {
|
|
812
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
813
|
+
}
|
|
814
|
+
const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
|
|
815
|
+
try {
|
|
816
|
+
await getService("release").publish(releaseId);
|
|
817
|
+
} catch (error) {
|
|
818
|
+
}
|
|
819
|
+
this.cancel(releaseId);
|
|
820
|
+
});
|
|
821
|
+
if (scheduledJobs.has(releaseId)) {
|
|
822
|
+
this.cancel(releaseId);
|
|
823
|
+
}
|
|
824
|
+
scheduledJobs.set(releaseId, job);
|
|
825
|
+
return scheduledJobs;
|
|
672
826
|
},
|
|
673
|
-
|
|
674
|
-
if (
|
|
675
|
-
|
|
827
|
+
cancel(releaseId) {
|
|
828
|
+
if (scheduledJobs.has(releaseId)) {
|
|
829
|
+
scheduledJobs.get(releaseId).cancel();
|
|
830
|
+
scheduledJobs.delete(releaseId);
|
|
676
831
|
}
|
|
677
|
-
|
|
678
|
-
|
|
832
|
+
return scheduledJobs;
|
|
833
|
+
},
|
|
834
|
+
getAll() {
|
|
835
|
+
return scheduledJobs;
|
|
836
|
+
},
|
|
837
|
+
/**
|
|
838
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
839
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
840
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
841
|
+
*/
|
|
842
|
+
async syncFromDatabase() {
|
|
843
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
844
|
+
where: {
|
|
845
|
+
scheduledAt: {
|
|
846
|
+
$gte: /* @__PURE__ */ new Date()
|
|
847
|
+
},
|
|
848
|
+
releasedAt: null
|
|
849
|
+
}
|
|
679
850
|
});
|
|
851
|
+
for (const release2 of releases) {
|
|
852
|
+
this.set(release2.id, release2.scheduledAt);
|
|
853
|
+
}
|
|
854
|
+
return scheduledJobs;
|
|
680
855
|
}
|
|
681
856
|
};
|
|
682
857
|
};
|
|
683
858
|
const services = {
|
|
684
859
|
release: createReleaseService,
|
|
685
|
-
"release-action": createReleaseActionService,
|
|
686
860
|
"release-validation": createReleaseValidationService,
|
|
687
|
-
"
|
|
861
|
+
...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
|
|
688
862
|
};
|
|
689
863
|
const RELEASE_SCHEMA = yup__namespace.object().shape({
|
|
690
|
-
name: yup__namespace.string().trim().required()
|
|
864
|
+
name: yup__namespace.string().trim().required(),
|
|
865
|
+
// scheduledAt is a date, but we always receive strings from the client
|
|
866
|
+
scheduledAt: yup__namespace.string().nullable()
|
|
691
867
|
}).required().noUnknown();
|
|
692
868
|
const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
|
|
693
869
|
const releaseController = {
|
|
@@ -704,9 +880,7 @@ const releaseController = {
|
|
|
704
880
|
const contentTypeUid = query.contentTypeUid;
|
|
705
881
|
const entryId = query.entryId;
|
|
706
882
|
const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
|
|
707
|
-
const data = await releaseService.
|
|
708
|
-
hasEntryAttached
|
|
709
|
-
});
|
|
883
|
+
const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
|
|
710
884
|
ctx.body = { data };
|
|
711
885
|
} else {
|
|
712
886
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
@@ -729,19 +903,18 @@ const releaseController = {
|
|
|
729
903
|
const id = ctx.params.id;
|
|
730
904
|
const releaseService = getService("release", { strapi });
|
|
731
905
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
});
|
|
736
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
906
|
+
if (!release2) {
|
|
907
|
+
throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
|
|
908
|
+
}
|
|
737
909
|
const count = await releaseService.countActions({
|
|
738
910
|
filters: {
|
|
739
911
|
release: id
|
|
740
912
|
}
|
|
741
913
|
});
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
914
|
+
const sanitizedRelease = {
|
|
915
|
+
...release2,
|
|
916
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
917
|
+
};
|
|
745
918
|
const data = {
|
|
746
919
|
...sanitizedRelease,
|
|
747
920
|
actions: {
|
|
@@ -794,8 +967,27 @@ const releaseController = {
|
|
|
794
967
|
const id = ctx.params.id;
|
|
795
968
|
const releaseService = getService("release", { strapi });
|
|
796
969
|
const release2 = await releaseService.publish(id, { user });
|
|
970
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
971
|
+
releaseService.countActions({
|
|
972
|
+
filters: {
|
|
973
|
+
release: id,
|
|
974
|
+
type: "publish"
|
|
975
|
+
}
|
|
976
|
+
}),
|
|
977
|
+
releaseService.countActions({
|
|
978
|
+
filters: {
|
|
979
|
+
release: id,
|
|
980
|
+
type: "unpublish"
|
|
981
|
+
}
|
|
982
|
+
})
|
|
983
|
+
]);
|
|
797
984
|
ctx.body = {
|
|
798
|
-
data: release2
|
|
985
|
+
data: release2,
|
|
986
|
+
meta: {
|
|
987
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
988
|
+
totalPublishedEntries: countPublishActions,
|
|
989
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
990
|
+
}
|
|
799
991
|
};
|
|
800
992
|
}
|
|
801
993
|
};
|
|
@@ -1063,19 +1255,15 @@ const routes = {
|
|
|
1063
1255
|
};
|
|
1064
1256
|
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
1065
1257
|
const getPlugin = () => {
|
|
1066
|
-
if (features.isEnabled("cms-content-releases")
|
|
1258
|
+
if (features.isEnabled("cms-content-releases")) {
|
|
1067
1259
|
return {
|
|
1068
1260
|
register,
|
|
1069
1261
|
bootstrap,
|
|
1262
|
+
destroy,
|
|
1070
1263
|
contentTypes,
|
|
1071
1264
|
services,
|
|
1072
1265
|
controllers,
|
|
1073
|
-
routes
|
|
1074
|
-
destroy() {
|
|
1075
|
-
if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
|
|
1076
|
-
getService("event-manager").destroyAllListeners();
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1266
|
+
routes
|
|
1079
1267
|
};
|
|
1080
1268
|
}
|
|
1081
1269
|
return {
|