@strapi/content-releases 0.0.0-experimental.e60ec1829240dae21c1e1d29076681c322288813 → 0.0.0-experimental.eba25ec571b091c6bde1104eb6c753debdf15462

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 (104) hide show
  1. package/LICENSE +17 -1
  2. package/dist/_chunks/{App-BsUSTHVD.mjs → App-FQyYFBJT.mjs} +642 -426
  3. package/dist/_chunks/App-FQyYFBJT.mjs.map +1 -0
  4. package/dist/_chunks/{App-CXRpb2hi.js → App-lx4Ucy9W.js} +680 -466
  5. package/dist/_chunks/App-lx4Ucy9W.js.map +1 -0
  6. package/dist/_chunks/ReleasesSettingsPage-DqBxvJ9i.mjs +178 -0
  7. package/dist/_chunks/ReleasesSettingsPage-DqBxvJ9i.mjs.map +1 -0
  8. package/dist/_chunks/ReleasesSettingsPage-T5VEAV03.js +178 -0
  9. package/dist/_chunks/ReleasesSettingsPage-T5VEAV03.js.map +1 -0
  10. package/dist/_chunks/{en-DtFJ5ViE.js → en-BWPPsSH-.js} +18 -2
  11. package/dist/_chunks/en-BWPPsSH-.js.map +1 -0
  12. package/dist/_chunks/{en-B9Ur3VsE.mjs → en-D9Q4YW03.mjs} +18 -2
  13. package/dist/_chunks/en-D9Q4YW03.mjs.map +1 -0
  14. package/dist/_chunks/{index-DJLIZdZv.mjs → index-CK9G80CL.mjs} +740 -600
  15. package/dist/_chunks/index-CK9G80CL.mjs.map +1 -0
  16. package/dist/_chunks/{index-B6-lic1Q.js → index-Cl3tM1YW.js} +731 -593
  17. package/dist/_chunks/index-Cl3tM1YW.js.map +1 -0
  18. package/dist/_chunks/schemas-BE1LxE9J.js +62 -0
  19. package/dist/_chunks/schemas-BE1LxE9J.js.map +1 -0
  20. package/dist/_chunks/schemas-DdA2ic2U.mjs +44 -0
  21. package/dist/_chunks/schemas-DdA2ic2U.mjs.map +1 -0
  22. package/dist/admin/index.js +1 -1
  23. package/dist/admin/index.mjs +2 -2
  24. package/dist/admin/src/components/EntryValidationPopover.d.ts +13 -0
  25. package/dist/admin/src/components/ReleaseActionMenu.d.ts +3 -3
  26. package/dist/admin/src/components/{CMReleasesContainer.d.ts → ReleaseActionModal.d.ts} +3 -1
  27. package/dist/admin/src/components/ReleaseListCell.d.ts +28 -0
  28. package/dist/admin/src/components/ReleaseModal.d.ts +3 -2
  29. package/dist/admin/src/components/ReleasesPanel.d.ts +3 -0
  30. package/dist/admin/src/constants.d.ts +18 -0
  31. package/dist/admin/src/modules/hooks.d.ts +7 -0
  32. package/dist/admin/src/pages/ReleasesSettingsPage.d.ts +1 -0
  33. package/dist/admin/src/services/release.d.ts +53 -370
  34. package/dist/admin/src/utils/api.d.ts +6 -0
  35. package/dist/admin/src/utils/time.d.ts +9 -0
  36. package/dist/admin/src/validation/schemas.d.ts +6 -0
  37. package/dist/server/index.js +888 -612
  38. package/dist/server/index.js.map +1 -1
  39. package/dist/server/index.mjs +889 -613
  40. package/dist/server/index.mjs.map +1 -1
  41. package/dist/server/src/bootstrap.d.ts.map +1 -1
  42. package/dist/server/src/constants.d.ts +11 -2
  43. package/dist/server/src/constants.d.ts.map +1 -1
  44. package/dist/server/src/content-types/index.d.ts +3 -5
  45. package/dist/server/src/content-types/index.d.ts.map +1 -1
  46. package/dist/server/src/content-types/release-action/index.d.ts +3 -5
  47. package/dist/server/src/content-types/release-action/index.d.ts.map +1 -1
  48. package/dist/server/src/content-types/release-action/schema.d.ts +3 -5
  49. package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -1
  50. package/dist/server/src/controllers/index.d.ts +6 -1
  51. package/dist/server/src/controllers/index.d.ts.map +1 -1
  52. package/dist/server/src/controllers/release-action.d.ts.map +1 -1
  53. package/dist/server/src/controllers/release.d.ts +7 -1
  54. package/dist/server/src/controllers/release.d.ts.map +1 -1
  55. package/dist/server/src/controllers/settings.d.ts +11 -0
  56. package/dist/server/src/controllers/settings.d.ts.map +1 -0
  57. package/dist/server/src/controllers/validation/release-action.d.ts +7 -1
  58. package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -1
  59. package/dist/server/src/controllers/validation/release.d.ts +2 -0
  60. package/dist/server/src/controllers/validation/release.d.ts.map +1 -1
  61. package/dist/server/src/controllers/validation/settings.d.ts +3 -0
  62. package/dist/server/src/controllers/validation/settings.d.ts.map +1 -0
  63. package/dist/server/src/index.d.ts +64 -49
  64. package/dist/server/src/index.d.ts.map +1 -1
  65. package/dist/server/src/middlewares/documents.d.ts +6 -0
  66. package/dist/server/src/middlewares/documents.d.ts.map +1 -0
  67. package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts +9 -0
  68. package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts.map +1 -0
  69. package/dist/server/src/migrations/index.d.ts.map +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.d.ts.map +1 -1
  74. package/dist/server/src/routes/settings.d.ts +18 -0
  75. package/dist/server/src/routes/settings.d.ts.map +1 -0
  76. package/dist/server/src/services/index.d.ts +36 -38
  77. package/dist/server/src/services/index.d.ts.map +1 -1
  78. package/dist/server/src/services/release-action.d.ts +34 -0
  79. package/dist/server/src/services/release-action.d.ts.map +1 -0
  80. package/dist/server/src/services/release.d.ts +6 -41
  81. package/dist/server/src/services/release.d.ts.map +1 -1
  82. package/dist/server/src/services/settings.d.ts +13 -0
  83. package/dist/server/src/services/settings.d.ts.map +1 -0
  84. package/dist/server/src/services/validation.d.ts +1 -1
  85. package/dist/server/src/services/validation.d.ts.map +1 -1
  86. package/dist/server/src/utils/index.d.ts +29 -8
  87. package/dist/server/src/utils/index.d.ts.map +1 -1
  88. package/dist/shared/contracts/release-actions.d.ts +17 -11
  89. package/dist/shared/contracts/release-actions.d.ts.map +1 -1
  90. package/dist/shared/contracts/releases.d.ts +9 -7
  91. package/dist/shared/contracts/releases.d.ts.map +1 -1
  92. package/dist/shared/contracts/settings.d.ts +39 -0
  93. package/dist/shared/contracts/settings.d.ts.map +1 -0
  94. package/package.json +23 -22
  95. package/dist/_chunks/App-BsUSTHVD.mjs.map +0 -1
  96. package/dist/_chunks/App-CXRpb2hi.js.map +0 -1
  97. package/dist/_chunks/en-B9Ur3VsE.mjs.map +0 -1
  98. package/dist/_chunks/en-DtFJ5ViE.js.map +0 -1
  99. package/dist/_chunks/index-B6-lic1Q.js.map +0 -1
  100. package/dist/_chunks/index-DJLIZdZv.mjs.map +0 -1
  101. package/dist/admin/src/services/axios.d.ts +0 -29
  102. package/dist/shared/validation-schemas.d.ts +0 -2
  103. package/dist/shared/validation-schemas.d.ts.map +0 -1
  104. package/strapi-server.js +0 -3
@@ -71,6 +71,23 @@ 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 = {
@@ -79,16 +96,13 @@ const ALLOWED_WEBHOOK_EVENTS = {
79
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 }) => {
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 }) => {
105
+ const isEntryValid = async (contentTypeUid, entry, { strapi: strapi2 }) => {
92
106
  try {
93
107
  await strapi2.entityValidator.validateEntityCreation(
94
108
  strapi2.getModel(contentTypeUid),
@@ -97,11 +111,54 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 }) =
97
111
  // @ts-expect-error - FIXME: entity here is unnecessary
98
112
  entry
99
113
  );
114
+ const workflowsService = strapi2.plugin("review-workflows").service("workflows");
115
+ const workflow = await workflowsService.getAssignedWorkflow(contentTypeUid, {
116
+ populate: "stageRequiredToPublish"
117
+ });
118
+ if (workflow?.stageRequiredToPublish) {
119
+ return entry.strapi_stage.id === workflow.stageRequiredToPublish.id;
120
+ }
100
121
  return true;
101
122
  } catch {
102
123
  return false;
103
124
  }
