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

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 (142) hide show
  1. package/dist/_chunks/App-FQyYFBJT.mjs +1559 -0
  2. package/dist/_chunks/App-FQyYFBJT.mjs.map +1 -0
  3. package/dist/_chunks/App-lx4Ucy9W.js +1580 -0
  4. package/dist/_chunks/App-lx4Ucy9W.js.map +1 -0
  5. package/dist/_chunks/{PurchaseContentReleases-bpIYXOfu.js → PurchaseContentReleases-Be3acS2L.js} +7 -6
  6. package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js.map +1 -0
  7. package/dist/_chunks/{PurchaseContentReleases-3tRbmbY3.mjs → PurchaseContentReleases-_MxP6-Dt.mjs} +8 -7
  8. package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs.map +1 -0
  9. package/dist/_chunks/ReleasesSettingsPage-DqBxvJ9i.mjs +178 -0
  10. package/dist/_chunks/ReleasesSettingsPage-DqBxvJ9i.mjs.map +1 -0
  11. package/dist/_chunks/ReleasesSettingsPage-T5VEAV03.js +178 -0
  12. package/dist/_chunks/ReleasesSettingsPage-T5VEAV03.js.map +1 -0
  13. package/dist/_chunks/{en-HrREghh3.js → en-BWPPsSH-.js} +18 -2
  14. package/dist/_chunks/en-BWPPsSH-.js.map +1 -0
  15. package/dist/_chunks/{en-ltT1TlKQ.mjs → en-D9Q4YW03.mjs} +18 -2
  16. package/dist/_chunks/en-D9Q4YW03.mjs.map +1 -0
  17. package/dist/_chunks/index-CK9G80CL.mjs +1380 -0
  18. package/dist/_chunks/index-CK9G80CL.mjs.map +1 -0
  19. package/dist/_chunks/index-Cl3tM1YW.js +1399 -0
  20. package/dist/_chunks/index-Cl3tM1YW.js.map +1 -0
  21. package/dist/_chunks/schemas-BE1LxE9J.js +62 -0
  22. package/dist/_chunks/schemas-BE1LxE9J.js.map +1 -0
  23. package/dist/_chunks/schemas-DdA2ic2U.mjs +44 -0
  24. package/dist/_chunks/schemas-DdA2ic2U.mjs.map +1 -0
  25. package/dist/admin/index.js +1 -15
  26. package/dist/admin/index.js.map +1 -1
  27. package/dist/admin/index.mjs +2 -16
  28. package/dist/admin/index.mjs.map +1 -1
  29. package/dist/admin/src/components/EntryValidationPopover.d.ts +13 -0
  30. package/dist/admin/src/components/RelativeTime.d.ts +28 -0
  31. package/dist/admin/src/components/ReleaseAction.d.ts +3 -0
  32. package/dist/admin/src/components/ReleaseActionMenu.d.ts +26 -0
  33. package/dist/admin/src/components/ReleaseActionModal.d.ts +24 -0
  34. package/dist/admin/src/components/ReleaseActionOptions.d.ts +9 -0
  35. package/dist/admin/src/components/ReleaseListCell.d.ts +28 -0
  36. package/dist/admin/src/components/ReleaseModal.d.ts +17 -0
  37. package/dist/admin/src/components/ReleasesPanel.d.ts +3 -0
  38. package/dist/admin/src/constants.d.ts +76 -0
  39. package/dist/admin/src/index.d.ts +3 -0
  40. package/dist/admin/src/modules/hooks.d.ts +7 -0
  41. package/dist/admin/src/pages/App.d.ts +1 -0
  42. package/dist/admin/src/pages/PurchaseContentReleases.d.ts +2 -0
  43. package/dist/admin/src/pages/ReleaseDetailsPage.d.ts +2 -0
  44. package/dist/admin/src/pages/ReleasesPage.d.ts +8 -0
  45. package/dist/admin/src/pages/ReleasesSettingsPage.d.ts +1 -0
  46. package/dist/admin/src/pages/tests/mockReleaseDetailsPageData.d.ts +181 -0
  47. package/dist/admin/src/pages/tests/mockReleasesPageData.d.ts +39 -0
  48. package/dist/admin/src/pluginId.d.ts +1 -0
  49. package/dist/admin/src/services/release.d.ts +112 -0
  50. package/dist/admin/src/store/hooks.d.ts +7 -0
  51. package/dist/admin/src/utils/api.d.ts +6 -0
  52. package/dist/admin/src/utils/prefixPluginTranslations.d.ts +3 -0
  53. package/dist/admin/src/utils/time.d.ts +10 -0
  54. package/dist/admin/src/validation/schemas.d.ts +6 -0
  55. package/dist/server/index.js +926 -660
  56. package/dist/server/index.js.map +1 -1
  57. package/dist/server/index.mjs +927 -660
  58. package/dist/server/index.mjs.map +1 -1
  59. package/dist/server/src/bootstrap.d.ts +5 -0
  60. package/dist/server/src/bootstrap.d.ts.map +1 -0
  61. package/dist/server/src/constants.d.ts +21 -0
  62. package/dist/server/src/constants.d.ts.map +1 -0
  63. package/dist/server/src/content-types/index.d.ts +97 -0
  64. package/dist/server/src/content-types/index.d.ts.map +1 -0
  65. package/dist/server/src/content-types/release/index.d.ts +48 -0
  66. package/dist/server/src/content-types/release/index.d.ts.map +1 -0
  67. package/dist/server/src/content-types/release/schema.d.ts +47 -0
  68. package/dist/server/src/content-types/release/schema.d.ts.map +1 -0
  69. package/dist/server/src/content-types/release-action/index.d.ts +48 -0
  70. package/dist/server/src/content-types/release-action/index.d.ts.map +1 -0
  71. package/dist/server/src/content-types/release-action/schema.d.ts +47 -0
  72. package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -0
  73. package/dist/server/src/controllers/index.d.ts +25 -0
  74. package/dist/server/src/controllers/index.d.ts.map +1 -0
  75. package/dist/server/src/controllers/release-action.d.ts +10 -0
  76. package/dist/server/src/controllers/release-action.d.ts.map +1 -0
  77. package/dist/server/src/controllers/release.d.ts +18 -0
  78. package/dist/server/src/controllers/release.d.ts.map +1 -0
  79. package/dist/server/src/controllers/settings.d.ts +11 -0
  80. package/dist/server/src/controllers/settings.d.ts.map +1 -0
  81. package/dist/server/src/controllers/validation/release-action.d.ts +14 -0
  82. package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -0
  83. package/dist/server/src/controllers/validation/release.d.ts +4 -0
  84. package/dist/server/src/controllers/validation/release.d.ts.map +1 -0
  85. package/dist/server/src/controllers/validation/settings.d.ts +3 -0
  86. package/dist/server/src/controllers/validation/settings.d.ts.map +1 -0
  87. package/dist/server/src/destroy.d.ts +5 -0
  88. package/dist/server/src/destroy.d.ts.map +1 -0
  89. package/dist/server/src/index.d.ts +2111 -0
  90. package/dist/server/src/index.d.ts.map +1 -0
  91. package/dist/server/src/middlewares/documents.d.ts +6 -0
  92. package/dist/server/src/middlewares/documents.d.ts.map +1 -0
  93. package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts +9 -0
  94. package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts.map +1 -0
  95. package/dist/server/src/migrations/index.d.ts +13 -0
  96. package/dist/server/src/migrations/index.d.ts.map +1 -0
  97. package/dist/server/src/register.d.ts +5 -0
  98. package/dist/server/src/register.d.ts.map +1 -0
  99. package/dist/server/src/routes/index.d.ts +51 -0
  100. package/dist/server/src/routes/index.d.ts.map +1 -0
  101. package/dist/server/src/routes/release-action.d.ts +18 -0
  102. package/dist/server/src/routes/release-action.d.ts.map +1 -0
  103. package/dist/server/src/routes/release.d.ts +18 -0
  104. package/dist/server/src/routes/release.d.ts.map +1 -0
  105. package/dist/server/src/routes/settings.d.ts +18 -0
  106. package/dist/server/src/routes/settings.d.ts.map +1 -0
  107. package/dist/server/src/services/index.d.ts +1824 -0
  108. package/dist/server/src/services/index.d.ts.map +1 -0
  109. package/dist/server/src/services/release-action.d.ts +34 -0
  110. package/dist/server/src/services/release-action.d.ts.map +1 -0
  111. package/dist/server/src/services/release.d.ts +31 -0
  112. package/dist/server/src/services/release.d.ts.map +1 -0
  113. package/dist/server/src/services/scheduling.d.ts +18 -0
  114. package/dist/server/src/services/scheduling.d.ts.map +1 -0
  115. package/dist/server/src/services/settings.d.ts +13 -0
  116. package/dist/server/src/services/settings.d.ts.map +1 -0
  117. package/dist/server/src/services/validation.d.ts +18 -0
  118. package/dist/server/src/services/validation.d.ts.map +1 -0
  119. package/dist/server/src/utils/index.d.ts +35 -0
  120. package/dist/server/src/utils/index.d.ts.map +1 -0
  121. package/dist/shared/contracts/release-actions.d.ts +137 -0
  122. package/dist/shared/contracts/release-actions.d.ts.map +1 -0
  123. package/dist/shared/contracts/releases.d.ts +184 -0
  124. package/dist/shared/contracts/releases.d.ts.map +1 -0
  125. package/dist/shared/contracts/settings.d.ts +39 -0
  126. package/dist/shared/contracts/settings.d.ts.map +1 -0
  127. package/dist/shared/types.d.ts +24 -0
  128. package/dist/shared/types.d.ts.map +1 -0
  129. package/package.json +34 -39
  130. package/dist/_chunks/App-dLXY5ei3.js +0 -1353
  131. package/dist/_chunks/App-dLXY5ei3.js.map +0 -1
  132. package/dist/_chunks/App-jrh58sXY.mjs +0 -1330
  133. package/dist/_chunks/App-jrh58sXY.mjs.map +0 -1
  134. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +0 -1
  135. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +0 -1
  136. package/dist/_chunks/en-HrREghh3.js.map +0 -1
  137. package/dist/_chunks/en-ltT1TlKQ.mjs.map +0 -1
  138. package/dist/_chunks/index-CVO0Rqdm.js +0 -1336
  139. package/dist/_chunks/index-CVO0Rqdm.js.map +0 -1
  140. package/dist/_chunks/index-PiOGBETy.mjs +0 -1315
  141. package/dist/_chunks/index-PiOGBETy.mjs.map +0 -1
  142. package/strapi-server.js +0 -3
