@strapi/content-releases 0.0.0-experimental.d954d57341a6623992a0d211daaec8e245c3517d → 0.0.0-experimental.da85533897155e719d784f0271223c866d2f69ab

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 (99) hide show
  1. package/LICENSE +17 -1
  2. package/dist/_chunks/{App-Do-Rnv0A.mjs → App-B4mkcLmw.mjs} +296 -263
  3. package/dist/_chunks/App-B4mkcLmw.mjs.map +1 -0
  4. package/dist/_chunks/{App-CqbuK4M6.js → App-DpoC8s97.js} +293 -261
  5. package/dist/_chunks/App-DpoC8s97.js.map +1 -0
  6. package/dist/_chunks/ReleasesSettingsPage-B89WWWJf.js +178 -0
  7. package/dist/_chunks/ReleasesSettingsPage-B89WWWJf.js.map +1 -0
  8. package/dist/_chunks/ReleasesSettingsPage-DfL6yxLG.mjs +178 -0
  9. package/dist/_chunks/ReleasesSettingsPage-DfL6yxLG.mjs.map +1 -0
  10. package/dist/_chunks/{en-DtFJ5ViE.js → en-CmYoEnA7.js} +9 -2
  11. package/dist/_chunks/en-CmYoEnA7.js.map +1 -0
  12. package/dist/_chunks/{en-B9Ur3VsE.mjs → en-D0yVZFqf.mjs} +9 -2
  13. package/dist/_chunks/en-D0yVZFqf.mjs.map +1 -0
  14. package/dist/_chunks/{index-D_pgdqQL.mjs → index-B3cqcIza.mjs} +780 -448
  15. package/dist/_chunks/index-B3cqcIza.mjs.map +1 -0
  16. package/dist/_chunks/{index-Tedsw4GC.js → index-sGcuP2hw.js} +763 -431
  17. package/dist/_chunks/index-sGcuP2hw.js.map +1 -0
  18. package/dist/_chunks/schemas-63pFihNF.mjs +44 -0
  19. package/dist/_chunks/schemas-63pFihNF.mjs.map +1 -0
  20. package/dist/_chunks/schemas-z5zp-_Gd.js +62 -0
  21. package/dist/_chunks/schemas-z5zp-_Gd.js.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/ReleaseActionMenu.d.ts +2 -2
  25. package/dist/admin/src/components/{CMReleasesContainer.d.ts → ReleaseActionModal.d.ts} +3 -1
  26. package/dist/admin/src/components/ReleaseListCell.d.ts +28 -0
  27. package/dist/admin/src/components/ReleasesPanel.d.ts +3 -0
  28. package/dist/admin/src/constants.d.ts +18 -0
  29. package/dist/admin/src/modules/hooks.d.ts +7 -0
  30. package/dist/admin/src/pages/ReleasesSettingsPage.d.ts +1 -0
  31. package/dist/admin/src/services/release.d.ts +43 -36
  32. package/dist/admin/src/utils/time.d.ts +9 -0
  33. package/dist/admin/src/validation/schemas.d.ts +6 -0
  34. package/dist/server/index.js +796 -623
  35. package/dist/server/index.js.map +1 -1
  36. package/dist/server/index.mjs +797 -624
  37. package/dist/server/index.mjs.map +1 -1
  38. package/dist/server/src/bootstrap.d.ts.map +1 -1
  39. package/dist/server/src/constants.d.ts +11 -2
  40. package/dist/server/src/constants.d.ts.map +1 -1
  41. package/dist/server/src/content-types/index.d.ts +3 -5
  42. package/dist/server/src/content-types/index.d.ts.map +1 -1
  43. package/dist/server/src/content-types/release-action/index.d.ts +3 -5
  44. package/dist/server/src/content-types/release-action/index.d.ts.map +1 -1
  45. package/dist/server/src/content-types/release-action/schema.d.ts +3 -5
  46. package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -1
  47. package/dist/server/src/controllers/index.d.ts +6 -1
  48. package/dist/server/src/controllers/index.d.ts.map +1 -1
  49. package/dist/server/src/controllers/release-action.d.ts.map +1 -1
  50. package/dist/server/src/controllers/release.d.ts +7 -1
  51. package/dist/server/src/controllers/release.d.ts.map +1 -1
  52. package/dist/server/src/controllers/settings.d.ts +11 -0
  53. package/dist/server/src/controllers/settings.d.ts.map +1 -0
  54. package/dist/server/src/controllers/validation/release-action.d.ts +7 -1
  55. package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -1
  56. package/dist/server/src/controllers/validation/release.d.ts +2 -0
  57. package/dist/server/src/controllers/validation/release.d.ts.map +1 -1
  58. package/dist/server/src/controllers/validation/settings.d.ts +3 -0
  59. package/dist/server/src/controllers/validation/settings.d.ts.map +1 -0
  60. package/dist/server/src/index.d.ts +66 -49
  61. package/dist/server/src/index.d.ts.map +1 -1
  62. package/dist/server/src/middlewares/documents.d.ts +6 -0
  63. package/dist/server/src/middlewares/documents.d.ts.map +1 -0
  64. package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts +9 -0
  65. package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts.map +1 -0
  66. package/dist/server/src/migrations/index.d.ts.map +1 -1
  67. package/dist/server/src/register.d.ts.map +1 -1
  68. package/dist/server/src/routes/index.d.ts +16 -0
  69. package/dist/server/src/routes/index.d.ts.map +1 -1
  70. package/dist/server/src/routes/release.d.ts.map +1 -1
  71. package/dist/server/src/routes/settings.d.ts +18 -0
  72. package/dist/server/src/routes/settings.d.ts.map +1 -0
  73. package/dist/server/src/services/index.d.ts +38 -38
  74. package/dist/server/src/services/index.d.ts.map +1 -1
  75. package/dist/server/src/services/release-action.d.ts +36 -0
  76. package/dist/server/src/services/release-action.d.ts.map +1 -0
  77. package/dist/server/src/services/release.d.ts +6 -41
  78. package/dist/server/src/services/release.d.ts.map +1 -1
  79. package/dist/server/src/services/settings.d.ts +13 -0
  80. package/dist/server/src/services/settings.d.ts.map +1 -0
  81. package/dist/server/src/services/validation.d.ts +1 -1
  82. package/dist/server/src/services/validation.d.ts.map +1 -1
  83. package/dist/server/src/utils/index.d.ts +29 -8
  84. package/dist/server/src/utils/index.d.ts.map +1 -1
  85. package/dist/shared/contracts/release-actions.d.ts +9 -10
  86. package/dist/shared/contracts/release-actions.d.ts.map +1 -1
  87. package/dist/shared/contracts/releases.d.ts +9 -7
  88. package/dist/shared/contracts/releases.d.ts.map +1 -1
  89. package/dist/shared/contracts/settings.d.ts +39 -0
  90. package/dist/shared/contracts/settings.d.ts.map +1 -0
  91. package/package.json +10 -9
  92. package/dist/_chunks/App-CqbuK4M6.js.map +0 -1
  93. package/dist/_chunks/App-Do-Rnv0A.mjs.map +0 -1
  94. package/dist/_chunks/en-B9Ur3VsE.mjs.map +0 -1
  95. package/dist/_chunks/en-DtFJ5ViE.js.map +0 -1
  96. package/dist/_chunks/index-D_pgdqQL.mjs.map +0 -1
  97. package/dist/_chunks/index-Tedsw4GC.js.map +0 -1
  98. package/dist/shared/validation-schemas.d.ts +0 -2
  99. package/dist/shared/validation-schemas.d.ts.map +0 -1
