@strapi/content-releases 0.0.0-next.a9d79bec775daaf0da4e506b2aebafdb4ca95b06 → 0.0.0-next.ac2b9fdba5ef59eb22c4e387ac1c5a13dd219f29

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 (32) hide show
  1. package/LICENSE +17 -1
  2. package/dist/_chunks/{App-p8aKBitd.js → App-dLXY5ei3.js} +138 -131
  3. package/dist/_chunks/App-dLXY5ei3.js.map +1 -0
  4. package/dist/_chunks/{App-bpzO2Ljh.mjs → App-jrh58sXY.mjs} +141 -134
  5. package/dist/_chunks/App-jrh58sXY.mjs.map +1 -0
  6. package/dist/_chunks/{PurchaseContentReleases-Clm0iACO.mjs → PurchaseContentReleases-3tRbmbY3.mjs} +2 -2
  7. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +1 -0
  8. package/dist/_chunks/{PurchaseContentReleases-YhAPgpG9.js → PurchaseContentReleases-bpIYXOfu.js} +2 -2
  9. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +1 -0
  10. package/dist/_chunks/{en-gcJJ5htG.js → en-HrREghh3.js} +11 -3
  11. package/dist/_chunks/en-HrREghh3.js.map +1 -0
  12. package/dist/_chunks/{en-WuuhP6Bn.mjs → en-ltT1TlKQ.mjs} +11 -3
  13. package/dist/_chunks/en-ltT1TlKQ.mjs.map +1 -0
  14. package/dist/_chunks/{index-fP3qoWZ4.js → index-CVO0Rqdm.js} +320 -18
  15. package/dist/_chunks/index-CVO0Rqdm.js.map +1 -0
  16. package/dist/_chunks/{index-AECgcaDa.mjs → index-PiOGBETy.mjs} +325 -23
  17. package/dist/_chunks/index-PiOGBETy.mjs.map +1 -0
  18. package/dist/admin/index.js +1 -1
  19. package/dist/admin/index.mjs +1 -1
  20. package/dist/server/index.js +291 -160
  21. package/dist/server/index.js.map +1 -1
  22. package/dist/server/index.mjs +291 -160
  23. package/dist/server/index.mjs.map +1 -1
  24. package/package.json +15 -14
  25. package/dist/_chunks/App-bpzO2Ljh.mjs.map +0 -1
  26. package/dist/_chunks/App-p8aKBitd.js.map +0 -1
  27. package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +0 -1
  28. package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +0 -1
  29. package/dist/_chunks/en-WuuhP6Bn.mjs.map +0 -1
  30. package/dist/_chunks/en-gcJJ5htG.js.map +0 -1
  31. package/dist/_chunks/index-AECgcaDa.mjs.map +0 -1
  32. package/dist/_chunks/index-fP3qoWZ4.js.map +0 -1
