@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.
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  const utils = require("@strapi/utils");
3
+ const lodash = require("lodash");
3
4
  const _ = require("lodash/fp");
4
5
  const EE = require("@strapi/strapi/dist/utils/ee");
6
+ const nodeSchedule = require("node-schedule");
5
7
  const yup = require("yup");
6
8
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
7
9
  function _interopNamespace(e) {
@@ -71,40 +73,50 @@ const ACTIONS = [
71
73
  pluginName: "content-releases"
72
74
  }
73
75
  ];
74
- const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
75
- return strapi2.plugin("content-releases").service(name);
76
- };
76
+ async function deleteActionsOnDisableDraftAndPublish({
77
+ oldContentTypes,
78
+ contentTypes: contentTypes2
79
+ }) {
80
+ if (!oldContentTypes) {
81
+ return;
82
+ }
83
+ for (const uid in contentTypes2) {
84
+ if (!oldContentTypes[uid]) {
85
+ continue;
86
+ }
87
+ const oldContentType = oldContentTypes[uid];
88
+ const contentType = contentTypes2[uid];
89
+ if (utils.contentTypes.hasDraftAndPublish(oldContentType) && !utils.contentTypes.hasDraftAndPublish(contentType)) {
90
+ await strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: uid }).execute();
91
+ }
92
+ }
93
+ }
94
+ async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
95
+ const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
96
+ if (deletedContentTypes.length) {
97
+ await utils.mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
98
+ return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
99
+ });
100
+ }
101
+ }
77
102
  const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
78
103
  const register = async ({ strapi: strapi2 }) => {
79
- if (features$2.isEnabled("cms-content-releases") && strapi2.features.future.isEnabled("contentReleases")) {
104
+ if (features$2.isEnabled("cms-content-releases")) {
80
105
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
81
- const releaseActionService = getService("release-action", { strapi: strapi2 });
82
- const eventManager = getService("event-manager", { strapi: strapi2 });
83
- const destroyContentTypeUpdateListener = strapi2.eventHub.on(
84
- "content-type.update",
85
- async ({ contentType }) => {
86
- if (contentType.schema.options.draftAndPublish === false) {
87
- await releaseActionService.deleteManyForContentType(contentType.uid);
88
- }
89
- }
90
- );
91
- eventManager.addDestroyListenerCallback(destroyContentTypeUpdateListener);
92
- const destroyContentTypeDeleteListener = strapi2.eventHub.on(
93
- "content-type.delete",
94
- async ({ contentType }) => {
95
- await releaseActionService.deleteManyForContentType(contentType.uid);
96
- }
97
- );
98
- eventManager.addDestroyListenerCallback(destroyContentTypeDeleteListener);
106
+ strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
107
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
99
108
  }
100
109
  };
110
+ const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
111
+ return strapi2.plugin("content-releases").service(name);
112
+ };
101
113
  const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