@@ -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),
@@ -102,6 +116,38 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 }) =
102
116
  return false;
103
117
  }
104
118
  };
119
+ const getEntry = async ({
120
+ contentType,
121
+ documentId,
122
+ locale,
123
+ populate,
124
+ status = "draft"
125
+ }, { strapi: strapi2 }) => {
126
+ if (documentId) {
127
+ return strapi2.documents(contentType).findOne({ documentId, locale, populate, status });
128
+ }
129
+ return strapi2.documents(contentType).findFirst({ locale, populate, status });
130
+ };
131
+ const getEntryStatus = async (contentType, entry) => {
132
+ if (entry.publishedAt) {
133
+ return "published";
134
+ }
135
+ const publishedEntry = await strapi.documents(contentType).findOne({
136
+ documentId: entry.documentId,
137
+ locale: entry.locale,
138
+ status: "published",
139
+ fields: ["updatedAt"]
140
+ });
141
+ if (!publishedEntry) {
142
+ return "draft";
143
+ }
144
+ const entryUpdatedAt = new Date(entry.updatedAt).getTime();
145
+ const publishedEntryUpdatedAt = new Date(publishedEntry.updatedAt).getTime();
146
+ if (entryUpdatedAt > publishedEntryUpdatedAt) {
147
+ return "modified";
148
+ }
149
+ return "published";
150
+ };
105
151
  async function deleteActionsOnDisableDraftAndPublish({
106
152
  oldContentTypes,
107
153
  contentTypes: contentTypes2
@@ -147,20 +193,22 @@ async function migrateIsValidAndStatusReleases() {
147
193
  const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
148
194
  for (const action of notValidatedActions) {
149
195
  if (action.entry) {
150
- const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
151
- strapi
196
+ const isEntryValid2 = getDraftEntryValidStatus(
197
+ {
198
+ contentType: action.contentType,
199
+ documentId: action.entryDocumentId,
200
+ locale: action.locale
201
+ },
202
+ { strapi }
203
+ );
204
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
205
+ where: {
206
+ id: action.id
207
+ },
208
+ data: {
209
+ isEntryValid: isEntryValid2
210
+ }
152
211
  });
153
- if (populatedEntry) {
154
- const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
155
- await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
156
- where: {
157
- id: action.id
158
- },
159
- data: {
160
- isEntryValid
161
- }
162
- });
163
- }
164
212
  }
165
213
  }
166
214
  return getService("release", { strapi }).updateReleaseStatus(release2.id);
@@ -204,24 +252,24 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
204
252
  }
205
253
  });
206
254
  await utils.async.map(actions, async (action) => {
207
- if (action.entry && action.release) {
208
- const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
209
- strapi
255
+ if (action.entry && action.release && action.type === "publish") {
256
+ const isEntryValid2 = await getDraftEntryValidStatus(
257
+ {
258
+ contentType: contentTypeUID,
259
+ documentId: action.entryDocumentId,
260
+ locale: action.locale
261
+ },
262
+ { strapi }
263
+ );
264
+ releasesAffected.add(action.release.id);
265
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
266
+ where: {
267
+ id: action.id
268
+ },
269
+ data: {
270
+ isEntryValid: isEntryValid2
271
+ }
210
272
  });
211
- if (populatedEntry) {
212
- const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
213
- strapi
214
- });
215
- releasesAffected.add(action.release.id);
216
- await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
217
- where: {
218
- id: action.id
219
- },
220
- data: {
221
- isEntryValid
222
- }
223
- });
224
- }
225
273
  }
226
274
  });
227
275
  }
@@ -278,9 +326,38 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
278
326
  }
279
327
  }
280
328
  }
329
+ const addEntryDocumentToReleaseActions = {
330
+ name: "content-releases::5.0.0-add-entry-document-id-to-release-actions",
331
+ async up(trx, db) {
332
+ const hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
333
+ if (hasPolymorphicColumn) {
334
+ const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
335
+ "strapi_release_actions",
336
+ "entry_document_id"
337
+ );
338
+ if (!hasEntryDocumentIdColumn) {
339
+ await trx.schema.alterTable("strapi_release_actions", (table) => {
340
+ table.string("entry_document_id");
341
+ });
342
+ }
343
+ const releaseActions = await trx.select("*").from("strapi_release_actions");
344
+ utils.async.map(releaseActions, async (action) => {
345
+ const { target_type, target_id } = action;
346
+ const entry = await db.query(target_type).findOne({ where: { id: target_id } });
347
+ if (entry) {
348
+ await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
349
+ }
350
+ });
351
+ }
352
+ },
353
+ async down() {
354
+ throw new Error("not implemented");
355
+ }
356
+ };
281
357
  const register = async ({ strapi: strapi2 }) => {
282
358
  if (strapi2.ee.features.isEnabled("cms-content-releases")) {
283
359
  await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
360
+ strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
284
361
  strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
285
362
  strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
286
363
  }
@@ -290,6 +367,104 @@ const register = async ({ strapi: strapi2 }) => {
290
367
  graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
291
368
  }
292
369
  };