104
125
  };
126
+ const getEntry = async ({
127
+ contentType,
128
+ documentId,
129
+ locale,
130
+ populate,
131
+ status = "draft"
132
+ }, { strapi: strapi2 }) => {
133
+ if (documentId) {
134
+ const entry = await strapi2.documents(contentType).findOne({ documentId, locale, populate, status });
135
+ if (status === "published" && !entry) {
136
+ return strapi2.documents(contentType).findOne({ documentId, locale, populate, status: "draft" });
137
+ }
138
+ return entry;
139
+ }
140
+ return strapi2.documents(contentType).findFirst({ locale, populate, status });
141
+ };
142
+ const getEntryStatus = async (contentType, entry) => {
143
+ if (entry.publishedAt) {
144
+ return "published";
145
+ }
146
+ const publishedEntry = await strapi.documents(contentType).findOne({
147
+ documentId: entry.documentId,
148
+ locale: entry.locale,
149
+ status: "published",
150
+ fields: ["updatedAt"]
151
+ });
152
+ if (!publishedEntry) {
153
+ return "draft";
154
+ }
155
+ const entryUpdatedAt = new Date(entry.updatedAt).getTime();
156
+ const publishedEntryUpdatedAt = new Date(publishedEntry.updatedAt).getTime();
157
+ if (entryUpdatedAt > publishedEntryUpdatedAt) {
158
+ return "modified";
159
+ }
160
+ return "published";
161
+ };
105
162
  async function deleteActionsOnDisableDraftAndPublish({
106
163
  oldContentTypes,
107
164
  contentTypes: contentTypes2
@@ -147,20 +204,22 @@ async function migrateIsValidAndStatusReleases() {
147
204
  const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
148
205
  for (const action of notValidatedActions) {
149
206
  if (action.entry) {
150
- const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
151
- strapi
207
+ const isEntryValid2 = getDraftEntryValidStatus(
208
+ {
209
+ contentType: action.contentType,
210
+ documentId: action.entryDocumentId,
211
+ locale: action.locale
212
+ },
213
+ { strapi }
214
+ );
215
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
216
+ where: {
217
+ id: action.id
218
+ },
219
+ data: {
220
+ isEntryValid: isEntryValid2
221
+ }
152
222
  });
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
223
  }
165
224
  }
166
225
  return getService("release", { strapi }).updateReleaseStatus(release2.id);
@@ -204,24 +263,24 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
204
263
  }
205
264
  });
206
265
  await utils.async.map(actions, async (action) => {
207
- if (action.entry && action.release) {
208
- const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
209
- strapi
266
+ if (action.entry && action.release && action.type === "publish") {
267
+ const isEntryValid2 = await getDraftEntryValidStatus(
268
+ {
269
+ contentType: contentTypeUID,
270
+ documentId: action.entryDocumentId,
271
+ locale: action.locale
272
+ },
273
+ { strapi }
274
+ );
275
+ releasesAffected.add(action.release.id);
276
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
277
+ where: {
278
+ id: action.id
279
+ },
280
+ data: {
281
+ isEntryValid: isEntryValid2
282
+ }
210
283
  });
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
284
  }
226
285
  });
227
286
  }
@@ -278,9 +337,42 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
278
337
  }
279
338
  }
280
339
  }
340
+ const addEntryDocumentToReleaseActions = {
341
+ name: "content-releases::5.0.0-add-entry-document-id-to-release-actions",
342
+ async up(trx, db) {
343
+ const hasTable = await trx.schema.hasTable("strapi_release_actions");
344
+ if (!hasTable) {
345
+ return;
346
+ }
347
+ const hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
348
+ if (hasPolymorphicColumn) {
349
+ const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
350
+ "strapi_release_actions",
351
+ "entry_document_id"
352
+ );
353
+ if (!hasEntryDocumentIdColumn) {
354
+ await trx.schema.alterTable("strapi_release_actions", (table) => {
355
+ table.string("entry_document_id");
356
+ });
357
+ }
358
+ const releaseActions = await trx.select("*").from("strapi_release_actions");
359
+ utils.async.map(releaseActions, async (action) => {
360
+ const { target_type, target_id } = action;
361
+ const entry = await db.query(target_type).findOne({ where: { id: target_id } });
362
+ if (entry) {
363
+ await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
364
+ }
365
+ });
366
+ }
367
+ },
368
+ async down() {
369
+ throw new Error("not implemented");
370
+ }
371
+ };
281
372
  const register = async ({ strapi: strapi2 }) => {
282
373
  if (strapi2.ee.features.isEnabled("cms-content-releases")) {
283
374
  await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
375
+ strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
284
376
  strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
285
377
  strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
286
378
  }
@@ -290,6 +382,105 @@ const register = async ({ strapi: strapi2 }) => {
290
382
  graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
291
383
  }
292
384
  };
385
+ const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
386
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
387
+ where: {
388
+ releasedAt: null,
389
+ actions: {
390
+ contentType,
391
+ entryDocumentId: entry.documentId,
392
+ locale: entry.locale
393
+ }
394
+ }
395
+ });
396
+ const entryStatus = await isEntryValid(contentType, entry, { strapi });
397
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).updateMany({
398
+ where: {
399
+ contentType,
400
+ entryDocumentId: entry.documentId,
401
+ locale: entry.locale
402
+ },
403
+ data: {
404
+ isEntryValid: entryStatus
405
+ }
406
+ });
407
+ for (const release2 of releases) {
408
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
409
+ }
410
+ };
411
+ const deleteActionsAndUpdateReleaseStatus = async (params) => {
412
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
413
+ where: {
414
+ actions: params
415
+ }
416
+ });
417
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
418
+ where: params
419
+ });
420
+ for (const release2 of releases) {
421
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
422
+ }
423
+ };
424
+ const deleteActionsOnDelete = async (ctx, next) => {
425
+ if (ctx.action !== "delete") {
426
+ return next();
427
+ }
428
+ if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
429
+ return next();
430
+ }
431
+ const contentType = ctx.contentType.uid;
432
+ const { documentId, locale } = ctx.params;
433
+ const result = await next();
434
+ if (!result) {
435
+ return result;
436
+ }
437
+ try {
438
+ deleteActionsAndUpdateReleaseStatus({
439
+ contentType,
440
+ entryDocumentId: documentId,
441
+ ...locale !== "*" && { locale }
442
+ });
443
+ } catch (error) {
444
+ strapi.log.error("Error while deleting release actions after delete", {
445
+ error
446
+ });
447
+ }
448
+ return result;
449
+ };
450
+ const updateActionsOnUpdate = async (ctx, next) => {
451
+ if (ctx.action !== "update") {
452
+ return next();
453
+ }
454
+ if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
455
+ return next();
456
+ }
457
+ const contentType = ctx.contentType.uid;
458
+ const result = await next();
459
+ if (!result) {
460
+ return result;
461
+ }
462
+ try {
463
+ updateActionsStatusAndUpdateReleaseStatus(contentType, result);
464
+ } catch (error) {
465
+ strapi.log.error("Error while updating release actions after update", {
466
+ error
467
+ });
468
+ }
469
+ return result;
470
+ };
471
+ const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
472
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
473
+ where: {
474
+ actions: params
475
+ }
476
+ });
477
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
478
+ where: params
479
+ });
480
+ for (const release2 of releases) {
481
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
482
+ }
483
+ };
293
484
  const bootstrap = async ({ strapi: strapi2 }) => {
294
485
  if (strapi2.ee.features.isEnabled("cms-content-releases")) {
295
486
  const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
@@ -297,115 +488,29 @@ const bootstrap = async ({ strapi: strapi2 }) => {
297
488
  );
298
489
  strapi2.db.lifecycles.subscribe({
299
490
  models: contentTypesWithDraftAndPublish,
300
- async afterDelete(event) {
301
- try {
302
- const { model, result } = event;
303
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
304
- const { id } = result;
305
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
306
- where: {
307
- actions: {
308
- target_type: model.uid,
309
- target_id: id
310
- }
311
- }
312
- });
313
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
314
- where: {
315
- target_type: model.uid,
316
- target_id: id
317
- }
318
- });
319
- for (const release2 of releases) {
320
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
321
- }
322
- }
323
- } catch (error) {
324
- strapi2.log.error("Error while deleting release actions after entry delete", { error });
325
- }
326
- },
327
- /**
328
- * deleteMany hook doesn't return the deleted entries ids
329
- * so we need to fetch them before deleting the entries to save the ids on our state
330
- */
331
- async beforeDeleteMany(event) {
332
- const { model, params } = event;
333
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
334
- const { where } = params;
335
- const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
336
- event.state.entriesToDelete = entriesToDelete;
337
- }
338
- },
339
491
  /**
340
- * We delete the release actions related to deleted entries
341
- * We make this only after deleteMany is succesfully executed to avoid errors
492
+ * deleteMany is still used outside documents service, for example when deleting a locale
342
493
  */