102
114
  const bootstrap = async ({ strapi: strapi2 }) => {
103
- if (features$1.isEnabled("cms-content-releases") && strapi2.features.future.isEnabled("contentReleases")) {
115
+ if (features$1.isEnabled("cms-content-releases")) {
104
116
  strapi2.db.lifecycles.subscribe({
105
117
  afterDelete(event) {
106
118
  const { model, result } = event;
107
- if (model.kind === "collectionType" && model.options.draftAndPublish) {
119
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
108
120
  const { id } = result;
109
121
  strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
110
122
  where: {
@@ -120,7 +132,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
120
132
  */
121
133
  async beforeDeleteMany(event) {
122
134
  const { model, params } = event;
123
- if (model.kind === "collectionType" && model.options.draftAndPublish) {
135
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
124
136
  const { where } = params;
125
137
  const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
126
138
  event.state.entriesToDelete = entriesToDelete;
@@ -145,6 +157,24 @@ const bootstrap = async ({ strapi: strapi2 }) => {
145
157
  }
146
158
  }
147
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
+ }
148
178
  }
149
179
  };
150
180
  const schema$1 = {
@@ -173,6 +203,9 @@ const schema$1 = {
173
203
  releasedAt: {
174
204
  type: "datetime"
175
205
  },
206
+ scheduledAt: {
207
+ type: "datetime"
208
+ },
176
209
  actions: {
177
210
  type: "relation",
178
211
  relation: "oneToMany",
@@ -235,15 +268,6 @@ const contentTypes = {
235
268
  release: release$1,
236
269
  "release-action": releaseAction$1
237
270
  };
238
- const createReleaseActionService = ({ strapi: strapi2 }) => ({
239
- async deleteManyForContentType(contentTypeUid) {
240
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
241
- where: {
242
- target_type: contentTypeUid
243
- }
244
- });
245
- }
246
- });
247
271
  const getGroupName = (queryValue) => {
248
272
  switch (queryValue) {
249
273
  case "contentType":
@@ -259,10 +283,24 @@ const getGroupName = (queryValue) => {
259
283
  const createReleaseService = ({ strapi: strapi2 }) => ({
260
284
  async create(releaseData, { user }) {
261
285
  const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
262
- await getService("release-validation", { strapi: strapi2 }).validatePendingReleasesLimit();
263
- return strapi2.entityService.create(RELEASE_MODEL_UID, {
286
+ const {
287
+ validatePendingReleasesLimit,
288
+ validateUniqueNameForPendingRelease,
289
+ validateScheduledAtIsLaterThanNow
290
+ } = getService("release-validation", { strapi: strapi2 });
291
+ await Promise.all([
292
+ validatePendingReleasesLimit(),
293
+ validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
294
+ validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
295
+ ]);
296
+ const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
264
297
  data: releaseWithCreatorFields
265
298
  });
299
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
300
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
301
+ await schedulingService.set(release2.id, release2.scheduledAt);
302
+ }
303
+ return release2;
266
304
  },
267
305
  async findOne(id, query = {}) {
268
306
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
@@ -281,51 +319,66 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
281
319
  }
282
320
  });
283
321
  },
284
- async findManyForContentTypeEntry(contentTypeUid, entryId, {
285
- hasEntryAttached
286
- } = {
287
- hasEntryAttached: false
288
- }) {
289
- const whereActions = hasEntryAttached ? {
290
- // Find all Releases where the content type entry is present
291
- actions: {
292
- target_type: contentTypeUid,
293
- target_id: entryId
294
- }
295
- } : {
296
- // Find all Releases where the content type entry is not present
297
- $or: [
298
- {
299
- $not: {
300
- actions: {
301
- target_type: contentTypeUid,
302
- target_id: entryId
303
- }
304
- }
322
+ async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
323
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
324
+ where: {
325
+ actions: {
326
+ target_type: contentTypeUid,
327
+ target_id: entryId
305
328
  },
306
- {
307
- actions: null
329
+ releasedAt: {
330
+ $null: true
308
331
  }
309
- ]
310
- };
311
- const populateAttachedAction = hasEntryAttached ? {
312
- // Filter the action to get only the content type entry
313
- actions: {
314
- where: {
332
+ },
333
+ populate: {
334
+ // Filter the action to get only the content type entry
335
+ actions: {
336
+ where: {
337
+ target_type: contentTypeUid,
338
+ target_id: entryId
339
+ }
340
+ }
341
+ }
342
+ });
343
+ return releases.map((release2) => {
344
+ if (release2.actions?.length) {
345
+ const [actionForEntry] = release2.actions;
346
+ delete release2.actions;
347
+ return {
348
+ ...release2,
349
+ action: actionForEntry
350
+ };
351
+ }
352
+ return release2;
353
+ });
354
+ },
355
+ async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
356
+ const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
357
+ where: {
358
+ releasedAt: {
359
+ $null: true
360
+ },
361
+ actions: {
315
362
  target_type: contentTypeUid,
316
363
  target_id: entryId
317
364
  }
318
365
  }
319
- } : {};
366
+ });
320
367
  const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
321
368
  where: {
322
- ...whereActions,
369
+ $or: [
370
+ {
371
+ id: {
372
+ $notIn: releasesRelated.map((release2) => release2.id)
373
+ }
374
+ },
375
+ {
376
+ actions: null
377
+ }
378
+ ],
323
379
  releasedAt: {
324
380
  $null: true
325
381
  }
326
- },
327
- populate: {
328
- ...populateAttachedAction
329
382
  }
330
383
  });
331
384
  return releases.map((release2) => {
@@ -342,6 +395,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
342
395
  },
343
396
  async update(id, releaseData, { user }) {
344
397
  const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(releaseData);
398
+ const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
399
+ "release-validation",
400
+ { strapi: strapi2 }
401
+ );
402
+ await Promise.all([
403
+ validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
404
+ validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
405
+ ]);
345
406
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
346
407
  if (!release2) {
347
408
  throw new utils.errors.NotFoundError(`No release found for id ${id}`);
@@ -357,6 +418,14 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
357
418
  // @ts-expect-error see above
358
419
  data: releaseWithCreatorFields
359
420
  });
421
+ if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
422
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
423
+ if (releaseData.scheduledAt) {
424
+ await schedulingService.set(id, releaseData.scheduledAt);
425
+ } else if (release2.scheduledAt) {
426
+ schedulingService.cancel(id);
427
+ }
428
+ }
360
429
  return updatedRelease;
361
430
  },
362
431
  async createAction(releaseId, action) {
@@ -524,7 +593,9 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
524
593
  populate: {
525
594
  actions: {
526
595
  populate: {
527
- entry: true
596
+ entry: {
597
+ fields: ["id"]
598
+ }
528
599
  }
529
600
  }
530
601
  }
@@ -539,30 +610,80 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
539
610
  if (releaseWithPopulatedActionEntries.actions.length === 0) {
540
611
  throw new utils.errors.ValidationError("No entries to publish");
541
612
  }
542
- const actions = {};
613
+ const collectionTypeActions = {};
614
+ const singleTypeActions = [];
543
615
  for (const action of releaseWithPopulatedActionEntries.actions) {
544
616
  const contentTypeUid = action.contentType;
545
- if (!actions[contentTypeUid]) {
546
- actions[contentTypeUid] = {
547
- publish: [],
548
- unpublish: []
549
- };
550
- }
551
- if (action.type === "publish") {
552
- actions[contentTypeUid].publish.push(action.entry);
617
+ if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
618
+ if (!collectionTypeActions[contentTypeUid]) {
619
+ collectionTypeActions[contentTypeUid] = {
620
+ entriestoPublishIds: [],
621
+ entriesToUnpublishIds: []
622
+ };
623
+ }
624
+ if (action.type === "publish") {
625
+ collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
626
+ } else {
627
+ collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
628
+ }
553
629
  } else {
554
- actions[contentTypeUid].unpublish.push(action.entry);
630
+ singleTypeActions.push({
631
+ uid: contentTypeUid,
632
+ action: action.type,
633
+ id: action.entry.id
634
+ });
555
635
  }
556
636
  }
557
637
  const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
638
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
558
639
  await strapi2.db.transaction(async () => {
559
- for (const contentTypeUid of Object.keys(actions)) {
560
- const { publish, unpublish } = actions[contentTypeUid];
561
- if (publish.length > 0) {
562
- await entityManagerService.publishMany(publish, contentTypeUid);
640
+ for (const { uid, action, id } of singleTypeActions) {
641
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
642
+ const entry = await strapi2.entityService.findOne(uid, id, { populate });
643
+ try {
644
+ if (action === "publish") {
645
+ await entityManagerService.publish(entry, uid);
646
+ } else {
647
+ await entityManagerService.unpublish(entry, uid);
648
+ }
649
+ } catch (error) {
650
+ if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
651
+ ;
652
+ else {
653
+ throw error;
654
+ }
655
+ }
656
+ }
657
+ for (const contentTypeUid of Object.keys(collectionTypeActions)) {
658
+ const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
659
+ const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
660
+ const entriesToPublish = await strapi2.entityService.findMany(
661
+ contentTypeUid,
662
+ {
663
+ filters: {
664
+ id: {
665
+ $in: entriestoPublishIds
666
+ }
667
+ },
668
+ populate
669
+ }
670
+ );
671
+ const entriesToUnpublish = await strapi2.entityService.findMany(
672
+ contentTypeUid,
673
+ {
674
+ filters: {
675
+ id: {
676
+ $in: entriesToUnpublishIds
677
+ }
678
+ },
679
+ populate
680
+ }
681
+ );
682
+ if (entriesToPublish.length > 0) {
683
+ await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
563
684
  }
564
- if (unpublish.length > 0) {
565
- await entityManagerService.unpublishMany(unpublish, contentTypeUid);
685
+ if (entriesToUnpublish.length > 0) {
686
+ await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
566
687
  }
567
688
  }
568
689
  });
@@ -660,34 +781,89 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
660
781
  if (pendingReleasesCount >= maximumPendingReleases) {
661
782
  throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
662
783
  }
784
+ },
785
+ async validateUniqueNameForPendingRelease(name, id) {
786
+ const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
787
+ filters: {
788
+ releasedAt: {
789
+ $null: true
790
+ },
791
+ name,
792
+ ...id && { id: { $ne: id } }
793
+ }
794
+ });
795
+ const isNameUnique = pendingReleases.length === 0;
796
+ if (!isNameUnique) {
797
+ throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
798
+ }
799
+ },
800
+ async validateScheduledAtIsLaterThanNow(scheduledAt) {
801
+ if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
802
+ throw new utils.errors.ValidationError("Scheduled at must be later than now");
803
+ }
663
804
  }
664
805
  });
665
- const createEventManagerService = () => {
666
- const state = {
667
- destroyListenerCallbacks: []
668
- };
806
+ const createSchedulingService = ({ strapi: strapi2 }) => {
807
+ const scheduledJobs = /* @__PURE__ */ new Map();
669
808
  return {
670
- addDestroyListenerCallback(destroyListenerCallback) {
671
- state.destroyListenerCallbacks.push(destroyListenerCallback);
809
+ async set(releaseId, scheduleDate) {
810
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
811
+ if (!release2) {
812
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
813
+ }
814
+ const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
815
+ try {
816
+ await getService("release").publish(releaseId);
817
+ } catch (error) {
818
+ }
819
+ this.cancel(releaseId);
820
+ });
821
+ if (scheduledJobs.has(releaseId)) {
822
+ this.cancel(releaseId);
823
+ }
824
+ scheduledJobs.set(releaseId, job);
825
+ return scheduledJobs;
672
826
  },
673
- destroyAllListeners() {
674
- if (!state.destroyListenerCallbacks.length) {
675
- return;
827
+ cancel(releaseId) {
828
+ if (scheduledJobs.has(releaseId)) {
829
+ scheduledJobs.get(releaseId).cancel();
830
+ scheduledJobs.delete(releaseId);
676
831
  }
677
- state.destroyListenerCallbacks.forEach((destroyListenerCallback) => {
678
- destroyListenerCallback();
832
+ return scheduledJobs;
833
+ },
834
+ getAll() {
835
+ return scheduledJobs;
836
+ },
837
+ /**
838
+ * On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
839
+ * This is useful in case the server was restarted and the scheduled jobs were lost
840
+ * This also could be used to sync different Strapi instances in case of a cluster
841
+ */
842
+ async syncFromDatabase() {
843
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
844
+ where: {
845
+ scheduledAt: {
846
+ $gte: /* @__PURE__ */ new Date()
847
+ },
848
+ releasedAt: null
849
+ }
679
850
  });
851
+ for (const release2 of releases) {
852
+ this.set(release2.id, release2.scheduledAt);
853
+ }
854
+ return scheduledJobs;
680
855
  }
681
856
  };
682
857
  };
683
858
  const services = {
684
859
  release: createReleaseService,
685
- "release-action": createReleaseActionService,
686
860
  "release-validation": createReleaseValidationService,
687
- "event-manager": createEventManagerService
861
+ ...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
688
862
  };
689
863
  const RELEASE_SCHEMA = yup__namespace.object().shape({
690
- name: yup__namespace.string().trim().required()
864
+ name: yup__namespace.string().trim().required(),
865
+ // scheduledAt is a date, but we always receive strings from the client
866
+ scheduledAt: yup__namespace.string().nullable()
691
867
  }).required().noUnknown();
692
868
  const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
693
869
  const releaseController = {
@@ -704,9 +880,7 @@ const releaseController = {
704
880
  const contentTypeUid = query.contentTypeUid;
705
881
  const entryId = query.entryId;
706
882
  const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
707
- const data = await releaseService.findManyForContentTypeEntry(contentTypeUid, entryId, {
708
- hasEntryAttached
709
- });
883
+ const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
710
884
  ctx.body = { data };
711
885
  } else {
712
886
  const query = await permissionsManager.sanitizeQuery(ctx.query);
@@ -729,19 +903,18 @@ const releaseController = {
729
903
  const id = ctx.params.id;
730
904
  const releaseService = getService("release", { strapi });
731
905
  const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
732
- const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
733
- ability: ctx.state.userAbility,
734
- model: RELEASE_MODEL_UID
735
- });
736
- const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
906
+ if (!release2) {
907
+ throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
908
+ }
737
909
  const count = await releaseService.countActions({
738
910
  filters: {
739
911
  release: id
740
912
  }
741
913
  });
742
- if (!release2) {
743
- throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
744
- }
914
+ const sanitizedRelease = {
915
+ ...release2,
916
+ createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
917
+ };
745
918
  const data = {
746
919
  ...sanitizedRelease,
747
920
  actions: {
@@ -794,8 +967,27 @@ const releaseController = {
794
967
  const id = ctx.params.id;
795
968
  const releaseService = getService("release", { strapi });
796
969
  const release2 = await releaseService.publish(id, { user });
970
+ const [countPublishActions, countUnpublishActions] = await Promise.all([
971
+ releaseService.countActions({
972
+ filters: {
973
+ release: id,
974
+ type: "publish"
975
+ }
976
+ }),
977
+ releaseService.countActions({
978
+ filters: {
979
+ release: id,
980
+ type: "unpublish"
981
+ }
982
+ })
983
+ ]);
797
984
  ctx.body = {
798
- data: release2
985
+ data: release2,
986
+ meta: {
987
+ totalEntries: countPublishActions + countUnpublishActions,
988
+ totalPublishedEntries: countPublishActions,
989
+ totalUnpublishedEntries: countUnpublishActions
990
+ }
799
991
  };
800
992
  }
801
993
  };
@@ -1063,19 +1255,15 @@ const routes = {
1063
1255
  };
1064
1256
  const { features } = require("@strapi/strapi/dist/utils/ee");
1065
1257
  const getPlugin = () => {
1066
- if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
1258
+ if (features.isEnabled("cms-content-releases")) {
1067
1259
  return {
1068
1260
  register,
1069
1261
  bootstrap,
1262
+ destroy,
1070
1263
  contentTypes,
1071
1264
  services,
1072
1265
  controllers,
1073
- routes,
1074
- destroy() {
1075
- if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
1076
- getService("event-manager").destroyAllListeners();
1077
- }
1078
- }
1266
+ routes
1079
1267
  };
1080
1268
  }
1081
1269
  return {