@strapi/content-releases 0.0.0-experimental.f0d4afee92a0d386f80385590c87955656f995ce → 0.0.0-experimental.f28dba7c374eae9c02b95b4b77aba4c3ad41a841

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-Cne--1Z8.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 +638 -834
  22. package/dist/server/index.js.map +1 -1
  23. package/dist/server/index.mjs +638 -835
  24. package/dist/server/index.mjs.map +1 -1
  25. package/package.json +39 -33
  26. package/strapi-server.js +3 -0
  27. package/dist/_chunks/App-BKB1esYS.js +0 -1395
  28. package/dist/_chunks/App-BKB1esYS.js.map +0 -1
  29. package/dist/_chunks/App-Cne--1Z8.mjs.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-C1WwGWIH.mjs +0 -178
  33. package/dist/_chunks/ReleasesSettingsPage-C1WwGWIH.mjs.map +0 -1
  34. package/dist/_chunks/ReleasesSettingsPage-kuXIwpWp.js +0 -178
  35. package/dist/_chunks/ReleasesSettingsPage-kuXIwpWp.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-5Odi61vw.js +0 -1381
  39. package/dist/_chunks/index-5Odi61vw.js.map +0 -1
  40. package/dist/_chunks/index-Cy7qwpaU.mjs +0 -1362
  41. package/dist/_chunks/index-Cy7qwpaU.mjs.map +0 -1
  42. package/dist/_chunks/schemas-BE1LxE9J.js +0 -62
  43. package/dist/_chunks/schemas-BE1LxE9J.js.map +0 -1
  44. package/dist/_chunks/schemas-DdA2ic2U.mjs +0 -44
  45. package/dist/_chunks/schemas-DdA2ic2U.mjs.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,43 +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 hasTable = await trx.schema.hasTable("strapi_release_actions");
310
- if (!hasTable) {
311
- return;
312
- }
313
- const hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
314
- if (hasPolymorphicColumn) {
315
- const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
316
- "strapi_release_actions",
317
- "entry_document_id"
318
- );
319
- if (!hasEntryDocumentIdColumn) {
320
- await trx.schema.alterTable("strapi_release_actions", (table) => {
321
- table.string("entry_document_id");
322
- });
323
- }
324
- const releaseActions = await trx.select("*").from("strapi_release_actions");
325
- async.map(releaseActions, async (action) => {
326
- const { target_type, target_id } = action;
327
- const entry = await db.query(target_type).findOne({ where: { id: target_id } });
328
- if (entry) {
329
- await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
330
- }
331
- });
332
- }
333
- },
334
- async down() {
335
- throw new Error("not implemented");
336
- }
337
- };
256
+ const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
338
257
  const register = async ({ strapi: strapi2 }) => {
339
- if (strapi2.ee.features.isEnabled("cms-content-releases")) {
340
- await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
341
- strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
342
- strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
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);
343
261
  strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
344
262
  }
345
263
  if (strapi2.plugin("graphql")) {
@@ -348,134 +266,129 @@ const register = async ({ strapi: strapi2 }) => {
348
266
  graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
349
267
  }
350
268
  };
351
- const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
352
- const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
353
- where: {
354
- actions: {
355
- contentType,
356
- entryDocumentId: entry.documentId,
357
- locale: entry.locale
358
- }
359
- }
360
- });
361
- const entryStatus = await isEntryValid(contentType, entry, { strapi });
362
- await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
363
- where: {
364
- contentType,
365
- entryDocumentId: entry.documentId,
366
- locale: entry.locale
367
- },
368
- data: {
369
- isEntryValid: entryStatus
370
- }
371
- });
372
- for (const release2 of releases) {
373
- getService("release", { strapi }).updateReleaseStatus(release2.id);
374
- }
375
- };
376
- const deleteActionsAndUpdateReleaseStatus = async (params) => {
377
- const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
378
- where: {
379
- actions: params
380
- }
381
- });
382
- await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
383
- where: params
384
- });
385
- for (const release2 of releases) {
386
- getService("release", { strapi }).updateReleaseStatus(release2.id);
387
- }
388
- };
389
- const deleteActionsOnDelete = async (ctx, next) => {
390
- if (ctx.action !== "delete") {
391
- return next();
392
- }
393
- if (!contentTypes$1.hasDraftAndPublish(ctx.contentType)) {
394
- return next();
395
- }
396
- const contentType = ctx.contentType.uid;
397
- const { documentId, locale } = ctx.params;
398
- const result = await next();
399
- if (!result) {
400
- return result;
401
- }
402
- try {
403
- deleteActionsAndUpdateReleaseStatus({
404
- contentType,
405
- entryDocumentId: documentId,
406
- ...locale !== "*" && { locale }
407
- });
408
- } catch (error) {
409
- strapi.log.error("Error while deleting release actions after delete", {
410
- error
411
- });
412
- }
413
- return result;
414
- };
415
- const updateActionsOnUpdate = async (ctx, next) => {
416
- if (ctx.action !== "update") {
417
- return next();
418
- }
419
- if (!contentTypes$1.hasDraftAndPublish(ctx.contentType)) {
420
- return next();
421
- }
422
- const contentType = ctx.contentType.uid;
423
- const result = await next();
424
- if (!result) {
425
- return result;
426
- }
427
- try {
428
- updateActionsStatusAndUpdateReleaseStatus(contentType, result);
429
- } catch (error) {
430
- strapi.log.error("Error while updating release actions after update", {
431
- error
432
- });
433
- }
434
- return result;
435
- };
436
- const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
437
- const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
438
- where: {
439
- actions: params
440
- }
441
- });
442
- await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
443
- where: params
444
- });
445
- for (const release2 of releases) {
446
- getService("release", { strapi }).updateReleaseStatus(release2.id);
447
- }
448
- };
269
+ const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
449
270
  const bootstrap = async ({ strapi: strapi2 }) => {
450
- if (strapi2.ee.features.isEnabled("cms-content-releases")) {
271
+ if (features$1.isEnabled("cms-content-releases")) {
451
272
  const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
452
273
  (uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
453
274
  );
454
275
  strapi2.db.lifecycles.subscribe({
455
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
+ },
304
+ /**
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
+ },
456
316
  /**
457
- * deleteMany is still used outside documents service, for example when deleting a locale
317
+ * We delete the release actions related to deleted entries
318
+ * We make this only after deleteMany is succesfully executed to avoid errors
458
319
  */
459
320
  async afterDeleteMany(event) {
460
321
  try {
461
- const model = strapi2.getModel(event.model.uid);
462
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
463
- const { where } = event.params;
464
- deleteReleasesActionsAndUpdateReleaseStatus({
465
- contentType: model.uid,
466
- locale: where.locale ?? null,
467
- ...where.documentId && { entryDocumentId: where.documentId }
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
+ }
468
344
  });
345
+ for (const release2 of releases) {
346
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
347
+ }
469
348
  }
470
349
  } catch (error) {
471
350
  strapi2.log.error("Error while deleting release actions after entry deleteMany", {
472
351
  error
473
352
  });
474
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
+ }
475
390
  }
476
391
  });