343
494
  async afterDeleteMany(event) {
344
495
  try {
345
- const { model, state } = event;
346
- const entriesToDelete = state.entriesToDelete;
347
- if (entriesToDelete) {
348
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
349
- where: {
350
- actions: {
351
- target_type: model.uid,
352
- target_id: {
353
- $in: entriesToDelete.map((entry) => entry.id)
354
- }
355
- }
356
- }
357
- });
358
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
359
- where: {
360
- target_type: model.uid,
361
- target_id: {
362
- $in: entriesToDelete.map((entry) => entry.id)
363
- }
364
- }
496
+ const model = strapi2.getModel(event.model.uid);
497
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
498
+ const { where } = event.params;
499
+ deleteReleasesActionsAndUpdateReleaseStatus({
500
+ contentType: model.uid,
501
+ locale: where?.locale ?? null,
502
+ ...where?.documentId && { entryDocumentId: where.documentId }
365
503
  });
366
- for (const release2 of releases) {
367
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
368
- }
369
504
  }
370
505
  } catch (error) {
371
506
  strapi2.log.error("Error while deleting release actions after entry deleteMany", {
372
507
  error
373
508
  });
374
509
  }
375
- },
376
- async afterUpdate(event) {
377
- try {
378
- const { model, result } = event;
379
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
380
- const isEntryValid = await getEntryValidStatus(model.uid, result, {
381
- strapi: strapi2
382
- });
383
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
384
- where: {
385
- target_type: model.uid,
386
- target_id: result.id
387
- },
388
- data: {
389
- isEntryValid
390
- }
391
- });
392
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
393
- where: {
394
- actions: {
395
- target_type: model.uid,
396
- target_id: result.id
397
- }
398
- }
399
- });
400
- for (const release2 of releases) {
401
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
402
- }
403
- }
404
- } catch (error) {
405
- strapi2.log.error("Error while updating release actions after entry update", { error });
406
- }
407
510
  }
408
511
  });
512
+ strapi2.documents.use(deleteActionsOnDelete);
513
+ strapi2.documents.use(updateActionsOnUpdate);
409
514
  getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
410
515
  strapi2.log.error(
411
516
  "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
@@ -497,15 +602,13 @@ const schema = {
497
602
  enum: ["publish", "unpublish"],
498
603
  required: true
499
604
  },
500
- entry: {
501
- type: "relation",
502
- relation: "morphToOne",
503
- configurable: false
504
- },
505
605
  contentType: {
506
606
  type: "string",
507
607
  required: true
508
608
  },
609
+ entryDocumentId: {
610
+ type: "string"
611
+ },
509
612
  locale: {
510
613
  type: "string"
511
614
  },
@@ -527,18 +630,6 @@ const contentTypes = {
527
630
  release: release$1,
528
631
  "release-action": releaseAction$1
529
632
  };
530
- const getGroupName = (queryValue) => {
531
- switch (queryValue) {
532
- case "contentType":
533
- return "contentType.displayName";
534
- case "action":
535
- return "type";
536
- case "locale":
537
- return ___default.default.getOr("No locale", "locale.name");
538
- default:
539
- return "contentType.displayName";
540
- }
541
- };
542
633
  const createReleaseService = ({ strapi: strapi2 }) => {
543
634
  const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
544
635
  strapi2.eventHub.emit(event, {
@@ -547,93 +638,32 @@ const createReleaseService = ({ strapi: strapi2 }) => {
547
638
  release: release2
548
639
  });
549
640
  };
550
- const publishSingleTypeAction = async (uid, actionType, entryId) => {
551
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
552
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
553
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
554
- const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
555
- try {
556
- if (actionType === "publish") {
557
- await entityManagerService.publish(entry, uid);
558
- } else {
559
- await entityManagerService.unpublish(entry, uid);
560
- }
561
- } catch (error) {
562
- if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
563
- ;
564
- else {
565
- throw error;
566
- }
567
- }
568
- };
569
- const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
570
- const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
571
- const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
572
- const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
573
- const entriesToPublish = await strapi2.entityService.findMany(uid, {
574
- filters: {
575
- id: {
576
- $in: entriesToPublishIds
577
- }
578
- },
579
- populate
580
- });
581
- const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
582
- filters: {
583
- id: {
584
- $in: entriestoUnpublishIds
585
- }
586
- },
587
- populate
588
- });
589
- if (entriesToPublish.length > 0) {
590
- await entityManagerService.publishMany(entriesToPublish, uid);
591
- }
592
- if (entriesToUnpublish.length > 0) {
593
- await entityManagerService.unpublishMany(entriesToUnpublish, uid);
594
- }
595
- };
596
641
  const getFormattedActions = async (releaseId) => {
597
642
  const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
598
643
  where: {
599
644
  release: {
600
645
  id: releaseId
601
646
  }
602
- },
603
- populate: {
604
- entry: {
605
- fields: ["id"]
606
- }
607
647
  }
608
648
  });
609
649
  if (actions.length === 0) {
610
650
  throw new utils.errors.ValidationError("No entries to publish");
611
651
  }
612
- const collectionTypeActions = {};
613
- const singleTypeActions = [];
652
+ const formattedActions = {};
614
653
  for (const action of actions) {
615
654
  const contentTypeUid = action.contentType;
616
- if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
617
- if (!collectionTypeActions[contentTypeUid]) {
618
- collectionTypeActions[contentTypeUid] = {
619
- entriesToPublishIds: [],
620
- entriesToUnpublishIds: []
621
- };
622
- }
623
- if (action.type === "publish") {
624
- collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
625
- } else {
626
- collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
627
- }
628
- } else {
629
- singleTypeActions.push({
630
- uid: contentTypeUid,
631
- action: action.type,
632
- id: action.entry.id
633
- });
655
+ if (!formattedActions[contentTypeUid]) {
656
+ formattedActions[contentTypeUid] = {
657
+ publish: [],
658
+ unpublish: []
659
+ };
634
660
  }
661
+ formattedActions[contentTypeUid][action.type].push({
662
+ documentId: action.entryDocumentId,
663
+ locale: action.locale
664
+ });
635
665
  }
636
- return { collectionTypeActions, singleTypeActions };
666
+ return formattedActions;
637
667
  };
638
668
  return {
639
669
  async create(releaseData, { user }) {
@@ -680,91 +710,10 @@ const createReleaseService = ({ strapi: strapi2 }) => {
680
710
  }
681
711
  });
682
712
  },