@@ -3,7 +3,6 @@ const utils = require("@strapi/utils");
3
3
  const isEqual = require("lodash/isEqual");
4
4
  const lodash = require("lodash");
5
5
  const _ = require("lodash/fp");
6
- const EE = require("@strapi/strapi/dist/utils/ee");
7
6
  const nodeSchedule = require("node-schedule");
8
7
  const yup = require("yup");
9
8
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
@@ -27,7 +26,6 @@ function _interopNamespace(e) {
27
26
  }
28
27
  const isEqual__default = /* @__PURE__ */ _interopDefault(isEqual);
29
28
  const ___default = /* @__PURE__ */ _interopDefault(_);
30
- const EE__default = /* @__PURE__ */ _interopDefault(EE);
31
29
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
32
30
  const RELEASE_MODEL_UID = "plugin::content-releases.release";
33
31
  const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
@@ -73,21 +71,38 @@ const ACTIONS = [
73
71
  displayName: "Add an entry to a release",
74
72
  uid: "create-action",
75
73
  pluginName: "content-releases"
74
+ },
75
+ // Settings
76
+ {
77
+ uid: "settings.read",
78
+ section: "settings",
79
+ displayName: "Read",
80
+ category: "content releases",
81
+ subCategory: "options",
82
+ pluginName: "content-releases"
83
+ },
84
+ {
85
+ uid: "settings.update",
86
+ section: "settings",
87
+ displayName: "Edit",
88
+ category: "content releases",
89
+ subCategory: "options",
90
+ pluginName: "content-releases"
76
91
  }
77
92
  ];
78
93
  const ALLOWED_WEBHOOK_EVENTS = {
79
94
  RELEASES_PUBLISH: "releases.publish"
80
95
  };
81
- const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
96
+ const getService = (name, { strapi: strapi2 }) => {
82
97
  return strapi2.plugin("content-releases").service(name);
83
98
  };
84
- const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
99
+ const getDraftEntryValidStatus = async ({ contentType, documentId, locale }, { strapi: strapi2 }) => {
85
100
  const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
86
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
87
- const entry = await strapi2.entityService.findOne(contentTypeUid, entryId, { populate });
88
- return entry;
101
+ const populate = await populateBuilderService(contentType).populateDeep(Infinity).build();
102
+ const entry = await getEntry({ contentType, documentId, locale, populate }, { strapi: strapi2 });
103
+ return isEntryValid(contentType, entry, { strapi: strapi2 });
89
104
  };
90
- const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
105
+ const isEntryValid = async (contentTypeUid, entry, { strapi: strapi2 }) => {
91
106
  try {
92
107
  await strapi2.entityValidator.validateEntityCreation(
93
108
  strapi2.getModel(contentTypeUid),
@@ -96,11 +111,54 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } =
96
111
  // @ts-expect-error - FIXME: entity here is unnecessary
97
112
  entry
98
113
  );
114
+ const workflowsService = strapi2.plugin("review-workflows").service("workflows");
115
+ const workflow = await workflowsService.getAssignedWorkflow(contentTypeUid, {
116
+ populate: "stageRequiredToPublish"
117
+ });
118
+ if (workflow?.stageRequiredToPublish) {
119
+ return entry.strapi_stage.id === workflow.stageRequiredToPublish.id;
120
+ }
99
121
  return true;
100
122
  } catch {
101
123
  return false;
102
124
  }
103
125
  };
126
+ const getEntry = async ({
127
+ contentType,
128
+ documentId,
129
+ locale,
130
+ populate,
131
+ status = "draft"
132
+ }, { strapi: strapi2 }) => {
133
+ if (documentId) {
134
+ const entry = await strapi2.documents(contentType).findOne({ documentId, locale, populate, status });
135
+ if (status === "published" && !entry) {
136
+ return strapi2.documents(contentType).findOne({ documentId, locale, populate, status: "draft" });
137
+ }
138
+ return entry;
139
+ }
140
+ return strapi2.documents(contentType).findFirst({ locale, populate, status });
141
+ };
142
+ const getEntryStatus = async (contentType, entry) => {
143
+ if (entry.publishedAt) {
144
+ return "published";
145
+ }
146
+ const publishedEntry = await strapi.documents(contentType).findOne({
147
+ documentId: entry.documentId,
148
+ locale: entry.locale,
149
+ status: "published",
150
+ fields: ["updatedAt"]
151
+ });
152
+ if (!publishedEntry) {
153
+ return "draft";
154
+ }
155
+ const entryUpdatedAt = new Date(entry.updatedAt).getTime();
156
+ const publishedEntryUpdatedAt = new Date(publishedEntry.updatedAt).getTime();
157
+ if (entryUpdatedAt > publishedEntryUpdatedAt) {
158
+ return "modified";
159
+ }
160
+ return "published";
161
+ };
104
162
  async function deleteActionsOnDisableDraftAndPublish({
105
163
  oldContentTypes,
106
164
  contentTypes: contentTypes2
@@ -122,7 +180,7 @@ async function deleteActionsOnDisableDraftAndPublish({
122
180
  async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
123
181
  const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
124
182
  if (deletedContentTypes.length) {
125
- await utils.mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
183
+ await utils.async.map(deletedContentTypes, async (deletedContentTypeUID) => {
126
184
  return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
127
185
  });
128
186
  }
@@ -141,25 +199,27 @@ async function migrateIsValidAndStatusReleases() {
141
199
  }
142
200
  }
143
201
  });
144
- utils.mapAsync(releasesWithoutStatus, async (release2) => {
202
+ utils.async.map(releasesWithoutStatus, async (release2) => {
145
203
  const actions = release2.actions;
146
204
  const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
147
205
  for (const action of notValidatedActions) {
148
206
  if (action.entry) {
149
- const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
150
- strapi
207
+ const isEntryValid2 = getDraftEntryValidStatus(
208
+ {
209
+ contentType: action.contentType,
210
+ documentId: action.entryDocumentId,
211
+ locale: action.locale
212
+ },
213
+ { strapi }
214
+ );
215
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
216
+ where: {
217
+ id: action.id
218
+ },
219
+ data: {
220
+ isEntryValid: isEntryValid2
221
+ }
151
222
  });
152
- if (populatedEntry) {
153
- const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
154
- await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
155
- where: {
156
- id: action.id
157
- },
158
- data: {
159
- isEntryValid
160
- }
161
- });
162
- }
163
223
  }
164
224
  }
165
225
  return getService("release", { strapi }).updateReleaseStatus(release2.id);
@@ -172,7 +232,7 @@ async function migrateIsValidAndStatusReleases() {
172
232
  }
173
233
  }
174
234
  });
175
- utils.mapAsync(publishedReleases, async (release2) => {
235
+ utils.async.map(publishedReleases, async (release2) => {
176
236
  return strapi.db.query(RELEASE_MODEL_UID).update({
177
237
  where: {
178
238
  id: release2.id
@@ -189,7 +249,7 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
189
249
  (uid) => oldContentTypes[uid]?.options?.draftAndPublish
190
250
  );
191
251
  const releasesAffected = /* @__PURE__ */ new Set();
192
- utils.mapAsync(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
252
+ utils.async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
193
253
  const oldContentType = oldContentTypes[contentTypeUID];
194
254
  const contentType = contentTypes2[contentTypeUID];
195
255
  if (!isEqual__default.default(oldContentType?.attributes, contentType?.attributes)) {
@@ -202,30 +262,30 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
202
262
  release: true
203
263
  }
204
264
  });
205
- await utils.mapAsync(actions, async (action) => {
206
- if (action.entry && action.release) {
207
- const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
208
- strapi
265
+ await utils.async.map(actions, async (action) => {
266
+ if (action.entry && action.release && action.type === "publish") {
267
+ const isEntryValid2 = await getDraftEntryValidStatus(
268
+ {
269
+ contentType: contentTypeUID,
270
+ documentId: action.entryDocumentId,
271
+ locale: action.locale
272
+ },
273
+ { strapi }
274
+ );
275
+ releasesAffected.add(action.release.id);
276
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
277
+ where: {
278
+ id: action.id
279
+ },
280
+ data: {
281
+ isEntryValid: isEntryValid2
282
+ }
209
283
  });
210
- if (populatedEntry) {
211
- const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
212
- strapi
213
- });
214
- releasesAffected.add(action.release.id);
215
- await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
216
- where: {
217
- id: action.id
218
- },
219
- data: {
220
- isEntryValid
221
- }
222
- });
223
- }
224
284
  }
225
285
  });
226
286
  }
227
287
  }).then(() => {
228
- utils.mapAsync(releasesAffected, async (releaseId) => {
288
+ utils.async.map(releasesAffected, async (releaseId) => {
229
289
  return getService("release", { strapi }).updateReleaseStatus(releaseId);
230
290
  });
231
291
  });
@@ -277,11 +337,43 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
277
337
  }
278
338
  }
279
339
  }
