@strapi/content-releases 0.0.0-next.d10040847b91742ccb8083938399b63ffa289c7a → 0.0.0-next.de77e236e318525454da54a1a2617d86742e6e6c

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.
Files changed (31) hide show
  1. package/dist/_chunks/{App-lnXbSPgp.js → App-HjWtUYmc.js} +673 -630
  2. package/dist/_chunks/App-HjWtUYmc.js.map +1 -0
  3. package/dist/_chunks/{App-g3vtS2Wa.mjs → App-gu1aiP6i.mjs} +686 -643
  4. package/dist/_chunks/App-gu1aiP6i.mjs.map +1 -0
  5. package/dist/_chunks/{PurchaseContentReleases-Clm0iACO.mjs → PurchaseContentReleases-3tRbmbY3.mjs} +2 -2
  6. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +1 -0
  7. package/dist/_chunks/{PurchaseContentReleases-YhAPgpG9.js → PurchaseContentReleases-bpIYXOfu.js} +2 -2
  8. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +1 -0
  9. package/dist/_chunks/{en-gcJJ5htG.js → en-HrREghh3.js} +11 -3
  10. package/dist/_chunks/en-HrREghh3.js.map +1 -0
  11. package/dist/_chunks/{en-WuuhP6Bn.mjs → en-ltT1TlKQ.mjs} +11 -3
  12. package/dist/_chunks/en-ltT1TlKQ.mjs.map +1 -0
  13. package/dist/_chunks/{index-ItlgrLcr.js → index-ZNwxYN8H.js} +319 -18
  14. package/dist/_chunks/index-ZNwxYN8H.js.map +1 -0
  15. package/dist/_chunks/{index-uGex_IIQ.mjs → index-mvj9PSKd.mjs} +334 -33
  16. package/dist/_chunks/index-mvj9PSKd.mjs.map +1 -0
  17. package/dist/admin/index.js +1 -1
  18. package/dist/admin/index.mjs +1 -1
  19. package/dist/server/index.js +331 -159
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +331 -159
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +12 -12
  24. package/dist/_chunks/App-g3vtS2Wa.mjs.map +0 -1
  25. package/dist/_chunks/App-lnXbSPgp.js.map +0 -1
  26. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +0 -1
  27. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +0 -1
  28. package/dist/_chunks/en-WuuhP6Bn.mjs.map +0 -1
  29. package/dist/_chunks/en-gcJJ5htG.js.map +0 -1
  30. package/dist/_chunks/index-ItlgrLcr.js.map +0 -1
  31. package/dist/_chunks/index-uGex_IIQ.mjs.map +0 -1
@@ -203,7 +203,7 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
203
203
  }
204
204
  });
205
205
  await utils.mapAsync(actions, async (action) => {
206
- if (action.entry) {
206
+ if (action.entry && action.release) {
207
207
  const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
208
208
  strapi
209
209
  });
@@ -231,12 +231,63 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
231
231
  });
232
232
  }
233
233
  }
234
+ async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
235
+ if (!oldContentTypes) {
236
+ return;
237
+ }
238
+ const i18nPlugin = strapi.plugin("i18n");
239
+ if (!i18nPlugin) {
240
+ return;
241
+ }
242
+ for (const uid in contentTypes2) {
243
+ if (!oldContentTypes[uid]) {
244
+ continue;
245
+ }
246
+ const oldContentType = oldContentTypes[uid];
247
+ const contentType = contentTypes2[uid];
248
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
249
+ if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
250
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
251
+ locale: null
252
+ }).where({ contentType: uid }).execute();
253
+ }
254
+ }
255
+ }
256
+ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
257
+ if (!oldContentTypes) {
258
+ return;
259
+ }
260
+ const i18nPlugin = strapi.plugin("i18n");
261
+ if (!i18nPlugin) {
262
+ return;
263
+ }
264
+ for (const uid in contentTypes2) {
265
+ if (!oldContentTypes[uid]) {
266
+ continue;
267
+ }
268
+ const oldContentType = oldContentTypes[uid];
269
+ const contentType = contentTypes2[uid];
270
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
271
+ const { getDefaultLocale } = i18nPlugin.service("locales");
272
+ if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
273
+ const defaultLocale = await getDefaultLocale();
274
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
275
+ locale: defaultLocale
276
+ }).where({ contentType: uid }).execute();
277
+ }
278
+ }
279
+ }
234
280
  const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