683
- async findManyWithContentTypeEntryAttached(contentTypeUid, entriesIds) {
684
- let entries = entriesIds;
685
- if (!Array.isArray(entriesIds)) {
686
- entries = [entriesIds];
687
- }
688
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
689
- where: {
690
- actions: {
691
- target_type: contentTypeUid,
692
- target_id: {
693
- $in: entries
694
- }
695
- },
696
- releasedAt: {
697
- $null: true
698
- }
699
- },
700
- populate: {
701
- // Filter the action to get only the content type entry
702
- actions: {
703
- where: {
704
- target_type: contentTypeUid,
705
- target_id: {
706
- $in: entries
707
- }
708
- },
709
- populate: {
710
- entry: {
711
- select: ["id"]
712
- }
713
- }
714
- }
715
- }
716
- });
717
- return releases.map((release2) => {
718
- if (release2.actions?.length) {
719
- const actionsForEntry = release2.actions;
720
- delete release2.actions;
721
- return {
722
- ...release2,
723
- actions: actionsForEntry
724
- };
725
- }
726
- return release2;
727
- });
728
- },
729
- async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
730
- const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
731
- where: {
732
- releasedAt: {
733
- $null: true
734
- },
735
- actions: {
736
- target_type: contentTypeUid,
737
- target_id: entryId
738
- }
739
- }
740
- });
741
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
742
- where: {
743
- $or: [
744
- {
745
- id: {
746
- $notIn: releasesRelated.map((release2) => release2.id)
747
- }
748
- },
749
- {
750
- actions: null
751
- }
752
- ],
753
- releasedAt: {
754
- $null: true
755
- }
756
- }
757
- });
758
- return releases.map((release2) => {
759
- if (release2.actions?.length) {
760
- const [actionForEntry] = release2.actions;
761
- delete release2.actions;
762
- return {
763
- ...release2,
764
- action: actionForEntry
765
- };
766
- }
767
- return release2;
713
+ findMany(query) {
714
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
715
+ return strapi2.db.query(RELEASE_MODEL_UID).findMany({
716
+ ...dbQuery
768
717
  });
769
718
  },
770
719
  async update(id, releaseData, { user }) {
@@ -800,14 +749,218 @@ const createReleaseService = ({ strapi: strapi2 }) => {
800
749
  strapi2.telemetry.send("didUpdateContentRelease");
801
750
  return updatedRelease;
802
751
  },
803
- async createAction(releaseId, action) {
804
- const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
752
+ async getAllComponents() {
753
+ const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
754
+ const components = await contentManagerComponentsService.findAllComponents();
755
+ const componentsMap = components.reduce(
756
+ (acc, component) => {
757
+ acc[component.uid] = component;
758
+ return acc;
759
+ },
760
+ {}
761
+ );
762
+ return componentsMap;
763
+ },
764
+ async delete(releaseId) {
765
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
766
+ where: { id: releaseId },
767
+ populate: {
768
+ actions: {
769
+ select: ["id"]
770
+ }
771
+ }
772
+ });
773
+ if (!release2) {
774
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
775
+ }
776
+ if (release2.releasedAt) {
777
+ throw new utils.errors.ValidationError("Release already published");
778
+ }
779
+ await strapi2.db.transaction(async () => {
780
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
781
+ where: {
782
+ id: {
783
+ $in: release2.actions.map((action) => action.id)
784
+ }
785
+ }
786
+ });
787
+ await strapi2.db.query(RELEASE_MODEL_UID).delete({
788
+ where: {
789
+ id: releaseId
790
+ }
791
+ });
792
+ });
793
+ if (release2.scheduledAt) {
794
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
795
+ await schedulingService.cancel(release2.id);
796
+ }
797
+ strapi2.telemetry.send("didDeleteContentRelease");
798
+ return release2;
799
+ },
800
+ async publish(releaseId) {
801
+ const {
802
+ release: release2,
803
+ error
804
+ } = await strapi2.db.transaction(async ({ trx }) => {
805
+ const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
806
+ if (!lockedRelease) {
807
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
808
+ }
809
+ if (lockedRelease.releasedAt) {
810
+ throw new utils.errors.ValidationError("Release already published");
811
+ }
812
+ if (lockedRelease.status === "failed") {
813
+ throw new utils.errors.ValidationError("Release failed to publish");
814
+ }
815
+ try {
816
+ strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
817
+ const formattedActions = await getFormattedActions(releaseId);
818
+ await strapi2.db.transaction(
819
+ async () => Promise.all(
820
+ Object.keys(formattedActions).map(async (contentTypeUid) => {
821
+ const contentType = contentTypeUid;
822
+ const { publish, unpublish } = formattedActions[contentType];
823
+ return Promise.all([
824
+ ...publish.map((params) => strapi2.documents(contentType).publish(params)),
825
+ ...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
826
+ ]);
827
+ })
828
+ )
829
+ );
830
+ const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
831
+ where: {
832
+ id: releaseId
833
+ },
834
+ data: {
835
+ status: "done",
836
+ releasedAt: /* @__PURE__ */ new Date()
837
+ }
838
+ });
839
+ dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
840
+ isPublished: true,
841
+ release: release22
842
+ });
843
+ strapi2.telemetry.send("didPublishContentRelease");
844
+ return { release: release22, error: null };
845
+ } catch (error2) {
846
+ dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
847
+ isPublished: false,
848
+ error: error2
849
+ });
850
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
851
+ status: "failed"
852
+ }).transacting(trx).execute();
853
+ return {
854
+ release: null,
855
+ error: error2
856
+ };
857
+ }
858
+ });
859
+ if (error instanceof Error) {
860
+ throw error;
861
+ }
862
+ return release2;
863
+ },
864
+ async updateReleaseStatus(releaseId) {
865
+ const releaseActionService = getService("release-action", { strapi: strapi2 });
866
+ const [totalActions, invalidActions] = await Promise.all([
867
+ releaseActionService.countActions({
868
+ filters: {
869
+ release: releaseId
870
+ }
871
+ }),
872
+ releaseActionService.countActions({
873
+ filters: {
874
+ release: releaseId,
875
+ isEntryValid: false
876
+ }
877
+ })
878
+ ]);
879
+ if (totalActions > 0) {
880
+ if (invalidActions > 0) {
881
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
882
+ where: {
883
+ id: releaseId
884
+ },
885
+ data: {
886
+ status: "blocked"
887
+ }
888
+ });
889
+ }
890
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
891
+ where: {
892
+ id: releaseId
893
+ },
894
+ data: {
895
+ status: "ready"
896
+ }
897
+ });
898
+ }
899
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
900
+ where: {
901
+ id: releaseId
902
+ },
903
+ data: {
904
+ status: "empty"
905
+ }
906
+ });
907
+ }
908
+ };
909
+ };
910
+ const getGroupName = (queryValue) => {
911
+ switch (queryValue) {
912
+ case "contentType":
913
+ return "contentType.displayName";
914
+ case "type":
915
+ return "type";
916
+ case "locale":
917
+ return ___default.default.getOr("No locale", "locale.name");
918
+ default:
919
+ return "contentType.displayName";
920
+ }
921
+ };
922
+ const createReleaseActionService = ({ strapi: strapi2 }) => {
923
+ const getLocalesDataForActions = async () => {
924
+ if (!strapi2.plugin("i18n")) {
925
+ return {};
926
+ }
927
+ const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
928
+ return allLocales.reduce((acc, locale) => {
929
+ acc[locale.code] = { name: locale.name, code: locale.code };
930
+ return acc;
931
+ }, {});
932
+ };
933
+ const getContentTypesDataForActions = async (contentTypesUids) => {
934
+ const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
935
+ const contentTypesData = {};
936
+ for (const contentTypeUid of contentTypesUids) {
937
+ const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
938
+ uid: contentTypeUid
939
+ });
940
+ contentTypesData[contentTypeUid] = {
941
+ mainField: contentTypeConfig.settings.mainField,
942
+ displayName: strapi2.getModel(contentTypeUid).info.displayName
943
+ };
944
+ }
945
+ return contentTypesData;
946
+ };
947
+ return {
948
+ async create(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
949
+ const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
805
950
  strapi: strapi2
806
951
  });
807
952
  await Promise.all([
808
- validateEntryContentType(action.entry.contentType),
953
+ validateEntryData(action.contentType, action.entryDocumentId),
809
954
  validateUniqueEntry(releaseId, action)
810
955
  ]);
956
+ const model = strapi2.contentType(action.contentType);
957
+ if (model.kind === "singleType") {
958
+ const document = await strapi2.db.query(model.uid).findOne({ select: ["documentId"] });
959
+ if (!document) {
960
+ throw new utils.errors.NotFoundError(`No entry found for contentType ${action.contentType}`);
961
+ }
962
+ action.entryDocumentId = document.documentId;
963
+ }
811
964
  const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
812
965
  if (!release2) {
813
966
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
@@ -815,28 +968,30 @@ const createReleaseService = ({ strapi: strapi2 }) => {
815
968
  if (release2.releasedAt) {
816
969
  throw new utils.errors.ValidationError("Release already published");
817
970
  }
818
- const { entry, type } = action;
819
- const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
820
- const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
971
+ const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
972
+ {
973
+ contentType: action.contentType,
974
+ documentId: action.entryDocumentId,
975
+ locale: action.locale
976
+ },
977
+ {
978
+ strapi: strapi2
979
+ }
980
+ ) : true;
821
981
  const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
822
982
  data: {
823
- type,
824
- contentType: entry.contentType,
825
- locale: entry.locale,
826
- isEntryValid,
827
- entry: {
828
- id: entry.id,
829
- __type: entry.contentType,
830
- __pivot: { field: "entry" }
831
- },
832
- release: releaseId
983
+ ...action,
984
+ release: release2.id,
985
+ isEntryValid: actionStatus
833
986
  },
834
- populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
987
+ populate: { release: { select: ["id"] } }
835
988
  });
836
- this.updateReleaseStatus(releaseId);
989
+ if (!disableUpdateReleaseStatus) {
990
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
991
+ }
837
992
  return releaseAction2;
838
993
  },
839
- async findActions(releaseId, query) {
994
+ async findPage(releaseId, query) {
840
995
  const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
841
996
  where: { id: releaseId },
842
997
  select: ["id"]
@@ -845,21 +1000,35 @@ const createReleaseService = ({ strapi: strapi2 }) => {
845
1000
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
846
1001
  }
847
1002
  const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
848
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
1003
+ const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
849
1004
  ...dbQuery,
850
- populate: {
851
- entry: {
852
- populate: "*"
853
- }
854
- },
855
1005
  where: {
856
1006
  release: releaseId
857
1007
  }
858
1008
  });
