@strapi/content-releases 0.0.0-experimental.c592deb623aed3f74ef7fdacfad9757ed59d34f7 → 0.0.0-experimental.c95cecbc9ed31c3bd68eff1ae5fa4b64f2a5020e

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