@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.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,9 @@ const schema$1 = {
|
|
|
150
180
|
releasedAt: {
|
|
151
181
|
type: "datetime"
|
|
152
182
|
},
|
|
183
|
+
scheduledAt: {
|
|
184
|
+
type: "datetime"
|
|
185
|
+
},
|
|
153
186
|
actions: {
|
|
154
187
|
type: "relation",
|
|
155
188
|
relation: "oneToMany",
|
|
@@ -212,15 +245,6 @@ const contentTypes = {
|
|
|
212
245
|
release: release$1,
|
|
213
246
|
"release-action": releaseAction$1
|
|
214
247
|
};
|
|
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
248
|
const getGroupName = (queryValue) => {
|
|
225
249
|
switch (queryValue) {
|
|
226
250
|
case "contentType":
|
|
@@ -236,10 +260,24 @@ const getGroupName = (queryValue) => {
|
|
|
236
260
|
const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
237
261
|
async create(releaseData, { user }) {
|
|
238
262
|
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
239
|
-
|
|
240
|
-
|
|
263
|
+
const {
|
|
264
|
+
validatePendingReleasesLimit,
|
|
265
|
+
validateUniqueNameForPendingRelease,
|
|
266
|
+
validateScheduledAtIsLaterThanNow
|
|
267
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
268
|
+
await Promise.all([
|
|
269
|
+
validatePendingReleasesLimit(),
|
|
270
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
271
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
272
|
+
]);
|
|
273
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
241
274
|
data: releaseWithCreatorFields
|
|
242
275
|
});
|
|
276
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
|
|
277
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
278
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
279
|
+
}
|
|
280
|
+
return release2;
|
|
243
281
|
},
|
|
244
282
|
async findOne(id, query = {}) {
|
|
245
283
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
@@ -258,51 +296,66 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
258
296
|
}
|
|
259
297
|
});
|
|
260
298
|
},
|
|
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
|
-
}
|
|
299
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
300
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
301
|
+
where: {
|
|
302
|
+
actions: {
|
|
303
|
+
target_type: contentTypeUid,
|
|
304
|
+
target_id: entryId
|
|
282
305
|
},
|
|
283
|
-
{
|
|
284
|
-
|
|
306
|
+
releasedAt: {
|
|
307
|
+
$null: true
|
|
285
308
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
309
|
+
},
|
|
310
|
+
populate: {
|
|
311
|
+
// Filter the action to get only the content type entry
|
|
312
|
+
actions: {
|
|
313
|
+
where: {
|
|
314
|
+
target_type: contentTypeUid,
|
|
315
|
+
target_id: entryId
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
return releases.map((release2) => {
|
|
321
|
+
if (release2.actions?.length) {
|
|
322
|
+
const [actionForEntry] = release2.actions;
|
|
323
|
+
delete release2.actions;
|
|
324
|
+
return {
|
|
325
|
+
...release2,
|
|
326
|
+
action: actionForEntry
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return release2;
|
|
330
|
+
});
|
|
331
|
+
},
|
|
332
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
333
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
334
|
+
where: {
|
|
335
|
+
releasedAt: {
|
|
336
|
+
$null: true
|
|
337
|
+
},
|
|
338
|
+
actions: {
|
|
292
339
|
target_type: contentTypeUid,
|
|
293
340
|
target_id: entryId
|
|
294
341
|
}
|
|
295
342
|
}
|
|
296
|
-
}
|
|
343
|
+
});
|
|
297
344
|
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
298
345
|
where: {
|
|
299
|
-
|
|
346
|
+
$or: [
|
|
347
|
+
{
|
|
348
|
+
id: {
|
|
349
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
actions: null
|
|
354
|
+
}
|
|
355
|
+
],
|
|
300
356
|
releasedAt: {
|
|
301
357
|
$null: true
|
|
302
358
|
}
|
|
303
|
-
},
|
|
304
|
-
populate: {
|
|
305
|
-
...populateAttachedAction
|
|
306
359
|
}
|
|
307
360
|
});
|
|
308
361
|
return releases.map((release2) => {
|
|
@@ -319,6 +372,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
319
372
|
},
|
|
320
373
|
async update(id, releaseData, { user }) {
|
|
321
374
|
const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(releaseData);
|
|
375
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
376
|
+
"release-validation",
|
|
377
|
+
{ strapi: strapi2 }
|
|
378
|
+
);
|
|
379
|
+
await Promise.all([
|
|
380
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
381
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
382
|
+
]);
|
|
322
383
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
323
384
|
if (!release2) {
|
|
324
385
|
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
@@ -334,6 +395,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
334
395
|
// @ts-expect-error see above
|
|
335
396
|
data: releaseWithCreatorFields
|
|
336
397
|
});
|
|
398
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
399
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
400
|
+
if (releaseData.scheduledAt) {
|
|
401
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
402
|
+
} else if (release2.scheduledAt) {
|
|
403
|
+
schedulingService.cancel(id);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
337
406
|
return updatedRelease;
|
|
338
407
|
},
|
|
339
408
|
async createAction(releaseId, action) {
|
|
@@ -501,7 +570,9 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
501
570
|
populate: {
|
|
502
571
|
actions: {
|
|
503
572
|
populate: {
|
|
504
|
-
entry:
|
|
573
|
+
entry: {
|
|
574
|
+
fields: ["id"]
|
|
575
|
+
}
|
|
505
576
|
}
|
|
506
577
|
}
|
|
507
578
|
}
|
|
@@ -516,30 +587,80 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
516
587
|
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
517
588
|
throw new errors.ValidationError("No entries to publish");
|
|
518
589
|
}
|
|
519
|
-
const
|
|
590
|
+
const collectionTypeActions = {};
|
|
591
|
+
const singleTypeActions = [];
|
|
520
592
|
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
521
593
|
const contentTypeUid = action.contentType;
|
|
522
|
-
if (
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
594
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
595
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
596
|
+
collectionTypeActions[contentTypeUid] = {
|
|
597
|
+
entriestoPublishIds: [],
|
|
598
|
+
entriesToUnpublishIds: []
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
if (action.type === "publish") {
|
|
602
|
+
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
603
|
+
} else {
|
|
604
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
605
|
+
}
|
|
530
606
|
} else {
|
|
531
|
-
|
|
607
|
+
singleTypeActions.push({
|
|
608
|
+
uid: contentTypeUid,
|
|
609
|
+
action: action.type,
|
|
610
|
+
id: action.entry.id
|
|
611
|
+
});
|
|
532
612
|
}
|
|
533
613
|
}
|
|
534
614
|
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
615
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
535
616
|
await strapi2.db.transaction(async () => {
|
|
536
|
-
for (const
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
617
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
618
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
619
|
+
const entry = await strapi2.entityService.findOne(uid, id, { populate });
|
|
620
|
+
try {
|
|
621
|
+
if (action === "publish") {
|
|
622
|
+
await entityManagerService.publish(entry, uid);
|
|
623
|
+
} else {
|
|
624
|
+
await entityManagerService.unpublish(entry, uid);
|
|
625
|
+
}
|
|
626
|
+
} catch (error) {
|
|
627
|
+
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
628
|
+
;
|
|
629
|
+
else {
|
|
630
|
+
throw error;
|
|
631
|
+
}
|
|
540
632
|
}
|
|
541
|
-
|
|
542
|
-
|
|
633
|
+
}
|
|
634
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
635
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
636
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
637
|
+
const entriesToPublish = await strapi2.entityService.findMany(
|
|
638
|
+
contentTypeUid,
|
|
639
|
+
{
|
|
640
|
+
filters: {
|
|
641
|
+
id: {
|
|
642
|
+
$in: entriestoPublishIds
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
populate
|
|
646
|
+
}
|
|
647
|
+
);
|
|
648
|
+
const entriesToUnpublish = await strapi2.entityService.findMany(
|
|
649
|
+
contentTypeUid,
|
|
650
|
+
{
|
|
651
|
+
filters: {
|
|
652
|
+
id: {
|
|
653
|
+
$in: entriesToUnpublishIds
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
populate
|
|
657
|
+
}
|
|
658
|
+
);
|
|
659
|
+
if (entriesToPublish.length > 0) {
|
|
660
|
+
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
661
|
+
}
|
|
662
|
+
if (entriesToUnpublish.length > 0) {
|
|
663
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
|
|
543
664
|
}
|
|
544
665
|
}
|
|
545
666
|
});
|
|
@@ -637,34 +758,89 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
637
758
|
if (pendingReleasesCount >= maximumPendingReleases) {
|
|
638
759
|
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
639
760
|
}
|
|
761
|
+
},
|
|
762
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
763
|
+
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
764
|
+
filters: {
|
|
765
|
+
releasedAt: {
|
|
766
|
+
$null: true
|
|
767
|
+
},
|
|
768
|
+
name,
|
|
769
|
+
...id && { id: { $ne: id } }
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
const isNameUnique = pendingReleases.length === 0;
|
|
773
|
+
if (!isNameUnique) {
|
|
774
|
+
throw new errors.ValidationError(`Release with name ${name} already exists`);
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
778
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
779
|
+
throw new errors.ValidationError("Scheduled at must be later than now");
|
|
780
|
+
}
|
|
640
781
|
}
|
|
641
782
|
});
|
|
642
|
-
const
|
|
643
|
-
const
|
|
644
|
-
destroyListenerCallbacks: []
|
|
645
|
-
};
|
|
783
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
784
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
646
785
|
return {
|
|
647
|
-
|
|
648
|
-
|
|
786
|
+
async set(releaseId, scheduleDate) {
|
|
787
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
788
|
+
if (!release2) {
|
|
789
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
790
|
+
}
|
|
791
|
+
const job = scheduleJob(scheduleDate, async () => {
|
|
792
|
+
try {
|
|
793
|
+
await getService("release").publish(releaseId);
|
|
794
|
+
} catch (error) {
|
|
795
|
+
}
|
|
796
|
+
this.cancel(releaseId);
|
|
797
|
+
});
|
|
798
|
+
if (scheduledJobs.has(releaseId)) {
|
|
799
|
+
this.cancel(releaseId);
|
|
800
|
+
}
|
|
801
|
+
scheduledJobs.set(releaseId, job);
|
|
802
|
+
return scheduledJobs;
|
|
649
803
|
},
|
|
650
|
-
|
|
651
|
-
if (
|
|
652
|
-
|
|
804
|
+
cancel(releaseId) {
|
|
805
|
+
if (scheduledJobs.has(releaseId)) {
|
|
806
|
+
scheduledJobs.get(releaseId).cancel();
|
|
807
|
+
scheduledJobs.delete(releaseId);
|
|
653
808
|
}
|
|
654
|
-
|
|
655
|
-
|
|
809
|
+
return scheduledJobs;
|
|
810
|
+
},
|
|
811
|
+
getAll() {
|
|
812
|
+
return scheduledJobs;
|
|
813
|
+
},
|
|
814
|
+
/**
|
|
815
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
816
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
817
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
818
|
+
*/
|
|
819
|
+
async syncFromDatabase() {
|
|
820
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
821
|
+
where: {
|
|
822
|
+
scheduledAt: {
|
|
823
|
+
$gte: /* @__PURE__ */ new Date()
|
|
824
|
+
},
|
|
825
|
+
releasedAt: null
|
|
826
|
+
}
|
|
656
827
|
});
|
|
828
|
+
for (const release2 of releases) {
|
|
829
|
+
this.set(release2.id, release2.scheduledAt);
|
|
830
|
+
}
|
|
831
|
+
return scheduledJobs;
|
|
657
832
|
}
|
|
658
833
|
};
|
|
659
834
|
};
|
|
660
835
|
const services = {
|
|
661
836
|
release: createReleaseService,
|
|
662
|
-
"release-action": createReleaseActionService,
|
|
663
837
|
"release-validation": createReleaseValidationService,
|
|
664
|
-
"
|
|
838
|
+
...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
|
|
665
839
|
};
|
|
666
840
|
const RELEASE_SCHEMA = yup.object().shape({
|
|
667
|
-
name: yup.string().trim().required()
|
|
841
|
+
name: yup.string().trim().required(),
|
|
842
|
+
// scheduledAt is a date, but we always receive strings from the client
|
|
843
|
+
scheduledAt: yup.string().nullable()
|
|
668
844
|
}).required().noUnknown();
|
|
669
845
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
670
846
|
const releaseController = {
|
|
@@ -681,9 +857,7 @@ const releaseController = {
|
|
|
681
857
|
const contentTypeUid = query.contentTypeUid;
|
|
682
858
|
const entryId = query.entryId;
|
|
683
859
|
const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
|
|
684
|
-
const data = await releaseService.
|
|
685
|
-
hasEntryAttached
|
|
686
|
-
});
|
|
860
|
+
const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
|
|
687
861
|
ctx.body = { data };
|
|
688
862
|
} else {
|
|
689
863
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
@@ -706,19 +880,18 @@ const releaseController = {
|
|
|
706
880
|
const id = ctx.params.id;
|
|
707
881
|
const releaseService = getService("release", { strapi });
|
|
708
882
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
});
|
|
713
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
883
|
+
if (!release2) {
|
|
884
|
+
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
885
|
+
}
|
|
714
886
|
const count = await releaseService.countActions({
|
|
715
887
|
filters: {
|
|
716
888
|
release: id
|
|
717
889
|
}
|
|
718
890
|
});
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
891
|
+
const sanitizedRelease = {
|
|
892
|
+
...release2,
|
|
893
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
894
|
+
};
|
|
722
895
|
const data = {
|
|
723
896
|
...sanitizedRelease,
|
|
724
897
|
actions: {
|
|
@@ -771,8 +944,27 @@ const releaseController = {
|
|
|
771
944
|
const id = ctx.params.id;
|
|
772
945
|
const releaseService = getService("release", { strapi });
|
|
773
946
|
const release2 = await releaseService.publish(id, { user });
|
|
947
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
948
|
+
releaseService.countActions({
|
|
949
|
+
filters: {
|
|
950
|
+
release: id,
|
|
951
|
+
type: "publish"
|
|
952
|
+
}
|
|
953
|
+
}),
|
|
954
|
+
releaseService.countActions({
|
|
955
|
+
filters: {
|
|
956
|
+
release: id,
|
|
957
|
+
type: "unpublish"
|
|
958
|
+
}
|
|
959
|
+
})
|
|
960
|
+
]);
|
|
774
961
|
ctx.body = {
|
|
775
|
-
data: release2
|
|
962
|
+
data: release2,
|
|
963
|
+
meta: {
|
|
964
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
965
|
+
totalPublishedEntries: countPublishActions,
|
|
966
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
967
|
+
}
|
|
776
968
|
};
|
|
777
969
|
}
|
|
778
970
|
};
|
|
@@ -1040,19 +1232,15 @@ const routes = {
|
|
|
1040
1232
|
};
|
|
1041
1233
|
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
1042
1234
|
const getPlugin = () => {
|
|
1043
|
-
if (features.isEnabled("cms-content-releases")
|
|
1235
|
+
if (features.isEnabled("cms-content-releases")) {
|
|
1044
1236
|
return {
|
|
1045
1237
|
register,
|
|
1046
1238
|
bootstrap,
|
|
1239
|
+
destroy,
|
|
1047
1240
|
contentTypes,
|
|
1048
1241
|
services,
|
|
1049
1242
|
controllers,
|
|
1050
|
-
routes
|
|
1051
|
-
destroy() {
|
|
1052
|
-
if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
|
|
1053
|
-
getService("event-manager").destroyAllListeners();
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1243
|
+
routes
|
|
1056
1244
|
};
|
|
1057
1245
|
}
|
|
1058
1246
|
return {
|