859
- },
860
- async countActions(query) {
861
- const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
862
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
1009
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
1010
+ const actionsWithEntry = await utils.async.map(actions, async (action) => {
1011
+ const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
1012
+ const entry = await getEntry(
1013
+ {
1014
+ contentType: action.contentType,
1015
+ documentId: action.entryDocumentId,
1016
+ locale: action.locale,
1017
+ populate,
1018
+ status: action.type === "publish" ? "draft" : "published"
1019
+ },
1020
+ { strapi: strapi2 }
1021
+ );
1022
+ return {
1023
+ ...action,
1024
+ entry,
1025
+ status: entry ? await getEntryStatus(action.contentType, entry) : null
1026
+ };
1027
+ });
1028
+ return {
1029
+ results: actionsWithEntry,
1030
+ pagination
1031
+ };
863
1032
  },
864
1033
  async groupActions(actions, groupBy) {
865
1034
  const contentTypeUids = actions.reduce((acc, action) => {
@@ -868,8 +1037,8 @@ const createReleaseService = ({ strapi: strapi2 }) => {
868
1037
  }
869
1038
  return acc;
870
1039
  }, []);
871
- const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(contentTypeUids);
872
- const allLocalesDictionary = await this.getLocalesDataForActions();
1040
+ const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
1041
+ const allLocalesDictionary = await getLocalesDataForActions();
873
1042
  const formattedData = actions.map((action) => {
874
1043
  const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
875
1044
  return {
@@ -881,165 +1050,42 @@ const createReleaseService = ({ strapi: strapi2 }) => {
881
1050
  uid: action.contentType
882
1051
  }
883
1052
  };
884
- });
885
- const groupName = getGroupName(groupBy);
886
- return ___default.default.groupBy(groupName)(formattedData);
887
- },
888
- async getLocalesDataForActions() {
889
- if (!strapi2.plugin("i18n")) {
890
- return {};
891
- }
892
- const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
893
- return allLocales.reduce((acc, locale) => {
894
- acc[locale.code] = { name: locale.name, code: locale.code };
895
- return acc;
896
- }, {});
897
- },
898
- async getContentTypesDataForActions(contentTypesUids) {
899
- const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
900
- const contentTypesData = {};
901
- for (const contentTypeUid of contentTypesUids) {
902
- const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
903
- uid: contentTypeUid
904
- });
905
- contentTypesData[contentTypeUid] = {
906
- mainField: contentTypeConfig.settings.mainField,
907
- displayName: strapi2.getModel(contentTypeUid).info.displayName
908
- };
909
- }
910
- return contentTypesData;
911
- },
912
- getContentTypeModelsFromActions(actions) {
913
- const contentTypeUids = actions.reduce((acc, action) => {
914
- if (!acc.includes(action.contentType)) {
915
- acc.push(action.contentType);
916
- }
917
- return acc;
918
- }, []);
919
- const contentTypeModelsMap = contentTypeUids.reduce(
920
- (acc, contentTypeUid) => {
921
- acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
922
- return acc;
923
- },
924
- {}
925
- );
926
- return contentTypeModelsMap;
927
- },
928
- async getAllComponents() {
929
- const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
930
- const components = await contentManagerComponentsService.findAllComponents();
931
- const componentsMap = components.reduce(
932
- (acc, component) => {
933
- acc[component.uid] = component;
934
- return acc;
935
- },
936
- {}
937
- );
938
- return componentsMap;
939
- },
940
- async delete(releaseId) {
941
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
942
- where: { id: releaseId },
943
- populate: {
944
- actions: {
945
- select: ["id"]
946
- }
947
- }
948
- });
949
- if (!release2) {
950
- throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
951
- }
952
- if (release2.releasedAt) {
953
- throw new utils.errors.ValidationError("Release already published");
954
- }
955
- await strapi2.db.transaction(async () => {
956
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
957
- where: {
958
- id: {
959
- $in: release2.actions.map((action) => action.id)
960
- }
961
- }
962
- });
963
- await strapi2.db.query(RELEASE_MODEL_UID).delete({
964
- where: {
965
- id: releaseId
966
- }
967
- });
968
- });
969
- if (release2.scheduledAt) {
970
- const schedulingService = getService("scheduling", { strapi: strapi2 });
971
- await schedulingService.cancel(release2.id);
972
- }
973
- strapi2.telemetry.send("didDeleteContentRelease");
974
- return release2;
1053
+ });
1054
+ const groupName = getGroupName(groupBy);
1055
+ return ___default.default.groupBy(groupName)(formattedData);
975
1056
  },
976
- async publish(releaseId) {
977
- const {
978
- release: release2,
979
- error
980
- } = await strapi2.db.transaction(async ({ trx }) => {
981
- const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
982
- if (!lockedRelease) {
983
- throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
984
- }
985
- if (lockedRelease.releasedAt) {
986
- throw new utils.errors.ValidationError("Release already published");
987
- }
988
- if (lockedRelease.status === "failed") {
989
- throw new utils.errors.ValidationError("Release failed to publish");
1057
+ async getContentTypeModelsFromActions(actions) {
1058
+ const contentTypeUids = actions.reduce((acc, action) => {
1059
+ if (!acc.includes(action.contentType)) {
1060
+ acc.push(action.contentType);
990
1061
  }
991
- try {
992
- strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
993
- const { collectionTypeActions, singleTypeActions } = await getFormattedActions(releaseId);
994
- await strapi2.db.transaction(async () => {
995
- for (const { uid, action, id } of singleTypeActions) {
996
- await publishSingleTypeAction(uid, action, id);
997
- }
998
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
999
- const uid = contentTypeUid;
1000
- await publishCollectionTypeAction(
1001
- uid,
1002
- collectionTypeActions[uid].entriesToPublishIds,
1003
- collectionTypeActions[uid].entriesToUnpublishIds
1004
- );
1005
- }
1006
- });
1007
- const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
1008
- where: {
1009
- id: releaseId
1010
- },
1011
- data: {
1012
- status: "done",
1013
- releasedAt: /* @__PURE__ */ new Date()
1014
- }
1015
- });
1016
- dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
1017
- isPublished: true,
1018
- release: release22
1019
- });
1020
- strapi2.telemetry.send("didPublishContentRelease");
1021
- return { release: release22, error: null };
1022
- } catch (error2) {
1023
- dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
1024
- isPublished: false,
1025
- error: error2
1062
+ return acc;
1063
+ }, []);
1064
+ const workflowsService = strapi2.plugin("review-workflows").service("workflows");
1065
+ const contentTypeModelsMap = await utils.async.reduce(contentTypeUids)(
1066
+ async (accPromise, contentTypeUid) => {
1067
+ const acc = await accPromise;
1068
+ const contentTypeModel = strapi2.getModel(contentTypeUid);
1069
+ const workflow = await workflowsService.getAssignedWorkflow(contentTypeUid, {
1070
+ populate: "stageRequiredToPublish"
1026
1071
  });
1027
- await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
1028
- status: "failed"
1029
- }).transacting(trx).execute();
1030
- return {
1031
- release: null,
1032
- error: error2
1072
+ acc[contentTypeUid] = {
1073
+ ...contentTypeModel,
1074
+ hasReviewWorkflow: !!workflow,
1075
+ stageRequiredToPublish: workflow?.stageRequiredToPublish
1033
1076
  };
1034
- }
1035
- });
1036
- if (error instanceof Error) {
1037
- throw error;
1038
- }
1039
- return release2;
1077
+ return acc;
1078
+ },
1079
+ {}
1080
+ );
1081
+ return contentTypeModelsMap;
1040
1082
  },
1041
- async updateAction(actionId, releaseId, update) {
1042
- const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
1083
+ async countActions(query) {
1084
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
1085
+ return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
1086
+ },
1087
+ async update(actionId, releaseId, update) {
1088
+ const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
1043
1089
  where: {
1044
1090
  id: actionId,
1045
1091
  release: {
@@ -1048,17 +1094,42 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1048
1094
  $null: true
1049
1095
  }
1050
1096
  }
1051
- },
1052
- data: update
1097
+ }
1053
1098
  });
