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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE +17 -1
  2. package/dist/_chunks/{App-Do-Rnv0A.mjs → App-B4mkcLmw.mjs} +296 -263
  3. package/dist/_chunks/App-B4mkcLmw.mjs.map +1 -0
  4. package/dist/_chunks/{App-CqbuK4M6.js → App-DpoC8s97.js} +293 -261
  5. package/dist/_chunks/App-DpoC8s97.js.map +1 -0
  6. package/dist/_chunks/ReleasesSettingsPage-B89WWWJf.js +178 -0
  7. package/dist/_chunks/ReleasesSettingsPage-B89WWWJf.js.map +1 -0
  8. package/dist/_chunks/ReleasesSettingsPage-DfL6yxLG.mjs +178 -0
  9. package/dist/_chunks/ReleasesSettingsPage-DfL6yxLG.mjs.map +1 -0
  10. package/dist/_chunks/{en-DtFJ5ViE.js → en-CmYoEnA7.js} +9 -2
  11. package/dist/_chunks/en-CmYoEnA7.js.map +1 -0
  12. package/dist/_chunks/{en-B9Ur3VsE.mjs → en-D0yVZFqf.mjs} +9 -2
  13. package/dist/_chunks/en-D0yVZFqf.mjs.map +1 -0
  14. package/dist/_chunks/{index-D_pgdqQL.mjs → index-B3cqcIza.mjs} +780 -448
  15. package/dist/_chunks/index-B3cqcIza.mjs.map +1 -0
  16. package/dist/_chunks/{index-Tedsw4GC.js → index-sGcuP2hw.js} +763 -431
  17. package/dist/_chunks/index-sGcuP2hw.js.map +1 -0
  18. package/dist/_chunks/schemas-63pFihNF.mjs +44 -0
  19. package/dist/_chunks/schemas-63pFihNF.mjs.map +1 -0
  20. package/dist/_chunks/schemas-z5zp-_Gd.js +62 -0
  21. package/dist/_chunks/schemas-z5zp-_Gd.js.map +1 -0
  22. package/dist/admin/index.js +1 -1
  23. package/dist/admin/index.mjs +2 -2
  24. package/dist/admin/src/components/ReleaseActionMenu.d.ts +2 -2
  25. package/dist/admin/src/components/{CMReleasesContainer.d.ts → ReleaseActionModal.d.ts} +3 -1
  26. package/dist/admin/src/components/ReleaseListCell.d.ts +28 -0
  27. package/dist/admin/src/components/ReleasesPanel.d.ts +3 -0
  28. package/dist/admin/src/constants.d.ts +18 -0
  29. package/dist/admin/src/modules/hooks.d.ts +7 -0
  30. package/dist/admin/src/pages/ReleasesSettingsPage.d.ts +1 -0
  31. package/dist/admin/src/services/release.d.ts +43 -36
  32. package/dist/admin/src/utils/time.d.ts +9 -0
  33. package/dist/admin/src/validation/schemas.d.ts +6 -0
  34. package/dist/server/index.js +796 -623
  35. package/dist/server/index.js.map +1 -1
  36. package/dist/server/index.mjs +797 -624
  37. package/dist/server/index.mjs.map +1 -1
  38. package/dist/server/src/bootstrap.d.ts.map +1 -1
  39. package/dist/server/src/constants.d.ts +11 -2
  40. package/dist/server/src/constants.d.ts.map +1 -1
  41. package/dist/server/src/content-types/index.d.ts +3 -5
  42. package/dist/server/src/content-types/index.d.ts.map +1 -1
  43. package/dist/server/src/content-types/release-action/index.d.ts +3 -5
  44. package/dist/server/src/content-types/release-action/index.d.ts.map +1 -1
  45. package/dist/server/src/content-types/release-action/schema.d.ts +3 -5
  46. package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -1
  47. package/dist/server/src/controllers/index.d.ts +6 -1
  48. package/dist/server/src/controllers/index.d.ts.map +1 -1
  49. package/dist/server/src/controllers/release-action.d.ts.map +1 -1
  50. package/dist/server/src/controllers/release.d.ts +7 -1
  51. package/dist/server/src/controllers/release.d.ts.map +1 -1
  52. package/dist/server/src/controllers/settings.d.ts +11 -0
  53. package/dist/server/src/controllers/settings.d.ts.map +1 -0
  54. package/dist/server/src/controllers/validation/release-action.d.ts +7 -1
  55. package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -1
  56. package/dist/server/src/controllers/validation/release.d.ts +2 -0
  57. package/dist/server/src/controllers/validation/release.d.ts.map +1 -1
  58. package/dist/server/src/controllers/validation/settings.d.ts +3 -0
  59. package/dist/server/src/controllers/validation/settings.d.ts.map +1 -0
  60. package/dist/server/src/index.d.ts +66 -49
  61. package/dist/server/src/index.d.ts.map +1 -1
  62. package/dist/server/src/middlewares/documents.d.ts +6 -0
  63. package/dist/server/src/middlewares/documents.d.ts.map +1 -0
  64. package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts +9 -0
  65. package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts.map +1 -0
  66. package/dist/server/src/migrations/index.d.ts.map +1 -1
  67. package/dist/server/src/register.d.ts.map +1 -1
  68. package/dist/server/src/routes/index.d.ts +16 -0
  69. package/dist/server/src/routes/index.d.ts.map +1 -1
  70. package/dist/server/src/routes/release.d.ts.map +1 -1
  71. package/dist/server/src/routes/settings.d.ts +18 -0
  72. package/dist/server/src/routes/settings.d.ts.map +1 -0
  73. package/dist/server/src/services/index.d.ts +38 -38
  74. package/dist/server/src/services/index.d.ts.map +1 -1
  75. package/dist/server/src/services/release-action.d.ts +36 -0
  76. package/dist/server/src/services/release-action.d.ts.map +1 -0
  77. package/dist/server/src/services/release.d.ts +6 -41
  78. package/dist/server/src/services/release.d.ts.map +1 -1
  79. package/dist/server/src/services/settings.d.ts +13 -0
  80. package/dist/server/src/services/settings.d.ts.map +1 -0
  81. package/dist/server/src/services/validation.d.ts +1 -1
  82. package/dist/server/src/services/validation.d.ts.map +1 -1
  83. package/dist/server/src/utils/index.d.ts +29 -8
  84. package/dist/server/src/utils/index.d.ts.map +1 -1
  85. package/dist/shared/contracts/release-actions.d.ts +9 -10
  86. package/dist/shared/contracts/release-actions.d.ts.map +1 -1
  87. package/dist/shared/contracts/releases.d.ts +9 -7
  88. package/dist/shared/contracts/releases.d.ts.map +1 -1
  89. package/dist/shared/contracts/settings.d.ts +39 -0
  90. package/dist/shared/contracts/settings.d.ts.map +1 -0
  91. package/package.json +10 -9
  92. package/dist/_chunks/App-CqbuK4M6.js.map +0 -1
  93. package/dist/_chunks/App-Do-Rnv0A.mjs.map +0 -1
  94. package/dist/_chunks/en-B9Ur3VsE.mjs.map +0 -1
  95. package/dist/_chunks/en-DtFJ5ViE.js.map +0 -1
  96. package/dist/_chunks/index-D_pgdqQL.mjs.map +0 -1
  97. package/dist/_chunks/index-Tedsw4GC.js.map +0 -1
  98. package/dist/shared/validation-schemas.d.ts +0 -2
  99. package/dist/shared/validation-schemas.d.ts.map +0 -1