477
- strapi2.documents.use(deleteActionsOnDelete);
478
- strapi2.documents.use(updateActionsOnUpdate);
479
392
  getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
480
393
  strapi2.log.error(
481
394
  "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
@@ -483,7 +396,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
483
396
  throw err;
484
397
  });
485
398
  Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
486
- strapi2.get("webhookStore").addAllowedEvent(key, value);
399
+ strapi2.webhookStore.addAllowedEvent(key, value);
487
400
  });
488
401
  }
489
402
  };
@@ -567,13 +480,15 @@ const schema = {
567
480
  enum: ["publish", "unpublish"],
568
481
  required: true
569
482
  },
483
+ entry: {
484
+ type: "relation",
485
+ relation: "morphToOne",
486
+ configurable: false
487
+ },
570
488
  contentType: {
571
489
  type: "string",
572
490
  required: true
573
491
  },
574
- entryDocumentId: {
575
- type: "string"
576
- },
577
492
  locale: {
578
493
  type: "string"
579
494
  },
@@ -595,6 +510,18 @@ const contentTypes = {
595
510
  release: release$1,
596
511
  "release-action": releaseAction$1
597
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
+ };
598
525
  const createReleaseService = ({ strapi: strapi2 }) => {
599
526
  const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
600
527
  strapi2.eventHub.emit(event, {
@@ -603,32 +530,93 @@ const createReleaseService = ({ strapi: strapi2 }) => {
603
530
  release: release2
604
531
  });
605
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
+ };
606
579
  const getFormattedActions = async (releaseId) => {
607
580
  const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
608
581
  where: {
609
582
  release: {
610
583
  id: releaseId
611
584
  }
585
+ },
586
+ populate: {
587
+ entry: {
588
+ fields: ["id"]
589
+ }
612
590
  }
613
591
  });
614
592
  if (actions.length === 0) {
615
593
  throw new errors.ValidationError("No entries to publish");
616
594
  }
617
- const formattedActions = {};
595
+ const collectionTypeActions = {};
596
+ const singleTypeActions = [];
618
597
  for (const action of actions) {
619
598
  const contentTypeUid = action.contentType;
620
- if (!formattedActions[contentTypeUid]) {
621
- formattedActions[contentTypeUid] = {
622
- publish: [],
623
- unpublish: []
624
- };
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
+ });
625
617
  }
626
- formattedActions[contentTypeUid][action.type].push({
627
- documentId: action.entryDocumentId,
628
- locale: action.locale
629
- });
630
618
  }
631
- return formattedActions;
619
+ return { collectionTypeActions, singleTypeActions };
632
620
  };
633
621
  return {
634
622
  async create(releaseData, { user }) {
@@ -643,7 +631,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
643
631
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
644
632
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
645
633
  ]);
646
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
634
+ const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
647
635
  data: {
648
636
  ...releaseWithCreatorFields,
649
637
  status: "empty"
@@ -657,28 +645,107 @@ const createReleaseService = ({ strapi: strapi2 }) => {
657
645
  return release2;
658
646
  },
659
647
  async findOne(id, query = {}) {
660
- const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query);
661
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
662
- ...dbQuery,
663
- where: { id }
648
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
649
+ ...query
664
650
  });
665
651
  return release2;
666
652
  },
667
653
  findPage(query) {
668
- const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
669
- return strapi2.db.query(RELEASE_MODEL_UID).findPage({
670
- ...dbQuery,
654
+ return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
655
+ ...query,
671
656
  populate: {
672
657
  actions: {
658
+ // @ts-expect-error Ignore missing properties
673
659
  count: true
674
660
  }
675
661
  }
676
662
  });
677
663
  },
678
- findMany(query) {
679
- const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
680
- return strapi2.db.query(RELEASE_MODEL_UID).findMany({
681
- ...dbQuery
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;
682
749
  });
683
750
  },
684
751
  async update(id, releaseData, { user }) {
@@ -693,15 +760,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
693
760
  validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
694
761
  validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
695
762
  ]);
696
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
763
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
697
764
  if (!release2) {
698
765
  throw new errors.NotFoundError(`No release found for id ${id}`);
699
766
  }
700
767
  if (release2.releasedAt) {
701
768
  throw new errors.ValidationError("Release already published");
702
769
  }
