@strapi/content-releases 0.0.0-experimental.defd8568ae03ef8d52f86e1f3541979f953c3941 → 0.0.0-experimental.df298029ec6478763dcca7d59fafe8d2ae4ed60a

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 (110) hide show
  1. package/LICENSE +17 -1
  2. package/dist/_chunks/{App-C0DlH0im.js → App-BKB1esYS.js} +434 -405
  3. package/dist/_chunks/App-BKB1esYS.js.map +1 -0
  4. package/dist/_chunks/{App-O0ZO-S35.mjs → App-Cne--1Z8.mjs} +431 -400
  5. package/dist/_chunks/App-Cne--1Z8.mjs.map +1 -0
  6. package/dist/_chunks/{PurchaseContentReleases-DAHdUpAA.js → PurchaseContentReleases-Be3acS2L.js} +4 -3
  7. package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js.map +1 -0
  8. package/dist/_chunks/{PurchaseContentReleases-Ex09YpKR.mjs → PurchaseContentReleases-_MxP6-Dt.mjs} +5 -4
  9. package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs.map +1 -0
  10. package/dist/_chunks/ReleasesSettingsPage-C1WwGWIH.mjs +178 -0
  11. package/dist/_chunks/ReleasesSettingsPage-C1WwGWIH.mjs.map +1 -0
  12. package/dist/_chunks/ReleasesSettingsPage-kuXIwpWp.js +178 -0
  13. package/dist/_chunks/ReleasesSettingsPage-kuXIwpWp.js.map +1 -0
  14. package/dist/_chunks/{en-DtFJ5ViE.js → en-CmYoEnA7.js} +9 -2
  15. package/dist/_chunks/en-CmYoEnA7.js.map +1 -0
  16. package/dist/_chunks/{en-B9Ur3VsE.mjs → en-D0yVZFqf.mjs} +9 -2
  17. package/dist/_chunks/en-D0yVZFqf.mjs.map +1 -0
  18. package/dist/_chunks/{index-DoZNNtsb.js → index-5Odi61vw.js} +714 -595
  19. package/dist/_chunks/index-5Odi61vw.js.map +1 -0
  20. package/dist/_chunks/{index-DjDPK8kb.mjs → index-Cy7qwpaU.mjs} +723 -602
  21. package/dist/_chunks/index-Cy7qwpaU.mjs.map +1 -0
  22. package/dist/_chunks/schemas-BE1LxE9J.js +62 -0
  23. package/dist/_chunks/schemas-BE1LxE9J.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/ReleaseAction.d.ts +1 -1
  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 +786 -580
  42. package/dist/server/index.js.map +1 -1
  43. package/dist/server/index.mjs +787 -581
  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 +68 -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 +40 -38
  81. package/dist/server/src/services/index.d.ts.map +1 -1
  82. package/dist/server/src/services/release-action.d.ts +38 -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 +9 -10
  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 +22 -22
  99. package/dist/_chunks/App-C0DlH0im.js.map +0 -1
  100. package/dist/_chunks/App-O0ZO-S35.mjs.map +0 -1
  101. package/dist/_chunks/PurchaseContentReleases-DAHdUpAA.js.map +0 -1
  102. package/dist/_chunks/PurchaseContentReleases-Ex09YpKR.mjs.map +0 -1
  103. package/dist/_chunks/en-B9Ur3VsE.mjs.map +0 -1
  104. package/dist/_chunks/en-DtFJ5ViE.js.map +0 -1
  105. package/dist/_chunks/index-DjDPK8kb.mjs.map +0 -1
  106. package/dist/_chunks/index-DoZNNtsb.js.map +0 -1
  107. package/dist/admin/src/services/axios.d.ts +0 -29
  108. package/dist/shared/validation-schemas.d.ts +0 -2
  109. package/dist/shared/validation-schemas.d.ts.map +0 -1
  110. package/strapi-server.js +0 -3
@@ -71,6 +71,23 @@ const ACTIONS = [
71
71
  displayName: "Add an entry to a release",
72
72
  uid: "create-action",
73
73
  pluginName: "content-releases"
74
+ },
75
+ // Settings
76
+ {
77
+ uid: "settings.read",
78
+ section: "settings",
79
+ displayName: "Read",
80
+ category: "content releases",
81
+ subCategory: "options",
82
+ pluginName: "content-releases"
83
+ },
84
+ {
85
+ uid: "settings.update",
86
+ section: "settings",
87
+ displayName: "Edit",
88
+ category: "content releases",
89
+ subCategory: "options",
90
+ pluginName: "content-releases"
74
91
  }
75
92
  ];