@@ -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,38 @@ 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 hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
310
+ if (hasPolymorphicColumn) {
311
+ const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
312
+ "strapi_release_actions",
313
+ "entry_document_id"
314
+ );
315
+ if (!hasEntryDocumentIdColumn) {
316
+ await trx.schema.alterTable("strapi_release_actions", (table) => {
317
+ table.string("entry_document_id");
318
+ });
319
+ }
320
+ const releaseActions = await trx.select("*").from("strapi_release_actions");
321
+ async.map(releaseActions, async (action) => {
322
+ const { target_type, target_id } = action;
323
+ const entry = await db.query(target_type).findOne({ where: { id: target_id } });
324
+ if (entry) {
325
+ await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
326
+ }
327
+ });
328
+ }
329
+ },
330
+ async down() {
331
+ throw new Error("not implemented");
332
+ }
333
+ };
258
334
  const register = async ({ strapi: strapi2 }) => {
259
335
  if (strapi2.ee.features.isEnabled("cms-content-releases")) {
260
336
  await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
337
+ strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
261
338
  strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
262
339
  strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
263
340
  }
@@ -267,6 +344,104 @@ const register = async ({ strapi: strapi2 }) => {
267
344
  graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
268
345
  }
269
346
  };
347
+ const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
348
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
349
+ where: {
350
+ actions: {
351
+ contentType,
352
+ entryDocumentId: entry.documentId,
353
+ locale: entry.locale
354
+ }
355
+ }
356
+ });
357
+ const entryStatus = await isEntryValid(contentType, entry, { strapi });
358
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
359
+ where: {
360
+ contentType,
361
+ entryDocumentId: entry.documentId,
362
+ locale: entry.locale
363
+ },
364
+ data: {
365
+ isEntryValid: entryStatus
366
+ }
367
+ });
368
+ for (const release2 of releases) {
369
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
370
+ }
371
+ };
372
+ const deleteActionsAndUpdateReleaseStatus = async (params) => {
373
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
374
+ where: {
375
+ actions: params
376
+ }
377
+ });
378
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
379
+ where: params
380
+ });
381
+ for (const release2 of releases) {
382
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
383
+ }
384
+ };
385
+ const deleteActionsOnDelete = async (ctx, next) => {
386
+ if (ctx.action !== "delete") {
387
+ return next();
388
+ }
389
+ if (!contentTypes$1.hasDraftAndPublish(ctx.contentType)) {
390
+ return next();
391
+ }
392
+ const contentType = ctx.contentType.uid;
393
+ const { documentId, locale } = ctx.params;
394
+ const result = await next();
395
+ if (!result) {
396
+ return result;
397
+ }
398
+ try {
399
+ deleteActionsAndUpdateReleaseStatus({
400
+ contentType,
401
+ entryDocumentId: documentId,
402
+ ...locale !== "*" && { locale }
403
+ });
404
+ } catch (error) {
405
+ strapi.log.error("Error while deleting release actions after delete", {
406
+ error
407
+ });
408
+ }
409
+ return result;
410
+ };
411
+ const updateActionsOnUpdate = async (ctx, next) => {
412
+ if (ctx.action !== "update") {
413
+ return next();
414
+ }
415
+ if (!contentTypes$1.hasDraftAndPublish(ctx.contentType)) {
416
+ return next();
417
+ }
418
+ const contentType = ctx.contentType.uid;
419
+ const result = await next();
420
+ if (!result) {
421
+ return result;
422
+ }
423
+ try {
424
+ updateActionsStatusAndUpdateReleaseStatus(contentType, result);
425
+ } catch (error) {
426
+ strapi.log.error("Error while updating release actions after update", {
427
+ error
428
+ });
429
+ }
430
+ return result;
431
+ };
432
+ const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
433
+ const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
434
+ where: {
435
+ actions: params
436
+ }
437
+ });
438
+ await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
439
+ where: params
440
+ });
441
+ for (const release2 of releases) {
442
+ getService("release", { strapi }).updateReleaseStatus(release2.id);
443
+ }
444
+ };
270
445
  const bootstrap = async ({ strapi: strapi2 }) => {
271
446
  if (strapi2.ee.features.isEnabled("cms-content-releases")) {
272
447
  const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
@@ -274,115 +449,29 @@ const bootstrap = async ({ strapi: strapi2 }) => {
274
449
  );
275
450
  strapi2.db.lifecycles.subscribe({
276
451
  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
452
  /**
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
- /**
317
- * We delete the release actions related to deleted entries
318
- * We make this only after deleteMany is succesfully executed to avoid errors
453
+ * deleteMany is still used outside documents service, for example when deleting a locale
319
454
  */
320
455
  async afterDeleteMany(event) {
321
456
  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
- }
457
+ const model = strapi2.getModel(event.model.uid);
458
+ if (model.kind === "collectionType" && model.options?.draftAndPublish) {
459
+ const { where } = event.params;
460
+ deleteReleasesActionsAndUpdateReleaseStatus({
461
+ contentType: model.uid,
462
+ locale: where.locale ?? null,
463
+ ...where.documentId && { entryDocumentId: where.documentId }
342
464
  });
343
- for (const release2 of releases) {
344
- getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
345
- }
346
465
  }
347
466
  } catch (error) {
348
467
  strapi2.log.error("Error while deleting release actions after entry deleteMany", {
349
468
  error
350
469
  });
351
470
  }
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
471
  }
385
472
  });