280
- const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
340
+ const addEntryDocumentToReleaseActions = {
341
+ name: "content-releases::5.0.0-add-entry-document-id-to-release-actions",
342
+ async up(trx, db) {
343
+ const hasTable = await trx.schema.hasTable("strapi_release_actions");
344
+ if (!hasTable) {
345
+ return;
346
+ }
347
+ const hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
348
+ if (hasPolymorphicColumn) {
349
+ const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
350
+ "strapi_release_actions",
351
+ "entry_document_id"
352
+ );
353
+ if (!hasEntryDocumentIdColumn) {
354
+ await trx.schema.alterTable("strapi_release_actions", (table) => {
355
+ table.string("entry_document_id");
356
+ });
357
+ }
358
+ const releaseActions = await trx.select("*").from("strapi_release_actions");
359
+ utils.async.map(releaseActions, async (action) => {
360
+ const { target_type, target_id } = action;
361
+ const entry = await db.query(target_type).findOne({ where: { id: target_id } });
362
+ if (entry) {
363
+ await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
364
+ }
365
+ });
366
+ }
367
+ },
368
+ async down() {
369
+ throw new Error("not implemented");
370
+ }
371
+ };
281
372
  const register = async ({ strapi: strapi2 }) => {
282
- if (features$2.isEnabled("cms-content-releases")) {
283
- await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
284
- strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish).register(disableContentTypeLocalized);
373
+ if (strapi2.ee.features.isEnabled("cms-content-releases")) {
374
+ await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
375
+ strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
376
+ strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
285
377
  strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
286
378
  }
287
379
  if (strapi2.plugin("graphql")) {
@@ -290,129 +382,135 @@ const register = async ({ strapi: strapi2 }) => {
290
382
  graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
291
383
  }
292
384
  };
293
- const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
385
+ const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
386
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
387
+ where: {
388
+ releasedAt: null,
389
+ actions: {
390
+ contentType,
391
+ entryDocumentId: entry.documentId,
392
+ locale: entry.locale
393
+ }
394
+ }
395
+ });
396
+ const entryStatus = await isEntryValid(contentType, entry, { strapi });
397
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).updateMany({
398
+ where: {
399
+ contentType,
400
+ entryDocumentId: entry.documentId,
401
+ locale: entry.locale
402
+ },
403
+ data: {
404
+ isEntryValid: entryStatus
405
+ }
406
+ });
407
+ for (const release2 of releases) {
408
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
409
+ }
410
+ };
411
+ const deleteActionsAndUpdateReleaseStatus = async (params) => {
412
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
413
+ where: {
414
+ actions: params
415
+ }
416
+ });
417
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
418
+ where: params
419
+ });
420
+ for (const release2 of releases) {
421
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
422
+ }
423
+ };
424
+ const deleteActionsOnDelete = async (ctx, next) => {
425
+ if (ctx.action !== "delete") {
426
+ return next();
427
+ }
428
+ if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
429
+ return next();
430
+ }
431
+ const contentType = ctx.contentType.uid;
432
+ const { documentId, locale } = ctx.params;
433
+ const result = await next();
434
+ if (!result) {
435
+ return result;
436
+ }
437
+ try {
438
+ deleteActionsAndUpdateReleaseStatus({
439
+ contentType,
440
+ entryDocumentId: documentId,
441
+ ...locale !== "*" && { locale }
442
+ });
443
+ } catch (error) {
444
+ strapi.log.error("Error while deleting release actions after delete", {
445
+ error
446
+ });
447
+ }
448
+ return result;
449
+ };
450
+ const updateActionsOnUpdate = async (ctx, next) => {
451
+ if (ctx.action !== "update") {
452
+ return next();
453
+ }
454
+ if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
455
+ return next();
456
+ }
457
+ const contentType = ctx.contentType.uid;
458
+ const result = await next();
459
+ if (!result) {
460
+ return result;
461
+ }
462
+ try {
463
+ updateActionsStatusAndUpdateReleaseStatus(contentType, result);
464
+ } catch (error) {
465
+ strapi.log.error("Error while updating release actions after update", {
466
+ error
467
+ });
468
+ }
469
+ return result;
470
+ };
471
+ const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
472
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
473
+ where: {
474
+ actions: params
475
+ }
476
+ });
477
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
478
+ where: params
479
+ });
480
+ for (const release2 of releases) {
481
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
482
+ }
483
+ };
294
484
  const bootstrap = async ({ strapi: strapi2 }) => {
295
- if (features$1.isEnabled("cms-content-releases")) {
485
+ if (strapi2.ee.features.isEnabled("cms-content-releases")) {
296
486
  const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
297
487
  (uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
298
488
  );
299
489
  strapi2.db.lifecycles.subscribe({
300
490
  models: contentTypesWithDraftAndPublish,
301
- async afterDelete(event) {
302
- try {
303
- const { model, result } = event;
304
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
305
- const { id } = result;
306
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
307
- where: {
308
- actions: {
309
- target_type: model.uid,
310
- target_id: id
311
- }
312
- }
313
- });
314
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
315
- where: {
316
- target_type: model.uid,
317
- target_id: id
318
- }
319
- });
320
- for (const release2 of releases) {
321
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
322
- }
323
- }
324
- } catch (error) {
325
- strapi2.log.error("Error while deleting release actions after entry delete", { error });
326
- }
327
- },
328
491
  /**
329
- * deleteMany hook doesn't return the deleted entries ids
330
- * so we need to fetch them before deleting the entries to save the ids on our state
331
- */
332
- async beforeDeleteMany(event) {
333
- const { model, params } = event;
334
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
335
- const { where } = params;
336
- const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
337
- event.state.entriesToDelete = entriesToDelete;
338
- }
339
- },
340
- /**
341
- * We delete the release actions related to deleted entries
342
- * We make this only after deleteMany is succesfully executed to avoid errors
492
+ * deleteMany is still used outside documents service, for example when deleting a locale
343
493
  */
344
494
  async afterDeleteMany(event) {
345
495
  try {
346
- const { model, state } = event;
347
- const entriesToDelete = state.entriesToDelete;
348
- if (entriesToDelete) {
349
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
350
- where: {
351
- actions: {
352
- target_type: model.uid,
353
- target_id: {
354
- $in: entriesToDelete.map(
355
- (entry) => entry.id
356
- )
357
- }
358
- }
359
- }
360
- });
361
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
362
- where: {
363
- target_type: model.uid,
364
- target_id: {
365
- $in: entriesToDelete.map((entry) => entry.id)
366
- }
367
- }
496
+ const model = strapi2.getModel(event.model.uid);
497
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
498
+ const { where } = event.params;
499
+ deleteReleasesActionsAndUpdateReleaseStatus({
500
+ contentType: model.uid,
501
+ locale: where?.locale ?? null,
502
+ ...where?.documentId && { entryDocumentId: where.documentId }
368
503
  });
369
- for (const release2 of releases) {
370
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
371
- }
372
504
  }
373
505
  } catch (error) {
374
506
  strapi2.log.error("Error while deleting release actions after entry deleteMany", {
375
507
  error
376
508
  });
377
509
  }
378
- },
379
- async afterUpdate(event) {
380
- try {
381
- const { model, result } = event;
382
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
383
- const isEntryValid = await getEntryValidStatus(
384
- model.uid,
385
- result,
386
- {
387
- strapi: strapi2
388
- }
389
- );
390
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
391
- where: {
392
- target_type: model.uid,
393
- target_id: result.id
394
- },
395
- data: {
396
- isEntryValid
397
- }
398
- });
399
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
400
- where: {
401
- actions: {
402
- target_type: model.uid,
403
- target_id: result.id
404
- }
405
- }
406
- });
407
- for (const release2 of releases) {
408
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
409
- }
410
- }
411
- } catch (error) {
412
- strapi2.log.error("Error while updating release actions after entry update", { error });
413
- }
414
510
  }
415
511
  });
512
+ strapi2.documents.use(deleteActionsOnDelete);
513
+ strapi2.documents.use(updateActionsOnUpdate);
416
514
  getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
417
515
  strapi2.log.error(
418
516
  "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
@@ -420,7 +518,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
420
518
  throw err;
421
519
  });
422
520
  Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
423
- strapi2.webhookStore.addAllowedEvent(key, value);
521
+ strapi2.get("webhookStore").addAllowedEvent(key, value);
424
522
  });
425
523
  }
426
524
  };
@@ -504,15 +602,13 @@ const schema = {
504
602
  enum: ["publish", "unpublish"],
505
603
  required: true
506
604
  },
507
- entry: {
508
- type: "relation",
509
- relation: "morphToOne",
510
- configurable: false
511
- },
512
605
  contentType: {
513
606
  type: "string",
514
607
  required: true
515
608
  },
609
+ entryDocumentId: {
610
+ type: "string"
611
+ },
516
612
  locale: {
517
613
  type: "string"
518
614
  },
@@ -534,18 +630,6 @@ const contentTypes = {
534
630
  release: release$1,
535
631
  "release-action": releaseAction$1
536
632
  };
537
- const getGroupName = (queryValue) => {
538
- switch (queryValue) {
539
- case "contentType":
540
- return "contentType.displayName";
541
- case "action":
542
- return "type";
543
- case "locale":
544
- return ___default.default.getOr("No locale", "locale.name");
545
- default:
546
- return "contentType.displayName";
547
- }
548
- };
549
633
  const createReleaseService = ({ strapi: strapi2 }) => {
550
634
  const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
551
635
  strapi2.eventHub.emit(event, {
@@ -554,93 +638,32 @@ const createReleaseService = ({ strapi: strapi2 }) => {
554
638
  release: release2
555
639
  });
556
640
  };
557
- const publishSingleTypeAction = async (uid, actionType, entryId) => {
558
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
559
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
560
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
561
- const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
562
- try {
563
- if (actionType === "publish") {
564
- await entityManagerService.publish(entry, uid);
565
- } else {
566
- await entityManagerService.unpublish(entry, uid);
567
- }
568
- } catch (error) {
569
- if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
570
- ;
571
- else {
572
- throw error;
573
- }
574
- }
575
- };
576
- const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
577
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
578
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
579
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
580
- const entriesToPublish = await strapi2.entityService.findMany(uid, {
581
- filters: {
582
- id: {
583
- $in: entriesToPublishIds
584
- }
585
- },
586
- populate
587
- });
588
- const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
589
- filters: {
590
- id: {
591
- $in: entriestoUnpublishIds
592
- }
593
- },
594
- populate
595
- });
596
- if (entriesToPublish.length > 0) {
597
- await entityManagerService.publishMany(entriesToPublish, uid);
598
- }
599
- if (entriesToUnpublish.length > 0) {
600
- await entityManagerService.unpublishMany(entriesToUnpublish, uid);
601
- }
602
- };
603
641
  const getFormattedActions = async (releaseId) => {
604
642
  const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
605
643
  where: {
606
644
  release: {
607
645
  id: releaseId
608
646
  }
609
- },
610
- populate: {
611
- entry: {
612
- fields: ["id"]
613
- }
614
647
  }
615
648
  });
