@strapi/content-releases 0.0.0-experimental.950b677f6614f483cde77801d941607d6847a6e3 → 0.0.0-experimental.cae3a5a17d131a6f59673b62d01cfac869ea9cc2

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 (89) hide show
  1. package/dist/_chunks/{App-3f94ba77.mjs → App-_Jj3tWts.mjs} +350 -154
  2. package/dist/_chunks/App-_Jj3tWts.mjs.map +1 -0
  3. package/dist/_chunks/{App-ae9b2380.js → App-iqqoPnBO.js} +347 -151
  4. package/dist/_chunks/App-iqqoPnBO.js.map +1 -0
  5. package/dist/_chunks/{en-13576ce2.js → en-2DuPv5k0.js} +17 -4
  6. package/dist/_chunks/en-2DuPv5k0.js.map +1 -0
  7. package/dist/_chunks/{en-e98d8b57.mjs → en-SOqjCdyh.mjs} +17 -4
  8. package/dist/_chunks/en-SOqjCdyh.mjs.map +1 -0
  9. package/dist/_chunks/{index-40da7f6d.js → index-_lT-gI3M.js} +126 -37
  10. package/dist/_chunks/index-_lT-gI3M.js.map +1 -0
  11. package/dist/_chunks/{index-6ddf4a38.mjs → index-bsuc8ZwZ.mjs} +137 -48
  12. package/dist/_chunks/index-bsuc8ZwZ.mjs.map +1 -0
  13. package/dist/admin/index.js +2 -1
  14. package/dist/admin/index.js.map +1 -1
  15. package/dist/admin/index.mjs +3 -2
  16. package/dist/admin/index.mjs.map +1 -1
  17. package/dist/server/index.js +359 -102
  18. package/dist/server/index.js.map +1 -1
  19. package/dist/server/index.mjs +357 -103
  20. package/dist/server/index.mjs.map +1 -1
  21. package/package.json +13 -11
  22. package/dist/_chunks/App-3f94ba77.mjs.map +0 -1
  23. package/dist/_chunks/App-ae9b2380.js.map +0 -1
  24. package/dist/_chunks/en-13576ce2.js.map +0 -1
  25. package/dist/_chunks/en-e98d8b57.mjs.map +0 -1
  26. package/dist/_chunks/index-40da7f6d.js.map +0 -1
  27. package/dist/_chunks/index-6ddf4a38.mjs.map +0 -1
  28. package/dist/admin/src/components/CMReleasesContainer.d.ts +0 -1
  29. package/dist/admin/src/components/ReleaseActionMenu.d.ts +0 -7
  30. package/dist/admin/src/components/ReleaseActionOptions.d.ts +0 -8
  31. package/dist/admin/src/components/ReleaseModal.d.ts +0 -11
  32. package/dist/admin/src/constants.d.ts +0 -58
  33. package/dist/admin/src/index.d.ts +0 -3
  34. package/dist/admin/src/pages/App.d.ts +0 -1
  35. package/dist/admin/src/pages/ReleaseDetailsPage.d.ts +0 -10
  36. package/dist/admin/src/pages/ReleasesPage.d.ts +0 -11
  37. package/dist/admin/src/pages/tests/mockReleaseDetailsPageData.d.ts +0 -104
  38. package/dist/admin/src/pages/tests/mockReleasesPageData.d.ts +0 -38
  39. package/dist/admin/src/pluginId.d.ts +0 -1
  40. package/dist/admin/src/services/axios.d.ts +0 -29
  41. package/dist/admin/src/services/release.d.ts +0 -348
  42. package/dist/server/src/constants.d.ts +0 -9
  43. package/dist/server/src/constants.d.ts.map +0 -1
  44. package/dist/server/src/content-types/index.d.ts +0 -82
  45. package/dist/server/src/content-types/index.d.ts.map +0 -1
  46. package/dist/server/src/content-types/release/index.d.ts +0 -37
  47. package/dist/server/src/content-types/release/index.d.ts.map +0 -1
  48. package/dist/server/src/content-types/release/schema.d.ts +0 -36
  49. package/dist/server/src/content-types/release/schema.d.ts.map +0 -1
  50. package/dist/server/src/content-types/release-action/index.d.ts +0 -44
  51. package/dist/server/src/content-types/release-action/index.d.ts.map +0 -1
  52. package/dist/server/src/content-types/release-action/schema.d.ts +0 -43
  53. package/dist/server/src/content-types/release-action/schema.d.ts.map +0 -1
  54. package/dist/server/src/controllers/index.d.ts +0 -18
  55. package/dist/server/src/controllers/index.d.ts.map +0 -1
  56. package/dist/server/src/controllers/release-action.d.ts +0 -9
  57. package/dist/server/src/controllers/release-action.d.ts.map +0 -1
  58. package/dist/server/src/controllers/release.d.ts +0 -11
  59. package/dist/server/src/controllers/release.d.ts.map +0 -1
  60. package/dist/server/src/controllers/validation/release-action.d.ts +0 -3
  61. package/dist/server/src/controllers/validation/release-action.d.ts.map +0 -1
  62. package/dist/server/src/controllers/validation/release.d.ts +0 -2
  63. package/dist/server/src/controllers/validation/release.d.ts.map +0 -1
  64. package/dist/server/src/index.d.ts +0 -3749
  65. package/dist/server/src/index.d.ts.map +0 -1
  66. package/dist/server/src/register.d.ts +0 -5
  67. package/dist/server/src/register.d.ts.map +0 -1
  68. package/dist/server/src/routes/index.d.ts +0 -35
  69. package/dist/server/src/routes/index.d.ts.map +0 -1
  70. package/dist/server/src/routes/release-action.d.ts +0 -18
  71. package/dist/server/src/routes/release-action.d.ts.map +0 -1
  72. package/dist/server/src/routes/release.d.ts +0 -18
  73. package/dist/server/src/routes/release.d.ts.map +0 -1
  74. package/dist/server/src/services/index.d.ts +0 -3525
  75. package/dist/server/src/services/index.d.ts.map +0 -1
  76. package/dist/server/src/services/release.d.ts +0 -1779
  77. package/dist/server/src/services/release.d.ts.map +0 -1
  78. package/dist/server/src/services/validation.d.ts +0 -10
  79. package/dist/server/src/services/validation.d.ts.map +0 -1
  80. package/dist/server/src/utils/index.d.ts +0 -4
  81. package/dist/server/src/utils/index.d.ts.map +0 -1
  82. package/dist/shared/contracts/release-actions.d.ts +0 -95
  83. package/dist/shared/contracts/release-actions.d.ts.map +0 -1
  84. package/dist/shared/contracts/releases.d.ts +0 -153
  85. package/dist/shared/contracts/releases.d.ts.map +0 -1
  86. package/dist/shared/types.d.ts +0 -24
  87. package/dist/shared/types.d.ts.map +0 -1
  88. package/dist/shared/validation-schemas.d.ts +0 -2
  89. package/dist/shared/validation-schemas.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;"}