235
281
  const register = async ({ strapi: strapi2 }) => {
236
282
  if (features$2.isEnabled("cms-content-releases")) {
237
283
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
238
- strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
239
- strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
284
+ strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
285
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
286
+ }
287
+ if (strapi2.plugin("graphql")) {
288
+ const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
289
+ graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
290
+ graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
240
291
  }
241
292
  };
242
293
  const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
@@ -362,27 +413,23 @@ const bootstrap = async ({ strapi: strapi2 }) => {
362
413
  }
363
414
  }
364
415
  });
365
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
366
- getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
367
- strapi2.log.error(
368
- "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
369
- );
370
- throw err;
371
- });
372
- Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
373
- strapi2.webhookStore.addAllowedEvent(key, value);
374
- });
375
- }
416
+ getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
417
+ strapi2.log.error(
418
+ "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
419
+ );
420
+ throw err;
421
+ });
422
+ Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
423
+ strapi2.webhookStore.addAllowedEvent(key, value);
424
+ });
376
425
  }
377
426
  };
378
427
  const destroy = async ({ strapi: strapi2 }) => {
379
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
380
- const scheduledJobs = getService("scheduling", {
381
- strapi: strapi2
382
- }).getAll();
383
- for (const [, job] of scheduledJobs) {
384
- job.cancel();
385
- }
428
+ const scheduledJobs = getService("scheduling", {
429
+ strapi: strapi2
430
+ }).getAll();
431
+ for (const [, job] of scheduledJobs) {
432
+ job.cancel();
386
433
  }
387
434
  };
388
435
  const schema$1 = {
@@ -507,6 +554,94 @@ const createReleaseService = ({ strapi: strapi2 }) => {
507
554
  release: release2
508
555
  });
509
556
  };
557
+ const publishSingleTypeAction = async (uid, actionType, entryId) => {
558
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
559
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
560
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
561
+ const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
562
+ try {
563
+ if (actionType === "publish") {
564
+ await entityManagerService.publish(entry, uid);
565
+ } else {
566
+ await entityManagerService.unpublish(entry, uid);
567
+ }
568
+ } catch (error) {
569
+ if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
570
+ ;
571
+ else {
572
+ throw error;
573
+ }
574
+ }
575
+ };
576
+ const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
577
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
578
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
579
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
580
+ const entriesToPublish = await strapi2.entityService.findMany(uid, {
581
+ filters: {
582
+ id: {
583
+ $in: entriesToPublishIds
584
+ }
585
+ },
586
+ populate
587
+ });
588
+ const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
589
+ filters: {
590
+ id: {
591
+ $in: entriestoUnpublishIds
592
+ }
593
+ },
594
+ populate
595
+ });
596
+ if (entriesToPublish.length > 0) {
597
+ await entityManagerService.publishMany(entriesToPublish, uid);
598
+ }
599
+ if (entriesToUnpublish.length > 0) {
600
+ await entityManagerService.unpublishMany(entriesToUnpublish, uid);
601
+ }
602
+ };
603
+ const getFormattedActions = async (releaseId) => {
604
+ const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
605
+ where: {
606
+ release: {
607
+ id: releaseId
608
+ }
609
+ },
610
+ populate: {
611
+ entry: {
612
+ fields: ["id"]
613
+ }
614
+ }
615
+ });
616
+ if (actions.length === 0) {
617
+ throw new utils.errors.ValidationError("No entries to publish");
618
+ }
619
+ const collectionTypeActions = {};
620
+ const singleTypeActions = [];
621
+ for (const action of actions) {
622
+ const contentTypeUid = action.contentType;
623
+ if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
624
+ if (!collectionTypeActions[contentTypeUid]) {
625
+ collectionTypeActions[contentTypeUid] = {
626
+ entriesToPublishIds: [],
627
+ entriesToUnpublishIds: []
628
+ };
629
+ }
630
+ if (action.type === "publish") {
631
+ collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
632
+ } else {
633
+ collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
634
+ }
635
+ } else {
636
+ singleTypeActions.push({
637
+ uid: contentTypeUid,
638
+ action: action.type,
639
+ id: action.entry.id
640
+ });
641
+ }
642
+ }
643
+ return { collectionTypeActions, singleTypeActions };
644
+ };
510
645
  return {
511
646
  async create(releaseData, { user }) {
512
647
  const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
@@ -526,7 +661,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
526
661
  status: "empty"
527
662
  }
528
663
  });
