@strapi/content-releases 4.20.5 → 5.0.0-alpha.1

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 (101) hide show
  1. package/dist/_chunks/{App-p8aKBitd.js → App-vS3MaAL8.js} +277 -279
  2. package/dist/_chunks/App-vS3MaAL8.js.map +1 -0
  3. package/dist/_chunks/{App-bpzO2Ljh.mjs → App-wYLtVioz.mjs} +271 -273
  4. package/dist/_chunks/App-wYLtVioz.mjs.map +1 -0
  5. package/dist/_chunks/{en-WuuhP6Bn.mjs → en-RdapH-9X.mjs} +1 -2
  6. package/dist/_chunks/en-RdapH-9X.mjs.map +1 -0
  7. package/dist/_chunks/{en-gcJJ5htG.js → en-faJDuv3q.js} +1 -2
  8. package/dist/_chunks/en-faJDuv3q.js.map +1 -0
  9. package/dist/_chunks/{index-fP3qoWZ4.js → index-EoKpj3V5.js} +70 -61
  10. package/dist/_chunks/index-EoKpj3V5.js.map +1 -0
  11. package/dist/_chunks/{index-AECgcaDa.mjs → index-RhLsjv0x.mjs} +72 -63
  12. package/dist/_chunks/index-RhLsjv0x.mjs.map +1 -0
  13. package/dist/admin/index.js +1 -14
  14. package/dist/admin/index.js.map +1 -1
  15. package/dist/admin/index.mjs +1 -14
  16. package/dist/admin/index.mjs.map +1 -1
  17. package/dist/admin/src/components/CMReleasesContainer.d.ts +1 -0
  18. package/dist/admin/src/components/RelativeTime.d.ts +28 -0
  19. package/dist/admin/src/components/ReleaseActionMenu.d.ts +26 -0
  20. package/dist/admin/src/components/ReleaseActionOptions.d.ts +9 -0
  21. package/dist/admin/src/components/ReleaseModal.d.ts +16 -0
  22. package/dist/admin/src/constants.d.ts +58 -0
  23. package/dist/admin/src/index.d.ts +3 -0
  24. package/dist/admin/src/pages/App.d.ts +1 -0
  25. package/dist/admin/src/pages/PurchaseContentReleases.d.ts +2 -0
  26. package/dist/admin/src/pages/ReleaseDetailsPage.d.ts +2 -0
  27. package/dist/admin/src/pages/ReleasesPage.d.ts +8 -0
  28. package/dist/admin/src/pages/tests/mockReleaseDetailsPageData.d.ts +181 -0
  29. package/dist/admin/src/pages/tests/mockReleasesPageData.d.ts +39 -0
  30. package/dist/admin/src/pluginId.d.ts +1 -0
  31. package/dist/admin/src/services/axios.d.ts +29 -0
  32. package/dist/admin/src/services/release.d.ts +369 -0
  33. package/dist/admin/src/store/hooks.d.ts +7 -0
  34. package/dist/admin/src/utils/time.d.ts +1 -0
  35. package/dist/server/index.js +277 -232
  36. package/dist/server/index.js.map +1 -1
  37. package/dist/server/index.mjs +278 -232
  38. package/dist/server/index.mjs.map +1 -1
  39. package/dist/server/src/bootstrap.d.ts +5 -0
  40. package/dist/server/src/bootstrap.d.ts.map +1 -0
  41. package/dist/server/src/constants.d.ts +12 -0
  42. package/dist/server/src/constants.d.ts.map +1 -0
  43. package/dist/server/src/content-types/index.d.ts +99 -0
  44. package/dist/server/src/content-types/index.d.ts.map +1 -0
  45. package/dist/server/src/content-types/release/index.d.ts +48 -0
  46. package/dist/server/src/content-types/release/index.d.ts.map +1 -0
  47. package/dist/server/src/content-types/release/schema.d.ts +47 -0
  48. package/dist/server/src/content-types/release/schema.d.ts.map +1 -0
  49. package/dist/server/src/content-types/release-action/index.d.ts +50 -0
  50. package/dist/server/src/content-types/release-action/index.d.ts.map +1 -0
  51. package/dist/server/src/content-types/release-action/schema.d.ts +49 -0
  52. package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -0
  53. package/dist/server/src/controllers/index.d.ts +19 -0
  54. package/dist/server/src/controllers/index.d.ts.map +1 -0
  55. package/dist/server/src/controllers/release-action.d.ts +10 -0
  56. package/dist/server/src/controllers/release-action.d.ts.map +1 -0
  57. package/dist/server/src/controllers/release.d.ts +11 -0
  58. package/dist/server/src/controllers/release.d.ts.map +1 -0
  59. package/dist/server/src/controllers/validation/release-action.d.ts +8 -0
  60. package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -0
  61. package/dist/server/src/controllers/validation/release.d.ts +2 -0
  62. package/dist/server/src/controllers/validation/release.d.ts.map +1 -0
  63. package/dist/server/src/destroy.d.ts +5 -0
  64. package/dist/server/src/destroy.d.ts.map +1 -0
  65. package/dist/server/src/index.d.ts +2095 -0
  66. package/dist/server/src/index.d.ts.map +1 -0
  67. package/dist/server/src/migrations/index.d.ts +12 -0
  68. package/dist/server/src/migrations/index.d.ts.map +1 -0
  69. package/dist/server/src/register.d.ts +5 -0
  70. package/dist/server/src/register.d.ts.map +1 -0
  71. package/dist/server/src/routes/index.d.ts +35 -0
  72. package/dist/server/src/routes/index.d.ts.map +1 -0
  73. package/dist/server/src/routes/release-action.d.ts +18 -0
  74. package/dist/server/src/routes/release-action.d.ts.map +1 -0
  75. package/dist/server/src/routes/release.d.ts +18 -0
  76. package/dist/server/src/routes/release.d.ts.map +1 -0
  77. package/dist/server/src/services/index.d.ts +1826 -0
  78. package/dist/server/src/services/index.d.ts.map +1 -0
  79. package/dist/server/src/services/release.d.ts +66 -0
  80. package/dist/server/src/services/release.d.ts.map +1 -0
  81. package/dist/server/src/services/scheduling.d.ts +18 -0
  82. package/dist/server/src/services/scheduling.d.ts.map +1 -0
  83. package/dist/server/src/services/validation.d.ts +18 -0
  84. package/dist/server/src/services/validation.d.ts.map +1 -0
  85. package/dist/server/src/utils/index.d.ts +14 -0
  86. package/dist/server/src/utils/index.d.ts.map +1 -0
  87. package/dist/shared/contracts/release-actions.d.ts +131 -0
  88. package/dist/shared/contracts/release-actions.d.ts.map +1 -0
  89. package/dist/shared/contracts/releases.d.ts +166 -0
  90. package/dist/shared/contracts/releases.d.ts.map +1 -0
  91. package/dist/shared/types.d.ts +24 -0
  92. package/dist/shared/types.d.ts.map +1 -0
  93. package/dist/shared/validation-schemas.d.ts +2 -0
  94. package/dist/shared/validation-schemas.d.ts.map +1 -0
  95. package/package.json +22 -28
  96. package/dist/_chunks/App-bpzO2Ljh.mjs.map +0 -1
  97. package/dist/_chunks/App-p8aKBitd.js.map +0 -1
  98. package/dist/_chunks/en-WuuhP6Bn.mjs.map +0 -1
  99. package/dist/_chunks/en-gcJJ5htG.js.map +0 -1
  100. package/dist/_chunks/index-AECgcaDa.mjs.map +0 -1
  101. package/dist/_chunks/index-fP3qoWZ4.js.map +0 -1