76
93
  const ALLOWED_WEBHOOK_EVENTS = {
@@ -79,16 +96,13 @@ const ALLOWED_WEBHOOK_EVENTS = {
79
96
  const getService = (name, { strapi: strapi2 }) => {
80
97
  return strapi2.plugin("content-releases").service(name);
81
98
  };
82
- const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 }) => {
99
+ const getDraftEntryValidStatus = async ({ contentType, documentId, locale }, { strapi: strapi2 }) => {
83
100
  const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
84
- const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
85
- const entry = await strapi2.db.query(contentTypeUid).findOne({
86
- where: { id: entryId },
87
- populate
88
- });
89
- return entry;
101
+ const populate = await populateBuilderService(contentType).populateDeep(Infinity).build();
102
+ const entry = await getEntry({ contentType, documentId, locale, populate }, { strapi: strapi2 });
103
+ return isEntryValid(contentType, entry, { strapi: strapi2 });
90
104
  };
91
- const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 }) => {
105
+ const isEntryValid = async (contentTypeUid, entry, { strapi: strapi2 }) => {
92
106
  try {
93
107
  await strapi2.entityValidator.validateEntityCreation(
94
108
  strapi2.getModel(contentTypeUid),
@@ -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,42 @@ 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 hasTable = await trx.schema.hasTable("strapi_release_actions");
333
+ if (!hasTable) {
334
+ return;
335
+ }
336
+ const hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
337
+ if (hasPolymorphicColumn) {
338
+ const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
339
+ "strapi_release_actions",
340
+ "entry_document_id"
341
+ );
342
+ if (!hasEntryDocumentIdColumn) {
343
+ await trx.schema.alterTable("strapi_release_actions", (table) => {
344
+ table.string("entry_document_id");
345
+ });
346
+ }
347
+ const releaseActions = await trx.select("*").from("strapi_release_actions");
348
+ utils.async.map(releaseActions, async (action) => {
349
+ const { target_type, target_id } = action;
350
+ const entry = await db.query(target_type).findOne({ where: { id: target_id } });
351
+ if (entry) {
352
+ await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
353
+ }
354
+ });
355
+ }
356
+ },
357
+ async down() {
358
+ throw new Error("not implemented");
359
+ }
360
+ };
281
361
  const register = async ({ strapi: strapi2 }) => {
282
362
  if (strapi2.ee.features.isEnabled("cms-content-releases")) {
283
363
  await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
364
+ strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
284
365
  strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
285
366
  strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
286
367
  }
@@ -290,6 +371,104 @@ const register = async ({ strapi: strapi2 }) => {
290
371
  graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
291
372
  }
292
373
  };
374
+ const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
375
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
376
+ where: {
377
+ actions: {
378
+ contentType,
379
+ entryDocumentId: entry.documentId,
380
+ locale: entry.locale
381
+ }
382
+ }
383
+ });
384
+ const entryStatus = await isEntryValid(contentType, entry, { strapi });
385
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
386
+ where: {
387
+ contentType,
388
+ entryDocumentId: entry.documentId,
389
+ locale: entry.locale
390
+ },
391
+ data: {
392
+ isEntryValid: entryStatus
393
+ }
394
+ });
395
+ for (const release2 of releases) {
396
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
397
+ }
398
+ };
399
+ const deleteActionsAndUpdateReleaseStatus = async (params) => {
400
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
401
+ where: {
402
+ actions: params
403
+ }
404
+ });
405
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
406
+ where: params
407
+ });
408
+ for (const release2 of releases) {
409
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
410
+ }
411
+ };
412
+ const deleteActionsOnDelete = async (ctx, next) => {
413
+ if (ctx.action !== "delete") {
414
+ return next();
415
+ }
416
+ if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
417
+ return next();
418
+ }
419
+ const contentType = ctx.contentType.uid;
420
+ const { documentId, locale } = ctx.params;
421
+ const result = await next();
422
+ if (!result) {
423
+ return result;
424
+ }
425
+ try {
426
+ deleteActionsAndUpdateReleaseStatus({
427
+ contentType,
428
+ entryDocumentId: documentId,
429
+ ...locale !== "*" && { locale }
430
+ });
431
+ } catch (error) {
432
+ strapi.log.error("Error while deleting release actions after delete", {
433
+ error
434
+ });
435
+ }
436
+ return result;
437
+ };
438
+ const updateActionsOnUpdate = async (ctx, next) => {
439
+ if (ctx.action !== "update") {
440
+ return next();
441
+ }
442
+ if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
443
+ return next();
444
+ }
445
+ const contentType = ctx.contentType.uid;
446
+ const result = await next();
447
+ if (!result) {
448
+ return result;
449
+ }
450
+ try {
451
+ updateActionsStatusAndUpdateReleaseStatus(contentType, result);
452
+ } catch (error) {
453
+ strapi.log.error("Error while updating release actions after update", {
454
+ error
455
+ });
456
+ }
457
+ return result;
458
+ };
459
+ const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
460
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
461
+ where: {
462
+ actions: params
463
+ }
464
+ });
465
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
466
+ where: params
467
+ });
468
+ for (const release2 of releases) {
469
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
470
+ }
471
+ };
293
472
  const bootstrap = async ({ strapi: strapi2 }) => {
294
473
  if (strapi2.ee.features.isEnabled("cms-content-releases")) {
295
474
  const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
@@ -297,115 +476,29 @@ const bootstrap = async ({ strapi: strapi2 }) => {
297
476
  );
298
477
  strapi2.db.lifecycles.subscribe({
299
478
  models: contentTypesWithDraftAndPublish,
300
- async afterDelete(event) {
301
- try {
302
- const { model, result } = event;
303
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
304
- const { id } = result;
305
- const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
306
- where: {
307
- actions: {
308
- target_type: model.uid,
309
- target_id: id
310
- }
311
- }
312
- });
313
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
314
- where: {
315
- target_type: model.uid,
316
- target_id: id
317
- }
318
- });
319
- for (const release2 of releases) {
320
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
321
- }
322
- }
323
- } catch (error) {
324
- strapi2.log.error("Error while deleting release actions after entry delete", { error });
325
- }
326
- },
327
- /**
328
- * deleteMany hook doesn't return the deleted entries ids
329
- * so we need to fetch them before deleting the entries to save the ids on our state
330
- */
331
- async beforeDeleteMany(event) {
332
- const { model, params } = event;
333
- if (model.kind === "collectionType" && model.options?.draftAndPublish) {
334
- const { where } = params;
335
- const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
336
- event.state.entriesToDelete = entriesToDelete;
337
- }
338
- },
339
479
  /**
340
- * We delete the release actions related to deleted entries
341
- * We make this only after deleteMany is succesfully executed to avoid errors
480
+ * deleteMany is still used outside documents service, for example when deleting a locale
342
481
  */
343
482
  async afterDeleteMany(event) {
344
483
  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
- }
484
+ const model = strapi2.getModel(event.model.uid);
485
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
486
+ const { where } = event.params;
487
+ deleteReleasesActionsAndUpdateReleaseStatus({
488
+ contentType: model.uid,
489
+ locale: where.locale ?? null,
490
+ ...where.documentId && { entryDocumentId: where.documentId }
365
491
  });
366
- for (const release2 of releases) {
367
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
368
- }
369
492
  }