529
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
664
+ if (releaseWithCreatorFields.scheduledAt) {
530
665
  const schedulingService = getService("scheduling", { strapi: strapi2 });
531
666
  await schedulingService.set(release2.id, release2.scheduledAt);
532
667
  }
@@ -550,12 +685,18 @@ const createReleaseService = ({ strapi: strapi2 }) => {
550
685
  }
551
686
  });
552
687
  },
553
- async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
688
+ async findManyWithContentTypeEntryAttached(contentTypeUid, entriesIds) {
689
+ let entries = entriesIds;
690
+ if (!Array.isArray(entriesIds)) {
691
+ entries = [entriesIds];
692
+ }
554
693
  const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
555
694
  where: {
556
695
  actions: {
557
696
  target_type: contentTypeUid,
558
- target_id: entryId
697
+ target_id: {
698
+ $in: entries
699
+ }
559
700
  },
560
701
  releasedAt: {
561
702
  $null: true
@@ -566,18 +707,25 @@ const createReleaseService = ({ strapi: strapi2 }) => {
566
707
  actions: {
567
708
  where: {
568
709
  target_type: contentTypeUid,
569
- target_id: entryId
710
+ target_id: {
711
+ $in: entries
712
+ }
713
+ },
714
+ populate: {
715
+ entry: {
716
+ select: ["id"]
717
+ }
570
718
  }
571
719
  }
572
720
  }
573
721
  });
574
722
  return releases.map((release2) => {
575
723
  if (release2.actions?.length) {
576
- const [actionForEntry] = release2.actions;
724
+ const actionsForEntry = release2.actions;
577
725
  delete release2.actions;
578
726
  return {
579
727
  ...release2,
580
- action: actionForEntry
728
+ actions: actionsForEntry
581
729
  };
582
730
  }
583
731
  return release2;
@@ -651,13 +799,11 @@ const createReleaseService = ({ strapi: strapi2 }) => {
651
799
  // @ts-expect-error see above
652
800
  data: releaseWithCreatorFields
653
801
  });
654
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
655
- const schedulingService = getService("scheduling", { strapi: strapi2 });
656
- if (releaseData.scheduledAt) {
657
- await schedulingService.set(id, releaseData.scheduledAt);
658
- } else if (release2.scheduledAt) {
659
- schedulingService.cancel(id);
660
- }
802
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
803
+ if (releaseData.scheduledAt) {
804
+ await schedulingService.set(id, releaseData.scheduledAt);
805
+ } else if (release2.scheduledAt) {
806
+ schedulingService.cancel(id);
661
807
  }
662
808
  this.updateReleaseStatus(id);
663
809
  strapi2.telemetry.send("didUpdateContentRelease");
@@ -823,7 +969,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
823
969
  });
824
970
  await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
825
971
  });