703
- const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
704
- 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
705
776
  data: releaseWithCreatorFields
706
777
  });
707
778
  const schedulingService = getService("scheduling", { strapi: strapi2 });
@@ -714,6 +785,132 @@ const createReleaseService = ({ strapi: strapi2 }) => {
714
785
  strapi2.telemetry.send("didUpdateContentRelease");
715
786
  return updatedRelease;
716
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
+ },
717
914
  async getAllComponents() {
718
915
  const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
719
916
  const components = await contentManagerComponentsService.findAllComponents();
@@ -727,11 +924,10 @@ const createReleaseService = ({ strapi: strapi2 }) => {
727
924
  return componentsMap;
728
925
  },
729
926
  async delete(releaseId) {
730
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
731
- where: { id: releaseId },
927
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
732
928
  populate: {
733
929
  actions: {
734
- select: ["id"]
930
+ fields: ["id"]
735
931
  }
736
932
  }
737
933
  });
@@ -749,11 +945,7 @@ const createReleaseService = ({ strapi: strapi2 }) => {
749
945
  }
750
946
  }
751
947
  });
752
- await strapi2.db.query(RELEASE_MODEL_UID).delete({
753
- where: {
754
- id: releaseId
755
- }
756
- });
948
+ await strapi2.entityService.delete(RELEASE_MODEL_UID, releaseId);
757
949
  });
758
950
  if (release2.scheduledAt) {
759
951
  const schedulingService = getService("scheduling", { strapi: strapi2 });
@@ -779,19 +971,22 @@ const createReleaseService = ({ strapi: strapi2 }) => {
779
971
  }
780
972
  try {
781
973
  strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
782
- const formattedActions = await getFormattedActions(releaseId);
783
- await strapi2.db.transaction(
784
- async () => Promise.all(
785
- Object.keys(formattedActions).map(async (contentTypeUid) => {
786
- const contentType = contentTypeUid;
787
- const { publish, unpublish } = formattedActions[contentType];
788
- return Promise.all([
789
- ...publish.map((params) => strapi2.documents(contentType).publish(params)),
790
- ...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
791
- ]);
792
- })
793
- )
974
+ const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
975
+ releaseId
794
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
+ });
795
990
  const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
796
991
  where: {
797
992
  id: releaseId
@@ -821,226 +1016,13 @@ const createReleaseService = ({ strapi: strapi2 }) => {
821
1016
  };
822
1017
  }
823
1018
  });
824
- if (error instanceof Error) {
825
- throw error;
826
- }
827
- return release2;
828
- },
829
- async updateReleaseStatus(releaseId) {
830
- const releaseActionService = getService("release-action", { strapi: strapi2 });
831
- const [totalActions, invalidActions] = await Promise.all([
832
- releaseActionService.countActions({
833
- filters: {
834
- release: releaseId
835
- }
836
- }),
837
- releaseActionService.countActions({
838
- filters: {
839
- release: releaseId,
840
- isEntryValid: false
841
- }
842
- })
843
- ]);
844
- if (totalActions > 0) {
845
- if (invalidActions > 0) {
846
- return strapi2.db.query(RELEASE_MODEL_UID).update({
847
- where: {
848
- id: releaseId
849
- },
850
- data: {
851
- status: "blocked"
852
- }
853
- });
854
- }
855
- return strapi2.db.query(RELEASE_MODEL_UID).update({
856
- where: {
857
- id: releaseId
858
- },
859
- data: {
860
- status: "ready"
861
- }
862
- });
863
- }
864
- return strapi2.db.query(RELEASE_MODEL_UID).update({
865
- where: {
866
- id: releaseId
867
- },
868
- data: {
869
- status: "empty"
870
- }
871
- });
872
- }
873
- };
874
- };
875
- const getGroupName = (queryValue) => {
876
- switch (queryValue) {
877
- case "contentType":
878
- return "contentType.displayName";
879
- case "type":
880
- return "type";
881
- case "locale":
882
- return _.getOr("No locale", "locale.name");
883
- default:
884
- return "contentType.displayName";
885
- }
886
- };
887
- const createReleaseActionService = ({ strapi: strapi2 }) => {
888
- const getLocalesDataForActions = async () => {
889
- if (!strapi2.plugin("i18n")) {
890
- return {};
891
- }
892
- const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
893
- return allLocales.reduce((acc, locale) => {
894
- acc[locale.code] = { name: locale.name, code: locale.code };
895
- return acc;
896
- }, {});
897
- };
898
- const getContentTypesDataForActions = async (contentTypesUids) => {
899
- const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
900
- const contentTypesData = {};
901
- for (const contentTypeUid of contentTypesUids) {
902
- const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
903
- uid: contentTypeUid
904
- });
905
- contentTypesData[contentTypeUid] = {
906
- mainField: contentTypeConfig.settings.mainField,
907
- displayName: strapi2.getModel(contentTypeUid).info.displayName
908
- };
909
- }
910
- return contentTypesData;
911
- };
912
- return {
913
- async create(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
914
- const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
915
- strapi: strapi2
916
- });
917
- await Promise.all([
918
- validateEntryData(action.contentType, action.entryDocumentId),
919
- validateUniqueEntry(releaseId, action)
920
- ]);
921
- const model = strapi2.contentType(action.contentType);
922
- if (model.kind === "singleType") {
923
- const document = await strapi2.db.query(model.uid).findOne({ select: ["documentId"] });
924
- if (!document) {
925
- throw new errors.NotFoundError(`No entry found for contentType ${action.contentType}`);
926
- }
927
- action.entryDocumentId = document.documentId;
928
- }
929
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
930
- if (!release2) {
931
- throw new errors.NotFoundError(`No release found for id ${releaseId}`);
932
- }
933
- if (release2.releasedAt) {
934
- throw new errors.ValidationError("Release already published");
935
- }
936
- const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
937
- {
938
- contentType: action.contentType,
939
- documentId: action.entryDocumentId,
940
- locale: action.locale
941
- },
942
- {
943
- strapi: strapi2
944
- }
945
- ) : true;
946
- const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
947
- data: {
948
- ...action,
949
- release: release2.id,
950
- isEntryValid: actionStatus
951
- },
952
- populate: { release: { select: ["id"] } }
953
- });
954
- if (!disableUpdateReleaseStatus) {
955
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
956
- }
957
- return releaseAction2;
958
- },
959
- async findPage(releaseId, query) {
960
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
961
- where: { id: releaseId },
962
- select: ["id"]
963
- });
964
- if (!release2) {
965
- throw new errors.NotFoundError(`No release found for id ${releaseId}`);
1019
+ if (error) {
1020
+ throw error;
966
1021
  }
967
- const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
968
- const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
969
- ...dbQuery,
970
- where: {
971
- release: releaseId
972
- }
973
- });
974
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
975
- const actionsWithEntry = await async.map(actions, async (action) => {
976
- const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
977
- const entry = await getEntry(
978
- {
979
- contentType: action.contentType,
980
- documentId: action.entryDocumentId,
981
- locale: action.locale,
982
- populate,
983
- status: action.type === "publish" ? "draft" : "published"
984
- },
985
- { strapi: strapi2 }
986
- );
987
- return {
988
- ...action,
989
- entry,
990
- status: entry ? await getEntryStatus(action.contentType, entry) : null
991
- };
992
- });
993
- return {
994
- results: actionsWithEntry,
995
- pagination
996
- };
997
- },
998
- async groupActions(actions, groupBy) {
999
- const contentTypeUids = actions.reduce((acc, action) => {
1000
- if (!acc.includes(action.contentType)) {
1001
- acc.push(action.contentType);
1002
- }
1003
- return acc;
1004
- }, []);
1005
- const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
1006
- const allLocalesDictionary = await getLocalesDataForActions();
1007
- const formattedData = actions.map((action) => {
1008
- const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
1009
- return {
1010
- ...action,
1011
- locale: action.locale ? allLocalesDictionary[action.locale] : null,
1012
- contentType: {
1013
- displayName,
1014
- mainFieldValue: action.entry[mainField],
1015
- uid: action.contentType
1016
- }
1017
- };
1018
- });
1019
- const groupName = getGroupName(groupBy);
1020
- return _.groupBy(groupName)(formattedData);
1021
- },
1022
- getContentTypeModelsFromActions(actions) {
1023
- const contentTypeUids = actions.reduce((acc, action) => {
1024
- if (!acc.includes(action.contentType)) {
1025
- acc.push(action.contentType);
1026
- }
1027
- return acc;
1028
- }, []);
1029
- const contentTypeModelsMap = contentTypeUids.reduce(
1030
- (acc, contentTypeUid) => {
1031
- acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
1032
- return acc;
1033
- },
1034
- {}
1035
- );
1036
- return contentTypeModelsMap;
1037
- },
1038
- async countActions(query) {
1039
- const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
1040
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
1022
+ return release2;
1041
1023
  },