473
+ strapi2.documents.use(deleteActionsOnDelete);
474
+ strapi2.documents.use(updateActionsOnUpdate);
386
475
  getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
387
476
  strapi2.log.error(
388
477
  "Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
@@ -474,15 +563,13 @@ const schema = {
474
563
  enum: ["publish", "unpublish"],
475
564
  required: true
476
565
  },
477
- entry: {
478
- type: "relation",
479
- relation: "morphToOne",
480
- configurable: false
481
- },
482
566
  contentType: {
483
567
  type: "string",
484
568
  required: true
485
569
  },
570
+ entryDocumentId: {
571
+ type: "string"
572
+ },
486
573
  locale: {
487
574
  type: "string"
488
575
  },
@@ -504,18 +591,6 @@ const contentTypes = {
504
591
  release: release$1,
505
592
  "release-action": releaseAction$1
506
593
  };
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
594
  const createReleaseService = ({ strapi: strapi2 }) => {
520
595
  const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
521
596
  strapi2.eventHub.emit(event, {
@@ -524,93 +599,32 @@ const createReleaseService = ({ strapi: strapi2 }) => {
524
599
  release: release2
525
600
  });
526
601
  };
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
602
  const getFormattedActions = async (releaseId) => {
574
603
  const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
575
604
  where: {
576
605
  release: {
577
606
  id: releaseId
578
607
  }
579
- },
580
- populate: {
581
- entry: {
582
- fields: ["id"]
583
- }
584
608
  }
585
609
  });
586
610
  if (actions.length === 0) {
587
611
  throw new errors.ValidationError("No entries to publish");
588
612
  }
589
- const collectionTypeActions = {};
590
- const singleTypeActions = [];
613
+ const formattedActions = {};
591
614
  for (const action of actions) {
592
615
  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
- });
616
+ if (!formattedActions[contentTypeUid]) {
617
+ formattedActions[contentTypeUid] = {
618
+ publish: [],
619
+ unpublish: []
620
+ };
611
621
  }
622
+ formattedActions[contentTypeUid][action.type].push({
623
+ documentId: action.entryDocumentId,
624
+ locale: action.locale
625
+ });
612
626
  }
613
- return { collectionTypeActions, singleTypeActions };
627
+ return formattedActions;
614
628
  };
615
629
  return {
616
630
  async create(releaseData, { user }) {
@@ -657,91 +671,10 @@ const createReleaseService = ({ strapi: strapi2 }) => {
657
671
  }
658
672
  });
659
673
  },
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;
674
+ findMany(query) {
675
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
676
+ return strapi2.db.query(RELEASE_MODEL_UID).findMany({
677
+ ...dbQuery
745
678
  });
746
679
  },
747
680
  async update(id, releaseData, { user }) {
@@ -777,12 +710,208 @@ const createReleaseService = ({ strapi: strapi2 }) => {
777
710
  strapi2.telemetry.send("didUpdateContentRelease");
778
711
  return updatedRelease;
779
712
  },
780
- async createAction(releaseId, action) {
781
- const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
713
+ async getAllComponents() {
714
+ const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
715
+ const components = await contentManagerComponentsService.findAllComponents();
716
+ const componentsMap = components.reduce(
717
+ (acc, component) => {
718
+ acc[component.uid] = component;
719
+ return acc;
720
+ },
721
+ {}
722
+ );
723
+ return componentsMap;
724
+ },
725
+ async delete(releaseId) {
726
+ const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
727
+ where: { id: releaseId },
728
+ populate: {
729
+ actions: {
730
+ select: ["id"]
731
+ }
732
+ }
733
+ });
734
+ if (!release2) {
735
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
736
+ }
737
+ if (release2.releasedAt) {
738
+ throw new errors.ValidationError("Release already published");
739
+ }
740
+ await strapi2.db.transaction(async () => {
741
+ await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
742
+ where: {
743
+ id: {
744
+ $in: release2.actions.map((action) => action.id)
745
+ }
746
+ }
747
+ });
748
+ await strapi2.db.query(RELEASE_MODEL_UID).delete({
749
+ where: {
750
+ id: releaseId
751
+ }
752
+ });
753
+ });
754
+ if (release2.scheduledAt) {
755
+ const schedulingService = getService("scheduling", { strapi: strapi2 });
756
+ await schedulingService.cancel(release2.id);
757
+ }
758
+ strapi2.telemetry.send("didDeleteContentRelease");
759
+ return release2;
760
+ },
761
+ async publish(releaseId) {
762
+ const {
763
+ release: release2,
764
+ error
765
+ } = await strapi2.db.transaction(async ({ trx }) => {
766
+ const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
767
+ if (!lockedRelease) {
768
+ throw new errors.NotFoundError(`No release found for id ${releaseId}`);
769
+ }
770
+ if (lockedRelease.releasedAt) {
771
+ throw new errors.ValidationError("Release already published");
772
+ }
773
+ if (lockedRelease.status === "failed") {
774
+ throw new errors.ValidationError("Release failed to publish");
775
+ }
776
+ try {
777
+ strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
778
+ const formattedActions = await getFormattedActions(releaseId);
779
+ await strapi2.db.transaction(
780
+ async () => Promise.all(
781
+ Object.keys(formattedActions).map(async (contentTypeUid) => {
782
+ const contentType = contentTypeUid;
783
+ const { publish, unpublish } = formattedActions[contentType];
784
+ return Promise.all([
785
+ ...publish.map((params) => strapi2.documents(contentType).publish(params)),
786
+ ...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
787
+ ]);
788
+ })
789
+ )
790
+ );
791
+ const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
792
+ where: {
793
+ id: releaseId
794
+ },
795
+ data: {
796
+ status: "done",
797
+ releasedAt: /* @__PURE__ */ new Date()
798
+ }
799
+ });
800
+ dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
801
+ isPublished: true,
802
+ release: release22
803
+ });
804
+ strapi2.telemetry.send("didPublishContentRelease");
805
+ return { release: release22, error: null };
806
+ } catch (error2) {
807
+ dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
808
+ isPublished: false,
809
+ error: error2
810
+ });
811
+ await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
812
+ status: "failed"
813
+ }).transacting(trx).execute();
814
+ return {
815
+ release: null,
816
+ error: error2
817
+ };
818
+ }
819
+ });
820
+ if (error instanceof Error) {
821
+ throw error;
822
+ }
823
+ return release2;
824
+ },
825
+ async updateReleaseStatus(releaseId) {
826
+ const releaseActionService = getService("release-action", { strapi: strapi2 });
827
+ const [totalActions, invalidActions] = await Promise.all([
828
+ releaseActionService.countActions({
829
+ filters: {
830
+ release: releaseId
831
+ }
832
+ }),
833
+ releaseActionService.countActions({
834
+ filters: {
835
+ release: releaseId,
836
+ isEntryValid: false
837
+ }
838
+ })
839
+ ]);
840
+ if (totalActions > 0) {
841
+ if (invalidActions > 0) {
842
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
843
+ where: {
844
+ id: releaseId
845
+ },
846
+ data: {
847
+ status: "blocked"
848
+ }
849
+ });
850
+ }
851
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
852
+ where: {
853
+ id: releaseId
854
+ },
855
+ data: {
856
+ status: "ready"
857
+ }
858
+ });
859
+ }
860
+ return strapi2.db.query(RELEASE_MODEL_UID).update({
861
+ where: {
862
+ id: releaseId
863
+ },
864
+ data: {
865
+ status: "empty"
866
+ }
867
+ });
868
+ }
869
+ };
870
+ };
871
+ const getGroupName = (queryValue) => {
872
+ switch (queryValue) {
873
+ case "contentType":
874
+ return "contentType.displayName";
875
+ case "type":
876
+ return "type";
877
+ case "locale":
878
+ return _.getOr("No locale", "locale.name");
879
+ default:
880
+ return "contentType.displayName";
881
+ }
882
+ };
883
+ const createReleaseActionService = ({ strapi: strapi2 }) => {
884
+ const getLocalesDataForActions = async () => {
885
+ if (!strapi2.plugin("i18n")) {
886
+ return {};
887
+ }
888
+ const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
889
+ return allLocales.reduce((acc, locale) => {
890
+ acc[locale.code] = { name: locale.name, code: locale.code };
891
+ return acc;
892
+ }, {});
893
+ };
894
+ const getContentTypesDataForActions = async (contentTypesUids) => {
895
+ const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
896
+ const contentTypesData = {};
897
+ for (const contentTypeUid of contentTypesUids) {
898
+ const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
899
+ uid: contentTypeUid
900
+ });
901
+ contentTypesData[contentTypeUid] = {
902
+ mainField: contentTypeConfig.settings.mainField,
903
+ displayName: strapi2.getModel(contentTypeUid).info.displayName
904
+ };
905
+ }
906
+ return contentTypesData;
907
+ };
908
+ return {
909
+ async create(releaseId, action) {
910
+ const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
782
911
  strapi: strapi2
783
912
  });