@@ -1,4 +1,4 @@
1
- import { j } from "../_chunks/index-6ddf4a38.mjs";
1
+ import { k } from "../_chunks/index-bsuc8ZwZ.mjs";
2
2
  import "@strapi/helper-plugin";
3
3
  import "@strapi/icons";
4
4
  import "react/jsx-runtime";
@@ -13,7 +13,8 @@ import "react-router-dom";
13
13
  import "yup";
14
14
  import "@reduxjs/toolkit/query/react";
15
15
  import "styled-components";
16
+ import "react-redux";
16
17
  export {
17
- j as default
18
+ k as default
18
19
  };
19
20
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;"}
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  const utils = require("@strapi/utils");
3
+ const lodash = require("lodash");
4
+ const _ = require("lodash/fp");
5
+ const EE = require("@strapi/strapi/dist/utils/ee");
3
6
  const yup = require("yup");
7
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
4
8
  function _interopNamespace(e) {
5
9
  if (e && e.__esModule)
6
10
  return e;
@@ -19,6 +23,8 @@ function _interopNamespace(e) {
19
23
  n.default = e;
20
24
  return Object.freeze(n);
21
25
  }
26
+ const ___default = /* @__PURE__ */ _interopDefault(_);
27
+ const EE__default = /* @__PURE__ */ _interopDefault(EE);
22
28
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
23
29
  const RELEASE_MODEL_UID = "plugin::content-releases.release";
24
30
  const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
@@ -66,10 +72,87 @@ const ACTIONS = [
66
72
  pluginName: "content-releases"
67
73
  }
68
74
  ];