370
+ const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
371
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
372
+ where: {
373
+ actions: {
374
+ contentType,
375
+ entryDocumentId: entry.documentId,
376
+ locale: entry.locale
377
+ }
378
+ }
379
+ });
380
+ const entryStatus = await isEntryValid(contentType, entry, { strapi });
381
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
382
+ where: {
383
+ contentType,
384
+ entryDocumentId: entry.documentId,
385
+ locale: entry.locale
386
+ },
387
+ data: {
388
+ isEntryValid: entryStatus
389
+ }
390
+ });
391
+ for (const release2 of releases) {
392
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
393
+ }
394
+ };
395
+ const deleteActionsAndUpdateReleaseStatus = async (params) => {
396
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
397
+ where: {
398
+ actions: params
399
+ }
400
+ });
401
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
402
+ where: params
403
+ });
404
+ for (const release2 of releases) {
405
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
406
+ }
407
+ };
408
+ const deleteActionsOnDelete = async (ctx, next) => {
409
+ if (ctx.action !== "delete") {
410
+ return next();
411
+ }
412
+ if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
413
+ return next();
414
+ }
415
+ const contentType = ctx.contentType.uid;
416
+ const { documentId, locale } = ctx.params;
417
+ const result = await next();
418
+ if (!result) {
419
+ return result;
420
+ }
421
+ try {
422
+ deleteActionsAndUpdateReleaseStatus({
423
+ contentType,
424
+ entryDocumentId: documentId,
425
+ ...locale !== "*" && { locale }
426
+ });
427
+ } catch (error) {
428
+ strapi.log.error("Error while deleting release actions after delete", {
429
+ error
430
+ });
431
+ }
432
+ return result;
433
+ };
434
+ const updateActionsOnUpdate = async (ctx, next) => {
435
+ if (ctx.action !== "update") {
436
+ return next();
437
+ }
438
+ if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
439
+ return next();
440
+ }
441
+ const contentType = ctx.contentType.uid;
442
+ const result = await next();
443
+ if (!result) {
444
+ return result;
445
+ }
446
+ try {
447
+ updateActionsStatusAndUpdateReleaseStatus(contentType, result);
448
+ } catch (error) {
449
+ strapi.log.error("Error while updating release actions after update", {
450
+ error
451
+ });
452
+ }
453
+ return result;
454
+ };
455
+ const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
456
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
457
+ where: {
458
+ actions: params
459
+ }
460
+ });
461
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
462
+ where: params
463
+ });
464
+ for (const release2 of releases) {
465
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
466
+ }
467
+ };
293
468
  const bootstrap = async ({ strapi: strapi2 }) => {
294
469
  if (strapi2.ee.features.isEnabled("cms-content-releases")) {
295
470
  const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
@@ -297,115 +472,29 @@ const bootstrap = async ({ strapi: strapi2 }) => {
297
472
  );
298
473
  strapi2.db.lifecycles.subscribe({
299
474
  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
475
  /**
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
- /**
340
- * We delete the release actions related to deleted entries
341
- * We make this only after deleteMany is succesfully executed to avoid errors
476
+ * deleteMany is still used outside documents service, for example when deleting a locale
342
477
  */
343
478
  async afterDeleteMany(event) {
344
479
  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
- }
480
+ const model = strapi2.getModel(event.model.uid);
481
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
482
+ const { where } = event.params;
483
+ deleteReleasesActionsAndUpdateReleaseStatus({
484
+ contentType: model.uid,
485
+ locale: where.locale ?? null,
486
+ ...where.documentId && { entryDocumentId: where.documentId }
365
487
  });
366
- for (const release2 of releases) {
367
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
368
- }
369
488
  }
370
489
  } catch (error) {
371
490
  strapi2.log.error("Error while deleting release actions after entry deleteMany", {
372
491
  error
373
492
  });
374
493
  }
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
494
  }
408
495
  });
496
+ strapi2.documents.use(deleteActionsOnDelete);
497
+ strapi2.documents.use(updateActionsOnUpdate);
409
498
  getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
410
499
  strapi2.log.error(
411
500
  "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
@@ -497,15 +586,13 @@ const schema = {
497
586
  enum: ["publish", "unpublish"],
498
587
  required: true
499
588
  },
500
- entry: {
501
- type: "relation",
502
- relation: "morphToOne",
503
- configurable: false
504
- },
505
589
  contentType: {
506
590
  type: "string",
507
591
  required: true
508
592
  },
593
+ entryDocumentId: {
594
+ type: "string"
595
+ },
509
596
  locale: {
510
597
  type: "string"
511
598
  },
@@ -527,18 +614,6 @@ const contentTypes = {
527
614
  release: release$1,
528
615
  "release-action": releaseAction$1
529
616
  };
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
617
  const createReleaseService = ({ strapi: strapi2 }) => {
543
618
  const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
544
619
  strapi2.eventHub.emit(event, {
@@ -547,93 +622,32 @@ const createReleaseService = ({ strapi: strapi2 }) => {
547
622
  release: release2
548
623
  });
549
624
  };
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
625
  const getFormattedActions = async (releaseId) => {
597
626
  const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
598
627
  where: {
599
628
  release: {
600
629
  id: releaseId
601
630
  }
602
- },
603
- populate: {
604
- entry: {
605
- fields: ["id"]
606
- }
607
631
  }
608
632
  });
609
633
  if (actions.length === 0) {
610
634
  throw new utils.errors.ValidationError("No entries to publish");
611
635
  }
612
- const collectionTypeActions = {};
613
- const singleTypeActions = [];
636
+ const formattedActions = {};
614
637
  for (const action of actions) {
615
638
  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
- });
639
+ if (!formattedActions[contentTypeUid]) {
640
+ formattedActions[contentTypeUid] = {
641
+ publish: [],
642
+ unpublish: []
643
+ };
634
644
  }
645
+ formattedActions[contentTypeUid][action.type].push({
646
+ documentId: action.entryDocumentId,
647
+ locale: action.locale
648
+ });
635
649
  }
636
- return { collectionTypeActions, singleTypeActions };
650
+ return formattedActions;
637
651
  };
638
652
  return {
639
653
  async create(releaseData, { user }) {
@@ -680,91 +694,10 @@ const createReleaseService = ({ strapi: strapi2 }) => {
680
694
  }
681
695
  });
682
696
  },
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;
697
+ findMany(query) {
698
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
699
+ return strapi2.db.query(RELEASE_MODEL_UID).findMany({
700
+ ...dbQuery
768
701
  });
769
702
  },