826
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
972
+ if (release2.scheduledAt) {
827
973
  const schedulingService = getService("scheduling", { strapi: strapi2 });
828
974
  await schedulingService.cancel(release2.id);
829
975
  }
@@ -831,145 +977,71 @@ const createReleaseService = ({ strapi: strapi2 }) => {
831
977
  return release2;
832
978
  },
833
979
  async publish(releaseId) {
834
- try {
835
- const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
836
- RELEASE_MODEL_UID,
837
- releaseId,
838
- {
839
- populate: {
840
- actions: {
841
- populate: {
842
- entry: {
843
- fields: ["id"]
844
- }
845
- }
846
- }
847
- }
848
- }
849
- );
850
- if (!releaseWithPopulatedActionEntries) {
980
+ const {
981
+ release: release2,
982
+ error
983
+ } = await strapi2.db.transaction(async ({ trx }) => {
984
+ const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
985
+ if (!lockedRelease) {
851
986
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
852
987
  }
853
- if (releaseWithPopulatedActionEntries.releasedAt) {
988
+ if (lockedRelease.releasedAt) {
854
989
  throw new utils.errors.ValidationError("Release already published");
855
990
  }
856
- if (releaseWithPopulatedActionEntries.actions.length === 0) {
857
- throw new utils.errors.ValidationError("No entries to publish");
991
+ if (lockedRelease.status === "failed") {
992
+ throw new utils.errors.ValidationError("Release failed to publish");
858
993
  }
859
- const collectionTypeActions = {};
860
- const singleTypeActions = [];
861
- for (const action of releaseWithPopulatedActionEntries.actions) {
862
- const contentTypeUid = action.contentType;
863
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
864
- if (!collectionTypeActions[contentTypeUid]) {
865
- collectionTypeActions[contentTypeUid] = {
866
- entriestoPublishIds: [],
867
- entriesToUnpublishIds: []
868
- };
869
- }
870
- if (action.type === "publish") {
871
- collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
872
- } else {
873
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
874
- }
875
- } else {
876
- singleTypeActions.push({
877
- uid: contentTypeUid,
878
- action: action.type,
879
- id: action.entry.id
880
- });
881
- }
882
- }
883
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
884
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
885
- await strapi2.db.transaction(async () => {
886
- for (const { uid, action, id } of singleTypeActions) {
887
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
888
- const entry = await strapi2.entityService.findOne(uid, id, { populate });
889
- try {
890
- if (action === "publish") {
891
- await entityManagerService.publish(entry, uid);
892
- } else {
893
- await entityManagerService.unpublish(entry, uid);
894
- }
895
- } catch (error) {
896
- if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
897
- } else {
898
- throw error;
899
- }
900
- }
901
- }
902
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
903
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
904
- const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
905
- const entriesToPublish = await strapi2.entityService.findMany(
906
- contentTypeUid,
907
- {
908
- filters: {
909
- id: {
910
- $in: entriestoPublishIds
911
- }
912
- },
913
- populate
914
- }
915
- );
916
- const entriesToUnpublish = await strapi2.entityService.findMany(
917
- contentTypeUid,
918
- {
919
- filters: {
920
- id: {
921
- $in: entriesToUnpublishIds
922
- }
923
- },
924
- populate
925
- }
926
- );
927
- if (entriesToPublish.length > 0) {
928
- await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
994
+ try {
995
+ strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
996
+ const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
997
+ releaseId
998
+ );
999
+ await strapi2.db.transaction(async () => {
1000
+ for (const { uid, action, id } of singleTypeActions) {
1001
+ await publishSingleTypeAction(uid, action, id);
929
1002
  }
930
- if (entriesToUnpublish.length > 0) {
931
- await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
1003
+ for (const contentTypeUid of Object.keys(collectionTypeActions)) {
1004
+ const uid = contentTypeUid;
1005
+ await publishCollectionTypeAction(
1006
+ uid,
1007
+ collectionTypeActions[uid].entriesToPublishIds,
1008
+ collectionTypeActions[uid].entriesToUnpublishIds
1009
+ );
932
1010
  }
933
- }
934
- });
935
- const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
936
- data: {
937
- /*
938
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
939
- */
940
- // @ts-expect-error see above
941
- releasedAt: /* @__PURE__ */ new Date()
942
- },
943
- populate: {
944
- actions: {
945
- // @ts-expect-error is not expecting count but it is working
946
- count: true
1011
+ });
1012
+ const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
1013
+ where: {
1014
+ id: releaseId
1015
+ },
1016
+ data: {
1017
+ status: "done",
1018
+ releasedAt: /* @__PURE__ */ new Date()
947
1019
  }
948
- }
949
- });
950
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
1020
+ });
951
1021
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
952
1022
  isPublished: true,
953
- release: release2
1023
+ release: release22
954
1024
  });