616
649
  if (actions.length === 0) {
617
650
  throw new utils.errors.ValidationError("No entries to publish");
618
651
  }
619
- const collectionTypeActions = {};
620
- const singleTypeActions = [];
652
+ const formattedActions = {};
621
653
  for (const action of actions) {
622
654
  const contentTypeUid = action.contentType;
623
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
624
- if (!collectionTypeActions[contentTypeUid]) {
625
- collectionTypeActions[contentTypeUid] = {
626
- entriesToPublishIds: [],
627
- entriesToUnpublishIds: []
628
- };
629
- }
630
- if (action.type === "publish") {
631
- collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
632
- } else {
633
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
634
- }
635
- } else {
636
- singleTypeActions.push({
637
- uid: contentTypeUid,
638
- action: action.type,
639
- id: action.entry.id
640
- });
655
+ if (!formattedActions[contentTypeUid]) {
656
+ formattedActions[contentTypeUid] = {
657
+ publish: [],
658
+ unpublish: []
659
+ };
641
660
  }
661
+ formattedActions[contentTypeUid][action.type].push({
662
+ documentId: action.entryDocumentId,
663
+ locale: action.locale
664
+ });
642
665
  }
643
- return { collectionTypeActions, singleTypeActions };
666
+ return formattedActions;
644
667
  };
645
668
  return {
646
669
  async create(releaseData, { user }) {
@@ -655,7 +678,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
655
678
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
656
679
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
657
680
  ]);
658
- const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
681
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
659
682
  data: {
660
683
  ...releaseWithCreatorFields,
661
684
  status: "empty"
@@ -669,107 +692,28 @@ const createReleaseService = ({ strapi: strapi2 }) => {
669
692
  return release2;
670
693
  },
671
694
  async findOne(id, query = {}) {
672
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
673
- ...query
695
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query);
696
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
697
+ ...dbQuery,
698
+ where: { id }
674
699
  });
675
700
  return release2;
676
701
  },
677
702
  findPage(query) {
678
- return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
679
- ...query,
703
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
704
+ return strapi2.db.query(RELEASE_MODEL_UID).findPage({
705
+ ...dbQuery,
680
706
  populate: {
681
707
  actions: {
682
- // @ts-expect-error Ignore missing properties
683
708
  count: true
684
709
  }
685
710
  }
686
711
  });
687
712
  },
688
- async findManyWithContentTypeEntryAttached(contentTypeUid, entriesIds) {
689
- let entries = entriesIds;
690
- if (!Array.isArray(entriesIds)) {
691
- entries = [entriesIds];
692
- }
693
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
694
- where: {
695
- actions: {
696
- target_type: contentTypeUid,
697
- target_id: {
698
- $in: entries
699
- }
700
- },
701
- releasedAt: {
702
- $null: true
703
- }
704
- },
705
- populate: {
706
- // Filter the action to get only the content type entry
707
- actions: {
708
- where: {
709
- target_type: contentTypeUid,
710
- target_id: {
711
- $in: entries
712
- }
713
- },
714
- populate: {
715
- entry: {
716
- select: ["id"]
717
- }
718
- }
719
- }
720
- }
721
- });
722
- return releases.map((release2) => {
723
- if (release2.actions?.length) {
724
- const actionsForEntry = release2.actions;
725
- delete release2.actions;
726
- return {
727
- ...release2,
728
- actions: actionsForEntry
729
- };
730
- }
731
- return release2;
732
- });
733
- },
734
- async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
735
- const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
736
- where: {
737
- releasedAt: {
738
- $null: true
739
- },
740
- actions: {
741
- target_type: contentTypeUid,
742
- target_id: entryId
743
- }
744
- }
745
- });
746
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
747
- where: {
748
- $or: [
749
- {
750
- id: {
751
- $notIn: releasesRelated.map((release2) => release2.id)
752
- }
753
- },
754
- {
755
- actions: null
756
- }
757
- ],
758
- releasedAt: {
759
- $null: true
760
- }
761
- }
762
- });
763
- return releases.map((release2) => {
764
- if (release2.actions?.length) {
765
- const [actionForEntry] = release2.actions;
766
- delete release2.actions;
767
- return {
768
- ...release2,
769
- action: actionForEntry
770
- };
771
- }
772
- return release2;
713
+ findMany(query) {
714
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
715
+ return strapi2.db.query(RELEASE_MODEL_UID).findMany({
716
+ ...dbQuery
773
717
  });
774
718
  },
775
719
  async update(id, releaseData, { user }) {
@@ -784,19 +728,15 @@ const createReleaseService = ({ strapi: strapi2 }) => {
784
728
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
785
729
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
786
730
  ]);
787
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
731
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
788
732
  if (!release2) {
789
733
  throw new utils.errors.NotFoundError(`No release found for id ${id}`);
790
734
  }
791
735
  if (release2.releasedAt) {
792
736
  throw new utils.errors.ValidationError("Release already published");
793
737
  }
794
- const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
795
- /*
796
- * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
797
- * is not compatible with the type we are passing here: UpdateRelease.Request['body']
798
- */
799
- // @ts-expect-error see above
738
+ const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
739
+ where: { id },
800
740
  data: releaseWithCreatorFields
801
741
  });
802
742
  const schedulingService = getService("scheduling", { strapi: strapi2 });
@@ -809,132 +749,6 @@ const createReleaseService = ({ strapi: strapi2 }) => {
809
749
  strapi2.telemetry.send("didUpdateContentRelease");
810
750
  return updatedRelease;
811
751
  },
812
- async createAction(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
813
- const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
814
- strapi: strapi2
815
- });
816
- await Promise.all([
817
- validateEntryContentType(action.entry.contentType),
818
- validateUniqueEntry(releaseId, action)
819
- ]);
820
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
821
- if (!release2) {
822
- throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
823
- }
824
- if (release2.releasedAt) {
825
- throw new utils.errors.ValidationError("Release already published");
826
- }
827
- const { entry, type } = action;
828
- const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
829
- const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
830
- const releaseAction2 = await strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
831
- data: {
832
- type,
833
- contentType: entry.contentType,
834
- locale: entry.locale,
835
- isEntryValid,
836
- entry: {
837
- id: entry.id,
838
- __type: entry.contentType,
839
- __pivot: { field: "entry" }
840
- },
841
- release: releaseId
842
- },
843
- populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
844
- });
845
- if (!disableUpdateReleaseStatus) {
846
- this.updateReleaseStatus(releaseId);
847
- }
848
- return releaseAction2;
849
- },
850
- async findActions(releaseId, query) {
851
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
852
- fields: ["id"]
853
- });
854
- if (!release2) {
855
- throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
856
- }
857
- return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
858
- ...query,
859
- populate: {
860
- entry: {
861
- populate: "*"
862
- }
863
- },
864
- filters: {
865
- release: releaseId
866
- }
867
- });
868
- },
869
- async countActions(query) {
870
- return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
871
- },
872
- async groupActions(actions, groupBy) {
873
- const contentTypeUids = actions.reduce((acc, action) => {
874
- if (!acc.includes(action.contentType)) {
875
- acc.push(action.contentType);
876
- }
877
- return acc;
878
- }, []);
879
- const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
880
- contentTypeUids
881
- );
882
- const allLocalesDictionary = await this.getLocalesDataForActions();
883
- const formattedData = actions.map((action) => {
884
- const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
885
- return {
886
- ...action,
887
- locale: action.locale ? allLocalesDictionary[action.locale] : null,
888
- contentType: {
889
- displayName,
890
- mainFieldValue: action.entry[mainField],
891
- uid: action.contentType
892
- }
893
- };
894
- });
895
- const groupName = getGroupName(groupBy);
896
- return ___default.default.groupBy(groupName)(formattedData);
897
- },
898
- async getLocalesDataForActions() {
899
- if (!strapi2.plugin("i18n")) {
900
- return {};
901
- }
902
- const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
903
- return allLocales.reduce((acc, locale) => {
904
- acc[locale.code] = { name: locale.name, code: locale.code };
905
- return acc;
906
- }, {});
907
- },
908
- async getContentTypesDataForActions(contentTypesUids) {
909
- const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
910
- const contentTypesData = {};
911
- for (const contentTypeUid of contentTypesUids) {
912
- const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
913
- uid: contentTypeUid
914
- });
915
- contentTypesData[contentTypeUid] = {
916
- mainField: contentTypeConfig.settings.mainField,
917
- displayName: strapi2.getModel(contentTypeUid).info.displayName
918
- };
919
- }
920
- return contentTypesData;
921
- },
922
- getContentTypeModelsFromActions(actions) {
923
- const contentTypeUids = actions.reduce((acc, action) => {
924
- if (!acc.includes(action.contentType)) {
925
- acc.push(action.contentType);
926
- }
927
- return acc;
928
- }, []);
929
- const contentTypeModelsMap = contentTypeUids.reduce(
930
- (acc, contentTypeUid) => {
931
- acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
932
- return acc;
933
- },
934
- {}
935
- );
936
- return contentTypeModelsMap;
937
- },
938
752
  async getAllComponents() {
939
753
  const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
940
754
  const components = await contentManagerComponentsService.findAllComponents();
@@ -948,10 +762,11 @@ const createReleaseService = ({ strapi: strapi2 }) => {
948
762
  return componentsMap;
949
763
  },
950
764
  async delete(releaseId) {
951
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
765
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
766
+ where: { id: releaseId },
952
767
  populate: {
953
768
  actions: {
954
- fields: ["id"]
769
+ select: ["id"]
955
770
  }
956
771
  }
957
772
  });
@@ -969,7 +784,11 @@ const createReleaseService = ({ strapi: strapi2 }) => {
969
784
  }
970
785
  }
971
786
  });
972
- await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
787
+ await strapi2.db.query(RELEASE_MODEL_UID).delete({
788
+ where: {
789
+ id: releaseId
790
+ }
791
+ });
973
792
  });