770
703
  async update(id, releaseData, { user }) {
@@ -800,12 +733,208 @@ const createReleaseService = ({ strapi: strapi2 }) => {
800
733
  strapi2.telemetry.send("didUpdateContentRelease");
801
734
  return updatedRelease;
802
735
  },
803
- async createAction(releaseId, action) {
804
- const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
736
+ async getAllComponents() {
737
+ const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
738
+ const components = await contentManagerComponentsService.findAllComponents();
739
+ const componentsMap = components.reduce(
740
+ (acc, component) => {
741
+ acc[component.uid] = component;
742
+ return acc;
743
+ },
744
+ {}
745
+ );
746
+ return componentsMap;
747
+ },
748
+ async delete(releaseId) {
749
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
750
+ where: { id: releaseId },
751
+ populate: {
752
+ actions: {
753
+ select: ["id"]
754
+ }
755
+ }
756
+ });
757
+ if (!release2) {
758
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
759
+ }
760
+ if (release2.releasedAt) {
761
+ throw new utils.errors.ValidationError("Release already published");
762
+ }
763
+ await strapi2.db.transaction(async () => {
764
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
765
+ where: {
766
+ id: {
767
+ $in: release2.actions.map((action) => action.id)
768
+ }
769
+ }
770
+ });
771
+ await strapi2.db.query(RELEASE_MODEL_UID).delete({
772
+ where: {
773
+ id: releaseId
774
+ }
775
+ });
776
+ });
777
+ if (release2.scheduledAt) {
778
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
779
+ await schedulingService.cancel(release2.id);
780
+ }
781
+ strapi2.telemetry.send("didDeleteContentRelease");
782
+ return release2;
783
+ },
784
+ async publish(releaseId) {
785
+ const {
786
+ release: release2,
787
+ error
788
+ } = await strapi2.db.transaction(async ({ trx }) => {
789
+ const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
790
+ if (!lockedRelease) {
791
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
792
+ }
793
+ if (lockedRelease.releasedAt) {
794
+ throw new utils.errors.ValidationError("Release already published");
795
+ }
796
+ if (lockedRelease.status === "failed") {
797
+ throw new utils.errors.ValidationError("Release failed to publish");
798
+ }
799
+ try {
800
+ strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
801
+ const formattedActions = await getFormattedActions(releaseId);
802
+ await strapi2.db.transaction(
803
+ async () => Promise.all(
804
+ Object.keys(formattedActions).map(async (contentTypeUid) => {
805
+ const contentType = contentTypeUid;
806
+ const { publish, unpublish } = formattedActions[contentType];
807
+ return Promise.all([
808
+ ...publish.map((params) => strapi2.documents(contentType).publish(params)),
809
+ ...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
810
+ ]);
811
+ })
812
+ )
813
+ );
814
+ const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
815
+ where: {
816
+ id: releaseId
817
+ },
818
+ data: {
819
+ status: "done",
820
+ releasedAt: /* @__PURE__ */ new Date()
821
+ }
822
+ });
823
+ dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
824
+ isPublished: true,
825
+ release: release22
826
+ });
827
+ strapi2.telemetry.send("didPublishContentRelease");
828
+ return { release: release22, error: null };
829
+ } catch (error2) {
830
+ dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
831
+ isPublished: false,
832
+ error: error2
833
+ });
834
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
835
+ status: "failed"
836
+ }).transacting(trx).execute();
837
+ return {
838
+ release: null,
839
+ error: error2
840
+ };
841
+ }
842
+ });
843
+ if (error instanceof Error) {
844
+ throw error;
845
+ }
846
+ return release2;
847
+ },
848
+ async updateReleaseStatus(releaseId) {
849
+ const releaseActionService = getService("release-action", { strapi: strapi2 });
850
+ const [totalActions, invalidActions] = await Promise.all([
851
+ releaseActionService.countActions({
852
+ filters: {
853
+ release: releaseId
854
+ }
855
+ }),
856
+ releaseActionService.countActions({
857
+ filters: {
858
+ release: releaseId,
859
+ isEntryValid: false
860
+ }
861
+ })
862
+ ]);
863
+ if (totalActions > 0) {
864
+ if (invalidActions > 0) {
865
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
866
+ where: {
867
+ id: releaseId
868
+ },
869
+ data: {
870
+ status: "blocked"
871
+ }
872
+ });
873
+ }
874
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
875
+ where: {
876
+ id: releaseId
877
+ },
878
+ data: {
879
+ status: "ready"
880
+ }
881
+ });
882
+ }
883
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
884
+ where: {
885
+ id: releaseId
886
+ },
887
+ data: {
888
+ status: "empty"
889
+ }
890
+ });
891
+ }
892
+ };
893
+ };
894
+ const getGroupName = (queryValue) => {
895
+ switch (queryValue) {
896
+ case "contentType":
897
+ return "contentType.displayName";
898
+ case "type":
899
+ return "type";
900
+ case "locale":
901
+ return ___default.default.getOr("No locale", "locale.name");
902
+ default:
903
+ return "contentType.displayName";
904
+ }
905
+ };
906
+ const createReleaseActionService = ({ strapi: strapi2 }) => {
907
+ const getLocalesDataForActions = async () => {
908
+ if (!strapi2.plugin("i18n")) {
909
+ return {};
910
+ }
911
+ const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
912
+ return allLocales.reduce((acc, locale) => {
913
+ acc[locale.code] = { name: locale.name, code: locale.code };
914
+ return acc;
915
+ }, {});
916
+ };
917
+ const getContentTypesDataForActions = async (contentTypesUids) => {
918
+ const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
919
+ const contentTypesData = {};
920
+ for (const contentTypeUid of contentTypesUids) {
921
+ const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
922
+ uid: contentTypeUid
923
+ });
924
+ contentTypesData[contentTypeUid] = {
925
+ mainField: contentTypeConfig.settings.mainField,
926
+ displayName: strapi2.getModel(contentTypeUid).info.displayName
927
+ };
928
+ }
929
+ return contentTypesData;
930
+ };
931
+ return {
932
+ async create(releaseId, action) {
933
+ const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
805
934
  strapi: strapi2
806
935
  });
807
936
  await Promise.all([
808
- validateEntryContentType(action.entry.contentType),
937
+ validateEntryData(action.contentType, action.entryDocumentId),
809
938
  validateUniqueEntry(releaseId, action)
810
939
  ]);
811
940
  const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