370
493
  } catch (error) {
371
494
  strapi2.log.error("Error while deleting release actions after entry deleteMany", {
372
495
  error
373
496
  });
374
497
  }
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
498
  }
408
499
  });
500
+ strapi2.documents.use(deleteActionsOnDelete);
501
+ strapi2.documents.use(updateActionsOnUpdate);
409
502
  getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
410
503
  strapi2.log.error(
411
504
  "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
@@ -497,15 +590,13 @@ const schema = {
497
590
  enum: ["publish", "unpublish"],
498
591
  required: true
499
592
  },
500
- entry: {
501
- type: "relation",
502
- relation: "morphToOne",
503
- configurable: false
504
- },
505
593
  contentType: {
506
594
  type: "string",
507
595
  required: true
508
596
  },
597
+ entryDocumentId: {
598
+ type: "string"
599
+ },
509
600
  locale: {
510
601
  type: "string"
511
602
  },
@@ -527,18 +618,6 @@ const contentTypes = {
527
618
  release: release$1,
528
619
  "release-action": releaseAction$1
529
620
  };
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
621
  const createReleaseService = ({ strapi: strapi2 }) => {
543
622
  const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
544
623
  strapi2.eventHub.emit(event, {
@@ -547,93 +626,32 @@ const createReleaseService = ({ strapi: strapi2 }) => {
547
626
  release: release2
548
627
  });
549
628
  };
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
629
  const getFormattedActions = async (releaseId) => {
597
630
  const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
598
631
  where: {
599
632
  release: {
600
633
  id: releaseId
601
634
  }
602
- },
603
- populate: {
604
- entry: {
605
- fields: ["id"]
606
- }
607
635
  }
608
636
  });
609
637
  if (actions.length === 0) {
610
638
  throw new utils.errors.ValidationError("No entries to publish");
611
639
  }
612
- const collectionTypeActions = {};
613
- const singleTypeActions = [];
640
+ const formattedActions = {};
614
641
  for (const action of actions) {
615
642
  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
- });
643
+ if (!formattedActions[contentTypeUid]) {
644
+ formattedActions[contentTypeUid] = {
645
+ publish: [],
646
+ unpublish: []
647
+ };
634
648
  }
649
+ formattedActions[contentTypeUid][action.type].push({
650
+ documentId: action.entryDocumentId,
651
+ locale: action.locale
652
+ });
635
653
  }
636
- return { collectionTypeActions, singleTypeActions };
654
+ return formattedActions;
637
655
  };
638
656
  return {
639
657
  async create(releaseData, { user }) {
@@ -680,91 +698,10 @@ const createReleaseService = ({ strapi: strapi2 }) => {
680
698
  }
681
699
  });
682
700
  },
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;
701
+ findMany(query) {
702
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
703
+ return strapi2.db.query(RELEASE_MODEL_UID).findMany({
704
+ ...dbQuery
768
705
  });
769
706
  },
770
707
  async update(id, releaseData, { user }) {
@@ -800,131 +737,6 @@ const createReleaseService = ({ strapi: strapi2 }) => {
800
737
  strapi2.telemetry.send("didUpdateContentRelease");
801
738
  return updatedRelease;
802
739
  },
803
- async createAction(releaseId, action) {
804
- const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
805
- strapi: strapi2
806
- });
807
- await Promise.all([
808
- validateEntryContentType(action.entry.contentType),
809
- validateUniqueEntry(releaseId, action)
810
- ]);
811
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
812
- if (!release2) {
813
- throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
814
- }
815
- if (release2.releasedAt) {
816
- throw new utils.errors.ValidationError("Release already published");
817
- }
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 });
821
- const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
822
- 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
833
- },
834
- populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
835
- });
836
- this.updateReleaseStatus(releaseId);
837
- return releaseAction2;
838
- },
839
- async findActions(releaseId, query) {
840
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
841
- where: { id: releaseId },
842
- select: ["id"]
843
- });
844
- if (!release2) {
845
- throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
846
- }
847
- const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
848
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
849
- ...dbQuery,
850
- populate: {
851
- entry: {
852
- populate: "*"
853
- }
854
- },
855
- where: {
856
- release: releaseId
857
- }
858
- });
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);
863
- },
864
- async groupActions(actions, groupBy) {
865
- const contentTypeUids = actions.reduce((acc, action) => {
866
- if (!acc.includes(action.contentType)) {
867
- acc.push(action.contentType);
868
- }
869
- return acc;
870
- }, []);
871
- const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(contentTypeUids);
872
- const allLocalesDictionary = await this.getLocalesDataForActions();
873
- const formattedData = actions.map((action) => {
874
- const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
875
- return {
876
- ...action,
877
- locale: action.locale ? allLocalesDictionary[action.locale] : null,
878
- contentType: {
879
- displayName,
880
- mainFieldValue: action.entry[mainField],
881
- uid: action.contentType
882
- }
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
740
  async getAllComponents() {
929
741
  const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
930
742
  const components = await contentManagerComponentsService.findAllComponents();
@@ -990,20 +802,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
990
802
  }
991
803
  try {
992
804
  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
- });
805
+ const formattedActions = await getFormattedActions(releaseId);
806
+ await strapi2.db.transaction(
807
+ async () => Promise.all(
808
+ Object.keys(formattedActions).map(async (contentTypeUid) => {
809
+ const contentType = contentTypeUid;
810
+ const { publish, unpublish } = formattedActions[contentType];
811
+ return Promise.all([
812
+ ...publish.map((params) => strapi2.documents(contentType).publish(params)),
813
+ ...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
814
+ ]);
815
+ })
816
+ )
817
+ );
1007
818
  const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
1008
819
  where: {
1009
820
  id: releaseId
@@ -1033,13 +844,226 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1033
844
  };
1034
845
  }
1035
846
  });