974
793
  if (release2.scheduledAt) {
975
794
  const schedulingService = getService("scheduling", { strapi: strapi2 });
@@ -993,60 +812,280 @@ const createReleaseService = ({ strapi: strapi2 }) => {
993
812
  if (lockedRelease.status === "failed") {
994
813
  throw new utils.errors.ValidationError("Release failed to publish");
995
814
  }
996
- try {
997
- strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
998
- const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
999
- releaseId
1000
- );
1001
- await strapi2.db.transaction(async () => {
1002
- for (const { uid, action, id } of singleTypeActions) {
1003
- await publishSingleTypeAction(uid, action, id);
1004
- }
1005
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
1006
- const uid = contentTypeUid;
1007
- await publishCollectionTypeAction(
1008
- uid,
1009
- collectionTypeActions[uid].entriesToPublishIds,
1010
- collectionTypeActions[uid].entriesToUnpublishIds
1011
- );
1012
- }
1013
- });
1014
- const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
1015
- where: {
1016
- id: releaseId
1017
- },
1018
- data: {
1019
- status: "done",
1020
- releasedAt: /* @__PURE__ */ new Date()
1021
- }
1022
- });
1023
- dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
1024
- isPublished: true,
1025
- release: release22
1026
- });
1027
- strapi2.telemetry.send("didPublishContentRelease");
1028
- return { release: release22, error: null };
1029
- } catch (error2) {
1030
- dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
1031
- isPublished: false,
1032
- error: error2
815
+ try {
816
+ strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
817
+ const formattedActions = await getFormattedActions(releaseId);
818
+ await strapi2.db.transaction(
819
+ async () => Promise.all(
820
+ Object.keys(formattedActions).map(async (contentTypeUid) => {
821
+ const contentType = contentTypeUid;
822
+ const { publish, unpublish } = formattedActions[contentType];
823
+ return Promise.all([
824
+ ...publish.map((params) => strapi2.documents(contentType).publish(params)),
825
+ ...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
826
+ ]);
827
+ })
828
+ )
829
+ );
830
+ const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
831
+ where: {
832
+ id: releaseId
833
+ },
834
+ data: {
835
+ status: "done",
836
+ releasedAt: /* @__PURE__ */ new Date()
837
+ }
838
+ });
839
+ dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
840
+ isPublished: true,
841
+ release: release22
842
+ });
843
+ strapi2.telemetry.send("didPublishContentRelease");
844
+ return { release: release22, error: null };
845
+ } catch (error2) {
846
+ dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
847
+ isPublished: false,
848
+ error: error2
849
+ });
850
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
851
+ status: "failed"
852
+ }).transacting(trx).execute();
853
+ return {
854
+ release: null,
855
+ error: error2
856
+ };
857
+ }
858
+ });
859
+ if (error instanceof Error) {
860
+ throw error;
861
+ }
862
+ return release2;
863
+ },
864
+ async updateReleaseStatus(releaseId) {
865
+ const releaseActionService = getService("release-action", { strapi: strapi2 });
866
+ const [totalActions, invalidActions] = await Promise.all([
867
+ releaseActionService.countActions({
868
+ filters: {
869
+ release: releaseId
870
+ }
871
+ }),
872
+ releaseActionService.countActions({
873
+ filters: {
874
+ release: releaseId,
875
+ isEntryValid: false
876
+ }
877
+ })
878
+ ]);
879
+ if (totalActions > 0) {
880
+ if (invalidActions > 0) {
881
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
882
+ where: {
883
+ id: releaseId
884
+ },
885
+ data: {
886
+ status: "blocked"
887
+ }
888
+ });
889
+ }
890
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
891
+ where: {
892
+ id: releaseId
893
+ },
894
+ data: {
895
+ status: "ready"
896
+ }
897
+ });
898
+ }
899
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
900
+ where: {
901
+ id: releaseId
902
+ },
903
+ data: {
904
+ status: "empty"
905
+ }
906
+ });
907
+ }
908
+ };
909
+ };
910
+ const getGroupName = (queryValue) => {
911
+ switch (queryValue) {
912
+ case "contentType":
913
+ return "contentType.displayName";
914
+ case "type":
915
+ return "type";
916
+ case "locale":
917
+ return ___default.default.getOr("No locale", "locale.name");
918
+ default:
919
+ return "contentType.displayName";
920
+ }
921
+ };
922
+ const createReleaseActionService = ({ strapi: strapi2 }) => {
923
+ const getLocalesDataForActions = async () => {
924
+ if (!strapi2.plugin("i18n")) {
925
+ return {};
926
+ }
927
+ const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
928
+ return allLocales.reduce((acc, locale) => {
929
+ acc[locale.code] = { name: locale.name, code: locale.code };
930
+ return acc;
931
+ }, {});
932
+ };
933
+ const getContentTypesDataForActions = async (contentTypesUids) => {
934
+ const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
935
+ const contentTypesData = {};
936
+ for (const contentTypeUid of contentTypesUids) {
937
+ const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
938
+ uid: contentTypeUid
939
+ });
940
+ contentTypesData[contentTypeUid] = {
941
+ mainField: contentTypeConfig.settings.mainField,
942
+ displayName: strapi2.getModel(contentTypeUid).info.displayName
943
+ };
944
+ }
945
+ return contentTypesData;
946
+ };
947
+ return {
948
+ async create(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
949
+ const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
950
+ strapi: strapi2
951
+ });
952
+ await Promise.all([
953
+ validateEntryData(action.contentType, action.entryDocumentId),
954
+ validateUniqueEntry(releaseId, action)
955
+ ]);
956
+ const model = strapi2.contentType(action.contentType);
957
+ if (model.kind === "singleType") {
958
+ const document = await strapi2.db.query(model.uid).findOne({ select: ["documentId"] });
959
+ if (!document) {
960
+ throw new utils.errors.NotFoundError(`No entry found for contentType ${action.contentType}`);
961
+ }
962
+ action.entryDocumentId = document.documentId;
963
+ }
964
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
965
+ if (!release2) {
966
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
967
+ }
968
+ if (release2.releasedAt) {
969
+ throw new utils.errors.ValidationError("Release already published");
970
+ }
971
+ const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
972
+ {
973
+ contentType: action.contentType,
974
+ documentId: action.entryDocumentId,
975
+ locale: action.locale
976
+ },
977
+ {
978
+ strapi: strapi2
979
+ }
980
+ ) : true;
981
+ const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
982
+ data: {
983
+ ...action,
984
+ release: release2.id,
985
+ isEntryValid: actionStatus
986
+ },
987
+ populate: { release: { select: ["id"] } }
988
+ });
989
+ if (!disableUpdateReleaseStatus) {
990
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
991
+ }
992
+ return releaseAction2;
993
+ },
994
+ async findPage(releaseId, query) {
995
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
996
+ where: { id: releaseId },
997
+ select: ["id"]
998
+ });
999
+ if (!release2) {
1000
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
1001
+ }
1002
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
1003
+ const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
1004
+ ...dbQuery,
1005
+ where: {
1006
+ release: releaseId
1007
+ }
1008
+ });
1009
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
1010
+ const actionsWithEntry = await utils.async.map(actions, async (action) => {
1011
+ const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
1012
+ const entry = await getEntry(
1013
+ {
1014
+ contentType: action.contentType,
1015
+ documentId: action.entryDocumentId,
1016
+ locale: action.locale,
1017
+ populate,
1018
+ status: action.type === "publish" ? "draft" : "published"
1019
+ },
1020
+ { strapi: strapi2 }
1021
+ );
1022
+ return {
1023
+ ...action,
1024
+ entry,
1025
+ status: entry ? await getEntryStatus(action.contentType, entry) : null
1026
+ };
1027
+ });
1028
+ return {
1029
+ results: actionsWithEntry,
1030
+ pagination
1031
+ };
1032
+ },
1033
+ async groupActions(actions, groupBy) {
1034
+ const contentTypeUids = actions.reduce((acc, action) => {
1035
+ if (!acc.includes(action.contentType)) {
1036
+ acc.push(action.contentType);
1037
+ }
1038
+ return acc;
1039
+ }, []);
1040
+ const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
1041
+ const allLocalesDictionary = await getLocalesDataForActions();
1042
+ const formattedData = actions.map((action) => {
1043
+ const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
1044
+ return {
1045
+ ...action,
1046
+ locale: action.locale ? allLocalesDictionary[action.locale] : null,
1047
+ contentType: {
1048
+ displayName,
1049
+ mainFieldValue: action.entry[mainField],
1050
+ uid: action.contentType
1051
+ }
1052
+ };
1053
+ });
1054
+ const groupName = getGroupName(groupBy);
1055
+ return ___default.default.groupBy(groupName)(formattedData);
1056
+ },
1057
+ async getContentTypeModelsFromActions(actions) {
1058
+ const contentTypeUids = actions.reduce((acc, action) => {
1059
+ if (!acc.includes(action.contentType)) {
1060
+ acc.push(action.contentType);
1061
+ }
1062
+ return acc;
1063
+ }, []);
1064
+ const workflowsService = strapi2.plugin("review-workflows").service("workflows");
1065
+ const contentTypeModelsMap = await utils.async.reduce(contentTypeUids)(
1066
+ async (accPromise, contentTypeUid) => {
1067
+ const acc = await accPromise;
1068
+ const contentTypeModel = strapi2.getModel(contentTypeUid);
1069
+ const workflow = await workflowsService.getAssignedWorkflow(contentTypeUid, {
1070
+ populate: "stageRequiredToPublish"
1033
1071
  });
1034
- await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
1035
- status: "failed"
1036
- }).transacting(trx).execute();
1037
- return {
1038
- release: null,
1039
- error: error2
1072
+ acc[contentTypeUid] = {
1073
+ ...contentTypeModel,
1074
+ hasReviewWorkflow: !!workflow,
1075
+ stageRequiredToPublish: workflow?.stageRequiredToPublish
1040
1076
  };
1041
- }
1042
- });
1043
- if (error) {
1044
- throw error;
1045
- }
1046
- return release2;
1077
+ return acc;
1078
+ },
1079
+ {}
1080
+ );
1081
+ return contentTypeModelsMap;
1047
1082
  },