@@ -1,8 +1,7 @@
1
- import { contentTypes as contentTypes$1, mapAsync, setCreatorFields, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
1
+ import { async, setCreatorFields, convertQueryParams, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
2
2
  import isEqual from "lodash/isEqual";
3
3
  import { difference, keys } from "lodash";
4
4
  import _ from "lodash/fp";
5
- import EE from "@strapi/strapi/dist/utils/ee";
6
5
  import { scheduleJob } from "node-schedule";
7
6
  import * as yup from "yup";
8
7
  const RELEASE_MODEL_UID = "plugin::content-releases.release";
@@ -60,7 +59,10 @@ const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
60
59
  const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
61
60
  const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
62
61
  const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
63
- const entry = await strapi2.entityService.findOne(contentTypeUid, entryId, { populate });
62
+ const entry = await strapi2.db.query(contentTypeUid).findOne({
63
+ where: { id: entryId },
64
+ populate
65
+ });
64
66
  return entry;
65
67
  };
66
68
  const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
@@ -77,28 +79,10 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } =
77
79
  return false;
78
80
  }
79
81
  };
80
- async function deleteActionsOnDisableDraftAndPublish({
81
- oldContentTypes,
82
- contentTypes: contentTypes2
83
- }) {
84
- if (!oldContentTypes) {
85
- return;
86
- }
87
- for (const uid in contentTypes2) {
88
- if (!oldContentTypes[uid]) {
89
- continue;
90
- }
91
- const oldContentType = oldContentTypes[uid];
92
- const contentType = contentTypes2[uid];
93
- if (contentTypes$1.hasDraftAndPublish(oldContentType) && !contentTypes$1.hasDraftAndPublish(contentType)) {
94
- await strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: uid }).execute();
95
- }
96
- }
97
- }
98
82
  async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
