@strapi/content-releases 0.0.0-next.6d59515520a3850456f256fb0e4c54b75054ddf4 → 0.0.0-next.aa7c7ec6724534e157d8a23fe85ee8318dabbf37
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-L1jSxCiL.mjs → App-8FCxPK8-.mjs} +252 -195
- package/dist/_chunks/App-8FCxPK8-.mjs.map +1 -0
- package/dist/_chunks/{App-_20W9dYa.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-c4zRX_sg.mjs → index-8aK7GzI5.mjs} +75 -26
- package/dist/_chunks/index-8aK7GzI5.mjs.map +1 -0
- package/dist/_chunks/{index-KJa1Rb5F.js → index-nGaPcY9m.js} +74 -25
- 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 +185 -34
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +185 -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,9 @@ const schema$1 = {
|
|
|
158
180
|
releasedAt: {
|
|
159
181
|
type: "datetime"
|
|
160
182
|
},
|
|
183
|
+
scheduledAt: {
|
|
184
|
+
type: "datetime"
|
|
185
|
+
},
|
|
161
186
|
actions: {
|
|
162
187
|
type: "relation",
|
|
163
188
|
relation: "oneToMany",
|
|
@@ -220,9 +245,6 @@ const contentTypes = {
|
|
|
220
245
|
release: release$1,
|
|
221
246
|
"release-action": releaseAction$1
|
|
222
247
|
};
|
|
223
|
-
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
224
|
-
return strapi2.plugin("content-releases").service(name);
|
|
225
|
-
};
|
|
226
248
|
const getGroupName = (queryValue) => {
|
|
227
249
|
switch (queryValue) {
|
|
228
250
|
case "contentType":
|
|
@@ -238,17 +260,24 @@ const getGroupName = (queryValue) => {
|
|
|
238
260
|
const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
239
261
|
async create(releaseData, { user }) {
|
|
240
262
|
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
241
|
-
const {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
263
|
+
const {
|
|
264
|
+
validatePendingReleasesLimit,
|
|
265
|
+
validateUniqueNameForPendingRelease,
|
|
266
|
+
validateScheduledAtIsLaterThanNow
|
|
267
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
245
268
|
await Promise.all([
|
|
246
269
|
validatePendingReleasesLimit(),
|
|
247
|
-
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name)
|
|
270
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
271
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
248
272
|
]);
|
|
249
|
-
|
|
273
|
+
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
250
274
|
data: releaseWithCreatorFields
|
|
251
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;
|
|
252
281
|
},
|
|
253
282
|
async findOne(id, query = {}) {
|
|
254
283
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
@@ -343,6 +372,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
343
372
|
},
|
|
344
373
|
async update(id, releaseData, { user }) {
|
|
345
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
|
+
]);
|
|
346
383
|
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
|
|
347
384
|
if (!release2) {
|
|
348
385
|
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
@@ -358,6 +395,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
358
395
|
// @ts-expect-error see above
|
|
359
396
|
data: releaseWithCreatorFields
|
|
360
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
|
+
}
|
|
361
406
|
return updatedRelease;
|
|
362
407
|
},
|
|
363
408
|
async createAction(releaseId, action) {
|
|
@@ -542,27 +587,53 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
542
587
|
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
543
588
|
throw new errors.ValidationError("No entries to publish");
|
|
544
589
|
}
|
|
545
|
-
const
|
|
590
|
+
const collectionTypeActions = {};
|
|
591
|
+
const singleTypeActions = [];
|
|
546
592
|
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
547
593
|
const contentTypeUid = action.contentType;
|
|
548
|
-
if (
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
+
}
|
|
556
606
|
} else {
|
|
557
|
-
|
|
607
|
+
singleTypeActions.push({
|
|
608
|
+
uid: contentTypeUid,
|
|
609
|
+
action: action.type,
|
|
610
|
+
id: action.entry.id
|
|
611
|
+
});
|
|
558
612
|
}
|
|
559
613
|
}
|
|
560
614
|
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
561
615
|
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
562
616
|
await strapi2.db.transaction(async () => {
|
|
563
|
-
for (const
|
|
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
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
564
635
|
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
565
|
-
const { entriestoPublishIds, entriesToUnpublishIds } =
|
|
636
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
566
637
|
const entriesToPublish = await strapi2.entityService.findMany(
|
|
567
638
|
contentTypeUid,
|
|
568
639
|
{
|
|
@@ -688,27 +759,88 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
688
759
|
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
689
760
|
}
|
|
690
761
|
},
|
|
691
|
-
async validateUniqueNameForPendingRelease(name) {
|
|
762
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
692
763
|
const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
|
|
693
764
|
filters: {
|
|
694
765
|
releasedAt: {
|
|
695
766
|
$null: true
|
|
696
767
|
},
|
|
697
|
-
name
|
|
768
|
+
name,
|
|
769
|
+
...id && { id: { $ne: id } }
|
|
698
770
|
}
|
|
699
771
|
});
|
|
700
772
|
const isNameUnique = pendingReleases.length === 0;
|
|
701
773
|
if (!isNameUnique) {
|
|
702
774
|
throw new errors.ValidationError(`Release with name ${name} already exists`);
|
|
703
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
|
+
}
|
|
704
781
|
}
|
|
705
782
|
});
|
|
783
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
784
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
785
|
+
return {
|
|
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;
|
|
803
|
+
},
|
|
804
|
+
cancel(releaseId) {
|
|
805
|
+
if (scheduledJobs.has(releaseId)) {
|
|
806
|
+
scheduledJobs.get(releaseId).cancel();
|
|
807
|
+
scheduledJobs.delete(releaseId);
|
|
808
|
+
}
|
|
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
|
+
}
|
|
827
|
+
});
|
|
828
|
+
for (const release2 of releases) {
|
|
829
|
+
this.set(release2.id, release2.scheduledAt);
|
|
830
|
+
}
|
|
831
|
+
return scheduledJobs;
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
};
|
|
706
835
|
const services = {
|
|
707
836
|
release: createReleaseService,
|
|
708
|
-
"release-validation": createReleaseValidationService
|
|
837
|
+
"release-validation": createReleaseValidationService,
|
|
838
|
+
...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
|
|
709
839
|
};
|
|
710
840
|
const RELEASE_SCHEMA = yup.object().shape({
|
|
711
|
-
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()
|
|
712
844
|
}).required().noUnknown();
|
|
713
845
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
714
846
|
const releaseController = {
|
|
@@ -748,19 +880,18 @@ const releaseController = {
|
|
|
748
880
|
const id = ctx.params.id;
|
|
749
881
|
const releaseService = getService("release", { strapi });
|
|
750
882
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
});
|
|
755
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
883
|
+
if (!release2) {
|
|
884
|
+
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
885
|
+
}
|
|
756
886
|
const count = await releaseService.countActions({
|
|
757
887
|
filters: {
|
|
758
888
|
release: id
|
|
759
889
|
}
|
|
760
890
|
});
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
891
|
+
const sanitizedRelease = {
|
|
892
|
+
...release2,
|
|
893
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
894
|
+
};
|
|
764
895
|
const data = {
|
|
765
896
|
...sanitizedRelease,
|
|
766
897
|
actions: {
|
|
@@ -813,8 +944,27 @@ const releaseController = {
|
|
|
813
944
|
const id = ctx.params.id;
|
|
814
945
|
const releaseService = getService("release", { strapi });
|
|
815
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
|
+
]);
|
|
816
961
|
ctx.body = {
|
|
817
|
-
data: release2
|
|
962
|
+
data: release2,
|
|
963
|
+
meta: {
|
|
964
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
965
|
+
totalPublishedEntries: countPublishActions,
|
|
966
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
967
|
+
}
|
|
818
968
|
};
|
|
819
969
|
}
|
|
820
970
|
};
|
|
@@ -1086,6 +1236,7 @@ const getPlugin = () => {
|
|
|
1086
1236
|
return {
|
|
1087
1237
|
register,
|
|
1088
1238
|
bootstrap,
|
|
1239
|
+
destroy,
|
|
1089
1240
|
contentTypes,
|
|
1090
1241
|
services,
|
|
1091
1242
|
controllers,
|