1042
- async update(actionId, releaseId, update) {
1043
- 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({
1044
1026
  where: {
1045
1027
  id: actionId,
1046
1028
  release: {
@@ -1049,42 +1031,17 @@ const createReleaseActionService = ({ strapi: strapi2 }) => {
1049
1031
  $null: true
1050
1032
  }
1051
1033
  }
1052
- }
1034
+ },
1035
+ data: update
1053
1036
  });
1054
- if (!action) {
1037
+ if (!updatedAction) {
1055
1038
  throw new errors.NotFoundError(
1056
1039
  `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1057
1040
  );
1058
1041
  }
1059
- const actionStatus = update.type === "publish" ? await getDraftEntryValidStatus(
1060
- {
1061
- contentType: action.contentType,
1062
- documentId: action.entryDocumentId,
1063
- locale: action.locale
1064
- },
1065
- {
1066
- strapi: strapi2
1067
- }
1068
- ) : true;
1069
- const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
1070
- where: {
1071
- id: actionId,
1072
- release: {
1073
- id: releaseId,
1074
- releasedAt: {
1075
- $null: true
1076
- }
1077
- }
1078
- },
1079
- data: {
1080
- ...update,
1081
- isEntryValid: actionStatus
1082
- }
1083
- });
1084
- getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1085
1042
  return updatedAction;
1086
1043
  },
1087
- async delete(actionId, releaseId) {
1044
+ async deleteAction(actionId, releaseId) {
1088
1045
  const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
1089
1046
  where: {
1090
1047
  id: actionId,
@@ -1101,8 +1058,51 @@ const createReleaseActionService = ({ strapi: strapi2 }) => {
1101
1058
  `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1102
1059
  );
1103
1060
  }
1104
- getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1061
+ this.updateReleaseStatus(releaseId);
1105
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
+ });
1106
1106
  }
1107
1107
  };
1108
1108
  };
@@ -1114,43 +1114,37 @@ class AlreadyOnReleaseError extends errors.ApplicationError {
1114
1114
  }
1115
1115
  const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1116
1116
  async validateUniqueEntry(releaseId, releaseActionArgs) {
1117
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
1118
- where: {
1119
- id: releaseId
1120
- },
1121
- populate: {
1122
- actions: true
1123
- }
1117
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
1118
+ populate: { actions: { populate: { entry: { fields: ["id"] } } } }
1124
1119
  });
1125
1120
  if (!release2) {
1126
1121
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
1127
1122
  }
