@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
@@ -71,24 +71,38 @@ const ACTIONS = [
71
71
  displayName: "Add an entry to a release",
72
72
  uid: "create-action",
73
73
  pluginName: "content-releases"
74
+ },
75
+ // Settings
76
+ {
77
+ uid: "settings.read",
78
+ section: "settings",
79
+ displayName: "Read",
80
+ category: "content releases",
81
+ subCategory: "options",
82
+ pluginName: "content-releases"
83
+ },
84
+ {
85
+ uid: "settings.update",
86
+ section: "settings",
87
+ displayName: "Edit",
88
+ category: "content releases",
89
+ subCategory: "options",
90
+ pluginName: "content-releases"
74
91
  }
75
92
  ];
76
93
  const ALLOWED_WEBHOOK_EVENTS = {
77
94
  RELEASES_PUBLISH: "releases.publish"
78
95
  };
79
- const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
96
+ const getService = (name, { strapi: strapi2 }) => {
80
97
  return strapi2.plugin("content-releases").service(name);
81
98
  };
82
- const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
99
+ const getDraftEntryValidStatus = async ({ contentType, documentId, locale }, { strapi: strapi2 }) => {
83
100
  const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
84
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
85
- const entry = await strapi2.db.query(contentTypeUid).findOne({
86
- where: { id: entryId },
87
- populate
88
- });
89
- return entry;
101
+ const populate = await populateBuilderService(contentType).populateDeep(Infinity).build();
102
+ const entry = await getEntry({ contentType, documentId, locale, populate }, { strapi: strapi2 });
103
+ return isEntryValid(contentType, entry, { strapi: strapi2 });
90
104
  };
91
- const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
105
+ const isEntryValid = async (contentTypeUid, entry, { strapi: strapi2 }) => {
92
106
  try {
93
107
  await strapi2.entityValidator.validateEntityCreation(
94
108
  strapi2.getModel(contentTypeUid),
@@ -102,6 +116,38 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } =
102
116
  return false;
103
117
  }
104
118
  };
119
+ const getEntry = async ({
120
+ contentType,
121
+ documentId,
122
+ locale,
123
+ populate,
124
+ status = "draft"
125
+ }, { strapi: strapi2 }) => {
126
+ if (documentId) {
127
+ return strapi2.documents(contentType).findOne({ documentId, locale, populate, status });
128
+ }
129
+ return strapi2.documents(contentType).findFirst({ locale, populate, status });
130
+ };
131
+ const getEntryStatus = async (contentType, entry) => {
132
+ if (entry.publishedAt) {
133
+ return "published";
134
+ }
135
+ const publishedEntry = await strapi.documents(contentType).findOne({
136
+ documentId: entry.documentId,
137
+ locale: entry.locale,
138
+ status: "published",
139
+ fields: ["updatedAt"]
140
+ });
141
+ if (!publishedEntry) {
142
+ return "draft";
143
+ }
144
+ const entryUpdatedAt = new Date(entry.updatedAt).getTime();
145
+ const publishedEntryUpdatedAt = new Date(publishedEntry.updatedAt).getTime();
146
+ if (entryUpdatedAt > publishedEntryUpdatedAt) {
147
+ return "modified";
148
+ }
149
+ return "published";
150
+ };
105
151
  async function deleteActionsOnDisableDraftAndPublish({
106
152
  oldContentTypes,
107
153
  contentTypes: contentTypes2
@@ -147,20 +193,22 @@ async function migrateIsValidAndStatusReleases() {
147
193
  const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
148
194
  for (const action of notValidatedActions) {
149
195
  if (action.entry) {
150
- const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
151
- strapi
196
+ const isEntryValid2 = getDraftEntryValidStatus(
197
+ {
198
+ contentType: action.contentType,
199
+ documentId: action.entryDocumentId,
200
+ locale: action.locale
201
+ },
202
+ { strapi }
203
+ );
204
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
205
+ where: {
206
+ id: action.id
207
+ },
208
+ data: {
209
+ isEntryValid: isEntryValid2
210
+ }
152
211
  });
153
- if (populatedEntry) {
154
- const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
155
- await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
156
- where: {
157
- id: action.id
158
- },
159
- data: {
160
- isEntryValid
161
- }
162
- });
163
- }
164
212
  }
165
213
  }
166
214
  return getService("release", { strapi }).updateReleaseStatus(release2.id);
@@ -204,24 +252,24 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
204
252
  }
205
253
  });
206
254
  await utils.async.map(actions, async (action) => {
207
- if (action.entry && action.release) {
208
- const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
209
- strapi
255
+ if (action.entry && action.release && action.type === "publish") {
256
+ const isEntryValid2 = await getDraftEntryValidStatus(
257
+ {
258
+ contentType: contentTypeUID,
259
+ documentId: action.entryDocumentId,
260
+ locale: action.locale
261
+ },
262
+ { strapi }
263
+ );
264
+ releasesAffected.add(action.release.id);
265
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
266
+ where: {
267
+ id: action.id
268
+ },
269
+ data: {
270
+ isEntryValid: isEntryValid2
271
+ }
210
272
  });
211
- if (populatedEntry) {
212
- const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
213
- strapi
214
- });
215
- releasesAffected.add(action.release.id);
216
- await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
217
- where: {
218
- id: action.id
219
- },
220
- data: {
221
- isEntryValid
222
- }
223
- });
224
- }
225
273
  }
226
274
  });
227
275
  }
@@ -236,13 +284,16 @@ async function disableContentTypeLocalized({ oldContentTypes, contentTypes: cont
236
284
  if (!oldContentTypes) {
237
285
  return;
238
286
  }
287
+ const i18nPlugin = strapi.plugin("i18n");
288
+ if (!i18nPlugin) {
289
+ return;
290
+ }
239
291
  for (const uid in contentTypes2) {
240
292
  if (!oldContentTypes[uid]) {
241
293
  continue;
242
294
  }
243
295
  const oldContentType = oldContentTypes[uid];
244
296
  const contentType = contentTypes2[uid];
245
- const i18nPlugin = strapi.plugin("i18n");
246
297
  const { isLocalizedContentType } = i18nPlugin.service("content-types");
247
298
  if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
248
299
  await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
@@ -255,13 +306,16 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
255
306
  if (!oldContentTypes) {
256
307
  return;
257
308
  }
309
+ const i18nPlugin = strapi.plugin("i18n");
310
+ if (!i18nPlugin) {
311
+ return;
312
+ }
258
313
  for (const uid in contentTypes2) {
259
314
  if (!oldContentTypes[uid]) {
260
315
  continue;
261
316
  }
262
317
  const oldContentType = oldContentTypes[uid];
263
318
  const contentType = contentTypes2[uid];
264
- const i18nPlugin = strapi.plugin("i18n");
265
319
  const { isLocalizedContentType } = i18nPlugin.service("content-types");
266
320
  const { getDefaultLocale } = i18nPlugin.service("locales");
267
321
  if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
@@ -272,9 +326,38 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
272
326
  }
273
327
  }
274
328
  }
329
+ const addEntryDocumentToReleaseActions = {
330
+ name: "content-releases::5.0.0-add-entry-document-id-to-release-actions",
331
+ async up(trx, db) {
332
+ const hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
333
+ if (hasPolymorphicColumn) {
334
+ const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
335
+ "strapi_release_actions",
336
+ "entry_document_id"
337
+ );
338
+ if (!hasEntryDocumentIdColumn) {
339
+ await trx.schema.alterTable("strapi_release_actions", (table) => {
340
+ table.string("entry_document_id");
341
+ });
342
+ }
343
+ const releaseActions = await trx.select("*").from("strapi_release_actions");
344
+ utils.async.map(releaseActions, async (action) => {
345
+ const { target_type, target_id } = action;
346
+ const entry = await db.query(target_type).findOne({ where: { id: target_id } });
347
+ if (entry) {
348
+ await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
349
+ }
350
+ });
351
+ }
352
+ },
353
+ async down() {
354
+ throw new Error("not implemented");
355
+ }
356
+ };
275
357
  const register = async ({ strapi: strapi2 }) => {
276
358
  if (strapi2.ee.features.isEnabled("cms-content-releases")) {
277
- await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
359
+ await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
360
+ strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
278
361
  strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
279
362
  strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
280
363
  }
@@ -284,6 +367,104 @@ const register = async ({ strapi: strapi2 }) => {
284
367
  graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
285
368
  }
286
369
  };