1054
- if (!updatedAction) {
1099
+ if (!action) {
1055
1100
  throw new utils.errors.NotFoundError(
1056
1101
  `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1057
1102
  );
1058
1103
  }
1104
+ const actionStatus = update.type === "publish" ? await getDraftEntryValidStatus(
1105
+ {
1106
+ contentType: action.contentType,
1107
+ documentId: action.entryDocumentId,
1108
+ locale: action.locale
1109
+ },
1110
+ {
1111
+ strapi: strapi2
1112
+ }
1113
+ ) : true;
1114
+ const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
1115
+ where: {
1116
+ id: actionId,
1117
+ release: {
1118
+ id: releaseId,
1119
+ releasedAt: {
1120
+ $null: true
1121
+ }
1122
+ }
1123
+ },
1124
+ data: {
1125
+ ...update,
1126
+ isEntryValid: actionStatus
1127
+ }
1128
+ });
1129
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1059
1130
  return updatedAction;
1060
1131
  },
1061
- async deleteAction(actionId, releaseId) {
1132
+ async delete(actionId, releaseId) {
1062
1133
  const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
1063
1134
  where: {
1064
1135
  id: actionId,
@@ -1075,51 +1146,56 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1075
1146
  `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1076
1147
  );
1077
1148
  }
1078
- this.updateReleaseStatus(releaseId);
1149
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1079
1150
  return deletedAction;
1080
1151
  },
1081
- async updateReleaseStatus(releaseId) {
1082
- const [totalActions, invalidActions] = await Promise.all([
1083
- this.countActions({
1084
- filters: {
1085
- release: releaseId
1086
- }
1087
- }),
1088
- this.countActions({
1089
- filters: {
1090
- release: releaseId,
1091
- isEntryValid: false
1092
- }
1093
- })
1094
- ]);
1095
- if (totalActions > 0) {
1096
- if (invalidActions > 0) {
1097
- return strapi2.db.query(RELEASE_MODEL_UID).update({
1098
- where: {
1099
- id: releaseId
1100
- },
1101
- data: {
1102
- status: "blocked"
1152
+ async validateActionsByContentTypes(contentTypeUids) {
1153
+ const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
1154
+ where: {
1155
+ contentType: {
1156
+ $in: contentTypeUids
1157
+ },
1158
+ // We only want to validate actions that are going to be published
1159
+ type: "publish",
1160
+ release: {
1161
+ releasedAt: {
1162
+ $null: true
1103
1163
  }
1104
- });
1105
- }
1106
- return strapi2.db.query(RELEASE_MODEL_UID).update({
1164
+ }
1165
+ },
1166
+ populate: { release: true }
1167
+ });
1168
+ const releasesUpdated = [];
1169
+ await utils.async.map(actions, async (action) => {
1170
+ const isValid = await getDraftEntryValidStatus(
1171
+ {
1172
+ contentType: action.contentType,
1173
+ documentId: action.entryDocumentId,
1174
+ locale: action.locale
1175
+ },
1176
+ { strapi: strapi2 }
1177
+ );
1178
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
1107
1179
  where: {
1108
- id: releaseId
1180
+ id: action.id
1109
1181
  },
1110
1182
  data: {
1111
- status: "ready"
1183
+ isEntryValid: isValid
1112
1184
  }
1113
1185
  });
1114
- }
1115
- return strapi2.db.query(RELEASE_MODEL_UID).update({
1116
- where: {
1117
- id: releaseId
1118
- },
1119
- data: {
1120
- status: "empty"
1186
+ if (!releasesUpdated.includes(action.release.id)) {
1187
+ releasesUpdated.push(action.release.id);
1121
1188
  }
1189
+ return {
1190
+ id: action.id,
1191
+ isEntryValid: isValid
1192
+ };
1122
1193
  });
1194
+ if (releasesUpdated.length > 0) {
1195
+ await utils.async.map(releasesUpdated, async (releaseId) => {
1196
+ await getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1197
+ });
1198
+ }
1123
1199
  }
1124
1200
  };
1125
1201
  };
@@ -1135,30 +1211,35 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1135
1211
  where: {
1136
1212
  id: releaseId
1137
1213
  },
1138
- populate: { actions: { populate: { entry: { select: ["id"] } } } }
1214
+ populate: {
1215
+ actions: true
1216
+ }
1139
1217
  });
1140
1218
  if (!release2) {
1141
1219
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
1142
1220
  }
1143
1221
  const isEntryInRelease = release2.actions.some(
1144
- (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1222
+ (action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
1145
1223
  );
1146
1224
  if (isEntryInRelease) {
1147
1225
  throw new AlreadyOnReleaseError(
1148
- `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1226
+ `Entry with documentId ${releaseActionArgs.entryDocumentId}${releaseActionArgs.locale ? `( ${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
1149
1227
  );
1150
1228
  }
1151
1229
  },
1152
- validateEntryContentType(contentTypeUid) {
1230
+ validateEntryData(contentTypeUid, entryDocumentId) {
1153
1231
  const contentType = strapi2.contentType(contentTypeUid);
1154
1232
  if (!contentType) {
1155
1233
  throw new utils.errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1156
1234
  }
1157
- if (!contentType.options?.draftAndPublish) {
1235
+ if (!utils.contentTypes.hasDraftAndPublish(contentType)) {
1158
1236
  throw new utils.errors.ValidationError(
1159
1237
  `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1160
1238
  );
1161
1239
  }
1240
+ if (contentType.kind === "collectionType" && !entryDocumentId) {
1241
+ throw new utils.errors.ValidationError("Document id is required for collection type");
1242
+ }
1162
1243
  },
1163
1244
  async validatePendingReleasesLimit() {
1164
1245
  const featureCfg = strapi2.ee.features.get("cms-content-releases");
@@ -1247,78 +1328,165 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1247
1328
  }
1248
1329
  };
1249
1330
  };
1331
+ const DEFAULT_SETTINGS = {
1332
+ defaultTimezone: null
1333
+ };
1334
+ const createSettingsService = ({ strapi: strapi2 }) => {
1335
+ const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
1336
+ return {
1337
+ async update({ settings: settings2 }) {
1338
+ const store = await getStore();
1339
+ store.set({ key: "settings", value: settings2 });
1340
+ return settings2;
1341
+ },
1342
+ async find() {
1343
+ const store = await getStore();
1344
+ const settings2 = await store.get({ key: "settings" });
1345
+ return {
1346
+ ...DEFAULT_SETTINGS,
1347
+ ...settings2 || {}
1348
+ };
1349
+ }
1350
+ };
1351
+ };
1250
1352
  const services = {
1251
1353
  release: createReleaseService,
1354
+ "release-action": createReleaseActionService,
1252
1355
  "release-validation": createReleaseValidationService,
1253
- scheduling: createSchedulingService
1356
+ scheduling: createSchedulingService,
1357
+ settings: createSettingsService
1254
1358
  };
