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

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 (184) 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--2aLCv-G.mjs → ComponentConfigurationPage-B3yDbeU1.mjs} +3 -3
  7. package/dist/_chunks/{ComponentConfigurationPage--2aLCv-G.mjs.map → ComponentConfigurationPage-B3yDbeU1.mjs.map} +1 -1
  8. package/dist/_chunks/{ComponentConfigurationPage-43KmCNQE.js → ComponentConfigurationPage-KXSuLnQD.js} +3 -3
  9. package/dist/_chunks/{ComponentConfigurationPage-43KmCNQE.js.map → ComponentConfigurationPage-KXSuLnQD.js.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-BQ17--5R.js} +3 -3
  15. package/dist/_chunks/{EditConfigurationPage-BfFzJ4Br.js.map → EditConfigurationPage-BQ17--5R.js.map} +1 -1
  16. package/dist/_chunks/{EditConfigurationPage-CUcGHHvQ.mjs → EditConfigurationPage-D7PrLO8j.mjs} +3 -3
  17. package/dist/_chunks/{EditConfigurationPage-CUcGHHvQ.mjs.map → EditConfigurationPage-D7PrLO8j.mjs.map} +1 -1
  18. package/dist/_chunks/{EditViewPage-Bm8lgcm6.mjs → EditViewPage-B7VgwJaG.mjs} +58 -47
  19. package/dist/_chunks/EditViewPage-B7VgwJaG.mjs.map +1 -0
  20. package/dist/_chunks/{EditViewPage-CzOT5Kpj.js → EditViewPage-BgjdnGz2.js} +57 -48
  21. package/dist/_chunks/EditViewPage-BgjdnGz2.js.map +1 -0
  22. package/dist/_chunks/{Field-Caef4JjM.js → Field-CdK7ZLmv.js} +1030 -800
  23. package/dist/_chunks/Field-CdK7ZLmv.js.map +1 -0
  24. package/dist/_chunks/{Field-Dlh0uGnL.mjs → Field-tHCw4lGA.mjs} +981 -750
  25. package/dist/_chunks/Field-tHCw4lGA.mjs.map +1 -0
  26. package/dist/_chunks/{Form-EnaQL_6L.mjs → Form-BJxdTv3Q.mjs} +56 -43
  27. package/dist/_chunks/Form-BJxdTv3Q.mjs.map +1 -0
  28. package/dist/_chunks/{Form-BzuAjtRq.js → Form-C_0KTVvV.js} +55 -43
  29. package/dist/_chunks/Form-C_0KTVvV.js.map +1 -0
  30. package/dist/_chunks/{History-D6sbCJvo.mjs → History-DR2txJLE.mjs} +151 -57
  31. package/dist/_chunks/History-DR2txJLE.mjs.map +1 -0
  32. package/dist/_chunks/{History-C17LiyRg.js → History-nuEzM5qm.js} +151 -58
  33. package/dist/_chunks/History-nuEzM5qm.js.map +1 -0
  34. package/dist/_chunks/{ListConfigurationPage-Dks5SX6f.js → ListConfigurationPage-CnB86Psm.js} +70 -61
  35. package/dist/_chunks/ListConfigurationPage-CnB86Psm.js.map +1 -0
  36. package/dist/_chunks/{ListConfigurationPage-Ce4qs7qE.mjs → ListConfigurationPage-voFVtXu6.mjs} +67 -57
  37. package/dist/_chunks/ListConfigurationPage-voFVtXu6.mjs.map +1 -0
  38. package/dist/_chunks/{ListViewPage-Be7S5aKL.mjs → ListViewPage-B_GaWgRH.mjs} +95 -106
  39. package/dist/_chunks/ListViewPage-B_GaWgRH.mjs.map +1 -0
  40. package/dist/_chunks/{ListViewPage-BwrZrPsh.js → ListViewPage-SXIXm-RM.js} +100 -111
  41. package/dist/_chunks/ListViewPage-SXIXm-RM.js.map +1 -0
  42. package/dist/_chunks/{NoContentTypePage-Cu5r1-JT.js → NoContentTypePage-BzsQ3hLZ.js} +5 -5
  43. package/dist/_chunks/NoContentTypePage-BzsQ3hLZ.js.map +1 -0
  44. package/dist/_chunks/{NoContentTypePage-CIPmYQMm.mjs → NoContentTypePage-CYiGpsbj.mjs} +7 -7
  45. package/dist/_chunks/NoContentTypePage-CYiGpsbj.mjs.map +1 -0
  46. package/dist/_chunks/{NoPermissionsPage-DhJ7LYrr.mjs → NoPermissionsPage-B5baIHal.mjs} +5 -6
  47. package/dist/_chunks/NoPermissionsPage-B5baIHal.mjs.map +1 -0
  48. package/dist/_chunks/{NoPermissionsPage-C-j6TEUF.js → NoPermissionsPage-IGkId4C5.js} +4 -5
  49. package/dist/_chunks/NoPermissionsPage-IGkId4C5.js.map +1 -0
  50. package/dist/_chunks/{Relations-CY7AtkDA.mjs → Relations-CIYDdKU-.mjs} +67 -57
  51. package/dist/_chunks/Relations-CIYDdKU-.mjs.map +1 -0
  52. package/dist/_chunks/{Relations-Czs-uZ-s.js → Relations-Dhuurpx2.js} +71 -62
  53. package/dist/_chunks/Relations-Dhuurpx2.js.map +1 -0
  54. package/dist/_chunks/{en-MBPul9Su.mjs → en-BrCTWlZv.mjs} +11 -4
  55. package/dist/_chunks/{en-MBPul9Su.mjs.map → en-BrCTWlZv.mjs.map} +1 -1
  56. package/dist/_chunks/{en-C-V1_90f.js → en-uOUIxfcQ.js} +11 -4
  57. package/dist/_chunks/{en-C-V1_90f.js.map → en-uOUIxfcQ.js.map} +1 -1
  58. package/dist/_chunks/{index-DNVx8ssZ.mjs → index-C9TJPyni.mjs} +1696 -912
  59. package/dist/_chunks/index-C9TJPyni.mjs.map +1 -0
  60. package/dist/_chunks/{index-X_2tafck.js → index-CdT0kHZ8.js} +1626 -843
  61. package/dist/_chunks/index-CdT0kHZ8.js.map +1 -0
  62. package/dist/_chunks/{layout-Dnh0PNp9.mjs → layout-BNqvLR_b.mjs} +45 -28
  63. package/dist/_chunks/layout-BNqvLR_b.mjs.map +1 -0
  64. package/dist/_chunks/{layout-dBc7wN7L.js → layout-C6dxWYT7.js} +45 -30
  65. package/dist/_chunks/layout-C6dxWYT7.js.map +1 -0
  66. package/dist/_chunks/{relations-Dx7tMKJN.mjs → relations-CkKqKw65.mjs} +2 -2
  67. package/dist/_chunks/{relations-Dx7tMKJN.mjs.map → relations-CkKqKw65.mjs.map} +1 -1
  68. package/dist/_chunks/{relations-4pHtBrHJ.js → relations-DtFaDnP1.js} +2 -2
  69. package/dist/_chunks/{relations-4pHtBrHJ.js.map → relations-DtFaDnP1.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/index.d.ts +3 -0
  84. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  85. package/dist/admin/src/hooks/useDocument.d.ts +5 -8
  86. package/dist/admin/src/hooks/useDocumentActions.d.ts +24 -3
  87. package/dist/admin/src/hooks/useDocumentLayout.d.ts +2 -2
  88. package/dist/admin/src/hooks/useDragAndDrop.d.ts +4 -4
  89. package/dist/admin/src/hooks/useKeyboardDragAndDrop.d.ts +1 -1
  90. package/dist/admin/src/index.d.ts +1 -0
  91. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +11 -4
  92. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksInput.d.ts +3 -3
  93. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/constants.d.ts +4 -0
  94. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Input.d.ts +2 -2
  95. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.d.ts +3 -5
  96. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/Field.d.ts +1 -1
  97. package/dist/admin/src/pages/EditView/components/FormInputs/Relations.d.ts +30 -18
  98. package/dist/admin/src/pages/EditView/components/FormInputs/UID.d.ts +2 -2
  99. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +3 -49
  100. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/Field.d.ts +2 -2
  101. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygFooter.d.ts +2 -2
  102. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +48 -53
  103. package/dist/admin/src/pages/EditView/components/InputRenderer.d.ts +2 -10
  104. package/dist/admin/src/pages/ListView/components/BulkActions/Actions.d.ts +3 -30
  105. package/dist/admin/src/pages/ListView/components/BulkActions/ConfirmBulkActionDialog.d.ts +2 -2
  106. package/dist/admin/src/pages/ListView/components/BulkActions/PublishAction.d.ts +9 -26
  107. package/dist/admin/src/services/api.d.ts +2 -3
  108. package/dist/admin/src/services/components.d.ts +2 -2
  109. package/dist/admin/src/services/contentTypes.d.ts +5 -5
  110. package/dist/admin/src/services/documents.d.ts +29 -17
  111. package/dist/admin/src/services/init.d.ts +2 -2
  112. package/dist/admin/src/services/relations.d.ts +3 -3
  113. package/dist/admin/src/services/uid.d.ts +3 -3
  114. package/dist/admin/src/utils/api.d.ts +4 -18
  115. package/dist/admin/src/utils/validation.d.ts +1 -6
  116. package/dist/server/index.js +602 -426
  117. package/dist/server/index.js.map +1 -1
  118. package/dist/server/index.mjs +610 -434
  119. package/dist/server/index.mjs.map +1 -1
  120. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  121. package/dist/server/src/controllers/single-types.d.ts.map +1 -1
  122. package/dist/server/src/controllers/uid.d.ts.map +1 -1
  123. package/dist/server/src/controllers/utils/metadata.d.ts +8 -0
  124. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -0
  125. package/dist/server/src/controllers/validation/dimensions.d.ts +11 -0
  126. package/dist/server/src/controllers/validation/dimensions.d.ts.map +1 -0
  127. package/dist/server/src/controllers/validation/index.d.ts +1 -1
  128. package/dist/server/src/history/services/history.d.ts +2 -4
  129. package/dist/server/src/history/services/history.d.ts.map +1 -1
  130. package/dist/server/src/history/services/index.d.ts +6 -2
  131. package/dist/server/src/history/services/index.d.ts.map +1 -1
  132. package/dist/server/src/history/services/lifecycles.d.ts +9 -0
  133. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -0
  134. package/dist/server/src/history/services/utils.d.ts +41 -9
  135. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  136. package/dist/server/src/history/utils.d.ts +6 -2
  137. package/dist/server/src/history/utils.d.ts.map +1 -1
  138. package/dist/server/src/index.d.ts +18 -39
  139. package/dist/server/src/index.d.ts.map +1 -1
  140. package/dist/server/src/services/document-manager.d.ts +13 -12
  141. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  142. package/dist/server/src/services/document-metadata.d.ts +8 -29
  143. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  144. package/dist/server/src/services/index.d.ts +18 -39
  145. package/dist/server/src/services/index.d.ts.map +1 -1
  146. package/dist/server/src/services/utils/populate.d.ts +8 -1
  147. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  148. package/dist/shared/contracts/collection-types.d.ts +14 -6
  149. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  150. package/dist/shared/contracts/relations.d.ts +2 -2
  151. package/dist/shared/contracts/relations.d.ts.map +1 -1
  152. package/package.json +13 -14
  153. package/dist/_chunks/CardDragPreview-DSVYodBX.js.map +0 -1
  154. package/dist/_chunks/CardDragPreview-ikSG4M46.mjs.map +0 -1
  155. package/dist/_chunks/ComponentIcon-BBQsYCVn.js.map +0 -1
  156. package/dist/_chunks/ComponentIcon-BOFnK76n.mjs.map +0 -1
  157. package/dist/_chunks/EditViewPage-Bm8lgcm6.mjs.map +0 -1
  158. package/dist/_chunks/EditViewPage-CzOT5Kpj.js.map +0 -1
  159. package/dist/_chunks/Field-Caef4JjM.js.map +0 -1
  160. package/dist/_chunks/Field-Dlh0uGnL.mjs.map +0 -1
  161. package/dist/_chunks/Form-BzuAjtRq.js.map +0 -1
  162. package/dist/_chunks/Form-EnaQL_6L.mjs.map +0 -1
  163. package/dist/_chunks/History-C17LiyRg.js.map +0 -1
  164. package/dist/_chunks/History-D6sbCJvo.mjs.map +0 -1
  165. package/dist/_chunks/ListConfigurationPage-Ce4qs7qE.mjs.map +0 -1
  166. package/dist/_chunks/ListConfigurationPage-Dks5SX6f.js.map +0 -1
  167. package/dist/_chunks/ListViewPage-Be7S5aKL.mjs.map +0 -1
  168. package/dist/_chunks/ListViewPage-BwrZrPsh.js.map +0 -1
  169. package/dist/_chunks/NoContentTypePage-CIPmYQMm.mjs.map +0 -1
  170. package/dist/_chunks/NoContentTypePage-Cu5r1-JT.js.map +0 -1
  171. package/dist/_chunks/NoPermissionsPage-C-j6TEUF.js.map +0 -1
  172. package/dist/_chunks/NoPermissionsPage-DhJ7LYrr.mjs.map +0 -1
  173. package/dist/_chunks/Relations-CY7AtkDA.mjs.map +0 -1
  174. package/dist/_chunks/Relations-Czs-uZ-s.js.map +0 -1
  175. package/dist/_chunks/index-DNVx8ssZ.mjs.map +0 -1
  176. package/dist/_chunks/index-X_2tafck.js.map +0 -1
  177. package/dist/_chunks/layout-Dnh0PNp9.mjs.map +0 -1
  178. package/dist/_chunks/layout-dBc7wN7L.js.map +0 -1
  179. package/dist/_chunks/urls-CbOsUOoW.mjs +0 -7
  180. package/dist/_chunks/urls-CbOsUOoW.mjs.map +0 -1
  181. package/dist/_chunks/urls-DzZya_gm.js +0 -6
  182. package/dist/_chunks/urls-DzZya_gm.js.map +0 -1
  183. package/dist/server/src/controllers/utils/dimensions.d.ts +0 -5
  184. 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, castArray, intersection, pipe, propOr, isEqual, isEmpty, set, isNil as isNil$1, has, prop, assoc, mapValues, flow, uniq, uniqBy, concat, getOr, propEq, merge, groupBy } 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,25 +186,39 @@ 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);
