@strapi/content-releases 0.0.0-next.836f74517f9a428a4798ed889c3f05057ec6beb1 → 0.0.0-next.8d33a8c285192abcf44a37c79c2e1545437c69e3

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-pNsURCL_.js → App-HjWtUYmc.js} +146 -138
  2. package/dist/_chunks/App-HjWtUYmc.js.map +1 -0
  3. package/dist/_chunks/{App-fcvNs2Qb.mjs → App-gu1aiP6i.mjs} +150 -142
  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-gzTuOXiK.js → index-ZNwxYN8H.js} +319 -18
  14. package/dist/_chunks/index-ZNwxYN8H.js.map +1 -0
  15. package/dist/_chunks/{index-pxhi8wsT.mjs → index-mvj9PSKd.mjs} +324 -23
  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 +290 -158
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +290 -158
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +12 -12
  24. package/dist/_chunks/App-fcvNs2Qb.mjs.map +0 -1
  25. package/dist/_chunks/App-pNsURCL_.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-gzTuOXiK.js.map +0 -1
  31. package/dist/_chunks/index-pxhi8wsT.mjs.map +0 -1
@@ -235,13 +235,16 @@ async function disableContentTypeLocalized({ oldContentTypes, contentTypes: cont
235
235
  if (!oldContentTypes) {
236
236
  return;
237
237
  }
238
+ const i18nPlugin = strapi.plugin("i18n");
239
+ if (!i18nPlugin) {
240
+ return;
241
+ }
238
242
  for (const uid in contentTypes2) {
239
243
  if (!oldContentTypes[uid]) {
240
244
  continue;
241
245
  }
242
246
  const oldContentType = oldContentTypes[uid];
243
247
  const contentType = contentTypes2[uid];
244
- const i18nPlugin = strapi.plugin("i18n");
245
248
  const { isLocalizedContentType } = i18nPlugin.service("content-types");
246
249
  if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
247
250
  await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
@@ -254,13 +257,16 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
254
257
  if (!oldContentTypes) {
255
258
  return;
256
259
  }
260
+ const i18nPlugin = strapi.plugin("i18n");
261
+ if (!i18nPlugin) {
262
+ return;
263
+ }
257
264
  for (const uid in contentTypes2) {
258
265
  if (!oldContentTypes[uid]) {
259
266
  continue;
260
267
  }
261
268
  const oldContentType = oldContentTypes[uid];
262
269
  const contentType = contentTypes2[uid];
263
- const i18nPlugin = strapi.plugin("i18n");
264
270
  const { isLocalizedContentType } = i18nPlugin.service("content-types");
265
271
  const { getDefaultLocale } = i18nPlugin.service("locales");
266
272
  if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
@@ -278,6 +284,11 @@ const register = async ({ strapi: strapi2 }) => {
278
284
  strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
279
285
  strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
280
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();
291
+ }
281
292
  };
282
293
  const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
283
294
  const bootstrap = async ({ strapi: strapi2 }) => {
@@ -402,27 +413,23 @@ const bootstrap = async ({ strapi: strapi2 }) => {
402
413
  }
403
414
  }
404
415
  });
405
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
406
- getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
407
- strapi2.log.error(
408
- "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
409
- );
410
- throw err;
411
- });
412
- Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
413
- strapi2.webhookStore.addAllowedEvent(key, value);
414
- });
415
- }
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
+ });
416
425
  }
417
426
  };
418
427
  const destroy = async ({ strapi: strapi2 }) => {
419
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
420
- const scheduledJobs = getService("scheduling", {
421
- strapi: strapi2
422
- }).getAll();
423
- for (const [, job] of scheduledJobs) {
424
- job.cancel();
425
- }
428
+ const scheduledJobs = getService("scheduling", {
429
+ strapi: strapi2
430
+ }).getAll();
431
+ for (const [, job] of scheduledJobs) {
432
+ job.cancel();
426
433
  }
427
434
  };
428
435
  const schema$1 = {
@@ -547,6 +554,94 @@ const createReleaseService = ({ strapi: strapi2 }) => {
547
554
  release: release2
548
555
  });
549
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
+ };
550
645
  return {
551
646
  async create(releaseData, { user }) {
552
647
  const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
@@ -566,7 +661,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
566
661
  status: "empty"
567
662
  }
568
663
  });
569
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
664
+ if (releaseWithCreatorFields.scheduledAt) {
570
665
  const schedulingService = getService("scheduling", { strapi: strapi2 });
571
666
  await schedulingService.set(release2.id, release2.scheduledAt);
572
667
  }
@@ -590,12 +685,18 @@ const createReleaseService = ({ strapi: strapi2 }) => {
590
685
  }
591
686
  });
592
687
  },