1255
- const RELEASE_SCHEMA = yup__namespace.object().shape({
1256
- name: yup__namespace.string().trim().required(),
1257
- scheduledAt: yup__namespace.string().nullable(),
1258
- isScheduled: yup__namespace.boolean().optional(),
1259
- time: yup__namespace.string().when("isScheduled", {
1260
- is: true,
1261
- then: yup__namespace.string().trim().required(),
1262
- otherwise: yup__namespace.string().nullable()
1263
- }),
1264
- timezone: yup__namespace.string().when("isScheduled", {
1265
- is: true,
1266
- then: yup__namespace.string().required().nullable(),
1267
- otherwise: yup__namespace.string().nullable()
1268
- }),
1269
- date: yup__namespace.string().when("isScheduled", {
1270
- is: true,
1271
- then: yup__namespace.string().required().nullable(),
1272
- otherwise: yup__namespace.string().nullable()
1359
+ const RELEASE_SCHEMA = utils.yup.object().shape({
1360
+ name: utils.yup.string().trim().required(),
1361
+ scheduledAt: utils.yup.string().nullable(),
1362
+ timezone: utils.yup.string().when("scheduledAt", {
1363
+ is: (value) => value !== null && value !== void 0,
1364
+ then: utils.yup.string().required(),
1365
+ otherwise: utils.yup.string().nullable()
1273
1366
  })
1274
1367
  }).required().noUnknown();
1368
+ const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = utils.yup.object().shape({
1369
+ contentType: utils.yup.string().required(),
1370
+ entryDocumentId: utils.yup.string().nullable(),
1371
+ hasEntryAttached: utils.yup.string().nullable(),
1372
+ locale: utils.yup.string().nullable()
1373
+ }).required().noUnknown();
1275
1374
  const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
1375
+ const validatefindByDocumentAttachedParams = utils.validateYupSchema(
1376
+ FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
1377
+ );
1276
1378
  const releaseController = {
1277
- async findMany(ctx) {
1379
+ /**
1380
+ * Find releases based on documents attached or not to the release.
1381
+ * If `hasEntryAttached` is true, it will return all releases that have the entry attached.
1382
+ * If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
1383
+ */
1384
+ async findByDocumentAttached(ctx) {
1278
1385
  const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1279
1386
  ability: ctx.state.userAbility,
1280
1387
  model: RELEASE_MODEL_UID
1281
1388
  });
1282
1389
  await permissionsManager.validateQuery(ctx.query);
1283
1390
  const releaseService = getService("release", { strapi });
1284
- const isFindManyForContentTypeEntry = Boolean(ctx.query?.contentTypeUid && ctx.query?.entryId);
1285
- if (isFindManyForContentTypeEntry) {
1286
- const query = await permissionsManager.sanitizeQuery(ctx.query);
1287
- const contentTypeUid = query.contentTypeUid;
1288
- const entryId = query.entryId;
1289
- const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
1290
- const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
1291
- ctx.body = { data };
1292
- } else {
1293
- const query = await permissionsManager.sanitizeQuery(ctx.query);
1294
- const { results, pagination } = await releaseService.findPage(query);
1295
- const data = results.map((release2) => {
1296
- const { actions, ...releaseData } = release2;
1297
- return {
1298
- ...releaseData,
1391
+ const query = await permissionsManager.sanitizeQuery(ctx.query);
1392
+ await validatefindByDocumentAttachedParams(query);
1393
+ const model = strapi.getModel(query.contentType);
1394
+ if (model.kind && model.kind === "singleType") {
1395
+ const document = await strapi.db.query(model.uid).findOne({ select: ["documentId"] });
1396
+ if (!document) {
1397
+ throw new utils.errors.NotFoundError(`No entry found for contentType ${query.contentType}`);
1398
+ }
1399
+ query.entryDocumentId = document.documentId;
1400
+ }
1401
+ const { contentType, hasEntryAttached, entryDocumentId, locale } = query;
1402
+ const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
1403
+ if (isEntryAttached) {
1404
+ const releases = await releaseService.findMany({
1405
+ where: {
1406
+ releasedAt: null,
1407
+ actions: {
1408
+ contentType,
1409
+ entryDocumentId: entryDocumentId ?? null,
1410
+ locale: locale ?? null
1411
+ }
1412
+ },
1413
+ populate: {
1299
1414
  actions: {
1300
- meta: {
1301
- count: actions.count
1415
+ fields: ["type"],
1416
+ filters: {
1417
+ contentType,
1418
+ entryDocumentId: entryDocumentId ?? null,
1419
+ locale: locale ?? null
1302
1420
  }
1303
1421
  }
1304
- };
1422
+ }
1423
+ });
1424
+ ctx.body = { data: releases };
1425
+ } else {
1426
+ const relatedReleases = await releaseService.findMany({
1427
+ where: {
1428
+ releasedAt: null,
1429
+ actions: {
1430
+ contentType,
1431
+ entryDocumentId: entryDocumentId ?? null,
1432
+ locale: locale ?? null
1433
+ }
1434
+ }
1305
1435
  });
1306
- const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1436
+ const releases = await releaseService.findMany({
1307
1437
  where: {
1438
+ $or: [
1439
+ {
1440
+ id: {
1441
+ $notIn: relatedReleases.map((release2) => release2.id)
1442
+ }
1443
+ },
1444
+ {
1445
+ actions: null
1446
+ }
1447
+ ],
1308
1448
  releasedAt: null
1309
1449
  }
1310
1450
  });
1311
- ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1451
+ ctx.body = { data: releases };
1312
1452
  }
1313
1453
  },
1454
+ async findPage(ctx) {
1455
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1456
+ ability: ctx.state.userAbility,
1457
+ model: RELEASE_MODEL_UID
1458
+ });
1459
+ await permissionsManager.validateQuery(ctx.query);
1460
+ const releaseService = getService("release", { strapi });
1461
+ const query = await permissionsManager.sanitizeQuery(ctx.query);
1462
+ const { results, pagination } = await releaseService.findPage(query);
1463
+ const data = results.map((release2) => {
1464
+ const { actions, ...releaseData } = release2;
1465
+ return {
1466
+ ...releaseData,
1467
+ actions: {
1468
+ meta: {
1469
+ count: actions.count
1470
+ }
1471
+ }
1472
+ };
1473
+ });
1474
+ const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1475
+ where: {
1476
+ releasedAt: null
1477
+ }
1478
+ });
1479
+ ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1480
+ },
1314
1481
  async findOne(ctx) {
1315
1482
  const id = ctx.params.id;
1316
1483
  const releaseService = getService("release", { strapi });
1484
+ const releaseActionService = getService("release-action", { strapi });
1317
1485
  const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
1318
1486
  if (!release2) {
1319
1487
  throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
1320
1488
  }
1321
- const count = await releaseService.countActions({
1489
+ const count = await releaseActionService.countActions({
1322
1490
  filters: {
1323
1491
  release: id
1324
1492
  }
@@ -1338,28 +1506,43 @@ const releaseController = {
1338
1506
  ctx.body = { data };
1339
1507
  },
1340
1508
  async mapEntriesToReleases(ctx) {
1341
- const { contentTypeUid, entriesIds } = ctx.query;
1342
- if (!contentTypeUid || !entriesIds) {
1509
+ const { contentTypeUid, documentIds, locale } = ctx.query;
1510
+ if (!contentTypeUid || !documentIds) {
1343
1511
  throw new utils.errors.ValidationError("Missing required query parameters");
1344
1512
  }
1345
1513
  const releaseService = getService("release", { strapi });
1346
- const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
1347
- contentTypeUid,
1348
- entriesIds
1349
- );
1514
+ const releasesWithActions = await releaseService.findMany({
1515
+ where: {
1516
+ releasedAt: null,
1517
+ actions: {
1518
+ contentType: contentTypeUid,
1519
+ entryDocumentId: {
1520
+ $in: documentIds
1521
+ },
1522
+ locale
1523
+ }
1524
+ },
1525
+ populate: {
1526
+ actions: true
1527
+ }
1528
+ });
1350
1529
  const mappedEntriesInReleases = releasesWithActions.reduce(
1351
- // TODO: Fix for v5 removed mappedEntriedToRelease
1352
1530
  (acc, release2) => {
1353
1531
  release2.actions.forEach((action) => {
1354
- if (!acc[action.entry.id]) {
1355
- acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
1532
+ if (action.contentType !== contentTypeUid) {
1533
+ return;
1534
+ }
1535
+ if (locale && action.locale !== locale) {
1536
+ return;
1537
+ }
1538
+ if (!acc[action.entryDocumentId]) {
1539
+ acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
1356
1540
  } else {
1357
- acc[action.entry.id].push({ id: release2.id, name: release2.name });
1541
+ acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
1358
1542
  }
1359
1543
  });
1360
1544
  return acc;
1361
1545
  },
1362
- // TODO: Fix for v5 removed mappedEntriedToRelease
1363
1546
  {}
1364
1547
  );
1365
1548
  ctx.body = {
@@ -1404,18 +1587,18 @@ const releaseController = {
1404
1587
  };
1405
1588
  },
1406
1589
  async publish(ctx) {
1407
- const user = ctx.state.user;
1408
1590
  const id = ctx.params.id;
1409
1591
  const releaseService = getService("release", { strapi });
1410
- const release2 = await releaseService.publish(id, { user });
1592
+ const releaseActionService = getService("release-action", { strapi });
1593
+ const release2 = await releaseService.publish(id);
1411
1594
  const [countPublishActions, countUnpublishActions] = await Promise.all([
1412
- releaseService.countActions({
1595
+ releaseActionService.countActions({
1413
1596
  filters: {
1414
1597
  release: id,
1415
1598
  type: "publish"
1416
1599
  }
1417
1600
  }),
1418
- releaseService.countActions({
1601
+ releaseActionService.countActions({
1419
1602
  filters: {
1420
1603
  release: id,
1421
1604
  type: "unpublish"
@@ -1433,24 +1616,27 @@ const releaseController = {
1433
1616
  }
1434
1617
  };
1435
1618
  const RELEASE_ACTION_SCHEMA = utils.yup.object().shape({
1436
- entry: utils.yup.object().shape({
1437
- id: utils.yup.strapiID().required(),
1438
- contentType: utils.yup.string().required()
1439
- }).required(),
1619
+ contentType: utils.yup.string().required(),
1620
+ entryDocumentId: utils.yup.strapiID(),
1621
+ locale: utils.yup.string(),
1440
1622
  type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
1441
1623
  });
1442
1624
  const RELEASE_ACTION_UPDATE_SCHEMA = utils.yup.object().shape({
1443
1625
  type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
1444
1626
  });
1627
+ const FIND_MANY_ACTIONS_PARAMS = utils.yup.object().shape({
1628
+ groupBy: utils.yup.string().oneOf(["action", "contentType", "locale"])
1629
+ });
1445
1630
  const validateReleaseAction = utils.validateYupSchema(RELEASE_ACTION_SCHEMA);
1446
1631
  const validateReleaseActionUpdateSchema = utils.validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
1632
+ const validateFindManyActionsParams = utils.validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
1447
1633
  const releaseActionController = {
1448
1634
  async create(ctx) {
1449
1635
  const releaseId = ctx.params.releaseId;
1450
1636
  const releaseActionArgs = ctx.request.body;
1451
1637
  await validateReleaseAction(releaseActionArgs);
1452
- const releaseService = getService("release", { strapi });
1453
- const releaseAction2 = await releaseService.createAction(releaseId, releaseActionArgs);
1638
+ const releaseActionService = getService("release-action", { strapi });
1639
+ const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
1454
1640
  ctx.created({
1455
1641
  data: releaseAction2
1456
1642
  });
@@ -1461,12 +1647,15 @@ const releaseActionController = {
1461
1647
  await Promise.all(
1462
1648
  releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1463
1649
  );
1650
+ const releaseActionService = getService("release-action", { strapi });
1464
1651
  const releaseService = getService("release", { strapi });
1465
1652
  const releaseActions = await strapi.db.transaction(async () => {
1466
1653
  const releaseActions2 = await Promise.all(
1467
1654
  releaseActionsArgs.map(async (releaseActionArgs) => {
1468
1655
  try {
1469
- const action = await releaseService.createAction(releaseId, releaseActionArgs);
1656
+ const action = await releaseActionService.create(releaseId, releaseActionArgs, {
1657
+ disableUpdateReleaseStatus: true
1658
+ });
1470
1659
  return action;
1471
1660
  } catch (error) {
1472
1661
  if (error instanceof AlreadyOnReleaseError) {
@@ -1479,6 +1668,9 @@ const releaseActionController = {
1479
1668
  return releaseActions2;
1480
1669
  });
1481
1670
  const newReleaseActions = releaseActions.filter((action) => action !== null);
1671
+ if (newReleaseActions.length > 0) {
1672
+ releaseService.updateReleaseStatus(releaseId);
1673
+ }
1482
1674
  ctx.created({
1483
1675
  data: newReleaseActions,
1484
1676
  meta: {
@@ -1493,10 +1685,17 @@ const releaseActionController = {
1493
1685
  ability: ctx.state.userAbility,
1494
1686
  model: RELEASE_ACTION_MODEL_UID
1495
1687
  });
1688
+ await validateFindManyActionsParams(ctx.query);
1689
+ if (ctx.query.groupBy) {
1690
+ if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
1691
+ ctx.badRequest("Invalid groupBy parameter");
1692
+ }
1693
+ }
1694
+ ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
1695
+ delete ctx.query.groupBy;
1496
1696
  const query = await permissionsManager.sanitizeQuery(ctx.query);
1497
- const releaseService = getService("release", { strapi });
1498
- const { results, pagination } = await releaseService.findActions(releaseId, {
1499
- sort: query.groupBy === "action" ? "type" : query.groupBy,
1697
+ const releaseActionService = getService("release-action", { strapi });
1698
+ const { results, pagination } = await releaseActionService.findPage(releaseId, {
1500
1699
  ...query
1501
1700
  });
1502
1701
  const contentTypeOutputSanitizers = results.reduce((acc, action) => {
@@ -1512,10 +1711,11 @@ const releaseActionController = {
1512
1711
  }, {});
1513
1712
  const sanitizedResults = await utils.async.map(results, async (action) => ({
1514
1713
  ...action,
1515
- entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1714
+ entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
1516
1715
  }));
1517
- const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
1518
- const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
1716
+ const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
1717
+ const contentTypes2 = await releaseActionService.getContentTypeModelsFromActions(results);
1718
+ const releaseService = getService("release", { strapi });
1519
1719
  const components = await releaseService.getAllComponents();
1520
1720
  ctx.body = {
1521
1721
  data: groupedData,
@@ -1531,8 +1731,8 @@ const releaseActionController = {
1531
1731
  const releaseId = ctx.params.releaseId;
1532
1732
  const releaseActionUpdateArgs = ctx.request.body;
1533
1733
  await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
1534
- const releaseService = getService("release", { strapi });
1535
- const updatedAction = await releaseService.updateAction(
1734
+ const releaseActionService = getService("release-action", { strapi });
1735
+ const updatedAction = await releaseActionService.update(
1536
1736
  actionId,
1537
1737
  releaseId,
1538
1738
  releaseActionUpdateArgs
@@ -1544,14 +1744,36 @@ const releaseActionController = {
1544
1744
  async delete(ctx) {
1545
1745
  const actionId = ctx.params.actionId;
1546
1746
  const releaseId = ctx.params.releaseId;
1547
- const releaseService = getService("release", { strapi });
1548
- const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
1747
+ const releaseActionService = getService("release-action", { strapi });
1748
+ const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
1549
1749
  ctx.body = {
1550
1750
  data: deletedReleaseAction
1551
1751
  };
1552
1752
  }
1553
1753
  };
1554
- const controllers = { release: releaseController, "release-action": releaseActionController };
1754
+ const SETTINGS_SCHEMA = yup__namespace.object().shape({
1755
+ defaultTimezone: yup__namespace.string().nullable().default(null)
1756
+ }).required().noUnknown();
1757
+ const validateSettings = utils.validateYupSchema(SETTINGS_SCHEMA);
1758
+ const settingsController = {
1759
+ async find(ctx) {
1760
+ const settingsService = getService("settings", { strapi });
1761
+ const settings2 = await settingsService.find();
1762
+ ctx.body = { data: settings2 };
1763
+ },
1764
+ async update(ctx) {
1765
+ const settingsBody = ctx.request.body;
1766
+ const settings2 = await validateSettings(settingsBody);
1767
+ const settingsService = getService("settings", { strapi });
1768
+ const updatedSettings = await settingsService.update({ settings: settings2 });
1769
+ ctx.body = { data: updatedSettings };
1770
+ }
1771
+ };
1772
+ const controllers = {
1773
+ release: releaseController,
1774
+ "release-action": releaseActionController,
1775
+ settings: settingsController
1776
+ };
1555
1777
  const release = {
1556
1778
  type: "admin",
1557
1779
  routes: [
@@ -1571,6 +1793,22 @@ const release = {
1571
1793
  ]
1572
1794
  }
1573
1795
  },
1796
+ {
1797
+ method: "GET",
1798
+ path: "/getByDocumentAttached",
1799
+ handler: "release.findByDocumentAttached",
1800
+ config: {
1801
+ policies: [
1802
+ "admin::isAuthenticatedAdmin",
1803
+ {
1804
+ name: "admin::hasPermissions",
1805
+ config: {
1806
+ actions: ["plugin::content-releases.read"]
1807
+ }
1808
+ }
1809
+ ]
1810
+ }
1811
+ },
1574
1812
  {
1575
1813
  method: "POST",
1576
1814
  path: "/",
@@ -1590,7 +1828,7 @@ const release = {
1590
1828
  {
1591
1829
  method: "GET",
1592
1830
  path: "/",
1593
- handler: "release.findMany",
1831
+ handler: "release.findPage",
1594
1832
  config: {
1595
1833
  policies: [
1596
1834
  "admin::isAuthenticatedAdmin",
@@ -1754,7 +1992,45 @@ const releaseAction = {
1754
1992
  }
1755
1993
  ]
1756
1994
  };
1995
+ const settings = {
1996
+ type: "admin",
1997
+ routes: [
1998
+ {
1999
+ method: "GET",
2000
+ path: "/settings",
2001
+ handler: "settings.find",
2002
+ config: {
2003
+ policies: [
2004
+ "admin::isAuthenticatedAdmin",
2005
+ {
2006
+ name: "admin::hasPermissions",
2007
+ config: {
2008
+ actions: ["plugin::content-releases.settings.read"]
2009
+ }
2010
+ }
2011
+ ]
2012
+ }
2013
+ },
2014
+ {
2015
+ method: "PUT",
2016
+ path: "/settings",
2017
+ handler: "settings.update",
2018
+ config: {
2019
+ policies: [
2020
+ "admin::isAuthenticatedAdmin",
2021
+ {
2022
+ name: "admin::hasPermissions",
2023
+ config: {
2024
+ actions: ["plugin::content-releases.settings.update"]
2025
+ }
2026
+ }
2027
+ ]
2028
+ }
2029
+ }
2030
+ ]
2031
+ };
1757
2032
  const routes = {
2033
+ settings,
1758
2034
  release,
1759
2035
  "release-action": releaseAction
1760
2036
  };