@strapi/content-releases 4.20.4 → 5.0.0-alpha.0

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-6ugQxqYE.mjs → App-dbdAcsz_.mjs} +296 -296
  2. package/dist/_chunks/App-dbdAcsz_.mjs.map +1 -0
  3. package/dist/_chunks/{App-P1kyM3gT.js → App-zwe_jKPv.js} +301 -301
  4. package/dist/_chunks/App-zwe_jKPv.js.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-_eBuegHN.mjs → index-RBaVMtyr.mjs} +79 -70
  10. package/dist/_chunks/index-RBaVMtyr.mjs.map +1 -0
  11. package/dist/_chunks/{index-2xzbhaQP.js → index-TBrVNrv9.js} +77 -68
  12. package/dist/_chunks/index-TBrVNrv9.js.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 +327 -234
  36. package/dist/server/index.js.map +1 -1
  37. package/dist/server/index.mjs +328 -234
  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 +3 -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 +24 -30
  96. package/dist/_chunks/App-6ugQxqYE.mjs.map +0 -1
  97. package/dist/_chunks/App-P1kyM3gT.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-2xzbhaQP.js.map +0 -1
  101. package/dist/_chunks/index-_eBuegHN.mjs.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,8 +160,8 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
178
160
  release: true
179
161
  }
180
162
  });
181
- await mapAsync(actions, async (action) => {
182
- if (action.entry) {
163
+ await async.map(actions, async (action) => {
164
+ if (action.entry && action.release) {
183
165
  const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
184
166
  strapi
185
167
  });
@@ -201,26 +183,67 @@ 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
  });
208
190
  }
209
191
  }
210
- const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
192
+ async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
193
+ if (!oldContentTypes) {
194
+ return;
195
+ }
196
+ for (const uid in contentTypes2) {
197
+ if (!oldContentTypes[uid]) {
198
+ continue;
199
+ }
200
+ const oldContentType = oldContentTypes[uid];
201
+ const contentType = contentTypes2[uid];
202
+ const i18nPlugin = strapi.plugin("i18n");
203
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
204
+ if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
205
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
206
+ locale: null
207
+ }).where({ contentType: uid }).execute();
208
+ }
209
+ }
210
+ }
211
+ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
212
+ if (!oldContentTypes) {
213
+ return;
214
+ }
215
+ for (const uid in contentTypes2) {
216
+ if (!oldContentTypes[uid]) {
217
+ continue;
218
+ }
219
+ const oldContentType = oldContentTypes[uid];
220
+ const contentType = contentTypes2[uid];
221
+ const i18nPlugin = strapi.plugin("i18n");
222
+ const { isLocalizedContentType } = i18nPlugin.service("content-types");
223
+ const { getDefaultLocale } = i18nPlugin.service("locales");
224
+ if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
225
+ const defaultLocale = await getDefaultLocale();
226
+ await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
227
+ locale: defaultLocale
228
+ }).where({ contentType: uid }).execute();
229
+ }
230
+ }
231
+ }
211
232
  const register = async ({ strapi: strapi2 }) => {
212
- if (features$2.isEnabled("cms-content-releases")) {
233
+ if (strapi2.ee.features.isEnabled("cms-content-releases")) {
213
234
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
214
- strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
215
- strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
235
+ strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized);
236
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
237
+ }
238
+ if (strapi2.plugin("graphql")) {
239
+ const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
240
+ graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
241
+ graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
216
242
  }
217
243
  };
218
- const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
219
244
  const bootstrap = async ({ strapi: strapi2 }) => {
220
- if (features$1.isEnabled("cms-content-releases")) {
221
- const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
222
- (uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
223
- );
245
+ if (strapi2.ee.features.isEnabled("cms-content-releases")) {
246
+ const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes);
224
247
  strapi2.db.lifecycles.subscribe({
225
248
  models: contentTypesWithDraftAndPublish,
226
249
  async afterDelete(event) {
@@ -256,7 +279,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
256
279
  */
257
280
  async beforeDeleteMany(event) {
258
281
  const { model, params } = event;
259
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
282
+ if (model.kind === "collectionType") {
260
283
  const { where } = params;
261
284
  const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
262
285
  event.state.entriesToDelete = entriesToDelete;
@@ -338,27 +361,23 @@ const bootstrap = async ({ strapi: strapi2 }) => {
338
361
  }
339
362
  }
340
363
  });
341
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
342
- getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
343
- strapi2.log.error(
344
- "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
345
- );
346
- throw err;
347
- });
348
- Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
349
- strapi2.webhookStore.addAllowedEvent(key, value);
350
- });
351
- }
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
+ });
352
373
  }
353
374
  };