99
83
  const deletedContentTypes = difference(keys(oldContentTypes), keys(contentTypes2)) ?? [];
100
84
  if (deletedContentTypes.length) {
101
- await mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
85
+ await async.map(deletedContentTypes, async (deletedContentTypeUID) => {
102
86
  return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
103
87
  });
104
88
  }
@@ -117,7 +101,7 @@ async function migrateIsValidAndStatusReleases() {
117
101
  }
118
102
  }
119
103
  });
120
- mapAsync(releasesWithoutStatus, async (release2) => {
104
+ async.map(releasesWithoutStatus, async (release2) => {
121
105
  const actions = release2.actions;
122
106
  const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
123
107
  for (const action of notValidatedActions) {
@@ -148,7 +132,7 @@ async function migrateIsValidAndStatusReleases() {
148
132
  }
149
133
  }
150
134
  });
151
- mapAsync(publishedReleases, async (release2) => {
135
+ async.map(publishedReleases, async (release2) => {
152
136
  return strapi.db.query(RELEASE_MODEL_UID).update({
153
137
  where: {
154
138
  id: release2.id
@@ -161,11 +145,9 @@ async function migrateIsValidAndStatusReleases() {
161
145
  }
162
146
  async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
163
147
  if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
164
- const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
165
- (uid) => oldContentTypes[uid]?.options?.draftAndPublish
166
- );
148
+ const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes);
167
149
  const releasesAffected = /* @__PURE__ */ new Set();
168
- mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
150
+ async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
169
151
  const oldContentType = oldContentTypes[contentTypeUID];
170
152
  const contentType = contentTypes2[contentTypeUID];
171
153
  if (!isEqual(oldContentType?.attributes, contentType?.attributes)) {
@@ -178,7 +160,7 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
178
160
  release: true
179
161
  }
180
162
  });
181
- await mapAsync(actions, async (action) => {
163
+ await async.map(actions, async (action) => {
182
164
  if (action.entry && action.release) {
183
165
  const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
184
166
  strapi
@@ -201,7 +183,7 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
201
183
  });
202
184
  }
203
185
  }).then(() => {
204
- mapAsync(releasesAffected, async (releaseId) => {
186
+ async.map(releasesAffected, async (releaseId) => {
205
187
  return getService("release", { strapi }).updateReleaseStatus(releaseId);
206
188
  });
207
189
  });
@@ -247,11 +229,10 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
247
229
  }
248
230
  }
249
231
  }
250
- const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
251
232
  const register = async ({ strapi: strapi2 }) => {
252
- if (features$2.isEnabled("cms-content-releases")) {
233
+ if (strapi2.ee.features.isEnabled("cms-content-releases")) {
253
234
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
254
- strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
235
+ strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized);
255
236
  strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
256
237
  }
257
238
  if (strapi2.plugin("graphql")) {
@@ -260,12 +241,9 @@ const register = async ({ strapi: strapi2 }) => {
260
241
  graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
261
242
  }
262
243
  };
263
- const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
264
244
  const bootstrap = async ({ strapi: strapi2 }) => {
265
- if (features$1.isEnabled("cms-content-releases")) {
266
- const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
267
- (uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
268
- );
245
+ if (strapi2.ee.features.isEnabled("cms-content-releases")) {
246
+ const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes);
269
247
  strapi2.db.lifecycles.subscribe({
270
248
  models: contentTypesWithDraftAndPublish,
271
249
  async afterDelete(event) {
@@ -301,7 +279,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
301
279
  */
302
280
  async beforeDeleteMany(event) {
303
281
  const { model, params } = event;
304
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
282
+ if (model.kind === "collectionType") {
305
283
  const { where } = params;
306
284
  const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
307
285
  event.state.entriesToDelete = entriesToDelete;
@@ -383,27 +361,23 @@ const bootstrap = async ({ strapi: strapi2 }) => {
383
361
  }
384
362
  }
385
363
  });
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
- }
364
+ getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
365
+ strapi2.log.error(
366
+ "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
367
+ );
368
+ throw err;
369
+ });
370
+ Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
371
+ strapi2.webhookStore.addAllowedEvent(key, value);
372
+ });
397
373
  }
398
374
  };
399
375
  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
- }
376
+ const scheduledJobs = getService("scheduling", {
377
+ strapi: strapi2
378
+ }).getAll();
379
+ for (const [, job] of scheduledJobs) {
380
+ job.cancel();
407
381
  }
408
382
  };