1048
- async updateAction(actionId, releaseId, update) {
1049
- const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
1083
+ async countActions(query) {
1084
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
1085
+ return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
1086
+ },
1087
+ async update(actionId, releaseId, update) {
1088
+ const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
1050
1089
  where: {
1051
1090
  id: actionId,
1052
1091
  release: {
@@ -1055,17 +1094,42 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1055
1094
  $null: true
1056
1095
  }
1057
1096
  }
1058
- },
1059
- data: update
1097
+ }
1060
1098
  });
1061
- if (!updatedAction) {
1099
+ if (!action) {
1062
1100
  throw new utils.errors.NotFoundError(
1063
1101
  `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1064
1102
  );
1065
1103
  }
1104
+ const actionStatus = update.type === "publish" ? await getDraftEntryValidStatus(
1105
+ {
1106
+ contentType: action.contentType,
1107
+ documentId: action.entryDocumentId,
1108
+ locale: action.locale
1109
+ },
1110
+ {
1111
+ strapi: strapi2
1112
+ }
1113
+ ) : true;
1114
+ const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
1115
+ where: {
1116
+ id: actionId,
1117
+ release: {
1118
+ id: releaseId,
1119
+ releasedAt: {
1120
+ $null: true
1121
+ }
1122
+ }
1123
+ },
1124
+ data: {
1125
+ ...update,
1126
+ isEntryValid: actionStatus
1127
+ }
1128
+ });
1129
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1066
1130
  return updatedAction;
1067
1131
  },
1068
- async deleteAction(actionId, releaseId) {
1132
+ async delete(actionId, releaseId) {
1069
1133
  const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
1070
1134
  where: {
1071
1135
  id: actionId,
@@ -1082,51 +1146,56 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1082
1146
  `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1083
1147
  );
1084
1148
  }
1085
- this.updateReleaseStatus(releaseId);
1149
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1086
1150
  return deletedAction;
1087
1151
  },
1088
- async updateReleaseStatus(releaseId) {
1089
- const [totalActions, invalidActions] = await Promise.all([
1090
- this.countActions({
1091
- filters: {
1092
- release: releaseId
1093
- }
1094
- }),
1095
- this.countActions({
1096
- filters: {
1097
- release: releaseId,
1098
- isEntryValid: false
1099
- }
1100
- })
1101
- ]);
1102
- if (totalActions > 0) {
1103
- if (invalidActions > 0) {
1104
- return strapi2.db.query(RELEASE_MODEL_UID).update({
1105
- where: {
1106
- id: releaseId
1107
- },
1108
- data: {
1109
- status: "blocked"
1152
+ async validateActionsByContentTypes(contentTypeUids) {
1153
+ const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
1154
+ where: {
1155
+ contentType: {
1156
+ $in: contentTypeUids
1157
+ },
1158
+ // We only want to validate actions that are going to be published
1159
+ type: "publish",
1160
+ release: {
1161
+ releasedAt: {
1162
+ $null: true
1110
1163
  }
1111
- });
1112
- }
1113
- return strapi2.db.query(RELEASE_MODEL_UID).update({
1164
+ }
1165
+ },
1166
+ populate: { release: true }
1167
+ });
1168
+ const releasesUpdated = [];
1169
+ await utils.async.map(actions, async (action) => {
1170
+ const isValid = await getDraftEntryValidStatus(
1171
+ {
1172
+ contentType: action.contentType,
1173
+ documentId: action.entryDocumentId,
1174
+ locale: action.locale
1175
+ },
1176
+ { strapi: strapi2 }
1177
+ );
1178
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
1114
1179
  where: {
1115
- id: releaseId
1180
+ id: action.id
1116
1181
  },
1117
1182
  data: {
1118
- status: "ready"
1183
+ isEntryValid: isValid
1119
1184
  }
1120
1185
  });
1121
- }
1122
- return strapi2.db.query(RELEASE_MODEL_UID).update({
1123
- where: {
1124
- id: releaseId
1125
- },
1126
- data: {
1127
- status: "empty"
1186
+ if (!releasesUpdated.includes(action.release.id)) {
1187
+ releasesUpdated.push(action.release.id);
1128
1188
  }
1189
+ return {
1190
+ id: action.id,
1191
+ isEntryValid: isValid
1192
+ };
1129
1193
  });
1194
+ if (releasesUpdated.length > 0) {
1195
+ await utils.async.map(releasesUpdated, async (releaseId) => {
1196
+ await getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1197
+ });
1198
+ }
1130
1199
  }
1131
1200
  };
1132
1201
  };
@@ -1138,37 +1207,43 @@ class AlreadyOnReleaseError extends utils.errors.ApplicationError {
1138
1207
  }
1139
1208
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1140
1209
  async validateUniqueEntry(releaseId, releaseActionArgs) {
1141
- const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
1142
- populate: { actions: { populate: { entry: { fields: ["id"] } } } }
1210
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
1211
+ where: {
1212
+ id: releaseId
1213
+ },
1214
+ populate: {
1215
+ actions: true
1216
+ }
1143
1217
  });
1144
1218
  if (!release2) {
1145
1219
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
1146
1220
  }