1128
1123
  const isEntryInRelease = release2.actions.some(
1129
- (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
1130
1125
  );
1131
1126
  if (isEntryInRelease) {
1132
1127
  throw new AlreadyOnReleaseError(
1133
- `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}`
1134
1129
  );
1135
1130
  }
1136
1131
  },
1137
- validateEntryData(contentTypeUid, entryDocumentId) {
1132
+ validateEntryContentType(contentTypeUid) {
1138
1133
  const contentType = strapi2.contentType(contentTypeUid);
1139
1134
  if (!contentType) {
1140
1135
  throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1141
1136
  }
1142
- if (!contentTypes$1.hasDraftAndPublish(contentType)) {
1137
+ if (!contentType.options?.draftAndPublish) {
1143
1138
  throw new errors.ValidationError(
1144
1139
  `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1145
1140
  );
1146
1141
  }
1147
- if (contentType.kind === "collectionType" && !entryDocumentId) {
1148
- throw new errors.ValidationError("Document id is required for collection type");
1149
- }
1150
1142
  },
1151
1143
  async validatePendingReleasesLimit() {
1152
- const featureCfg = strapi2.ee.features.get("cms-content-releases");
1153
- 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
+ );
1154
1148
  const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
1155
1149
  filters: {
1156
1150
  releasedAt: {
@@ -1163,8 +1157,8 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1163
1157
  }
1164
1158
  },
1165
1159
  async validateUniqueNameForPendingRelease(name, id) {
1166
- const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
1167
- where: {
1160
+ const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
1161
+ filters: {
1168
1162
  releasedAt: {
1169
1163
  $null: true
1170
1164
  },
@@ -1193,7 +1187,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1193
1187
  }
1194
1188
  const job = scheduleJob(scheduleDate, async () => {
1195
1189
  try {
1196
- await getService("release", { strapi: strapi2 }).publish(releaseId);
1190
+ await getService("release").publish(releaseId);
1197
1191
  } catch (error) {
1198
1192
  }
1199
1193
  this.cancel(releaseId);
@@ -1235,172 +1229,85 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1235
1229
  }
1236
1230
  };
1237
1231
  };
1238
- const DEFAULT_SETTINGS = {
1239
- defaultTimezone: null
1240
- };
1241
- const createSettingsService = ({ strapi: strapi2 }) => {
1242
- const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
1243
- return {
1244
- async update({ settings: settings2 }) {
1245
- const store = await getStore();
1246
- store.set({ key: "settings", value: settings2 });
1247
- return settings2;
1248
- },
1249
- async find() {
1250
- const store = await getStore();
1251
- const settings2 = await store.get({ key: "settings" });
1252
- return {
1253
- ...DEFAULT_SETTINGS,
1254
- ...settings2 || {}
1255
- };
1256
- }
1257
- };
1258
- };
1259
1232
  const services = {
1260
1233
  release: createReleaseService,
1261
- "release-action": createReleaseActionService,
1262
1234
  "release-validation": createReleaseValidationService,
1263
- scheduling: createSchedulingService,
1264
- settings: createSettingsService
1235
+ scheduling: createSchedulingService
1265
1236
  };
1266
- const RELEASE_SCHEMA = yup$1.object().shape({
1267
- name: yup$1.string().trim().required(),
1268
- scheduledAt: yup$1.string().nullable(),
1269
- timezone: yup$1.string().when("scheduledAt", {
1270
- is: (value) => value !== null && value !== void 0,
1271
- then: yup$1.string().required(),
1272
- otherwise: yup$1.string().nullable()
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()
1273
1255
  })
1274
1256
  }).required().noUnknown();
1275
- const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = yup$1.object().shape({
1276
- contentType: yup$1.string().required(),
1277
- entryDocumentId: yup$1.string().nullable(),
1278
- hasEntryAttached: yup$1.string().nullable(),
1279
- locale: yup$1.string().nullable()
1280
- }).required().noUnknown();
1281
1257
  const validateRelease = validateYupSchema(RELEASE_SCHEMA);
1282
- const validatefindByDocumentAttachedParams = validateYupSchema(
1283
- FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
1284
- );
1285
1258
  const releaseController = {
1286
- /**
1287
- * Find releases based on documents attached or not to the release.
1288
- * If `hasEntryAttached` is true, it will return all releases that have the entry attached.
1289
- * If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
1290
- */
1291
- async findByDocumentAttached(ctx) {
1292
- const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1259
+ async findMany(ctx) {
1260
+ const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
1293
1261
  ability: ctx.state.userAbility,
1294
1262
  model: RELEASE_MODEL_UID
1295
1263
  });
1296
1264
  await permissionsManager.validateQuery(ctx.query);
1297
1265
  const releaseService = getService("release", { strapi });
1298
- const query = await permissionsManager.sanitizeQuery(ctx.query);
1299
- await validatefindByDocumentAttachedParams(query);
1300
- const model = strapi.getModel(query.contentType);
1301
- if (model.kind && model.kind === "singleType") {
1302
- const document = await strapi.db.query(model.uid).findOne({ select: ["documentId"] });
1303
- if (!document) {
1304
- throw new errors.NotFoundError(`No entry found for contentType ${query.contentType}`);
1305
- }
1306
- query.entryDocumentId = document.documentId;
1307
- }
1308
- const { contentType, hasEntryAttached, entryDocumentId, locale } = query;
1309
- const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
1310
- if (isEntryAttached) {
1311
- const releases = await releaseService.findMany({
1312
- where: {
1313
- releasedAt: null,
1314
- actions: {
1315
- contentType,
1316
- entryDocumentId: entryDocumentId ?? null,
1317
- locale: locale ?? null
1318
- }
1319
- },
1320
- populate: {
1321
- actions: {
1322
- fields: ["type"],
1323
- filters: {
1324
- contentType,
1325
- entryDocumentId: entryDocumentId ?? null,
1326
- locale: locale ?? null
1327
- }
1328
- }
1329
- }
1330
- });
1331
- 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 };
1332
1274
  } else {
1333
- const relatedReleases = await releaseService.findMany({
1334
- where: {
1335
- 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,
1336
1281
  actions: {
1337
- contentType,
1338
- entryDocumentId: entryDocumentId ?? null,
1339
- locale: locale ?? null
1282
+ meta: {
1283
+ count: actions.count
1284
+ }
1340
1285
  }
1341
- }
1286
+ };
1342
1287
  });
1343
- const releases = await releaseService.findMany({
1288
+ const pendingReleasesCount = await strapi.query(RELEASE_MODEL_UID).count({
1344
1289
  where: {
1345
- $or: [
1346
- {
1347
- id: {
1348
- $notIn: relatedReleases.map((release2) => release2.id)
1349
- }
1350
- },
1351
- {
1352
- actions: null
1353
- }
1354
- ],
1355
1290
  releasedAt: null
1356
1291
  }
1357
1292
  });
1358
- ctx.body = { data: releases };
1293
+ ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1359
1294
  }
1360
1295
  },
1361
- async findPage(ctx) {
1362
- const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1363
- ability: ctx.state.userAbility,
1364
- model: RELEASE_MODEL_UID
1365
- });
1366
- await permissionsManager.validateQuery(ctx.query);
1367
- const releaseService = getService("release", { strapi });
1368
- const query = await permissionsManager.sanitizeQuery(ctx.query);
1369
- const { results, pagination } = await releaseService.findPage(query);
1370
- const data = results.map((release2) => {
1371
- const { actions, ...releaseData } = release2;
1372
- return {
1373
- ...releaseData,
1374
- actions: {
1375
- meta: {
1376
- count: actions.count
1377
- }
1378
- }
1379
- };
1380
- });
1381
- const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1382
- where: {
1383
- releasedAt: null
1384
- }
1385
- });
1386
- ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1387
- },
1388
1296
  async findOne(ctx) {
1389
1297
  const id = ctx.params.id;
1390
1298
  const releaseService = getService("release", { strapi });
1391
- const releaseActionService = getService("release-action", { strapi });
1392
1299
  const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
1393
1300
  if (!release2) {
1394
1301
  throw new errors.NotFoundError(`Release not found for id: ${id}`);
1395
1302
  }
1396
- const count = await releaseActionService.countActions({
1303
+ const count = await releaseService.countActions({
1397
1304
  filters: {
1398
1305
  release: id
1399
1306
  }
1400
1307
  });
1401
1308
  const sanitizedRelease = {
1402
1309
  ...release2,
1403
- createdBy: release2.createdBy ? strapi.service("admin::user").sanitizeUser(release2.createdBy) : null
1310
+ createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
1404
1311
  };
1405
1312
  const data = {
1406
1313
  ...sanitizedRelease,
@@ -1413,39 +1320,22 @@ const releaseController = {
1413
1320
  ctx.body = { data };
1414
1321
  },
1415
1322
  async mapEntriesToReleases(ctx) {
1416
- const { contentTypeUid, documentIds, locale } = ctx.query;
1417
- if (!contentTypeUid || !documentIds) {
1323
+ const { contentTypeUid, entriesIds } = ctx.query;
1324
+ if (!contentTypeUid || !entriesIds) {
1418
1325
  throw new errors.ValidationError("Missing required query parameters");
1419
1326
  }
1420
1327
  const releaseService = getService("release", { strapi });
1421
- const releasesWithActions = await releaseService.findMany({
1422
- where: {
1423
- releasedAt: null,
1424
- actions: {
1425
- contentType: contentTypeUid,
1426
- entryDocumentId: {
1427
- $in: documentIds
1428
- },
1429
- locale
1430
- }
1431
- },
1432
- populate: {
1433
- actions: true
1434
- }
1435
- });
1328
+ const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
1329
+ contentTypeUid,
1330
+ entriesIds
1331
+ );
1436
1332
  const mappedEntriesInReleases = releasesWithActions.reduce(
1437
1333
  (acc, release2) => {
1438
1334
  release2.actions.forEach((action) => {
1439
- if (action.contentType !== contentTypeUid) {
1440
- return;
1441
- }
1442
- if (locale && action.locale !== locale) {
1443
- return;
1444
- }
1445
- if (!acc[action.entryDocumentId]) {
1446
- acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
1335
+ if (!acc[action.entry.id]) {
1336
+ acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
1447
1337
  } else {
1448
- acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
1338
+ acc[action.entry.id].push({ id: release2.id, name: release2.name });
1449
1339
  }
1450
1340
  });
1451
1341
  return acc;
@@ -1462,13 +1352,13 @@ const releaseController = {
1462
1352
  await validateRelease(releaseArgs);
1463
1353
  const releaseService = getService("release", { strapi });
1464
1354
  const release2 = await releaseService.create(releaseArgs, { user });
1465
- const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1355
+ const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
1466
1356
  ability: ctx.state.userAbility,
1467
1357
  model: RELEASE_MODEL_UID
1468
1358
  });
1469
- ctx.created({
1359
+ ctx.body = {
1470
1360
  data: await permissionsManager.sanitizeOutput(release2)
1471
- });
1361
+ };
1472
1362
  },
1473
1363
  async update(ctx) {
1474
1364
  const user = ctx.state.user;
@@ -1477,7 +1367,7 @@ const releaseController = {
1477
1367
  await validateRelease(releaseArgs);
1478
1368
  const releaseService = getService("release", { strapi });
1479
1369
  const release2 = await releaseService.update(id, releaseArgs, { user });
1480
- const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1370
+ const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
1481
1371
  ability: ctx.state.userAbility,
1482
1372
  model: RELEASE_MODEL_UID
1483
1373
  });
@@ -1494,18 +1384,18 @@ const releaseController = {
1494
1384
  };
1495
1385
  },
1496
1386
  async publish(ctx) {
1387
+ const user = ctx.state.user;
1497
1388
  const id = ctx.params.id;
1498
1389
  const releaseService = getService("release", { strapi });
1499
- const releaseActionService = getService("release-action", { strapi });
1500
- const release2 = await releaseService.publish(id);
1390
+ const release2 = await releaseService.publish(id, { user });
1501
1391
  const [countPublishActions, countUnpublishActions] = await Promise.all([
1502
- releaseActionService.countActions({
1392
+ releaseService.countActions({
1503
1393
  filters: {
1504
1394
  release: id,
1505
1395
  type: "publish"
1506
1396
  }
1507
1397
  }),
1508
- releaseActionService.countActions({
1398
+ releaseService.countActions({
1509
1399
  filters: {
1510
1400
  release: id,
1511
1401
  type: "unpublish"
@@ -1523,30 +1413,27 @@ const releaseController = {
1523
1413
  }
1524
1414
  };
1525
1415
  const RELEASE_ACTION_SCHEMA = yup$1.object().shape({
1526
- contentType: yup$1.string().required(),
1527
- entryDocumentId: yup$1.strapiID(),
1528
- locale: yup$1.string(),
1416
+ entry: yup$1.object().shape({
1417
+ id: yup$1.strapiID().required(),
1418
+ contentType: yup$1.string().required()
1419
+ }).required(),
1529
1420
  type: yup$1.string().oneOf(["publish", "unpublish"]).required()
1530
1421
  });
1531
1422
  const RELEASE_ACTION_UPDATE_SCHEMA = yup$1.object().shape({
1532
1423
  type: yup$1.string().oneOf(["publish", "unpublish"]).required()
1533
1424
  });
1534
- const FIND_MANY_ACTIONS_PARAMS = yup$1.object().shape({
1535
- groupBy: yup$1.string().oneOf(["action", "contentType", "locale"])
1536
- });
1537
1425
  const validateReleaseAction = validateYupSchema(RELEASE_ACTION_SCHEMA);
1538
1426
  const validateReleaseActionUpdateSchema = validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
1539
- const validateFindManyActionsParams = validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
1540
1427
  const releaseActionController = {
1541
1428
  async create(ctx) {
1542
1429
  const releaseId = ctx.params.releaseId;
1543
1430
  const releaseActionArgs = ctx.request.body;
1544
1431
  await validateReleaseAction(releaseActionArgs);
1545
- const releaseActionService = getService("release-action", { strapi });
1546
- const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
1547
- ctx.created({
1432
+ const releaseService = getService("release", { strapi });
1433
+ const releaseAction2 = await releaseService.createAction(releaseId, releaseActionArgs);
1434
+ ctx.body = {
1548
1435
  data: releaseAction2
1549
- });
1436
+ };
1550
1437
  },
1551
1438
  async createMany(ctx) {
1552
1439
  const releaseId = ctx.params.releaseId;
@@ -1554,13 +1441,12 @@ const releaseActionController = {
1554
1441
  await Promise.all(
1555
1442
  releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1556
1443
  );
1557
- const releaseActionService = getService("release-action", { strapi });
1558
1444
  const releaseService = getService("release", { strapi });
1559
1445
  const releaseActions = await strapi.db.transaction(async () => {
1560
1446
  const releaseActions2 = await Promise.all(
1561
1447
  releaseActionsArgs.map(async (releaseActionArgs) => {
1562
1448
  try {
1563
- const action = await releaseActionService.create(releaseId, releaseActionArgs, {
1449
+ const action = await releaseService.createAction(releaseId, releaseActionArgs, {
1564
1450
  disableUpdateReleaseStatus: true
1565
1451
  });
1566
1452
  return action;
@@ -1578,51 +1464,43 @@ const releaseActionController = {
1578
1464
  if (newReleaseActions.length > 0) {
1579
1465
  releaseService.updateReleaseStatus(releaseId);
1580
1466
  }
1581
- ctx.created({
1467
+ ctx.body = {
1582
1468
  data: newReleaseActions,
1583
1469
  meta: {
1584
1470
  entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
1585
1471
  totalEntries: releaseActions.length
1586
1472
  }
1587
- });
1473
+ };
1588
1474
  },
1589
1475
  async findMany(ctx) {
1590
1476
  const releaseId = ctx.params.releaseId;
1591
- const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1477
+ const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
1592
1478
  ability: ctx.state.userAbility,
1593
1479
  model: RELEASE_ACTION_MODEL_UID
1594
1480
  });
1595
- await validateFindManyActionsParams(ctx.query);
1596
- if (ctx.query.groupBy) {
1597
- if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
1598
- ctx.badRequest("Invalid groupBy parameter");
1599
- }
1600
- }
1601
- ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
1602
- delete ctx.query.groupBy;
1603
1481
  const query = await permissionsManager.sanitizeQuery(ctx.query);
1604
- const releaseActionService = getService("release-action", { strapi });
1605
- 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,
1606
1485
  ...query
1607
1486
  });
1608
1487
  const contentTypeOutputSanitizers = results.reduce((acc, action) => {
1609
1488
  if (acc[action.contentType]) {
1610
1489
  return acc;
1611
1490
  }
1612
- const contentTypePermissionsManager = strapi.service("admin::permission").createPermissionsManager({
1491
+ const contentTypePermissionsManager = strapi.admin.services.permission.createPermissionsManager({
1613
1492
  ability: ctx.state.userAbility,
1614
1493
  model: action.contentType
1615
1494
  });
1616
1495
  acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
1617
1496
  return acc;
1618
1497
  }, {});
1619
- const sanitizedResults = await async.map(results, async (action) => ({
1498
+ const sanitizedResults = await mapAsync(results, async (action) => ({
1620
1499
  ...action,
1621
- entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
1500
+ entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1622
1501
  }));
1623
- const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
1624
- const contentTypes2 = releaseActionService.getContentTypeModelsFromActions(results);
1625
- const releaseService = getService("release", { strapi });
1502
+ const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
1503
+ const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
1626
1504
  const components = await releaseService.getAllComponents();
1627
1505
  ctx.body = {
1628
1506
  data: groupedData,
@@ -1638,8 +1516,8 @@ const releaseActionController = {
1638
1516
  const releaseId = ctx.params.releaseId;
1639
1517
  const releaseActionUpdateArgs = ctx.request.body;
1640
1518
  await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
1641
- const releaseActionService = getService("release-action", { strapi });
1642
- const updatedAction = await releaseActionService.update(
1519
+ const releaseService = getService("release", { strapi });
1520
+ const updatedAction = await releaseService.updateAction(
1643
1521
  actionId,
1644
1522
  releaseId,
1645
1523
  releaseActionUpdateArgs
@@ -1651,36 +1529,14 @@ const releaseActionController = {
1651
1529
  async delete(ctx) {
1652
1530
  const actionId = ctx.params.actionId;
1653
1531
  const releaseId = ctx.params.releaseId;
1654
- const releaseActionService = getService("release-action", { strapi });
1655
- const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
1532
+ const releaseService = getService("release", { strapi });
1533
+ const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
1656
1534
  ctx.body = {
1657
1535
  data: deletedReleaseAction
1658
1536
  };
1659
1537
  }
1660
1538
  };
1661
- const SETTINGS_SCHEMA = yup.object().shape({
1662
- defaultTimezone: yup.string().nullable().default(null)
1663
- }).required().noUnknown();
1664
- const validateSettings = validateYupSchema(SETTINGS_SCHEMA);
1665
- const settingsController = {
1666
- async find(ctx) {
1667
- const settingsService = getService("settings", { strapi });
1668
- const settings2 = await settingsService.find();
1669
- ctx.body = { data: settings2 };
1670
- },
1671
- async update(ctx) {
1672
- const settingsBody = ctx.request.body;
1673
- const settings2 = await validateSettings(settingsBody);
1674
- const settingsService = getService("settings", { strapi });
1675
- const updatedSettings = await settingsService.update({ settings: settings2 });
1676
- ctx.body = { data: updatedSettings };
1677
- }
1678
- };
1679
- const controllers = {
1680
- release: releaseController,
1681
- "release-action": releaseActionController,
1682
- settings: settingsController
1683
- };
1539
+ const controllers = { release: releaseController, "release-action": releaseActionController };
1684
1540
  const release = {
1685
1541
  type: "admin",
1686
1542
  routes: [
@@ -1700,22 +1556,6 @@ const release = {
1700
1556
  ]
1701
1557
  }
1702
1558
  },
1703
- {
1704
- method: "GET",
1705
- path: "/getByDocumentAttached",
1706
- handler: "release.findByDocumentAttached",
1707
- config: {
1708
- policies: [
1709
- "admin::isAuthenticatedAdmin",
1710
- {
1711
- name: "admin::hasPermissions",
1712
- config: {
1713
- actions: ["plugin::content-releases.read"]
1714
- }
1715
- }
1716
- ]
1717
- }
1718
- },
1719
1559
  {
1720
1560
  method: "POST",
1721
1561
  path: "/",
@@ -1735,7 +1575,7 @@ const release = {
1735
1575
  {
1736
1576
  method: "GET",
1737
1577
  path: "/",
1738
- handler: "release.findPage",
1578
+ handler: "release.findMany",
1739
1579
  config: {
1740
1580
  policies: [
1741
1581
  "admin::isAuthenticatedAdmin",
@@ -1899,50 +1739,13 @@ const releaseAction = {
1899
1739
  }
1900
1740
  ]
1901
1741
  };
1902
- const settings = {
1903
- type: "admin",
1904
- routes: [
1905
- {
1906
- method: "GET",
1907
- path: "/settings",
1908
- handler: "settings.find",
1909
- config: {
1910
- policies: [
1911
- "admin::isAuthenticatedAdmin",
1912
- {
1913
- name: "admin::hasPermissions",
1914
- config: {
1915
- actions: ["plugin::content-releases.settings.read"]
1916
- }
1917
- }
1918
- ]
1919
- }
1920
- },
1921
- {
1922
- method: "PUT",
1923
- path: "/settings",
1924
- handler: "settings.update",
1925
- config: {
1926
- policies: [
1927
- "admin::isAuthenticatedAdmin",
1928
- {
1929
- name: "admin::hasPermissions",
1930
- config: {
1931
- actions: ["plugin::content-releases.settings.update"]
1932
- }
1933
- }
1934
- ]
1935
- }
1936
- }
1937
- ]
1938
- };
1939
1742
  const routes = {
1940
- settings,
1941
1743
  release,
1942
1744
  "release-action": releaseAction
1943
1745
  };
1746
+ const { features } = require("@strapi/strapi/dist/utils/ee");
1944
1747
  const getPlugin = () => {
1945
- if (strapi.ee.features.isEnabled("cms-content-releases")) {
1748
+ if (features.isEnabled("cms-content-releases")) {
1946
1749
  return {
1947
1750
  register,
1948
1751
  bootstrap,