370
+ const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
371
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
372
+ where: {
373
+ actions: {
374
+ contentType,
375
+ entryDocumentId: entry.documentId,
376
+ locale: entry.locale
377
+ }
378
+ }
379
+ });
380
+ const entryStatus = await isEntryValid(contentType, entry, { strapi });
381
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
382
+ where: {
383
+ contentType,
384
+ entryDocumentId: entry.documentId,
385
+ locale: entry.locale
386
+ },
387
+ data: {
388
+ isEntryValid: entryStatus
389
+ }
390
+ });
391
+ for (const release2 of releases) {
392
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
393
+ }
394
+ };
395
+ const deleteActionsAndUpdateReleaseStatus = async (params) => {
396
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
397
+ where: {
398
+ actions: params
399
+ }
400
+ });
401
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
402
+ where: params
403
+ });
404
+ for (const release2 of releases) {
405
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
406
+ }
407
+ };
408
+ const deleteActionsOnDelete = async (ctx, next) => {
409
+ if (ctx.action !== "delete") {
410
+ return next();
411
+ }
412
+ if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
413
+ return next();
414
+ }
415
+ const contentType = ctx.contentType.uid;
416
+ const { documentId, locale } = ctx.params;
417
+ const result = await next();
418
+ if (!result) {
419
+ return result;
420
+ }
421
+ try {
422
+ deleteActionsAndUpdateReleaseStatus({
423
+ contentType,
424
+ entryDocumentId: documentId,
425
+ ...locale !== "*" && { locale }
426
+ });
427
+ } catch (error) {
428
+ strapi.log.error("Error while deleting release actions after delete", {
429
+ error
430
+ });
431
+ }
432
+ return result;
433
+ };
434
+ const updateActionsOnUpdate = async (ctx, next) => {
435
+ if (ctx.action !== "update") {
436
+ return next();
437
+ }
438
+ if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
439
+ return next();
440
+ }
441
+ const contentType = ctx.contentType.uid;
442
+ const result = await next();
443
+ if (!result) {
444
+ return result;
445
+ }
446
+ try {
447
+ updateActionsStatusAndUpdateReleaseStatus(contentType, result);
448
+ } catch (error) {
449
+ strapi.log.error("Error while updating release actions after update", {
450
+ error
451
+ });
452
+ }
453
+ return result;
454
+ };
455
+ const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
456
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
457
+ where: {
458
+ actions: params
459
+ }
460
+ });
461
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
462
+ where: params
463
+ });
464
+ for (const release2 of releases) {
465
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
466
+ }
467
+ };
287
468
  const bootstrap = async ({ strapi: strapi2 }) => {
288
469
  if (strapi2.ee.features.isEnabled("cms-content-releases")) {
289
470
  const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
@@ -291,115 +472,29 @@ const bootstrap = async ({ strapi: strapi2 }) => {
291
472
  );
292
473
  strapi2.db.lifecycles.subscribe({
293
474
  models: contentTypesWithDraftAndPublish,
294
- async afterDelete(event) {
295
- try {
296
- const { model, result } = event;
297
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
298
- const { id } = result;
299
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
300
- where: {
301
- actions: {
302
- target_type: model.uid,
303
- target_id: id
304
- }
305
- }
306
- });
307
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
308
- where: {
309
- target_type: model.uid,
310
- target_id: id
311
- }
312
- });
313
- for (const release2 of releases) {
314
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
315
- }
316
- }
317
- } catch (error) {
318
- strapi2.log.error("Error while deleting release actions after entry delete", { error });
319
- }
320
- },
321
- /**
322
- * deleteMany hook doesn't return the deleted entries ids
323
- * so we need to fetch them before deleting the entries to save the ids on our state
324
- */
325
- async beforeDeleteMany(event) {
326
- const { model, params } = event;
327
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
328
- const { where } = params;
329
- const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
330
- event.state.entriesToDelete = entriesToDelete;
331
- }
332
- },
333
475
  /**
334
- * We delete the release actions related to deleted entries
335
- * We make this only after deleteMany is succesfully executed to avoid errors
476
+ * deleteMany is still used outside documents service, for example when deleting a locale
336
477
  */
337
478
  async afterDeleteMany(event) {
338
479
  try {
339
- const { model, state } = event;
340
- const entriesToDelete = state.entriesToDelete;
341
- if (entriesToDelete) {
342
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
343
- where: {
344
- actions: {
345
- target_type: model.uid,
346
- target_id: {
347
- $in: entriesToDelete.map((entry) => entry.id)
348
- }
349
- }
350
- }
351
- });
352
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
353
- where: {
354
- target_type: model.uid,
355
- target_id: {
356
- $in: entriesToDelete.map((entry) => entry.id)
357
- }
358
- }
480
+ const model = strapi2.getModel(event.model.uid);
481
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
482
+ const { where } = event.params;
483
+ deleteReleasesActionsAndUpdateReleaseStatus({
484
+ contentType: model.uid,
485
+ locale: where.locale ?? null,
486
+ ...where.documentId && { entryDocumentId: where.documentId }
359
487
  });
360
- for (const release2 of releases) {
361
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
362
- }
363
488
  }
364
489
  } catch (error) {
365
490
  strapi2.log.error("Error while deleting release actions after entry deleteMany", {
366
491
  error
367
492
  });
368
493
  }
369
- },
370
- async afterUpdate(event) {
371
- try {
372
- const { model, result } = event;
373
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
374
- const isEntryValid = await getEntryValidStatus(model.uid, result, {
375
- strapi: strapi2
376
- });
377
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
378
- where: {
379
- target_type: model.uid,
380
- target_id: result.id
381
- },
382
- data: {
383
- isEntryValid
384
- }
385
- });
386
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
387
- where: {
388
- actions: {
389
- target_type: model.uid,
390
- target_id: result.id
391
- }
392
- }
393
- });
394
- for (const release2 of releases) {
395
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
396
- }
397
- }
398
- } catch (error) {
399
- strapi2.log.error("Error while updating release actions after entry update", { error });
400
- }
401
494
  }
402
495
  });
496
+ strapi2.documents.use(deleteActionsOnDelete);
497
+ strapi2.documents.use(updateActionsOnUpdate);
403
498
  getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
404
499
  strapi2.log.error(
405
500
  "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
@@ -407,7 +502,7 @@ const bootstrap = async ({ strapi: strapi2 }) => {
407
502
  throw err;
408
503
  });
409
504
  Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
410
- strapi2.webhookStore.addAllowedEvent(key, value);
505
+ strapi2.get("webhookStore").addAllowedEvent(key, value);
411
506
  });
412
507
  }
413
508
  };
@@ -491,15 +586,13 @@ const schema = {
491
586
  enum: ["publish", "unpublish"],
492
587
  required: true
493
588
  },
494
- entry: {
495
- type: "relation",
496
- relation: "morphToOne",
497
- configurable: false
498
- },
499
589
  contentType: {
500
590
  type: "string",
501
591
  required: true
502
592
  },
