@strapi/content-releases 0.0.0-next.6d59515520a3850456f256fb0e4c54b75054ddf4 → 0.0.0-next.7abe81e395faf152048bfba0b088b9062e8ac602
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 +197 -34
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +197 -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) {
|
|
@@ -515,6 +563,10 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
515
563
|
});
|
|
516
564
|
await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
|
517
565
|
});
|
|
566
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
|
|
567
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
568
|
+
await schedulingService.cancel(release2.id);
|
|
569
|
+
}
|
|
518
570
|
return release2;
|
|
519
571
|
},
|
|
520
572
|
async publish(releaseId) {
|
|
@@ -542,27 +594,53 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
542
594
|
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
543
595
|
throw new errors.ValidationError("No entries to publish");
|
|
544
596
|
}
|
|
545
|
-
const
|
|
597
|
+
const collectionTypeActions = {};
|
|
598
|
+
const singleTypeActions = [];
|
|
546
599
|
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
547
600
|
const contentTypeUid = action.contentType;
|
|
548
|
-
if (
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
601
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
602
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
603
|
+
collectionTypeActions[contentTypeUid] = {
|
|
604
|
+
entriestoPublishIds: [],
|
|
605
|
+
entriesToUnpublishIds: []
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
if (action.type === "publish") {
|
|
609
|
+
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
610
|
+
} else {
|
|
611
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
612
|
+
}
|
|
556
613
|
} else {
|
|
557
|
-
|
|
614
|
+
singleTypeActions.push({
|
|
615
|
+
uid: contentTypeUid,
|
|
616
|
+
action: action.type,
|
|
617
|
+
id: action.entry.id
|
|
618
|
+
});
|
|
558
619
|
}
|
|
559
620
|
}
|
|
560
621
|
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
561
622
|
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
562
623
|
await strapi2.db.transaction(async () => {
|
|
563
|
-
for (const
|
|
624
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
625
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
626
|
+
const entry = await strapi2.entityService.findOne(uid, id, { populate });
|
|
627
|
+
try {
|
|
628
|
+
if (action === "publish") {
|
|
629
|
+
await entityManagerService.publish(entry, uid);
|
|
630
|
+
} else {
|
|
631
|
+
await entityManagerService.unpublish(entry, uid);
|
|
632
|
+
}
|
|
633
|
+
} catch (error) {
|
|
634
|
+
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
635
|
+
;
|
|
636
|
+
else {
|
|
637
|
+
throw error;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
564
642
|
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
565
|
-
const { entriestoPublishIds, entriesToUnpublishIds } =
|
|
643
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
566
644
|
const entriesToPublish = await strapi2.entityService.findMany(
|
|
567
645
|
contentTypeUid,
|
|
568
646
|
{
|
|
@@ -688,27 +766,93 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
688
766
|
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
689
767
|
}
|
|
690
768
|
},
|
|
691
|
-
async validateUniqueNameForPendingRelease(name) {
|
|
769
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
692
770
|
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
693
771
|
filters: {
|
|
694
772
|
releasedAt: {
|
|
695
773
|
$null: true
|
|
696
774
|
},
|
|
697
|
-
name
|
|
775
|
+
name,
|
|
776
|
+
...id && { id: { $ne: id } }
|
|
698
777
|
}
|
|
699
778
|
});
|
|
700
779
|
const isNameUnique = pendingReleases.length === 0;
|
|
701
780
|
if (!isNameUnique) {
|
|
702
781
|
throw new errors.ValidationError(`Release with name ${name} already exists`);
|
|
703
782
|
}
|
|
783
|
+
},
|
|
784
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
785
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
786
|
+
throw new errors.ValidationError("Scheduled at must be later than now");
|
|
787
|
+
}
|
|
704
788
|
}
|
|
705
789
|
});
|
|
790
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
791
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
792
|
+
return {
|
|
793
|
+
async set(releaseId, scheduleDate) {
|
|
794
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
795
|
+
if (!release2) {
|
|
796
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
797
|
+
}
|
|
798
|
+
const job = scheduleJob(scheduleDate, async () => {
|
|
799
|
+
try {
|
|
800
|
+
await getService("release").publish(releaseId);
|
|
801
|
+
} catch (error) {
|
|
802
|
+
}
|
|
803
|
+
this.cancel(releaseId);
|
|
804
|
+
});
|
|
805
|
+
if (scheduledJobs.has(releaseId)) {
|
|
806
|
+
this.cancel(releaseId);
|
|
807
|
+
}
|
|
808
|
+
scheduledJobs.set(releaseId, job);
|
|
809
|
+
return scheduledJobs;
|
|
810
|
+
},
|
|
811
|
+
cancel(releaseId) {
|
|
812
|
+
if (scheduledJobs.has(releaseId)) {
|
|
813
|
+
scheduledJobs.get(releaseId).cancel();
|
|
814
|
+
scheduledJobs.delete(releaseId);
|
|
815
|
+
}
|
|
816
|
+
return scheduledJobs;
|
|
817
|
+
},
|
|
818
|
+
getAll() {
|
|
819
|
+
return scheduledJobs;
|
|
820
|
+
},
|
|
821
|
+
/**
|
|
822
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
823
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
824
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
825
|
+
*/
|
|
826
|
+
async syncFromDatabase() {
|
|
827
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
828
|
+
where: {
|
|
829
|
+
scheduledAt: {
|
|
830
|
+
$gte: /* @__PURE__ */ new Date()
|
|
831
|
+
},
|
|
832
|
+
releasedAt: null
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
for (const release2 of releases) {
|
|
836
|
+
this.set(release2.id, release2.scheduledAt);
|
|
837
|
+
}
|
|
838
|
+
return scheduledJobs;
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
};
|
|
706
842
|
const services = {
|
|
707
843
|
release: createReleaseService,
|
|
708
|
-
"release-validation": createReleaseValidationService
|
|
844
|
+
"release-validation": createReleaseValidationService,
|
|
845
|
+
...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
|
|
709
846
|
};
|
|
710
847
|
const RELEASE_SCHEMA = yup.object().shape({
|
|
711
|
-
name: yup.string().trim().required()
|
|
848
|
+
name: yup.string().trim().required(),
|
|
849
|
+
// scheduledAt is a date, but we always receive strings from the client
|
|
850
|
+
scheduledAt: yup.string().nullable(),
|
|
851
|
+
timezone: yup.string().when("scheduledAt", {
|
|
852
|
+
is: (scheduledAt) => !!scheduledAt,
|
|
853
|
+
then: yup.string().required(),
|
|
854
|
+
otherwise: yup.string().nullable()
|
|
855
|
+
})
|
|
712
856
|
}).required().noUnknown();
|
|
713
857
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
714
858
|
const releaseController = {
|
|
@@ -748,19 +892,18 @@ const releaseController = {
|
|
|
748
892
|
const id = ctx.params.id;
|
|
749
893
|
const releaseService = getService("release", { strapi });
|
|
750
894
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
});
|
|
755
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
895
|
+
if (!release2) {
|
|
896
|
+
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
897
|
+
}
|
|
756
898
|
const count = await releaseService.countActions({
|
|
757
899
|
filters: {
|
|
758
900
|
release: id
|
|
759
901
|
}
|
|
760
902
|
});
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
903
|
+
const sanitizedRelease = {
|
|
904
|
+
...release2,
|
|
905
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
906
|
+
};
|
|
764
907
|
const data = {
|
|
765
908
|
...sanitizedRelease,
|
|
766
909
|
actions: {
|
|
@@ -813,8 +956,27 @@ const releaseController = {
|
|
|
813
956
|
const id = ctx.params.id;
|
|
814
957
|
const releaseService = getService("release", { strapi });
|
|
815
958
|
const release2 = await releaseService.publish(id, { user });
|
|
959
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
960
|
+
releaseService.countActions({
|
|
961
|
+
filters: {
|
|
962
|
+
release: id,
|
|
963
|
+
type: "publish"
|
|
964
|
+
}
|
|
965
|
+
}),
|
|
966
|
+
releaseService.countActions({
|
|
967
|
+
filters: {
|
|
968
|
+
release: id,
|
|
969
|
+
type: "unpublish"
|
|
970
|
+
}
|
|
971
|
+
})
|
|
972
|
+
]);
|
|
816
973
|
ctx.body = {
|
|
817
|
-
data: release2
|
|
974
|
+
data: release2,
|
|
975
|
+
meta: {
|
|
976
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
977
|
+
totalPublishedEntries: countPublishActions,
|
|
978
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
979
|
+
}
|
|
818
980
|
};
|
|
819
981
|
}
|
|
820
982
|
};
|
|
@@ -1086,6 +1248,7 @@ const getPlugin = () => {
|
|
|
1086
1248
|
return {
|
|
1087
1249
|
register,
|
|
1088
1250
|
bootstrap,
|
|
1251
|
+
destroy,
|
|
1089
1252
|
contentTypes,
|
|
1090
1253
|
services,
|
|
1091
1254
|
controllers,
|