@@ -211,13 +211,16 @@ async function disableContentTypeLocalized({ oldContentTypes, contentTypes: cont
211
211
  if (!oldContentTypes) {
212
212
  return;
213
213
  }
214
+ const i18nPlugin = strapi.plugin("i18n");
215
+ if (!i18nPlugin) {
216
+ return;
217
+ }
214
218
  for (const uid in contentTypes2) {
215
219
  if (!oldContentTypes[uid]) {
216
220
  continue;
217
221
  }
218
222
  const oldContentType = oldContentTypes[uid];
219
223
  const contentType = contentTypes2[uid];
220
- const i18nPlugin = strapi.plugin("i18n");
221
224
  const { isLocalizedContentType } = i18nPlugin.service("content-types");
222
225
  if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
223
226
  await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
@@ -230,13 +233,16 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
230
233
  if (!oldContentTypes) {
231
234
  return;
232
235
  }
236
+ const i18nPlugin = strapi.plugin("i18n");
237
+ if (!i18nPlugin) {
238
+ return;
239
+ }
233
240
  for (const uid in contentTypes2) {
234
241
  if (!oldContentTypes[uid]) {
235
242
  continue;
236
243
  }
237
244
  const oldContentType = oldContentTypes[uid];
238
245
  const contentType = contentTypes2[uid];
239
- const i18nPlugin = strapi.plugin("i18n");
240
246
  const { isLocalizedContentType } = i18nPlugin.service("content-types");
241
247
  const { getDefaultLocale } = i18nPlugin.service("locales");
242
248
  if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
@@ -383,27 +389,23 @@ const bootstrap = async ({ strapi: strapi2 }) => {
383
389
  }
384
390
  }
385
391
  });
386
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
387
- getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
388
- strapi2.log.error(
389
- "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
390
- );
391
- throw err;
392
- });
393
- Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
394
- strapi2.webhookStore.addAllowedEvent(key, value);
395
- });
396
- }
392
+ getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
393
+ strapi2.log.error(
394
+ "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
395
+ );
396
+ throw err;
397
+ });
398
+ Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
399
+ strapi2.webhookStore.addAllowedEvent(key, value);
400
+ });
397
401
  }
398
402
  };
399
403
  const destroy = async ({ strapi: strapi2 }) => {
400
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
401
- const scheduledJobs = getService("scheduling", {
402
- strapi: strapi2
403
- }).getAll();
404
- for (const [, job] of scheduledJobs) {
405
- job.cancel();
406
- }
404
+ const scheduledJobs = getService("scheduling", {
405
+ strapi: strapi2
406
+ }).getAll();
407
+ for (const [, job] of scheduledJobs) {
408
+ job.cancel();
407
409
  }
408
410
  };
409
411
  const schema$1 = {
@@ -528,6 +530,94 @@ const createReleaseService = ({ strapi: strapi2 }) => {
528
530
  release: release2
529
531
  });
530
532
  };
533
+ const publishSingleTypeAction = async (uid, actionType, entryId) => {
534
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
535
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
536
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
537
+ const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
538
+ try {
539
+ if (actionType === "publish") {
540
+ await entityManagerService.publish(entry, uid);
541
+ } else {
542
+ await entityManagerService.unpublish(entry, uid);
543
+ }
544
+ } catch (error) {
545
+ if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
546
+ ;
547
+ else {
548
+ throw error;
549
+ }
550
+ }
551
+ };
552
+ const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
553
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
554
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
555
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
556
+ const entriesToPublish = await strapi2.entityService.findMany(uid, {
557
+ filters: {
558
+ id: {
559
+ $in: entriesToPublishIds
560
+ }
561
+ },
562
+ populate
563
+ });
564
+ const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
565
+ filters: {
566
+ id: {
567
+ $in: entriestoUnpublishIds
568
+ }
569
+ },
570
+ populate
571
+ });
572
+ if (entriesToPublish.length > 0) {
573
+ await entityManagerService.publishMany(entriesToPublish, uid);
574
+ }
575
+ if (entriesToUnpublish.length > 0) {
576
+ await entityManagerService.unpublishMany(entriesToUnpublish, uid);
577
+ }
578
+ };
579
+ const getFormattedActions = async (releaseId) => {
580
+ const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
581
+ where: {
582
+ release: {
583
+ id: releaseId
584
+ }
585
+ },
586
+ populate: {
587
+ entry: {
588
+ fields: ["id"]
589
+ }
590
+ }
591
+ });
592
+ if (actions.length === 0) {
593
+ throw new errors.ValidationError("No entries to publish");
594
+ }
595
+ const collectionTypeActions = {};
596
+ const singleTypeActions = [];
597
+ for (const action of actions) {
598
+ const contentTypeUid = action.contentType;
599
+ if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
600
+ if (!collectionTypeActions[contentTypeUid]) {
601
+ collectionTypeActions[contentTypeUid] = {
602
+ entriesToPublishIds: [],
603
+ entriesToUnpublishIds: []
604
+ };
605
+ }
606
+ if (action.type === "publish") {
607
+ collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
608
+ } else {
609
+ collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
610
+ }
611
+ } else {
612
+ singleTypeActions.push({
613
+ uid: contentTypeUid,
614
+ action: action.type,
615
+ id: action.entry.id
616
+ });
617
+ }
618
+ }
619
+ return { collectionTypeActions, singleTypeActions };
620
+ };
531
621
  return {
532
622
  async create(releaseData, { user }) {
533
623
  const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
@@ -547,7 +637,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
547
637
  status: "empty"
548
638
  }
549
639
  });