1036
- if (error instanceof Error) {
1037
- throw error;
847
+ if (error instanceof Error) {
848
+ throw error;
849
+ }
850
+ return release2;
851
+ },
852
+ async updateReleaseStatus(releaseId) {
853
+ const releaseActionService = getService("release-action", { strapi: strapi2 });
854
+ const [totalActions, invalidActions] = await Promise.all([
855
+ releaseActionService.countActions({
856
+ filters: {
857
+ release: releaseId
858
+ }
859
+ }),
860
+ releaseActionService.countActions({
861
+ filters: {
862
+ release: releaseId,
863
+ isEntryValid: false
864
+ }
865
+ })
866
+ ]);
867
+ if (totalActions > 0) {
868
+ if (invalidActions > 0) {
869
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
870
+ where: {
871
+ id: releaseId
872
+ },
873
+ data: {
874
+ status: "blocked"
875
+ }
876
+ });
877
+ }
878
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
879
+ where: {
880
+ id: releaseId
881
+ },
882
+ data: {
883
+ status: "ready"
884
+ }
885
+ });
886
+ }
887
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
888
+ where: {
889
+ id: releaseId
890
+ },
891
+ data: {
892
+ status: "empty"
893
+ }
894
+ });
895
+ }
896
+ };
897
+ };
898
+ const getGroupName = (queryValue) => {
899
+ switch (queryValue) {
900
+ case "contentType":
901
+ return "contentType.displayName";
902
+ case "type":
903
+ return "type";
904
+ case "locale":
905
+ return ___default.default.getOr("No locale", "locale.name");
906
+ default:
907
+ return "contentType.displayName";
908
+ }
909
+ };
910
+ const createReleaseActionService = ({ strapi: strapi2 }) => {
911
+ const getLocalesDataForActions = async () => {
912
+ if (!strapi2.plugin("i18n")) {
913
+ return {};
914
+ }
915
+ const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
916
+ return allLocales.reduce((acc, locale) => {
917
+ acc[locale.code] = { name: locale.name, code: locale.code };
918
+ return acc;
919
+ }, {});
920
+ };
921
+ const getContentTypesDataForActions = async (contentTypesUids) => {
922
+ const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
923
+ const contentTypesData = {};
924
+ for (const contentTypeUid of contentTypesUids) {
925
+ const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
926
+ uid: contentTypeUid
927
+ });
928
+ contentTypesData[contentTypeUid] = {
929
+ mainField: contentTypeConfig.settings.mainField,
930
+ displayName: strapi2.getModel(contentTypeUid).info.displayName
931
+ };
932
+ }
933
+ return contentTypesData;
934
+ };
935
+ return {
936
+ async create(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
937
+ const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
938
+ strapi: strapi2
939
+ });
940
+ await Promise.all([
941
+ validateEntryData(action.contentType, action.entryDocumentId),
942
+ validateUniqueEntry(releaseId, action)
943
+ ]);
944
+ const model = strapi2.contentType(action.contentType);
945
+ if (model.kind === "singleType") {
946
+ const document = await strapi2.db.query(model.uid).findOne({ select: ["documentId"] });
947
+ if (!document) {
948
+ throw new utils.errors.NotFoundError(`No entry found for contentType ${action.contentType}`);
949
+ }
950
+ action.entryDocumentId = document.documentId;
951
+ }
952
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
953
+ if (!release2) {
954
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
955
+ }
956
+ if (release2.releasedAt) {
957
+ throw new utils.errors.ValidationError("Release already published");
958
+ }
959
+ const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
960
+ {
961
+ contentType: action.contentType,
962
+ documentId: action.entryDocumentId,
963
+ locale: action.locale
964
+ },
965
+ {
966
+ strapi: strapi2
967
+ }
968
+ ) : true;
969
+ const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
970
+ data: {
971
+ ...action,
972
+ release: release2.id,
973
+ isEntryValid: actionStatus
974
+ },
975
+ populate: { release: { select: ["id"] } }
976
+ });
977
+ if (!disableUpdateReleaseStatus) {
978
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
1038
979
  }
1039
- return release2;
980
+ return releaseAction2;
1040
981
  },
1041
- async updateAction(actionId, releaseId, update) {
1042
- const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
982
+ async findPage(releaseId, query) {
983
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
984
+ where: { id: releaseId },
985
+ select: ["id"]
986
+ });
987
+ if (!release2) {
988
+ throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
989
+ }
990
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
991
+ const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
992
+ ...dbQuery,
993
+ where: {
994
+ release: releaseId
995
+ }
996
+ });
997
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
998
+ const actionsWithEntry = await utils.async.map(actions, async (action) => {
999
+ const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
1000
+ const entry = await getEntry(
1001
+ {
1002
+ contentType: action.contentType,
1003
+ documentId: action.entryDocumentId,
1004
+ locale: action.locale,
1005
+ populate,
1006
+ status: action.type === "publish" ? "draft" : "published"
1007
+ },
1008
+ { strapi: strapi2 }
1009
+ );
1010
+ return {
1011
+ ...action,
1012
+ entry,
1013
+ status: entry ? await getEntryStatus(action.contentType, entry) : null
1014
+ };
1015
+ });
1016
+ return {
1017
+ results: actionsWithEntry,
1018
+ pagination
1019
+ };
1020
+ },
1021
+ async groupActions(actions, groupBy) {
1022
+ const contentTypeUids = actions.reduce((acc, action) => {
1023
+ if (!acc.includes(action.contentType)) {
1024
+ acc.push(action.contentType);
1025
+ }
1026
+ return acc;
1027
+ }, []);
1028
+ const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
1029
+ const allLocalesDictionary = await getLocalesDataForActions();
1030
+ const formattedData = actions.map((action) => {
1031
+ const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
1032
+ return {
1033
+ ...action,
1034
+ locale: action.locale ? allLocalesDictionary[action.locale] : null,
1035
+ contentType: {
1036
+ displayName,
1037
+ mainFieldValue: action.entry[mainField],
1038
+ uid: action.contentType
1039
+ }
1040
+ };
1041
+ });
1042
+ const groupName = getGroupName(groupBy);
1043
+ return ___default.default.groupBy(groupName)(formattedData);
1044
+ },
1045
+ getContentTypeModelsFromActions(actions) {
1046
+ const contentTypeUids = actions.reduce((acc, action) => {
1047
+ if (!acc.includes(action.contentType)) {
1048
+ acc.push(action.contentType);
1049
+ }
1050
+ return acc;
1051
+ }, []);
1052
+ const contentTypeModelsMap = contentTypeUids.reduce(
1053
+ (acc, contentTypeUid) => {
1054
+ acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
1055
+ return acc;
1056
+ },
1057
+ {}
1058
+ );
1059
+ return contentTypeModelsMap;
1060
+ },
1061
+ async countActions(query) {
1062
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
1063
+ return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
1064
+ },
1065
+ async update(actionId, releaseId, update) {
1066
+ const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
1043
1067
  where: {
1044
1068
  id: actionId,
1045
1069
  release: {
@@ -1048,17 +1072,42 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1048
1072
  $null: true
1049
1073
  }
1050
1074
  }
1051
- },
1052
- data: update
1075
+ }
1053
1076
  });