167
201
  return documentMetadataService.getStatus(document, meta.availableStatus);
168
202
  };
169
- const getDeepPopulate2 = (uid2) => {
203
+ const getDeepPopulate2 = (uid2, useDatabaseSyntax = false) => {
170
204
  const model = strapi2.getModel(uid2);
171
205
  const attributes = Object.entries(model.attributes);
206
+ const fieldSelector = useDatabaseSyntax ? "select" : "fields";
172
207
  return attributes.reduce((acc, [attributeName, attribute]) => {
173
208
  switch (attribute.type) {
174
209
  case "relation": {
210
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
211
+ if (isMorphRelation) {
212
+ break;
213
+ }
175
214
  const isVisible2 = contentTypes$1.isVisibleAttribute(model, attributeName);
176
215
  if (isVisible2) {
177
- acc[attributeName] = { fields: ["documentId", "locale", "publishedAt"] };
216
+ acc[attributeName] = { [fieldSelector]: ["documentId", "locale", "publishedAt"] };
178
217
  }
179
218
  break;
180
219
  }
181
220
  case "media": {
182
- acc[attributeName] = { fields: ["id"] };
221
+ acc[attributeName] = { [fieldSelector]: ["id"] };
183
222
  break;
184
223
  }
185
224
  case "component": {
@@ -202,80 +241,68 @@ const createHistoryService = ({ strapi: strapi2 }) => {
202
241
  return acc;
203
242
  }, {});
204
243
  };
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();
244
+ const buildMediaResponse = async (values) => {
245
+ return values.slice(0, 25).reduce(
246
+ async (currentRelationDataPromise, entry) => {
247
+ const currentRelationData = await currentRelationDataPromise;
248
+ if (!entry) {
249
+ return currentRelationData;
213
250
  }
214
- if (context.action !== "create" && context.action !== "update" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
215
- return next();
251
+ const relatedEntry = await strapi2.db.query("plugin::upload.file").findOne({ where: { id: entry.id } });
252
+ if (relatedEntry) {
253
+ currentRelationData.results.push(relatedEntry);
254
+ } else {
255
+ currentRelationData.meta.missingCount += 1;
216
256
  }
217
- const contentTypeUid = context.contentType.uid;
218
- if (!contentTypeUid.startsWith("api::")) {
219
- return next();
257
+ return currentRelationData;
258
+ },
259
+ Promise.resolve({
260
+ results: [],
261
+ meta: { missingCount: 0 }
262
+ })
263
+ );
264
+ };
265
+ const buildRelationReponse = async (values, attributeSchema) => {
266
+ return values.slice(0, 25).reduce(
267
+ async (currentRelationDataPromise, entry) => {
268
+ const currentRelationData = await currentRelationDataPromise;
269
+ if (!entry) {
270
+ return currentRelationData;
220
271
  }
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
- });
272
+ const relatedEntry = await strapi2.documents(attributeSchema.target).findOne({ documentId: entry.documentId, locale: entry.locale || void 0 });
273
+ if (relatedEntry) {
274
+ currentRelationData.results.push({
275
+ ...relatedEntry,
276
+ status: await getVersionStatus(attributeSchema.target, relatedEntry)
256
277
  });
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
- },
278
+ } else {
279
+ currentRelationData.meta.missingCount += 1;
280
+ }
281
+ return currentRelationData;
282
+ },
283
+ Promise.resolve({
284
+ results: [],
285
+ meta: { missingCount: 0 }
286
+ })
287
+ );
288
+ };
289
+ return {
290
+ getSchemaAttributesDiff,
291
+ getRelationRestoreValue,
292
+ getMediaRestoreValue,
293
+ getDefaultLocale,
294
+ getLocaleDictionary,
295
+ getRetentionDays,
296
+ getVersionStatus,
297
+ getDeepPopulate: getDeepPopulate2,
298
+ buildMediaResponse,
299
+ buildRelationReponse
300
+ };
301
+ };
302
+ const createHistoryService = ({ strapi: strapi2 }) => {
303
+ const query = strapi2.db.query(HISTORY_VERSION_UID);
304
+ const serviceUtils = createServiceUtils({ strapi: strapi2 });
305
+ return {
279
306
  async createVersion(historyVersionData) {
280
307
  await query.create({
281
308
  data: {
@@ -286,8 +313,8 @@ const createHistoryService = ({ strapi: strapi2 }) => {
286
313
  });
287
314
  },
288
315
  async findVersionsPage(params) {
289
- const locale = params.query.locale || await getDefaultLocale();
290
- const [{ results, pagination }, localeDictionary] = await Promise.all([
316
+ const locale = params.query.locale || await serviceUtils.getDefaultLocale();
317
+ const [{ results, pagination: pagination2 }, localeDictionary] = await Promise.all([
291
318
  query.findPage({
292
319
  ...params.query,
293
320
  where: {
@@ -300,78 +327,34 @@ const createHistoryService = ({ strapi: strapi2 }) => {
300
327
  populate: ["createdBy"],
301
328
  orderBy: [{ createdAt: "desc" }]
302
329
  }),
303
- getLocaleDictionary()
330
+ serviceUtils.getLocaleDictionary()
304
331
  ]);
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
332
  const populateEntryRelations = async (entry) => {
361
333
  const entryWithRelations = await Object.entries(entry.schema).reduce(
362
334
  async (currentDataWithRelations, [attributeKey, attributeSchema]) => {
363
335
  const attributeValue = entry.data[attributeKey];
364
336
  const attributeValues = Array.isArray(attributeValue) ? attributeValue : [attributeValue];
365
337
  if (attributeSchema.type === "media") {
338
+ const permissionChecker2 = getService$1("permission-checker").create({
339
+ userAbility: params.state.userAbility,
340
+ model: "plugin::upload.file"
341
+ });
342
+ const response = await serviceUtils.buildMediaResponse(attributeValues);
343
+ const sanitizedResults = await Promise.all(
344
+ response.results.map((media) => permissionChecker2.sanitizeOutput(media))
345
+ );
366
346
  return {
367
347
  ...await currentDataWithRelations,
368
- [attributeKey]: await buildMediaResponse(attributeValues)
348
+ [attributeKey]: {
349
+ results: sanitizedResults,
350
+ meta: response.meta
351
+ }
369
352
  };
370
353
  }
371
354
  if (attributeSchema.type === "relation" && attributeSchema.relation !== "morphToOne" && attributeSchema.relation !== "morphToMany") {
372
355
  if (attributeSchema.target === "admin::user") {
373
356
  const adminUsers = await Promise.all(
374
- attributeValues.map(async (userToPopulate) => {
357
+ attributeValues.map((userToPopulate) => {
375
358
  if (userToPopulate == null) {
376
359
  return null;
377
360
  }
@@ -388,9 +371,23 @@ const createHistoryService = ({ strapi: strapi2 }) => {
388
371
  [attributeKey]: adminUsers
389
372
  };
390
373
  }
374
+ const permissionChecker2 = getService$1("permission-checker").create({
375
+ userAbility: params.state.userAbility,
376
+ model: attributeSchema.target
377
+ });
378
+ const response = await serviceUtils.buildRelationReponse(
379
+ attributeValues,
380
+ attributeSchema
381
+ );
382
+ const sanitizedResults = await Promise.all(
383
+ response.results.map((media) => permissionChecker2.sanitizeOutput(media))
384
+ );
391
385
  return {
392
386
  ...await currentDataWithRelations,
393
- [attributeKey]: await buildRelationReponse(attributeValues, attributeSchema)
387
+ [attributeKey]: {
388
+ results: sanitizedResults,
389
+ meta: response.meta
390
+ }
394
391
  };
395
392
  }
396
393
  return currentDataWithRelations;
@@ -405,7 +402,7 @@ const createHistoryService = ({ strapi: strapi2 }) => {
405
402
  ...result,
406
403
  data: await populateEntryRelations(result),
407
404
  meta: {
408
- unknownAttributes: getSchemaAttributesDiff(
405
+ unknownAttributes: serviceUtils.getSchemaAttributesDiff(
409
406
  result.schema,
410
407
  strapi2.getModel(params.query.contentType).attributes
411
408
  )
@@ -416,13 +413,16 @@ const createHistoryService = ({ strapi: strapi2 }) => {
416
413
  );
417
414
  return {
418
415
  results: formattedResults,
419
- pagination
416
+ pagination: pagination2
420
417
  };
421
418
  },
422
419
  async restoreVersion(versionId) {
423
420
  const version = await query.findOne({ where: { id: versionId } });
424
421
  const contentTypeSchemaAttributes = strapi2.getModel(version.contentType).attributes;
425
- const schemaDiff = getSchemaAttributesDiff(version.schema, contentTypeSchemaAttributes);
422
+ const schemaDiff = serviceUtils.getSchemaAttributesDiff(
423
+ version.schema,
424
+ contentTypeSchemaAttributes
425
+ );
426
426
  const dataWithoutAddedAttributes = Object.keys(schemaDiff.added).reduce(
427
427
  (currentData, addedKey) => {
428
428
  currentData[addedKey] = null;
@@ -435,61 +435,26 @@ const createHistoryService = ({ strapi: strapi2 }) => {
435
435
  FIELDS_TO_IGNORE,
436
436
  contentTypeSchemaAttributes
437
437
  );
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) {
438
+ const reducer = async.reduce(Object.entries(sanitizedSchemaAttributes));
439
+ const dataWithoutMissingRelations = await reducer(
440
+ async (previousRelationAttributes, [name, attribute]) => {
441
+ const versionRelationData = version.data[name];
442
+ if (!versionRelationData) {
443
443
  return previousRelationAttributes;
444
444
  }
445
445
  if (attribute.type === "relation" && // TODO: handle polymorphic relations
446
446
  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
- }
447
+ const data2 = await serviceUtils.getRelationRestoreValue(versionRelationData, attribute);
448
+ previousRelationAttributes[name] = data2;
471
449
  }
472
450
  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
- }
451
+ const data2 = await serviceUtils.getMediaRestoreValue(versionRelationData, attribute);
452
+ previousRelationAttributes[name] = data2;
488
453
  }
489
454
  return previousRelationAttributes;
490
455
  },
491
456
  // Clone to avoid mutating the original version data
492
- Promise.resolve(structuredClone(dataWithoutAddedAttributes))
457
+ structuredClone(dataWithoutAddedAttributes)
493
458
  );
494
459
  const data = omit(["id", ...Object.keys(schemaDiff.removed)], dataWithoutMissingRelations);
495
460
  const restoredDocument = await strapi2.documents(version.contentType).update({
@@ -504,8 +469,118 @@ const createHistoryService = ({ strapi: strapi2 }) => {
504
469
  }
505
470
  };
506
471
  };
472
+ const shouldCreateHistoryVersion = (context) => {
473
+ if (!strapi.requestContext.get()?.request.url.startsWith("/content-manager")) {
474
+ return false;
475
+ }
476
+ if (context.action !== "create" && context.action !== "update" && context.action !== "clone" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
477
+ return false;
478
+ }
479
+ if (context.action === "update" && strapi.requestContext.get()?.request.url.endsWith("/actions/publish")) {
480
+ return false;
481
+ }
482
+ if (!context.contentType.uid.startsWith("api::")) {
483
+ return false;
484
+ }
485
+ return true;
486
+ };
487
+ const getSchemas = (uid2) => {
488
+ const attributesSchema = strapi.getModel(uid2).attributes;
489
+ const componentsSchemas = Object.keys(attributesSchema).reduce(
490
+ (currentComponentSchemas, key) => {
491
+ const fieldSchema = attributesSchema[key];
492
+ if (fieldSchema.type === "component") {
493
+ const componentSchema = strapi.getModel(fieldSchema.component).attributes;
494
+ return {
495
+ ...currentComponentSchemas,
496
+ [fieldSchema.component]: componentSchema
497
+ };
498
+ }
499
+ return currentComponentSchemas;
500
+ },
501
+ {}
502
+ );
503
+ return {
504
+ schema: omit(FIELDS_TO_IGNORE, attributesSchema),
505
+ componentsSchemas
506
+ };
507
+ };
508
+ const createLifecyclesService = ({ strapi: strapi2 }) => {
509
+ const state = {
510
+ deleteExpiredJob: null,
511
+ isInitialized: false
512
+ };
513
+ const serviceUtils = createServiceUtils({ strapi: strapi2 });
514
+ return {
515
+ async bootstrap() {
516
+ if (state.isInitialized) {
517
+ return;
518
+ }
519
+ strapi2.documents.use(async (context, next) => {
520
+ const result = await next();
521
+ if (!shouldCreateHistoryVersion(context)) {
522
+ return result;
523
+ }
524
+ const documentId = context.action === "create" || context.action === "clone" ? result.documentId : context.params.documentId;
525
+ const defaultLocale = await serviceUtils.getDefaultLocale();
526
+ const locales = castArray(context.params?.locale || defaultLocale);
527
+ if (!locales.length) {
528
+ return result;
529
+ }
530
+ const uid2 = context.contentType.uid;
531
+ const schemas = getSchemas(uid2);
532
+ const localeEntries = await strapi2.db.query(uid2).findMany({
533
+ where: {
534
+ documentId,
535
+ locale: { $in: locales },
536
+ publishedAt: null
537
+ },
538
+ populate: serviceUtils.getDeepPopulate(
539
+ uid2,
540
+ true
541
+ /* use database syntax */
542
+ )
543
+ });
544
+ await strapi2.db.transaction(async ({ onCommit }) => {
545
+ onCommit(async () => {
546
+ for (const entry of localeEntries) {
547
+ const status = await serviceUtils.getVersionStatus(uid2, entry);
548
+ await getService(strapi2, "history").createVersion({
549
+ contentType: uid2,
550
+ data: omit(FIELDS_TO_IGNORE, entry),
551
+ relatedDocumentId: documentId,
552
+ locale: entry.locale,
553
+ status,
554
+ ...schemas
555
+ });
556
+ }
557
+ });
558
+ });
559
+ return result;
560
+ });
561
+ state.deleteExpiredJob = scheduleJob("0 0 * * *", () => {
562
+ const retentionDaysInMilliseconds = serviceUtils.getRetentionDays() * 24 * 60 * 60 * 1e3;
563
+ const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
564
+ strapi2.db.query(HISTORY_VERSION_UID).deleteMany({
565
+ where: {
566
+ created_at: {
567
+ $lt: expirationDate.toISOString()
568
+ }
569
+ }
570
+ });
571
+ });
572
+ state.isInitialized = true;
573
+ },
574
+ async destroy() {
575
+ if (state.deleteExpiredJob) {
576
+ state.deleteExpiredJob.cancel();
577
+ }
578
+ }
579
+ };
580
+ };
507
581
  const services$1 = {
508
- history: createHistoryService
582
+ history: createHistoryService,
583
+ lifecycles: createLifecyclesService
509
584
  };
510
585
  const info = { pluginName: "content-manager", type: "admin" };
511
586
  const historyVersionRouter = {
@@ -585,10 +660,10 @@ const getFeature = () => {
585
660
  strapi2.get("models").add(historyVersion);
586
661
  },
587
662
  bootstrap({ strapi: strapi2 }) {
588
- getService(strapi2, "history").bootstrap();
663
+ getService(strapi2, "lifecycles").bootstrap();
589
664
  },
590
665
  destroy({ strapi: strapi2 }) {
591
- getService(strapi2, "history").destroy();
666
+ getService(strapi2, "lifecycles").destroy();
592
667
  },
593
668
  controllers: controllers$1,
594
669
  services: services$1,
@@ -1407,7 +1482,7 @@ const { PaginationError, ValidationError } = errors;
1407
1482
  const TYPES = ["singleType", "collectionType"];
1408
1483
  const kindSchema = yup$1.string().oneOf(TYPES).nullable();
1409
1484
  const bulkActionInputSchema = yup$1.object({
1410
- ids: yup$1.array().of(yup$1.strapiID()).min(1).required()
1485
+ documentIds: yup$1.array().of(yup$1.strapiID()).min(1).required()
1411
1486
  }).required();
1412
1487
  const generateUIDInputSchema = yup$1.object({
1413
1488
  contentTypeUID: yup$1.string().required(),
@@ -1506,15 +1581,49 @@ const excludeNotCreatableFields = (uid2, permissionChecker2) => (body, path = []
1506
1581
  }
1507
1582
  }, body);
1508
1583
  };
1509
- const getDocumentLocaleAndStatus = (request) => {
1510
- 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}`);
1584
+ const singleLocaleSchema = yup$1.string().nullable();
1585
+ const multipleLocaleSchema = yup$1.lazy(
1586
+ (value) => Array.isArray(value) ? yup$1.array().of(singleLocaleSchema.required()) : singleLocaleSchema
1587
+ );
1588
+ const statusSchema = yup$1.mixed().oneOf(["draft", "published"], "Invalid status");
1589
+ const getDocumentLocaleAndStatus = async (request, model, opts = { allowMultipleLocales: false }) => {
1590
+ const { allowMultipleLocales } = opts;
1591
+ const { locale, status: providedStatus, ...rest } = request || {};
1592
+ const defaultStatus = contentTypes$1.hasDraftAndPublish(strapi.getModel(model)) ? void 0 : "published";
1593
+ const status = providedStatus !== void 0 ? providedStatus : defaultStatus;
1594
+ const schema = yup$1.object().shape({
1595
+ locale: allowMultipleLocales ? multipleLocaleSchema : singleLocaleSchema,
1596
+ status: statusSchema
1597
+ });
1598
+ try {
1599
+ await validateYupSchema(schema, { strict: true, abortEarly: false })(request);
1600
+ return { locale, status, ...rest };
1601
+ } catch (error) {
1602
+ throw new errors.ValidationError(`Validation error: ${error.message}`);
1516
1603
  }
1517
- return { locale, status, ...rest };
1604
+ };
1605
+ const formatDocumentWithMetadata = async (permissionChecker2, uid2, document, opts = {}) => {
1606
+ const documentMetadata2 = getService$1("document-metadata");
1607
+ const serviceOutput = await documentMetadata2.formatDocumentWithMetadata(uid2, document, opts);
1608
+ let {
1609
+ meta: { availableLocales, availableStatus }
1610
+ } = serviceOutput;
1611
+ const metadataSanitizer = permissionChecker2.sanitizeOutput;
1612
+ availableLocales = await async.map(
1613
+ availableLocales,
1614
+ async (localeDocument) => metadataSanitizer(localeDocument)
1615
+ );
1616
+ availableStatus = await async.map(
1617
+ availableStatus,
1618
+ async (statusDocument) => metadataSanitizer(statusDocument)
1619
+ );
1620
+ return {
1621
+ ...serviceOutput,
1622
+ meta: {
1623
+ availableLocales,
1624
+ availableStatus
1625
+ }
1626
+ };
1518
1627
  };
1519
1628
  const createDocument = async (ctx, opts) => {
1520
1629
  const { userAbility, user } = ctx.state;
@@ -1529,7 +1638,7 @@ const createDocument = async (ctx, opts) => {
1529
1638
  const setCreator = setCreatorFields({ user });
1530
1639
  const sanitizeFn = async.pipe(pickPermittedFields, setCreator);
1531
1640
  const sanitizedBody = await sanitizeFn(body);
1532
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(body);
1641
+ const { locale, status } = await getDocumentLocaleAndStatus(body, model);
1533
1642
  return documentManager2.create(model, {
1534
1643
  data: sanitizedBody,
1535
1644
  locale,
@@ -1548,7 +1657,7 @@ const updateDocument = async (ctx, opts) => {
1548
1657
  }
1549
1658
  const permissionQuery = await permissionChecker2.sanitizedQuery.update(ctx.query);
1550
1659
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1551
- const { locale } = getDocumentLocaleAndStatus(body);
1660
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1552
1661
  const [documentVersion, documentExists] = await Promise.all([
1553
1662
  documentManager2.findOne(id, model, { populate, locale, status: "draft" }),
1554
1663
  documentManager2.exists(model, id)
@@ -1586,8 +1695,8 @@ const collectionTypes = {
1586
1695
  }
1587
1696
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
1588
1697
  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(
1698
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
1699
+ const { results: documents, pagination: pagination2 } = await documentManager2.findPage(
1591
1700
  { ...permissionQuery, populate, locale, status },
1592
1701
  model
1593
1702
  );
@@ -1608,21 +1717,20 @@ const collectionTypes = {
1608
1717
  );
1609
1718
  ctx.body = {
1610
1719
  results,
1611
- pagination
1720
+ pagination: pagination2
1612
1721
  };
1613
1722
  },
1614
1723
  async findOne(ctx) {
1615
1724
  const { userAbility } = ctx.state;
1616
1725
  const { model, id } = ctx.params;
1617
1726
  const documentManager2 = getService$1("document-manager");
1618
- const documentMetadata2 = getService$1("document-metadata");
1619
1727
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1620
1728
  if (permissionChecker2.cannot.read()) {
1621
1729
  return ctx.forbidden();
1622
1730
  }
1623
1731
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1624
1732
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1625
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
1733
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
1626
1734
  const version = await documentManager2.findOne(id, model, {
1627
1735
  populate,
1628
1736
  locale,
@@ -1633,8 +1741,10 @@ const collectionTypes = {
1633
1741
  if (!exists) {
1634
1742
  return ctx.notFound();
1635
1743
  }
1636
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
1744
+ const { meta } = await formatDocumentWithMetadata(
1745
+ permissionChecker2,
1637
1746
  model,
1747
+ // @ts-expect-error TODO: fix
1638
1748
  { id, locale, publishedAt: null },
1639
1749
  { availableLocales: true, availableStatus: false }
1640
1750
  );
@@ -1645,12 +1755,11 @@ const collectionTypes = {
1645
1755
  return ctx.forbidden();
1646
1756
  }
1647
1757
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
1648
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1758
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1649
1759
  },
1650
1760
  async create(ctx) {
1651
1761
  const { userAbility } = ctx.state;
1652
1762
  const { model } = ctx.params;
1653
- const documentMetadata2 = getService$1("document-metadata");
1654
1763
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1655
1764
  const [totalEntries, document] = await Promise.all([
1656
1765
  strapi.db.query(model).count(),
@@ -1658,7 +1767,7 @@ const collectionTypes = {
1658
1767
  ]);
1659
1768
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
1660
1769
  ctx.status = 201;
1661
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1770
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1662
1771
  // Empty metadata as it's not relevant for a new document
1663
1772
  availableLocales: false,
1664
1773
  availableStatus: false
@@ -1672,25 +1781,23 @@ const collectionTypes = {
1672
1781
  async update(ctx) {
1673
1782
  const { userAbility } = ctx.state;
1674
1783
  const { model } = ctx.params;
1675
- const documentMetadata2 = getService$1("document-metadata");
1676
1784
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1677
1785
  const updatedVersion = await updateDocument(ctx);
1678
1786
  const sanitizedVersion = await permissionChecker2.sanitizeOutput(updatedVersion);
1679
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedVersion);
1787
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedVersion);
1680
1788
  },
1681
1789
  async clone(ctx) {
1682
1790
  const { userAbility, user } = ctx.state;
1683
1791
  const { model, sourceId: id } = ctx.params;
1684
1792
  const { body } = ctx.request;
1685
1793
  const documentManager2 = getService$1("document-manager");
1686
- const documentMetadata2 = getService$1("document-metadata");
1687
1794
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1688
1795
  if (permissionChecker2.cannot.create()) {
1689
1796
  return ctx.forbidden();
1690
1797
  }
1691
1798
  const permissionQuery = await permissionChecker2.sanitizedQuery.create(ctx.query);
1692
1799
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1693
- const { locale } = getDocumentLocaleAndStatus(body);
1800
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1694
1801
  const document = await documentManager2.findOne(id, model, {
1695
1802
  populate,
1696
1803
  locale,
@@ -1706,7 +1813,7 @@ const collectionTypes = {
1706
1813
  const sanitizedBody = await sanitizeFn(body);
1707
1814
  const clonedDocument = await documentManager2.clone(document.documentId, sanitizedBody, model);
1708
1815
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(clonedDocument);
1709
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1816
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1710
1817
  // Empty metadata as it's not relevant for a new document
1711
1818
  availableLocales: false,
1712
1819
  availableStatus: false
@@ -1735,7 +1842,7 @@ const collectionTypes = {
1735
1842
  }
1736
1843
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(ctx.query);
1737
1844
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1738
- const { locale } = getDocumentLocaleAndStatus(ctx.query);
1845
+ const { locale } = await getDocumentLocaleAndStatus(ctx.query, model);
1739
1846
  const documentLocales = await documentManager2.findLocales(id, model, { populate, locale });
1740
1847
  if (documentLocales.length === 0) {
1741
1848
  return ctx.notFound();
@@ -1757,7 +1864,6 @@ const collectionTypes = {
1757
1864
  const { id, model } = ctx.params;
1758
1865
  const { body } = ctx.request;
1759
1866
  const documentManager2 = getService$1("document-manager");
1760
- const documentMetadata2 = getService$1("document-metadata");
1761
1867
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1762
1868
  if (permissionChecker2.cannot.publish()) {
1763
1869
  return ctx.forbidden();
@@ -1765,25 +1871,46 @@ const collectionTypes = {
1765
1871
  const publishedDocument = await strapi.db.transaction(async () => {
1766
1872
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1767
1873
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1768
- const document = id ? await updateDocument(ctx, { populate }) : await createDocument(ctx, { populate });
1874
+ let document;
1875
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1876
+ const isCreate = isNil$1(id);
1877
+ if (isCreate) {
1878
+ if (permissionChecker2.cannot.create()) {
1879
+ throw new errors.ForbiddenError();
1880
+ }
1881
+ document = await createDocument(ctx, { populate });
1882
+ }
1883
+ const isUpdate = !isCreate;
1884
+ if (isUpdate) {
1885
+ document = await documentManager2.findOne(id, model, { populate, locale });
1886
+ if (!document) {
1887
+ throw new errors.NotFoundError("Document not found");
1888
+ }
1889
+ if (permissionChecker2.can.update(document)) {
1890
+ await updateDocument(ctx);
1891
+ }
1892
+ }
1769
1893
  if (permissionChecker2.cannot.publish(document)) {
1770
1894
  throw new errors.ForbiddenError();
1771
1895
  }
1772
- const { locale } = getDocumentLocaleAndStatus(body);
1773
- return documentManager2.publish(document.documentId, model, {
1896
+ const publishResult = await documentManager2.publish(document.documentId, model, {
1774
1897
  locale
1775
1898
  // TODO: Allow setting creator fields on publish
1776
1899
  // data: setCreatorFields({ user, isEdition: true })({}),
1777
1900
  });
1901
+ if (!publishResult || publishResult.length === 0) {
1902
+ throw new errors.NotFoundError("Document not found or already published.");
1903
+ }
1904
+ return publishResult[0];
1778
1905
  });
1779
1906
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
1780
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1907
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1781
1908
  },
1782
1909
  async bulkPublish(ctx) {
1783
1910
  const { userAbility } = ctx.state;
1784
1911
  const { model } = ctx.params;
1785
1912
  const { body } = ctx.request;
1786
- const { ids } = body;
1913
+ const { documentIds } = body;
1787
1914
  await validateBulkActionInput(body);
1788
1915
  const documentManager2 = getService$1("document-manager");
1789
1916
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1792,8 +1919,13 @@ const collectionTypes = {
1792
1919
  }
1793
1920
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1794
1921
  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);
1922
+ const { locale } = await getDocumentLocaleAndStatus(body, model, {
1923
+ allowMultipleLocales: true
1924
+ });
1925
+ const entityPromises = documentIds.map(
1926
+ (documentId) => documentManager2.findLocales(documentId, model, { populate, locale, isPublished: false })
1927
+ );
1928
+ const entities = (await Promise.all(entityPromises)).flat();
1797
1929
  for (const entity of entities) {
1798
1930
  if (!entity) {
1799
1931
  return ctx.notFound();
@@ -1802,24 +1934,25 @@ const collectionTypes = {
1802
1934
  return ctx.forbidden();
1803
1935
  }
1804
1936
  }
1805
- const { count } = await documentManager2.publishMany(entities, model);
1937
+ const count = await documentManager2.publishMany(model, documentIds, locale);
1806
1938
  ctx.body = { count };
1807
1939
  },
1808
1940
  async bulkUnpublish(ctx) {
1809
1941
  const { userAbility } = ctx.state;
1810
1942
  const { model } = ctx.params;
1811
1943
  const { body } = ctx.request;
1812
- const { ids } = body;
1944
+ const { documentIds } = body;
1813
1945
  await validateBulkActionInput(body);
1814
1946
  const documentManager2 = getService$1("document-manager");
1815
1947
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1816
1948
  if (permissionChecker2.cannot.unpublish()) {
1817
1949
  return ctx.forbidden();
1818
1950
  }
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);
1951
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1952
+ const entityPromises = documentIds.map(
1953
+ (documentId) => documentManager2.findLocales(documentId, model, { locale, isPublished: true })
1954
+ );
1955
+ const entities = (await Promise.all(entityPromises)).flat();
1823
1956
  for (const entity of entities) {
1824
1957
  if (!entity) {
1825
1958
  return ctx.notFound();
@@ -1828,7 +1961,8 @@ const collectionTypes = {
1828
1961
  return ctx.forbidden();
1829
1962
  }
1830
1963
  }
1831
- const { count } = await documentManager2.unpublishMany(entities, model);
1964
+ const entitiesIds = entities.map((document) => document.documentId);
1965
+ const { count } = await documentManager2.unpublishMany(entitiesIds, model, { locale });
1832
1966
  ctx.body = { count };
1833
1967
  },
1834
1968
  async unpublish(ctx) {
@@ -1838,7 +1972,6 @@ const collectionTypes = {
1838
1972
  body: { discardDraft, ...body }
1839
1973
  } = ctx.request;
1840
1974
  const documentManager2 = getService$1("document-manager");
1841
- const documentMetadata2 = getService$1("document-metadata");
1842
1975
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1843
1976
  if (permissionChecker2.cannot.unpublish()) {
1844
1977
  return ctx.forbidden();
@@ -1848,7 +1981,7 @@ const collectionTypes = {
1848
1981
  }
1849
1982
  const permissionQuery = await permissionChecker2.sanitizedQuery.unpublish(ctx.query);
1850
1983
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1851
- const { locale } = getDocumentLocaleAndStatus(body);
1984
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1852
1985
  const document = await documentManager2.findOne(id, model, {
1853
1986
  populate,
1854
1987
  locale,
@@ -1870,7 +2003,7 @@ const collectionTypes = {
1870
2003
  ctx.body = await async.pipe(
1871
2004
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
1872
2005
  permissionChecker2.sanitizeOutput,
1873
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2006
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1874
2007
  )(document);
1875
2008
  });
1876
2009
  },
@@ -1879,14 +2012,13 @@ const collectionTypes = {
1879
2012
  const { id, model } = ctx.params;
1880
2013
  const { body } = ctx.request;
1881
2014
  const documentManager2 = getService$1("document-manager");
1882
- const documentMetadata2 = getService$1("document-metadata");
1883
2015
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1884
2016
  if (permissionChecker2.cannot.discard()) {
1885
2017
  return ctx.forbidden();
1886
2018
  }
1887
2019
  const permissionQuery = await permissionChecker2.sanitizedQuery.discard(ctx.query);
1888
2020
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1889
- const { locale } = getDocumentLocaleAndStatus(body);
2021
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1890
2022
  const document = await documentManager2.findOne(id, model, {
1891
2023
  populate,
1892
2024
  locale,
@@ -1901,14 +2033,14 @@ const collectionTypes = {
1901
2033
  ctx.body = await async.pipe(
1902
2034
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
1903
2035
  permissionChecker2.sanitizeOutput,
1904
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2036
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1905
2037
  )(document);
1906
2038
  },
1907
2039
  async bulkDelete(ctx) {
1908
2040
  const { userAbility } = ctx.state;
1909
2041
  const { model } = ctx.params;
1910
2042
  const { query, body } = ctx.request;
1911
- const { ids } = body;
2043
+ const { documentIds } = body;
1912
2044
  await validateBulkActionInput(body);
1913
2045
  const documentManager2 = getService$1("document-manager");
1914
2046
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1916,14 +2048,22 @@ const collectionTypes = {
1916
2048
  return ctx.forbidden();
1917
2049
  }
1918
2050
  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 || [])
2051
+ const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2052
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2053
+ const documentLocales = await documentManager2.findLocales(documentIds, model, {
2054
+ populate,
2055
+ locale
2056
+ });
2057
+ if (documentLocales.length === 0) {
2058
+ return ctx.notFound();
2059
+ }
2060
+ for (const document of documentLocales) {
2061
+ if (permissionChecker2.cannot.delete(document)) {
2062
+ return ctx.forbidden();
1924
2063
  }
1925
- };
1926
- const { count } = await documentManager2.deleteMany(params, model);
2064
+ }
2065
+ const localeDocumentsIds = documentLocales.map((document) => document.documentId);
2066
+ const { count } = await documentManager2.deleteMany(localeDocumentsIds, model, { locale });
1927
2067
  ctx.body = { count };
1928
2068
  },
1929
2069
  async countDraftRelations(ctx) {
@@ -1936,7 +2076,7 @@ const collectionTypes = {
1936
2076
  }
1937
2077
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1938
2078
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1939
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
2079
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
1940
2080
  const entity = await documentManager2.findOne(id, model, { populate, locale, status });
1941
2081
  if (!entity) {
1942
2082
  return ctx.notFound();
@@ -1951,7 +2091,7 @@ const collectionTypes = {
1951
2091
  },
1952
2092
  async countManyEntriesDraftRelations(ctx) {
1953
2093
  const { userAbility } = ctx.state;
1954
- const ids = ctx.request.query.ids;
2094
+ const ids = ctx.request.query.documentIds;
1955
2095
  const locale = ctx.request.query.locale;
1956
2096
  const { model } = ctx.params;
1957
2097
  const documentManager2 = getService$1("document-manager");
@@ -1959,16 +2099,16 @@ const collectionTypes = {
1959
2099
  if (permissionChecker2.cannot.read()) {
1960
2100
  return ctx.forbidden();
1961
2101
  }
1962
- const entities = await documentManager2.findMany(
2102
+ const documents = await documentManager2.findMany(
1963
2103
  {
1964
2104
  filters: {
1965
- id: ids
2105
+ documentId: ids
1966
2106
  },
1967
2107
  locale
1968
2108
  },
1969
2109
  model
1970
2110
  );
1971
- if (!entities) {
2111
+ if (!documents) {
1972
2112
  return ctx.notFound();
1973
2113
  }
1974
2114
  const number = await documentManager2.countManyEntriesDraftRelations(ids, model, locale);
@@ -2464,7 +2604,7 @@ const createOrUpdateDocument = async (ctx, opts) => {
2464
2604
  throw new errors.ForbiddenError();
2465
2605
  }
2466
2606
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.update(query);
2467
- const { locale } = getDocumentLocaleAndStatus(body);
2607
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2468
2608
  const [documentVersion, otherDocumentVersion] = await Promise.all([
2469
2609
  findDocument(sanitizedQuery, model, { locale, status: "draft" }),
2470
2610
  // Find the first document to check if it exists
@@ -2501,12 +2641,11 @@ const singleTypes = {
2501
2641
  const { model } = ctx.params;
2502
2642
  const { query = {} } = ctx.request;
2503
2643
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2504
- const documentMetadata2 = getService$1("document-metadata");
2505
2644
  if (permissionChecker2.cannot.read()) {
2506
2645
  return ctx.forbidden();
2507
2646
  }
2508
2647
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
2509
- const { locale, status } = getDocumentLocaleAndStatus(query);
2648
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
2510
2649
  const version = await findDocument(permissionQuery, model, { locale, status });
2511
2650
  if (!version) {
2512
2651
  if (permissionChecker2.cannot.create()) {
@@ -2516,8 +2655,10 @@ const singleTypes = {
2516
2655
  if (!document) {
2517
2656
  return ctx.notFound();
2518
2657
  }
2519
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
2658
+ const { meta } = await formatDocumentWithMetadata(
2659
+ permissionChecker2,
2520
2660
  model,
2661
+ // @ts-expect-error - fix types
2521
2662
  { id: document.documentId, locale, publishedAt: null },
2522
2663
  { availableLocales: true, availableStatus: false }
2523
2664
  );
@@ -2528,16 +2669,15 @@ const singleTypes = {
2528
2669
  return ctx.forbidden();
2529
2670
  }
2530
2671
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
2531
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2672
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2532
2673
  },
2533
2674
  async createOrUpdate(ctx) {
2534
2675
  const { userAbility } = ctx.state;
2535
2676
  const { model } = ctx.params;
2536
- const documentMetadata2 = getService$1("document-metadata");
2537
2677
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2538
2678
  const document = await createOrUpdateDocument(ctx);
2539
2679
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
2540
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2680
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2541
2681
  },
2542
2682
  async delete(ctx) {
2543
2683
  const { userAbility } = ctx.state;
@@ -2550,7 +2690,7 @@ const singleTypes = {
2550
2690
  }
2551
2691
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.delete(query);
2552
2692
  const populate = await buildPopulateFromQuery(sanitizedQuery, model);
2553
- const { locale } = getDocumentLocaleAndStatus(query);
2693
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2554
2694
  const documentLocales = await documentManager2.findLocales(void 0, model, {
2555
2695
  populate,
2556
2696
  locale
@@ -2573,7 +2713,6 @@ const singleTypes = {
2573
2713
  const { model } = ctx.params;
2574
2714
  const { query = {} } = ctx.request;
2575
2715
  const documentManager2 = getService$1("document-manager");
2576
- const documentMetadata2 = getService$1("document-metadata");
2577
2716
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2578
2717
  if (permissionChecker2.cannot.publish()) {
2579
2718
  return ctx.forbidden();
@@ -2588,11 +2727,12 @@ const singleTypes = {
2588
2727
  if (permissionChecker2.cannot.publish(document)) {
2589
2728
  throw new errors.ForbiddenError();
2590
2729
  }
2591
- const { locale } = getDocumentLocaleAndStatus(document);
2592
- return documentManager2.publish(document.documentId, model, { locale });
2730
+ const { locale } = await getDocumentLocaleAndStatus(document, model);
2731
+ const publishResult = await documentManager2.publish(document.documentId, model, { locale });
2732
+ return publishResult.at(0);
2593
2733
  });
2594
2734
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
2595
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2735
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2596
2736
  },
2597
2737
  async unpublish(ctx) {
2598
2738
  const { userAbility } = ctx.state;
@@ -2602,7 +2742,6 @@ const singleTypes = {
2602
2742
  query = {}
2603
2743
  } = ctx.request;
2604
2744
  const documentManager2 = getService$1("document-manager");
2605
- const documentMetadata2 = getService$1("document-metadata");
2606
2745
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2607
2746
  if (permissionChecker2.cannot.unpublish()) {
2608
2747
  return ctx.forbidden();
@@ -2611,7 +2750,7 @@ const singleTypes = {
2611
2750
  return ctx.forbidden();
2612
2751
  }
2613
2752
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.unpublish(query);
2614
- const { locale } = getDocumentLocaleAndStatus(body);
2753
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2615
2754
  const document = await findDocument(sanitizedQuery, model, { locale });
2616
2755
  if (!document) {
2617
2756
  return ctx.notFound();
@@ -2629,7 +2768,7 @@ const singleTypes = {
2629
2768
  ctx.body = await async.pipe(
2630
2769
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
2631
2770
  permissionChecker2.sanitizeOutput,
2632
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2771
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2633
2772
  )(document);
2634
2773
  });
2635
2774
  },
@@ -2638,13 +2777,12 @@ const singleTypes = {
2638
2777
  const { model } = ctx.params;
2639
2778
  const { body, query = {} } = ctx.request;
2640
2779
  const documentManager2 = getService$1("document-manager");
2641
- const documentMetadata2 = getService$1("document-metadata");
2642
2780
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2643
2781
  if (permissionChecker2.cannot.discard()) {
2644
2782
  return ctx.forbidden();
2645
2783
  }
2646
2784
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.discard(query);
2647
- const { locale } = getDocumentLocaleAndStatus(body);
2785
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2648
2786
  const document = await findDocument(sanitizedQuery, model, { locale, status: "published" });
2649
2787
  if (!document) {
2650
2788
  return ctx.notFound();
@@ -2655,7 +2793,7 @@ const singleTypes = {
2655
2793
  ctx.body = await async.pipe(
2656
2794
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
2657
2795
  permissionChecker2.sanitizeOutput,
2658
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2796
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2659
2797
  )(document);
2660
2798
  },
2661
2799
  async countDraftRelations(ctx) {
@@ -2664,7 +2802,7 @@ const singleTypes = {
2664
2802
  const { query } = ctx.request;
2665
2803
  const documentManager2 = getService$1("document-manager");
2666
2804
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2667
- const { locale } = getDocumentLocaleAndStatus(query);
2805
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2668
2806
  if (permissionChecker2.cannot.read()) {
2669
2807
  return ctx.forbidden();
2670
2808
  }
@@ -2685,7 +2823,7 @@ const uid$1 = {
2685
2823
  async generateUID(ctx) {
2686
2824
  const { contentTypeUID, field, data } = await validateGenerateUIDInput(ctx.request.body);
2687
2825
  const { query = {} } = ctx.request;
2688
- const { locale } = getDocumentLocaleAndStatus(query);
2826
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2689
2827
  await validateUIDField(contentTypeUID, field);
2690
2828
  const uidService = getService$1("uid");
2691
2829
  ctx.body = {
@@ -2697,7 +2835,7 @@ const uid$1 = {
2697
2835
  ctx.request.body
2698
2836
  );
2699
2837
  const { query = {} } = ctx.request;
2700
- const { locale } = getDocumentLocaleAndStatus(query);
2838
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2701
2839
  await validateUIDField(contentTypeUID, field);
2702
2840
  const uidService = getService$1("uid");
2703
2841
  const isAvailable = await uidService.checkUIDAvailability({
@@ -3488,7 +3626,7 @@ const permission = ({ strapi: strapi2 }) => ({
3488
3626
  await strapi2.service("admin::permission").actionProvider.registerMany(actions);
3489
3627
  }
3490
3628
  });
3491
- const { isVisibleAttribute: isVisibleAttribute$1 } = strapiUtils.contentTypes;
3629
+ const { isVisibleAttribute: isVisibleAttribute$1, isScalarAttribute, getDoesAttributeRequireValidation } = strapiUtils.contentTypes;
3492
3630
  const { isAnyToMany } = strapiUtils.relations;
3493
3631
  const { PUBLISHED_AT_ATTRIBUTE: PUBLISHED_AT_ATTRIBUTE$1 } = strapiUtils.contentTypes.constants;
3494
3632
  const isMorphToRelation = (attribute) => isRelation(attribute) && attribute.relation.includes("morphTo");
@@ -3579,6 +3717,42 @@ const getDeepPopulate = (uid2, {
3579
3717
  {}
3580
3718
  );
3581
3719
  };
3720
+ const getValidatableFieldsPopulate = (uid2, {
3721
+ initialPopulate = {},
3722
+ countMany = false,
3723
+ countOne = false,
3724
+ maxLevel = Infinity
3725
+ } = {}, level = 1) => {
3726
+ if (level > maxLevel) {
3727
+ return {};
3728
+ }
3729
+ const model = strapi.getModel(uid2);
3730
+ return Object.entries(model.attributes).reduce((populateAcc, [attributeName, attribute]) => {
3731
+ if (!getDoesAttributeRequireValidation(attribute)) {
3732
+ return populateAcc;
3733
+ }
3734
+ if (isScalarAttribute(attribute)) {
3735
+ return merge(populateAcc, {
3736
+ [attributeName]: true
3737
+ });
3738
+ }
3739
+ return merge(
3740
+ populateAcc,
3741
+ getPopulateFor(
3742
+ attributeName,
3743
+ model,
3744
+ {
3745
+ // @ts-expect-error - improve types
3746
+ initialPopulate: initialPopulate?.[attributeName],
3747
+ countMany,
3748
+ countOne,
3749
+ maxLevel
3750
+ },
3751
+ level
3752
+ )
3753
+ );
3754
+ }, {});
3755
+ };
3582
3756
  const getDeepPopulateDraftCount = (uid2) => {
3583
3757
  const model = strapi.getModel(uid2);
3584
3758
  let hasRelations = false;
@@ -3586,6 +3760,10 @@ const getDeepPopulateDraftCount = (uid2) => {
3586
3760
  const attribute = model.attributes[attributeName];
3587
3761
  switch (attribute.type) {
3588
3762
  case "relation": {
3763
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
3764
+ if (isMorphRelation) {
3765
+ break;
3766
+ }
3589
3767
  if (isVisibleAttribute$1(model, attributeName)) {
3590
3768
  populateAcc[attributeName] = {
3591
3769
  count: true,
@@ -3600,22 +3778,24 @@ const getDeepPopulateDraftCount = (uid2) => {
3600
3778
  attribute.component
3601
3779
  );
3602
3780
  if (childHasRelations) {
3603
- populateAcc[attributeName] = { populate: populate2 };
3781
+ populateAcc[attributeName] = {
3782
+ populate: populate2
3783
+ };
3604
3784
  hasRelations = true;
3605
3785
  }
3606
3786
  break;
3607
3787
  }
3608
3788
  case "dynamiczone": {
3609
- const dzPopulate = (attribute.components || []).reduce((acc, componentUID) => {
3610
- const { populate: populate2, hasRelations: childHasRelations } = getDeepPopulateDraftCount(componentUID);
3611
- if (childHasRelations) {
3789
+ const dzPopulateFragment = attribute.components?.reduce((acc, componentUID) => {
3790
+ const { populate: componentPopulate, hasRelations: componentHasRelations } = getDeepPopulateDraftCount(componentUID);
3791
+ if (componentHasRelations) {
3612
3792
  hasRelations = true;
3613
- return merge(acc, populate2);
3793
+ return { ...acc, [componentUID]: { populate: componentPopulate } };
3614
3794
  }
3615
3795
  return acc;
3616
3796
  }, {});
3617
- if (!isEmpty(dzPopulate)) {
3618
- populateAcc[attributeName] = { populate: dzPopulate };
3797
+ if (!isEmpty(dzPopulateFragment)) {
3798
+ populateAcc[attributeName] = { on: dzPopulateFragment };
3619
3799
  }
3620
3800
  break;
3621
3801
  }
@@ -3807,41 +3987,70 @@ const AVAILABLE_STATUS_FIELDS = [
3807
3987
  "updatedBy",
3808
3988
  "status"
3809
3989
  ];
3810
- const AVAILABLE_LOCALES_FIELDS = ["id", "locale", "updatedAt", "createdAt", "status"];
3990
+ const AVAILABLE_LOCALES_FIELDS = [
3991
+ "id",
3992
+ "locale",
3993
+ "updatedAt",
3994
+ "createdAt",
3995
+ "status",
3996
+ "publishedAt",
3997
+ "documentId"
3998
+ ];
3811
3999
  const CONTENT_MANAGER_STATUS = {
3812
4000
  PUBLISHED: "published",
3813
4001
  DRAFT: "draft",
3814
4002
  MODIFIED: "modified"
3815
4003
  };
3816
- const areDatesEqual = (date1, date2, threshold) => {
3817
- if (!date1 || !date2) {
4004
+ const getIsVersionLatestModification = (version, otherVersion) => {
4005
+ if (!version || !version.updatedAt) {
3818
4006
  return false;
3819
4007
  }
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;
4008
+ const versionUpdatedAt = version?.updatedAt ? new Date(version.updatedAt).getTime() : 0;
4009
+ const otherUpdatedAt = otherVersion?.updatedAt ? new Date(otherVersion.updatedAt).getTime() : 0;
4010
+ return versionUpdatedAt > otherUpdatedAt;
3824
4011
  };
3825
4012
  const documentMetadata = ({ strapi: strapi2 }) => ({
3826
4013
  /**
3827
4014
  * Returns available locales of a document for the current status
3828
4015
  */
3829
- getAvailableLocales(uid2, version, allVersions) {
4016
+ async getAvailableLocales(uid2, version, allVersions, validatableFields = []) {
3830
4017
  const versionsByLocale = groupBy("locale", allVersions);
3831
4018
  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]);
4019
+ const model = strapi2.getModel(uid2);
4020
+ const keysToKeep = [...AVAILABLE_LOCALES_FIELDS, ...validatableFields];
4021
+ const traversalFunction = async (localeVersion) => traverseEntity(
4022
+ ({ key }, { remove }) => {
4023
+ if (keysToKeep.includes(key)) {
4024
+ return;
4025
+ }
4026
+ remove(key);
4027
+ },
4028
+ { schema: model, getModel: strapi2.getModel.bind(strapi2) },
4029
+ // @ts-expect-error fix types DocumentVersion incompatible with Data
4030
+ localeVersion
4031
+ );
4032
+ const mappingResult = await async.map(
4033
+ Object.values(versionsByLocale),
4034
+ async (localeVersions) => {
4035
+ const mappedLocaleVersions = await async.map(
4036
+ localeVersions,
4037
+ traversalFunction
4038
+ );
4039
+ if (!contentTypes$1.hasDraftAndPublish(model)) {
4040
+ return mappedLocaleVersions[0];
4041
+ }
4042
+ const draftVersion = mappedLocaleVersions.find((v) => v.publishedAt === null);
4043
+ const otherVersions = mappedLocaleVersions.filter((v) => v.id !== draftVersion?.id);
4044
+ if (!draftVersion) {
4045
+ return;
4046
+ }
4047
+ return {
4048
+ ...draftVersion,
4049
+ status: this.getStatus(draftVersion, otherVersions)
4050
+ };
3835
4051
  }
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);
4052
+ );
4053
+ return mappingResult.filter(Boolean);
3845
4054
  },
3846
4055
  /**
3847
4056
  * Returns available status of a document for the current locale
@@ -3879,26 +4088,37 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3879
4088
  });
3880
4089
  },
3881
4090
  getStatus(version, otherDocumentStatuses) {
3882
- const isDraft = version.publishedAt === null;
3883
- if (!otherDocumentStatuses?.length) {
3884
- return isDraft ? CONTENT_MANAGER_STATUS.DRAFT : CONTENT_MANAGER_STATUS.PUBLISHED;
4091
+ let draftVersion;
4092
+ let publishedVersion;
4093
+ if (version.publishedAt) {
4094
+ publishedVersion = version;
4095
+ } else {
4096
+ draftVersion = version;
3885
4097
  }
3886
- if (isDraft) {
3887
- const publishedVersion = otherDocumentStatuses?.find((d) => d.publishedAt !== null);
3888
- if (!publishedVersion) {
3889
- return CONTENT_MANAGER_STATUS.DRAFT;
3890
- }
4098
+ const otherVersion = otherDocumentStatuses?.at(0);
4099
+ if (otherVersion?.publishedAt) {
4100
+ publishedVersion = otherVersion;
4101
+ } else if (otherVersion) {
4102
+ draftVersion = otherVersion;
3891
4103
  }
3892
- if (areDatesEqual(version.updatedAt, otherDocumentStatuses.at(0)?.updatedAt, 500)) {
4104
+ if (!draftVersion)
3893
4105
  return CONTENT_MANAGER_STATUS.PUBLISHED;
3894
- }
3895
- return CONTENT_MANAGER_STATUS.MODIFIED;
4106
+ if (!publishedVersion)
4107
+ return CONTENT_MANAGER_STATUS.DRAFT;
4108
+ const isDraftModified = getIsVersionLatestModification(draftVersion, publishedVersion);
4109
+ return isDraftModified ? CONTENT_MANAGER_STATUS.MODIFIED : CONTENT_MANAGER_STATUS.PUBLISHED;
3896
4110
  },
4111
+ // TODO is it necessary to return metadata on every page of the CM
4112
+ // We could refactor this so the locales are only loaded when they're
4113
+ // needed. e.g. in the bulk locale action modal.
3897
4114
  async getMetadata(uid2, version, { availableLocales = true, availableStatus = true } = {}) {
4115
+ const populate = getValidatableFieldsPopulate(uid2);
3898
4116
  const versions = await strapi2.db.query(uid2).findMany({
3899
4117
  where: { documentId: version.documentId },
3900
- select: ["createdAt", "updatedAt", "locale", "publishedAt", "documentId"],
3901
4118
  populate: {
4119
+ // Populate only fields that require validation for bulk locale actions
4120
+ ...populate,
4121
+ // NOTE: creator fields are selected in this way to avoid exposing sensitive data
3902
4122
  createdBy: {
3903
4123
  select: ["id", "firstname", "lastname", "email"]
3904
4124
  },
@@ -3907,7 +4127,7 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3907
4127
  }
3908
4128
  }
3909
4129
  });
3910
- const availableLocalesResult = availableLocales ? this.getAvailableLocales(uid2, version, versions) : [];
4130
+ const availableLocalesResult = availableLocales ? await this.getAvailableLocales(uid2, version, versions, Object.keys(populate)) : [];
3911
4131
  const availableStatusResult = availableStatus ? this.getAvailableStatus(version, versions) : null;
3912
4132
  return {
3913
4133
  availableLocales: availableLocalesResult,
@@ -3920,8 +4140,15 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3920
4140
  * - Available status of the document for the current locale
3921
4141
  */
3922
4142
  async formatDocumentWithMetadata(uid2, document, opts = {}) {
3923
- if (!document)
3924
- return document;
4143
+ if (!document) {
4144
+ return {
4145
+ data: document,
4146
+ meta: {
4147
+ availableLocales: [],
4148
+ availableStatus: []
4149
+ }
4150
+ };
4151
+ }
3925
4152
  const hasDraftAndPublish = contentTypes$1.hasDraftAndPublish(strapi2.getModel(uid2));
3926
4153
  if (!hasDraftAndPublish) {
3927
4154
  opts.availableStatus = false;
@@ -3971,26 +4198,9 @@ const sumDraftCounts = (entity, uid2) => {
3971
4198
  }, 0);
3972
4199
  };
3973
4200
  const { ApplicationError } = errors;
3974
- const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = ALLOWED_WEBHOOK_EVENTS;
3975
4201
  const { PUBLISHED_AT_ATTRIBUTE } = contentTypes$1.constants;
3976
4202
  const omitPublishedAtField = omit(PUBLISHED_AT_ATTRIBUTE);
3977
4203
  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
4204
  const documentManager = ({ strapi: strapi2 }) => {
3995
4205
  return {
3996
4206
  async findOne(id, uid2, opts = {}) {
@@ -4009,6 +4219,9 @@ const documentManager = ({ strapi: strapi2 }) => {
4009
4219
  } else if (opts.locale && opts.locale !== "*") {
4010
4220
  where.locale = opts.locale;
4011
4221
  }
4222
+ if (typeof opts.isPublished === "boolean") {
4223
+ where.publishedAt = { $notNull: opts.isPublished };
4224
+ }
4012
4225
  return strapi2.db.query(uid2).findMany({ populate: opts.populate, where });
4013
4226
  },
4014
4227
  async findMany(opts, uid2) {
@@ -4016,20 +4229,16 @@ const documentManager = ({ strapi: strapi2 }) => {
4016
4229
  return strapi2.documents(uid2).findMany(params);
4017
4230
  },
4018
4231
  async findPage(opts, uid2) {
4019
- const page = Number(opts?.page) || 1;
4020
- const pageSize = Number(opts?.pageSize) || 10;
4232
+ const params = pagination.withDefaultPagination(opts || {}, {
4233
+ maxLimit: 1e3
4234
+ });
4021
4235
  const [documents, total = 0] = await Promise.all([
4022
- strapi2.documents(uid2).findMany(opts),
4023
- strapi2.documents(uid2).count(opts)
4236
+ strapi2.documents(uid2).findMany(params),
4237
+ strapi2.documents(uid2).count(params)
4024
4238
  ]);
4025
4239
  return {
4026
4240
  results: documents,
4027
- pagination: {
4028
- page,
4029
- pageSize,
4030
- pageCount: Math.ceil(total / pageSize),
4031
- total
4032
- }
4241
+ pagination: pagination.transformPagedPaginationInfo(params, total)
4033
4242
  };
4034
4243
  },
4035
4244
  async create(uid2, opts = {}) {
@@ -4046,10 +4255,7 @@ const documentManager = ({ strapi: strapi2 }) => {
4046
4255
  async clone(id, body, uid2) {
4047
4256
  const populate = await buildDeepPopulate(uid2);
4048
4257
  const params = {
4049
- data: {
4050
- ...omitIdField(body),
4051
- [PUBLISHED_AT_ATTRIBUTE]: null
4052
- },
4258
+ data: omitIdField(body),
4053
4259
  populate
4054
4260
  };
4055
4261
  return strapi2.documents(uid2).clone({ ...params, documentId: id }).then((result) => result?.entries.at(0));
@@ -4075,70 +4281,36 @@ const documentManager = ({ strapi: strapi2 }) => {
4075
4281
  return {};
4076
4282
  },
4077
4283
  // 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 };
4284
+ async deleteMany(documentIds, uid2, opts = {}) {
4285
+ const deletedEntries = await strapi2.db.transaction(async () => {
4286
+ return Promise.all(documentIds.map(async (id) => this.delete(id, uid2, opts)));
4287
+ });
4288
+ return { count: deletedEntries.length };
4084
4289
  },
4085
4290
  async publish(id, uid2, opts = {}) {
4086
4291
  const populate = await buildDeepPopulate(uid2);
4087
4292
  const params = { ...opts, populate };
4088
- return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries.at(0));
4293
+ return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries);
4089
4294
  },
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
4295
+ async publishMany(uid2, documentIds, locale) {
4296
+ return strapi2.db.transaction(async () => {
4297
+ const results = await Promise.all(
4298
+ documentIds.map((documentId) => this.publish(documentId, uid2, { locale }))
4299
+ );
4300
+ const publishedEntitiesCount = results.flat().filter(Boolean).length;
4301
+ return publishedEntitiesCount;
4116
4302
  });
4117
- await Promise.all(
4118
- publishedEntities.map((doc) => emitEvent(uid2, ENTRY_PUBLISH, doc))
4119
- );
4120
- return publishedEntitiesCount;
4121
4303
  },
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
4304
+ async unpublishMany(documentIds, uid2, opts = {}) {
4305
+ const unpublishedEntries = await strapi2.db.transaction(async () => {
4306
+ return Promise.all(
4307
+ documentIds.map(
4308
+ (id) => strapi2.documents(uid2).unpublish({ ...opts, documentId: id }).then((result) => result?.entries)
4309
+ )
4310
+ );
4137
4311
  });
4138
- await Promise.all(
4139
- unpublishedEntities.map((doc) => emitEvent(uid2, ENTRY_UNPUBLISH, doc))
4140
- );
4141
- return unpublishedEntitiesCount;
4312
+ const unpublishedEntitiesCount = unpublishedEntries.flat().filter(Boolean).length;
4313
+ return { count: unpublishedEntitiesCount };
4142
4314
  },
4143
4315
  async unpublish(id, uid2, opts = {}) {
4144
4316
  const populate = await buildDeepPopulate(uid2);
@@ -4163,16 +4335,20 @@ const documentManager = ({ strapi: strapi2 }) => {
4163
4335
  }
4164
4336
  return sumDraftCounts(document, uid2);
4165
4337
  },
4166
- async countManyEntriesDraftRelations(ids, uid2, locale) {
4338
+ async countManyEntriesDraftRelations(documentIds, uid2, locale) {
4167
4339
  const { populate, hasRelations } = getDeepPopulateDraftCount(uid2);
4168
4340
  if (!hasRelations) {
4169
4341
  return 0;
4170
4342
  }
4343
+ let localeFilter = {};
4344
+ if (locale) {
4345
+ localeFilter = Array.isArray(locale) ? { locale: { $in: locale } } : { locale };
4346
+ }
4171
4347
  const entities = await strapi2.db.query(uid2).findMany({
4172
4348
  populate,
4173
4349
  where: {
4174
- id: { $in: ids },
4175
- ...locale ? { locale } : {}
4350
+ documentId: { $in: documentIds },
4351
+ ...localeFilter
4176
4352
  }
4177
4353
  });
4178
4354
  const totalNumberDraftRelations = entities.reduce(