@strapi/content-releases 5.0.0-beta.1 → 5.0.0-beta.11

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