784
913
  await Promise.all([
785
- validateEntryContentType(action.entry.contentType),
914
+ validateEntryData(action.contentType, action.entryDocumentId),
786
915
  validateUniqueEntry(releaseId, action)
787
916
  ]);
788
917
  const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
@@ -792,28 +921,28 @@ const createReleaseService = ({ strapi: strapi2 }) => {
792
921
  if (release2.releasedAt) {
793
922
  throw new errors.ValidationError("Release already published");
794
923
  }
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 });
924
+ const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
925
+ {
926
+ contentType: action.contentType,
927
+ documentId: action.entryDocumentId,
928
+ locale: action.locale
929
+ },
930
+ {
931
+ strapi: strapi2
932
+ }
933
+ ) : true;
798
934
  const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
799
935
  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
936
+ ...action,
937
+ release: release2.id,
938
+ isEntryValid: actionStatus
810
939
  },
811
- populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
940
+ populate: { release: { select: ["id"] } }
812
941
  });
813
- this.updateReleaseStatus(releaseId);
942
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
814
943
  return releaseAction2;
815
944
  },
816
- async findActions(releaseId, query) {
945
+ async findPage(releaseId, query) {
817
946
  const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
818
947
  where: { id: releaseId },
819
948
  select: ["id"]
@@ -822,21 +951,35 @@ const createReleaseService = ({ strapi: strapi2 }) => {
822
951
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
823
952
  }
824
953
  const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
825
- return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
954
+ const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
826
955
  ...dbQuery,
827
- populate: {
828
- entry: {
829
- populate: "*"
830
- }
831
- },
832
956
  where: {
833
957
  release: releaseId
834
958
  }
835
959
  });
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);
960
+ const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
961
+ const actionsWithEntry = await async.map(actions, async (action) => {
962
+ const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
963
+ const entry = await getEntry(
964
+ {
965
+ contentType: action.contentType,
966
+ documentId: action.entryDocumentId,
967
+ locale: action.locale,
968
+ populate,
969
+ status: action.type === "publish" ? "draft" : "published"
970
+ },
971
+ { strapi: strapi2 }
972
+ );
973
+ return {
974
+ ...action,
975
+ entry,
976
+ status: entry ? await getEntryStatus(action.contentType, entry) : null
977
+ };
978
+ });
979
+ return {
980
+ results: actionsWithEntry,
981
+ pagination
982
+ };
840
983
  },
841
984
  async groupActions(actions, groupBy) {
842
985
  const contentTypeUids = actions.reduce((acc, action) => {
@@ -845,8 +988,8 @@ const createReleaseService = ({ strapi: strapi2 }) => {
845
988
  }
846
989
  return acc;
847
990
  }, []);
848
- const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(contentTypeUids);
849
- const allLocalesDictionary = await this.getLocalesDataForActions();
991
+ const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
992
+ const allLocalesDictionary = await getLocalesDataForActions();
850
993
  const formattedData = actions.map((action) => {
851
994
  const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
852
995
  return {
@@ -857,165 +1000,58 @@ const createReleaseService = ({ strapi: strapi2 }) => {
857
1000
  mainFieldValue: action.entry[mainField],
858
1001
  uid: action.contentType
859
1002
  }
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
- async getAllComponents() {
906
- const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
907
- const components = await contentManagerComponentsService.findAllComponents();
908
- const componentsMap = components.reduce(
909
- (acc, component) => {
910
- acc[component.uid] = component;
911
- return acc;
912
- },
913
- {}
914
- );
915
- return componentsMap;
916
- },
917
- async delete(releaseId) {
918
- const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
919
- where: { id: releaseId },
920
- populate: {
921
- actions: {
922
- select: ["id"]
923
- }
924
- }
925
- });
926
- if (!release2) {
927
- throw new errors.NotFoundError(`No release found for id ${releaseId}`);
928
- }
929
- if (release2.releasedAt) {
930
- throw new errors.ValidationError("Release already published");
931
- }
932
- await strapi2.db.transaction(async () => {
933
- await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
934
- where: {
935
- id: {
936
- $in: release2.actions.map((action) => action.id)
937
- }
938
- }
939
- });
940
- await strapi2.db.query(RELEASE_MODEL_UID).delete({
941
- where: {
942
- id: releaseId
943
- }
944
- });
1003
+ };
945
1004
  });