955
- }
956
- strapi2.telemetry.send("didPublishContentRelease");
957
- return release2;
958
- } catch (error) {
959
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
1025
+ strapi2.telemetry.send("didPublishContentRelease");
1026
+ return { release: release22, error: null };
1027
+ } catch (error2) {
960
1028
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
961
1029
  isPublished: false,
962
- error
1030
+ error: error2
963
1031
  });
964
- }
965
- strapi2.db.query(RELEASE_MODEL_UID).update({
966
- where: { id: releaseId },
967
- data: {
1032
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
968
1033
  status: "failed"
969
- }
970
- });
1034
+ }).transacting(trx).execute();
1035
+ return {
1036
+ release: null,
1037
+ error: error2
1038
+ };
1039
+ }
1040
+ });
1041
+ if (error) {
971
1042
  throw error;
972
1043
  }
1044
+ return release2;
973
1045
  },
974
1046
  async updateAction(actionId, releaseId, update) {
975
1047
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
@@ -1056,6 +1128,12 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1056
1128
  }
1057
1129
  };
1058
1130
  };
1131
+ class AlreadyOnReleaseError extends utils.errors.ApplicationError {
1132
+ constructor(message) {
1133
+ super(message);
1134
+ this.name = "AlreadyOnReleaseError";
1135
+ }
1136
+ }
1059
1137
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1060
1138
  async validateUniqueEntry(releaseId, releaseActionArgs) {
1061
1139
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
@@ -1068,7 +1146,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1068
1146
  (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1069
1147
  );
1070
1148
  if (isEntryInRelease) {
1071
- throw new utils.errors.ValidationError(
1149
+ throw new AlreadyOnReleaseError(
1072
1150
  `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1073
1151
  );
1074
1152
  }
@@ -1176,7 +1254,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1176
1254
  const services = {
1177
1255
  release: createReleaseService,
1178
1256
  "release-validation": createReleaseValidationService,
1179
- ...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
1257
+ scheduling: createSchedulingService
1180
1258
  };
1181
1259
  const RELEASE_SCHEMA = yup__namespace.object().shape({
1182
1260
  name: yup__namespace.string().trim().required(),
@@ -1263,6 +1341,33 @@ const releaseController = {
1263
1341
  };
1264
1342
  ctx.body = { data };
1265
1343
  },
1344
+ async mapEntriesToReleases(ctx) {
1345
+ const { contentTypeUid, entriesIds } = ctx.query;
1346
+ if (!contentTypeUid || !entriesIds) {
1347
+ throw new utils.errors.ValidationError("Missing required query parameters");
1348
+ }
1349
+ const releaseService = getService("release", { strapi });
1350
+ const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
1351
+ contentTypeUid,
1352
+ entriesIds
1353
+ );
1354
+ const mappedEntriesInReleases = releasesWithActions.reduce(
1355
+ (acc, release2) => {
1356
+ release2.actions.forEach((action) => {
1357
+ if (!acc[action.entry.id]) {
1358
+ acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
1359
+ } else {
1360
+ acc[action.entry.id].push({ id: release2.id, name: release2.name });
1361
+ }
1362
+ });
1363
+ return acc;
1364
+ },
1365
+ {}
1366
+ );
1367
+ ctx.body = {
1368
+ data: mappedEntriesInReleases
1369
+ };
1370
+ },
1266
1371
  async create(ctx) {
1267
1372
  const user = ctx.state.user;
1268
1373
  const releaseArgs = ctx.request.body;
@@ -1352,6 +1457,38 @@ const releaseActionController = {
1352
1457
  data: releaseAction2
1353
1458
  };
1354
1459
  },
1460
+ async createMany(ctx) {
1461
+ const releaseId = ctx.params.releaseId;
1462
+ const releaseActionsArgs = ctx.request.body;
1463
+ await Promise.all(
1464
+ releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1465
+ );
1466
+ const releaseService = getService("release", { strapi });
1467
+ const releaseActions = await strapi.db.transaction(async () => {
1468
+ const releaseActions2 = await Promise.all(
1469
+ releaseActionsArgs.map(async (releaseActionArgs) => {
1470
+ try {
1471
+ const action = await releaseService.createAction(releaseId, releaseActionArgs);
1472
+ return action;
1473
+ } catch (error) {
1474
+ if (error instanceof AlreadyOnReleaseError) {
1475
+ return null;
1476
+ }
1477
+ throw error;
1478
+ }
1479
+ })
1480
+ );
1481
+ return releaseActions2;
1482
+ });
1483
+ const newReleaseActions = releaseActions.filter((action) => action !== null);
1484
+ ctx.body = {
1485
+ data: newReleaseActions,
1486
+ meta: {
1487
+ entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
1488
+ totalEntries: releaseActions.length
1489
+ }
1490
+ };
1491
+ },
1355
1492
  async findMany(ctx) {
1356
1493
  const releaseId = ctx.params.releaseId;
1357
1494
  const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
@@ -1420,6 +1557,22 @@ const controllers = { release: releaseController, "release-action": releaseActio
1420
1557
  const release = {
1421
1558
  type: "admin",
1422
1559
  routes: [
1560
+ {
1561
+ method: "GET",
1562
+ path: "/mapEntriesToReleases",
1563
+ handler: "release.mapEntriesToReleases",
1564
+ config: {
1565
+ policies: [
1566
+ "admin::isAuthenticatedAdmin",
1567
+ {
1568
+ name: "admin::hasPermissions",
1569
+ config: {
1570
+ actions: ["plugin::content-releases.read"]
1571
+ }
1572
+ }
1573
+ ]
1574
+ }
1575
+ },
1423
1576
  {
1424
1577
  method: "POST",
1425
1578
  path: "/",
@@ -1537,6 +1690,22 @@ const releaseAction = {
1537
1690
  ]
1538
1691
  }
1539
1692
  },
1693
+ {
1694
+ method: "POST",
1695
+ path: "/:releaseId/actions/bulk",
1696
+ handler: "release-action.createMany",
1697
+ config: {
1698
+ policies: [
1699
+ "admin::isAuthenticatedAdmin",
1700
+ {
1701
+ name: "admin::hasPermissions",
1702
+ config: {
1703
+ actions: ["plugin::content-releases.create-action"]
1704
+ }
1705
+ }
1706
+ ]
1707
+ }
1708
+ },
1540
1709
  {
1541
1710
  method: "GET",
1542
1711
  path: "/:releaseId/actions",
@@ -1605,6 +1774,9 @@ const getPlugin = () => {
1605
1774
  };
1606
1775
  }
1607
1776
  return {
1777
+ // Always return register, it handles its own feature check
1778
+ register,
1779
+ // Always return contentTypes to avoid losing data when the feature is disabled
1608
1780
  contentTypes
1609
1781
  };
1610
1782
  };