69
- const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
75
+ async function deleteActionsOnDisableDraftAndPublish({
76
+ oldContentTypes,
77
+ contentTypes: contentTypes2
78
+ }) {
79
+ if (!oldContentTypes) {
80
+ return;
81
+ }
82
+ for (const uid in contentTypes2) {
83
+ if (!oldContentTypes[uid]) {
84
+ continue;
85
+ }
86
+ const oldContentType = oldContentTypes[uid];
87
+ const contentType = contentTypes2[uid];
88
+ if (utils.contentTypes.hasDraftAndPublish(oldContentType) && !utils.contentTypes.hasDraftAndPublish(contentType)) {
89
+ await strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: uid }).execute();
90
+ }
91
+ }
92
+ }
93
+ async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
94
+ const deletedContentTypes = lodash.difference(lodash.keys(oldContentTypes), lodash.keys(contentTypes2)) ?? [];
95
+ if (deletedContentTypes.length) {
96
+ await utils.mapAsync(deletedContentTypes, async (deletedContentTypeUID) => {
97
+ return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
98
+ });
99
+ }
100
+ }
101
+ const { features: features$2 } = require("@strapi/strapi/dist/utils/ee");
70
102
  const register = async ({ strapi: strapi2 }) => {
71
- if (features$1.isEnabled("cms-content-releases") && strapi2.features.future.isEnabled("contentReleases")) {
103
+ if (features$2.isEnabled("cms-content-releases")) {
72
104
  await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
105
+ strapi2.hook("strapi::content-types.beforeSync").register(deleteActionsOnDisableDraftAndPublish);
106
+ strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType);
107
+ }
108
+ };
109
+ const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
110
+ const bootstrap = async ({ strapi: strapi2 }) => {
111
+ if (features$1.isEnabled("cms-content-releases")) {
112
+ strapi2.db.lifecycles.subscribe({
113
+ afterDelete(event) {
114
+ const { model, result } = event;
115
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
116
+ const { id } = result;
117
+ strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
118
+ where: {
119
+ target_type: model.uid,
120
+ target_id: id
121
+ }
122
+ });
123
+ }
124
+ },
125
+ /**
126
+ * deleteMany hook doesn't return the deleted entries ids
127
+ * so we need to fetch them before deleting the entries to save the ids on our state
128
+ */
129
+ async beforeDeleteMany(event) {
130
+ const { model, params } = event;
131
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
132
+ const { where } = params;
133
+ const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
134
+ event.state.entriesToDelete = entriesToDelete;
135
+ }
136
+ },
137
+ /**
138
+ * We delete the release actions related to deleted entries
139
+ * We make this only after deleteMany is succesfully executed to avoid errors
140
+ */
141
+ async afterDeleteMany(event) {
142
+ const { model, state } = event;
143
+ const entriesToDelete = state.entriesToDelete;
144
+ if (entriesToDelete) {
145
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
146
+ where: {
147
+ target_type: model.uid,
148
+ target_id: {
149
+ $in: entriesToDelete.map((entry) => entry.id)
150
+ }
151
+ }
152
+ });
153
+ }
154
+ }
155
+ });
73
156
  }
74
157
  };
