@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.js
CHANGED
|
@@ -3,6 +3,7 @@ const utils = require("@strapi/utils");
|
|
|
3
3
|
const lodash = require("lodash");
|
|
4
4
|
const _ = require("lodash/fp");
|
|
5
5
|
const EE = require("@strapi/strapi/dist/utils/ee");
|
|
6
|
+
const nodeSchedule = require("node-schedule");
|
|
6
7
|
const yup = require("yup");
|
|
7
8
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
8
9
|
function _interopNamespace(e) {
|
|
@@ -106,6 +107,9 @@ const register = async ({ strapi: strapi2 }) => {
|
|
|
106
107
|
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
|
|
107
108
|
}
|
|
108
109
|
};
|
|
110
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
111
|
+
return strapi2.plugin("content-releases").service(name);
|
|
112
|
+
};
|
|
109
113
|
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
110
114
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
111
115
|
if (features$1.isEnabled("cms-content-releases")) {
|
|
@@ -153,6 +157,24 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
153
157
|
}
|
|
154
158
|
}
|
|
155
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
|
+
}
|
|
156
178
|
}
|
|
157
179
|
};
|
|
158
180
|
const schema$1 = {
|
|
@@ -181,6 +203,12 @@ const schema$1 = {
|
|
|
181
203
|
releasedAt: {
|
|
182
204
|
type: "datetime"
|
|
183
205
|
},
|
|
206
|
+
scheduledAt: {
|
|
207
|
+
type: "datetime"
|
|
208
|
+
},
|
|
209
|
+
timezone: {
|
|
210
|
+
type: "string"
|
|
211
|
+
},
|
|
184
212
|
actions: {
|
|
185
213
|
type: "relation",
|
|
186
214
|
relation: "oneToMany",
|
|
@@ -243,9 +271,6 @@ const contentTypes = {
|
|
|
243
271
|
release: release$1,
|
|
244
272
|
"release-action": releaseAction$1
|
|
245
273
|
};
|
|
246
|
-
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
247
|
-
return strapi2.plugin("content-releases").service(name);
|
|
248
|
-
};
|
|
249
274
|
const getGroupName = (queryValue) => {
|
|
250
275
|
switch (queryValue) {
|
|
251
276
|
case "contentType":
|
|
@@ -261,17 +286,24 @@ const getGroupName = (queryValue) => {
|
|
|
261
286
|
const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
262
287
|
async create(releaseData, { user }) {
|
|
263
288
|
const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
|
|
264
|
-
const {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
289
|
+
const {
|
|
290
|
+
validatePendingReleasesLimit,
|
|
291
|
+
validateUniqueNameForPendingRelease,
|
|
292
|
+
validateScheduledAtIsLaterThanNow
|
|
293
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
268
294
|
await Promise.all([
|
|
269
295
|
validatePendingReleasesLimit(),
|
|
270
|
-
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name)
|
|
296
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
297
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
271
298
|
]);
|
|
272
|
-
|
|
299
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
273
300
|
data: releaseWithCreatorFields
|
|
274
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
|
+
return release2;
|
|
275
307
|
},
|
|
276
308
|
async findOne(id, query = {}) {
|
|
277
309
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
@@ -366,6 +398,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
366
398
|
},
|
|
367
399
|
async update(id, releaseData, { user }) {
|
|
368
400
|
const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(releaseData);
|
|
401
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
402
|
+
"release-validation",
|
|
403
|
+
{ strapi: strapi2 }
|
|
404
|
+
);
|
|
405
|
+
await Promise.all([
|
|
406
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
407
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
408
|
+
]);
|
|
369
409
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
370
410
|
if (!release2) {
|
|
371
411
|
throw new utils.errors.NotFoundError(`No release found for id ${id}`);
|
|
@@ -381,6 +421,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
381
421
|
// @ts-expect-error see above
|
|
382
422
|
data: releaseWithCreatorFields
|
|
383
423
|
});
|
|
424
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
425
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
426
|
+
if (releaseData.scheduledAt) {
|
|
427
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
428
|
+
} else if (release2.scheduledAt) {
|
|
429
|
+
schedulingService.cancel(id);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
384
432
|
return updatedRelease;
|
|
385
433
|
},
|
|
386
434
|
async createAction(releaseId, action) {
|
|
@@ -565,27 +613,53 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
565
613
|
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
566
614
|
throw new utils.errors.ValidationError("No entries to publish");
|
|
567
615
|
}
|
|
568
|
-
const
|
|
616
|
+
const collectionTypeActions = {};
|
|
617
|
+
const singleTypeActions = [];
|
|
569
618
|
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
570
619
|
const contentTypeUid = action.contentType;
|
|
571
|
-
if (
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
620
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
621
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
622
|
+
collectionTypeActions[contentTypeUid] = {
|
|
623
|
+
entriestoPublishIds: [],
|
|
624
|
+
entriesToUnpublishIds: []
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
if (action.type === "publish") {
|
|
628
|
+
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
629
|
+
} else {
|
|
630
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
631
|
+
}
|
|
579
632
|
} else {
|
|
580
|
-
|
|
633
|
+
singleTypeActions.push({
|
|
634
|
+
uid: contentTypeUid,
|
|
635
|
+
action: action.type,
|
|
636
|
+
id: action.entry.id
|
|
637
|
+
});
|
|
581
638
|
}
|
|
582
639
|
}
|
|
583
640
|
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
584
641
|
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
585
642
|
await strapi2.db.transaction(async () => {
|
|
586
|
-
for (const
|
|
643
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
644
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
645
|
+
const entry = await strapi2.entityService.findOne(uid, id, { populate });
|
|
646
|
+
try {
|
|
647
|
+
if (action === "publish") {
|
|
648
|
+
await entityManagerService.publish(entry, uid);
|
|
649
|
+
} else {
|
|
650
|
+
await entityManagerService.unpublish(entry, uid);
|
|
651
|
+
}
|
|
652
|
+
} catch (error) {
|
|
653
|
+
if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
654
|
+
;
|
|
655
|
+
else {
|
|
656
|
+
throw error;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
587
661
|
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
588
|
-
const { entriestoPublishIds, entriesToUnpublishIds } =
|
|
662
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
589
663
|
const entriesToPublish = await strapi2.entityService.findMany(
|
|
590
664
|
contentTypeUid,
|
|
591
665
|
{
|
|
@@ -711,27 +785,93 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
711
785
|
throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
|
|
712
786
|
}
|
|
713
787
|
},
|
|
714
|
-
async validateUniqueNameForPendingRelease(name) {
|
|
788
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
715
789
|
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
716
790
|
filters: {
|
|
717
791
|
releasedAt: {
|
|
718
792
|
$null: true
|
|
719
793
|
},
|
|
720
|
-
name
|
|
794
|
+
name,
|
|
795
|
+
...id && { id: { $ne: id } }
|
|
721
796
|
}
|
|
722
797
|
});
|
|
723
798
|
const isNameUnique = pendingReleases.length === 0;
|
|
724
799
|
if (!isNameUnique) {
|
|
725
800
|
throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
|
|
726
801
|
}
|
|
802
|
+
},
|
|
803
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
804
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
805
|
+
throw new utils.errors.ValidationError("Scheduled at must be later than now");
|
|
806
|
+
}
|
|
727
807
|
}
|
|
728
808
|
});
|
|
809
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
810
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
811
|
+
return {
|
|
812
|
+
async set(releaseId, scheduleDate) {
|
|
813
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
814
|
+
if (!release2) {
|
|
815
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
816
|
+
}
|
|
817
|
+
const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
|
|
818
|
+
try {
|
|
819
|
+
await getService("release").publish(releaseId);
|
|
820
|
+
} catch (error) {
|
|
821
|
+
}
|
|
822
|
+
this.cancel(releaseId);
|
|
823
|
+
});
|
|
824
|
+
if (scheduledJobs.has(releaseId)) {
|
|
825
|
+
this.cancel(releaseId);
|
|
826
|
+
}
|
|
827
|
+
scheduledJobs.set(releaseId, job);
|
|
828
|
+
return scheduledJobs;
|
|
829
|
+
},
|
|
830
|
+
cancel(releaseId) {
|
|
831
|
+
if (scheduledJobs.has(releaseId)) {
|
|
832
|
+
scheduledJobs.get(releaseId).cancel();
|
|
833
|
+
scheduledJobs.delete(releaseId);
|
|
834
|
+
}
|
|
835
|
+
return scheduledJobs;
|
|
836
|
+
},
|
|
837
|
+
getAll() {
|
|
838
|
+
return scheduledJobs;
|
|
839
|
+
},
|
|
840
|
+
/**
|
|
841
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
842
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
843
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
844
|
+
*/
|
|
845
|
+
async syncFromDatabase() {
|
|
846
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
847
|
+
where: {
|
|
848
|
+
scheduledAt: {
|
|
849
|
+
$gte: /* @__PURE__ */ new Date()
|
|
850
|
+
},
|
|
851
|
+
releasedAt: null
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
for (const release2 of releases) {
|
|
855
|
+
this.set(release2.id, release2.scheduledAt);
|
|
856
|
+
}
|
|
857
|
+
return scheduledJobs;
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
};
|
|
729
861
|
const services = {
|
|
730
862
|
release: createReleaseService,
|
|
731
|
-
"release-validation": createReleaseValidationService
|
|
863
|
+
"release-validation": createReleaseValidationService,
|
|
864
|
+
...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
|
|
732
865
|
};
|
|
733
866
|
const RELEASE_SCHEMA = yup__namespace.object().shape({
|
|
734
|
-
name: yup__namespace.string().trim().required()
|
|
867
|
+
name: yup__namespace.string().trim().required(),
|
|
868
|
+
// scheduledAt is a date, but we always receive strings from the client
|
|
869
|
+
scheduledAt: yup__namespace.string().nullable(),
|
|
870
|
+
timezone: yup__namespace.string().when("scheduledAt", {
|
|
871
|
+
is: (scheduledAt) => !!scheduledAt,
|
|
872
|
+
then: yup__namespace.string().required(),
|
|
873
|
+
otherwise: yup__namespace.string().nullable()
|
|
874
|
+
})
|
|
735
875
|
}).required().noUnknown();
|
|
736
876
|
const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
|
|
737
877
|
const releaseController = {
|
|
@@ -771,19 +911,18 @@ const releaseController = {
|
|
|
771
911
|
const id = ctx.params.id;
|
|
772
912
|
const releaseService = getService("release", { strapi });
|
|
773
913
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
});
|
|
778
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
914
|
+
if (!release2) {
|
|
915
|
+
throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
|
|
916
|
+
}
|
|
779
917
|
const count = await releaseService.countActions({
|
|
780
918
|
filters: {
|
|
781
919
|
release: id
|
|
782
920
|
}
|
|
783
921
|
});
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
922
|
+
const sanitizedRelease = {
|
|
923
|
+
...release2,
|
|
924
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
925
|
+
};
|
|
787
926
|
const data = {
|
|
788
927
|
...sanitizedRelease,
|
|
789
928
|
actions: {
|
|
@@ -836,8 +975,27 @@ const releaseController = {
|
|
|
836
975
|
const id = ctx.params.id;
|
|
837
976
|
const releaseService = getService("release", { strapi });
|
|
838
977
|
const release2 = await releaseService.publish(id, { user });
|
|
978
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
979
|
+
releaseService.countActions({
|
|
980
|
+
filters: {
|
|
981
|
+
release: id,
|
|
982
|
+
type: "publish"
|
|
983
|
+
}
|
|
984
|
+
}),
|
|
985
|
+
releaseService.countActions({
|
|
986
|
+
filters: {
|
|
987
|
+
release: id,
|
|
988
|
+
type: "unpublish"
|
|
989
|
+
}
|
|
990
|
+
})
|
|
991
|
+
]);
|
|
839
992
|
ctx.body = {
|
|
840
|
-
data: release2
|
|
993
|
+
data: release2,
|
|
994
|
+
meta: {
|
|
995
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
996
|
+
totalPublishedEntries: countPublishActions,
|
|
997
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
998
|
+
}
|
|
841
999
|
};
|
|
842
1000
|
}
|
|
843
1001
|
};
|
|
@@ -1109,6 +1267,7 @@ const getPlugin = () => {
|
|
|
1109
1267
|
return {
|
|
1110
1268
|
register,
|
|
1111
1269
|
bootstrap,
|
|
1270
|
+
destroy,
|
|
1112
1271
|
contentTypes,
|
|
1113
1272
|
services,
|
|
1114
1273
|
controllers,
|