@strapi/content-releases 0.0.0-next.78ea7925e0dad75936ae2e937a041a0666e3d65a → 0.0.0-next.7f1333f1967e625c57ab16648c057aea08c9dddb

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