593
+ entryDocumentId: {
594
+ type: "string"
595
+ },
503
596
  locale: {
504
597
  type: "string"
505
598
  },
@@ -521,18 +614,6 @@ const contentTypes = {
521
614
  release: release$1,
522
615
  "release-action": releaseAction$1
523
616
  };
524
- const getGroupName = (queryValue) => {
525
- switch (queryValue) {
526
- case "contentType":
527
- return "contentType.displayName";
528
- case "action":
529
- return "type";
530
- case "locale":
531
- return ___default.default.getOr("No locale", "locale.name");
532
- default:
533
- return "contentType.displayName";
534
- }
535
- };
536
617
  const createReleaseService = ({ strapi: strapi2 }) => {
537
618
  const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
538
619
  strapi2.eventHub.emit(event, {
@@ -541,93 +622,32 @@ const createReleaseService = ({ strapi: strapi2 }) => {
541
622
  release: release2
542
623
  });
543
624
  };
544
- const publishSingleTypeAction = async (uid, actionType, entryId) => {
545
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
546
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
547
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
548
- const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
549
- try {
550
- if (actionType === "publish") {
551
- await entityManagerService.publish(entry, uid);
552
- } else {
553
- await entityManagerService.unpublish(entry, uid);
554
- }
555
- } catch (error) {
556
- if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
557
- ;
558
- else {
559
- throw error;
560
- }
561
- }
562
- };
563
- const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
564
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
565
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
566
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
567
- const entriesToPublish = await strapi2.entityService.findMany(uid, {
568
- filters: {
569
- id: {
570
- $in: entriesToPublishIds
571
- }
572
- },
573
- populate
574
- });
575
- const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
576
- filters: {
577
- id: {
578
- $in: entriestoUnpublishIds
579
- }
580
- },
581
- populate
582
- });
583
- if (entriesToPublish.length > 0) {
584
- await entityManagerService.publishMany(entriesToPublish, uid);
585
- }
586
- if (entriesToUnpublish.length > 0) {
587
- await entityManagerService.unpublishMany(entriesToUnpublish, uid);
588
- }
589
- };
590
625
  const getFormattedActions = async (releaseId) => {
591
626
  const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
592
627
  where: {
593
628
  release: {
594
629
  id: releaseId
595
630
  }
596
- },
597
- populate: {
598
- entry: {
599
- fields: ["id"]
600
- }
601
631
  }
602
632
  });
603
633
  if (actions.length === 0) {
604
634
  throw new utils.errors.ValidationError("No entries to publish");
605
635
  }
606
- const collectionTypeActions = {};
607
- const singleTypeActions = [];
636
+ const formattedActions = {};
608
637
  for (const action of actions) {
609
638
  const contentTypeUid = action.contentType;
610
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
611
- if (!collectionTypeActions[contentTypeUid]) {
612
- collectionTypeActions[contentTypeUid] = {
613
- entriesToPublishIds: [],
614
- entriesToUnpublishIds: []
615
- };
616
- }
617
- if (action.type === "publish") {
618
- collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
619
- } else {
620
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
621
- }
622
- } else {
623
- singleTypeActions.push({
624
- uid: contentTypeUid,
625
- action: action.type,
626
- id: action.entry.id
627
- });
639
+ if (!formattedActions[contentTypeUid]) {
640
+ formattedActions[contentTypeUid] = {
641
+ publish: [],
642
+ unpublish: []
643
+ };
628
644
  }
645
+ formattedActions[contentTypeUid][action.type].push({
646
+ documentId: action.entryDocumentId,
647
+ locale: action.locale
648
+ });
629
649
  }
630
- return { collectionTypeActions, singleTypeActions };
650
+ return formattedActions;
631
651
  };
632
652
  return {
633
653
  async create(releaseData, { user }) {
@@ -674,78 +694,10 @@ const createReleaseService = ({ strapi: strapi2 }) => {
674
694
  }
675
695
  });
676
696
  },
677
- async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
678
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
679
- where: {
680
- actions: {
681
- target_type: contentTypeUid,
682
- target_id: entryId
683
- },
684
- releasedAt: {
685
- $null: true
686
- }
687
- },
688
- populate: {
689
- // Filter the action to get only the content type entry
690
- actions: {
691
- where: {
692
- target_type: contentTypeUid,
693
- target_id: entryId
694
- }
695
- }
696
- }
697
- });
698
- return releases.map((release2) => {
699
- if (release2.actions?.length) {
700
- const [actionForEntry] = release2.actions;
701
- delete release2.actions;
702
- return {
703
- ...release2,
704
- action: actionForEntry
705
- };
706
- }
707
- return release2;
708
- });
709
- },
710
- async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
711
- const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
712
- where: {
713
- releasedAt: {
714
- $null: true
715
- },
716
- actions: {
717
- target_type: contentTypeUid,
718
- target_id: entryId
719
- }
720
- }
721
- });
722
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
723
- where: {
724
- $or: [
725
- {
726
- id: {
727
- $notIn: releasesRelated.map((release2) => release2.id)
728
- }
729
- },
730
- {
731
- actions: null
732
- }
733
- ],
734
- releasedAt: {
735
- $null: true
736
- }
737
- }
738
- });
739
- return releases.map((release2) => {
740
- if (release2.actions?.length) {
741
- const [actionForEntry] = release2.actions;
742
- delete release2.actions;
743
- return {
744
- ...release2,
745
- action: actionForEntry
746
- };
747
- }
748
- return release2;
697
+ findMany(query) {
698
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
699
+ return strapi2.db.query(RELEASE_MODEL_UID).findMany({
700
+ ...dbQuery
749
701
  });
750
702
  },
751
703
  async update(id, releaseData, { user }) {
@@ -781,133 +733,6 @@ const createReleaseService = ({ strapi: strapi2 }) => {
781
733
  strapi2.telemetry.send("didUpdateContentRelease");
782
734
  return updatedRelease;
783
735
  },
784
- async createAction(releaseId, action) {
785
- const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
786
- strapi: strapi2
787
- });
788
- await Promise.all([
789
- validateEntryContentType(action.entry.contentType),
790
- validateUniqueEntry(releaseId, action)
791
- ]);
792
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
793
- if (!release2) {
794
- throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
795
- }
796
- if (release2.releasedAt) {
797
- throw new utils.errors.ValidationError("Release already published");
798
- }
799
- const { entry, type } = action;
800
- const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
801
- const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
802
- const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
803
- data: {
804
- type,
805
- contentType: entry.contentType,
806
- locale: entry.locale,
807
- isEntryValid,
808
- entry: {
809
- id: entry.id,
810
- __type: entry.contentType,
811
- __pivot: { field: "entry" }
812
- },
813
- release: releaseId
814
- },
815
- populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
816
- });
817
- this.updateReleaseStatus(releaseId);
818
- return releaseAction2;
819
- },
820
- async findActions(releaseId, query) {
821
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
822
- where: { id: releaseId },
823
- select: ["id"]
824
- });
825
- if (!release2) {
826
- throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
827
- }
828
- const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
829
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
830
- ...dbQuery,
831
- populate: {
832
- entry: {
833
- populate: "*"
834
- }
835
- },
836
- where: {
837
- release: releaseId
838
- }
839
- });
840
- },
841
- async countActions(query) {
842
- const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
843
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
844
- },
845
- async groupActions(actions, groupBy) {
846
- const contentTypeUids = actions.reduce((acc, action) => {
847
- if (!acc.includes(action.contentType)) {
848
- acc.push(action.contentType);
849
- }
850
- return acc;
851
- }, []);
852
- const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
853
- contentTypeUids
854
- );
855
- const allLocalesDictionary = await this.getLocalesDataForActions();
856
- const formattedData = actions.map((action) => {
857
- const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
858
- return {
859
- ...action,
860
- locale: action.locale ? allLocalesDictionary[action.locale] : null,
861
- contentType: {
862
- displayName,
863
- mainFieldValue: action.entry[mainField],
864
- uid: action.contentType
865
- }
866
- };
867
- });
868
- const groupName = getGroupName(groupBy);
869
- return ___default.default.groupBy(groupName)(formattedData);
870
- },
871
- async getLocalesDataForActions() {
872
- if (!strapi2.plugin("i18n")) {
873
- return {};
874
- }
875
- const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
876
- return allLocales.reduce((acc, locale) => {
877
- acc[locale.code] = { name: locale.name, code: locale.code };
878
- return acc;
879
- }, {});
880
- },
881
- async getContentTypesDataForActions(contentTypesUids) {
882
- const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
883
- const contentTypesData = {};
884
- for (const contentTypeUid of contentTypesUids) {
885
- const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
886
- uid: contentTypeUid
887
- });
888
- contentTypesData[contentTypeUid] = {
889
- mainField: contentTypeConfig.settings.mainField,
890
- displayName: strapi2.getModel(contentTypeUid).info.displayName
891
- };
892
- }
893
- return contentTypesData;
894
- },
895
- getContentTypeModelsFromActions(actions) {
896
- const contentTypeUids = actions.reduce((acc, action) => {
897
- if (!acc.includes(action.contentType)) {
898
- acc.push(action.contentType);
899
- }
900
- return acc;
901
- }, []);
902
- const contentTypeModelsMap = contentTypeUids.reduce(
903
- (acc, contentTypeUid) => {
904
- acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
905
- return acc;
906
- },
907
- {}
908
- );
909
- return contentTypeModelsMap;
910
- },
911
736
  async getAllComponents() {
912
737
  const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
913
738
  const components = await contentManagerComponentsService.findAllComponents();
@@ -973,22 +798,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
973
798
  }