354
375
  const destroy = async ({ strapi: strapi2 }) => {
355
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
356
- const scheduledJobs = getService("scheduling", {
357
- strapi: strapi2
358
- }).getAll();
359
- for (const [, job] of scheduledJobs) {
360
- job.cancel();
361
- }
376
+ const scheduledJobs = getService("scheduling", {
377
+ strapi: strapi2
378
+ }).getAll();
379
+ for (const [, job] of scheduledJobs) {
380
+ job.cancel();
362
381
  }
363
382
  };
364
383
  const schema$1 = {
@@ -483,6 +502,94 @@ const createReleaseService = ({ strapi: strapi2 }) => {
483
502
  release: release2
484
503
  });
485
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
+ };
486
593
  return {
487
594
  async create(releaseData, { user }) {
488
595
  const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
@@ -496,13 +603,13 @@ const createReleaseService = ({ strapi: strapi2 }) => {
496
603
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
497
604
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
498
605
  ]);
499
- const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
606
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
500
607
  data: {
501
608
  ...releaseWithCreatorFields,
502
609
  status: "empty"
503
610
  }
504
611
  });
505
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
612
+ if (releaseWithCreatorFields.scheduledAt) {
506
613
  const schedulingService = getService("scheduling", { strapi: strapi2 });
507
614
  await schedulingService.set(release2.id, release2.scheduledAt);
508
615
  }
@@ -510,17 +617,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
510
617
  return release2;
511
618
  },
512
619
  async findOne(id, query = {}) {
513
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
514
- ...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 }
515
624
  });
516
625
  return release2;
517
626
  },
518
627
  findPage(query) {
519
- return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
520
- ...query,
628
+ const dbQuery = convertQueryParams.transformParamsToQuery(RELEASE_MODEL_UID, query ?? {});
629
+ return strapi2.db.query(RELEASE_MODEL_UID).findPage({
630
+ ...dbQuery,
521
631
  populate: {
522
632
  actions: {
523
- // @ts-expect-error Ignore missing properties
524
633
  count: true
525
634
  }
526
635
  }
@@ -612,28 +721,22 @@ const createReleaseService = ({ strapi: strapi2 }) => {
612
721
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
613
722
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
614
723
  ]);
615
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
724
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
616
725
  if (!release2) {
617
726
  throw new errors.NotFoundError(`No release found for id ${id}`);
618
727
  }
619
728
  if (release2.releasedAt) {
620
729
  throw new errors.ValidationError("Release already published");
621
730
  }
622
- const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
623
- /*
624
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
625
- * is not compatible with the type we are passing here: UpdateRelease.Request['body']
626
- */
627
- // @ts-expect-error see above
731
+ const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
732
+ where: { id },
628
733
  data: releaseWithCreatorFields
629
734
  });
630
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
631
- const schedulingService = getService("scheduling", { strapi: strapi2 });
632
- if (releaseData.scheduledAt) {
633
- await schedulingService.set(id, releaseData.scheduledAt);
634
- } else if (release2.scheduledAt) {
635
- schedulingService.cancel(id);
636
- }
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);
637
740
  }
638
741
  this.updateReleaseStatus(id);
639
742
  strapi2.telemetry.send("didUpdateContentRelease");
@@ -647,7 +750,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
647
750
  validateEntryContentType(action.entry.contentType),
648
751
  validateUniqueEntry(releaseId, action)
649
752
  ]);
650
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
753
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
651
754
  if (!release2) {
652
755
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
653
756
  }
@@ -657,7 +760,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
657
760
  const { entry, type } = action;
658
761
  const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
659
762
  const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
660
- const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
763
+ const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
661
764
  data: {
662
765
  type,
663
766
  contentType: entry.contentType,
@@ -670,32 +773,41 @@ const createReleaseService = ({ strapi: strapi2 }) => {
670
773
  },
671
774
  release: releaseId
672
775
  },
673
- populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
776
+ populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
674
777
  });
675
778
  this.updateReleaseStatus(releaseId);
676
779
  return releaseAction2;
677
780
  },
678
781
  async findActions(releaseId, query) {
679
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
680
- fields: ["id"]
782
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
783
+ where: { id: releaseId },
784
+ select: ["id"]
681
785
  });
682
786
  if (!release2) {
683
787
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
684
788
  }
685
- return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
686
- ...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,
687
795
  populate: {
688
796
  entry: {
689
797
  populate: "*"
690
798
  }
691
799
  },
692
- filters: {
800
+ where: {
693
801
  release: releaseId
694
802
  }
695
803
  });
696
804
  },
697
805
  async countActions(query) {
698
- 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);
699
811
  },
