@strapi/content-manager 0.0.0-experimental.a65a85fdea97faae8679d3ffc5f9d79af61abd26 → 0.0.0-experimental.bd712ad3930045f4a5d2144c119e0b7856e97fc4

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 (180) hide show
  1. package/LICENSE +18 -3
  2. package/dist/_chunks/{CardDragPreview-DSVYodBX.js → CardDragPreview-C0QyJgRA.js} +10 -14
  3. package/dist/_chunks/CardDragPreview-C0QyJgRA.js.map +1 -0
  4. package/dist/_chunks/{CardDragPreview-ikSG4M46.mjs → CardDragPreview-DOxamsuj.mjs} +7 -9
  5. package/dist/_chunks/CardDragPreview-DOxamsuj.mjs.map +1 -0
  6. package/dist/_chunks/{ComponentConfigurationPage-43KmCNQE.js → ComponentConfigurationPage-BWQv6yRj.js} +3 -3
  7. package/dist/_chunks/{ComponentConfigurationPage-43KmCNQE.js.map → ComponentConfigurationPage-BWQv6yRj.js.map} +1 -1
  8. package/dist/_chunks/{ComponentConfigurationPage--2aLCv-G.mjs → ComponentConfigurationPage-C7ImeKGM.mjs} +3 -3
  9. package/dist/_chunks/{ComponentConfigurationPage--2aLCv-G.mjs.map → ComponentConfigurationPage-C7ImeKGM.mjs.map} +1 -1
  10. package/dist/_chunks/{ComponentIcon-BBQsYCVn.js → ComponentIcon-BXdiCGQp.js} +8 -2
  11. package/dist/_chunks/ComponentIcon-BXdiCGQp.js.map +1 -0
  12. package/dist/_chunks/{ComponentIcon-BOFnK76n.mjs → ComponentIcon-u4bIXTFY.mjs} +9 -3
  13. package/dist/_chunks/ComponentIcon-u4bIXTFY.mjs.map +1 -0
  14. package/dist/_chunks/{EditConfigurationPage-BfFzJ4Br.js → EditConfigurationPage-CEGwxV-L.js} +3 -3
  15. package/dist/_chunks/{EditConfigurationPage-BfFzJ4Br.js.map → EditConfigurationPage-CEGwxV-L.js.map} +1 -1
  16. package/dist/_chunks/{EditConfigurationPage-CUcGHHvQ.mjs → EditConfigurationPage-MItFGzT9.mjs} +3 -3
  17. package/dist/_chunks/{EditConfigurationPage-CUcGHHvQ.mjs.map → EditConfigurationPage-MItFGzT9.mjs.map} +1 -1
  18. package/dist/_chunks/{EditViewPage-CzOT5Kpj.js → EditViewPage-CmMi2Xsn.js} +46 -48
  19. package/dist/_chunks/EditViewPage-CmMi2Xsn.js.map +1 -0
  20. package/dist/_chunks/{EditViewPage-Bm8lgcm6.mjs → EditViewPage-DhmAg0NK.mjs} +47 -47
  21. package/dist/_chunks/EditViewPage-DhmAg0NK.mjs.map +1 -0
  22. package/dist/_chunks/{Field-Caef4JjM.js → Field-1DLtcLAI.js} +948 -777
  23. package/dist/_chunks/Field-1DLtcLAI.js.map +1 -0
  24. package/dist/_chunks/{Field-Dlh0uGnL.mjs → Field-Cs62u5pl.mjs} +896 -724
  25. package/dist/_chunks/Field-Cs62u5pl.mjs.map +1 -0
  26. package/dist/_chunks/{Form-BzuAjtRq.js → Form-CqFA7F_V.js} +42 -41
  27. package/dist/_chunks/Form-CqFA7F_V.js.map +1 -0
  28. package/dist/_chunks/{Form-EnaQL_6L.mjs → Form-zYHtzGUX.mjs} +43 -41
  29. package/dist/_chunks/Form-zYHtzGUX.mjs.map +1 -0
  30. package/dist/_chunks/{History-C17LiyRg.js → History-BblwXv7-.js} +149 -66
  31. package/dist/_chunks/History-BblwXv7-.js.map +1 -0
  32. package/dist/_chunks/{History-D6sbCJvo.mjs → History-DalgFQ3D.mjs} +149 -65
  33. package/dist/_chunks/History-DalgFQ3D.mjs.map +1 -0
  34. package/dist/_chunks/{ListConfigurationPage-Dks5SX6f.js → ListConfigurationPage-Cpy4QqNd.js} +61 -62
  35. package/dist/_chunks/ListConfigurationPage-Cpy4QqNd.js.map +1 -0
  36. package/dist/_chunks/{ListConfigurationPage-Ce4qs7qE.mjs → ListConfigurationPage-DWy-vRzs.mjs} +58 -58
  37. package/dist/_chunks/ListConfigurationPage-DWy-vRzs.mjs.map +1 -0
  38. package/dist/_chunks/{ListViewPage-Be7S5aKL.mjs → ListViewPage-BkAwIW9s.mjs} +90 -106
  39. package/dist/_chunks/ListViewPage-BkAwIW9s.mjs.map +1 -0
  40. package/dist/_chunks/{ListViewPage-BwrZrPsh.js → ListViewPage-DFjn1DNW.js} +95 -111
  41. package/dist/_chunks/ListViewPage-DFjn1DNW.js.map +1 -0
  42. package/dist/_chunks/{NoContentTypePage-CIPmYQMm.mjs → NoContentTypePage-B9BCNNdL.mjs} +7 -7
  43. package/dist/_chunks/NoContentTypePage-B9BCNNdL.mjs.map +1 -0
  44. package/dist/_chunks/{NoContentTypePage-Cu5r1-JT.js → NoContentTypePage-C-3ykoxs.js} +5 -5
  45. package/dist/_chunks/NoContentTypePage-C-3ykoxs.js.map +1 -0
  46. package/dist/_chunks/{NoPermissionsPage-DhJ7LYrr.mjs → NoPermissionsPage-Bt_HWGat.mjs} +5 -6
  47. package/dist/_chunks/NoPermissionsPage-Bt_HWGat.mjs.map +1 -0
  48. package/dist/_chunks/{NoPermissionsPage-C-j6TEUF.js → NoPermissionsPage-DKLmDZnZ.js} +4 -5
  49. package/dist/_chunks/NoPermissionsPage-DKLmDZnZ.js.map +1 -0
  50. package/dist/_chunks/{Relations-CY7AtkDA.mjs → Relations-CJmTbZ8T.mjs} +66 -56
  51. package/dist/_chunks/Relations-CJmTbZ8T.mjs.map +1 -0
  52. package/dist/_chunks/{Relations-Czs-uZ-s.js → Relations-CrxfoH2n.js} +70 -61
  53. package/dist/_chunks/Relations-CrxfoH2n.js.map +1 -0
  54. package/dist/_chunks/{en-MBPul9Su.mjs → en-Ux26r5pl.mjs} +7 -1
  55. package/dist/_chunks/{en-MBPul9Su.mjs.map → en-Ux26r5pl.mjs.map} +1 -1
  56. package/dist/_chunks/{en-C-V1_90f.js → en-fbKQxLGn.js} +7 -1
  57. package/dist/_chunks/{en-C-V1_90f.js.map → en-fbKQxLGn.js.map} +1 -1
  58. package/dist/_chunks/{index-X_2tafck.js → index-Buwn78Rt.js} +1507 -868
  59. package/dist/_chunks/index-Buwn78Rt.js.map +1 -0
  60. package/dist/_chunks/{index-DNVx8ssZ.mjs → index-D1344xdw.mjs} +1476 -836
  61. package/dist/_chunks/index-D1344xdw.mjs.map +1 -0
  62. package/dist/_chunks/{layout-Dnh0PNp9.mjs → layout-ChVuUpa1.mjs} +32 -27
  63. package/dist/_chunks/layout-ChVuUpa1.mjs.map +1 -0
  64. package/dist/_chunks/{layout-dBc7wN7L.js → layout-DRuJUpas.js} +32 -29
  65. package/dist/_chunks/layout-DRuJUpas.js.map +1 -0
  66. package/dist/_chunks/{relations-Dx7tMKJN.mjs → relations-B-deMCy4.mjs} +2 -2
  67. package/dist/_chunks/{relations-Dx7tMKJN.mjs.map → relations-B-deMCy4.mjs.map} +1 -1
  68. package/dist/_chunks/{relations-4pHtBrHJ.js → relations-DuoUwyJr.js} +2 -2
  69. package/dist/_chunks/{relations-4pHtBrHJ.js.map → relations-DuoUwyJr.js.map} +1 -1
  70. package/dist/_chunks/useDragAndDrop-DdHgKsqq.mjs.map +1 -1
  71. package/dist/_chunks/useDragAndDrop-J0TUUbR6.js.map +1 -1
  72. package/dist/_chunks/usePrev-B9w_-eYc.js +15 -0
  73. package/dist/_chunks/usePrev-B9w_-eYc.js.map +1 -0
  74. package/dist/_chunks/usePrev-DH6iah0A.mjs +16 -0
  75. package/dist/_chunks/usePrev-DH6iah0A.mjs.map +1 -0
  76. package/dist/admin/index.js +2 -1
  77. package/dist/admin/index.js.map +1 -1
  78. package/dist/admin/index.mjs +5 -4
  79. package/dist/admin/src/components/ComponentIcon.d.ts +6 -3
  80. package/dist/admin/src/content-manager.d.ts +3 -3
  81. package/dist/admin/src/exports.d.ts +1 -0
  82. package/dist/admin/src/history/components/VersionInputRenderer.d.ts +1 -1
  83. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  84. package/dist/admin/src/hooks/useDocument.d.ts +5 -8
  85. package/dist/admin/src/hooks/useDocumentActions.d.ts +24 -3
  86. package/dist/admin/src/hooks/useDocumentLayout.d.ts +2 -2
  87. package/dist/admin/src/hooks/useDragAndDrop.d.ts +4 -4
  88. package/dist/admin/src/hooks/useKeyboardDragAndDrop.d.ts +1 -1
  89. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +10 -4
  90. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksInput.d.ts +3 -3
  91. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/constants.d.ts +4 -0
  92. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Input.d.ts +2 -2
  93. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.d.ts +3 -5
  94. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/Field.d.ts +1 -1
  95. package/dist/admin/src/pages/EditView/components/FormInputs/Relations.d.ts +10 -18
  96. package/dist/admin/src/pages/EditView/components/FormInputs/UID.d.ts +2 -2
  97. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +3 -49
  98. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/Field.d.ts +2 -2
  99. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +59 -52
  100. package/dist/admin/src/pages/EditView/components/InputRenderer.d.ts +2 -10
  101. package/dist/admin/src/pages/ListView/components/BulkActions/Actions.d.ts +3 -30
  102. package/dist/admin/src/pages/ListView/components/BulkActions/ConfirmBulkActionDialog.d.ts +2 -2
  103. package/dist/admin/src/pages/ListView/components/BulkActions/PublishAction.d.ts +9 -26
  104. package/dist/admin/src/services/api.d.ts +2 -3
  105. package/dist/admin/src/services/components.d.ts +2 -2
  106. package/dist/admin/src/services/contentTypes.d.ts +5 -5
  107. package/dist/admin/src/services/documents.d.ts +29 -17
  108. package/dist/admin/src/services/init.d.ts +2 -2
  109. package/dist/admin/src/services/relations.d.ts +3 -3
  110. package/dist/admin/src/services/uid.d.ts +3 -3
  111. package/dist/admin/src/utils/api.d.ts +4 -18
  112. package/dist/admin/src/utils/validation.d.ts +1 -6
  113. package/dist/server/index.js +547 -416
  114. package/dist/server/index.js.map +1 -1
  115. package/dist/server/index.mjs +555 -424
  116. package/dist/server/index.mjs.map +1 -1
  117. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  118. package/dist/server/src/controllers/single-types.d.ts.map +1 -1
  119. package/dist/server/src/controllers/utils/metadata.d.ts +8 -0
  120. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -0
  121. package/dist/server/src/controllers/validation/dimensions.d.ts +9 -0
  122. package/dist/server/src/controllers/validation/dimensions.d.ts.map +1 -0
  123. package/dist/server/src/controllers/validation/index.d.ts +1 -1
  124. package/dist/server/src/history/services/history.d.ts +2 -4
  125. package/dist/server/src/history/services/history.d.ts.map +1 -1
  126. package/dist/server/src/history/services/index.d.ts +6 -2
  127. package/dist/server/src/history/services/index.d.ts.map +1 -1
  128. package/dist/server/src/history/services/lifecycles.d.ts +9 -0
  129. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -0
  130. package/dist/server/src/history/services/utils.d.ts +41 -9
  131. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  132. package/dist/server/src/history/utils.d.ts +6 -2
  133. package/dist/server/src/history/utils.d.ts.map +1 -1
  134. package/dist/server/src/index.d.ts +18 -39
  135. package/dist/server/src/index.d.ts.map +1 -1
  136. package/dist/server/src/services/document-manager.d.ts +13 -12
  137. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  138. package/dist/server/src/services/document-metadata.d.ts +8 -29
  139. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  140. package/dist/server/src/services/index.d.ts +18 -39
  141. package/dist/server/src/services/index.d.ts.map +1 -1
  142. package/dist/server/src/services/utils/populate.d.ts +8 -1
  143. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  144. package/dist/shared/contracts/collection-types.d.ts +14 -6
  145. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  146. package/dist/shared/contracts/relations.d.ts +2 -2
  147. package/dist/shared/contracts/relations.d.ts.map +1 -1
  148. package/package.json +13 -14
  149. package/dist/_chunks/CardDragPreview-DSVYodBX.js.map +0 -1
  150. package/dist/_chunks/CardDragPreview-ikSG4M46.mjs.map +0 -1
  151. package/dist/_chunks/ComponentIcon-BBQsYCVn.js.map +0 -1
  152. package/dist/_chunks/ComponentIcon-BOFnK76n.mjs.map +0 -1
  153. package/dist/_chunks/EditViewPage-Bm8lgcm6.mjs.map +0 -1
  154. package/dist/_chunks/EditViewPage-CzOT5Kpj.js.map +0 -1
  155. package/dist/_chunks/Field-Caef4JjM.js.map +0 -1
  156. package/dist/_chunks/Field-Dlh0uGnL.mjs.map +0 -1
  157. package/dist/_chunks/Form-BzuAjtRq.js.map +0 -1
  158. package/dist/_chunks/Form-EnaQL_6L.mjs.map +0 -1
  159. package/dist/_chunks/History-C17LiyRg.js.map +0 -1
  160. package/dist/_chunks/History-D6sbCJvo.mjs.map +0 -1
  161. package/dist/_chunks/ListConfigurationPage-Ce4qs7qE.mjs.map +0 -1
  162. package/dist/_chunks/ListConfigurationPage-Dks5SX6f.js.map +0 -1
  163. package/dist/_chunks/ListViewPage-Be7S5aKL.mjs.map +0 -1
  164. package/dist/_chunks/ListViewPage-BwrZrPsh.js.map +0 -1
  165. package/dist/_chunks/NoContentTypePage-CIPmYQMm.mjs.map +0 -1
  166. package/dist/_chunks/NoContentTypePage-Cu5r1-JT.js.map +0 -1
  167. package/dist/_chunks/NoPermissionsPage-C-j6TEUF.js.map +0 -1
  168. package/dist/_chunks/NoPermissionsPage-DhJ7LYrr.mjs.map +0 -1
  169. package/dist/_chunks/Relations-CY7AtkDA.mjs.map +0 -1
  170. package/dist/_chunks/Relations-Czs-uZ-s.js.map +0 -1
  171. package/dist/_chunks/index-DNVx8ssZ.mjs.map +0 -1
  172. package/dist/_chunks/index-X_2tafck.js.map +0 -1
  173. package/dist/_chunks/layout-Dnh0PNp9.mjs.map +0 -1
  174. package/dist/_chunks/layout-dBc7wN7L.js.map +0 -1
  175. package/dist/_chunks/urls-CbOsUOoW.mjs +0 -7
  176. package/dist/_chunks/urls-CbOsUOoW.mjs.map +0 -1
  177. package/dist/_chunks/urls-DzZya_gm.js +0 -6
  178. package/dist/_chunks/urls-DzZya_gm.js.map +0 -1
  179. package/dist/server/src/controllers/utils/dimensions.d.ts +0 -5
  180. package/dist/server/src/controllers/utils/dimensions.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