550
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
640
+ if (releaseWithCreatorFields.scheduledAt) {
551
641
  const schedulingService = getService("scheduling", { strapi: strapi2 });
552
642
  await schedulingService.set(release2.id, release2.scheduledAt);
553
643
  }
@@ -571,12 +661,18 @@ const createReleaseService = ({ strapi: strapi2 }) => {
571
661
  }
572
662
  });
573
663
  },
574
- async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
664
+ async findManyWithContentTypeEntryAttached(contentTypeUid, entriesIds) {
665
+ let entries = entriesIds;
666
+ if (!Array.isArray(entriesIds)) {
667
+ entries = [entriesIds];
668
+ }
575
669
  const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
576
670
  where: {
577
671
  actions: {
578
672
  target_type: contentTypeUid,
579
- target_id: entryId
673
+ target_id: {
674
+ $in: entries
675
+ }
580
676
  },
581
677
  releasedAt: {
582
678
  $null: true
@@ -587,18 +683,25 @@ const createReleaseService = ({ strapi: strapi2 }) => {
587
683
  actions: {
588
684
  where: {
589
685
  target_type: contentTypeUid,
590
- target_id: entryId
686
+ target_id: {
687
+ $in: entries
688
+ }
689
+ },
690
+ populate: {
691
+ entry: {
692
+ select: ["id"]
693
+ }
591
694
  }
592
695
  }
593
696
  }
594
697
  });
595
698
  return releases.map((release2) => {
596
699
  if (release2.actions?.length) {
597
- const [actionForEntry] = release2.actions;
700
+ const actionsForEntry = release2.actions;
598
701
  delete release2.actions;
599
702
  return {
600
703
  ...release2,
601
- action: actionForEntry
704
+ actions: actionsForEntry
602
705
  };
603
706
  }
604
707
  return release2;
@@ -672,19 +775,17 @@ const createReleaseService = ({ strapi: strapi2 }) => {
672
775
  // @ts-expect-error see above
673
776
  data: releaseWithCreatorFields
674
777
  });
675
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
676
- const schedulingService = getService("scheduling", { strapi: strapi2 });
677
- if (releaseData.scheduledAt) {
678
- await schedulingService.set(id, releaseData.scheduledAt);
679
- } else if (release2.scheduledAt) {
680
- schedulingService.cancel(id);
681
- }
778
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
779
+ if (releaseData.scheduledAt) {
780
+ await schedulingService.set(id, releaseData.scheduledAt);
781
+ } else if (release2.scheduledAt) {
782
+ schedulingService.cancel(id);
682
783
  }
683
784
  this.updateReleaseStatus(id);
684
785
  strapi2.telemetry.send("didUpdateContentRelease");
685
786
  return updatedRelease;
686
787
  },
687
- async createAction(releaseId, action) {
788
+ async createAction(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
688
789
  const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
689
790
  strapi: strapi2
690
791
  });
@@ -717,7 +818,9 @@ const createReleaseService = ({ strapi: strapi2 }) => {
717
818
  },
718
819
  populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
719
820
  });
720
- this.updateReleaseStatus(releaseId);
821
+ if (!disableUpdateReleaseStatus) {
822
+ this.updateReleaseStatus(releaseId);
823
+ }
721
824
  return releaseAction2;
