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

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