1054
- if (!updatedAction) {
1077
+ if (!action) {
1055
1078
  throw new utils.errors.NotFoundError(
1056
1079
  `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1057
1080
  );
1058
1081
  }
1082
+ const actionStatus = update.type === "publish" ? await getDraftEntryValidStatus(
1083
+ {
1084
+ contentType: action.contentType,
1085
+ documentId: action.entryDocumentId,
1086
+ locale: action.locale
1087
+ },
1088
+ {
1089
+ strapi: strapi2
1090
+ }
1091
+ ) : true;
1092
+ const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
1093
+ where: {
1094
+ id: actionId,
1095
+ release: {
1096
+ id: releaseId,
1097
+ releasedAt: {
1098
+ $null: true
1099
+ }
1100
+ }
1101
+ },
1102
+ data: {
1103
+ ...update,
1104
+ isEntryValid: actionStatus
1105
+ }
1106
+ });
1107
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1059
1108
  return updatedAction;
1060
1109
  },
1061
- async deleteAction(actionId, releaseId) {
1110
+ async delete(actionId, releaseId) {
1062
1111
  const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
1063
1112
  where: {
1064
1113
  id: actionId,
@@ -1075,51 +1124,8 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1075
1124
  `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1076
1125
  );
1077
1126
  }
1078
- this.updateReleaseStatus(releaseId);
1127
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1079
1128
  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
1129
  }
1124
1130
  };
1125
1131
  };
@@ -1135,30 +1141,35 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1135
1141
  where: {
1136
1142
  id: releaseId
1137
1143
  },
1138
- populate: { actions: { populate: { entry: { select: ["id"] } } } }
1144
+ populate: {
1145
+ actions: true
1146
+ }
1139
1147
  });
1140
1148
  if (!release2) {
1141
1149
  throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
1142
1150
  }
1143
1151
  const isEntryInRelease = release2.actions.some(
1144
- (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1152
+ (action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
1145
1153
  );
1146
1154
  if (isEntryInRelease) {
1147
1155
  throw new AlreadyOnReleaseError(
1148
- `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1156
+ `Entry with documentId ${releaseActionArgs.entryDocumentId}${releaseActionArgs.locale ? `( ${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
1149
1157
  );
1150
1158
  }
1151
1159
  },
1152
- validateEntryContentType(contentTypeUid) {
1160
+ validateEntryData(contentTypeUid, entryDocumentId) {
1153
1161
  const contentType = strapi2.contentType(contentTypeUid);
1154
1162
  if (!contentType) {
1155
1163
  throw new utils.errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1156
1164
  }
1157
- if (!contentType.options?.draftAndPublish) {
1165
+ if (!utils.contentTypes.hasDraftAndPublish(contentType)) {
1158
1166
  throw new utils.errors.ValidationError(
1159
1167
  `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1160
1168
  );
1161
1169
  }
1170
+ if (contentType.kind === "collectionType" && !entryDocumentId) {
1171
+ throw new utils.errors.ValidationError("Document id is required for collection type");
1172
+ }
1162
1173
  },
1163
1174
  async validatePendingReleasesLimit() {
1164
1175
  const featureCfg = strapi2.ee.features.get("cms-content-releases");
@@ -1247,78 +1258,165 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1247
1258
  }
1248
1259
  };
1249
1260
  };
1261
+ const DEFAULT_SETTINGS = {
1262
+ defaultTimezone: null
1263
+ };
1264
+ const createSettingsService = ({ strapi: strapi2 }) => {
1265
+ const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
1266
+ return {
1267
+ async update({ settings: settings2 }) {
1268
+ const store = await getStore();
1269
+ store.set({ key: "settings", value: settings2 });
1270
+ return settings2;
1271
+ },
1272
+ async find() {
1273
+ const store = await getStore();
1274
+ const settings2 = await store.get({ key: "settings" });
1275
+ return {
1276
+ ...DEFAULT_SETTINGS,
1277
+ ...settings2 || {}
1278
+ };
1279
+ }
1280
+ };
1281
+ };
1250
1282
  const services = {
1251
1283
  release: createReleaseService,
1284
+ "release-action": createReleaseActionService,
1252
1285
  "release-validation": createReleaseValidationService,
1253
- scheduling: createSchedulingService
1286
+ scheduling: createSchedulingService,
1287
+ settings: createSettingsService
1254
1288
  };
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()
1289
+ const RELEASE_SCHEMA = utils.yup.object().shape({
1290
+ name: utils.yup.string().trim().required(),
1291
+ scheduledAt: utils.yup.string().nullable(),
1292
+ timezone: utils.yup.string().when("scheduledAt", {
1293
+ is: (value) => value !== null && value !== void 0,
1294
+ then: utils.yup.string().required(),
1295
+ otherwise: utils.yup.string().nullable()
1273
1296
  })
1274
1297
  }).required().noUnknown();
