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