946
- if (release2.scheduledAt) {
947
- const schedulingService = getService("scheduling", { strapi: strapi2 });
948
- await schedulingService.cancel(release2.id);
949
- }
950
- strapi2.telemetry.send("didDeleteContentRelease");
951
- return release2;
1005
+ const groupName = getGroupName(groupBy);
1006
+ return _.groupBy(groupName)(formattedData);
952
1007
  },
953
- async publish(releaseId) {
954
- const {
955
- release: release2,
956
- error
957
- } = await strapi2.db.transaction(async ({ trx }) => {
958
- const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
959
- if (!lockedRelease) {
960
- throw new errors.NotFoundError(`No release found for id ${releaseId}`);
961
- }
962
- if (lockedRelease.releasedAt) {
963
- throw new errors.ValidationError("Release already published");
964
- }
965
- if (lockedRelease.status === "failed") {
966
- throw new errors.ValidationError("Release failed to publish");
1008
+ getContentTypeModelsFromActions(actions) {
1009
+ const contentTypeUids = actions.reduce((acc, action) => {
1010
+ if (!acc.includes(action.contentType)) {
1011
+ acc.push(action.contentType);
967
1012
  }
968
- try {
969
- strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
970
- const { collectionTypeActions, singleTypeActions } = await getFormattedActions(releaseId);
971
- await strapi2.db.transaction(async () => {
972
- for (const { uid, action, id } of singleTypeActions) {
973
- await publishSingleTypeAction(uid, action, id);
974
- }
975
- for (const contentTypeUid of Object.keys(collectionTypeActions)) {
976
- const uid = contentTypeUid;
977
- await publishCollectionTypeAction(
978
- uid,
979
- collectionTypeActions[uid].entriesToPublishIds,
980
- collectionTypeActions[uid].entriesToUnpublishIds
981
- );
982
- }
983
- });
984
- const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
985
- where: {
986
- id: releaseId
987
- },
988
- data: {
989
- status: "done",
990
- releasedAt: /* @__PURE__ */ new Date()
1013
+ return acc;
1014
+ }, []);
1015
+ const contentTypeModelsMap = contentTypeUids.reduce(
1016
+ (acc, contentTypeUid) => {
1017
+ acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
1018
+ return acc;
1019
+ },
1020
+ {}
1021
+ );
1022
+ return contentTypeModelsMap;
1023
+ },
1024
+ async countActions(query) {
1025
+ const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
1026
+ return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
1027
+ },
1028
+ async update(actionId, releaseId, update) {
1029
+ const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
1030
+ where: {
1031
+ id: actionId,
1032
+ release: {
1033
+ id: releaseId,
1034
+ releasedAt: {
1035
+ $null: true
991
1036
  }
992
- });
993
- dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
994
- isPublished: true,
995
- release: release22
996
- });
997
- strapi2.telemetry.send("didPublishContentRelease");
998
- return { release: release22, error: null };
999
- } catch (error2) {
1000
- dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
1001
- isPublished: false,
1002
- error: error2
1003
- });
1004
- await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
1005
- status: "failed"
1006
- }).transacting(trx).execute();
1007
- return {
1008
- release: null,
1009
- error: error2
1010
- };
1037
+ }
1011
1038
  }
1012
1039
  });
1013
- if (error instanceof Error) {
1014
- throw error;
1040
+ if (!action) {
1041
+ throw new errors.NotFoundError(
1042
+ `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1043
+ );
1015
1044
  }
1016
- return release2;
1017
- },
1018
- async updateAction(actionId, releaseId, update) {
1045
+ const actionStatus = update.type === "publish" ? getDraftEntryValidStatus(
1046
+ {
1047
+ contentType: action.contentType,
1048
+ documentId: action.entryDocumentId,
1049
+ locale: action.locale
1050
+ },
1051
+ {
1052
+ strapi: strapi2
1053
+ }
1054
+ ) : true;
1019
1055
  const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
1020
1056
  where: {
1021
1057
  id: actionId,
@@ -1026,16 +1062,15 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1026
1062
  }
1027
1063
  }
1028
1064
  },
1029
- data: update
1065
+ data: {
1066
+ ...update,
1067
+ isEntryValid: actionStatus
1068
+ }
1030
1069
  });
1031
- if (!updatedAction) {
1032
- throw new errors.NotFoundError(
1033
- `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1034
- );
1035
- }
1070
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1036
1071
  return updatedAction;
1037
1072
  },