722
825
  },
723
826
  async findActions(releaseId, query) {
@@ -844,7 +947,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
844
947
  });
845
948
  await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
846
949
  });
847
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
950
+ if (release2.scheduledAt) {
848
951
  const schedulingService = getService("scheduling", { strapi: strapi2 });
849
952
  await schedulingService.cancel(release2.id);
850
953
  }
@@ -852,145 +955,71 @@ const createReleaseService = ({ strapi: strapi2 }) => {
852
955
  return release2;
853
956
  },
854
957
  async publish(releaseId) {
855
- try {
856
- const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
857
- RELEASE_MODEL_UID,
858
- releaseId,
859
- {
860
- populate: {
861
- actions: {
862
- populate: {
863
- entry: {
864
- fields: ["id"]
865
- }
866
- }
867
- }
868
- }
869
- }
870
- );
871
- if (!releaseWithPopulatedActionEntries) {
958
+ const {
959
+ release: release2,
960
+ error
961
+ } = await strapi2.db.transaction(async ({ trx }) => {
962
+ const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
963
+ if (!lockedRelease) {
872
964
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
873
965
  }
874
- if (releaseWithPopulatedActionEntries.releasedAt) {
966
+ if (lockedRelease.releasedAt) {
875
967
  throw new errors.ValidationError("Release already published");
876
968
  }
877
- if (releaseWithPopulatedActionEntries.actions.length === 0) {
878
- throw new errors.ValidationError("No entries to publish");
969
+ if (lockedRelease.status === "failed") {
970
+ throw new errors.ValidationError("Release failed to publish");
879
971
  }
880
- const collectionTypeActions = {};
881
- const singleTypeActions = [];
882
- for (const action of releaseWithPopulatedActionEntries.actions) {
883
- const contentTypeUid = action.contentType;
884
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
885
- if (!collectionTypeActions[contentTypeUid]) {
886
- collectionTypeActions[contentTypeUid] = {
887
- entriestoPublishIds: [],
888
- entriesToUnpublishIds: []
889
- };
890
- }
891
- if (action.type === "publish") {
892
- collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
893
- } else {
894
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
895
- }
896
- } else {
897
- singleTypeActions.push({
898
- uid: contentTypeUid,
899
- action: action.type,
900
- id: action.entry.id
901
- });
902
- }
903
- }
904
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
905
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
906
- await strapi2.db.transaction(async () => {
907
- for (const { uid, action, id } of singleTypeActions) {
908
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
909
- const entry = await strapi2.entityService.findOne(uid, id, { populate });
910
- try {
911
- if (action === "publish") {
912
- await entityManagerService.publish(entry, uid);
913
- } else {
914
- await entityManagerService.unpublish(entry, uid);
915
- }
916
- } catch (error) {
917
- if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
918
- } else {
919
- throw error;
920
- }
921
- }
922
- }
923
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
924
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
925
- const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
926
- const entriesToPublish = await strapi2.entityService.findMany(
927
- contentTypeUid,
928
- {
929
- filters: {
930
- id: {
931
- $in: entriestoPublishIds
932
- }
933
- },
934
- populate
935
- }
936
- );
937
- const entriesToUnpublish = await strapi2.entityService.findMany(
938
- contentTypeUid,
939
- {
940
- filters: {
941
- id: {
942
- $in: entriesToUnpublishIds
943
- }
944
- },
945
- populate
946
- }
947
- );
948
- if (entriesToPublish.length > 0) {
949
- await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
972
+ try {
973
+ strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
974
+ const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
975
+ releaseId
976
+ );
977
+ await strapi2.db.transaction(async () => {
978
+ for (const { uid, action, id } of singleTypeActions) {
979
+ await publishSingleTypeAction(uid, action, id);
950
980
  }
951
- if (entriesToUnpublish.length > 0) {
952
- await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
981
+ for (const contentTypeUid of Object.keys(collectionTypeActions)) {
982
+ const uid = contentTypeUid;
983
+ await publishCollectionTypeAction(
984
+ uid,
985
+ collectionTypeActions[uid].entriesToPublishIds,
986
+ collectionTypeActions[uid].entriesToUnpublishIds
987
+ );
953
988
  }
954
- }
955
- });
956
- const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
957
- data: {
958
- /*
959
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
960
- */
961
- // @ts-expect-error see above
962
- releasedAt: /* @__PURE__ */ new Date()
963
- },
964
- populate: {
965
- actions: {
966
- // @ts-expect-error is not expecting count but it is working
967
- count: true
989
+ });
990
+ const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
991
+ where: {
992
+ id: releaseId
993
+ },
994
+ data: {
995
+ status: "done",
996
+ releasedAt: /* @__PURE__ */ new Date()
968
997
  }
969
- }
970
- });
971
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
998
+ });
972
999
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
973
1000
  isPublished: true,
974
- release: release2
1001
+ release: release22
975
1002
  });