700
812
  async groupActions(actions, groupBy) {
701
813
  const contentTypeUids = actions.reduce((acc, action) => {
@@ -776,10 +888,11 @@ const createReleaseService = ({ strapi: strapi2 }) => {
776
888
  return componentsMap;
777
889
  },
778
890
  async delete(releaseId) {
779
- 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 },
780
893
  populate: {
781
894
  actions: {
782
- fields: ["id"]
895
+ select: ["id"]
783
896
  }
784
897
  }
785
898
  });
@@ -797,9 +910,13 @@ const createReleaseService = ({ strapi: strapi2 }) => {
797
910
  }
798
911
  }
799
912
  });
800
- await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
913
+ await strapi2.db.query(RELEASE_MODEL_UID).delete({
914
+ where: {
915
+ id: releaseId
916
+ }
917
+ });
801
918
  });
802
- if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
919
+ if (release2.scheduledAt) {
803
920
  const schedulingService = getService("scheduling", { strapi: strapi2 });
804
921
  await schedulingService.cancel(release2.id);
805
922
  }
@@ -807,145 +924,71 @@ const createReleaseService = ({ strapi: strapi2 }) => {
807
924
  return release2;
808
925
  },
809
926
  async publish(releaseId) {
810
- try {
811
- const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
812
- RELEASE_MODEL_UID,
813
- releaseId,
814
- {
815
- populate: {
816
- actions: {
817
- populate: {
818
- entry: {
819
- fields: ["id"]
820
- }
821
- }
822
- }
823
- }
824
- }
825
- );
826
- 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) {
827
933
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
828
934
  }
829
- if (releaseWithPopulatedActionEntries.releasedAt) {
935
+ if (lockedRelease.releasedAt) {
830
936
  throw new errors.ValidationError("Release already published");
831
937
  }
832
- if (releaseWithPopulatedActionEntries.actions.length === 0) {
833
- throw new errors.ValidationError("No entries to publish");
938
+ if (lockedRelease.status === "failed") {
939
+ throw new errors.ValidationError("Release failed to publish");
834
940
  }
835
- const collectionTypeActions = {};
836
- const singleTypeActions = [];
837
- for (const action of releaseWithPopulatedActionEntries.actions) {
838
- const contentTypeUid = action.contentType;
839
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
840
- if (!collectionTypeActions[contentTypeUid]) {
841
- collectionTypeActions[contentTypeUid] = {
842
- entriestoPublishIds: [],
843
- entriesToUnpublishIds: []
844
- };
845
- }
846
- if (action.type === "publish") {
847
- collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
848
- } else {
849
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
850
- }
851
- } else {
852
- singleTypeActions.push({
853
- uid: contentTypeUid,
854
- action: action.type,
855
- id: action.entry.id
856
- });
857
- }
858
- }
859
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
860
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
861
- await strapi2.db.transaction(async () => {
862
- for (const { uid, action, id } of singleTypeActions) {
863
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
864
- const entry = await strapi2.entityService.findOne(uid, id, { populate });
865
- try {
866
- if (action === "publish") {
867
- await entityManagerService.publish(entry, uid);
868
- } else {
869
- await entityManagerService.unpublish(entry, uid);
870
- }
871
- } catch (error) {
872
- if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
873
- } else {
874
- throw error;
875
- }
876
- }
877
- }
878
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
879
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
880
- const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
881
- const entriesToPublish = await strapi2.entityService.findMany(
882
- contentTypeUid,
883
- {
884
- filters: {
885
- id: {
886
- $in: entriestoPublishIds
887
- }
888
- },
889
- populate
890
- }
891
- );
892
- const entriesToUnpublish = await strapi2.entityService.findMany(
893
- contentTypeUid,
894
- {
895
- filters: {
896
- id: {
897
- $in: entriesToUnpublishIds
898
- }
899
- },
900
- populate
901
- }
902
- );
903
- if (entriesToPublish.length > 0) {
904
- 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);
905
949
  }
906
- if (entriesToUnpublish.length > 0) {
907
- 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
+ );
908
957
  }
909
- }
910
- });
911
- const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
912
- data: {
913
- /*
914
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
915
- */
916
- // @ts-expect-error see above
917
- releasedAt: /* @__PURE__ */ new Date()
918
- },
919
- populate: {
920
- actions: {
921
- // @ts-expect-error is not expecting count but it is working
922
- 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()
923
966
  }
924
- }
925
- });
926
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
967
+ });
927
968
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
928
969
  isPublished: true,
929
- release: release2
970
+ release: release22
930
971
  });
931
- }
932
- strapi2.telemetry.send("didPublishContentRelease");
933
- return release2;
934
- } catch (error) {
935
- if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
972
+ strapi2.telemetry.send("didPublishContentRelease");
973
+ return { release: release22, error: null };
974
+ } catch (error2) {
936
975
  dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
937
976
  isPublished: false,
938
- error
977
+ error: error2
939
978
  });