1038
- async deleteAction(actionId, releaseId) {
1073
+ async delete(actionId, releaseId) {
1039
1074
  const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
1040
1075
  where: {
1041
1076
  id: actionId,
@@ -1052,51 +1087,8 @@ const createReleaseService = ({ strapi: strapi2 }) => {
1052
1087
  `Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
1053
1088
  );
1054
1089
  }
1055
- this.updateReleaseStatus(releaseId);
1090
+ getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
1056
1091
  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
1092
  }
1101
1093
  };
1102
1094
  };
@@ -1112,30 +1104,35 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
1112
1104
  where: {
1113
1105
  id: releaseId
1114
1106
  },
1115
- populate: { actions: { populate: { entry: { select: ["id"] } } } }
1107
+ populate: {
1108
+ actions: true
1109
+ }
1116
1110
  });
1117
1111
  if (!release2) {
1118
1112
  throw new errors.NotFoundError(`No release found for id ${releaseId}`);
1119
1113
  }
1120
1114
  const isEntryInRelease = release2.actions.some(
1121
- (action) => Number(action.entry.id) === Number(releaseActionArgs.entry.id) && action.contentType === releaseActionArgs.entry.contentType
1115
+ (action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
1122
1116
  );
1123
1117
  if (isEntryInRelease) {
1124
1118
  throw new AlreadyOnReleaseError(
1125
- `Entry with id ${releaseActionArgs.entry.id} and contentType ${releaseActionArgs.entry.contentType} already exists in release with id ${releaseId}`
1119
+ `Entry with documentId ${releaseActionArgs.entryDocumentId}${releaseActionArgs.locale ? `( ${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
1126
1120
  );
1127
1121
  }
1128
1122
  },
1129
- validateEntryContentType(contentTypeUid) {
1123
+ validateEntryData(contentTypeUid, entryDocumentId) {
1130
1124
  const contentType = strapi2.contentType(contentTypeUid);
1131
1125
  if (!contentType) {
1132
1126
  throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
1133
1127
  }
1134
- if (!contentType.options?.draftAndPublish) {
1128
+ if (!contentTypes$1.hasDraftAndPublish(contentType)) {
1135
1129
  throw new errors.ValidationError(
1136
1130
  `Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
1137
1131
  );
1138
1132
  }
1133
+ if (contentType.kind === "collectionType" && !entryDocumentId) {
1134
+ throw new errors.ValidationError("Document id is required for collection type");
1135
+ }
1139
1136
  },
1140
1137
  async validatePendingReleasesLimit() {
1141
1138
  const featureCfg = strapi2.ee.features.get("cms-content-releases");
@@ -1224,78 +1221,152 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
1224
1221
  }
1225
1222
  };
1226
1223
  };
1224
+ const DEFAULT_SETTINGS = {
1225
+ defaultTimezone: null
1226
+ };
1227
+ const createSettingsService = ({ strapi: strapi2 }) => {
1228
+ const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
1229
+ return {
1230
+ async update({ settings: settings2 }) {
1231
+ const store = await getStore();
1232
+ store.set({ key: "settings", value: settings2 });
1233
+ return settings2;
1234
+ },
1235
+ async find() {
1236
+ const store = await getStore();
1237
+ const settings2 = await store.get({ key: "settings" });
1238
+ return {
1239
+ ...DEFAULT_SETTINGS,
1240
+ ...settings2 || {}
1241
+ };
1242
+ }
1243
+ };
1244
+ };
1227
1245
  const services = {
1228
1246
  release: createReleaseService,
1247
+ "release-action": createReleaseActionService,
1229
1248
  "release-validation": createReleaseValidationService,
1230
- scheduling: createSchedulingService
1249
+ scheduling: createSchedulingService,
1250
+ settings: createSettingsService
1231
1251
  };
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()
1252
+ const RELEASE_SCHEMA = yup$1.object().shape({
1253
+ name: yup$1.string().trim().required(),
1254
+ scheduledAt: yup$1.string().nullable(),
1255
+ timezone: yup$1.string().when("scheduledAt", {
1256
+ is: (value) => value !== null && value !== void 0,
1257
+ then: yup$1.string().required(),
1258
+ otherwise: yup$1.string().nullable()
1250
1259
  })
1251
1260
  }).required().noUnknown();
1261
+ const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = yup$1.object().shape({
1262
+ contentType: yup$1.string().required(),
1263
+ entryDocumentId: yup$1.string().nullable(),
1264
+ hasEntryAttached: yup$1.string().nullable(),
1265
+ locale: yup$1.string().nullable()
1266
+ }).required().noUnknown();
1252
1267
  const validateRelease = validateYupSchema(RELEASE_SCHEMA);
1268
+ const validatefindByDocumentAttachedParams = validateYupSchema(
1269
+ FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
1270
+ );
1253
1271
  const releaseController = {
1254
- async findMany(ctx) {
1272
+ /**
1273
+ * Find releases based on documents attached or not to the release.
1274
+ * If `hasEntryAttached` is true, it will return all releases that have the entry attached.
1275
+ * If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
1276
+ */
1277
+ async findByDocumentAttached(ctx) {
1255
1278
  const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1256
1279
  ability: ctx.state.userAbility,
1257
1280
  model: RELEASE_MODEL_UID
1258
1281
  });
1259
1282
  await permissionsManager.validateQuery(ctx.query);
1260
1283
  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 };
1284
+ const query = await permissionsManager.sanitizeQuery(ctx.query);
1285
+ await validatefindByDocumentAttachedParams(query);
1286
+ const { contentType, entryDocumentId, hasEntryAttached, locale } = query;
1287
+ const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
1288
+ if (isEntryAttached) {
1289
+ const releases = await releaseService.findMany({
1290
+ where: {
1291
+ releasedAt: null,
1292
+ actions: {
1293
+ contentType,
1294
+ entryDocumentId: entryDocumentId ?? null,
1295
+ locale: locale ?? null
1296
+ }
1297
+ },
1298
+ populate: {
1299
+ actions: {
1300
+ fields: ["type"]
1301
+ }
1302
+ }
1303
+ });
1304
+ ctx.body = { data: releases };
1269
1305
  } 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,
1306
+ const relatedReleases = await releaseService.findMany({
1307
+ where: {
1308
+ releasedAt: null,
1276
1309
  actions: {
1277
- meta: {
1278
- count: actions.count
1279
- }
1310
+ contentType,
1311
+ entryDocumentId: entryDocumentId ?? null,
1312
+ locale: locale ?? null
1280
1313
  }
1281
- };
1314
+ }
1282
1315
  });
1283
- const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1316
+ const releases = await releaseService.findMany({
1284
1317
  where: {
1318
+ $or: [
1319
+ {
1320
+ id: {
1321
+ $notIn: relatedReleases.map((release2) => release2.id)
1322
+ }
1323
+ },
1324
+ {
1325
+ actions: null
1326
+ }
1327
+ ],
1285
1328
  releasedAt: null
1286
1329
  }
1287
1330
  });
1288
- ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1331
+ ctx.body = { data: releases };
1289
1332
  }
1290
1333
  },
