@strapi/content-releases 0.0.0-next.f4ff842a3cb7b83db540bee67554b704e042b042 → 0.0.0-next.f698d55751345c4ca87477ef683475c1a68f310a

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