- import strapiUtils, { validateYupSchema, errors, async, contentTypes as contentTypes$1, yup as yup$1, validateYupSchemaSync, policy, traverse, setCreatorFields, isOperatorOfType, relations as relations$1, sanitize } from "@strapi/utils";
2
- import { pick, omit, difference, intersection, pipe, propOr, isEqual, isEmpty, set, isNil as isNil$1, has, prop, assoc, mapValues, flow, uniq, uniqBy, concat, getOr, propEq, merge, groupBy, castArray } from "lodash/fp";
1
+ import strapiUtils, { validateYupSchema, errors, async, contentTypes as contentTypes$1, yup as yup$1, validateYupSchemaSync, policy, traverse, setCreatorFields, isOperatorOfType, relations as relations$1, traverseEntity, pagination } from "@strapi/utils";
2
+ import { pick, omit, difference, intersection, pipe, propOr, isEqual, isEmpty, set, has, prop, assoc, mapValues, flow, uniq, uniqBy, concat, isNil as isNil$1, getOr, propEq, merge, groupBy, castArray } from "lodash/fp";
3
3
  import "@strapi/types";
4
4
  import * as yup from "yup";
5
5
  import { scheduleJob } from "node-schedule";
@@ -54,7 +54,7 @@ const createHistoryVersionController = ({ strapi: strapi2 }) => {
54
54
  return ctx.forbidden();
55
55
  }
56
56
  const query = await permissionChecker2.sanitizeQuery(ctx.query);