1147
1221
  const isEntryInRelease = release2.actions.some(
1148
- (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1222
+ (action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
1149
1223
  );
1150
1224
  if (isEntryInRelease) {
1151
1225
  throw new AlreadyOnReleaseError(
1152
- `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1226
+ `Entry with documentId ${releaseActionArgs.entryDocumentId}${releaseActionArgs.locale ? `( ${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
1153
1227
  );
1154
1228
  }
1155
1229
  },
1156
- validateEntryContentType(contentTypeUid) {
1230
+ validateEntryData(contentTypeUid, entryDocumentId) {
1157
1231
  const contentType = strapi2.contentType(contentTypeUid);
1158
1232
  if (!contentType) {
1159
1233
  throw new utils.errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1160
1234
  }
1161
- if (!contentType.options?.draftAndPublish) {
1235
+ if (!utils.contentTypes.hasDraftAndPublish(contentType)) {
1162
1236
  throw new utils.errors.ValidationError(
1163
1237
  `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1164
1238
  );
1165
1239
  }
1240
+ if (contentType.kind === "collectionType" && !entryDocumentId) {
1241
+ throw new utils.errors.ValidationError("Document id is required for collection type");
1242
+ }
1166
1243
  },
1167
1244
  async validatePendingReleasesLimit() {
1168
- const maximumPendingReleases = (
1169
- // @ts-expect-error - options is not typed into features
1170
- EE__default.default.features.get("cms-content-releases")?.options?.maximumReleases || 3
1171
- );
1245
+ const featureCfg = strapi2.ee.features.get("cms-content-releases");
1246
+ const maximumPendingReleases = typeof featureCfg === "object" && featureCfg?.options?.maximumReleases || 3;
1172
1247
  const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
1173
1248
  filters: {
1174
1249
  releasedAt: {
@@ -1181,8 +1256,8 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1181
1256
  }
1182
1257
  },
1183
1258
  async validateUniqueNameForPendingRelease(name, id) {
1184
- const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
1185
- filters: {
1259
+ const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
1260
+ where: {
1186
1261
  releasedAt: {
1187
1262
  $null: true
1188
1263
  },
@@ -1211,7 +1286,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1211
1286
  }
1212
1287
  const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
1213
1288
  try {
1214
- await getService("release").publish(releaseId);
1289
+ await getService("release", { strapi: strapi2 }).publish(releaseId);
1215
1290
  } catch (error) {
1216
1291
  }
1217
1292
  this.cancel(releaseId);
@@ -1253,85 +1328,172 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1253
1328
  }
1254
1329
  };
1255
1330
  };
1331
+ const DEFAULT_SETTINGS = {
1332
+ defaultTimezone: null
1333
+ };
1334
+ const createSettingsService = ({ strapi: strapi2 }) => {
1335
+ const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
1336
+ return {
1337
+ async update({ settings: settings2 }) {
1338
+ const store = await getStore();
1339
+ store.set({ key: "settings", value: settings2 });
1340
+ return settings2;
1341
+ },
1342
+ async find() {
1343
+ const store = await getStore();
1344
+ const settings2 = await store.get({ key: "settings" });
1345
+ return {
1346
+ ...DEFAULT_SETTINGS,
1347
+ ...settings2 || {}
1348
+ };
1349
+ }
1350
+ };
1351
+ };
1256
1352
  const services = {
1257
1353
  release: createReleaseService,
1354
+ "release-action": createReleaseActionService,
1258
1355
  "release-validation": createReleaseValidationService,
1259
- scheduling: createSchedulingService
1356
+ scheduling: createSchedulingService,
1357
+ settings: createSettingsService
1260
1358
  };
1261
- const RELEASE_SCHEMA = yup__namespace.object().shape({
1262
- name: yup__namespace.string().trim().required(),
1263
- scheduledAt: yup__namespace.string().nullable(),
1264
- isScheduled: yup__namespace.boolean().optional(),
1265
- time: yup__namespace.string().when("isScheduled", {
1266
- is: true,
1267
- then: yup__namespace.string().trim().required(),
1268
- otherwise: yup__namespace.string().nullable()
1269
- }),
1270
- timezone: yup__namespace.string().when("isScheduled", {
1271
- is: true,
1272
- then: yup__namespace.string().required().nullable(),
1273
- otherwise: yup__namespace.string().nullable()
1274
- }),
1275
- date: yup__namespace.string().when("isScheduled", {
1276
- is: true,
1277
- then: yup__namespace.string().required().nullable(),
1278
- otherwise: yup__namespace.string().nullable()
1359
+ const RELEASE_SCHEMA = utils.yup.object().shape({
1360
+ name: utils.yup.string().trim().required(),
1361
+ scheduledAt: utils.yup.string().nullable(),
1362
+ timezone: utils.yup.string().when("scheduledAt", {
1363
+ is: (value) => value !== null && value !== void 0,
1364
+ then: utils.yup.string().required(),
1365
+ otherwise: utils.yup.string().nullable()
1279
1366
  })
1280
1367
  }).required().noUnknown();
1368
+ const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = utils.yup.object().shape({
1369
+ contentType: utils.yup.string().required(),
1370
+ entryDocumentId: utils.yup.string().nullable(),
1371
+ hasEntryAttached: utils.yup.string().nullable(),
1372
+ locale: utils.yup.string().nullable()
1373
+ }).required().noUnknown();
1281
1374
  const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
1375
+ const validatefindByDocumentAttachedParams = utils.validateYupSchema(
1376
+ FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
1377
+ );
1282
1378
  const releaseController = {
1283
- async findMany(ctx) {
1284
- const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
1379
+ /**
1380
+ * Find releases based on documents attached or not to the release.
1381
+ * If `hasEntryAttached` is true, it will return all releases that have the entry attached.
1382
+ * If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
1383
+ */
1384
+ async findByDocumentAttached(ctx) {
1385
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1285
1386
  ability: ctx.state.userAbility,
1286
1387
  model: RELEASE_MODEL_UID
1287
1388
  });
1288
1389
  await permissionsManager.validateQuery(ctx.query);
1289
1390
  const releaseService = getService("release", { strapi });
1290
- const isFindManyForContentTypeEntry = Boolean(ctx.query?.contentTypeUid && ctx.query?.entryId);
1291
- if (isFindManyForContentTypeEntry) {
1292
- const query = await permissionsManager.sanitizeQuery(ctx.query);
1293
- const contentTypeUid = query.contentTypeUid;
1294
- const entryId = query.entryId;
1295
- const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
1296
- const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
1297
- ctx.body = { data };
1298
- } else {
1299
- const query = await permissionsManager.sanitizeQuery(ctx.query);
1300
- const { results, pagination } = await releaseService.findPage(query);
1301
- const data = results.map((release2) => {
1302
- const { actions, ...releaseData } = release2;
1303
- return {
1304
- ...releaseData,
1391
+ const query = await permissionsManager.sanitizeQuery(ctx.query);
1392
+ await validatefindByDocumentAttachedParams(query);
1393
+ const model = strapi.getModel(query.contentType);
1394
+ if (model.kind && model.kind === "singleType") {
1395
+ const document = await strapi.db.query(model.uid).findOne({ select: ["documentId"] });
1396
+ if (!document) {
1397
+ throw new utils.errors.NotFoundError(`No entry found for contentType ${query.contentType}`);
1398
+ }
1399
+ query.entryDocumentId = document.documentId;
1400
+ }
1401
+ const { contentType, hasEntryAttached, entryDocumentId, locale } = query;
1402
+ const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
1403
+ if (isEntryAttached) {
1404
+ const releases = await releaseService.findMany({
1405
+ where: {
1406
+ releasedAt: null,
1305
1407
  actions: {
1306
- meta: {
1307
- count: actions.count
1408
+ contentType,
1409
+ entryDocumentId: entryDocumentId ?? null,
1410
+ locale: locale ?? null
1411
+ }
1412
+ },
1413
+ populate: {
1414
+ actions: {
1415
+ fields: ["type"],
1416
+ filters: {
1417
+ contentType,
1418
+ entryDocumentId: entryDocumentId ?? null,
1419
+ locale: locale ?? null
1308
1420
  }
1309
1421
  }
1310
- };
1422
+ }
1423
+ });
1424
+ ctx.body = { data: releases };
1425
+ } else {
1426
+ const relatedReleases = await releaseService.findMany({
1427
+ where: {
1428
+ releasedAt: null,
1429
+ actions: {
1430
+ contentType,
1431
+ entryDocumentId: entryDocumentId ?? null,
1432
+ locale: locale ?? null
1433
+ }
1434
+ }
1311
1435
  });
1312
- const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
1436
+ const releases = await releaseService.findMany({
1313
1437
  where: {
1438
+ $or: [
1439
+ {
1440
+ id: {
1441
+ $notIn: relatedReleases.map((release2) => release2.id)
1442
+ }
1443
+ },
1444
+ {
1445
+ actions: null
1446
+ }
1447
+ ],
1314
1448
  releasedAt: null
1315
1449
  }
1316
1450
  });
1317
- ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1451
+ ctx.body = { data: releases };
1318
1452
  }
1319
1453
  },
1454
+ async findPage(ctx) {
1455
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1456
+ ability: ctx.state.userAbility,
1457
+ model: RELEASE_MODEL_UID
1458
+ });
1459
+ await permissionsManager.validateQuery(ctx.query);
1460
+ const releaseService = getService("release", { strapi });
1461
+ const query = await permissionsManager.sanitizeQuery(ctx.query);
1462
+ const { results, pagination } = await releaseService.findPage(query);
1463
+ const data = results.map((release2) => {
1464
+ const { actions, ...releaseData } = release2;
1465
+ return {
1466
+ ...releaseData,
1467
+ actions: {
1468
+ meta: {
1469
+ count: actions.count
1470
+ }
1471
+ }
1472
+ };
1473
+ });
1474
+ const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1475
+ where: {
1476
+ releasedAt: null
1477
+ }
1478
+ });
1479
+ ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1480
+ },
1320
1481
  async findOne(ctx) {
1321
1482
  const id = ctx.params.id;
1322
1483
  const releaseService = getService("release", { strapi });
1484
+ const releaseActionService = getService("release-action", { strapi });
1323
1485
  const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
1324
1486
  if (!release2) {
1325
1487
  throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
1326
1488
  }
1327
- const count = await releaseService.countActions({
1489
+ const count = await releaseActionService.countActions({
1328
1490
  filters: {
1329
1491
  release: id
1330
1492
  }
1331
1493
  });
1332
1494
  const sanitizedRelease = {
1333
1495
  ...release2,
1334
- createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
1496
+ createdBy: release2.createdBy ? strapi.service("admin::user").sanitizeUser(release2.createdBy) : null
1335
1497
  };
1336
1498
  const data = {
1337
1499
  ...sanitizedRelease,
@@ -1344,22 +1506,39 @@ const releaseController = {
1344
1506
  ctx.body = { data };
1345
1507
  },
1346
1508
  async mapEntriesToReleases(ctx) {
1347
- const { contentTypeUid, entriesIds } = ctx.query;
1348
- if (!contentTypeUid || !entriesIds) {
1509
+ const { contentTypeUid, documentIds, locale } = ctx.query;
1510
+ if (!contentTypeUid || !documentIds) {
1349
1511
  throw new utils.errors.ValidationError("Missing required query parameters");
1350
1512
  }
1351
1513
  const releaseService = getService("release", { strapi });
1352
- const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
1353
- contentTypeUid,
1354
- entriesIds
1355
- );
1514
+ const releasesWithActions = await releaseService.findMany({
1515
+ where: {
1516
+ releasedAt: null,
1517
+ actions: {
1518
+ contentType: contentTypeUid,
1519
+ entryDocumentId: {
1520
+ $in: documentIds
1521
+ },
1522
+ locale
1523
+ }
1524
+ },
1525
+ populate: {
1526
+ actions: true
1527
+ }
1528
+ });
1356
1529
  const mappedEntriesInReleases = releasesWithActions.reduce(
1357
1530
  (acc, release2) => {
1358
1531
  release2.actions.forEach((action) => {
1359
- if (!acc[action.entry.id]) {
1360
- acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
1532
+ if (action.contentType !== contentTypeUid) {
1533
+ return;
1534
+ }
1535
+ if (locale && action.locale !== locale) {
1536
+ return;
1537
+ }
1538
+ if (!acc[action.entryDocumentId]) {
1539
+ acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
1361
1540
  } else {
1362
- acc[action.entry.id].push({ id: release2.id, name: release2.name });
1541
+ acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
1363
1542
  }
1364
1543
  });
1365
1544
  return acc;
@@ -1376,13 +1555,13 @@ const releaseController = {
1376
1555
  await validateRelease(releaseArgs);
1377
1556
  const releaseService = getService("release", { strapi });
1378
1557
  const release2 = await releaseService.create(releaseArgs, { user });
1379
- const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
1558
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1380
1559
  ability: ctx.state.userAbility,
1381
1560
  model: RELEASE_MODEL_UID
1382
1561
  });
1383
- ctx.body = {
1562
+ ctx.created({
1384
1563
  data: await permissionsManager.sanitizeOutput(release2)
1385
- };
1564
+ });
1386
1565
  },
1387
1566
  async update(ctx) {
1388
1567
  const user = ctx.state.user;
@@ -1391,7 +1570,7 @@ const releaseController = {
1391
1570
  await validateRelease(releaseArgs);
1392
1571
  const releaseService = getService("release", { strapi });
1393
1572
  const release2 = await releaseService.update(id, releaseArgs, { user });
1394
- const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
1573
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1395
1574
  ability: ctx.state.userAbility,
1396
1575
  model: RELEASE_MODEL_UID
1397
1576
  });
@@ -1408,18 +1587,18 @@ const releaseController = {
1408
1587
  };
1409
1588
  },
1410
1589
  async publish(ctx) {
1411
- const user = ctx.state.user;
1412
1590
  const id = ctx.params.id;
1413
1591
  const releaseService = getService("release", { strapi });
1414
- const release2 = await releaseService.publish(id, { user });
1592
+ const releaseActionService = getService("release-action", { strapi });
1593
+ const release2 = await releaseService.publish(id);
1415
1594
  const [countPublishActions, countUnpublishActions] = await Promise.all([
1416
- releaseService.countActions({
1595
+ releaseActionService.countActions({
1417
1596
  filters: {
1418
1597
  release: id,
1419
1598
  type: "publish"
1420
1599
  }
1421
1600
  }),
1422
- releaseService.countActions({
1601
+ releaseActionService.countActions({
1423
1602
  filters: {
1424
1603
  release: id,
1425
1604
  type: "unpublish"
@@ -1437,27 +1616,30 @@ const releaseController = {
1437
1616
  }
1438
1617
  };
1439
1618
  const RELEASE_ACTION_SCHEMA = utils.yup.object().shape({
1440
- entry: utils.yup.object().shape({
1441
- id: utils.yup.strapiID().required(),
1442
- contentType: utils.yup.string().required()
1443
- }).required(),
1619
+ contentType: utils.yup.string().required(),
1620
+ entryDocumentId: utils.yup.strapiID(),
1621
+ locale: utils.yup.string(),
1444
1622
  type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
1445
1623
  });
1446
1624
  const RELEASE_ACTION_UPDATE_SCHEMA = utils.yup.object().shape({
1447
1625
  type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
1448
1626
  });
1627
+ const FIND_MANY_ACTIONS_PARAMS = utils.yup.object().shape({
1628
+ groupBy: utils.yup.string().oneOf(["action", "contentType", "locale"])
1629
+ });
1449
1630
  const validateReleaseAction = utils.validateYupSchema(RELEASE_ACTION_SCHEMA);
1450
1631
  const validateReleaseActionUpdateSchema = utils.validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
1632
+ const validateFindManyActionsParams = utils.validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
1451
1633
  const releaseActionController = {
1452
1634
  async create(ctx) {
1453
1635
  const releaseId = ctx.params.releaseId;
1454
1636
  const releaseActionArgs = ctx.request.body;
1455
1637
  await validateReleaseAction(releaseActionArgs);
1456
- const releaseService = getService("release", { strapi });
1457
- const releaseAction2 = await releaseService.createAction(releaseId, releaseActionArgs);
1458
- ctx.body = {
1638
+ const releaseActionService = getService("release-action", { strapi });
1639
+ const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
1640
+ ctx.created({
1459
1641
  data: releaseAction2
1460
- };
1642
+ });
1461
1643
  },
1462
1644
  async createMany(ctx) {
1463
1645
  const releaseId = ctx.params.releaseId;
@@ -1465,12 +1647,13 @@ const releaseActionController = {
1465
1647
  await Promise.all(
1466
1648
  releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1467
1649
  );
1650
+ const releaseActionService = getService("release-action", { strapi });
1468
1651
  const releaseService = getService("release", { strapi });
1469
1652
  const releaseActions = await strapi.db.transaction(async () => {
1470
1653
  const releaseActions2 = await Promise.all(
1471
1654
  releaseActionsArgs.map(async (releaseActionArgs) => {
1472
1655
  try {
1473
- const action = await releaseService.createAction(releaseId, releaseActionArgs, {
1656
+ const action = await releaseActionService.create(releaseId, releaseActionArgs, {
1474
1657
  disableUpdateReleaseStatus: true
1475
1658
  });
1476
1659
  return action;
@@ -1488,43 +1671,51 @@ const releaseActionController = {
1488
1671
  if (newReleaseActions.length > 0) {
1489
1672
  releaseService.updateReleaseStatus(releaseId);
1490
1673
  }
1491
- ctx.body = {
1674
+ ctx.created({
1492
1675
  data: newReleaseActions,
1493
1676
  meta: {
1494
1677
  entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
1495
1678
  totalEntries: releaseActions.length
1496
1679
  }
1497
- };
1680
+ });
1498
1681
  },
1499
1682
  async findMany(ctx) {
1500
1683
  const releaseId = ctx.params.releaseId;
1501
- const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
1684
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1502
1685
  ability: ctx.state.userAbility,
1503
1686
  model: RELEASE_ACTION_MODEL_UID
1504
1687
  });
1688
+ await validateFindManyActionsParams(ctx.query);
1689
+ if (ctx.query.groupBy) {
1690
+ if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
1691
+ ctx.badRequest("Invalid groupBy parameter");
1692
+ }
1693
+ }
1694
+ ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
1695
+ delete ctx.query.groupBy;
1505
1696
  const query = await permissionsManager.sanitizeQuery(ctx.query);
1506
- const releaseService = getService("release", { strapi });
1507
- const { results, pagination } = await releaseService.findActions(releaseId, {
1508
- sort: query.groupBy === "action" ? "type" : query.groupBy,
1697
+ const releaseActionService = getService("release-action", { strapi });
1698
+ const { results, pagination } = await releaseActionService.findPage(releaseId, {
1509
1699
  ...query
1510
1700
  });
1511
1701
  const contentTypeOutputSanitizers = results.reduce((acc, action) => {
1512
1702
  if (acc[action.contentType]) {
1513
1703
  return acc;
1514
1704
  }
1515
- const contentTypePermissionsManager = strapi.admin.services.permission.createPermissionsManager({
1705
+ const contentTypePermissionsManager = strapi.service("admin::permission").createPermissionsManager({
1516
1706
  ability: ctx.state.userAbility,
1517
1707
  model: action.contentType
1518
1708
  });
1519
1709
  acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
1520
1710
  return acc;
1521
1711
  }, {});
1522
- const sanitizedResults = await utils.mapAsync(results, async (action) => ({
1712
+ const sanitizedResults = await utils.async.map(results, async (action) => ({
1523
1713
  ...action,
1524
- entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1714
+ entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
1525
1715
  }));
1526
- const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
1527
- const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
1716
+ const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
1717
+ const contentTypes2 = await releaseActionService.getContentTypeModelsFromActions(results);
1718
+ const releaseService = getService("release", { strapi });
1528
1719
  const components = await releaseService.getAllComponents();
1529
1720
  ctx.body = {
1530
1721
  data: groupedData,
@@ -1540,8 +1731,8 @@ const releaseActionController = {
1540
1731
  const releaseId = ctx.params.releaseId;
1541
1732
  const releaseActionUpdateArgs = ctx.request.body;
1542
1733
  await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
1543
- const releaseService = getService("release", { strapi });
1544
- const updatedAction = await releaseService.updateAction(
1734
+ const releaseActionService = getService("release-action", { strapi });
1735
+ const updatedAction = await releaseActionService.update(
1545
1736
  actionId,
1546
1737
  releaseId,
1547
1738
  releaseActionUpdateArgs
@@ -1553,14 +1744,36 @@ const releaseActionController = {
1553
1744
  async delete(ctx) {
1554
1745
  const actionId = ctx.params.actionId;
1555
1746
  const releaseId = ctx.params.releaseId;
1556
- const releaseService = getService("release", { strapi });
1557
- const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
1747
+ const releaseActionService = getService("release-action", { strapi });
1748
+ const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
1558
1749
  ctx.body = {
1559
1750
  data: deletedReleaseAction
1560
1751
  };
1561
1752
  }
1562
1753
  };
1563
- const controllers = { release: releaseController, "release-action": releaseActionController };
1754
+ const SETTINGS_SCHEMA = yup__namespace.object().shape({
1755
+ defaultTimezone: yup__namespace.string().nullable().default(null)
1756
+ }).required().noUnknown();
1757
+ const validateSettings = utils.validateYupSchema(SETTINGS_SCHEMA);
1758
+ const settingsController = {
1759
+ async find(ctx) {
1760
+ const settingsService = getService("settings", { strapi });
1761
+ const settings2 = await settingsService.find();
1762
+ ctx.body = { data: settings2 };
1763
+ },
1764
+ async update(ctx) {
1765
+ const settingsBody = ctx.request.body;
1766
+ const settings2 = await validateSettings(settingsBody);
1767
+ const settingsService = getService("settings", { strapi });
1768
+ const updatedSettings = await settingsService.update({ settings: settings2 });
1769
+ ctx.body = { data: updatedSettings };
1770
+ }
1771
+ };
1772
+ const controllers = {
1773
+ release: releaseController,
1774
+ "release-action": releaseActionController,
1775
+ settings: settingsController
1776
+ };
1564
1777
  const release = {
1565
1778
  type: "admin",
1566
1779
  routes: [
@@ -1580,6 +1793,22 @@ const release = {
1580
1793
  ]
1581
1794
  }
1582
1795
  },
1796
+ {
1797
+ method: "GET",
1798
+ path: "/getByDocumentAttached",
1799
+ handler: "release.findByDocumentAttached",
1800
+ config: {
1801
+ policies: [
1802
+ "admin::isAuthenticatedAdmin",
1803
+ {
1804
+ name: "admin::hasPermissions",
1805
+ config: {
1806
+ actions: ["plugin::content-releases.read"]
1807
+ }
1808
+ }
1809
+ ]
1810
+ }
1811
+ },
1583
1812
  {
1584
1813
  method: "POST",
1585
1814
  path: "/",
@@ -1599,7 +1828,7 @@ const release = {
1599
1828
  {
1600
1829
  method: "GET",
1601
1830
  path: "/",
1602
- handler: "release.findMany",
1831
+ handler: "release.findPage",
1603
1832
  config: {
1604
1833
  policies: [
1605
1834
  "admin::isAuthenticatedAdmin",
@@ -1763,13 +1992,50 @@ const releaseAction = {
1763
1992
  }
1764
1993
  ]
1765
1994
  };
1995
+ const settings = {
1996
+ type: "admin",
1997
+ routes: [
1998
+ {
1999
+ method: "GET",
2000
+ path: "/settings",
2001
+ handler: "settings.find",
2002
+ config: {
2003
+ policies: [
2004
+ "admin::isAuthenticatedAdmin",
2005
+ {
2006
+ name: "admin::hasPermissions",
2007
+ config: {
2008
+ actions: ["plugin::content-releases.settings.read"]
2009
+ }
2010
+ }
2011
+ ]
2012
+ }
2013
+ },
2014
+ {
2015
+ method: "PUT",
2016
+ path: "/settings",
2017
+ handler: "settings.update",
2018
+ config: {
2019
+ policies: [
2020
+ "admin::isAuthenticatedAdmin",
2021
+ {
2022
+ name: "admin::hasPermissions",
2023
+ config: {
2024
+ actions: ["plugin::content-releases.settings.update"]
2025
+ }
2026
+ }
2027
+ ]
2028
+ }
2029
+ }
2030
+ ]
2031
+ };
1766
2032
  const routes = {
2033
+ settings,
1767
2034
  release,
1768
2035
  "release-action": releaseAction
1769
2036
  };
1770
- const { features } = require("@strapi/strapi/dist/utils/ee");
1771
2037
  const getPlugin = () => {
1772
- if (features.isEnabled("cms-content-releases")) {
2038
+ if (strapi.ee.features.isEnabled("cms-content-releases")) {
1773
2039
  return {
1774
2040
  register,
1775
2041
  bootstrap,