@strapi/content-releases 4.19.1 → 4.20.1
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-_20W9dYa.js → App-1hHIqUoZ.js} +253 -191
- package/dist/_chunks/App-1hHIqUoZ.js.map +1 -0
- package/dist/_chunks/{App-L1jSxCiL.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-c4zRX_sg.mjs → index-gkExFBa0.mjs} +89 -26
- package/dist/_chunks/index-gkExFBa0.mjs.map +1 -0
- package/dist/_chunks/{index-KJa1Rb5F.js → index-l-FvkQlQ.js} +88 -25
- 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 +193 -34
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +193 -34
- package/dist/server/index.mjs.map +1 -1
- package/package.json +10 -9
- package/dist/_chunks/App-L1jSxCiL.mjs.map +0 -1
- package/dist/_chunks/App-_20W9dYa.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-KJa1Rb5F.js.map +0 -1
- package/dist/_chunks/index-c4zRX_sg.mjs.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { contentTypes as contentTypes$1, mapAsync, setCreatorFields, errors, val
|
|
|
2
2
|
import { difference, keys } from "lodash";
|
|
3
3
|
import _ from "lodash/fp";
|
|
4
4
|
import EE from "@strapi/strapi/dist/utils/ee";
|
|
5
|
+
import { scheduleJob } from "node-schedule";
|
|
5
6
|
import * as yup from "yup";
|
|
6
7
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
7
8
|
const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
|
|
@@ -83,6 +84,9 @@ const register = async ({ strapi: strapi2 }) => {
|
|
|
83
84
|
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
|
|
84
85
|
}
|
|
85
86
|
};
|
|
87
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
88
|
+
return strapi2.plugin("content-releases").service(name);
|
|
89
|
+
};
|
|
86
90
|
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
87
91
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
88
92
|
if (features$1.isEnabled("cms-content-releases")) {
|
|
@@ -130,6 +134,24 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
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
|
+
}
|
|
133
155
|
}
|
|
134
156
|
};
|
|
135
157
|
const schema$1 = {
|
|
@@ -158,6 +180,12 @@ const schema$1 = {
|
|
|
158
180
|
releasedAt: {
|
|
159
181
|
type: "datetime"
|
|
160
182
|
},
|
|
183
|
+
scheduledAt: {
|
|
184
|
+
type: "datetime"
|
|
185
|
+
},
|
|
186
|
+
timezone: {
|
|
187
|
+
type: "string"
|
|
188
|
+
},
|
|
161
189
|
actions: {
|
|
162
190
|
type: "relation",
|
|
163
191
|
relation: "oneToMany",
|
|
@@ -220,9 +248,6 @@ const contentTypes = {
|
|
|
220
248
|
release: release$1,
|
|
221
249
|
"release-action": releaseAction$1
|
|
222
250
|
};
|
|
223
|
-
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
224
|
-
return strapi2.plugin("content-releases").service(name);
|
|
225
|
-
};
|
|
226
251
|
const getGroupName = (queryValue) => {
|
|
227
252
|
switch (queryValue) {
|
|
228
253
|
case "contentType":
|
|
@@ -238,17 +263,24 @@ const getGroupName = (queryValue) => {
|
|
|
238
263
|
const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
239
264
|
async create(releaseData, { user }) {
|
|
240
265
|
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
241
|
-
const {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
266
|
+
const {
|
|
267
|
+
validatePendingReleasesLimit,
|
|
268
|
+
validateUniqueNameForPendingRelease,
|
|
269
|
+
validateScheduledAtIsLaterThanNow
|
|
270
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
245
271
|
await Promise.all([
|
|
246
272
|
validatePendingReleasesLimit(),
|
|
247
|
-
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name)
|
|
273
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
274
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
248
275
|
]);
|
|
249
|
-
|
|
276
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
250
277
|
data: releaseWithCreatorFields
|
|
251
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
|
+
return release2;
|
|
252
284
|
},
|
|
253
285
|
async findOne(id, query = {}) {
|
|
254
286
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
@@ -343,6 +375,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
343
375
|
},
|
|
344
376
|
async update(id, releaseData, { user }) {
|
|
345
377
|
const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(releaseData);
|
|
378
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
379
|
+
"release-validation",
|
|
380
|
+
{ strapi: strapi2 }
|
|
381
|
+
);
|
|
382
|
+
await Promise.all([
|
|
383
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
384
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
385
|
+
]);
|
|
346
386
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
347
387
|
if (!release2) {
|
|
348
388
|
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
@@ -358,6 +398,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
358
398
|
// @ts-expect-error see above
|
|
359
399
|
data: releaseWithCreatorFields
|
|
360
400
|
});
|
|
401
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
402
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
403
|
+
if (releaseData.scheduledAt) {
|
|
404
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
405
|
+
} else if (release2.scheduledAt) {
|
|
406
|
+
schedulingService.cancel(id);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
361
409
|
return updatedRelease;
|
|
362
410
|
},
|
|
363
411
|
async createAction(releaseId, action) {
|
|
@@ -542,27 +590,53 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
542
590
|
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
543
591
|
throw new errors.ValidationError("No entries to publish");
|
|
544
592
|
}
|
|
545
|
-
const
|
|
593
|
+
const collectionTypeActions = {};
|
|
594
|
+
const singleTypeActions = [];
|
|
546
595
|
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
547
596
|
const contentTypeUid = action.contentType;
|
|
548
|
-
if (
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
597
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
598
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
599
|
+
collectionTypeActions[contentTypeUid] = {
|
|
600
|
+
entriestoPublishIds: [],
|
|
601
|
+
entriesToUnpublishIds: []
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
if (action.type === "publish") {
|
|
605
|
+
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
606
|
+
} else {
|
|
607
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
608
|
+
}
|
|
556
609
|
} else {
|
|
557
|
-
|
|
610
|
+
singleTypeActions.push({
|
|
611
|
+
uid: contentTypeUid,
|
|
612
|
+
action: action.type,
|
|
613
|
+
id: action.entry.id
|
|
614
|
+
});
|
|
558
615
|
}
|
|
559
616
|
}
|
|
560
617
|
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
561
618
|
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
562
619
|
await strapi2.db.transaction(async () => {
|
|
563
|
-
for (const
|
|
620
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
621
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
622
|
+
const entry = await strapi2.entityService.findOne(uid, id, { populate });
|
|
623
|
+
try {
|
|
624
|
+
if (action === "publish") {
|
|
625
|
+
await entityManagerService.publish(entry, uid);
|
|
626
|
+
} else {
|
|
627
|
+
await entityManagerService.unpublish(entry, uid);
|
|
628
|
+
}
|
|
629
|
+
} catch (error) {
|
|
630
|
+
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
631
|
+
;
|
|
632
|
+
else {
|
|
633
|
+
throw error;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
564
638
|
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
565
|
-
const { entriestoPublishIds, entriesToUnpublishIds } =
|
|
639
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
566
640
|
const entriesToPublish = await strapi2.entityService.findMany(
|
|
567
641
|
contentTypeUid,
|
|
568
642
|
{
|
|
@@ -688,27 +762,93 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
688
762
|
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
689
763
|
}
|
|
690
764
|
},
|
|
691
|
-
async validateUniqueNameForPendingRelease(name) {
|
|
765
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
692
766
|
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
693
767
|
filters: {
|
|
694
768
|
releasedAt: {
|
|
695
769
|
$null: true
|
|
696
770
|
},
|
|
697
|
-
name
|
|
771
|
+
name,
|
|
772
|
+
...id && { id: { $ne: id } }
|
|
698
773
|
}
|
|
699
774
|
});
|
|
700
775
|
const isNameUnique = pendingReleases.length === 0;
|
|
701
776
|
if (!isNameUnique) {
|
|
702
777
|
throw new errors.ValidationError(`Release with name ${name} already exists`);
|
|
703
778
|
}
|
|
779
|
+
},
|
|
780
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
781
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
782
|
+
throw new errors.ValidationError("Scheduled at must be later than now");
|
|
783
|
+
}
|
|
704
784
|
}
|
|
705
785
|
});
|
|
786
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
787
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
788
|
+
return {
|
|
789
|
+
async set(releaseId, scheduleDate) {
|
|
790
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
791
|
+
if (!release2) {
|
|
792
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
793
|
+
}
|
|
794
|
+
const job = scheduleJob(scheduleDate, async () => {
|
|
795
|
+
try {
|
|
796
|
+
await getService("release").publish(releaseId);
|
|
797
|
+
} catch (error) {
|
|
798
|
+
}
|
|
799
|
+
this.cancel(releaseId);
|
|
800
|
+
});
|
|
801
|
+
if (scheduledJobs.has(releaseId)) {
|
|
802
|
+
this.cancel(releaseId);
|
|
803
|
+
}
|
|
804
|
+
scheduledJobs.set(releaseId, job);
|
|
805
|
+
return scheduledJobs;
|
|
806
|
+
},
|
|
807
|
+
cancel(releaseId) {
|
|
808
|
+
if (scheduledJobs.has(releaseId)) {
|
|
809
|
+
scheduledJobs.get(releaseId).cancel();
|
|
810
|
+
scheduledJobs.delete(releaseId);
|
|
811
|
+
}
|
|
812
|
+
return scheduledJobs;
|
|
813
|
+
},
|
|
814
|
+
getAll() {
|
|
815
|
+
return scheduledJobs;
|
|
816
|
+
},
|
|
817
|
+
/**
|
|
818
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
819
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
820
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
821
|
+
*/
|
|
822
|
+
async syncFromDatabase() {
|
|
823
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
824
|
+
where: {
|
|
825
|
+
scheduledAt: {
|
|
826
|
+
$gte: /* @__PURE__ */ new Date()
|
|
827
|
+
},
|
|
828
|
+
releasedAt: null
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
for (const release2 of releases) {
|
|
832
|
+
this.set(release2.id, release2.scheduledAt);
|
|
833
|
+
}
|
|
834
|
+
return scheduledJobs;
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
};
|
|
706
838
|
const services = {
|
|
707
839
|
release: createReleaseService,
|
|
708
|
-
"release-validation": createReleaseValidationService
|
|
840
|
+
"release-validation": createReleaseValidationService,
|
|
841
|
+
...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
|
|
709
842
|
};
|
|
710
843
|
const RELEASE_SCHEMA = yup.object().shape({
|
|
711
|
-
name: yup.string().trim().required()
|
|
844
|
+
name: yup.string().trim().required(),
|
|
845
|
+
// scheduledAt is a date, but we always receive strings from the client
|
|
846
|
+
scheduledAt: yup.string().nullable(),
|
|
847
|
+
timezone: yup.string().when("scheduledAt", {
|
|
848
|
+
is: (scheduledAt) => !!scheduledAt,
|
|
849
|
+
then: yup.string().required(),
|
|
850
|
+
otherwise: yup.string().nullable()
|
|
851
|
+
})
|
|
712
852
|
}).required().noUnknown();
|
|
713
853
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
714
854
|
const releaseController = {
|
|
@@ -748,19 +888,18 @@ const releaseController = {
|
|
|
748
888
|
const id = ctx.params.id;
|
|
749
889
|
const releaseService = getService("release", { strapi });
|
|
750
890
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
});
|
|
755
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
891
|
+
if (!release2) {
|
|
892
|
+
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
893
|
+
}
|
|
756
894
|
const count = await releaseService.countActions({
|
|
757
895
|
filters: {
|
|
758
896
|
release: id
|
|
759
897
|
}
|
|
760
898
|
});
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
899
|
+
const sanitizedRelease = {
|
|
900
|
+
...release2,
|
|
901
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
902
|
+
};
|
|
764
903
|
const data = {
|
|
765
904
|
...sanitizedRelease,
|
|
766
905
|
actions: {
|
|
@@ -813,8 +952,27 @@ const releaseController = {
|
|
|
813
952
|
const id = ctx.params.id;
|
|
814
953
|
const releaseService = getService("release", { strapi });
|
|
815
954
|
const release2 = await releaseService.publish(id, { user });
|
|
955
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
956
|
+
releaseService.countActions({
|
|
957
|
+
filters: {
|
|
958
|
+
release: id,
|
|
959
|
+
type: "publish"
|
|
960
|
+
}
|
|
961
|
+
}),
|
|
962
|
+
releaseService.countActions({
|
|
963
|
+
filters: {
|
|
964
|
+
release: id,
|
|
965
|
+
type: "unpublish"
|
|
966
|
+
}
|
|
967
|
+
})
|
|
968
|
+
]);
|
|
816
969
|
ctx.body = {
|
|
817
|
-
data: release2
|
|
970
|
+
data: release2,
|
|
971
|
+
meta: {
|
|
972
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
973
|
+
totalPublishedEntries: countPublishActions,
|
|
974
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
975
|
+
}
|
|
818
976
|
};
|
|
819
977
|
}
|
|
820
978
|
};
|
|
@@ -1086,6 +1244,7 @@ const getPlugin = () => {
|
|
|
1086
1244
|
return {
|
|
1087
1245
|
register,
|
|
1088
1246
|
bootstrap,
|
|
1247
|
+
destroy,
|
|
1089
1248
|
contentTypes,
|
|
1090
1249
|
services,
|
|
1091
1250
|
controllers,
|