57
- const { results, pagination } = await getService(strapi2, "history").findVersionsPage({
57
+ const { results, pagination: pagination2 } = await getService(strapi2, "history").findVersionsPage({
58
58
  query: {
59
59
  ...query,
60
60
  ...getValidPagination({ page: query.page, pageSize: query.pageSize })
@@ -73,7 +73,7 @@ const createHistoryVersionController = ({ strapi: strapi2 }) => {
73
73
  );
74
74
  return {
75
75
  data: sanitizedResults,
76
- meta: { pagination }
76
+ meta: { pagination: pagination2 }
77
77
  };
78
78
  },
79
79
  async restoreVersion(ctx) {
@@ -112,40 +112,65 @@ const FIELDS_TO_IGNORE = [
112
112
  "strapi_stage",
113
113
  "strapi_assignee"
114
114
  ];
115
- const getSchemaAttributesDiff = (versionSchemaAttributes, contentTypeSchemaAttributes) => {
116
- const sanitizedContentTypeSchemaAttributes = omit(FIELDS_TO_IGNORE, contentTypeSchemaAttributes);
117
- const reduceDifferenceToAttributesObject = (diffKeys, source) => {
118
- return diffKeys.reduce((previousAttributesObject, diffKey) => {
119
- previousAttributesObject[diffKey] = source[diffKey];
120
- return previousAttributesObject;
121
- }, {});
122
- };
123
- const versionSchemaKeys = Object.keys(versionSchemaAttributes);
124
- const contentTypeSchemaAttributesKeys = Object.keys(sanitizedContentTypeSchemaAttributes);
125
- const uniqueToContentType = difference(contentTypeSchemaAttributesKeys, versionSchemaKeys);
126
- const added = reduceDifferenceToAttributesObject(
127
- uniqueToContentType,
128
- sanitizedContentTypeSchemaAttributes
129
- );
130
- const uniqueToVersion = difference(versionSchemaKeys, contentTypeSchemaAttributesKeys);
131
- const removed = reduceDifferenceToAttributesObject(uniqueToVersion, versionSchemaAttributes);
132
- return { added, removed };
133
- };
134
115
  const DEFAULT_RETENTION_DAYS = 90;
135
- const createHistoryService = ({ strapi: strapi2 }) => {
136
- const state = {
137
- deleteExpiredJob: null,
138
- isInitialized: false
116
+ const createServiceUtils = ({ strapi: strapi2 }) => {
117
+ const getSchemaAttributesDiff = (versionSchemaAttributes, contentTypeSchemaAttributes) => {
118
+ const sanitizedContentTypeSchemaAttributes = omit(
119
+ FIELDS_TO_IGNORE,
120
+ contentTypeSchemaAttributes
121
+ );
122
+ const reduceDifferenceToAttributesObject = (diffKeys, source) => {
123
+ return diffKeys.reduce(
124
+ (previousAttributesObject, diffKey) => {
125
+ previousAttributesObject[diffKey] = source[diffKey];
126
+ return previousAttributesObject;
127
+ },
128
+ {}
129
+ );
130
+ };
131
+ const versionSchemaKeys = Object.keys(versionSchemaAttributes);
132
+ const contentTypeSchemaAttributesKeys = Object.keys(sanitizedContentTypeSchemaAttributes);
133
+ const uniqueToContentType = difference(contentTypeSchemaAttributesKeys, versionSchemaKeys);
134
+ const added = reduceDifferenceToAttributesObject(
135
+ uniqueToContentType,
136
+ sanitizedContentTypeSchemaAttributes
137
+ );
138
+ const uniqueToVersion = difference(versionSchemaKeys, contentTypeSchemaAttributesKeys);
139
+ const removed = reduceDifferenceToAttributesObject(uniqueToVersion, versionSchemaAttributes);
140
+ return { added, removed };
139
141
  };
140
- const query = strapi2.db.query(HISTORY_VERSION_UID);
141
- const getRetentionDays = (strapi22) => {
142
- const featureConfig = strapi22.ee.features.get("cms-content-history");
143
- const licenseRetentionDays = typeof featureConfig === "object" && featureConfig?.options.retentionDays;
144
- const userRetentionDays = strapi22.config.get("admin.history.retentionDays");
145
- if (userRetentionDays && userRetentionDays < licenseRetentionDays) {
146
- return userRetentionDays;
142
+ const getRelationRestoreValue = async (versionRelationData, attribute) => {
143
+ if (Array.isArray(versionRelationData)) {
144
+ if (versionRelationData.length === 0)
145
+ return versionRelationData;
146
+ const existingAndMissingRelations = await Promise.all(
147
+ versionRelationData.map((relation) => {
148
+ return strapi2.documents(attribute.target).findOne({
149
+ documentId: relation.documentId,
150
+ locale: relation.locale || void 0
151
+ });
152
+ })
153
+ );
154
+ return existingAndMissingRelations.filter(
155
+ (relation) => relation !== null
156
+ );
147
157
  }
148
- return Math.min(licenseRetentionDays, DEFAULT_RETENTION_DAYS);
158
+ return strapi2.documents(attribute.target).findOne({
159
+ documentId: versionRelationData.documentId,
160
+ locale: versionRelationData.locale || void 0
161
+ });
162
+ };
163
+ const getMediaRestoreValue = async (versionRelationData, attribute) => {
164
+ if (attribute.multiple) {
165
+ const existingAndMissingMedias = await Promise.all(
166
+ // @ts-expect-error Fix the type definitions so this isn't any
167
+ versionRelationData.map((media) => {
168
+ return strapi2.db.query("plugin::upload.file").findOne({ where: { id: media.id } });
169
+ })
170
+ );
171
+ return existingAndMissingMedias.filter((media) => media != null);
172
+ }
173
+ return strapi2.db.query("plugin::upload.file").findOne({ where: { id: versionRelationData.id } });
149
174
  };
150
175
  const localesService = strapi2.plugin("i18n")?.service("locales");
151
176
  const getDefaultLocale = async () => localesService ? localesService.getDefaultLocale() : null;
@@ -161,6 +186,15 @@ const createHistoryService = ({ strapi: strapi2 }) => {
161
186
  {}
162
187
  );
163
188
  };
189
+ const getRetentionDays = () => {
190
+ const featureConfig = strapi2.ee.features.get("cms-content-history");
191
+ const licenseRetentionDays = typeof featureConfig === "object" && featureConfig?.options.retentionDays;
192
+ const userRetentionDays = strapi2.config.get("admin.history.retentionDays");
193
+ if (userRetentionDays && userRetentionDays < licenseRetentionDays) {
194
+ return userRetentionDays;
195
+ }
196
+ return Math.min(licenseRetentionDays, DEFAULT_RETENTION_DAYS);
197
+ };
164
198
  const getVersionStatus = async (contentTypeUid, document) => {
165
199
  const documentMetadataService = strapi2.plugin("content-manager").service("document-metadata");
166
200
  const meta = await documentMetadataService.getMetadata(contentTypeUid, document);
@@ -172,6 +206,10 @@ const createHistoryService = ({ strapi: strapi2 }) => {
172
206
  return attributes.reduce((acc, [attributeName, attribute]) => {
173
207
  switch (attribute.type) {
174
208
  case "relation": {
209
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
210
+ if (isMorphRelation) {
211
+ break;
212
+ }
175
213
  const isVisible2 = contentTypes$1.isVisibleAttribute(model, attributeName);
176
214
  if (isVisible2) {
177
215
  acc[attributeName] = { fields: ["documentId", "locale", "publishedAt"] };
@@ -202,80 +240,68 @@ const createHistoryService = ({ strapi: strapi2 }) => {
202
240
  return acc;
203
241
  }, {});
204
242
  };
205
- return {
206
- async bootstrap() {
207
- if (state.isInitialized) {
208
- return;
209
- }
210
- strapi2.documents.use(async (context, next) => {
211
- if (!strapi2.requestContext.get()?.request.url.startsWith("/content-manager")) {
212
- return next();
243
+ const buildMediaResponse = async (values) => {
244
+ return values.slice(0, 25).reduce(
245
+ async (currentRelationDataPromise, entry) => {
246
+ const currentRelationData = await currentRelationDataPromise;
247
+ if (!entry) {
248
+ return currentRelationData;
213
249
  }
214
- if (context.action !== "create" && context.action !== "update" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
215
- return next();
250
+ const relatedEntry = await strapi2.db.query("plugin::upload.file").findOne({ where: { id: entry.id } });
251
+ if (relatedEntry) {
252
+ currentRelationData.results.push(relatedEntry);
253
+ } else {
254
+ currentRelationData.meta.missingCount += 1;
216
255
  }
217
- const contentTypeUid = context.contentType.uid;
218
- if (!contentTypeUid.startsWith("api::")) {
219
- return next();
256
+ return currentRelationData;
257
+ },
258
+ Promise.resolve({
259
+ results: [],
260
+ meta: { missingCount: 0 }
261
+ })
262
+ );
263
+ };
264
+ const buildRelationReponse = async (values, attributeSchema) => {
265
+ return values.slice(0, 25).reduce(
266
+ async (currentRelationDataPromise, entry) => {
267
+ const currentRelationData = await currentRelationDataPromise;
268
+ if (!entry) {
269
+ return currentRelationData;
220
270
  }
221
- const result = await next();
222
- const documentContext = context.action === "create" ? { documentId: result.documentId, locale: context.params?.locale } : { documentId: context.params.documentId, locale: context.params?.locale };
223
- const defaultLocale = await getDefaultLocale();
224
- const locale = documentContext.locale || defaultLocale;
225
- const document = await strapi2.documents(contentTypeUid).findOne({
226
- documentId: documentContext.documentId,
227
- locale,
228
- populate: getDeepPopulate2(contentTypeUid)
229
- });
230
- const status = await getVersionStatus(contentTypeUid, document);
231
- const attributesSchema = strapi2.getModel(contentTypeUid).attributes;
232
- const componentsSchemas = Object.keys(
233
- attributesSchema
234
- ).reduce((currentComponentSchemas, key) => {
235
- const fieldSchema = attributesSchema[key];
236
- if (fieldSchema.type === "component") {
237
- const componentSchema = strapi2.getModel(fieldSchema.component).attributes;
238
- return {
239
- ...currentComponentSchemas,
240
- [fieldSchema.component]: componentSchema
241
- };
242
- }
243
- return currentComponentSchemas;
244
- }, {});
245
- await strapi2.db.transaction(async ({ onCommit }) => {
246
- onCommit(() => {
247
- this.createVersion({
248
- contentType: contentTypeUid,
249
- data: omit(FIELDS_TO_IGNORE, document),
250
- schema: omit(FIELDS_TO_IGNORE, attributesSchema),
251
- componentsSchemas,
252
- relatedDocumentId: documentContext.documentId,
253
- locale,
254
- status
255
- });
271
+ const relatedEntry = await strapi2.documents(attributeSchema.target).findOne({ documentId: entry.documentId, locale: entry.locale || void 0 });
272
+ if (relatedEntry) {
273
+ currentRelationData.results.push({
274
+ ...relatedEntry,
275
+ status: await getVersionStatus(attributeSchema.target, relatedEntry)
256
276
  });
257
- });
258
- return result;
259
- });
260
- const retentionDays = getRetentionDays(strapi2);
261
- state.deleteExpiredJob = scheduleJob("0 0 * * *", () => {
262
- const retentionDaysInMilliseconds = retentionDays * 24 * 60 * 60 * 1e3;
263
- const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
264
- query.deleteMany({
265
- where: {
266
- created_at: {
267
- $lt: expirationDate.toISOString()
268
- }
269
- }
270
- });
271
- });
272
- state.isInitialized = true;
273
- },
274
- async destroy() {
275
- if (state.deleteExpiredJob) {
276
- state.deleteExpiredJob.cancel();
277
- }
278
- },
277
+ } else {
278
+ currentRelationData.meta.missingCount += 1;
279
+ }
280
+ return currentRelationData;
281
+ },
282
+ Promise.resolve({
283
+ results: [],
284
+ meta: { missingCount: 0 }
285
+ })
286
+ );
287
+ };
288
+ return {
289
+ getSchemaAttributesDiff,
290
+ getRelationRestoreValue,
291
+ getMediaRestoreValue,
292
+ getDefaultLocale,
293
+ getLocaleDictionary,
294
+ getRetentionDays,
295
+ getVersionStatus,
296
+ getDeepPopulate: getDeepPopulate2,
297
+ buildMediaResponse,
298
+ buildRelationReponse
299
+ };
300
+ };
301
+ const createHistoryService = ({ strapi: strapi2 }) => {
302
+ const query = strapi2.db.query(HISTORY_VERSION_UID);
303
+ const serviceUtils = createServiceUtils({ strapi: strapi2 });
304
+ return {
279
305
  async createVersion(historyVersionData) {
280
306
  await query.create({
281
307
  data: {
@@ -286,8 +312,8 @@ const createHistoryService = ({ strapi: strapi2 }) => {
286
312
  });
287
313
  },
288
314
  async findVersionsPage(params) {
289
- const locale = params.query.locale || await getDefaultLocale();
290
- const [{ results, pagination }, localeDictionary] = await Promise.all([
315
+ const locale = params.query.locale || await serviceUtils.getDefaultLocale();
316
+ const [{ results, pagination: pagination2 }, localeDictionary] = await Promise.all([
291
317
  query.findPage({
292
318
  ...params.query,
293
319
  where: {
@@ -300,78 +326,34 @@ const createHistoryService = ({ strapi: strapi2 }) => {
300
326
  populate: ["createdBy"],
301
327
  orderBy: [{ createdAt: "desc" }]
302
328
  }),
303
- getLocaleDictionary()
329
+ serviceUtils.getLocaleDictionary()
304
330
  ]);
305
- const buildRelationReponse = async (values, attributeSchema) => {
306
- return values.slice(0, 25).reduce(
307
- async (currentRelationDataPromise, entry) => {
308
- const currentRelationData = await currentRelationDataPromise;
309
- if (!entry) {
310
- return currentRelationData;
311
- }
312
- const relatedEntry = await strapi2.documents(attributeSchema.target).findOne({ documentId: entry.documentId, locale: entry.locale || void 0 });
313
- const permissionChecker2 = getService$1("permission-checker").create({
314
- userAbility: params.state.userAbility,
315
- model: attributeSchema.target
316
- });
317
- const sanitizedEntry = await permissionChecker2.sanitizeOutput(relatedEntry);
318
- if (sanitizedEntry) {
319
- currentRelationData.results.push({
320
- ...sanitizedEntry,
321
- status: await getVersionStatus(attributeSchema.target, sanitizedEntry)
322
- });
323
- } else {
324
- currentRelationData.meta.missingCount += 1;
325
- }
326
- return currentRelationData;
327
- },
328
- Promise.resolve({
329
- results: [],
330
- meta: { missingCount: 0 }
331
- })
332
- );
333
- };
334
- const buildMediaResponse = async (values) => {
335
- return values.slice(0, 25).reduce(
336
- async (currentRelationDataPromise, entry) => {
337
- const currentRelationData = await currentRelationDataPromise;
338
- if (!entry) {
339
- return currentRelationData;
340
- }
341
- const permissionChecker2 = getService$1("permission-checker").create({
342
- userAbility: params.state.userAbility,
343
- model: "plugin::upload.file"
344
- });
345
- const relatedEntry = await strapi2.db.query("plugin::upload.file").findOne({ where: { id: entry.id } });
346
- const sanitizedEntry = await permissionChecker2.sanitizeOutput(relatedEntry);
347
- if (sanitizedEntry) {
348
- currentRelationData.results.push(sanitizedEntry);
349
- } else {
350
- currentRelationData.meta.missingCount += 1;
351
- }
352
- return currentRelationData;
353
- },
354
- Promise.resolve({
355
- results: [],
356
- meta: { missingCount: 0 }
357
- })
358
- );
359
- };
360
331
  const populateEntryRelations = async (entry) => {
361
332
  const entryWithRelations = await Object.entries(entry.schema).reduce(
362
333
  async (currentDataWithRelations, [attributeKey, attributeSchema]) => {
363
334
  const attributeValue = entry.data[attributeKey];
364
335
  const attributeValues = Array.isArray(attributeValue) ? attributeValue : [attributeValue];
365
336
  if (attributeSchema.type === "media") {
337
+ const permissionChecker2 = getService$1("permission-checker").create({
338
+ userAbility: params.state.userAbility,
339
+ model: "plugin::upload.file"
340
+ });
341
+ const response = await serviceUtils.buildMediaResponse(attributeValues);
342
+ const sanitizedResults = await Promise.all(
343
+ response.results.map((media) => permissionChecker2.sanitizeOutput(media))
344
+ );
366
345
  return {
367
346
  ...await currentDataWithRelations,
368
- [attributeKey]: await buildMediaResponse(attributeValues)
347
+ [attributeKey]: {
348
+ results: sanitizedResults,
349
+ meta: response.meta
350
+ }
369
351
  };
370
352
  }
371
353
  if (attributeSchema.type === "relation" && attributeSchema.relation !== "morphToOne" && attributeSchema.relation !== "morphToMany") {
372
354
  if (attributeSchema.target === "admin::user") {
373
355
  const adminUsers = await Promise.all(
374
- attributeValues.map(async (userToPopulate) => {
356
+ attributeValues.map((userToPopulate) => {
375
357
  if (userToPopulate == null) {
376
358
  return null;
377
359
  }
@@ -388,9 +370,23 @@ const createHistoryService = ({ strapi: strapi2 }) => {
388
370
  [attributeKey]: adminUsers
389
371
  };
390
372
  }
373
+ const permissionChecker2 = getService$1("permission-checker").create({
374
+ userAbility: params.state.userAbility,
375
+ model: attributeSchema.target
376
+ });
377
+ const response = await serviceUtils.buildRelationReponse(
378
+ attributeValues,
379
+ attributeSchema
380
+ );
381
+ const sanitizedResults = await Promise.all(
382
+ response.results.map((media) => permissionChecker2.sanitizeOutput(media))
383
+ );
391
384
  return {
392
385
  ...await currentDataWithRelations,
393
- [attributeKey]: await buildRelationReponse(attributeValues, attributeSchema)
386
+ [attributeKey]: {
387
+ results: sanitizedResults,
388
+ meta: response.meta
389
+ }
394
390
  };
395
391
  }
396
392
  return currentDataWithRelations;
@@ -405,7 +401,7 @@ const createHistoryService = ({ strapi: strapi2 }) => {
405
401
  ...result,
406
402
  data: await populateEntryRelations(result),
407
403
  meta: {
408
- unknownAttributes: getSchemaAttributesDiff(
404
+ unknownAttributes: serviceUtils.getSchemaAttributesDiff(
409
405
  result.schema,
410
406
  strapi2.getModel(params.query.contentType).attributes
411
407
  )
@@ -416,13 +412,16 @@ const createHistoryService = ({ strapi: strapi2 }) => {
416
412
  );
417
413
  return {
418
414
  results: formattedResults,
419
- pagination
415
+ pagination: pagination2
420
416
  };
421
417
  },
422
418
  async restoreVersion(versionId) {
423
419
  const version = await query.findOne({ where: { id: versionId } });
424
420
  const contentTypeSchemaAttributes = strapi2.getModel(version.contentType).attributes;
425
- const schemaDiff = getSchemaAttributesDiff(version.schema, contentTypeSchemaAttributes);
421
+ const schemaDiff = serviceUtils.getSchemaAttributesDiff(
422
+ version.schema,
423
+ contentTypeSchemaAttributes
424
+ );
426
425
  const dataWithoutAddedAttributes = Object.keys(schemaDiff.added).reduce(
427
426
  (currentData, addedKey) => {
428
427
  currentData[addedKey] = null;
@@ -435,61 +434,26 @@ const createHistoryService = ({ strapi: strapi2 }) => {
435
434
  FIELDS_TO_IGNORE,
436
435
  contentTypeSchemaAttributes
437
436
  );
438
- const dataWithoutMissingRelations = await Object.entries(sanitizedSchemaAttributes).reduce(
439
- async (previousRelationAttributesPromise, [name, attribute]) => {
440
- const previousRelationAttributes = await previousRelationAttributesPromise;
441
- const relationData = version.data[name];
442
- if (relationData === null) {
437
+ const reducer = async.reduce(Object.entries(sanitizedSchemaAttributes));
438
+ const dataWithoutMissingRelations = await reducer(
439
+ async (previousRelationAttributes, [name, attribute]) => {
440
+ const versionRelationData = version.data[name];
441
+ if (!versionRelationData) {
443
442
  return previousRelationAttributes;
444
443
  }
445
444
  if (attribute.type === "relation" && // TODO: handle polymorphic relations
446
445
  attribute.relation !== "morphToOne" && attribute.relation !== "morphToMany") {
447
- if (Array.isArray(relationData)) {
448
- if (relationData.length === 0)
449
- return previousRelationAttributes;
450
- const existingAndMissingRelations = await Promise.all(
451
- relationData.map((relation) => {
452
- return strapi2.documents(attribute.target).findOne({
453
- documentId: relation.documentId,
454
- locale: relation.locale || void 0
455
- });
456
- })
457
- );
458
- const existingRelations = existingAndMissingRelations.filter(
459
- (relation) => relation !== null
460
- );
461
- previousRelationAttributes[name] = existingRelations;
462
- } else {
463
- const existingRelation = await strapi2.documents(attribute.target).findOne({
464
- documentId: relationData.documentId,
465
- locale: relationData.locale || void 0
466
- });
467
- if (!existingRelation) {
468
- previousRelationAttributes[name] = null;
469
- }
470
- }
446
+ const data2 = await serviceUtils.getRelationRestoreValue(versionRelationData, attribute);
447
+ previousRelationAttributes[name] = data2;
471
448
  }
472
449
  if (attribute.type === "media") {
473
- if (attribute.multiple) {
474
- const existingAndMissingMedias = await Promise.all(
475
- // @ts-expect-error Fix the type definitions so this isn't any
476
- relationData.map((media) => {
477
- return strapi2.db.query("plugin::upload.file").findOne({ where: { id: media.id } });
478
- })
479
- );
480
- const existingMedias = existingAndMissingMedias.filter((media) => media != null);
481
- previousRelationAttributes[name] = existingMedias;
482
- } else {
483
- const existingMedia = await strapi2.db.query("plugin::upload.file").findOne({ where: { id: version.data[name].id } });
484
- if (!existingMedia) {
485
- previousRelationAttributes[name] = null;
486
- }
487
- }
450
+ const data2 = await serviceUtils.getMediaRestoreValue(versionRelationData, attribute);
451
+ previousRelationAttributes[name] = data2;
488
452
  }
489
453
  return previousRelationAttributes;
490
454
  },
491
455
  // Clone to avoid mutating the original version data
492
- Promise.resolve(structuredClone(dataWithoutAddedAttributes))
456
+ structuredClone(dataWithoutAddedAttributes)
493
457
  );
494
458
  const data = omit(["id", ...Object.keys(schemaDiff.removed)], dataWithoutMissingRelations);
495
459
  const restoredDocument = await strapi2.documents(version.contentType).update({
@@ -504,8 +468,102 @@ const createHistoryService = ({ strapi: strapi2 }) => {
504
468
  }
505
469
  };
506
470
  };
471
+ const createLifecyclesService = ({ strapi: strapi2 }) => {
472
+ const state = {
473
+ deleteExpiredJob: null,
474
+ isInitialized: false
475
+ };
476
+ const serviceUtils = createServiceUtils({ strapi: strapi2 });
477
+ return {
478
+ async bootstrap() {
479
+ if (state.isInitialized) {
480
+ return;
481
+ }
482
+ strapi2.documents.use(async (context, next) => {
483
+ if (!strapi2.requestContext.get()?.request.url.startsWith("/content-manager")) {
484
+ return next();
485
+ }
486
+ if (context.action !== "create" && context.action !== "update" && context.action !== "clone" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
487
+ return next();
488
+ }
489
+ if (context.action === "update" && strapi2.requestContext.get()?.request.url.endsWith("/actions/publish")) {
490
+ return next();
491
+ }
492
+ const contentTypeUid = context.contentType.uid;
493
+ if (!contentTypeUid.startsWith("api::")) {
494
+ return next();
495
+ }
496
+ const result = await next();
497
+ const documentContext = {
498
+ documentId: context.action === "create" || context.action === "clone" ? result.documentId : context.params.documentId,
499
+ locale: context.params?.locale
500
+ };
501
+ const defaultLocale = await serviceUtils.getDefaultLocale();
502
+ const locale = documentContext.locale || defaultLocale;
503
+ if (Array.isArray(locale)) {
504
+ strapi2.log.warn(
505
+ "[Content manager history middleware]: An array of locales was provided, but only a single locale is supported for the findOne operation."
506
+ );
507
+ return next();
508
+ }
509
+ const document = await strapi2.documents(contentTypeUid).findOne({
510
+ documentId: documentContext.documentId,
511
+ locale,
512
+ populate: serviceUtils.getDeepPopulate(contentTypeUid)
513
+ });
514
+ const status = await serviceUtils.getVersionStatus(contentTypeUid, document);
515
+ const attributesSchema = strapi2.getModel(contentTypeUid).attributes;
516
+ const componentsSchemas = Object.keys(
517
+ attributesSchema
518
+ ).reduce((currentComponentSchemas, key) => {
519
+ const fieldSchema = attributesSchema[key];
520
+ if (fieldSchema.type === "component") {
521
+ const componentSchema = strapi2.getModel(fieldSchema.component).attributes;
522
+ return {
523
+ ...currentComponentSchemas,
524
+ [fieldSchema.component]: componentSchema
525
+ };
526
+ }
527
+ return currentComponentSchemas;
528
+ }, {});
529
+ await strapi2.db.transaction(async ({ onCommit }) => {
530
+ onCommit(() => {
531
+ getService(strapi2, "history").createVersion({
532
+ contentType: contentTypeUid,
533
+ data: omit(FIELDS_TO_IGNORE, document),
534
+ schema: omit(FIELDS_TO_IGNORE, attributesSchema),
535
+ componentsSchemas,
536
+ relatedDocumentId: documentContext.documentId,
537
+ locale,
538
+ status
539
+ });
540
+ });
541
+ });
542
+ return result;
543
+ });
544
+ state.deleteExpiredJob = scheduleJob("0 0 * * *", () => {
545
+ const retentionDaysInMilliseconds = serviceUtils.getRetentionDays() * 24 * 60 * 60 * 1e3;
546
+ const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
547
+ strapi2.db.query(HISTORY_VERSION_UID).deleteMany({
548
+ where: {
549
+ created_at: {
550
+ $lt: expirationDate.toISOString()
551
+ }
552
+ }
553
+ });
554
+ });
555
+ state.isInitialized = true;
556
+ },
557
+ async destroy() {
558
+ if (state.deleteExpiredJob) {
559
+ state.deleteExpiredJob.cancel();
560
+ }
561
+ }
562
+ };
563
+ };
507
564
  const services$1 = {
508
- history: createHistoryService
565
+ history: createHistoryService,
566
+ lifecycles: createLifecyclesService
509
567
  };
510
568
  const info = { pluginName: "content-manager", type: "admin" };
511
569
  const historyVersionRouter = {
@@ -585,10 +643,10 @@ const getFeature = () => {
585
643
  strapi2.get("models").add(historyVersion);
586
644
  },
587
645
  bootstrap({ strapi: strapi2 }) {
588
- getService(strapi2, "history").bootstrap();
646
+ getService(strapi2, "lifecycles").bootstrap();
589
647
  },
590
648
  destroy({ strapi: strapi2 }) {
591
- getService(strapi2, "history").destroy();
649
+ getService(strapi2, "lifecycles").destroy();
592
650
  },
593
651
  controllers: controllers$1,
594
652
  services: services$1,
@@ -1407,7 +1465,7 @@ const { PaginationError, ValidationError } = errors;
1407
1465
  const TYPES = ["singleType", "collectionType"];
1408
1466
  const kindSchema = yup$1.string().oneOf(TYPES).nullable();
1409
1467
  const bulkActionInputSchema = yup$1.object({
1410
- ids: yup$1.array().of(yup$1.strapiID()).min(1).required()
1468
+ documentIds: yup$1.array().of(yup$1.strapiID()).min(1).required()
1411
1469
  }).required();
1412
1470
  const generateUIDInputSchema = yup$1.object({
1413
1471
  contentTypeUID: yup$1.string().required(),
@@ -1506,15 +1564,47 @@ const excludeNotCreatableFields = (uid2, permissionChecker2) => (body, path = []
1506
1564
  }
1507
1565
  }, body);
1508
1566
  };
1509
- const getDocumentLocaleAndStatus = (request) => {
1567
+ const singleLocaleSchema = yup$1.string().nullable();
1568
+ const multipleLocaleSchema = yup$1.lazy(
1569
+ (value) => Array.isArray(value) ? yup$1.array().of(singleLocaleSchema.required()) : singleLocaleSchema
1570
+ );
1571
+ const statusSchema = yup$1.mixed().oneOf(["draft", "published"], "Invalid status");
1572
+ const getDocumentLocaleAndStatus = async (request, opts = { allowMultipleLocales: false }) => {
1573
+ const { allowMultipleLocales } = opts;
1510
1574
  const { locale, status, ...rest } = request || {};
1511
- if (!isNil$1(locale) && typeof locale !== "string") {
1512
- throw new errors.ValidationError(`Invalid locale: ${locale}`);
1513
- }
1514
- if (!isNil$1(status) && !["draft", "published"].includes(status)) {
1515
- throw new errors.ValidationError(`Invalid status: ${status}`);
1575
+ const schema = yup$1.object().shape({
1576
+ locale: allowMultipleLocales ? multipleLocaleSchema : singleLocaleSchema,
1577
+ status: statusSchema
1578
+ });
1579
+ try {
1580
+ await validateYupSchema(schema, { strict: true, abortEarly: false })(request);
1581
+ return { locale, status, ...rest };
1582
+ } catch (error) {
1583
+ throw new errors.ValidationError(`Validation error: ${error.message}`);
1516
1584
  }
1517
- return { locale, status, ...rest };
1585
+ };
1586
+ const formatDocumentWithMetadata = async (permissionChecker2, uid2, document, opts = {}) => {
1587
+ const documentMetadata2 = getService$1("document-metadata");
1588
+ const serviceOutput = await documentMetadata2.formatDocumentWithMetadata(uid2, document, opts);
1589
+ let {
1590
+ meta: { availableLocales, availableStatus }
1591
+ } = serviceOutput;
1592
+ const metadataSanitizer = permissionChecker2.sanitizeOutput;
1593
+ availableLocales = await async.map(
1594
+ availableLocales,
1595
+ async (localeDocument) => metadataSanitizer(localeDocument)
1596
+ );
1597
+ availableStatus = await async.map(
1598
+ availableStatus,
1599
+ async (statusDocument) => metadataSanitizer(statusDocument)
1600
+ );
1601
+ return {
1602
+ ...serviceOutput,
1603
+ meta: {
1604
+ availableLocales,
1605
+ availableStatus
1606
+ }
1607
+ };
1518
1608
  };
1519
1609
  const createDocument = async (ctx, opts) => {
1520
1610
  const { userAbility, user } = ctx.state;
@@ -1529,7 +1619,7 @@ const createDocument = async (ctx, opts) => {
1529
1619
  const setCreator = setCreatorFields({ user });
1530
1620
  const sanitizeFn = async.pipe(pickPermittedFields, setCreator);
1531
1621
  const sanitizedBody = await sanitizeFn(body);
1532
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(body);
1622
+ const { locale, status = "draft" } = await getDocumentLocaleAndStatus(body);
1533
1623
  return documentManager2.create(model, {
1534
1624
  data: sanitizedBody,
1535
1625
  locale,
@@ -1548,7 +1638,7 @@ const updateDocument = async (ctx, opts) => {
1548
1638
  }
1549
1639
  const permissionQuery = await permissionChecker2.sanitizedQuery.update(ctx.query);
1550
1640
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1551
- const { locale } = getDocumentLocaleAndStatus(body);
1641
+ const { locale } = await getDocumentLocaleAndStatus(body);
1552
1642
  const [documentVersion, documentExists] = await Promise.all([
1553
1643
  documentManager2.findOne(id, model, { populate, locale, status: "draft" }),
1554
1644
  documentManager2.exists(model, id)
@@ -1586,8 +1676,8 @@ const collectionTypes = {
1586
1676
  }
1587
1677
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
1588
1678
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(1).countRelations({ toOne: false, toMany: true }).build();
1589
- const { locale, status } = getDocumentLocaleAndStatus(query);
1590
- const { results: documents, pagination } = await documentManager2.findPage(
1679
+ const { locale, status } = await getDocumentLocaleAndStatus(query);
1680
+ const { results: documents, pagination: pagination2 } = await documentManager2.findPage(
1591
1681
  { ...permissionQuery, populate, locale, status },
1592
1682
  model
1593
1683
  );
@@ -1608,21 +1698,20 @@ const collectionTypes = {
1608
1698
  );
1609
1699
  ctx.body = {
1610
1700
  results,
1611
- pagination
1701
+ pagination: pagination2
1612
1702
  };
1613
1703
  },
1614
1704
  async findOne(ctx) {
1615
1705
  const { userAbility } = ctx.state;
1616
1706
  const { model, id } = ctx.params;
1617
1707
  const documentManager2 = getService$1("document-manager");
1618
- const documentMetadata2 = getService$1("document-metadata");
1619
1708
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1620
1709
  if (permissionChecker2.cannot.read()) {
1621
1710
  return ctx.forbidden();
1622
1711
  }
1623
1712
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1624
1713
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1625
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
1714
+ const { locale, status = "draft" } = await getDocumentLocaleAndStatus(ctx.query);
1626
1715
  const version = await documentManager2.findOne(id, model, {
1627
1716
  populate,
1628
1717
  locale,
@@ -1633,8 +1722,10 @@ const collectionTypes = {
1633
1722
  if (!exists) {
1634
1723
  return ctx.notFound();
1635
1724
  }
1636
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
1725
+ const { meta } = await formatDocumentWithMetadata(
1726
+ permissionChecker2,
1637
1727
  model,
1728
+ // @ts-expect-error TODO: fix
1638
1729
  { id, locale, publishedAt: null },
1639
1730
  { availableLocales: true, availableStatus: false }
1640
1731
  );
@@ -1645,12 +1736,11 @@ const collectionTypes = {
1645
1736
  return ctx.forbidden();
1646
1737
  }
1647
1738
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
1648
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1739
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1649
1740
  },
1650
1741
  async create(ctx) {
1651
1742
  const { userAbility } = ctx.state;
1652
1743
  const { model } = ctx.params;
1653
- const documentMetadata2 = getService$1("document-metadata");
1654
1744
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1655
1745
  const [totalEntries, document] = await Promise.all([
1656
1746
  strapi.db.query(model).count(),
@@ -1658,7 +1748,7 @@ const collectionTypes = {
1658
1748
  ]);
1659
1749
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
1660
1750
  ctx.status = 201;
1661
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1751
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1662
1752
  // Empty metadata as it's not relevant for a new document
1663
1753
  availableLocales: false,
1664
1754
  availableStatus: false
@@ -1672,25 +1762,23 @@ const collectionTypes = {
1672
1762
  async update(ctx) {
1673
1763
  const { userAbility } = ctx.state;
1674
1764
  const { model } = ctx.params;
1675
- const documentMetadata2 = getService$1("document-metadata");
1676
1765
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1677
1766
  const updatedVersion = await updateDocument(ctx);
1678
1767
  const sanitizedVersion = await permissionChecker2.sanitizeOutput(updatedVersion);
1679
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedVersion);
1768
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedVersion);
1680
1769
  },
1681
1770
  async clone(ctx) {
1682
1771
  const { userAbility, user } = ctx.state;
1683
1772
  const { model, sourceId: id } = ctx.params;
1684
1773
  const { body } = ctx.request;
1685
1774
  const documentManager2 = getService$1("document-manager");
1686
- const documentMetadata2 = getService$1("document-metadata");
1687
1775
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1688
1776
  if (permissionChecker2.cannot.create()) {
1689
1777
  return ctx.forbidden();
1690
1778
  }
1691
1779
  const permissionQuery = await permissionChecker2.sanitizedQuery.create(ctx.query);
1692
1780
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1693
- const { locale } = getDocumentLocaleAndStatus(body);
1781
+ const { locale } = await getDocumentLocaleAndStatus(body);
1694
1782
  const document = await documentManager2.findOne(id, model, {
1695
1783
  populate,
1696
1784
  locale,
@@ -1706,7 +1794,7 @@ const collectionTypes = {
1706
1794
  const sanitizedBody = await sanitizeFn(body);
1707
1795
  const clonedDocument = await documentManager2.clone(document.documentId, sanitizedBody, model);
1708
1796
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(clonedDocument);
1709
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1797
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1710
1798
  // Empty metadata as it's not relevant for a new document
1711
1799
  availableLocales: false,
1712
1800
  availableStatus: false
@@ -1735,7 +1823,7 @@ const collectionTypes = {
1735
1823
  }
1736
1824
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(ctx.query);
1737
1825
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1738
- const { locale } = getDocumentLocaleAndStatus(ctx.query);
1826
+ const { locale } = await getDocumentLocaleAndStatus(ctx.query);
1739
1827
  const documentLocales = await documentManager2.findLocales(id, model, { populate, locale });
1740
1828
  if (documentLocales.length === 0) {
1741
1829
  return ctx.notFound();
@@ -1757,7 +1845,6 @@ const collectionTypes = {
1757
1845
  const { id, model } = ctx.params;
1758
1846
  const { body } = ctx.request;
1759
1847
  const documentManager2 = getService$1("document-manager");
1760
- const documentMetadata2 = getService$1("document-metadata");
1761
1848
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1762
1849
  if (permissionChecker2.cannot.publish()) {
1763
1850
  return ctx.forbidden();
@@ -1769,21 +1856,25 @@ const collectionTypes = {
1769
1856
  if (permissionChecker2.cannot.publish(document)) {
1770
1857
  throw new errors.ForbiddenError();
1771
1858
  }
1772
- const { locale } = getDocumentLocaleAndStatus(body);
1773
- return documentManager2.publish(document.documentId, model, {
1859
+ const { locale } = await getDocumentLocaleAndStatus(body);
1860
+ const publishResult = await documentManager2.publish(document.documentId, model, {
1774
1861
  locale
1775
1862
  // TODO: Allow setting creator fields on publish
1776
1863
  // data: setCreatorFields({ user, isEdition: true })({}),
1777
1864
  });
1865
+ if (!publishResult || publishResult.length === 0) {
1866
+ throw new errors.NotFoundError("Document not found or already published.");
1867
+ }
1868
+ return publishResult[0];
1778
1869
  });
1779
1870
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
1780
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1871
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1781
1872
  },
1782
1873
  async bulkPublish(ctx) {
1783
1874
  const { userAbility } = ctx.state;
1784
1875
  const { model } = ctx.params;
1785
1876
  const { body } = ctx.request;
1786
- const { ids } = body;
1877
+ const { documentIds } = body;
1787
1878
  await validateBulkActionInput(body);
1788
1879
  const documentManager2 = getService$1("document-manager");
1789
1880
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1792,8 +1883,11 @@ const collectionTypes = {
1792
1883
  }
1793
1884
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1794
1885
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1795
- const entityPromises = ids.map((id) => documentManager2.findOne(id, model, { populate }));
1796
- const entities = await Promise.all(entityPromises);
1886
+ const { locale } = await getDocumentLocaleAndStatus(body, { allowMultipleLocales: true });
1887
+ const entityPromises = documentIds.map(
1888
+ (documentId) => documentManager2.findLocales(documentId, model, { populate, locale, isPublished: false })
1889
+ );
1890
+ const entities = (await Promise.all(entityPromises)).flat();
1797
1891
  for (const entity of entities) {
1798
1892
  if (!entity) {
1799
1893
  return ctx.notFound();
@@ -1802,24 +1896,25 @@ const collectionTypes = {
1802
1896
  return ctx.forbidden();
1803
1897
  }
1804
1898
  }
1805
- const { count } = await documentManager2.publishMany(entities, model);
1899
+ const count = await documentManager2.publishMany(model, documentIds, locale);
1806
1900
  ctx.body = { count };
1807
1901
  },
1808
1902
  async bulkUnpublish(ctx) {
1809
1903
  const { userAbility } = ctx.state;
1810
1904
  const { model } = ctx.params;
1811
1905
  const { body } = ctx.request;
1812
- const { ids } = body;
1906
+ const { documentIds } = body;
1813
1907
  await validateBulkActionInput(body);
1814
1908
  const documentManager2 = getService$1("document-manager");
1815
1909
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1816
1910
  if (permissionChecker2.cannot.unpublish()) {
1817
1911
  return ctx.forbidden();
1818
1912
  }
1819
- const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1820
- const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1821
- const entityPromises = ids.map((id) => documentManager2.findOne(id, model, { populate }));
1822
- const entities = await Promise.all(entityPromises);
1913
+ const { locale } = await getDocumentLocaleAndStatus(body);
1914
+ const entityPromises = documentIds.map(
1915
+ (documentId) => documentManager2.findLocales(documentId, model, { locale, isPublished: true })
1916
+ );
1917
+ const entities = (await Promise.all(entityPromises)).flat();
1823
1918
  for (const entity of entities) {
1824
1919
  if (!entity) {
1825
1920
  return ctx.notFound();
@@ -1828,7 +1923,8 @@ const collectionTypes = {
1828
1923
  return ctx.forbidden();
1829
1924
  }
1830
1925
  }
1831
- const { count } = await documentManager2.unpublishMany(entities, model);
1926
+ const entitiesIds = entities.map((document) => document.documentId);
1927
+ const { count } = await documentManager2.unpublishMany(entitiesIds, model, { locale });
1832
1928
  ctx.body = { count };
1833
1929
  },
1834
1930
  async unpublish(ctx) {
@@ -1838,7 +1934,6 @@ const collectionTypes = {
1838
1934
  body: { discardDraft, ...body }
1839
1935
  } = ctx.request;
1840
1936
  const documentManager2 = getService$1("document-manager");
1841
- const documentMetadata2 = getService$1("document-metadata");
1842
1937
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1843
1938
  if (permissionChecker2.cannot.unpublish()) {
1844
1939
  return ctx.forbidden();
@@ -1848,7 +1943,7 @@ const collectionTypes = {
1848
1943
  }
1849
1944
  const permissionQuery = await permissionChecker2.sanitizedQuery.unpublish(ctx.query);
1850
1945
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1851
- const { locale } = getDocumentLocaleAndStatus(body);
1946
+ const { locale } = await getDocumentLocaleAndStatus(body);
1852
1947
  const document = await documentManager2.findOne(id, model, {
1853
1948
  populate,
1854
1949
  locale,
@@ -1870,7 +1965,7 @@ const collectionTypes = {
1870
1965
  ctx.body = await async.pipe(
1871
1966
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
1872
1967
  permissionChecker2.sanitizeOutput,
1873
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
1968
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1874
1969
  )(document);
1875
1970
  });
1876
1971
  },
@@ -1879,14 +1974,13 @@ const collectionTypes = {
1879
1974
  const { id, model } = ctx.params;
1880
1975
  const { body } = ctx.request;
1881
1976
  const documentManager2 = getService$1("document-manager");
1882
- const documentMetadata2 = getService$1("document-metadata");
1883
1977
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1884
1978
  if (permissionChecker2.cannot.discard()) {
1885
1979
  return ctx.forbidden();
1886
1980
  }
1887
1981
  const permissionQuery = await permissionChecker2.sanitizedQuery.discard(ctx.query);
1888
1982
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1889
- const { locale } = getDocumentLocaleAndStatus(body);
1983
+ const { locale } = await getDocumentLocaleAndStatus(body);
1890
1984
  const document = await documentManager2.findOne(id, model, {
1891
1985
  populate,
1892
1986
  locale,
@@ -1901,14 +1995,14 @@ const collectionTypes = {
1901
1995
  ctx.body = await async.pipe(
1902
1996
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
1903
1997
  permissionChecker2.sanitizeOutput,
1904
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
1998
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1905
1999
  )(document);
1906
2000
  },
1907
2001
  async bulkDelete(ctx) {
1908
2002
  const { userAbility } = ctx.state;
1909
2003
  const { model } = ctx.params;
1910
2004
  const { query, body } = ctx.request;
1911
- const { ids } = body;
2005
+ const { documentIds } = body;
1912
2006
  await validateBulkActionInput(body);
1913
2007
  const documentManager2 = getService$1("document-manager");
1914
2008
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1916,14 +2010,22 @@ const collectionTypes = {
1916
2010
  return ctx.forbidden();
1917
2011
  }
1918
2012
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(query);
1919
- const idsWhereClause = { id: { $in: ids } };
1920
- const params = {
1921
- ...permissionQuery,
1922
- filters: {
1923
- $and: [idsWhereClause].concat(permissionQuery.filters || [])
2013
+ const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2014
+ const { locale } = await getDocumentLocaleAndStatus(body);
2015
+ const documentLocales = await documentManager2.findLocales(documentIds, model, {
2016
+ populate,
2017
+ locale
2018
+ });
2019
+ if (documentLocales.length === 0) {
2020
+ return ctx.notFound();
2021
+ }
2022
+ for (const document of documentLocales) {
2023
+ if (permissionChecker2.cannot.delete(document)) {
2024
+ return ctx.forbidden();
1924
2025
  }
1925
- };
1926
- const { count } = await documentManager2.deleteMany(params, model);
2026
+ }
2027
+ const localeDocumentsIds = documentLocales.map((document) => document.documentId);
2028
+ const { count } = await documentManager2.deleteMany(localeDocumentsIds, model, { locale });
1927
2029
  ctx.body = { count };
1928
2030
  },
1929
2031
  async countDraftRelations(ctx) {
@@ -1936,7 +2038,7 @@ const collectionTypes = {
1936
2038
  }
1937
2039
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1938
2040
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1939
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
2041
+ const { locale, status = "draft" } = await getDocumentLocaleAndStatus(ctx.query);
1940
2042
  const entity = await documentManager2.findOne(id, model, { populate, locale, status });
1941
2043
  if (!entity) {
1942
2044
  return ctx.notFound();
@@ -1951,7 +2053,7 @@ const collectionTypes = {
1951
2053
  },
1952
2054
  async countManyEntriesDraftRelations(ctx) {
1953
2055
  const { userAbility } = ctx.state;
1954
- const ids = ctx.request.query.ids;
2056
+ const ids = ctx.request.query.documentIds;
1955
2057
  const locale = ctx.request.query.locale;
1956
2058
  const { model } = ctx.params;
1957
2059
  const documentManager2 = getService$1("document-manager");
@@ -1959,16 +2061,16 @@ const collectionTypes = {
1959
2061
  if (permissionChecker2.cannot.read()) {
1960
2062
  return ctx.forbidden();
1961
2063
  }
1962
- const entities = await documentManager2.findMany(
2064
+ const documents = await documentManager2.findMany(
1963
2065
  {
1964
2066
  filters: {
1965
- id: ids
2067
+ documentId: ids
1966
2068
  },
1967
2069
  locale
1968
2070
  },
1969
2071
  model
1970
2072
  );
1971
- if (!entities) {
2073
+ if (!documents) {
1972
2074
  return ctx.notFound();
1973
2075
  }
1974
2076
  const number = await documentManager2.countManyEntriesDraftRelations(ids, model, locale);
@@ -2464,7 +2566,7 @@ const createOrUpdateDocument = async (ctx, opts) => {
2464
2566
  throw new errors.ForbiddenError();
2465
2567
  }
2466
2568
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.update(query);
2467
- const { locale } = getDocumentLocaleAndStatus(body);
2569
+ const { locale } = await getDocumentLocaleAndStatus(body);
2468
2570
  const [documentVersion, otherDocumentVersion] = await Promise.all([
2469
2571
  findDocument(sanitizedQuery, model, { locale, status: "draft" }),
2470
2572
  // Find the first document to check if it exists
@@ -2501,12 +2603,11 @@ const singleTypes = {
2501
2603
  const { model } = ctx.params;
2502
2604
  const { query = {} } = ctx.request;
2503
2605
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2504
- const documentMetadata2 = getService$1("document-metadata");
2505
2606
  if (permissionChecker2.cannot.read()) {
2506
2607
  return ctx.forbidden();
2507
2608
  }
2508
2609
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
2509
- const { locale, status } = getDocumentLocaleAndStatus(query);
2610
+ const { locale, status } = await getDocumentLocaleAndStatus(query);
2510
2611
  const version = await findDocument(permissionQuery, model, { locale, status });
2511
2612
  if (!version) {
2512
2613
  if (permissionChecker2.cannot.create()) {
@@ -2516,8 +2617,10 @@ const singleTypes = {
2516
2617
  if (!document) {
2517
2618
  return ctx.notFound();
2518
2619
  }
2519
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
2620
+ const { meta } = await formatDocumentWithMetadata(
2621
+ permissionChecker2,
2520
2622
  model,
2623
+ // @ts-expect-error - fix types
2521
2624
  { id: document.documentId, locale, publishedAt: null },
2522
2625
  { availableLocales: true, availableStatus: false }
2523
2626
  );
@@ -2528,16 +2631,15 @@ const singleTypes = {
2528
2631
  return ctx.forbidden();
2529
2632
  }
2530
2633
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
2531
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2634
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2532
2635
  },
2533
2636
  async createOrUpdate(ctx) {
2534
2637
  const { userAbility } = ctx.state;
2535
2638
  const { model } = ctx.params;
2536
- const documentMetadata2 = getService$1("document-metadata");
2537
2639
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2538
2640
  const document = await createOrUpdateDocument(ctx);
2539
2641
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
2540
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2642
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2541
2643
  },
2542
2644
  async delete(ctx) {
2543
2645
  const { userAbility } = ctx.state;
@@ -2550,7 +2652,7 @@ const singleTypes = {
2550
2652
  }
2551
2653
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.delete(query);
2552
2654
  const populate = await buildPopulateFromQuery(sanitizedQuery, model);
2553
- const { locale } = getDocumentLocaleAndStatus(query);
2655
+ const { locale } = await getDocumentLocaleAndStatus(query);
2554
2656
  const documentLocales = await documentManager2.findLocales(void 0, model, {
2555
2657
  populate,
2556
2658
  locale
@@ -2573,7 +2675,6 @@ const singleTypes = {
2573
2675
  const { model } = ctx.params;
2574
2676
  const { query = {} } = ctx.request;
2575
2677
  const documentManager2 = getService$1("document-manager");
2576
- const documentMetadata2 = getService$1("document-metadata");
2577
2678
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2578
2679
  if (permissionChecker2.cannot.publish()) {
2579
2680
  return ctx.forbidden();
@@ -2588,11 +2689,12 @@ const singleTypes = {
2588
2689
  if (permissionChecker2.cannot.publish(document)) {
2589
2690
  throw new errors.ForbiddenError();
2590
2691
  }
2591
- const { locale } = getDocumentLocaleAndStatus(document);
2592
- return documentManager2.publish(document.documentId, model, { locale });
2692
+ const { locale } = await getDocumentLocaleAndStatus(document);
2693
+ const publishResult = await documentManager2.publish(document.documentId, model, { locale });
2694
+ return publishResult.at(0);
2593
2695
  });
2594
2696
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
2595
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2697
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2596
2698
  },
2597
2699
  async unpublish(ctx) {
2598
2700
  const { userAbility } = ctx.state;
@@ -2602,7 +2704,6 @@ const singleTypes = {
2602
2704
  query = {}
2603
2705
  } = ctx.request;
2604
2706
  const documentManager2 = getService$1("document-manager");
2605
- const documentMetadata2 = getService$1("document-metadata");
2606
2707
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2607
2708
  if (permissionChecker2.cannot.unpublish()) {
2608
2709
  return ctx.forbidden();
@@ -2611,7 +2712,7 @@ const singleTypes = {
2611
2712
  return ctx.forbidden();
2612
2713
  }
2613
2714
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.unpublish(query);
2614
- const { locale } = getDocumentLocaleAndStatus(body);
2715
+ const { locale } = await getDocumentLocaleAndStatus(body);
2615
2716
  const document = await findDocument(sanitizedQuery, model, { locale });
2616
2717
  if (!document) {
2617
2718
  return ctx.notFound();
@@ -2629,7 +2730,7 @@ const singleTypes = {
2629
2730
  ctx.body = await async.pipe(
2630
2731
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
2631
2732
  permissionChecker2.sanitizeOutput,
2632
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2733
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2633
2734
  )(document);
2634
2735
  });
2635
2736
  },
@@ -2638,13 +2739,12 @@ const singleTypes = {
2638
2739
  const { model } = ctx.params;
2639
2740
  const { body, query = {} } = ctx.request;
2640
2741
  const documentManager2 = getService$1("document-manager");
2641
- const documentMetadata2 = getService$1("document-metadata");
2642
2742
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2643
2743
  if (permissionChecker2.cannot.discard()) {
2644
2744
  return ctx.forbidden();
2645
2745
  }
2646
2746
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.discard(query);
2647
- const { locale } = getDocumentLocaleAndStatus(body);
2747
+ const { locale } = await getDocumentLocaleAndStatus(body);
2648
2748
  const document = await findDocument(sanitizedQuery, model, { locale, status: "published" });
2649
2749
  if (!document) {
2650
2750
  return ctx.notFound();
@@ -2655,7 +2755,7 @@ const singleTypes = {
2655
2755
  ctx.body = await async.pipe(
2656
2756
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
2657
2757
  permissionChecker2.sanitizeOutput,
2658
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2758
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2659
2759
  )(document);
2660
2760
  },
2661
2761
  async countDraftRelations(ctx) {
@@ -2664,7 +2764,7 @@ const singleTypes = {
2664
2764
  const { query } = ctx.request;
2665
2765
  const documentManager2 = getService$1("document-manager");
2666
2766
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2667
- const { locale } = getDocumentLocaleAndStatus(query);
2767
+ const { locale } = await getDocumentLocaleAndStatus(query);
2668
2768
  if (permissionChecker2.cannot.read()) {
2669
2769
  return ctx.forbidden();
2670
2770
  }
@@ -2685,7 +2785,7 @@ const uid$1 = {
2685
2785
  async generateUID(ctx) {
2686
2786
  const { contentTypeUID, field, data } = await validateGenerateUIDInput(ctx.request.body);
2687
2787
  const { query = {} } = ctx.request;
2688
- const { locale } = getDocumentLocaleAndStatus(query);
2788
+ const { locale } = await getDocumentLocaleAndStatus(query);
2689
2789
  await validateUIDField(contentTypeUID, field);
2690
2790
  const uidService = getService$1("uid");
2691
2791
  ctx.body = {
@@ -2697,7 +2797,7 @@ const uid$1 = {
2697
2797
  ctx.request.body
2698
2798
  );
2699
2799
  const { query = {} } = ctx.request;
2700
- const { locale } = getDocumentLocaleAndStatus(query);
2800
+ const { locale } = await getDocumentLocaleAndStatus(query);
2701
2801
  await validateUIDField(contentTypeUID, field);
2702
2802
  const uidService = getService$1("uid");
2703
2803
  const isAvailable = await uidService.checkUIDAvailability({
@@ -3488,7 +3588,7 @@ const permission = ({ strapi: strapi2 }) => ({
3488
3588
  await strapi2.service("admin::permission").actionProvider.registerMany(actions);
3489
3589
  }
3490
3590
  });
3491
- const { isVisibleAttribute: isVisibleAttribute$1 } = strapiUtils.contentTypes;
3591
+ const { isVisibleAttribute: isVisibleAttribute$1, isScalarAttribute, getDoesAttributeRequireValidation } = strapiUtils.contentTypes;
3492
3592
  const { isAnyToMany } = strapiUtils.relations;
3493
3593
  const { PUBLISHED_AT_ATTRIBUTE: PUBLISHED_AT_ATTRIBUTE$1 } = strapiUtils.contentTypes.constants;
3494
3594
  const isMorphToRelation = (attribute) => isRelation(attribute) && attribute.relation.includes("morphTo");
@@ -3579,6 +3679,42 @@ const getDeepPopulate = (uid2, {
3579
3679
  {}
3580
3680
  );
3581
3681
  };
3682
+ const getValidatableFieldsPopulate = (uid2, {
3683
+ initialPopulate = {},
3684
+ countMany = false,
3685
+ countOne = false,
3686
+ maxLevel = Infinity
3687
+ } = {}, level = 1) => {
3688
+ if (level > maxLevel) {
3689
+ return {};
3690
+ }
3691
+ const model = strapi.getModel(uid2);
3692
+ return Object.entries(model.attributes).reduce((populateAcc, [attributeName, attribute]) => {
3693
+ if (!getDoesAttributeRequireValidation(attribute)) {
3694
+ return populateAcc;
3695
+ }
3696
+ if (isScalarAttribute(attribute)) {
3697
+ return merge(populateAcc, {
3698
+ [attributeName]: true
3699
+ });
3700
+ }
3701
+ return merge(
3702
+ populateAcc,
3703
+ getPopulateFor(
3704
+ attributeName,
3705
+ model,
3706
+ {
3707
+ // @ts-expect-error - improve types
3708
+ initialPopulate: initialPopulate?.[attributeName],
3709
+ countMany,
3710
+ countOne,
3711
+ maxLevel
3712
+ },
3713
+ level
3714
+ )
3715
+ );
3716
+ }, {});
3717
+ };
3582
3718
  const getDeepPopulateDraftCount = (uid2) => {
3583
3719
  const model = strapi.getModel(uid2);
3584
3720
  let hasRelations = false;
@@ -3600,22 +3736,24 @@ const getDeepPopulateDraftCount = (uid2) => {
3600
3736
  attribute.component
3601
3737
  );
3602
3738
  if (childHasRelations) {
3603
- populateAcc[attributeName] = { populate: populate2 };
3739
+ populateAcc[attributeName] = {
3740
+ populate: populate2
3741
+ };
3604
3742
  hasRelations = true;
3605
3743
  }
3606
3744
  break;
3607
3745
  }
3608
3746
  case "dynamiczone": {
3609
- const dzPopulate = (attribute.components || []).reduce((acc, componentUID) => {
3610
- const { populate: populate2, hasRelations: childHasRelations } = getDeepPopulateDraftCount(componentUID);
3611
- if (childHasRelations) {
3747
+ const dzPopulateFragment = attribute.components?.reduce((acc, componentUID) => {
3748
+ const { populate: componentPopulate, hasRelations: componentHasRelations } = getDeepPopulateDraftCount(componentUID);
3749
+ if (componentHasRelations) {
3612
3750
  hasRelations = true;
3613
- return merge(acc, populate2);
3751
+ return { ...acc, [componentUID]: { populate: componentPopulate } };
3614
3752
  }
3615
3753
  return acc;
3616
3754
  }, {});
3617
- if (!isEmpty(dzPopulate)) {
3618
- populateAcc[attributeName] = { populate: dzPopulate };
3755
+ if (!isEmpty(dzPopulateFragment)) {
3756
+ populateAcc[attributeName] = { on: dzPopulateFragment };
3619
3757
  }
3620
3758
  break;
3621
3759
  }
@@ -3807,41 +3945,70 @@ const AVAILABLE_STATUS_FIELDS = [
3807
3945
  "updatedBy",
3808
3946
  "status"
3809
3947
  ];
3810
- const AVAILABLE_LOCALES_FIELDS = ["id", "locale", "updatedAt", "createdAt", "status"];
3948
+ const AVAILABLE_LOCALES_FIELDS = [
3949
+ "id",
3950
+ "locale",
3951
+ "updatedAt",
3952
+ "createdAt",
3953
+ "status",
3954
+ "publishedAt",
3955
+ "documentId"
3956
+ ];
3811
3957
  const CONTENT_MANAGER_STATUS = {
3812
3958
  PUBLISHED: "published",
3813
3959
  DRAFT: "draft",
3814
3960
  MODIFIED: "modified"
3815
3961
  };
3816
- const areDatesEqual = (date1, date2, threshold) => {
3817
- if (!date1 || !date2) {
3962
+ const getIsVersionLatestModification = (version, otherVersion) => {
3963
+ if (!version || !version.updatedAt) {
3818
3964
  return false;
3819
3965
  }
3820
- const time1 = new Date(date1).getTime();
3821
- const time2 = new Date(date2).getTime();
3822
- const difference2 = Math.abs(time1 - time2);
3823
- return difference2 <= threshold;
3966
+ const versionUpdatedAt = version?.updatedAt ? new Date(version.updatedAt).getTime() : 0;
3967
+ const otherUpdatedAt = otherVersion?.updatedAt ? new Date(otherVersion.updatedAt).getTime() : 0;
3968
+ return versionUpdatedAt > otherUpdatedAt;
3824
3969
  };
3825
3970
  const documentMetadata = ({ strapi: strapi2 }) => ({
3826
3971
  /**
3827
3972
  * Returns available locales of a document for the current status
3828
3973
  */
3829
- getAvailableLocales(uid2, version, allVersions) {
3974
+ async getAvailableLocales(uid2, version, allVersions, validatableFields = []) {
3830
3975
  const versionsByLocale = groupBy("locale", allVersions);
3831
3976
  delete versionsByLocale[version.locale];
3832
- return Object.values(versionsByLocale).map((localeVersions) => {
3833
- if (!contentTypes$1.hasDraftAndPublish(strapi2.getModel(uid2))) {
3834
- return pick(AVAILABLE_LOCALES_FIELDS, localeVersions[0]);
3977
+ const model = strapi2.getModel(uid2);
3978
+ const keysToKeep = [...AVAILABLE_LOCALES_FIELDS, ...validatableFields];
3979
+ const traversalFunction = async (localeVersion) => traverseEntity(
3980
+ ({ key }, { remove }) => {
3981
+ if (keysToKeep.includes(key)) {
3982
+ return;
3983
+ }
3984
+ remove(key);
3985
+ },
3986
+ { schema: model, getModel: strapi2.getModel.bind(strapi2) },
3987
+ // @ts-expect-error fix types DocumentVersion incompatible with Data
3988
+ localeVersion
3989
+ );
3990
+ const mappingResult = await async.map(
3991
+ Object.values(versionsByLocale),
3992
+ async (localeVersions) => {
3993
+ const mappedLocaleVersions = await async.map(
3994
+ localeVersions,
3995
+ traversalFunction
3996
+ );
3997
+ if (!contentTypes$1.hasDraftAndPublish(model)) {
3998
+ return mappedLocaleVersions[0];
3999
+ }
4000
+ const draftVersion = mappedLocaleVersions.find((v) => v.publishedAt === null);
4001
+ const otherVersions = mappedLocaleVersions.filter((v) => v.id !== draftVersion?.id);
4002
+ if (!draftVersion) {
4003
+ return;
4004
+ }
4005
+ return {
4006
+ ...draftVersion,
4007
+ status: this.getStatus(draftVersion, otherVersions)
4008
+ };
3835
4009
  }
3836
- const draftVersion = localeVersions.find((v) => v.publishedAt === null);
3837
- const otherVersions = localeVersions.filter((v) => v.id !== draftVersion?.id);
3838
- if (!draftVersion)
3839
- return;
3840
- return {
3841
- ...pick(AVAILABLE_LOCALES_FIELDS, draftVersion),
3842
- status: this.getStatus(draftVersion, otherVersions)
3843
- };
3844
- }).filter(Boolean);
4010
+ );
4011
+ return mappingResult.filter(Boolean);
3845
4012
  },
3846
4013
  /**
3847
4014
  * Returns available status of a document for the current locale
@@ -3879,26 +4046,37 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3879
4046
  });
3880
4047
  },
3881
4048
  getStatus(version, otherDocumentStatuses) {
3882
- const isDraft = version.publishedAt === null;
3883
- if (!otherDocumentStatuses?.length) {
3884
- return isDraft ? CONTENT_MANAGER_STATUS.DRAFT : CONTENT_MANAGER_STATUS.PUBLISHED;
4049
+ let draftVersion;
4050
+ let publishedVersion;
4051
+ if (version.publishedAt) {
4052
+ publishedVersion = version;
4053
+ } else {
4054
+ draftVersion = version;
3885
4055
  }
3886
- if (isDraft) {
3887
- const publishedVersion = otherDocumentStatuses?.find((d) => d.publishedAt !== null);
3888
- if (!publishedVersion) {
3889
- return CONTENT_MANAGER_STATUS.DRAFT;
3890
- }
4056
+ const otherVersion = otherDocumentStatuses?.at(0);
4057
+ if (otherVersion?.publishedAt) {
4058
+ publishedVersion = otherVersion;
4059
+ } else if (otherVersion) {
4060
+ draftVersion = otherVersion;
3891
4061
  }
3892
- if (areDatesEqual(version.updatedAt, otherDocumentStatuses.at(0)?.updatedAt, 500)) {
4062
+ if (!draftVersion)
3893
4063
  return CONTENT_MANAGER_STATUS.PUBLISHED;
3894
- }
3895
- return CONTENT_MANAGER_STATUS.MODIFIED;
4064
+ if (!publishedVersion)
4065
+ return CONTENT_MANAGER_STATUS.DRAFT;
4066
+ const isDraftModified = getIsVersionLatestModification(draftVersion, publishedVersion);
4067
+ return isDraftModified ? CONTENT_MANAGER_STATUS.MODIFIED : CONTENT_MANAGER_STATUS.PUBLISHED;
3896
4068
  },
4069
+ // TODO is it necessary to return metadata on every page of the CM
4070
+ // We could refactor this so the locales are only loaded when they're
4071
+ // needed. e.g. in the bulk locale action modal.
3897
4072
  async getMetadata(uid2, version, { availableLocales = true, availableStatus = true } = {}) {
4073
+ const populate = getValidatableFieldsPopulate(uid2);
3898
4074
  const versions = await strapi2.db.query(uid2).findMany({
3899
4075
  where: { documentId: version.documentId },
3900
- select: ["createdAt", "updatedAt", "locale", "publishedAt", "documentId"],
3901
4076
  populate: {
4077
+ // Populate only fields that require validation for bulk locale actions
4078
+ ...populate,
4079
+ // NOTE: creator fields are selected in this way to avoid exposing sensitive data
3902
4080
  createdBy: {
3903
4081
  select: ["id", "firstname", "lastname", "email"]
3904
4082
  },
@@ -3907,7 +4085,7 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3907
4085
  }
3908
4086
  }
3909
4087
  });
3910
- const availableLocalesResult = availableLocales ? this.getAvailableLocales(uid2, version, versions) : [];
4088
+ const availableLocalesResult = availableLocales ? await this.getAvailableLocales(uid2, version, versions, Object.keys(populate)) : [];
3911
4089
  const availableStatusResult = availableStatus ? this.getAvailableStatus(version, versions) : null;
3912
4090
  return {
3913
4091
  availableLocales: availableLocalesResult,
@@ -3920,8 +4098,9 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3920
4098
  * - Available status of the document for the current locale
3921
4099
  */
3922
4100
  async formatDocumentWithMetadata(uid2, document, opts = {}) {
3923
- if (!document)
4101
+ if (!document) {
3924
4102
  return document;
4103
+ }
3925
4104
  const hasDraftAndPublish = contentTypes$1.hasDraftAndPublish(strapi2.getModel(uid2));
3926
4105
  if (!hasDraftAndPublish) {
3927
4106
  opts.availableStatus = false;
@@ -3971,26 +4150,9 @@ const sumDraftCounts = (entity, uid2) => {
3971
4150
  }, 0);
3972
4151
  };
3973
4152
  const { ApplicationError } = errors;
3974
- const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = ALLOWED_WEBHOOK_EVENTS;
3975
4153
  const { PUBLISHED_AT_ATTRIBUTE } = contentTypes$1.constants;
3976
4154
  const omitPublishedAtField = omit(PUBLISHED_AT_ATTRIBUTE);
3977
4155
  const omitIdField = omit("id");
3978
- const emitEvent = async (uid2, event, document) => {
3979
- const modelDef = strapi.getModel(uid2);
3980
- const sanitizedDocument = await sanitize.sanitizers.defaultSanitizeOutput(
3981
- {
3982
- schema: modelDef,
3983
- getModel(uid22) {
3984
- return strapi.getModel(uid22);
3985
- }
3986
- },
3987
- document
3988
- );
3989
- strapi.eventHub.emit(event, {
3990
- model: modelDef.modelName,
3991
- entry: sanitizedDocument
3992
- });
3993
- };
3994
4156
  const documentManager = ({ strapi: strapi2 }) => {
3995
4157
  return {
3996
4158
  async findOne(id, uid2, opts = {}) {
@@ -4009,6 +4171,9 @@ const documentManager = ({ strapi: strapi2 }) => {
4009
4171
  } else if (opts.locale && opts.locale !== "*") {
4010
4172
  where.locale = opts.locale;
4011
4173
  }
4174
+ if (typeof opts.isPublished === "boolean") {
4175
+ where.publishedAt = { $notNull: opts.isPublished };
4176
+ }
4012
4177
  return strapi2.db.query(uid2).findMany({ populate: opts.populate, where });
4013
4178
  },
4014
4179
  async findMany(opts, uid2) {
@@ -4016,20 +4181,16 @@ const documentManager = ({ strapi: strapi2 }) => {
4016
4181
  return strapi2.documents(uid2).findMany(params);
4017
4182
  },
4018
4183
  async findPage(opts, uid2) {
4019
- const page = Number(opts?.page) || 1;
4020
- const pageSize = Number(opts?.pageSize) || 10;
4184
+ const params = pagination.withDefaultPagination(opts || {}, {
4185
+ maxLimit: 1e3
4186
+ });
4021
4187
  const [documents, total = 0] = await Promise.all([
4022
- strapi2.documents(uid2).findMany(opts),
4023
- strapi2.documents(uid2).count(opts)
4188
+ strapi2.documents(uid2).findMany(params),
4189
+ strapi2.documents(uid2).count(params)
4024
4190
  ]);
4025
4191
  return {
4026
4192
  results: documents,
4027
- pagination: {
4028
- page,
4029
- pageSize,
4030
- pageCount: Math.ceil(total / pageSize),
4031
- total
4032
- }
4193
+ pagination: pagination.transformPagedPaginationInfo(params, total)
4033
4194
  };
4034
4195
  },
4035
4196
  async create(uid2, opts = {}) {
@@ -4075,70 +4236,36 @@ const documentManager = ({ strapi: strapi2 }) => {
4075
4236
  return {};
4076
4237
  },
4077
4238
  // FIXME: handle relations
4078
- async deleteMany(opts, uid2) {
4079
- const docs = await strapi2.documents(uid2).findMany(opts);
4080
- for (const doc of docs) {
4081
- await strapi2.documents(uid2).delete({ documentId: doc.documentId });
4082
- }
4083
- return { count: docs.length };
4239
+ async deleteMany(documentIds, uid2, opts = {}) {
4240
+ const deletedEntries = await strapi2.db.transaction(async () => {
4241
+ return Promise.all(documentIds.map(async (id) => this.delete(id, uid2, opts)));
4242
+ });
4243
+ return { count: deletedEntries.length };
4084
4244
  },
4085
4245
  async publish(id, uid2, opts = {}) {
4086
4246
  const populate = await buildDeepPopulate(uid2);
4087
4247
  const params = { ...opts, populate };
4088
- return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries.at(0));
4248
+ return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries);
4089
4249
  },
4090
- async publishMany(entities, uid2) {
4091
- if (!entities.length) {
4092
- return null;
4093
- }
4094
- await Promise.all(
4095
- entities.map((document) => {
4096
- return strapi2.entityValidator.validateEntityCreation(
4097
- strapi2.getModel(uid2),
4098
- document,
4099
- void 0,
4100
- // @ts-expect-error - FIXME: entity here is unnecessary
4101
- document
4102
- );
4103
- })
4104
- );
4105
- const entitiesToPublish = entities.filter((doc) => !doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4106
- const filters = { id: { $in: entitiesToPublish } };
4107
- const data = { [PUBLISHED_AT_ATTRIBUTE]: /* @__PURE__ */ new Date() };
4108
- const populate = await buildDeepPopulate(uid2);
4109
- const publishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4110
- where: filters,
4111
- data
4112
- });
4113
- const publishedEntities = await strapi2.db.query(uid2).findMany({
4114
- where: filters,
4115
- populate
4250
+ async publishMany(uid2, documentIds, locale) {
4251
+ return strapi2.db.transaction(async () => {
4252
+ const results = await Promise.all(
4253
+ documentIds.map((documentId) => this.publish(documentId, uid2, { locale }))
4254
+ );
4255
+ const publishedEntitiesCount = results.flat().filter(Boolean).length;
4256
+ return publishedEntitiesCount;
4116
4257
  });
4117
- await Promise.all(
4118
- publishedEntities.map((doc) => emitEvent(uid2, ENTRY_PUBLISH, doc))
4119
- );
4120
- return publishedEntitiesCount;
4121
4258
  },
4122
- async unpublishMany(documents, uid2) {
4123
- if (!documents.length) {
4124
- return null;
4125
- }
4126
- const entitiesToUnpublish = documents.filter((doc) => doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4127
- const filters = { id: { $in: entitiesToUnpublish } };
4128
- const data = { [PUBLISHED_AT_ATTRIBUTE]: null };
4129
- const populate = await buildDeepPopulate(uid2);
4130
- const unpublishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4131
- where: filters,
4132
- data
4133
- });
4134
- const unpublishedEntities = await strapi2.db.query(uid2).findMany({
4135
- where: filters,
4136
- populate
4259
+ async unpublishMany(documentIds, uid2, opts = {}) {
4260
+ const unpublishedEntries = await strapi2.db.transaction(async () => {
4261
+ return Promise.all(
4262
+ documentIds.map(
4263
+ (id) => strapi2.documents(uid2).unpublish({ ...opts, documentId: id }).then((result) => result?.entries)
4264
+ )
4265
+ );
4137
4266
  });
4138
- await Promise.all(
4139
- unpublishedEntities.map((doc) => emitEvent(uid2, ENTRY_UNPUBLISH, doc))
4140
- );
4141
- return unpublishedEntitiesCount;
4267
+ const unpublishedEntitiesCount = unpublishedEntries.flat().filter(Boolean).length;
4268
+ return { count: unpublishedEntitiesCount };
4142
4269
  },
4143
4270
  async unpublish(id, uid2, opts = {}) {
4144
4271
  const populate = await buildDeepPopulate(uid2);
@@ -4163,16 +4290,20 @@ const documentManager = ({ strapi: strapi2 }) => {
4163
4290
  }
4164
4291
  return sumDraftCounts(document, uid2);
4165
4292
  },
4166
- async countManyEntriesDraftRelations(ids, uid2, locale) {
4293
+ async countManyEntriesDraftRelations(documentIds, uid2, locale) {
4167
4294
  const { populate, hasRelations } = getDeepPopulateDraftCount(uid2);
4168
4295
  if (!hasRelations) {
4169
4296
  return 0;
4170
4297
  }
4298
+ let localeFilter = {};
4299
+ if (locale) {
4300
+ localeFilter = Array.isArray(locale) ? { locale: { $in: locale } } : { locale };
4301
+ }
4171
4302
  const entities = await strapi2.db.query(uid2).findMany({
4172
4303
  populate,
4173
4304
  where: {
4174
- id: { $in: ids },
4175
- ...locale ? { locale } : {}
4305
+ documentId: { $in: documentIds },
4306
+ ...localeFilter
4176
4307
  }
4177
4308
  });
4178
4309
  const totalNumberDraftRelations = entities.reduce(