@@ -815,28 +944,28 @@ const createReleaseService = ({ strapi: strapi2 }) => {
815
944
  if (release2.releasedAt) {
816
945
  throw new utils.errors.ValidationError("Release already published");
817
946
  }
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 });
947
+ const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
948
+ {
949
+ contentType: action.contentType,
950
+ documentId: action.entryDocumentId,
951
+ locale: action.locale
952
+ },
953
+ {
954
+ strapi: strapi2
955
+ }
956
+ ) : true;
821
957
  const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
822
958
  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
959
+ ...action,
960
+ release: release2.id,
961
+ isEntryValid: actionStatus
833
962
  },
834
- populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
963
+ populate: { release: { select: ["id"] } }
835
964
  });
836
- this.updateReleaseStatus(releaseId);
965
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
837
966
  return releaseAction2;
838
967
  },
839
- async findActions(releaseId, query) {
968
+ async findPage(releaseId, query) {
840
969
  const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
841
970
  where: { id: releaseId },
842
971
  select: ["id"]
@@ -845,21 +974,35 @@ const createReleaseService = ({ strapi: strapi2 }) => {
845
974
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
846
975
  }
847
976
  const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
848
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
977
+ const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
849
978
  ...dbQuery,
850
- populate: {
851
- entry: {
852
- populate: "*"
853
- }
854
- },
855
979
  where: {
856
980
  release: releaseId
857
981
  }
858
982
  });
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);
983
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
984
+ const actionsWithEntry = await utils.async.map(actions, async (action) => {
985
+ const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
986
+ const entry = await getEntry(
987
+ {
988
+ contentType: action.contentType,
989
+ documentId: action.entryDocumentId,
990
+ locale: action.locale,
991
+ populate,
992
+ status: action.type === "publish" ? "draft" : "published"
993
+ },
994
+ { strapi: strapi2 }
995
+ );
996
+ return {
997
+ ...action,
998
+ entry,
999
+ status: entry ? await getEntryStatus(action.contentType, entry) : null
1000
+ };
1001
+ });
1002
+ return {
1003
+ results: actionsWithEntry,
1004
+ pagination
1005
+ };
863
1006
  },
864
1007
  async groupActions(actions, groupBy) {
865
1008
  const contentTypeUids = actions.reduce((acc, action) => {
@@ -868,8 +1011,8 @@ const createReleaseService = ({ strapi: strapi2 }) => {
868
1011
  }
869
1012
  return acc;
870
1013
  }, []);
871
- const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(contentTypeUids);
872
- const allLocalesDictionary = await this.getLocalesDataForActions();
1014
+ const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
1015
+ const allLocalesDictionary = await getLocalesDataForActions();
873
1016
  const formattedData = actions.map((action) => {
874
1017
  const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
875
1018
  return {
@@ -880,165 +1023,58 @@ const createReleaseService = ({ strapi: strapi2 }) => {
880
1023
  mainFieldValue: action.entry[mainField],
881
1024
  uid: action.contentType
882
1025
  }
883
- };
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
- });
1026
+ };
968
1027
  });
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;
1028
+ const groupName = getGroupName(groupBy);
1029
+ return ___default.default.groupBy(groupName)(formattedData);
975
1030
  },
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");
1031
+ getContentTypeModelsFromActions(actions) {
1032
+ const contentTypeUids = actions.reduce((acc, action) => {
1033
+ if (!acc.includes(action.contentType)) {
1034
+ acc.push(action.contentType);
990
1035
  }
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()
1036
+ return acc;
1037
+ }, []);
1038
+ const contentTypeModelsMap = contentTypeUids.reduce(
1039
+ (acc, contentTypeUid) => {
1040
+ acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
1041
+ return acc;
1042
+ },
1043
+ {}
1044
+ );
1045
+ return contentTypeModelsMap;
1046
+ },
1047
+ async countActions(query) {
1048
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
1049
+ return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
1050
+ },
1051
+ async update(actionId, releaseId, update) {
1052
+ const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
1053
+ where: {
1054
+ id: actionId,
1055
+ release: {
1056
+ id: releaseId,
1057
+ releasedAt: {
1058
+ $null: true
1014
1059
  }
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
1026
- });
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
1033
- };
1060
+ }
1034
1061
  }
1035
1062
  });
1036
- if (error instanceof Error) {
1037
- throw error;
1063
+ if (!action) {
1064
+ throw new utils.errors.NotFoundError(
1065
+ `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1066
+ );
1038
1067
  }
1039
- return release2;
1040
- },
1041
- async updateAction(actionId, releaseId, update) {
1068
+ const actionStatus = update.type === "publish" ? getDraftEntryValidStatus(
1069
+ {
1070
+ contentType: action.contentType,
1071
+ documentId: action.entryDocumentId,
1072
+ locale: action.locale
1073
+ },
1074
+ {
1075
+ strapi: strapi2
1076
+ }
1077
+ ) : true;
1042
1078
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
1043
1079
  where: {
1044
1080
  id: actionId,
@@ -1049,16 +1085,15 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1049
1085
  }
1050
1086
  }
1051
1087
  },
1052
- data: update
1088
+ data: {
1089
+ ...update,
1090
+ isEntryValid: actionStatus
1091
+ }
1053
1092
  });
1054
- if (!updatedAction) {
1055
- throw new utils.errors.NotFoundError(
1056
- `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1057
- );
1058
- }
1093
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1059
1094
  return updatedAction;
1060
1095
  },