974
799
  try {
975
800
  strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
976
- const { collectionTypeActions, singleTypeActions } = await getFormattedActions(
977
- releaseId
801
+ const formattedActions = await getFormattedActions(releaseId);
802
+ await strapi2.db.transaction(
803
+ async () => Promise.all(
804
+ Object.keys(formattedActions).map(async (contentTypeUid) => {
805
+ const contentType = contentTypeUid;
806
+ const { publish, unpublish } = formattedActions[contentType];
807
+ return Promise.all([
808
+ ...publish.map((params) => strapi2.documents(contentType).publish(params)),
809
+ ...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
810
+ ]);
811
+ })
812
+ )
978
813
  );
979
- await strapi2.db.transaction(async () => {
980
- for (const { uid, action, id } of singleTypeActions) {
981
- await publishSingleTypeAction(uid, action, id);
982
- }
983
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
984
- const uid = contentTypeUid;
985
- await publishCollectionTypeAction(
986
- uid,
987
- collectionTypeActions[uid].entriesToPublishIds,
988
- collectionTypeActions[uid].entriesToUnpublishIds
989
- );
990
- }
991
- });
992
814
  const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
993
815
  where: {
994
816
  id: releaseId
@@ -1018,13 +840,216 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1018
840
  };
1019
841
  }
1020
842
  });
1021
- if (error instanceof Error) {
1022
- throw error;
1023
- }
1024
- return release2;
843
+ if (error instanceof Error) {
844
+ throw error;
845
+ }
846
+ return release2;
847
+ },
848
+ async updateReleaseStatus(releaseId) {
849
+ const releaseActionService = getService("release-action", { strapi: strapi2 });
850
+ const [totalActions, invalidActions] = await Promise.all([
851
+ releaseActionService.countActions({
852
+ filters: {
853
+ release: releaseId
854
+ }
855
+ }),
856
+ releaseActionService.countActions({
857
+ filters: {
858
+ release: releaseId,
859
+ isEntryValid: false
860
+ }
861
+ })
862
+ ]);
863
+ if (totalActions > 0) {
864
+ if (invalidActions > 0) {
865
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
866
+ where: {
867
+ id: releaseId
868
+ },
869
+ data: {
870
+ status: "blocked"
871
+ }
872
+ });
873
+ }
874
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
875
+ where: {
876
+ id: releaseId
877
+ },
878
+ data: {
879
+ status: "ready"
880
+ }
881
+ });
882
+ }
883
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
884
+ where: {
885
+ id: releaseId
886
+ },
887
+ data: {
888
+ status: "empty"
889
+ }
890
+ });
891
+ }
892
+ };
893
+ };
894
+ const getGroupName = (queryValue) => {
895
+ switch (queryValue) {
896
+ case "contentType":
897
+ return "contentType.displayName";
898
+ case "type":
899
+ return "type";
900
+ case "locale":
901
+ return ___default.default.getOr("No locale", "locale.name");
902
+ default:
903
+ return "contentType.displayName";
904
+ }
905
+ };
906
+ const createReleaseActionService = ({ strapi: strapi2 }) => {
907
+ const getLocalesDataForActions = async () => {
908
+ if (!strapi2.plugin("i18n")) {
909
+ return {};
910
+ }
911
+ const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
912
+ return allLocales.reduce((acc, locale) => {
913
+ acc[locale.code] = { name: locale.name, code: locale.code };
914
+ return acc;
915
+ }, {});
916
+ };
917
+ const getContentTypesDataForActions = async (contentTypesUids) => {
918
+ const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
919
+ const contentTypesData = {};
920
+ for (const contentTypeUid of contentTypesUids) {
921
+ const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
922
+ uid: contentTypeUid
923
+ });
924
+ contentTypesData[contentTypeUid] = {
925
+ mainField: contentTypeConfig.settings.mainField,
926
+ displayName: strapi2.getModel(contentTypeUid).info.displayName
927
+ };
928
+ }
929
+ return contentTypesData;
930
+ };
931
+ return {
932
+ async create(releaseId, action) {
933
+ const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
934
+ strapi: strapi2
935
+ });
936
+ await Promise.all([
937
+ validateEntryData(action.contentType, action.entryDocumentId),
938
+ validateUniqueEntry(releaseId, action)
939
+ ]);
940
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
941
+ if (!release2) {
942
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
943
+ }
944
+ if (release2.releasedAt) {
945
+ throw new utils.errors.ValidationError("Release already published");
946
+ }
947
+ const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
948
+ {
949
+ contentType: action.contentType,
950
+ documentId: action.entryDocumentId,
951
+ locale: action.locale
952
+ },
953
+ {
954
+ strapi: strapi2
955
+ }
956
+ ) : true;
957
+ const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
958
+ data: {
959
+ ...action,
960
+ release: release2.id,
961
+ isEntryValid: actionStatus
962
+ },
963
+ populate: { release: { select: ["id"] } }
964
+ });
965
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
966
+ return releaseAction2;
967
+ },
968
+ async findPage(releaseId, query) {
969
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
970
+ where: { id: releaseId },
971
+ select: ["id"]
972
+ });
973
+ if (!release2) {
974
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
975
+ }
976
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
977
+ const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
978
+ ...dbQuery,
979
+ where: {
980
+ release: releaseId
981
+ }
982
+ });
983
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
984
+ const actionsWithEntry = await utils.async.map(actions, async (action) => {
985
+ const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
986
+ const entry = await getEntry(
987
+ {
988
+ contentType: action.contentType,
989
+ documentId: action.entryDocumentId,
990
+ locale: action.locale,
991
+ populate,
992
+ status: action.type === "publish" ? "draft" : "published"
993
+ },
994
+ { strapi: strapi2 }
995
+ );
996
+ return {
997
+ ...action,
998
+ entry,
999
+ status: entry ? await getEntryStatus(action.contentType, entry) : null
1000
+ };
1001
+ });
1002
+ return {
1003
+ results: actionsWithEntry,
1004
+ pagination
1005
+ };
1006
+ },
1007
+ async groupActions(actions, groupBy) {
1008
+ const contentTypeUids = actions.reduce((acc, action) => {
1009
+ if (!acc.includes(action.contentType)) {
1010
+ acc.push(action.contentType);
1011
+ }
1012
+ return acc;
1013
+ }, []);
1014
+ const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
1015
+ const allLocalesDictionary = await getLocalesDataForActions();
1016
+ const formattedData = actions.map((action) => {
1017
+ const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
1018
+ return {
1019
+ ...action,
1020
+ locale: action.locale ? allLocalesDictionary[action.locale] : null,
1021
+ contentType: {
1022
+ displayName,
1023
+ mainFieldValue: action.entry[mainField],
1024
+ uid: action.contentType
1025
+ }
1026
+ };
1027
+ });
1028
+ const groupName = getGroupName(groupBy);
1029
+ return ___default.default.groupBy(groupName)(formattedData);
1025
1030
  },