409
383
  const schema$1 = {
@@ -528,6 +502,94 @@ const createReleaseService = ({ strapi: strapi2 }) => {
528
502
  release: release2
529
503
  });
530
504
  };
505
+ const publishSingleTypeAction = async (uid, actionType, entryId) => {
506
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
507
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
508
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
509
+ const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
510
+ try {
511
+ if (actionType === "publish") {
512
+ await entityManagerService.publish(entry, uid);
513
+ } else {
514
+ await entityManagerService.unpublish(entry, uid);
515
+ }
516
+ } catch (error) {
517
+ if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
518
+ ;
519
+ else {
520
+ throw error;
521
+ }
522
+ }
523
+ };
524
+ const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
525
+ const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
526
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
527
+ const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
528
+ const entriesToPublish = await strapi2.entityService.findMany(uid, {
529
+ filters: {
530
+ id: {
531
+ $in: entriesToPublishIds
532
+ }
533
+ },
534
+ populate
535
+ });
536
+ const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
537
+ filters: {
538
+ id: {
539
+ $in: entriestoUnpublishIds
540
+ }
541
+ },
542
+ populate
543
+ });
544
+ if (entriesToPublish.length > 0) {
545
+ await entityManagerService.publishMany(entriesToPublish, uid);
546
+ }
547
+ if (entriesToUnpublish.length > 0) {
548
+ await entityManagerService.unpublishMany(entriesToUnpublish, uid);
549
+ }
550
+ };
551
+ const getFormattedActions = async (releaseId) => {
552
+ const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
553
+ where: {
554
+ release: {
555
+ id: releaseId
556
+ }
557
+ },
558
+ populate: {
559
+ entry: {
560
+ fields: ["id"]
561
+ }
562
+ }
563
+ });
564
+ if (actions.length === 0) {
565
+ throw new errors.ValidationError("No entries to publish");
566
+ }
567
+ const collectionTypeActions = {};
568
+ const singleTypeActions = [];
569
+ for (const action of actions) {
570
+ const contentTypeUid = action.contentType;
571
+ if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
572
+ if (!collectionTypeActions[contentTypeUid]) {
573
+ collectionTypeActions[contentTypeUid] = {
574
+ entriesToPublishIds: [],
575
+ entriesToUnpublishIds: []
576
+ };
577
+ }
578
+ if (action.type === "publish") {
579
+ collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
580
+ } else {
581
+ collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
582
+ }
583
+ } else {
584
+ singleTypeActions.push({
585
+ uid: contentTypeUid,
586
+ action: action.type,
587
+ id: action.entry.id
588
+ });
589
+ }
590
+ }
591
+ return { collectionTypeActions, singleTypeActions };
592
+ };
531
593
  return {
532
594
  async create(releaseData, { user }) {
533
595
  const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
@@ -541,13 +603,13 @@ const createReleaseService = ({ strapi: strapi2 }) => {
541
603
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
542
604
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
543
605
  ]);
544
- const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
606
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
545
607
  data: {
546
608
  ...releaseWithCreatorFields,
547
609
  status: "empty"
548
610
  }
549
611
  });
