@strapi/content-releases 0.0.0-next.d10040847b91742ccb8083938399b63ffa289c7a → 0.0.0-next.d2d15ef227d67cce89c2673764c0555c841cd29c

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