75
158
  const schema$1 = {
@@ -142,6 +225,9 @@ const schema = {
142
225
  type: "string",
143
226
  required: true
144
227
  },
228
+ locale: {
229
+ type: "string"
230
+ },
145
231
  release: {
146
232
  type: "relation",
147
233
  relation: "manyToOne",
@@ -160,9 +246,29 @@ const contentTypes = {
160
246
  const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
161
247
  return strapi2.plugin("content-releases").service(name);
162
248
  };
249
+ const getGroupName = (queryValue) => {
250
+ switch (queryValue) {
251
+ case "contentType":
252
+ return "contentType.displayName";
253
+ case "action":
254
+ return "type";
255
+ case "locale":
256
+ return ___default.default.getOr("No locale", "locale.name");
257
+ default:
258
+ return "contentType.displayName";
259
+ }
260
+ };
163
261
  const createReleaseService = ({ strapi: strapi2 }) => ({
164
262
  async create(releaseData, { user }) {
165
263
  const releaseWithCreatorFields = await utils.setCreatorFields({ user })(releaseData);
264
+ const { validatePendingReleasesLimit, validateUniqueNameForPendingRelease } = getService(
265
+ "release-validation",
266
+ { strapi: strapi2 }
267
+ );
268
+ await Promise.all([
269
+ validatePendingReleasesLimit(),
270
+ validateUniqueNameForPendingRelease(releaseWithCreatorFields.name)
271
+ ]);
166
272
  return strapi2.entityService.create(RELEASE_MODEL_UID, {
167
273
  data: releaseWithCreatorFields
168
274
  });
@@ -184,51 +290,66 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
184
290
  }
185
291
  });
186
292
  },
187
- async findManyForContentTypeEntry(contentTypeUid, entryId, {
188
- hasEntryAttached
189
- } = {
190
- hasEntryAttached: false
191
- }) {
192
- const whereActions = hasEntryAttached ? {
193
- // Find all Releases where the content type entry is present
194
- actions: {
195
- target_type: contentTypeUid,
196
- target_id: entryId
197
- }
198
- } : {
199
- // Find all Releases where the content type entry is not present
200
- $or: [
201
- {
202
- $not: {
203
- actions: {
204
- target_type: contentTypeUid,
205
- target_id: entryId
206
- }
207
- }
293
+ async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
294
+ const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
295
+ where: {
296
+ actions: {
297
+ target_type: contentTypeUid,
298
+ target_id: entryId
208
299
  },
209
- {
210
- actions: null
300
+ releasedAt: {
301
+ $null: true
211
302
  }
212
- ]
213
- };
214
- const populateAttachedAction = hasEntryAttached ? {
215
- // Filter the action to get only the content type entry
216
- actions: {
217
- where: {
303
+ },
304
+ populate: {
305
+ // Filter the action to get only the content type entry
306
+ actions: {
307
+ where: {
308
+ target_type: contentTypeUid,
309
+ target_id: entryId
310
+ }
311
+ }
312
+ }
313
+ });
314
+ return releases.map((release2) => {
315
+ if (release2.actions?.length) {
316
+ const [actionForEntry] = release2.actions;
317
+ delete release2.actions;
318
+ return {
319
+ ...release2,
320
+ action: actionForEntry
321
+ };
322
+ }
323
+ return release2;
324
+ });
325
+ },
326
+ async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
327
+ const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
328
+ where: {
329
+ releasedAt: {
330
+ $null: true
331
+ },
332
+ actions: {
218
333
  target_type: contentTypeUid,
219
334
  target_id: entryId
220
335
  }
221
336
  }
222
- } : {};
337
+ });
223
338
  const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
224
339
  where: {
225
- ...whereActions,
340
+ $or: [
341
+ {
342
+ id: {
343
+ $notIn: releasesRelated.map((release2) => release2.id)
344
+ }
345
+ },
346
+ {
347
+ actions: null
348
+ }
349
+ ],
226
350
  releasedAt: {
227
351
  $null: true
228
352
  }
229
- },
230
- populate: {
231
- ...populateAttachedAction
232
353
  }
233
354
  });
234
355
  return releases.map((release2) => {
@@ -244,19 +365,23 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
244
365
  });
245
366
  },
246
367
  async update(id, releaseData, { user }) {
247
- const updatedRelease = await utils.setCreatorFields({ user, isEdition: true })(releaseData);
248
- const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
368
+ const releaseWithCreatorFields = await utils.setCreatorFields({ user, isEdition: true })(releaseData);
369
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id);
370
+ if (!release2) {
371
+ throw new utils.errors.NotFoundError(`No release found for id ${id}`);
372
+ }
373
+ if (release2.releasedAt) {
374
+ throw new utils.errors.ValidationError("Release already published");
375
+ }
376
+ const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
249
377
  /*
250
378
  * The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
251
379
  * is not compatible with the type we are passing here: UpdateRelease.Request['body']
252
380
  */
253
381
  // @ts-expect-error see above
254
- data: updatedRelease
382
+ data: releaseWithCreatorFields
255
383
  });
256
- if (!release2) {
257
- throw new utils.errors.NotFoundError(`No release found for id ${id}`);
258
- }
259
- return release2;
384
+ return updatedRelease;
260
385
  },
261
386
  async createAction(releaseId, action) {
262
387
  const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
@@ -266,11 +391,19 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
266
391
  validateEntryContentType(action.entry.contentType),
267
392
  validateUniqueEntry(releaseId, action)
268
393
  ]);