550
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
612
+ if (releaseWithCreatorFields.scheduledAt) {
551
613
  const schedulingService = getService("scheduling", { strapi: strapi2 });
552
614
  await schedulingService.set(release2.id, release2.scheduledAt);
553
615
  }
@@ -555,17 +617,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
555
617
  return release2;
556
618
  },
557
619
  async findOne(id, query = {}) {
558
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
559
- ...query
620
+ const dbQuery = convertQueryParams.transformParamsToQuery(RELEASE_MODEL_UID, query);
621
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
622
+ ...dbQuery,
623
+ where: { id }
560
624
  });
561
625
  return release2;
562
626
  },
563
627
  findPage(query) {
564
- return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
565
- ...query,
628
+ const dbQuery = convertQueryParams.transformParamsToQuery(RELEASE_MODEL_UID, query ?? {});
629
+ return strapi2.db.query(RELEASE_MODEL_UID).findPage({
630
+ ...dbQuery,
566
631
  populate: {
567
632
  actions: {
568
- // @ts-expect-error Ignore missing properties
569
633
  count: true
570
634
  }
571
635
  }
@@ -657,28 +721,22 @@ const createReleaseService = ({ strapi: strapi2 }) => {
657
721
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
658
722
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
659
723
  ]);
660
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
724
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
661
725
  if (!release2) {
662
726
  throw new errors.NotFoundError(`No release found for id ${id}`);
663
727
  }
664
728
  if (release2.releasedAt) {
665
729
  throw new errors.ValidationError("Release already published");
666
730
  }
667
- const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
668
- /*
669
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
670
- * is not compatible with the type we are passing here: UpdateRelease.Request['body']
671
- */
672
- // @ts-expect-error see above
731
+ const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
732
+ where: { id },
673
733
  data: releaseWithCreatorFields
674
734
  });
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
- }
735
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
736
+ if (releaseData.scheduledAt) {
737
+ await schedulingService.set(id, releaseData.scheduledAt);
738
+ } else if (release2.scheduledAt) {
739
+ schedulingService.cancel(id);
682
740
  }
683
741
  this.updateReleaseStatus(id);
684
742
  strapi2.telemetry.send("didUpdateContentRelease");
@@ -692,7 +750,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
692
750
  validateEntryContentType(action.entry.contentType),
693
751
  validateUniqueEntry(releaseId, action)