593
- async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
688
+ async findManyWithContentTypeEntryAttached(contentTypeUid, entriesIds) {
689
+ let entries = entriesIds;
690
+ if (!Array.isArray(entriesIds)) {
691
+ entries = [entriesIds];
692
+ }
594
693
  const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
595
694
  where: {
596
695
  actions: {
597
696
  target_type: contentTypeUid,
598
- target_id: entryId
697
+ target_id: {
698
+ $in: entries
699
+ }
599
700
  },
600
701
  releasedAt: {
601
702
  $null: true
@@ -606,18 +707,25 @@ const createReleaseService = ({ strapi: strapi2 }) => {
606
707
  actions: {
607
708
  where: {
608
709
  target_type: contentTypeUid,
609
- target_id: entryId
710
+ target_id: {
711
+ $in: entries
712
+ }
713
+ },
714
+ populate: {
715
+ entry: {
716
+ select: ["id"]
717
+ }
610
718
  }
611
719
  }
612
720
  }
613
721
  });
614
722
  return releases.map((release2) => {
615
723
  if (release2.actions?.length) {
616
- const [actionForEntry] = release2.actions;
724
+ const actionsForEntry = release2.actions;
617
725
  delete release2.actions;
618
726
  return {
619
727
  ...release2,
620
- action: actionForEntry
728
+ actions: actionsForEntry
621
729
  };
622
730
  }
623
731
  return release2;
@@ -691,13 +799,11 @@ const createReleaseService = ({ strapi: strapi2 }) => {
691
799
  // @ts-expect-error see above
692
800
  data: releaseWithCreatorFields
693
801
  });
694
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
695
- const schedulingService = getService("scheduling", { strapi: strapi2 });
696
- if (releaseData.scheduledAt) {
697
- await schedulingService.set(id, releaseData.scheduledAt);
698
- } else if (release2.scheduledAt) {
699
- schedulingService.cancel(id);
700
- }
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);
701
807
  }
702
808
  this.updateReleaseStatus(id);
703
809
  strapi2.telemetry.send("didUpdateContentRelease");
@@ -863,7 +969,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
863
969
  });
864
970
  await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
865
971
  });
866
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
972
+ if (release2.scheduledAt) {
867
973
  const schedulingService = getService("scheduling", { strapi: strapi2 });
868
974
  await schedulingService.cancel(release2.id);
869
975
  }
@@ -871,145 +977,71 @@ const createReleaseService = ({ strapi: strapi2 }) => {
871
977
  return release2;
872
978
  },
873
979
  async publish(releaseId) {
874
- try {
875
- const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
876
- RELEASE_MODEL_UID,
877
- releaseId,
878
- {
879
- populate: {
880
- actions: {
881
- populate: {
882
- entry: {
883
- fields: ["id"]
884
- }
885
- }
886
- }
887
- }
888
- }
889
- );
890
- 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) {
891
986
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
892
987
  }
893
- if (releaseWithPopulatedActionEntries.releasedAt) {
988
+ if (lockedRelease.releasedAt) {
894
989
  throw new utils.errors.ValidationError("Release already published");
895
990
  }
896
- if (releaseWithPopulatedActionEntries.actions.length === 0) {
897
- throw new utils.errors.ValidationError("No entries to publish");
898
- }
899
- const collectionTypeActions = {};
900
- const singleTypeActions = [];
901
- for (const action of releaseWithPopulatedActionEntries.actions) {
902
- const contentTypeUid = action.contentType;
903
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
904
- if (!collectionTypeActions[contentTypeUid]) {
905
- collectionTypeActions[contentTypeUid] = {
906
- entriestoPublishIds: [],
907
- entriesToUnpublishIds: []
908
- };
909
- }
910
- if (action.type === "publish") {
911
- collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
912
- } else {
913
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
914
- }
915
- } else {
916
- singleTypeActions.push({
917
- uid: contentTypeUid,
918
- action: action.type,
919
- id: action.entry.id
920
- });
921
- }
991
+ if (lockedRelease.status === "failed") {
992
+ throw new utils.errors.ValidationError("Release failed to publish");
922
993
  }
923
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
924
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
925
- await strapi2.db.transaction(async () => {
926
- for (const { uid, action, id } of singleTypeActions) {
927
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
928
- const entry = await strapi2.entityService.findOne(uid, id, { populate });
929
- try {
930
- if (action === "publish") {
931
- await entityManagerService.publish(entry, uid);
932
- } else {
933
- await entityManagerService.unpublish(entry, uid);
934
- }
935
- } catch (error) {
936
- if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
937
- } else {
938
- throw error;
939
- }
940
- }
941
- }
942
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
943
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
944
- const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
945
- const entriesToPublish = await strapi2.entityService.findMany(
946
- contentTypeUid,
947
- {
948
- filters: {
949
- id: {
950
- $in: entriestoPublishIds
951
- }
952
- },
953
- populate
954
- }
955
- );
956
- const entriesToUnpublish = await strapi2.entityService.findMany(
957
- contentTypeUid,
958
- {
959
- filters: {
960
- id: {
961
- $in: entriesToUnpublishIds
962
- }
963
- },
964
- populate
965
- }
966
- );
967
- if (entriesToPublish.length > 0) {
968
- 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);
969
1002
  }
970
- if (entriesToUnpublish.length > 0) {
971
- 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
+ );
972
1010
  }