1026
- async updateAction(actionId, releaseId, update) {
1027
- const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
1031
+ getContentTypeModelsFromActions(actions) {
1032
+ const contentTypeUids = actions.reduce((acc, action) => {
1033
+ if (!acc.includes(action.contentType)) {
1034
+ acc.push(action.contentType);
1035
+ }
1036
+ return acc;
1037
+ }, []);
1038
+ const contentTypeModelsMap = contentTypeUids.reduce(
1039
+ (acc, contentTypeUid) => {
1040
+ acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
1041
+ return acc;
1042
+ },
1043
+ {}
1044
+ );
1045
+ return contentTypeModelsMap;
1046
+ },
1047
+ async countActions(query) {
1048
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
1049
+ return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
1050
+ },
1051
+ async update(actionId, releaseId, update) {
1052
+ const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
1028
1053
  where: {
1029
1054
  id: actionId,
1030
1055
  release: {
@@ -1033,17 +1058,42 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1033
1058
  $null: true
1034
1059
  }
1035
1060
  }
1036
- },
1037
- data: update
1061
+ }
1038
1062
  });
1039
- if (!updatedAction) {
1063
+ if (!action) {
1040
1064
  throw new utils.errors.NotFoundError(
1041
1065
  `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1042
1066
  );
1043
1067
  }
1068
+ const actionStatus = update.type === "publish" ? getDraftEntryValidStatus(
1069
+ {
1070
+ contentType: action.contentType,
1071
+ documentId: action.entryDocumentId,
1072
+ locale: action.locale
1073
+ },
1074
+ {
1075
+ strapi: strapi2
1076
+ }
1077
+ ) : true;
1078
+ const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
1079
+ where: {
1080
+ id: actionId,
1081
+ release: {
1082
+ id: releaseId,
1083
+ releasedAt: {
1084
+ $null: true
1085
+ }
1086
+ }
1087
+ },
1088
+ data: {
1089
+ ...update,
1090
+ isEntryValid: actionStatus
1091
+ }
1092
+ });
1093
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1044
1094
  return updatedAction;
1045
1095
  },
1046
- async deleteAction(actionId, releaseId) {
1096
+ async delete(actionId, releaseId) {
1047
1097
  const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
1048
1098
  where: {
1049
1099
  id: actionId,
@@ -1060,51 +1110,8 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1060
1110
  `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1061
1111
  );
1062
1112
  }
1063
- this.updateReleaseStatus(releaseId);
1113
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1064
1114
  return deletedAction;
1065
- },
1066
- async updateReleaseStatus(releaseId) {
1067
- const [totalActions, invalidActions] = await Promise.all([
1068
- this.countActions({
1069
- filters: {
1070
- release: releaseId
1071
- }
1072
- }),
1073
- this.countActions({
1074
- filters: {
1075
- release: releaseId,
1076
- isEntryValid: false
1077
- }
1078
- })
1079
- ]);
1080
- if (totalActions > 0) {
1081
- if (invalidActions > 0) {
1082
- return strapi2.db.query(RELEASE_MODEL_UID).update({
1083
- where: {
1084
- id: releaseId
1085
- },
1086
- data: {
1087
- status: "blocked"
1088
- }
1089
- });
1090
- }
1091
- return strapi2.db.query(RELEASE_MODEL_UID).update({
1092
- where: {
1093
- id: releaseId
1094
- },
1095
- data: {
1096
- status: "ready"
1097
- }
1098
- });
1099
- }
1100
- return strapi2.db.query(RELEASE_MODEL_UID).update({
1101
- where: {
1102
- id: releaseId
1103
- },
1104
- data: {
1105
- status: "empty"
1106
- }
1107
- });
1108
1115
  }
1109
1116
  };
1110
1117
  };
@@ -1120,33 +1127,39 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1120
1127
  where: {
1121
1128
  id: releaseId
1122
1129
  },
1123
- populate: { actions: { populate: { entry: { select: ["id"] } } } }
1130
+ populate: {
1131
+ actions: true
1132
+ }
1124
1133
  });
1125
1134
  if (!release2) {
1126
1135
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
1127
1136
  }