394
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
395
+ if (!release2) {
396
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
397
+ }
398
+ if (release2.releasedAt) {
399
+ throw new utils.errors.ValidationError("Release already published");
400
+ }
269
401
  const { entry, type } = action;
270
402
  return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
271
403
  data: {
272
404
  type,
273
405
  contentType: entry.contentType,
406
+ locale: entry.locale,
274
407
  entry: {
275
408
  id: entry.id,
276
409
  __type: entry.contentType,
@@ -282,14 +415,18 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
282
415
  });
283
416
  },
284
417
  async findActions(releaseId, query) {
285
- const result = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
286
- if (!result) {
418
+ const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
419
+ fields: ["id"]
420
+ });
421
+ if (!release2) {
287
422
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
288
423
  }
289
424
  return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
290
425
  ...query,
291
426
  populate: {
292
- entry: true
427
+ entry: {
428
+ populate: "*"
429
+ }
293
430
  },
294
431
  filters: {
295
432
  release: releaseId
@@ -299,18 +436,43 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
299
436
  async countActions(query) {
300
437
  return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
301
438
  },
302
- async getAllContentTypeUids(releaseId) {
303
- const contentTypesFromReleaseActions = await strapi2.db.queryBuilder(RELEASE_ACTION_MODEL_UID).select("content_type").where({
304
- $and: [
305
- {
306
- release: releaseId
439
+ async groupActions(actions, groupBy) {
440
+ const contentTypeUids = actions.reduce((acc, action) => {
441
+ if (!acc.includes(action.contentType)) {
442
+ acc.push(action.contentType);
443
+ }
444
+ return acc;
445
+ }, []);
446
+ const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
447
+ contentTypeUids
448
+ );
449
+ const allLocalesDictionary = await this.getLocalesDataForActions();
450
+ const formattedData = actions.map((action) => {
451
+ const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
452
+ return {
453
+ ...action,
454
+ locale: action.locale ? allLocalesDictionary[action.locale] : null,
455
+ contentType: {
456
+ displayName,
457
+ mainFieldValue: action.entry[mainField],
458
+ uid: action.contentType
307
459
  }
308
- ]
309
- }).groupBy("content_type").execute();
310
- return contentTypesFromReleaseActions.map(({ contentType: contentTypeUid }) => contentTypeUid);
460
+ };
461
+ });
462
+ const groupName = getGroupName(groupBy);
463
+ return ___default.default.groupBy(groupName)(formattedData);
464
+ },
465
+ async getLocalesDataForActions() {
466
+ if (!strapi2.plugin("i18n")) {
467
+ return {};
468
+ }
469
+ const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
470
+ return allLocales.reduce((acc, locale) => {
471
+ acc[locale.code] = { name: locale.name, code: locale.code };
472
+ return acc;
473
+ }, {});
311
474
  },
312
- async getContentTypesDataForActions(releaseId) {
313
- const contentTypesUids = await this.getAllContentTypeUids(releaseId);
475
+ async getContentTypesDataForActions(contentTypesUids) {
314
476
  const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
315
477
  const contentTypesData = {};
316
478
  for (const contentTypeUid of contentTypesUids) {
@@ -324,6 +486,34 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
324
486
  }
325
487
  return contentTypesData;
326
488
  },
489
+ getContentTypeModelsFromActions(actions) {
490
+ const contentTypeUids = actions.reduce((acc, action) => {
491
+ if (!acc.includes(action.contentType)) {
492
+ acc.push(action.contentType);
493
+ }
494
+ return acc;
495
+ }, []);
496
+ const contentTypeModelsMap = contentTypeUids.reduce(
497
+ (acc, contentTypeUid) => {
498
+ acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
499
+ return acc;
500
+ },
501
+ {}
502
+ );
503
+ return contentTypeModelsMap;
504
+ },
505
+ async getAllComponents() {
506
+ const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
507
+ const components = await contentManagerComponentsService.findAllComponents();
508
+ const componentsMap = components.reduce(
509
+ (acc, component) => {
510
+ acc[component.uid] = component;
511
+ return acc;
512
+ },
513
+ {}
514
+ );
515
+ return componentsMap;
516
+ },
327
517
  async delete(releaseId) {
328
518
  const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
329
519
  populate: {
@@ -358,7 +548,9 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
358
548
  populate: {
359
549
  actions: {
360
550
  populate: {
361
- entry: true
551
+ entry: {
552
+ fields: ["id"]
553
+ }
362
554
  }
363
555
  }
364
556
  }
@@ -378,25 +570,49 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
378
570
  const contentTypeUid = action.contentType;
379
571
  if (!actions[contentTypeUid]) {
380
572
  actions[contentTypeUid] = {
381
- publish: [],
382
- unpublish: []
573
+ entriestoPublishIds: [],
574
+ entriesToUnpublishIds: []
383
575
  };
384
576
  }
385
577
  if (action.type === "publish") {
386
- actions[contentTypeUid].publish.push(action.entry);
578
+ actions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
387
579
  } else {
388
- actions[contentTypeUid].unpublish.push(action.entry);
580
+ actions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
389
581
  }
390
582
  }
391
583
  const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
584
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
392
585
  await strapi2.db.transaction(async () => {
393
586
  for (const contentTypeUid of Object.keys(actions)) {
394
- const { publish, unpublish } = actions[contentTypeUid];
395
- if (publish.length > 0) {
396
- await entityManagerService.publishMany(publish, contentTypeUid);
587
+ const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
588
+ const { entriestoPublishIds, entriesToUnpublishIds } = actions[contentTypeUid];
589
+ const entriesToPublish = await strapi2.entityService.findMany(
590
+ contentTypeUid,
591
+ {
592
+ filters: {
593
+ id: {
594
+ $in: entriestoPublishIds
595
+ }
596
+ },
597
+ populate
598
+ }
599
+ );
600
+ const entriesToUnpublish = await strapi2.entityService.findMany(
601
+ contentTypeUid,
602
+ {
603
+ filters: {
604
+ id: {
605
+ $in: entriesToUnpublishIds
606
+ }
607
+ },
608
+ populate
609
+ }
610
+ );
611
+ if (entriesToPublish.length > 0) {
612
+ await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
397
613
  }
398
- if (unpublish.length > 0) {
399
- await entityManagerService.unpublishMany(unpublish, contentTypeUid);
614
+ if (entriesToUnpublish.length > 0) {
615
+ await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
400
616
  }
401
617
  }
402
618
  });
@@ -415,13 +631,18 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
415
631
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
416
632
  where: {
417
633
  id: actionId,
418
- release: releaseId
634
+ release: {
635
+ id: releaseId,
636
+ releasedAt: {
637
+ $null: true
638
+ }
639
+ }
419
640
  },
420
641
  data: update
421
642
  });
422
643
  if (!updatedAction) {
423
644
  throw new utils.errors.NotFoundError(
424
- `Action with id ${actionId} not found in release with id ${releaseId}`
645
+ `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
425
646
  );
426
647
  }
427
648
  return updatedAction;
@@ -430,12 +651,17 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
430
651
  const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
431
652
  where: {
432
653
  id: actionId,
433
- release: releaseId
654
+ release: {
655
+ id: releaseId,
656
+ releasedAt: {
657
+ $null: true
658
+ }
659
+ }
434
660
  }
435
661
  });
436
662
  if (!deletedAction) {
437
663
  throw new utils.errors.NotFoundError(
438
- `Action with id ${actionId} not found in release with id ${releaseId}`
664
+ `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
439
665
  );
440
666
  }
441
667
  return deletedAction;
@@ -468,9 +694,42 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
468
694
  `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
469
695
  );
470
696
  }
697
+ },
698
+ async validatePendingReleasesLimit() {
699
+ const maximumPendingReleases = (
700
+ // @ts-expect-error - options is not typed into features
701
+ EE__default.default.features.get("cms-content-releases")?.options?.maximumReleases || 3
702
+ );
703
+ const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
704
+ filters: {
705
+ releasedAt: {
706
+ $null: true
707
+ }
708
+ }
709
+ });
710
+ if (pendingReleasesCount >= maximumPendingReleases) {
711
+ throw new utils.errors.ValidationError("You have reached the maximum number of pending releases");
712
+ }
713
+ },
714
+ async validateUniqueNameForPendingRelease(name) {
715
+ const pendingReleases = await strapi2.entityService.findMany(RELEASE_MODEL_UID, {
716
+ filters: {
717
+ releasedAt: {
718
+ $null: true
719
+ },
720
+ name
721
+ }
722
+ });
723
+ const isNameUnique = pendingReleases.length === 0;
724
+ if (!isNameUnique) {
725
+ throw new utils.errors.ValidationError(`Release with name ${name} already exists`);
726
+ }
471
727
  }
472
728
  });
473
- const services = { release: createReleaseService, "release-validation": createReleaseValidationService };
729
+ const services = {
730
+ release: createReleaseService,
731
+ "release-validation": createReleaseValidationService
732
+ };
474
733
  const RELEASE_SCHEMA = yup__namespace.object().shape({
475
734
  name: yup__namespace.string().trim().required()
476
735
  }).required().noUnknown();
@@ -489,9 +748,7 @@ const releaseController = {
489
748
  const contentTypeUid = query.contentTypeUid;
490
749
  const entryId = query.entryId;
491
750
  const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
492
- const data = await releaseService.findManyForContentTypeEntry(contentTypeUid, entryId, {
493
- hasEntryAttached
494
- });
751
+ const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
495
752
  ctx.body = { data };
496
753
  } else {
497
754
  const query = await permissionsManager.sanitizeQuery(ctx.query);
@@ -615,33 +872,34 @@ const releaseActionController = {
615
872
  });
616
873
  const query = await permissionsManager.sanitizeQuery(ctx.query);
617
874
  const releaseService = getService("release", { strapi });
618
- const { results, pagination } = await releaseService.findActions(releaseId, query);
619
- const allReleaseContentTypesDictionary = await releaseService.getContentTypesDataForActions(
620
- releaseId
621
- );
622
- const allLocales = await strapi.plugin("i18n").service("locales").find();
623
- const allLocalesDictionary = allLocales.reduce((acc, locale) => {
624
- acc[locale.code] = { name: locale.name, code: locale.code };
875
+ const { results, pagination } = await releaseService.findActions(releaseId, {
876
+ sort: query.groupBy === "action" ? "type" : query.groupBy,
877
+ ...query
878
+ });
879
+ const contentTypeOutputSanitizers = results.reduce((acc, action) => {
880
+ if (acc[action.contentType]) {
881
+ return acc;
882
+ }
883
+ const contentTypePermissionsManager = strapi.admin.services.permission.createPermissionsManager({
884
+ ability: ctx.state.userAbility,
885
+ model: action.contentType
886
+ });
887
+ acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
625
888
  return acc;
626
889
  }, {});
627
- const data = results.map((action) => {
628
- const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
629
- return {
630
- ...action,
631
- entry: {
632
- id: action.entry.id,
633
- contentType: {
634
- displayName,
635
- mainFieldValue: action.entry[mainField]
636
- },
637
- locale: allLocalesDictionary[action.entry.locale]
638
- }
639
- };
640
- });
890
+ const sanitizedResults = await utils.mapAsync(results, async (action) => ({
891
+ ...action,
892
+ entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
893
+ }));
894
+ const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
895
+ const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
896
+ const components = await releaseService.getAllComponents();
641
897
  ctx.body = {
642
- data,
898
+ data: groupedData,
643
899
  meta: {
644
- pagination
900
+ pagination,
901
+ contentTypes: contentTypes2,
902
+ components
645
903
  }
646
904
  };
647
905
  },
@@ -663,10 +921,8 @@ const releaseActionController = {
663
921
  async delete(ctx) {
664
922
  const actionId = ctx.params.actionId;
665
923
  const releaseId = ctx.params.releaseId;
666
- const deletedReleaseAction = await getService("release", { strapi }).deleteAction(
667
- actionId,
668
- releaseId
669
- );
924
+ const releaseService = getService("release", { strapi });
925
+ const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
670
926
  ctx.body = {
671
927
  data: deletedReleaseAction
672
928
  };
@@ -849,9 +1105,10 @@ const routes = {
849
1105
  };
850
1106
  const { features } = require("@strapi/strapi/dist/utils/ee");
851
1107
  const getPlugin = () => {
852
- if (features.isEnabled("cms-content-releases") && strapi.features.future.isEnabled("contentReleases")) {
1108
+ if (features.isEnabled("cms-content-releases")) {
853
1109
  return {
854
1110
  register,
1111
+ bootstrap,
855
1112
  contentTypes,
856
1113
  services,
857
1114
  controllers,