940
- }
941
- strapi2.db.query(RELEASE_MODEL_UID).update({
942
- where: { id: releaseId },
943
- data: {
979
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
944
980
  status: "failed"
945
- }
946
- });
981
+ }).transacting(trx).execute();
982
+ return {
983
+ release: null,
984
+ error: error2
985
+ };
986
+ }
987
+ });
988
+ if (error instanceof Error) {
947
989
  throw error;
948
990
  }
991
+ return release2;
949
992
  },
950
993
  async updateAction(actionId, releaseId, update) {
951
994
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
@@ -1032,10 +1075,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1032
1075
  }
1033
1076
  };
1034
1077
  };
1078
+ class AlreadyOnReleaseError extends errors.ApplicationError {
1079
+ constructor(message) {
1080
+ super(message);
1081
+ this.name = "AlreadyOnReleaseError";
1082
+ }
1083
+ }
1035
1084
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1036
1085
  async validateUniqueEntry(releaseId, releaseActionArgs) {
1037
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
1038
- 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"] } } } }
1039
1091
  });
1040
1092
  if (!release2) {
1041
1093
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
@@ -1044,7 +1096,7 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1044
1096
  (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1045
1097
  );
1046
1098
  if (isEntryInRelease) {
1047
- throw new errors.ValidationError(
1099
+ throw new AlreadyOnReleaseError(
1048
1100
  `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1049
1101
  );
1050
1102
  }
@@ -1054,17 +1106,9 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1054
1106
  if (!contentType) {
1055
1107
  throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1056
1108
  }
1057
- if (!contentType.options?.draftAndPublish) {
1058
- throw new errors.ValidationError(
1059
- `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1060
- );
1061
- }
1062
1109
  },
1063
1110
  async validatePendingReleasesLimit() {
1064
- const maximumPendingReleases = (
1065
- // @ts-expect-error - options is not typed into features
1066
- EE.features.get("cms-content-releases")?.options?.maximumReleases || 3
1067
- );
1111
+ const maximumPendingReleases = strapi2.ee.features.get("cms-content-releases")?.options?.maximumReleases || 3;
1068
1112
  const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
1069
1113
  filters: {
1070
1114
  releasedAt: {
@@ -1077,8 +1121,8 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1077
1121
  }
1078
1122
  },
1079
1123
  async validateUniqueNameForPendingRelease(name, id) {
1080
- const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
1081
- filters: {
1124
+ const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
1125
+ where: {
1082
1126
  releasedAt: {
1083
1127
  $null: true
1084
1128
  },
@@ -1152,7 +1196,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1152
1196
  const services = {
1153
1197
  release: createReleaseService,
1154
1198
  "release-validation": createReleaseValidationService,
1155
- ...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
1199
+ scheduling: createSchedulingService
1156
1200
  };
1157
1201
  const RELEASE_SCHEMA = yup.object().shape({
1158
1202
  name: yup.string().trim().required(),
@@ -1205,7 +1249,7 @@ const releaseController = {
1205
1249
  }
1206
1250
  };
1207
1251
  });
1208
- const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
1252
+ const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1209
1253
  where: {
1210
1254
  releasedAt: null
1211
1255
  }
@@ -1328,6 +1372,38 @@ const releaseActionController = {
1328
1372
  data: releaseAction2
1329
1373
  };
1330
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
+ },
1331
1407
  async findMany(ctx) {
1332
1408
  const releaseId = ctx.params.releaseId;
1333
1409
  const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
@@ -1351,7 +1427,7 @@ const releaseActionController = {
1351
1427
  acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
1352
1428
  return acc;
1353
1429
  }, {});
1354
- const sanitizedResults = await mapAsync(results, async (action) => ({
1430
+ const sanitizedResults = await async.map(results, async (action) => ({
1355
1431
  ...action,
1356
1432
  entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1357
1433
  }));
@@ -1513,6 +1589,22 @@ const releaseAction = {
1513
1589
  ]
1514
1590
  }
1515
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
+ },
1516
1608
  {
1517
1609
  method: "GET",
1518
1610
  path: "/:releaseId/actions",
@@ -1567,9 +1659,8 @@ const routes = {
1567
1659
  release,
1568
1660
  "release-action": releaseAction
1569
1661
  };
1570
- const { features } = require("@strapi/strapi/dist/utils/ee");
1571
1662
  const getPlugin = () => {
1572
- if (features.isEnabled("cms-content-releases")) {
1663
+ if (strapi.ee.features.isEnabled("cms-content-releases")) {
1573
1664
  return {
1574
1665
  register,
1575
1666
  bootstrap,
@@ -1581,6 +1672,9 @@ const getPlugin = () => {
1581
1672
  };
1582
1673
  }
1583
1674
  return {
1675
+ // Always return register, it handles its own feature check
1676
+ register,
1677
+ // Always return contentTypes to avoid losing data when the feature is disabled
1584
1678
  contentTypes
1585
1679
  };
1586
1680
  };