1128
1137
  const isEntryInRelease = release2.actions.some(
1129
- (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1138
+ (action) => Number(action.entryDocumentId) === Number(releaseActionArgs.entryDocumentId) && action.contentType === releaseActionArgs.contentType && action.locale === releaseActionArgs.locale
1130
1139
  );
1131
1140
  if (isEntryInRelease) {
1132
1141
  throw new AlreadyOnReleaseError(
1133
- `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1142
+ `Entry with documentId ${releaseActionArgs.entryDocumentId} ${releaseActionArgs.locale ? `(${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
1134
1143
  );
1135
1144
  }
1136
1145
  },
1137
- validateEntryContentType(contentTypeUid) {
1146
+ validateEntryData(contentTypeUid, entryDocumentId) {
1138
1147
  const contentType = strapi2.contentType(contentTypeUid);
1139
1148
  if (!contentType) {
1140
1149
  throw new utils.errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1141
1150
  }
1142
- if (!contentType.options?.draftAndPublish) {
1151
+ if (!utils.contentTypes.hasDraftAndPublish(contentType)) {
1143
1152
  throw new utils.errors.ValidationError(
1144
1153
  `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1145
1154
  );
1146
1155
  }
1156
+ if (contentType.kind === "collectionType" && !entryDocumentId) {
1157
+ throw new utils.errors.ValidationError("Document id is required for collection type");
1158
+ }
1147
1159
  },
1148
1160
  async validatePendingReleasesLimit() {
1149
- const maximumPendingReleases = strapi2.ee.features.get("cms-content-releases")?.options?.maximumReleases || 3;
1161
+ const featureCfg = strapi2.ee.features.get("cms-content-releases");
1162
+ const maximumPendingReleases = typeof featureCfg === "object" && featureCfg?.options?.maximumReleases || 3;
1150
1163
  const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
1151
1164
  filters: {
1152
1165
  releasedAt: {
@@ -1189,7 +1202,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1189
1202
  }
1190
1203
  const job = nodeSchedule.scheduleJob(scheduleDate, async () => {
1191
1204
  try {
1192
- await getService("release").publish(releaseId);
1205
+ await getService("release", { strapi: strapi2 }).publish(releaseId);
1193
1206
  } catch (error) {
1194
1207
  }
1195
1208
  this.cancel(releaseId);
@@ -1231,10 +1244,33 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1231
1244
  }
1232
1245
  };
1233
1246
  };
1247
+ const DEFAULT_SETTINGS = {
1248
+ defaultTimezone: null
1249
+ };
1250
+ const createSettingsService = ({ strapi: strapi2 }) => {
1251
+ const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
1252
+ return {
1253
+ async update({ settings: settings2 }) {
1254
+ const store = await getStore();
1255
+ store.set({ key: "settings", value: settings2 });
1256
+ return settings2;
1257
+ },
1258
+ async find() {
1259
+ const store = await getStore();
1260
+ const settings2 = await store.get({ key: "settings" });
1261
+ return {
1262
+ ...DEFAULT_SETTINGS,
1263
+ ...settings2 || {}
1264
+ };
1265
+ }
1266
+ };
1267
+ };
1234
1268
  const services = {
1235
1269
  release: createReleaseService,
1270
+ "release-action": createReleaseActionService,
1236
1271
  "release-validation": createReleaseValidationService,
1237
- scheduling: createSchedulingService
1272
+ scheduling: createSchedulingService,
1273
+ settings: createSettingsService
1238
1274
  };
1239
1275
  const RELEASE_SCHEMA = yup__namespace.object().shape({
1240
1276
  name: yup__namespace.string().trim().required(),
@@ -1256,60 +1292,125 @@ const RELEASE_SCHEMA = yup__namespace.object().shape({
1256
1292
  otherwise: yup__namespace.string().nullable()
1257
1293
  })
1258
1294
  }).required().noUnknown();
1295
+ const SETTINGS_SCHEMA = yup__namespace.object().shape({
1296
+ defaultTimezone: yup__namespace.string().nullable().default(null)
1297
+ }).required().noUnknown();
1298
+ const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = utils.yup.object().shape({
1299
+ contentType: utils.yup.string().required(),
1300
+ entryDocumentId: utils.yup.string().nullable(),
1301
+ hasEntryAttached: utils.yup.string().nullable(),
1302
+ locale: utils.yup.string().nullable()
1303
+ }).required().noUnknown();
1259
1304
  const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
1305
+ const validatefindByDocumentAttachedParams = utils.validateYupSchema(
1306
+ FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
1307
+ );
1260
1308
  const releaseController = {
1261
- async findMany(ctx) {
1262
- const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
1309
+ /**
1310
+ * Find releases based on documents attached or not to the release.
1311
+ * If `hasEntryAttached` is true, it will return all releases that have the entry attached.
1312
+ * If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
1313
+ */
1314
+ async findByDocumentAttached(ctx) {
1315
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1263
1316
  ability: ctx.state.userAbility,
1264
1317
  model: RELEASE_MODEL_UID
1265
1318
  });
1266
1319
  await permissionsManager.validateQuery(ctx.query);
1267
1320
  const releaseService = getService("release", { strapi });
1268
- const isFindManyForContentTypeEntry = Boolean(ctx.query?.contentTypeUid && ctx.query?.entryId);
1269
- if (isFindManyForContentTypeEntry) {
1270
- const query = await permissionsManager.sanitizeQuery(ctx.query);
1271
- const contentTypeUid = query.contentTypeUid;
1272
- const entryId = query.entryId;
1273
- const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
1274
- const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
1275
- ctx.body = { data };
1321
+ const query = await permissionsManager.sanitizeQuery(ctx.query);
1322
+ await validatefindByDocumentAttachedParams(query);
1323
+ const { contentType, entryDocumentId, hasEntryAttached, locale } = query;
1324
+ const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
1325
+ if (isEntryAttached) {
1326
+ const releases = await releaseService.findMany({
1327
+ where: {
1328
+ releasedAt: null,
1329
+ actions: {
1330
+ contentType,
1331
+ entryDocumentId: entryDocumentId ?? null,
1332
+ locale: locale ?? null
1333
+ }
1334
+ },
1335
+ populate: {
1336
+ actions: {
1337
+ fields: ["type"]
1338
+ }
1339
+ }
1340
+ });
1341
+ ctx.body = { data: releases };
1276
1342
  } else {
1277
- const query = await permissionsManager.sanitizeQuery(ctx.query);
1278
- const { results, pagination } = await releaseService.findPage(query);
1279
- const data = results.map((release2) => {
1280
- const { actions, ...releaseData } = release2;
1281
- return {
1282
- ...releaseData,
1343
+ const relatedReleases = await releaseService.findMany({
1344
+ where: {
1345
+ releasedAt: null,
1283
1346
  actions: {
1284
- meta: {
1285
- count: actions.count
1286
- }
1347
+ contentType,
1348
+ entryDocumentId: entryDocumentId ?? null,
1349
+ locale: locale ?? null
1287
1350
  }
1288
- };
1351
+ }
1289
1352
  });
1290
- const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1353
+ const releases = await releaseService.findMany({
1291
1354
  where: {
1355
+ $or: [
1356
+ {
1357
+ id: {
1358
+ $notIn: relatedReleases.map((release2) => release2.id)
1359
+ }
1360
+ },
1361
+ {
1362
+ actions: null
1363
+ }
1364
+ ],
1292
1365
  releasedAt: null
1293
1366
  }
1294
1367
  });
1295
- ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1368
+ ctx.body = { data: releases };
1296
1369
  }
1297
1370
  },
1371
+ async findPage(ctx) {
1372
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1373
+ ability: ctx.state.userAbility,
1374
+ model: RELEASE_MODEL_UID
1375
+ });
1376
+ await permissionsManager.validateQuery(ctx.query);
1377
+ const releaseService = getService("release", { strapi });
1378
+ const query = await permissionsManager.sanitizeQuery(ctx.query);
1379
+ const { results, pagination } = await releaseService.findPage(query);
1380
+ const data = results.map((release2) => {
1381
+ const { actions, ...releaseData } = release2;
1382
+ return {
1383
+ ...releaseData,
1384
+ actions: {
1385
+ meta: {
1386
+ count: actions.count
1387
+ }
1388
+ }
1389
+ };
1390
+ });
1391
+ const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1392
+ where: {
1393
+ releasedAt: null
1394
+ }
1395
+ });
1396
+ ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1397
+ },
1298
1398
  async findOne(ctx) {
1299
1399
  const id = ctx.params.id;
1300
1400
  const releaseService = getService("release", { strapi });
1401
+ const releaseActionService = getService("release-action", { strapi });
1301
1402
  const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
1302
1403
  if (!release2) {
1303
1404
  throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
1304
1405
  }
1305
- const count = await releaseService.countActions({
1406
+ const count = await releaseActionService.countActions({
1306
1407
  filters: {
1307
1408
  release: id
1308
1409
  }
1309
1410
  });
1310
1411
  const sanitizedRelease = {
1311
1412
  ...release2,
1312
- createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
1413
+ createdBy: release2.createdBy ? strapi.service("admin::user").sanitizeUser(release2.createdBy) : null
1313
1414
  };
1314
1415
  const data = {
1315
1416
  ...sanitizedRelease,
@@ -1321,19 +1422,56 @@ const releaseController = {
1321
1422
  };
1322
1423
  ctx.body = { data };
1323
1424
  },
1425
+ /* @TODO: Migrate to new api
1426
+ async mapEntriesToReleases(ctx: Koa.Context) {
1427
+ const { contentTypeUid, entriesIds } = ctx.query;
1428
+
1429
+ if (!contentTypeUid || !entriesIds) {
1430
+ throw new errors.ValidationError('Missing required query parameters');
1431
+ }
1432
+
1433
+ const releaseService = getService('release', { strapi });
1434
+
1435
+ const releasesWithActions = await releaseService.findMany(
1436
+ contentTypeUid,
1437
+ entriesIds
1438
+ );
1439
+
1440
+ const mappedEntriesInReleases = releasesWithActions.reduce(
1441
+ // TODO: Fix for v5 removed mappedEntriedToRelease
1442
+ (acc: MapEntriesToReleases.Response['data'], release: Release) => {
1443
+ release.actions.forEach((action) => {
1444
+ if (!acc[action.entry.id]) {
1445
+ acc[action.entry.id] = [{ id: release.id, name: release.name }];
1446
+ } else {
1447
+ acc[action.entry.id].push({ id: release.id, name: release.name });
1448
+ }
1449
+ });
1450
+
1451
+ return acc;
1452
+ },
1453
+ // TODO: Fix for v5 removed mappedEntriedToRelease
1454
+ {} as MapEntriesToReleases.Response['data']
1455
+ );
1456
+
1457
+ ctx.body = {
1458
+ data: mappedEntriesInReleases,
1459
+ };
1460
+ },
1461
+ */
1324
1462
  async create(ctx) {
1325
1463
  const user = ctx.state.user;
1326
1464
  const releaseArgs = ctx.request.body;
1327
1465
  await validateRelease(releaseArgs);
1328
1466
  const releaseService = getService("release", { strapi });
1329
1467
  const release2 = await releaseService.create(releaseArgs, { user });
1330
- const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
1468
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1331
1469
  ability: ctx.state.userAbility,
1332
1470
  model: RELEASE_MODEL_UID
1333
1471
  });
1334
- ctx.body = {
1472
+ ctx.created({
1335
1473
  data: await permissionsManager.sanitizeOutput(release2)
1336
- };
1474
+ });
1337
1475
  },
1338
1476
  async update(ctx) {
1339
1477
  const user = ctx.state.user;
@@ -1342,7 +1480,7 @@ const releaseController = {
1342
1480
  await validateRelease(releaseArgs);
1343
1481
  const releaseService = getService("release", { strapi });
1344
1482
  const release2 = await releaseService.update(id, releaseArgs, { user });
1345
- const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
1483
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1346
1484
  ability: ctx.state.userAbility,
1347
1485
  model: RELEASE_MODEL_UID
1348
1486
  });
@@ -1359,18 +1497,18 @@ const releaseController = {
1359
1497
  };
1360
1498
  },
1361
1499
  async publish(ctx) {
1362
- const user = ctx.state.user;
1363
1500
  const id = ctx.params.id;
1364
1501
  const releaseService = getService("release", { strapi });
1365
- const release2 = await releaseService.publish(id, { user });
1502
+ const releaseActionService = getService("release-action", { strapi });
1503
+ const release2 = await releaseService.publish(id);
1366
1504
  const [countPublishActions, countUnpublishActions] = await Promise.all([
1367
- releaseService.countActions({
1505
+ releaseActionService.countActions({
1368
1506
  filters: {
1369
1507
  release: id,
1370
1508
  type: "publish"
1371
1509
  }
1372
1510
  }),
1373
- releaseService.countActions({
1511
+ releaseActionService.countActions({
1374
1512
  filters: {
1375
1513
  release: id,
1376
1514
  type: "unpublish"
@@ -1388,42 +1526,47 @@ const releaseController = {
1388
1526
  }
1389
1527
  };
1390
1528
  const RELEASE_ACTION_SCHEMA = utils.yup.object().shape({
1391
- entry: utils.yup.object().shape({
1392
- id: utils.yup.strapiID().required(),
1393
- contentType: utils.yup.string().required()
1394
- }).required(),
1529
+ contentType: utils.yup.string().required(),
1530
+ entryDocumentId: utils.yup.strapiID(),
1531
+ locale: utils.yup.string(),
1395
1532
  type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
1396
1533
  });
1397
1534
  const RELEASE_ACTION_UPDATE_SCHEMA = utils.yup.object().shape({
1398
1535
  type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
1399
1536
  });
1537
+ const FIND_MANY_ACTIONS_PARAMS = utils.yup.object().shape({
1538
+ groupBy: utils.yup.string().oneOf(["action", "contentType", "locale"])
1539
+ });
1400
1540
  const validateReleaseAction = utils.validateYupSchema(RELEASE_ACTION_SCHEMA);
1401
1541
  const validateReleaseActionUpdateSchema = utils.validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
1542
+ const validateFindManyActionsParams = utils.validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
1402
1543
  const releaseActionController = {
1403
1544
  async create(ctx) {
1404
1545
  const releaseId = ctx.params.releaseId;
1405
1546
  const releaseActionArgs = ctx.request.body;
1406
1547
  await validateReleaseAction(releaseActionArgs);
1407
- const releaseService = getService("release", { strapi });
1408
- const releaseAction2 = await releaseService.createAction(releaseId, releaseActionArgs);
1409
- ctx.body = {
1548
+ const releaseActionService = getService("release-action", { strapi });
1549
+ const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
1550
+ ctx.created({
1410
1551
  data: releaseAction2
1411
- };
1552
+ });
1412
1553
  },
1413
- async createMany(ctx) {
1414
- const releaseId = ctx.params.releaseId;
1415
- const releaseActionsArgs = ctx.request.body;
1554
+ /*
1555
+ async createMany(ctx: Koa.Context) {
1556
+ const releaseId: CreateManyReleaseActions.Request['params']['releaseId'] = ctx.params.releaseId;
1557
+ const releaseActionsArgs = ctx.request.body as CreateManyReleaseActions.Request['body'];
1416
1558
  await Promise.all(
1417
1559
  releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1418
1560
  );
1419
- const releaseService = getService("release", { strapi });
1561
+ const releaseActionService = getService('release-action', { strapi });
1420
1562
  const releaseActions = await strapi.db.transaction(async () => {
1421
- const releaseActions2 = await Promise.all(
1563
+ const releaseActions = await Promise.all(
1422
1564
  releaseActionsArgs.map(async (releaseActionArgs) => {
1423
1565
  try {
1424
- const action = await releaseService.createAction(releaseId, releaseActionArgs);
1566
+ const action = await releaseActionService.create(releaseId, releaseActionArgs);
1425
1567
  return action;
1426
1568
  } catch (error) {
1569
+ // If the entry is already in the release, we don't want to throw an error, so we catch and ignore it
1427
1570
  if (error instanceof AlreadyOnReleaseError) {
1428
1571
  return null;
1429
1572
  }
@@ -1431,34 +1574,42 @@ const releaseActionController = {
1431
1574
  }
1432
1575
  })
1433
1576
  );
1434
- return releaseActions2;
1577
+ return releaseActions;
1435
1578
  });
1436
1579
  const newReleaseActions = releaseActions.filter((action) => action !== null);
1437
- ctx.body = {
1580
+ ctx.created({
1438
1581
  data: newReleaseActions,
1439
1582
  meta: {
1440
1583
  entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
1441
- totalEntries: releaseActions.length
1442
- }
1443
- };
1584
+ totalEntries: releaseActions.length,
1585
+ },
1586
+ });
1444
1587
  },
1588
+ */
1445
1589
  async findMany(ctx) {
1446
1590
  const releaseId = ctx.params.releaseId;
1447
- const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
1591
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1448
1592
  ability: ctx.state.userAbility,
1449
1593
  model: RELEASE_ACTION_MODEL_UID
1450
1594
  });
1595
+ await validateFindManyActionsParams(ctx.query);
1596
+ if (ctx.query.groupBy) {
1597
+ if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
1598
+ ctx.badRequest("Invalid groupBy parameter");
1599
+ }
1600
+ }
1601
+ ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
1602
+ delete ctx.query.groupBy;
1451
1603
  const query = await permissionsManager.sanitizeQuery(ctx.query);
1452
- const releaseService = getService("release", { strapi });
1453
- const { results, pagination } = await releaseService.findActions(releaseId, {
1454
- sort: query.groupBy === "action" ? "type" : query.groupBy,
1604
+ const releaseActionService = getService("release-action", { strapi });
1605
+ const { results, pagination } = await releaseActionService.findPage(releaseId, {
1455
1606
  ...query
1456
1607
  });
1457
1608
  const contentTypeOutputSanitizers = results.reduce((acc, action) => {
1458
1609
  if (acc[action.contentType]) {
1459
1610
  return acc;
1460
1611
  }
1461
- const contentTypePermissionsManager = strapi.admin.services.permission.createPermissionsManager({
1612
+ const contentTypePermissionsManager = strapi.service("admin::permission").createPermissionsManager({
1462
1613
  ability: ctx.state.userAbility,
1463
1614
  model: action.contentType
1464
1615
  });
@@ -1467,10 +1618,11 @@ const releaseActionController = {
1467
1618
  }, {});
1468
1619
  const sanitizedResults = await utils.async.map(results, async (action) => ({
1469
1620
  ...action,
1470
- entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1621
+ entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
1471
1622
  }));
1472
- const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
1473
- const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
1623
+ const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
1624
+ const contentTypes2 = releaseActionService.getContentTypeModelsFromActions(results);
1625
+ const releaseService = getService("release", { strapi });
1474
1626
  const components = await releaseService.getAllComponents();
1475
1627
  ctx.body = {
1476
1628
  data: groupedData,
@@ -1486,8 +1638,8 @@ const releaseActionController = {
1486
1638
  const releaseId = ctx.params.releaseId;
1487
1639
  const releaseActionUpdateArgs = ctx.request.body;
1488
1640
  await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
1489
- const releaseService = getService("release", { strapi });
1490
- const updatedAction = await releaseService.updateAction(
1641
+ const releaseActionService = getService("release-action", { strapi });
1642
+ const updatedAction = await releaseActionService.update(
1491
1643
  actionId,
1492
1644
  releaseId,
1493
1645
  releaseActionUpdateArgs
@@ -1499,17 +1651,70 @@ const releaseActionController = {
1499
1651
  async delete(ctx) {
1500
1652
  const actionId = ctx.params.actionId;
1501
1653
  const releaseId = ctx.params.releaseId;
1502
- const releaseService = getService("release", { strapi });
1503
- const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
1654
+ const releaseActionService = getService("release-action", { strapi });
1655
+ const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
1504
1656
  ctx.body = {
1505
1657
  data: deletedReleaseAction
1506
1658
  };
1507
1659
  }
1508
1660
  };
1509
- const controllers = { release: releaseController, "release-action": releaseActionController };
1661
+ const validateSettings = utils.validateYupSchema(SETTINGS_SCHEMA);
1662
+ const settingsController = {
1663
+ async find(ctx) {
1664
+ const settingsService = getService("settings", { strapi });
1665
+ const settings2 = await settingsService.find();
1666
+ ctx.body = { data: settings2 };
1667
+ },
1668
+ async update(ctx) {
1669
+ const settingsBody = ctx.request.body;
1670
+ const settings2 = await validateSettings(settingsBody);
1671
+ const settingsService = getService("settings", { strapi });
1672
+ const updatedSettings = await settingsService.update({ settings: settings2 });
1673
+ ctx.body = { data: updatedSettings };
1674
+ }
1675
+ };
1676
+ const controllers = {
1677
+ release: releaseController,
1678
+ "release-action": releaseActionController,
1679
+ settings: settingsController
1680
+ };
1510
1681
  const release = {
1511
1682
  type: "admin",
1512
1683
  routes: [
1684
+ /*
1685
+ {
1686
+ method: 'GET',
1687
+ path: '/mapEntriesToReleases',
1688
+ handler: 'release.mapEntriesToReleases',
1689
+ config: {
1690
+ policies: [
1691
+ 'admin::isAuthenticatedAdmin',
1692
+ {
1693
+ name: 'admin::hasPermissions',
1694
+ config: {
1695
+ actions: ['plugin::content-releases.read'],
1696
+ },
1697
+ },
1698
+ ],
1699
+ },
1700
+ },
1701
+ */
1702
+ {
1703
+ method: "GET",
1704
+ path: "/getByDocumentAttached",
1705
+ handler: "release.findByDocumentAttached",
1706
+ config: {
1707
+ policies: [
1708
+ "admin::isAuthenticatedAdmin",
1709
+ {
1710
+ name: "admin::hasPermissions",
1711
+ config: {
1712
+ actions: ["plugin::content-releases.read"]
1713
+ }
1714
+ }
1715
+ ]
1716
+ }
1717
+ },
1513
1718
  {
1514
1719
  method: "POST",
1515
1720
  path: "/",
@@ -1529,7 +1734,7 @@ const release = {
1529
1734
  {
1530
1735
  method: "GET",
1531
1736
  path: "/",
1532
- handler: "release.findMany",
1737
+ handler: "release.findPage",
1533
1738
  config: {
1534
1739
  policies: [
1535
1740
  "admin::isAuthenticatedAdmin",
@@ -1627,22 +1832,24 @@ const releaseAction = {
1627
1832
  ]
1628
1833
  }
1629
1834
  },
1835
+ /*
1630
1836
  {
1631
- method: "POST",
1632
- path: "/:releaseId/actions/bulk",
1633
- handler: "release-action.createMany",
1837
+ method: 'POST',
1838
+ path: '/:releaseId/actions/bulk',
1839
+ handler: 'release-action.createMany',
1634
1840
  config: {
1635
1841
  policies: [
1636
- "admin::isAuthenticatedAdmin",
1842
+ 'admin::isAuthenticatedAdmin',
1637
1843
  {
1638
- name: "admin::hasPermissions",
1844
+ name: 'admin::hasPermissions',
1639
1845
  config: {
1640
- actions: ["plugin::content-releases.create-action"]
1641
- }
1642
- }
1643
- ]
1644
- }
1846
+ actions: ['plugin::content-releases.create-action'],
1847
+ },
1848
+ },
1849
+ ],
1850
+ },
1645
1851
  },
1852
+ */
1646
1853
  {
1647
1854
  method: "GET",
1648
1855
  path: "/:releaseId/actions",
@@ -1693,7 +1900,45 @@ const releaseAction = {
1693
1900
  }
1694
1901
  ]
1695
1902
  };
1903
+ const settings = {
1904
+ type: "admin",
1905
+ routes: [
1906
+ {
1907
+ method: "GET",
1908
+ path: "/settings",
1909
+ handler: "settings.find",
1910
+ config: {
1911
+ policies: [
1912
+ "admin::isAuthenticatedAdmin",
1913
+ {
1914
+ name: "admin::hasPermissions",
1915
+ config: {
1916
+ actions: ["plugin::content-releases.settings.read"]
1917
+ }
1918
+ }
1919
+ ]
1920
+ }
1921
+ },
1922
+ {
1923
+ method: "PUT",
1924
+ path: "/settings",
1925
+ handler: "settings.update",
1926
+ config: {
1927
+ policies: [
1928
+ "admin::isAuthenticatedAdmin",
1929
+ {
1930
+ name: "admin::hasPermissions",
1931
+ config: {
1932
+ actions: ["plugin::content-releases.settings.update"]
1933
+ }
1934
+ }
1935
+ ]
1936
+ }
1937
+ }
1938
+ ]
1939
+ };
1696
1940
  const routes = {
1941
+ settings,
1697
1942
  release,
1698
1943
  "release-action": releaseAction
1699
1944
  };