1061
- async deleteAction(actionId, releaseId) {
1096
+ async delete(actionId, releaseId) {
1062
1097
  const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
1063
1098
  where: {
1064
1099
  id: actionId,
@@ -1075,51 +1110,8 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1075
1110
  `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1076
1111
  );
1077
1112
  }
1078
- this.updateReleaseStatus(releaseId);
1113
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1079
1114
  return deletedAction;
1080
- },
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"
1103
- }
1104
- });
1105
- }
1106
- return strapi2.db.query(RELEASE_MODEL_UID).update({
1107
- where: {
1108
- id: releaseId
1109
- },
1110
- data: {
1111
- status: "ready"
1112
- }
1113
- });
1114
- }
1115
- return strapi2.db.query(RELEASE_MODEL_UID).update({
1116
- where: {
1117
- id: releaseId
1118
- },
1119
- data: {
1120
- status: "empty"
1121
- }
1122
- });
1123
1115
  }
1124
1116
  };
1125
1117
  };
@@ -1135,30 +1127,35 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1135
1127
  where: {
1136
1128
  id: releaseId
1137
1129
  },
1138
- populate: { actions: { populate: { entry: { select: ["id"] } } } }
1130
+ populate: {
1131
+ actions: true
1132
+ }
1139
1133
  });
1140
1134
  if (!release2) {
1141
1135
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
1142
1136
  }
1143
1137
  const isEntryInRelease = release2.actions.some(
1144
- (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1138
+ (action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
1145
1139
  );
1146
1140
  if (isEntryInRelease) {
1147
1141
  throw new AlreadyOnReleaseError(
1148
- `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1142
+ `Entry with documentId ${releaseActionArgs.entryDocumentId}${releaseActionArgs.locale ? `( ${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
1149
1143
  );
1150
1144
  }
1151
1145
  },
1152
- validateEntryContentType(contentTypeUid) {
1146
+ validateEntryData(contentTypeUid, entryDocumentId) {
1153
1147
  const contentType = strapi2.contentType(contentTypeUid);
1154
1148
  if (!contentType) {
1155
1149
  throw new utils.errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1156
1150
  }
1157
- if (!contentType.options?.draftAndPublish) {
1151
+ if (!utils.contentTypes.hasDraftAndPublish(contentType)) {
1158
1152
  throw new utils.errors.ValidationError(
1159
1153
  `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1160
1154
  );
1161
1155
  }
1156
+ if (contentType.kind === "collectionType" && !entryDocumentId) {
1157
+ throw new utils.errors.ValidationError("Document id is required for collection type");
1158
+ }
1162
1159
  },
1163
1160
  async validatePendingReleasesLimit() {
1164
1161
  const featureCfg = strapi2.ee.features.get("cms-content-releases");
@@ -1247,78 +1244,152 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1247
1244
  }
1248
1245
  };
1249
1246
  };
1247
+ const DEFAULT_SETTINGS = {
1248
+ defaultTimezone: null
1249
+ };
1250
+ const createSettingsService = ({ strapi: strapi2 }) => {
1251
+ const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
1252
+ return {
1253
+ async update({ settings: settings2 }) {
1254
+ const store = await getStore();
1255
+ store.set({ key: "settings", value: settings2 });
1256
+ return settings2;
1257
+ },
1258
+ async find() {
1259
+ const store = await getStore();
1260
+ const settings2 = await store.get({ key: "settings" });
1261
+ return {
1262
+ ...DEFAULT_SETTINGS,
1263
+ ...settings2 || {}
1264
+ };
1265
+ }
1266
+ };
1267
+ };
1250
1268
  const services = {
1251
1269
  release: createReleaseService,
1270
+ "release-action": createReleaseActionService,
1252
1271
  "release-validation": createReleaseValidationService,
1253
- scheduling: createSchedulingService
1272
+ scheduling: createSchedulingService,
1273
+ settings: createSettingsService
1254
1274
  };
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()
1275
+ const RELEASE_SCHEMA = utils.yup.object().shape({
1276
+ name: utils.yup.string().trim().required(),
1277
+ scheduledAt: utils.yup.string().nullable(),
1278
+ timezone: utils.yup.string().when("scheduledAt", {
1279
+ is: (value) => value !== null && value !== void 0,
1280
+ then: utils.yup.string().required(),
1281
+ otherwise: utils.yup.string().nullable()
1273
1282
  })
1274
1283
  }).required().noUnknown();
1284
+ const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = utils.yup.object().shape({
1285
+ contentType: utils.yup.string().required(),
1286
+ entryDocumentId: utils.yup.string().nullable(),
1287
+ hasEntryAttached: utils.yup.string().nullable(),
1288
+ locale: utils.yup.string().nullable()
1289
+ }).required().noUnknown();
1275
1290
  const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
1291
+ const validatefindByDocumentAttachedParams = utils.validateYupSchema(
1292
+ FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
1293
+ );
1276
1294
  const releaseController = {
1277
- async findMany(ctx) {
1295
+ /**
1296
+ * Find releases based on documents attached or not to the release.
1297
+ * If `hasEntryAttached` is true, it will return all releases that have the entry attached.
1298
+ * If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
1299
+ */
1300
+ async findByDocumentAttached(ctx) {
1278
1301
  const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1279
1302
  ability: ctx.state.userAbility,
1280
1303
  model: RELEASE_MODEL_UID
1281
1304
  });
1282
1305
  await permissionsManager.validateQuery(ctx.query);
1283
1306
  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 };
1307
+ const query = await permissionsManager.sanitizeQuery(ctx.query);
1308
+ await validatefindByDocumentAttachedParams(query);
1309
+ const { contentType, entryDocumentId, hasEntryAttached, locale } = query;
1310
+ const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
1311
+ if (isEntryAttached) {
1312
+ const releases = await releaseService.findMany({
1313
+ where: {
1314
+ releasedAt: null,
1315
+ actions: {
1316
+ contentType,
1317
+ entryDocumentId: entryDocumentId ?? null,
1318
+ locale: locale ?? null
1319
+ }
1320
+ },
1321
+ populate: {
1322
+ actions: {
1323
+ fields: ["type"]
1324
+ }
1325
+ }
1326
+ });
1327
+ ctx.body = { data: releases };
1292
1328
  } 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,
1329
+ const relatedReleases = await releaseService.findMany({
1330
+ where: {
1331
+ releasedAt: null,
1299
1332
  actions: {
1300
- meta: {
1301
- count: actions.count
1302
- }
1333
+ contentType,
1334
+ entryDocumentId: entryDocumentId ?? null,
1335
+ locale: locale ?? null
1303
1336
  }
1304
- };
1337
+ }
1305
1338
  });
1306
- const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1339
+ const releases = await releaseService.findMany({
1307
1340
  where: {
1341
+ $or: [
1342
+ {
1343
+ id: {
1344
+ $notIn: relatedReleases.map((release2) => release2.id)
1345
+ }
1346
+ },
1347
+ {
1348
+ actions: null
1349
+ }
1350
+ ],
1308
1351
  releasedAt: null
1309
1352
  }
1310
1353
  });
1311
- ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1354
+ ctx.body = { data: releases };
1312
1355
  }
1313
1356
  },
1357
+ async findPage(ctx) {
1358
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1359
+ ability: ctx.state.userAbility,
1360
+ model: RELEASE_MODEL_UID
1361
+ });
1362
+ await permissionsManager.validateQuery(ctx.query);
1363
+ const releaseService = getService("release", { strapi });
1364
+ const query = await permissionsManager.sanitizeQuery(ctx.query);
1365
+ const { results, pagination } = await releaseService.findPage(query);
1366
+ const data = results.map((release2) => {
1367
+ const { actions, ...releaseData } = release2;
1368
+ return {
1369
+ ...releaseData,
1370
+ actions: {
1371
+ meta: {
1372
+ count: actions.count
1373
+ }
1374
+ }
1375
+ };
1376
+ });
1377
+ const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1378
+ where: {
1379
+ releasedAt: null
1380
+ }
1381
+ });
1382
+ ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1383
+ },
1314
1384
  async findOne(ctx) {
1315
1385
  const id = ctx.params.id;
1316
1386
  const releaseService = getService("release", { strapi });
1387
+ const releaseActionService = getService("release-action", { strapi });
1317
1388
  const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
1318
1389
  if (!release2) {
1319
1390
  throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
1320
1391
  }
1321
- const count = await releaseService.countActions({
1392
+ const count = await releaseActionService.countActions({
1322
1393
  filters: {
1323
1394
  release: id
1324
1395
  }
@@ -1338,28 +1409,43 @@ const releaseController = {
1338
1409
  ctx.body = { data };
1339
1410
  },
1340
1411
  async mapEntriesToReleases(ctx) {
1341
- const { contentTypeUid, entriesIds } = ctx.query;
1342
- if (!contentTypeUid || !entriesIds) {
1412
+ const { contentTypeUid, documentIds, locale } = ctx.query;
1413
+ if (!contentTypeUid || !documentIds) {
1343
1414
  throw new utils.errors.ValidationError("Missing required query parameters");
1344
1415
  }
1345
1416
  const releaseService = getService("release", { strapi });
1346
- const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
1347
- contentTypeUid,
1348
- entriesIds
1349
- );
1417
+ const releasesWithActions = await releaseService.findMany({
1418
+ where: {
1419
+ releasedAt: null,
1420
+ actions: {
1421
+ contentType: contentTypeUid,
1422
+ entryDocumentId: {
1423
+ $in: documentIds
1424
+ },
1425
+ locale
1426
+ }
1427
+ },
1428
+ populate: {
1429
+ actions: true
1430
+ }
1431
+ });
1350
1432
  const mappedEntriesInReleases = releasesWithActions.reduce(
1351
- // TODO: Fix for v5 removed mappedEntriedToRelease
1352
1433
  (acc, release2) => {
1353
1434
  release2.actions.forEach((action) => {
1354
- if (!acc[action.entry.id]) {
1355
- acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
1435
+ if (action.contentType !== contentTypeUid) {
1436
+ return;
1437
+ }
1438
+ if (locale && action.locale !== locale) {
1439
+ return;
1440
+ }
1441
+ if (!acc[action.entryDocumentId]) {
1442
+ acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
1356
1443
  } else {
1357
- acc[action.entry.id].push({ id: release2.id, name: release2.name });
1444
+ acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
1358
1445
  }
1359
1446
  });
1360
1447
  return acc;
1361
1448
  },
1362
- // TODO: Fix for v5 removed mappedEntriedToRelease
1363
1449
  {}
1364
1450
  );
1365
1451
  ctx.body = {
@@ -1404,18 +1490,18 @@ const releaseController = {
1404
1490
  };
1405
1491
  },
1406
1492
  async publish(ctx) {
1407
- const user = ctx.state.user;
1408
1493
  const id = ctx.params.id;
1409
1494
  const releaseService = getService("release", { strapi });
1410
- const release2 = await releaseService.publish(id, { user });
1495
+ const releaseActionService = getService("release-action", { strapi });
1496
+ const release2 = await releaseService.publish(id);
1411
1497
  const [countPublishActions, countUnpublishActions] = await Promise.all([
1412
- releaseService.countActions({
1498
+ releaseActionService.countActions({
1413
1499
  filters: {
1414
1500
  release: id,
1415
1501
  type: "publish"
1416
1502
  }
1417
1503
  }),
1418
- releaseService.countActions({
1504
+ releaseActionService.countActions({
1419
1505
  filters: {
1420
1506
  release: id,
1421
1507
  type: "unpublish"
@@ -1433,24 +1519,27 @@ const releaseController = {
1433
1519
  }
1434
1520
  };
1435
1521
  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(),
1522
+ contentType: utils.yup.string().required(),
1523
+ entryDocumentId: utils.yup.strapiID(),
1524
+ locale: utils.yup.string(),
1440
1525
  type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
1441
1526
  });
1442
1527
  const RELEASE_ACTION_UPDATE_SCHEMA = utils.yup.object().shape({
1443
1528
  type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
1444
1529
  });
1530
+ const FIND_MANY_ACTIONS_PARAMS = utils.yup.object().shape({
1531
+ groupBy: utils.yup.string().oneOf(["action", "contentType", "locale"])
1532
+ });
1445
1533
  const validateReleaseAction = utils.validateYupSchema(RELEASE_ACTION_SCHEMA);
1446
1534
  const validateReleaseActionUpdateSchema = utils.validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
1535
+ const validateFindManyActionsParams = utils.validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
1447
1536
  const releaseActionController = {
1448
1537
  async create(ctx) {
1449
1538
  const releaseId = ctx.params.releaseId;
1450
1539
  const releaseActionArgs = ctx.request.body;
1451
1540
  await validateReleaseAction(releaseActionArgs);
1452
- const releaseService = getService("release", { strapi });
1453
- const releaseAction2 = await releaseService.createAction(releaseId, releaseActionArgs);
1541
+ const releaseActionService = getService("release-action", { strapi });
1542
+ const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
1454
1543
  ctx.created({
1455
1544
  data: releaseAction2
1456
1545
  });
@@ -1461,12 +1550,12 @@ const releaseActionController = {
1461
1550
  await Promise.all(
1462
1551
  releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1463
1552
  );
1464
- const releaseService = getService("release", { strapi });
1553
+ const releaseActionService = getService("release-action", { strapi });
1465
1554
  const releaseActions = await strapi.db.transaction(async () => {
1466
1555
  const releaseActions2 = await Promise.all(
1467
1556
  releaseActionsArgs.map(async (releaseActionArgs) => {
1468
1557
  try {
1469
- const action = await releaseService.createAction(releaseId, releaseActionArgs);
1558
+ const action = await releaseActionService.create(releaseId, releaseActionArgs);
1470
1559
  return action;
1471
1560
  } catch (error) {
1472
1561
  if (error instanceof AlreadyOnReleaseError) {
@@ -1493,10 +1582,17 @@ const releaseActionController = {
1493
1582
  ability: ctx.state.userAbility,
1494
1583
  model: RELEASE_ACTION_MODEL_UID
1495
1584
  });
1585
+ await validateFindManyActionsParams(ctx.query);
1586
+ if (ctx.query.groupBy) {
1587
+ if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
1588
+ ctx.badRequest("Invalid groupBy parameter");
1589
+ }
1590
+ }
1591
+ ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
1592
+ delete ctx.query.groupBy;
1496
1593
  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,
1594
+ const releaseActionService = getService("release-action", { strapi });
1595
+ const { results, pagination } = await releaseActionService.findPage(releaseId, {
1500
1596
  ...query
1501
1597
  });
1502
1598
  const contentTypeOutputSanitizers = results.reduce((acc, action) => {
@@ -1512,10 +1608,11 @@ const releaseActionController = {
1512
1608
  }, {});
1513
1609
  const sanitizedResults = await utils.async.map(results, async (action) => ({
1514
1610
  ...action,
1515
- entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1611
+ entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
1516
1612
  }));
1517
- const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
1518
- const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
1613
+ const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
1614
+ const contentTypes2 = releaseActionService.getContentTypeModelsFromActions(results);
1615
+ const releaseService = getService("release", { strapi });
1519
1616
  const components = await releaseService.getAllComponents();
1520
1617
  ctx.body = {
1521
1618
  data: groupedData,
@@ -1531,8 +1628,8 @@ const releaseActionController = {
1531
1628
  const releaseId = ctx.params.releaseId;
1532
1629
  const releaseActionUpdateArgs = ctx.request.body;
1533
1630
  await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
1534
- const releaseService = getService("release", { strapi });
1535
- const updatedAction = await releaseService.updateAction(
1631
+ const releaseActionService = getService("release-action", { strapi });
1632
+ const updatedAction = await releaseActionService.update(
1536
1633
  actionId,
1537
1634
  releaseId,
1538
1635
  releaseActionUpdateArgs
@@ -1544,14 +1641,36 @@ const releaseActionController = {
1544
1641
  async delete(ctx) {
1545
1642
  const actionId = ctx.params.actionId;
1546
1643
  const releaseId = ctx.params.releaseId;
1547
- const releaseService = getService("release", { strapi });
1548
- const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
1644
+ const releaseActionService = getService("release-action", { strapi });
1645
+ const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
1549
1646
  ctx.body = {
1550
1647
  data: deletedReleaseAction
1551
1648
  };
1552
1649
  }
1553
1650
  };
1554
- const controllers = { release: releaseController, "release-action": releaseActionController };
1651
+ const SETTINGS_SCHEMA = yup__namespace.object().shape({
1652
+ defaultTimezone: yup__namespace.string().nullable().default(null)
1653
+ }).required().noUnknown();
1654
+ const validateSettings = utils.validateYupSchema(SETTINGS_SCHEMA);
1655
+ const settingsController = {
1656
+ async find(ctx) {
1657
+ const settingsService = getService("settings", { strapi });
1658
+ const settings2 = await settingsService.find();
1659
+ ctx.body = { data: settings2 };
1660
+ },
1661
+ async update(ctx) {
1662
+ const settingsBody = ctx.request.body;
1663
+ const settings2 = await validateSettings(settingsBody);
1664
+ const settingsService = getService("settings", { strapi });
1665
+ const updatedSettings = await settingsService.update({ settings: settings2 });
1666
+ ctx.body = { data: updatedSettings };
1667
+ }
1668
+ };
1669
+ const controllers = {
1670
+ release: releaseController,
1671
+ "release-action": releaseActionController,
1672
+ settings: settingsController
1673
+ };
1555
1674
  const release = {
1556
1675
  type: "admin",
1557
1676
  routes: [
@@ -1571,6 +1690,22 @@ const release = {
1571
1690
  ]
1572
1691
  }
1573
1692
  },
1693
+ {
1694
+ method: "GET",
1695
+ path: "/getByDocumentAttached",
1696
+ handler: "release.findByDocumentAttached",
1697
+ config: {
1698
+ policies: [
1699
+ "admin::isAuthenticatedAdmin",
1700
+ {
1701
+ name: "admin::hasPermissions",
1702
+ config: {
1703
+ actions: ["plugin::content-releases.read"]
1704
+ }
1705
+ }
1706
+ ]
1707
+ }
1708
+ },
1574
1709
  {
1575
1710
  method: "POST",
1576
1711
  path: "/",
@@ -1590,7 +1725,7 @@ const release = {
1590
1725
  {
1591
1726
  method: "GET",
1592
1727
  path: "/",
1593
- handler: "release.findMany",
1728
+ handler: "release.findPage",
1594
1729
  config: {
1595
1730
  policies: [
1596
1731
  "admin::isAuthenticatedAdmin",
@@ -1754,7 +1889,45 @@ const releaseAction = {
1754
1889
  }
1755
1890
  ]
1756
1891
  };
1892
+ const settings = {
1893
+ type: "admin",
1894
+ routes: [
1895
+ {
1896
+ method: "GET",
1897
+ path: "/settings",
1898
+ handler: "settings.find",
1899
+ config: {
1900
+ policies: [
1901
+ "admin::isAuthenticatedAdmin",
1902
+ {
1903
+ name: "admin::hasPermissions",
1904
+ config: {
1905
+ actions: ["plugin::content-releases.settings.read"]
1906
+ }
1907
+ }
1908
+ ]
1909
+ }
1910
+ },
1911
+ {
1912
+ method: "PUT",
1913
+ path: "/settings",
1914
+ handler: "settings.update",
1915
+ config: {
1916
+ policies: [
1917
+ "admin::isAuthenticatedAdmin",
1918
+ {
1919
+ name: "admin::hasPermissions",
1920
+ config: {
1921
+ actions: ["plugin::content-releases.settings.update"]
1922
+ }
1923
+ }
1924
+ ]
1925
+ }
1926
+ }
1927
+ ]
1928
+ };
1757
1929
  const routes = {
1930
+ settings,
1758
1931
  release,
1759
1932
  "release-action": releaseAction
1760
1933
  };