694
752
  ]);
695
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
753
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
696
754
  if (!release2) {
697
755
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
698
756
  }
@@ -702,7 +760,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
702
760
  const { entry, type } = action;
703
761
  const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
704
762
  const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
705
- const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
763
+ const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
706
764
  data: {
707
765
  type,
708
766
  contentType: entry.contentType,
@@ -715,32 +773,41 @@ const createReleaseService = ({ strapi: strapi2 }) => {
715
773
  },
716
774
  release: releaseId
717
775
  },
718
- populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
776
+ populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
719
777
  });
720
778
  this.updateReleaseStatus(releaseId);
721
779
  return releaseAction2;
722
780
  },
723
781
  async findActions(releaseId, query) {
724
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
725
- fields: ["id"]
782
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
783
+ where: { id: releaseId },
784
+ select: ["id"]
726
785
  });
727
786
  if (!release2) {
728
787
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
729
788
  }
730
- return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
731
- ...query,
789
+ const dbQuery = convertQueryParams.transformParamsToQuery(
790
+ RELEASE_ACTION_MODEL_UID,
791
+ query ?? {}
792
+ );
793
+ return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
794
+ ...dbQuery,
732
795
  populate: {
733
796
  entry: {
734
797
  populate: "*"
735
798
  }
736
799
  },
737
- filters: {
800
+ where: {
738
801
  release: releaseId
739
802
  }
740
803
  });
741
804
  },
742
805
  async countActions(query) {
743
- return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
806
+ const dbQuery = convertQueryParams.transformParamsToQuery(
807
+ RELEASE_ACTION_MODEL_UID,
808
+ query ?? {}
809
+ );
810
+ return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
744
811
  },
745
812
  async groupActions(actions, groupBy) {
746
813
  const contentTypeUids = actions.reduce((acc, action) => {
@@ -821,10 +888,11 @@ const createReleaseService = ({ strapi: strapi2 }) => {
821
888
  return componentsMap;
822
889
  },
823
890
  async delete(releaseId) {
824
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
891
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
892
+ where: { id: releaseId },
825
893
  populate: {
826
894
  actions: {
827
- fields: ["id"]
895
+ select: ["id"]
828
896
  }
829
897
  }
830
898
  });
@@ -842,9 +910,13 @@ const createReleaseService = ({ strapi: strapi2 }) => {
842
910
  }
843
911
  }
844
912
  });
845
- await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
913
+ await strapi2.db.query(RELEASE_MODEL_UID).delete({
914
+ where: {
915
+ id: releaseId
916
+ }
917
+ });
846
918
  });
847
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
919
+ if (release2.scheduledAt) {
848
920
  const schedulingService = getService("scheduling", { strapi: strapi2 });
849
921
  await schedulingService.cancel(release2.id);
850
922
  }
@@ -852,145 +924,71 @@ const createReleaseService = ({ strapi: strapi2 }) => {
852
924
  return release2;
853
925
  },
854
926
  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) {
927
+ const {
928
+ release: release2,
929
+ error
930
+ } = await strapi2.db.transaction(async ({ trx }) => {
931
+ const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
932
+ if (!lockedRelease) {
872
933
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
873
934
  }
874
- if (releaseWithPopulatedActionEntries.releasedAt) {
935
+ if (lockedRelease.releasedAt) {
875
936
  throw new errors.ValidationError("Release already published");
876
937
  }
877
- if (releaseWithPopulatedActionEntries.actions.length === 0) {
878
- throw new errors.ValidationError("No entries to publish");
879
- }
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
- }
938
+ if (lockedRelease.status === "failed") {
939
+ throw new errors.ValidationError("Release failed to publish");
903
940
  }
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);
941
+ try {
942
+ strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
943
+ const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
944
+ releaseId
945
+ );
946
+ await strapi2.db.transaction(async () => {
947
+ for (const { uid, action, id } of singleTypeActions) {
948
+ await publishSingleTypeAction(uid, action, id);
950
949
  }
951
- if (entriesToUnpublish.length > 0) {
952
- await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
950
+ for (const contentTypeUid of Object.keys(collectionTypeActions)) {
951
+ const uid = contentTypeUid;
952
+ await publishCollectionTypeAction(
953
+ uid,
954
+ collectionTypeActions[uid].entriesToPublishIds,
955
+ collectionTypeActions[uid].entriesToUnpublishIds
956
+ );
953
957
  }
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
958
+ });
959
+ const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
960
+ where: {
961
+ id: releaseId
962
+ },
963
+ data: {
964
+ status: "done",
965
+ releasedAt: /* @__PURE__ */ new Date()
968
966
  }
969
- }
970
- });
971
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
967
+ });
972
968
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
973
969
  isPublished: true,