973
- }
974
- });
975
- const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
976
- data: {
977
- /*
978
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
979
- */
980
- // @ts-expect-error see above
981
- releasedAt: /* @__PURE__ */ new Date()
982
- },
983
- populate: {
984
- actions: {
985
- // @ts-expect-error is not expecting count but it is working
986
- 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()
987
1019
  }
988
- }
989
- });
990
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
1020
+ });
991
1021
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
992
1022
  isPublished: true,
993
- release: release2
1023
+ release: release22
994
1024
  });
995
- }
996
- strapi2.telemetry.send("didPublishContentRelease");
997
- return release2;
998
- } catch (error) {
999
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
1025
+ strapi2.telemetry.send("didPublishContentRelease");
1026
+ return { release: release22, error: null };
1027
+ } catch (error2) {
1000
1028
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
1001
1029
  isPublished: false,
1002
- error
1030
+ error: error2
1003
1031
  });
1004
- }
1005
- strapi2.db.query(RELEASE_MODEL_UID).update({
1006
- where: { id: releaseId },
1007
- data: {
1032
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
1008
1033
  status: "failed"
1009
- }
1010
- });
1034
+ }).transacting(trx).execute();
1035
+ return {
1036
+ release: null,
1037
+ error: error2
1038
+ };
1039
+ }
1040
+ });
1041
+ if (error) {
1011
1042
  throw error;
1012
1043
  }
1044
+ return release2;
1013
1045
  },
1014
1046
  async updateAction(actionId, releaseId, update) {
1015
1047
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
@@ -1096,6 +1128,12 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1096
1128
  }
1097
1129
  };
1098
1130
  };
1131
+ class AlreadyOnReleaseError extends utils.errors.ApplicationError {
1132
+ constructor(message) {
1133
+ super(message);
1134
+ this.name = "AlreadyOnReleaseError";
1135
+ }
1136
+ }
1099
1137
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1100
1138
  async validateUniqueEntry(releaseId, releaseActionArgs) {
1101
1139
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
@@ -1108,7 +1146,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1108
1146
  (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1109
1147
  );
1110
1148
  if (isEntryInRelease) {
1111
- throw new utils.errors.ValidationError(
1149
+ throw new AlreadyOnReleaseError(
1112
1150
  `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1113
1151
  );
1114
1152
  }
@@ -1216,7 +1254,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1216
1254
  const services = {
1217
1255
  release: createReleaseService,
1218
1256
  "release-validation": createReleaseValidationService,
1219
- ...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
1257
+ scheduling: createSchedulingService
1220
1258
  };
1221
1259
  const RELEASE_SCHEMA = yup__namespace.object().shape({
1222
1260
  name: yup__namespace.string().trim().required(),
@@ -1303,6 +1341,33 @@ const releaseController = {
1303
1341
  };
1304
1342
  ctx.body = { data };
1305
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
+ },
1306
1371
  async create(ctx) {
1307
1372
  const user = ctx.state.user;
1308
1373
  const releaseArgs = ctx.request.body;
@@ -1392,6 +1457,38 @@ const releaseActionController = {
1392
1457
  data: releaseAction2
1393
1458
  };
1394
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
+ },
1395
1492
  async findMany(ctx) {
1396
1493
  const releaseId = ctx.params.releaseId;
1397
1494
  const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
@@ -1460,6 +1557,22 @@ const controllers = { release: releaseController, "release-action": releaseActio
1460
1557
  const release = {
1461
1558
  type: "admin",
1462
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
+ },
1463
1576
  {
1464
1577
  method: "POST",
1465
1578
  path: "/",
@@ -1577,6 +1690,22 @@ const releaseAction = {
1577
1690
  ]
1578
1691
  }
1579
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
+ },
1580
1709
  {
1581
1710
  method: "GET",
1582
1711
  path: "/:releaseId/actions",
@@ -1645,6 +1774,9 @@ const getPlugin = () => {
1645
1774
  };
1646
1775
  }
1647
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
1648
1780
  contentTypes
1649
1781
  };
1650
1782
  };