976
- }
977
- strapi2.telemetry.send("didPublishContentRelease");
978
- return release2;
979
- } catch (error) {
980
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
1003
+ strapi2.telemetry.send("didPublishContentRelease");
1004
+ return { release: release22, error: null };
1005
+ } catch (error2) {
981
1006
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
982
1007
  isPublished: false,
983
- error
1008
+ error: error2
984
1009
  });
985
- }
986
- strapi2.db.query(RELEASE_MODEL_UID).update({
987
- where: { id: releaseId },
988
- data: {
1010
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
989
1011
  status: "failed"
990
- }
991
- });
1012
+ }).transacting(trx).execute();
1013
+ return {
1014
+ release: null,
1015
+ error: error2
1016
+ };
1017
+ }
1018
+ });
1019
+ if (error) {
992
1020
  throw error;
993
1021
  }
1022
+ return release2;
994
1023
  },
995
1024
  async updateAction(actionId, releaseId, update) {
996
1025
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
@@ -1077,6 +1106,12 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1077
1106
  }
1078
1107
  };
1079
1108
  };
1109
+ class AlreadyOnReleaseError extends errors.ApplicationError {
1110
+ constructor(message) {
1111
+ super(message);
1112
+ this.name = "AlreadyOnReleaseError";
1113
+ }
1114
+ }
1080
1115
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1081
1116
  async validateUniqueEntry(releaseId, releaseActionArgs) {
1082
1117
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
@@ -1089,7 +1124,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1089
1124
  (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1090
1125
  );
1091
1126
  if (isEntryInRelease) {
1092
- throw new errors.ValidationError(
1127
+ throw new AlreadyOnReleaseError(
1093
1128
  `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1094
1129
  );
1095
1130
  }
@@ -1197,7 +1232,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1197
1232
  const services = {
1198
1233
  release: createReleaseService,
1199
1234
  "release-validation": createReleaseValidationService,
1200
- ...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
1235
+ scheduling: createSchedulingService
1201
1236
  };
1202
1237
  const RELEASE_SCHEMA = yup.object().shape({
1203
1238
  name: yup.string().trim().required(),
@@ -1284,6 +1319,33 @@ const releaseController = {
1284
1319
  };
1285
1320
  ctx.body = { data };
1286
1321
  },
1322
+ async mapEntriesToReleases(ctx) {
1323
+ const { contentTypeUid, entriesIds } = ctx.query;
1324
+ if (!contentTypeUid || !entriesIds) {
1325
+ throw new errors.ValidationError("Missing required query parameters");
1326
+ }
1327
+ const releaseService = getService("release", { strapi });
1328
+ const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
1329
+ contentTypeUid,
1330
+ entriesIds
1331
+ );
1332
+ const mappedEntriesInReleases = releasesWithActions.reduce(
1333
+ (acc, release2) => {
1334
+ release2.actions.forEach((action) => {
1335
+ if (!acc[action.entry.id]) {
1336
+ acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
1337
+ } else {
1338
+ acc[action.entry.id].push({ id: release2.id, name: release2.name });
1339
+ }
1340
+ });
1341
+ return acc;
1342
+ },
1343
+ {}
1344
+ );
1345
+ ctx.body = {
1346
+ data: mappedEntriesInReleases
1347
+ };
1348
+ },
1287
1349
  async create(ctx) {
1288
1350
  const user = ctx.state.user;
1289
1351
  const releaseArgs = ctx.request.body;
@@ -1373,6 +1435,43 @@ const releaseActionController = {
1373
1435
  data: releaseAction2
1374
1436
  };
1375
1437
  },
1438
+ async createMany(ctx) {
1439
+ const releaseId = ctx.params.releaseId;
1440
+ const releaseActionsArgs = ctx.request.body;
1441
+ await Promise.all(
1442
+ releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1443
+ );
1444
+ const releaseService = getService("release", { strapi });
1445
+ const releaseActions = await strapi.db.transaction(async () => {
1446
+ const releaseActions2 = await Promise.all(
1447
+ releaseActionsArgs.map(async (releaseActionArgs) => {
1448
+ try {
1449
+ const action = await releaseService.createAction(releaseId, releaseActionArgs, {
1450
+ disableUpdateReleaseStatus: true
1451
+ });
1452
+ return action;
1453
+ } catch (error) {
1454
+ if (error instanceof AlreadyOnReleaseError) {
1455
+ return null;
1456
+ }
1457
+ throw error;
1458
+ }
1459
+ })
1460
+ );
1461
+ return releaseActions2;
1462
+ });
1463
+ const newReleaseActions = releaseActions.filter((action) => action !== null);
1464
+ if (newReleaseActions.length > 0) {
1465
+ releaseService.updateReleaseStatus(releaseId);
1466
+ }
1467
+ ctx.body = {
1468
+ data: newReleaseActions,
1469
+ meta: {
1470
+ entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
1471
+ totalEntries: releaseActions.length
1472
+ }
1473
+ };
1474
+ },
1376
1475
  async findMany(ctx) {
1377
1476
  const releaseId = ctx.params.releaseId;
1378
1477
  const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
@@ -1441,6 +1540,22 @@ const controllers = { release: releaseController, "release-action": releaseActio
1441
1540
  const release = {
1442
1541
  type: "admin",
1443
1542
  routes: [
1543
+ {
1544
+ method: "GET",
1545
+ path: "/mapEntriesToReleases",
1546
+ handler: "release.mapEntriesToReleases",
1547
+ config: {
1548
+ policies: [
1549
+ "admin::isAuthenticatedAdmin",
1550
+ {
1551
+ name: "admin::hasPermissions",
1552
+ config: {
1553
+ actions: ["plugin::content-releases.read"]
1554
+ }
1555
+ }
1556
+ ]
1557
+ }
1558
+ },
1444
1559
  {
1445
1560
  method: "POST",
1446
1561
  path: "/",
@@ -1558,6 +1673,22 @@ const releaseAction = {
1558
1673
  ]
1559
1674
  }
1560
1675
  },
1676
+ {
1677
+ method: "POST",
1678
+ path: "/:releaseId/actions/bulk",
1679
+ handler: "release-action.createMany",
1680
+ config: {
1681
+ policies: [
1682
+ "admin::isAuthenticatedAdmin",
1683
+ {
1684
+ name: "admin::hasPermissions",
1685
+ config: {
1686
+ actions: ["plugin::content-releases.create-action"]
1687
+ }
1688
+ }
1689
+ ]
1690
+ }
1691
+ },
1561
1692
  {
1562
1693
  method: "GET",
1563
1694
  path: "/:releaseId/actions",