974
- release: release2
970
+ release: release22
975
971
  });
976
- }
977
- strapi2.telemetry.send("didPublishContentRelease");
978
- return release2;
979
- } catch (error) {
980
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
972
+ strapi2.telemetry.send("didPublishContentRelease");
973
+ return { release: release22, error: null };
974
+ } catch (error2) {
981
975
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
982
976
  isPublished: false,
983
- error
977
+ error: error2
984
978
  });
985
- }
986
- strapi2.db.query(RELEASE_MODEL_UID).update({
987
- where: { id: releaseId },
988
- data: {
979
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
989
980
  status: "failed"
990
- }
991
- });
981
+ }).transacting(trx).execute();
982
+ return {
983
+ release: null,
984
+ error: error2
985
+ };
986
+ }
987
+ });
988
+ if (error instanceof Error) {
992
989
  throw error;
993
990
  }
991
+ return release2;
994
992
  },
995
993
  async updateAction(actionId, releaseId, update) {
996
994
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
@@ -1077,10 +1075,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1077
1075
  }
1078
1076
  };
1079
1077
  };
1078
+ class AlreadyOnReleaseError extends errors.ApplicationError {
1079
+ constructor(message) {
1080
+ super(message);
1081
+ this.name = "AlreadyOnReleaseError";
1082
+ }
1083
+ }
1080
1084
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1081
1085
  async validateUniqueEntry(releaseId, releaseActionArgs) {
1082
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
1083
- populate: { actions: { populate: { entry: { fields: ["id"] } } } }
1086
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
1087
+ where: {
1088
+ id: releaseId
1089
+ },
1090
+ populate: { actions: { populate: { entry: { select: ["id"] } } } }
1084
1091
  });