1298
+ const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = utils.yup.object().shape({
1299
+ contentType: utils.yup.string().required(),
1300
+ entryDocumentId: utils.yup.string().nullable(),
1301
+ hasEntryAttached: utils.yup.string().nullable(),
1302
+ locale: utils.yup.string().nullable()
1303
+ }).required().noUnknown();
1275
1304
  const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
1305
+ const validatefindByDocumentAttachedParams = utils.validateYupSchema(
1306
+ FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
1307
+ );
1276
1308
  const releaseController = {
1277
- async findMany(ctx) {
1309
+ /**
1310
+ * Find releases based on documents attached or not to the release.
1311
+ * If `hasEntryAttached` is true, it will return all releases that have the entry attached.
1312
+ * If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
1313
+ */
1314
+ async findByDocumentAttached(ctx) {
1278
1315
  const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1279
1316
  ability: ctx.state.userAbility,
1280
1317
  model: RELEASE_MODEL_UID
1281
1318
  });
1282
1319
  await permissionsManager.validateQuery(ctx.query);
1283
1320
  const releaseService = getService("release", { strapi });
1284
- const isFindManyForContentTypeEntry = Boolean(ctx.query?.contentTypeUid && ctx.query?.entryId);
1285
- if (isFindManyForContentTypeEntry) {
1286
- const query = await permissionsManager.sanitizeQuery(ctx.query);
1287
- const contentTypeUid = query.contentTypeUid;
1288
- const entryId = query.entryId;
1289
- const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
1290
- const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
1291
- ctx.body = { data };
1292
- } else {
1293
- const query = await permissionsManager.sanitizeQuery(ctx.query);
1294
- const { results, pagination } = await releaseService.findPage(query);
1295
- const data = results.map((release2) => {
1296
- const { actions, ...releaseData } = release2;
1297
- return {
1298
- ...releaseData,
1321
+ const query = await permissionsManager.sanitizeQuery(ctx.query);
1322
+ await validatefindByDocumentAttachedParams(query);
1323
+ const model = strapi.getModel(query.contentType);
1324
+ if (model.kind && model.kind === "singleType") {
1325
+ const document = await strapi.db.query(model.uid).findOne({ select: ["documentId"] });
1326
+ if (!document) {
1327
+ throw new utils.errors.NotFoundError(`No entry found for contentType ${query.contentType}`);
1328
+ }
1329
+ query.entryDocumentId = document.documentId;
1330
+ }
1331
+ const { contentType, hasEntryAttached, entryDocumentId, locale } = query;
1332
+ const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
1333
+ if (isEntryAttached) {
1334
+ const releases = await releaseService.findMany({
1335
+ where: {
1336
+ releasedAt: null,
1337
+ actions: {
1338
+ contentType,
1339
+ entryDocumentId: entryDocumentId ?? null,
1340
+ locale: locale ?? null
1341
+ }
1342
+ },
1343
+ populate: {
1299
1344
  actions: {
1300
- meta: {
1301
- count: actions.count
1345
+ fields: ["type"],
1346
+ filters: {
1347
+ contentType,
1348
+ entryDocumentId: entryDocumentId ?? null,
1349
+ locale: locale ?? null
1302
1350
  }
1303
1351
  }
1304
- };
1352
+ }
1353
+ });
1354
+ ctx.body = { data: releases };
1355
+ } else {
1356
+ const relatedReleases = await releaseService.findMany({
1357
+ where: {
1358
+ releasedAt: null,
1359
+ actions: {
1360
+ contentType,
1361
+ entryDocumentId: entryDocumentId ?? null,
1362
+ locale: locale ?? null
1363
+ }
1364
+ }
1305
1365
  });
1306
- const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1366
+ const releases = await releaseService.findMany({
1307
1367
  where: {
1368
+ $or: [
1369
+ {
1370
+ id: {
1371
+ $notIn: relatedReleases.map((release2) => release2.id)
1372
+ }
1373
+ },
1374
+ {
1375
+ actions: null
1376
+ }
1377
+ ],
1308
1378
  releasedAt: null
1309
1379
  }
1310
1380
  });
1311
- ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1381
+ ctx.body = { data: releases };
1312
1382
  }
1313
1383
  },
1384
+ async findPage(ctx) {
1385
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1386
+ ability: ctx.state.userAbility,
1387
+ model: RELEASE_MODEL_UID
1388
+ });
1389
+ await permissionsManager.validateQuery(ctx.query);
1390
+ const releaseService = getService("release", { strapi });
1391
+ const query = await permissionsManager.sanitizeQuery(ctx.query);
1392
+ const { results, pagination } = await releaseService.findPage(query);
1393
+ const data = results.map((release2) => {
1394
+ const { actions, ...releaseData } = release2;
1395
+ return {
1396
+ ...releaseData,
1397
+ actions: {
1398
+ meta: {
1399
+ count: actions.count
1400
+ }
1401
+ }
1402
+ };
1403
+ });
1404
+ const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1405
+ where: {
1406
+ releasedAt: null
1407
+ }
1408
+ });
1409
+ ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1410
+ },
1314
1411
  async findOne(ctx) {
1315
1412
  const id = ctx.params.id;
1316
1413
  const releaseService = getService("release", { strapi });
1414
+ const releaseActionService = getService("release-action", { strapi });
1317
1415
  const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
1318
1416
  if (!release2) {
1319
1417
  throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
1320
1418
  }
1321
- const count = await releaseService.countActions({
1419
+ const count = await releaseActionService.countActions({
1322
1420
  filters: {
1323
1421
  release: id
1324
1422
  }
@@ -1338,28 +1436,43 @@ const releaseController = {
1338
1436
  ctx.body = { data };
1339
1437
  },
1340
1438
  async mapEntriesToReleases(ctx) {
1341
- const { contentTypeUid, entriesIds } = ctx.query;
1342
- if (!contentTypeUid || !entriesIds) {
1439
+ const { contentTypeUid, documentIds, locale } = ctx.query;
1440
+ if (!contentTypeUid || !documentIds) {
1343
1441
  throw new utils.errors.ValidationError("Missing required query parameters");
1344
1442
  }
1345
1443
  const releaseService = getService("release", { strapi });
1346
- const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
1347
- contentTypeUid,
1348
- entriesIds
1349
- );
1444
+ const releasesWithActions = await releaseService.findMany({
1445
+ where: {
1446
+ releasedAt: null,
1447
+ actions: {
1448
+ contentType: contentTypeUid,
1449
+ entryDocumentId: {
1450
+ $in: documentIds
1451
+ },
1452
+ locale
1453
+ }
1454
+ },
1455
+ populate: {
1456
+ actions: true
1457
+ }
1458
+ });
1350
1459
  const mappedEntriesInReleases = releasesWithActions.reduce(
1351
- // TODO: Fix for v5 removed mappedEntriedToRelease
1352
1460
  (acc, release2) => {
1353
1461
  release2.actions.forEach((action) => {
1354
- if (!acc[action.entry.id]) {
1355
- acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
1462
+ if (action.contentType !== contentTypeUid) {
1463
+ return;
1464
+ }
1465
+ if (locale && action.locale !== locale) {
1466
+ return;
1467
+ }
1468
+ if (!acc[action.entryDocumentId]) {
1469
+ acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
1356
1470
  } else {
1357
- acc[action.entry.id].push({ id: release2.id, name: release2.name });
1471
+ acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
1358
1472
  }
1359
1473
  });
1360
1474
  return acc;
1361
1475
  },
1362
- // TODO: Fix for v5 removed mappedEntriedToRelease
1363
1476
  {}
1364
1477
  );
1365
1478
  ctx.body = {
@@ -1404,18 +1517,18 @@ const releaseController = {
1404
1517
  };
1405
1518
  },
1406
1519
  async publish(ctx) {
1407
- const user = ctx.state.user;
1408
1520
  const id = ctx.params.id;
1409
1521
  const releaseService = getService("release", { strapi });
1410
- const release2 = await releaseService.publish(id, { user });
1522
+ const releaseActionService = getService("release-action", { strapi });
1523
+ const release2 = await releaseService.publish(id);
1411
1524
  const [countPublishActions, countUnpublishActions] = await Promise.all([
1412
- releaseService.countActions({
1525
+ releaseActionService.countActions({
1413
1526
  filters: {
1414
1527
  release: id,
1415
1528
  type: "publish"
1416
1529
  }
1417
1530
  }),
1418
- releaseService.countActions({
1531
+ releaseActionService.countActions({
1419
1532
  filters: {
1420
1533
  release: id,
1421
1534
  type: "unpublish"
@@ -1433,24 +1546,27 @@ const releaseController = {
1433
1546
  }
1434
1547
  };
1435
1548
  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(),
1549
+ contentType: utils.yup.string().required(),
1550
+ entryDocumentId: utils.yup.strapiID(),
1551
+ locale: utils.yup.string(),
1440
1552
  type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
1441
1553
  });
1442
1554
  const RELEASE_ACTION_UPDATE_SCHEMA = utils.yup.object().shape({
1443
1555
  type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
1444
1556
  });
1557
+ const FIND_MANY_ACTIONS_PARAMS = utils.yup.object().shape({
1558
+ groupBy: utils.yup.string().oneOf(["action", "contentType", "locale"])
1559
+ });
1445
1560
  const validateReleaseAction = utils.validateYupSchema(RELEASE_ACTION_SCHEMA);
1446
1561
  const validateReleaseActionUpdateSchema = utils.validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
1562
+ const validateFindManyActionsParams = utils.validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
1447
1563
  const releaseActionController = {
1448
1564
  async create(ctx) {
1449
1565
  const releaseId = ctx.params.releaseId;
1450
1566
  const releaseActionArgs = ctx.request.body;
1451
1567
  await validateReleaseAction(releaseActionArgs);
1452
- const releaseService = getService("release", { strapi });
1453
- const releaseAction2 = await releaseService.createAction(releaseId, releaseActionArgs);
1568
+ const releaseActionService = getService("release-action", { strapi });
1569
+ const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
1454
1570
  ctx.created({
1455
1571
  data: releaseAction2
1456
1572
  });
@@ -1461,12 +1577,15 @@ const releaseActionController = {
1461
1577
  await Promise.all(
1462
1578
  releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1463
1579
  );
1580
+ const releaseActionService = getService("release-action", { strapi });
1464
1581
  const releaseService = getService("release", { strapi });
1465
1582
  const releaseActions = await strapi.db.transaction(async () => {
1466
1583
  const releaseActions2 = await Promise.all(
1467
1584
  releaseActionsArgs.map(async (releaseActionArgs) => {
1468
1585
  try {
1469
- const action = await releaseService.createAction(releaseId, releaseActionArgs);
1586
+ const action = await releaseActionService.create(releaseId, releaseActionArgs, {
1587
+ disableUpdateReleaseStatus: true
1588
+ });
1470
1589
  return action;
1471
1590
  } catch (error) {
1472
1591
  if (error instanceof AlreadyOnReleaseError) {
@@ -1479,6 +1598,9 @@ const releaseActionController = {
1479
1598
  return releaseActions2;
1480
1599
  });
1481
1600
  const newReleaseActions = releaseActions.filter((action) => action !== null);
1601
+ if (newReleaseActions.length > 0) {
1602
+ releaseService.updateReleaseStatus(releaseId);
1603
+ }
1482
1604
  ctx.created({
1483
1605
  data: newReleaseActions,
1484
1606
  meta: {
@@ -1493,10 +1615,17 @@ const releaseActionController = {
1493
1615
  ability: ctx.state.userAbility,
1494
1616
  model: RELEASE_ACTION_MODEL_UID
1495
1617
  });
1618
+ await validateFindManyActionsParams(ctx.query);
1619
+ if (ctx.query.groupBy) {
1620
+ if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
1621
+ ctx.badRequest("Invalid groupBy parameter");
1622
+ }
1623
+ }
1624
+ ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
1625
+ delete ctx.query.groupBy;
1496
1626
  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,
1627
+ const releaseActionService = getService("release-action", { strapi });
1628
+ const { results, pagination } = await releaseActionService.findPage(releaseId, {
1500
1629
  ...query
1501
1630
  });
1502
1631
  const contentTypeOutputSanitizers = results.reduce((acc, action) => {
@@ -1512,10 +1641,11 @@ const releaseActionController = {
1512
1641
  }, {});
1513
1642
  const sanitizedResults = await utils.async.map(results, async (action) => ({
1514
1643
  ...action,
1515
- entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1644
+ entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
1516
1645
  }));
1517
- const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
1518
- const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
1646
+ const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
1647
+ const contentTypes2 = releaseActionService.getContentTypeModelsFromActions(results);
1648
+ const releaseService = getService("release", { strapi });
1519
1649
  const components = await releaseService.getAllComponents();
1520
1650
  ctx.body = {
1521
1651
  data: groupedData,
@@ -1531,8 +1661,8 @@ const releaseActionController = {
1531
1661
  const releaseId = ctx.params.releaseId;
1532
1662
  const releaseActionUpdateArgs = ctx.request.body;
1533
1663
  await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
1534
- const releaseService = getService("release", { strapi });
1535
- const updatedAction = await releaseService.updateAction(
1664
+ const releaseActionService = getService("release-action", { strapi });
1665
+ const updatedAction = await releaseActionService.update(
1536
1666
  actionId,
1537
1667
  releaseId,
1538
1668
  releaseActionUpdateArgs
@@ -1544,14 +1674,36 @@ const releaseActionController = {
1544
1674
  async delete(ctx) {
1545
1675
  const actionId = ctx.params.actionId;
1546
1676
  const releaseId = ctx.params.releaseId;
1547
- const releaseService = getService("release", { strapi });
1548
- const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
1677
+ const releaseActionService = getService("release-action", { strapi });
1678
+ const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
1549
1679
  ctx.body = {
1550
1680
  data: deletedReleaseAction
1551
1681
  };
1552
1682
  }
1553
1683
  };
1554
- const controllers = { release: releaseController, "release-action": releaseActionController };
1684
+ const SETTINGS_SCHEMA = yup__namespace.object().shape({
1685
+ defaultTimezone: yup__namespace.string().nullable().default(null)
1686
+ }).required().noUnknown();
1687
+ const validateSettings = utils.validateYupSchema(SETTINGS_SCHEMA);
1688
+ const settingsController = {
1689
+ async find(ctx) {
1690
+ const settingsService = getService("settings", { strapi });
1691
+ const settings2 = await settingsService.find();
1692
+ ctx.body = { data: settings2 };
1693
+ },
1694
+ async update(ctx) {
1695
+ const settingsBody = ctx.request.body;
1696
+ const settings2 = await validateSettings(settingsBody);
1697
+ const settingsService = getService("settings", { strapi });
1698
+ const updatedSettings = await settingsService.update({ settings: settings2 });
1699
+ ctx.body = { data: updatedSettings };
1700
+ }
1701
+ };
1702
+ const controllers = {
1703
+ release: releaseController,
1704
+ "release-action": releaseActionController,
1705
+ settings: settingsController
1706
+ };
1555
1707
  const release = {
1556
1708
  type: "admin",
1557
1709
  routes: [
@@ -1571,6 +1723,22 @@ const release = {
1571
1723
  ]
1572
1724
  }
1573
1725
  },
1726
+ {
1727
+ method: "GET",
1728
+ path: "/getByDocumentAttached",
1729
+ handler: "release.findByDocumentAttached",
1730
+ config: {
1731
+ policies: [
1732
+ "admin::isAuthenticatedAdmin",
1733
+ {
1734
+ name: "admin::hasPermissions",
1735
+ config: {
1736
+ actions: ["plugin::content-releases.read"]
1737
+ }
1738
+ }
1739
+ ]
1740
+ }
1741
+ },
1574
1742
  {
1575
1743
  method: "POST",
1576
1744
  path: "/",
@@ -1590,7 +1758,7 @@ const release = {
1590
1758
  {
1591
1759
  method: "GET",
1592
1760
  path: "/",
1593
- handler: "release.findMany",
1761
+ handler: "release.findPage",
1594
1762
  config: {
1595
1763
  policies: [
1596
1764
  "admin::isAuthenticatedAdmin",
@@ -1754,7 +1922,45 @@ const releaseAction = {
1754
1922
  }
1755
1923
  ]
1756
1924
  };
1925
+ const settings = {
1926
+ type: "admin",
1927
+ routes: [
1928
+ {
1929
+ method: "GET",
1930
+ path: "/settings",
1931
+ handler: "settings.find",
1932
+ config: {
1933
+ policies: [
1934
+ "admin::isAuthenticatedAdmin",
1935
+ {
1936
+ name: "admin::hasPermissions",
1937
+ config: {
1938
+ actions: ["plugin::content-releases.settings.read"]
1939
+ }
1940
+ }
1941
+ ]
1942
+ }
1943
+ },
1944
+ {
1945
+ method: "PUT",
1946
+ path: "/settings",
1947
+ handler: "settings.update",
1948
+ config: {
1949
+ policies: [
1950
+ "admin::isAuthenticatedAdmin",
1951
+ {
1952
+ name: "admin::hasPermissions",
1953
+ config: {
1954
+ actions: ["plugin::content-releases.settings.update"]
1955
+ }
1956
+ }
1957
+ ]
1958
+ }
1959
+ }
1960
+ ]
1961
+ };
1757
1962
  const routes = {
1963
+ settings,
1758
1964
  release,
1759
1965
  "release-action": releaseAction
1760
1966
  };