1334
+ async findPage(ctx) {
1335
+ const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
1336
+ ability: ctx.state.userAbility,
1337
+ model: RELEASE_MODEL_UID
1338
+ });
1339
+ await permissionsManager.validateQuery(ctx.query);
1340
+ const releaseService = getService("release", { strapi });
1341
+ const query = await permissionsManager.sanitizeQuery(ctx.query);
1342
+ const { results, pagination } = await releaseService.findPage(query);
1343
+ const data = results.map((release2) => {
1344
+ const { actions, ...releaseData } = release2;
1345
+ return {
1346
+ ...releaseData,
1347
+ actions: {
1348
+ meta: {
1349
+ count: actions.count
1350
+ }
1351
+ }
1352
+ };
1353
+ });
1354
+ const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
1355
+ where: {
1356
+ releasedAt: null
1357
+ }
1358
+ });
1359
+ ctx.body = { data, meta: { pagination, pendingReleasesCount } };
1360
+ },
1291
1361
  async findOne(ctx) {
1292
1362
  const id = ctx.params.id;
1293
1363
  const releaseService = getService("release", { strapi });
1364
+ const releaseActionService = getService("release-action", { strapi });
1294
1365
  const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
1295
1366
  if (!release2) {
1296
1367
  throw new errors.NotFoundError(`Release not found for id: ${id}`);
1297
1368
  }
1298
- const count = await releaseService.countActions({
1369
+ const count = await releaseActionService.countActions({
1299
1370
  filters: {
1300
1371
  release: id
1301
1372
  }
@@ -1315,28 +1386,43 @@ const releaseController = {
1315
1386
  ctx.body = { data };
1316
1387
  },
1317
1388
  async mapEntriesToReleases(ctx) {
1318
- const { contentTypeUid, entriesIds } = ctx.query;
1319
- if (!contentTypeUid || !entriesIds) {
1389
+ const { contentTypeUid, documentIds, locale } = ctx.query;
1390
+ if (!contentTypeUid || !documentIds) {
1320
1391
  throw new errors.ValidationError("Missing required query parameters");
1321
1392
  }
1322
1393
  const releaseService = getService("release", { strapi });
1323
- const releasesWithActions = await releaseService.findManyWithContentTypeEntryAttached(
1324
- contentTypeUid,
1325
- entriesIds
1326
- );
1394
+ const releasesWithActions = await releaseService.findMany({
1395
+ where: {
1396
+ releasedAt: null,
1397
+ actions: {
1398
+ contentType: contentTypeUid,
1399
+ entryDocumentId: {
1400
+ $in: documentIds
1401
+ },
1402
+ locale
1403
+ }
1404
+ },
1405
+ populate: {
1406
+ actions: true
1407
+ }
1408
+ });
1327
1409
  const mappedEntriesInReleases = releasesWithActions.reduce(
1328
- // TODO: Fix for v5 removed mappedEntriedToRelease
1329
1410
  (acc, release2) => {
1330
1411
  release2.actions.forEach((action) => {
1331
- if (!acc[action.entry.id]) {
1332
- acc[action.entry.id] = [{ id: release2.id, name: release2.name }];
1412
+ if (action.contentType !== contentTypeUid) {
1413
+ return;
1414
+ }
1415
+ if (locale && action.locale !== locale) {
1416
+ return;
1417
+ }
1418
+ if (!acc[action.entryDocumentId]) {
1419
+ acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
1333
1420
  } else {
1334
- acc[action.entry.id].push({ id: release2.id, name: release2.name });
1421
+ acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
1335
1422
  }
1336
1423
  });
1337
1424
  return acc;
1338
1425
  },
1339
- // TODO: Fix for v5 removed mappedEntriedToRelease
1340
1426
  {}
1341
1427
  );
1342
1428
  ctx.body = {
@@ -1381,18 +1467,18 @@ const releaseController = {
1381
1467
  };
1382
1468
  },
1383
1469
  async publish(ctx) {
1384
- const user = ctx.state.user;
1385
1470
  const id = ctx.params.id;
1386
1471
  const releaseService = getService("release", { strapi });
1387
- const release2 = await releaseService.publish(id, { user });
1472
+ const releaseActionService = getService("release-action", { strapi });
1473
+ const release2 = await releaseService.publish(id);
1388
1474
  const [countPublishActions, countUnpublishActions] = await Promise.all([
1389
- releaseService.countActions({
1475
+ releaseActionService.countActions({
1390
1476
  filters: {
1391
1477
  release: id,
1392
1478
  type: "publish"
1393
1479
  }
1394
1480
  }),
1395
- releaseService.countActions({
1481
+ releaseActionService.countActions({
1396
1482
  filters: {
1397
1483
  release: id,
1398
1484
  type: "unpublish"
@@ -1410,24 +1496,27 @@ const releaseController = {
1410
1496
  }
1411
1497
  };
1412
1498
  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(),
1499
+ contentType: yup$1.string().required(),
1500
+ entryDocumentId: yup$1.strapiID(),
1501
+ locale: yup$1.string(),
1417
1502
  type: yup$1.string().oneOf(["publish", "unpublish"]).required()
1418
1503
  });
1419
1504
  const RELEASE_ACTION_UPDATE_SCHEMA = yup$1.object().shape({
1420
1505
  type: yup$1.string().oneOf(["publish", "unpublish"]).required()
1421
1506
  });
1507
+ const FIND_MANY_ACTIONS_PARAMS = yup$1.object().shape({
1508
+ groupBy: yup$1.string().oneOf(["action", "contentType", "locale"])
1509
+ });
1422
1510
  const validateReleaseAction = validateYupSchema(RELEASE_ACTION_SCHEMA);
1423
1511
  const validateReleaseActionUpdateSchema = validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
1512
+ const validateFindManyActionsParams = validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
1424
1513
  const releaseActionController = {
1425
1514
  async create(ctx) {
1426
1515
  const releaseId = ctx.params.releaseId;
1427
1516
  const releaseActionArgs = ctx.request.body;
1428
1517
  await validateReleaseAction(releaseActionArgs);
1429
- const releaseService = getService("release", { strapi });
1430
- const releaseAction2 = await releaseService.createAction(releaseId, releaseActionArgs);
1518
+ const releaseActionService = getService("release-action", { strapi });
1519
+ const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
1431
1520
  ctx.created({
1432
1521
  data: releaseAction2
1433
1522
  });
@@ -1438,12 +1527,12 @@ const releaseActionController = {
1438
1527
  await Promise.all(
1439
1528
  releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
1440
1529
  );
1441
- const releaseService = getService("release", { strapi });
1530
+ const releaseActionService = getService("release-action", { strapi });
1442
1531
  const releaseActions = await strapi.db.transaction(async () => {
1443
1532
  const releaseActions2 = await Promise.all(
1444
1533
  releaseActionsArgs.map(async (releaseActionArgs) => {
1445
1534
  try {
1446
- const action = await releaseService.createAction(releaseId, releaseActionArgs);
1535
+ const action = await releaseActionService.create(releaseId, releaseActionArgs);
1447
1536
  return action;
1448
1537
  } catch (error) {
1449
1538
  if (error instanceof AlreadyOnReleaseError) {
@@ -1470,10 +1559,17 @@ const releaseActionController = {
1470
1559
  ability: ctx.state.userAbility,
1471
1560
  model: RELEASE_ACTION_MODEL_UID
1472
1561
  });
1562
+ await validateFindManyActionsParams(ctx.query);
1563
+ if (ctx.query.groupBy) {
1564
+ if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
1565
+ ctx.badRequest("Invalid groupBy parameter");
1566
+ }
1567
+ }
1568
+ ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
1569
+ delete ctx.query.groupBy;
1473
1570
  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,
1571
+ const releaseActionService = getService("release-action", { strapi });
1572
+ const { results, pagination } = await releaseActionService.findPage(releaseId, {
1477
1573
  ...query
1478
1574
  });
1479
1575
  const contentTypeOutputSanitizers = results.reduce((acc, action) => {
@@ -1489,10 +1585,11 @@ const releaseActionController = {
1489
1585
  }, {});
1490
1586
  const sanitizedResults = await async.map(results, async (action) => ({
1491
1587
  ...action,
1492
- entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
1588
+ entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
1493
1589
  }));
1494
- const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
1495
- const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
1590
+ const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
1591
+ const contentTypes2 = releaseActionService.getContentTypeModelsFromActions(results);
1592
+ const releaseService = getService("release", { strapi });
1496
1593
  const components = await releaseService.getAllComponents();
1497
1594
  ctx.body = {
1498
1595
  data: groupedData,
@@ -1508,8 +1605,8 @@ const releaseActionController = {
1508
1605
  const releaseId = ctx.params.releaseId;
1509
1606
  const releaseActionUpdateArgs = ctx.request.body;
1510
1607
  await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
1511
- const releaseService = getService("release", { strapi });
1512
- const updatedAction = await releaseService.updateAction(
1608
+ const releaseActionService = getService("release-action", { strapi });
1609
+ const updatedAction = await releaseActionService.update(
1513
1610
  actionId,
1514
1611
  releaseId,
1515
1612
  releaseActionUpdateArgs
@@ -1521,14 +1618,36 @@ const releaseActionController = {
1521
1618
  async delete(ctx) {
1522
1619
  const actionId = ctx.params.actionId;
1523
1620
  const releaseId = ctx.params.releaseId;
1524
- const releaseService = getService("release", { strapi });
1525
- const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
1621
+ const releaseActionService = getService("release-action", { strapi });
1622
+ const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
1526
1623
  ctx.body = {
1527
1624
  data: deletedReleaseAction
1528
1625
  };
1529
1626
  }
1530
1627
  };
1531
- const controllers = { release: releaseController, "release-action": releaseActionController };
1628
+ const SETTINGS_SCHEMA = yup.object().shape({
1629
+ defaultTimezone: yup.string().nullable().default(null)
1630
+ }).required().noUnknown();
1631
+ const validateSettings = validateYupSchema(SETTINGS_SCHEMA);
1632
+ const settingsController = {
1633
+ async find(ctx) {
1634
+ const settingsService = getService("settings", { strapi });
1635
+ const settings2 = await settingsService.find();
1636
+ ctx.body = { data: settings2 };
1637
+ },
1638
+ async update(ctx) {
1639
+ const settingsBody = ctx.request.body;
1640
+ const settings2 = await validateSettings(settingsBody);
1641
+ const settingsService = getService("settings", { strapi });
1642
+ const updatedSettings = await settingsService.update({ settings: settings2 });
1643
+ ctx.body = { data: updatedSettings };
1644
+ }
1645
+ };
1646
+ const controllers = {
1647
+ release: releaseController,
1648
+ "release-action": releaseActionController,
1649
+ settings: settingsController
1650
+ };
1532
1651
  const release = {
1533
1652
  type: "admin",
1534
1653
  routes: [
@@ -1548,6 +1667,22 @@ const release = {
1548
1667
  ]
1549
1668
  }
1550
1669
  },
1670
+ {
1671
+ method: "GET",
1672
+ path: "/getByDocumentAttached",
1673
+ handler: "release.findByDocumentAttached",
1674
+ config: {
1675
+ policies: [
1676
+ "admin::isAuthenticatedAdmin",
1677
+ {
1678
+ name: "admin::hasPermissions",
1679
+ config: {
1680
+ actions: ["plugin::content-releases.read"]
1681
+ }
1682
+ }
1683
+ ]
1684
+ }
1685
+ },
1551
1686
  {
1552
1687
  method: "POST",
1553
1688
  path: "/",
@@ -1567,7 +1702,7 @@ const release = {
1567
1702
  {
1568
1703
  method: "GET",
1569
1704
  path: "/",
1570
- handler: "release.findMany",
1705
+ handler: "release.findPage",
1571
1706
  config: {
1572
1707
  policies: [
1573
1708
  "admin::isAuthenticatedAdmin",
@@ -1731,7 +1866,45 @@ const releaseAction = {
1731
1866
  }
1732
1867
  ]
1733
1868
  };
1869
+ const settings = {
1870
+ type: "admin",
1871
+ routes: [
1872
+ {
1873
+ method: "GET",
1874
+ path: "/settings",
1875
+ handler: "settings.find",
1876
+ config: {
1877
+ policies: [
1878
+ "admin::isAuthenticatedAdmin",
1879
+ {
1880
+ name: "admin::hasPermissions",
1881
+ config: {
1882
+ actions: ["plugin::content-releases.settings.read"]
1883
+ }
1884
+ }
1885
+ ]
1886
+ }
1887
+ },
1888
+ {
1889
+ method: "PUT",
1890
+ path: "/settings",
1891
+ handler: "settings.update",
1892
+ config: {
1893
+ policies: [
1894
+ "admin::isAuthenticatedAdmin",
1895
+ {
1896
+ name: "admin::hasPermissions",
1897
+ config: {
1898
+ actions: ["plugin::content-releases.settings.update"]
1899
+ }
1900
+ }
1901
+ ]
1902
+ }
1903
+ }
1904
+ ]
1905
+ };
1734
1906
  const routes = {
1907
+ settings,
1735
1908
  release,
1736
1909
  "release-action": releaseAction
1737
1910
  };