1085
1092
  if (!release2) {
1086
1093
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
@@ -1089,7 +1096,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1089
1096
  (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1090
1097
  );
1091
1098
  if (isEntryInRelease) {
1092
- throw new errors.ValidationError(
1099
+ throw new AlreadyOnReleaseError(
1093
1100
  `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1094
1101
  );
1095
1102
  }
@@ -1099,17 +1106,9 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1099
1106
  if (!contentType) {
1100
1107
  throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1101
1108
  }
1102
- if (!contentType.options?.draftAndPublish) {
1103
- throw new errors.ValidationError(
1104
- `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1105
- );
1106
- }
1107
1109
  },
1108
1110
  async validatePendingReleasesLimit() {
1109
- const maximumPendingReleases = (
1110
- // @ts-expect-error - options is not typed into features
1111
- EE.features.get("cms-content-releases")?.options?.maximumReleases || 3
1112
- );
1111
+ const maximumPendingReleases = strapi2.ee.features.get("cms-content-releases")?.options?.maximumReleases || 3;
1113
1112
  const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
1114
1113
  filters: {
1115
1114
  releasedAt: {
@@ -1122,8 +1121,8 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1122
1121
  }
1123
1122
  },
1124
1123
  async validateUniqueNameForPendingRelease(name, id) {
1125
- const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
1126
- filters: {
1124
+ const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
1125
+ where: {
1127
1126
  releasedAt: {
1128
1127
  $null: true
1129
1128
  },
@@ -1197,7 +1196,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1197
1196
  const services = {
1198
1197
  release: createReleaseService,
1199
1198
  "release-validation": createReleaseValidationService,
1200
- ...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
1199
+ scheduling: createSchedulingService
1201
1200
  };
1202
1201
  const RELEASE_SCHEMA = yup.object().shape({
1203
1202
  name: yup.string().trim().required(),
@@ -1250,7 +1249,7 @@ const releaseController = {
1250
1249
  }
1251
1250
  };
1252
1251
  });
1253
- const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
1252
+ const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1254
1253
  where: {
1255
1254
  releasedAt: null
1256
1255
  }
@@ -1373,6 +1372,38 @@ const releaseActionController = {
1373
1372
  data: releaseAction2
1374
1373
  };
1375
1374
  },
1375
+ async createMany(ctx) {
1376
+ const releaseId = ctx.params.releaseId;
1377
+ const releaseActionsArgs = ctx.request.body;
1378
+ await Promise.all(
1379
+ releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1380
+ );
1381
+ const releaseService = getService("release", { strapi });
1382
+ const releaseActions = await strapi.db.transaction(async () => {
1383
+ const releaseActions2 = await Promise.all(
1384
+ releaseActionsArgs.map(async (releaseActionArgs) => {
1385
+ try {
1386
+ const action = await releaseService.createAction(releaseId, releaseActionArgs);
1387
+ return action;
1388
+ } catch (error) {
1389
+ if (error instanceof AlreadyOnReleaseError) {
1390
+ return null;
1391
+ }
1392
+ throw error;
1393
+ }
1394
+ })
1395
+ );
1396
+ return releaseActions2;
1397
+ });
1398
+ const newReleaseActions = releaseActions.filter((action) => action !== null);
1399
+ ctx.body = {
1400
+ data: newReleaseActions,
1401
+ meta: {
1402
+ entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
1403
+ totalEntries: releaseActions.length
1404
+ }
1405
+ };
1406
+ },
1376
1407
  async findMany(ctx) {
1377
1408
  const releaseId = ctx.params.releaseId;
1378
1409
  const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
@@ -1396,7 +1427,7 @@ const releaseActionController = {
1396
1427
  acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
1397
1428
  return acc;
1398
1429
  }, {});
1399
- const sanitizedResults = await mapAsync(results, async (action) => ({
1430
+ const sanitizedResults = await async.map(results, async (action) => ({
1400
1431
  ...action,
1401
1432
  entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1402
1433
  }));
@@ -1558,6 +1589,22 @@ const releaseAction = {
1558
1589
  ]
1559
1590
  }
1560
1591
  },
1592
+ {
1593
+ method: "POST",
1594
+ path: "/:releaseId/actions/bulk",
1595
+ handler: "release-action.createMany",
1596
+ config: {
1597
+ policies: [
1598
+ "admin::isAuthenticatedAdmin",
1599
+ {
1600
+ name: "admin::hasPermissions",
1601
+ config: {
1602
+ actions: ["plugin::content-releases.create-action"]
1603
+ }
1604
+ }
1605
+ ]
1606
+ }
1607
+ },
1561
1608
  {
1562
1609
  method: "GET",
1563
1610
  path: "/:releaseId/actions",
@@ -1612,9 +1659,8 @@ const routes = {
1612
1659
  release,
1613
1660
  "release-action": releaseAction
1614
1661
  };
1615
- const { features } = require("@strapi/strapi/dist/utils/ee");
1616
1662
  const getPlugin = () => {
1617
- if (features.isEnabled("cms-content-releases")) {
1663
+ if (strapi.ee.features.isEnabled("cms-content-releases")) {
1618
1664
  return {
1619
1665
  register,
1620
1666
  bootstrap,