@strapi/content-manager 5.0.0-beta.6 → 5.0.0-beta.8

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 (175) hide show
  1. package/dist/_chunks/{CardDragPreview-DSVYodBX.js → CardDragPreview-C0QyJgRA.js} +10 -14
  2. package/dist/_chunks/CardDragPreview-C0QyJgRA.js.map +1 -0
  3. package/dist/_chunks/{CardDragPreview-ikSG4M46.mjs → CardDragPreview-DOxamsuj.mjs} +7 -9
  4. package/dist/_chunks/CardDragPreview-DOxamsuj.mjs.map +1 -0
  5. package/dist/_chunks/{ComponentConfigurationPage--2aLCv-G.mjs → ComponentConfigurationPage-CuWgXugY.mjs} +3 -3
  6. package/dist/_chunks/{ComponentConfigurationPage--2aLCv-G.mjs.map → ComponentConfigurationPage-CuWgXugY.mjs.map} +1 -1
  7. package/dist/_chunks/{ComponentConfigurationPage-43KmCNQE.js → ComponentConfigurationPage-by0e_kNd.js} +3 -3
  8. package/dist/_chunks/{ComponentConfigurationPage-43KmCNQE.js.map → ComponentConfigurationPage-by0e_kNd.js.map} +1 -1
  9. package/dist/_chunks/{ComponentIcon-BBQsYCVn.js → ComponentIcon-BXdiCGQp.js} +8 -2
  10. package/dist/_chunks/ComponentIcon-BXdiCGQp.js.map +1 -0
  11. package/dist/_chunks/{ComponentIcon-BOFnK76n.mjs → ComponentIcon-u4bIXTFY.mjs} +9 -3
  12. package/dist/_chunks/ComponentIcon-u4bIXTFY.mjs.map +1 -0
  13. package/dist/_chunks/{EditConfigurationPage-BfFzJ4Br.js → EditConfigurationPage-CqBeCPGH.js} +3 -3
  14. package/dist/_chunks/{EditConfigurationPage-BfFzJ4Br.js.map → EditConfigurationPage-CqBeCPGH.js.map} +1 -1
  15. package/dist/_chunks/{EditConfigurationPage-CUcGHHvQ.mjs → EditConfigurationPage-DbI4KMyz.mjs} +3 -3
  16. package/dist/_chunks/{EditConfigurationPage-CUcGHHvQ.mjs.map → EditConfigurationPage-DbI4KMyz.mjs.map} +1 -1
  17. package/dist/_chunks/{EditViewPage-CzOT5Kpj.js → EditViewPage-ChgloMyO.js} +7 -9
  18. package/dist/_chunks/EditViewPage-ChgloMyO.js.map +1 -0
  19. package/dist/_chunks/{EditViewPage-Bm8lgcm6.mjs → EditViewPage-dFPBya9U.mjs} +6 -6
  20. package/dist/_chunks/EditViewPage-dFPBya9U.mjs.map +1 -0
  21. package/dist/_chunks/{Field-Dlh0uGnL.mjs → Field-C1nUKcdS.mjs} +500 -608
  22. package/dist/_chunks/Field-C1nUKcdS.mjs.map +1 -0
  23. package/dist/_chunks/{Field-Caef4JjM.js → Field-dLk-vgLL.js} +552 -661
  24. package/dist/_chunks/Field-dLk-vgLL.js.map +1 -0
  25. package/dist/_chunks/{Form-BzuAjtRq.js → Form-CbXtmHC_.js} +21 -19
  26. package/dist/_chunks/Form-CbXtmHC_.js.map +1 -0
  27. package/dist/_chunks/{Form-EnaQL_6L.mjs → Form-DOlpi7Js.mjs} +21 -18
  28. package/dist/_chunks/Form-DOlpi7Js.mjs.map +1 -0
  29. package/dist/_chunks/{History-D6sbCJvo.mjs → History-BFNUAiGc.mjs} +32 -43
  30. package/dist/_chunks/History-BFNUAiGc.mjs.map +1 -0
  31. package/dist/_chunks/{History-C17LiyRg.js → History-BjDfohBr.js} +32 -44
  32. package/dist/_chunks/History-BjDfohBr.js.map +1 -0
  33. package/dist/_chunks/{ListConfigurationPage-Ce4qs7qE.mjs → ListConfigurationPage-DDi0KqFm.mjs} +14 -14
  34. package/dist/_chunks/ListConfigurationPage-DDi0KqFm.mjs.map +1 -0
  35. package/dist/_chunks/{ListConfigurationPage-Dks5SX6f.js → ListConfigurationPage-IQBgWTaa.js} +17 -19
  36. package/dist/_chunks/ListConfigurationPage-IQBgWTaa.js.map +1 -0
  37. package/dist/_chunks/{ListViewPage-Be7S5aKL.mjs → ListViewPage-BPjljUsH.mjs} +25 -45
  38. package/dist/_chunks/ListViewPage-BPjljUsH.mjs.map +1 -0
  39. package/dist/_chunks/{ListViewPage-BwrZrPsh.js → ListViewPage-CZYGqlvF.js} +31 -51
  40. package/dist/_chunks/ListViewPage-CZYGqlvF.js.map +1 -0
  41. package/dist/_chunks/{NoContentTypePage-Cu5r1-JT.js → NoContentTypePage-BOAI6VZ1.js} +5 -5
  42. package/dist/_chunks/NoContentTypePage-BOAI6VZ1.js.map +1 -0
  43. package/dist/_chunks/{NoContentTypePage-CIPmYQMm.mjs → NoContentTypePage-DaWw67K-.mjs} +7 -7
  44. package/dist/_chunks/NoContentTypePage-DaWw67K-.mjs.map +1 -0
  45. package/dist/_chunks/{NoPermissionsPage-DhJ7LYrr.mjs → NoPermissionsPage-CZrJH00p.mjs} +5 -6
  46. package/dist/_chunks/NoPermissionsPage-CZrJH00p.mjs.map +1 -0
  47. package/dist/_chunks/{NoPermissionsPage-C-j6TEUF.js → NoPermissionsPage-cYEtLc_e.js} +4 -5
  48. package/dist/_chunks/NoPermissionsPage-cYEtLc_e.js.map +1 -0
  49. package/dist/_chunks/{Relations-CY7AtkDA.mjs → Relations-DTowyge2.mjs} +66 -56
  50. package/dist/_chunks/Relations-DTowyge2.mjs.map +1 -0
  51. package/dist/_chunks/{Relations-Czs-uZ-s.js → Relations-DU6B7irU.js} +70 -61
  52. package/dist/_chunks/Relations-DU6B7irU.js.map +1 -0
  53. package/dist/_chunks/{en-C-V1_90f.js → en-DTULi5-d.js} +3 -1
  54. package/dist/_chunks/{en-C-V1_90f.js.map → en-DTULi5-d.js.map} +1 -1
  55. package/dist/_chunks/{en-MBPul9Su.mjs → en-GCOTL6jR.mjs} +3 -1
  56. package/dist/_chunks/{en-MBPul9Su.mjs.map → en-GCOTL6jR.mjs.map} +1 -1
  57. package/dist/_chunks/{index-DNVx8ssZ.mjs → index-BaGHmIir.mjs} +507 -202
  58. package/dist/_chunks/index-BaGHmIir.mjs.map +1 -0
  59. package/dist/_chunks/{index-X_2tafck.js → index-CCJeB7Rw.js} +502 -198
  60. package/dist/_chunks/index-CCJeB7Rw.js.map +1 -0
  61. package/dist/_chunks/{layout-Dnh0PNp9.mjs → layout-BinjszSQ.mjs} +13 -13
  62. package/dist/_chunks/layout-BinjszSQ.mjs.map +1 -0
  63. package/dist/_chunks/{layout-dBc7wN7L.js → layout-ni_L9kT1.js} +14 -16
  64. package/dist/_chunks/layout-ni_L9kT1.js.map +1 -0
  65. package/dist/_chunks/{relations-4pHtBrHJ.js → relations-CeJAJc5I.js} +2 -2
  66. package/dist/_chunks/{relations-4pHtBrHJ.js.map → relations-CeJAJc5I.js.map} +1 -1
  67. package/dist/_chunks/{relations-Dx7tMKJN.mjs → relations-c91ji5eR.mjs} +2 -2
  68. package/dist/_chunks/{relations-Dx7tMKJN.mjs.map → relations-c91ji5eR.mjs.map} +1 -1
  69. package/dist/_chunks/useDragAndDrop-DdHgKsqq.mjs.map +1 -1
  70. package/dist/_chunks/useDragAndDrop-J0TUUbR6.js.map +1 -1
  71. package/dist/_chunks/usePrev-B9w_-eYc.js +15 -0
  72. package/dist/_chunks/usePrev-B9w_-eYc.js.map +1 -0
  73. package/dist/_chunks/usePrev-DH6iah0A.mjs +16 -0
  74. package/dist/_chunks/usePrev-DH6iah0A.mjs.map +1 -0
  75. package/dist/admin/index.js +2 -1
  76. package/dist/admin/index.js.map +1 -1
  77. package/dist/admin/index.mjs +5 -4
  78. package/dist/admin/src/components/ComponentIcon.d.ts +6 -3
  79. package/dist/admin/src/content-manager.d.ts +3 -3
  80. package/dist/admin/src/exports.d.ts +1 -0
  81. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  82. package/dist/admin/src/hooks/useDocument.d.ts +5 -8
  83. package/dist/admin/src/hooks/useDocumentActions.d.ts +24 -3
  84. package/dist/admin/src/hooks/useDocumentLayout.d.ts +2 -2
  85. package/dist/admin/src/hooks/useDragAndDrop.d.ts +4 -4
  86. package/dist/admin/src/hooks/useKeyboardDragAndDrop.d.ts +1 -1
  87. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +3 -1
  88. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksInput.d.ts +3 -3
  89. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Input.d.ts +2 -2
  90. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.d.ts +3 -5
  91. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/Field.d.ts +1 -1
  92. package/dist/admin/src/pages/EditView/components/FormInputs/Relations.d.ts +10 -18
  93. package/dist/admin/src/pages/EditView/components/FormInputs/UID.d.ts +2 -2
  94. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +3 -49
  95. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/Field.d.ts +2 -2
  96. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +67 -52
  97. package/dist/admin/src/pages/EditView/components/InputRenderer.d.ts +2 -10
  98. package/dist/admin/src/services/api.d.ts +2 -3
  99. package/dist/admin/src/services/components.d.ts +2 -2
  100. package/dist/admin/src/services/contentTypes.d.ts +5 -5
  101. package/dist/admin/src/services/documents.d.ts +29 -17
  102. package/dist/admin/src/services/init.d.ts +2 -2
  103. package/dist/admin/src/services/relations.d.ts +3 -3
  104. package/dist/admin/src/services/uid.d.ts +3 -3
  105. package/dist/admin/src/utils/api.d.ts +4 -18
  106. package/dist/admin/src/utils/validation.d.ts +1 -6
  107. package/dist/server/index.js +529 -407
  108. package/dist/server/index.js.map +1 -1
  109. package/dist/server/index.mjs +537 -415
  110. package/dist/server/index.mjs.map +1 -1
  111. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  112. package/dist/server/src/controllers/single-types.d.ts.map +1 -1
  113. package/dist/server/src/controllers/utils/metadata.d.ts +8 -0
  114. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -0
  115. package/dist/server/src/controllers/validation/dimensions.d.ts +9 -0
  116. package/dist/server/src/controllers/validation/dimensions.d.ts.map +1 -0
  117. package/dist/server/src/controllers/validation/index.d.ts +1 -1
  118. package/dist/server/src/history/services/history.d.ts +2 -4
  119. package/dist/server/src/history/services/history.d.ts.map +1 -1
  120. package/dist/server/src/history/services/index.d.ts +6 -2
  121. package/dist/server/src/history/services/index.d.ts.map +1 -1
  122. package/dist/server/src/history/services/lifecycles.d.ts +9 -0
  123. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -0
  124. package/dist/server/src/history/services/utils.d.ts +41 -9
  125. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  126. package/dist/server/src/history/utils.d.ts +6 -2
  127. package/dist/server/src/history/utils.d.ts.map +1 -1
  128. package/dist/server/src/index.d.ts +18 -39
  129. package/dist/server/src/index.d.ts.map +1 -1
  130. package/dist/server/src/services/document-manager.d.ts +13 -12
  131. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  132. package/dist/server/src/services/document-metadata.d.ts +8 -29
  133. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  134. package/dist/server/src/services/index.d.ts +18 -39
  135. package/dist/server/src/services/index.d.ts.map +1 -1
  136. package/dist/server/src/services/utils/populate.d.ts +8 -1
  137. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  138. package/dist/shared/contracts/collection-types.d.ts +14 -6
  139. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  140. package/dist/shared/contracts/relations.d.ts +2 -2
  141. package/dist/shared/contracts/relations.d.ts.map +1 -1
  142. package/package.json +13 -14
  143. package/dist/_chunks/CardDragPreview-DSVYodBX.js.map +0 -1
  144. package/dist/_chunks/CardDragPreview-ikSG4M46.mjs.map +0 -1
  145. package/dist/_chunks/ComponentIcon-BBQsYCVn.js.map +0 -1
  146. package/dist/_chunks/ComponentIcon-BOFnK76n.mjs.map +0 -1
  147. package/dist/_chunks/EditViewPage-Bm8lgcm6.mjs.map +0 -1
  148. package/dist/_chunks/EditViewPage-CzOT5Kpj.js.map +0 -1
  149. package/dist/_chunks/Field-Caef4JjM.js.map +0 -1
  150. package/dist/_chunks/Field-Dlh0uGnL.mjs.map +0 -1
  151. package/dist/_chunks/Form-BzuAjtRq.js.map +0 -1
  152. package/dist/_chunks/Form-EnaQL_6L.mjs.map +0 -1
  153. package/dist/_chunks/History-C17LiyRg.js.map +0 -1
  154. package/dist/_chunks/History-D6sbCJvo.mjs.map +0 -1
  155. package/dist/_chunks/ListConfigurationPage-Ce4qs7qE.mjs.map +0 -1
  156. package/dist/_chunks/ListConfigurationPage-Dks5SX6f.js.map +0 -1
  157. package/dist/_chunks/ListViewPage-Be7S5aKL.mjs.map +0 -1
  158. package/dist/_chunks/ListViewPage-BwrZrPsh.js.map +0 -1
  159. package/dist/_chunks/NoContentTypePage-CIPmYQMm.mjs.map +0 -1
  160. package/dist/_chunks/NoContentTypePage-Cu5r1-JT.js.map +0 -1
  161. package/dist/_chunks/NoPermissionsPage-C-j6TEUF.js.map +0 -1
  162. package/dist/_chunks/NoPermissionsPage-DhJ7LYrr.mjs.map +0 -1
  163. package/dist/_chunks/Relations-CY7AtkDA.mjs.map +0 -1
  164. package/dist/_chunks/Relations-Czs-uZ-s.js.map +0 -1
  165. package/dist/_chunks/index-DNVx8ssZ.mjs.map +0 -1
  166. package/dist/_chunks/index-X_2tafck.js.map +0 -1
  167. package/dist/_chunks/layout-Dnh0PNp9.mjs.map +0 -1
  168. package/dist/_chunks/layout-dBc7wN7L.js.map +0 -1
  169. package/dist/_chunks/urls-CbOsUOoW.mjs +0 -7
  170. package/dist/_chunks/urls-CbOsUOoW.mjs.map +0 -1
  171. package/dist/_chunks/urls-DzZya_gm.js +0 -6
  172. package/dist/_chunks/urls-DzZya_gm.js.map +0 -1
  173. package/dist/admin/src/pages/ListView/components/BulkActions/PublishAction.d.ts +0 -31
  174. package/dist/server/src/controllers/utils/dimensions.d.ts +0 -5
  175. package/dist/server/src/controllers/utils/dimensions.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
- import strapiUtils, { validateYupSchema, errors, async, contentTypes as contentTypes$1, yup as yup$1, validateYupSchemaSync, policy, traverse, setCreatorFields, isOperatorOfType, relations as relations$1, sanitize } from "@strapi/utils";
2
- import { pick, omit, difference, intersection, pipe, propOr, isEqual, isEmpty, set, isNil as isNil$1, has, prop, assoc, mapValues, flow, uniq, uniqBy, concat, getOr, propEq, merge, groupBy, castArray } from "lodash/fp";
1
+ import strapiUtils, { validateYupSchema, errors, async, contentTypes as contentTypes$1, yup as yup$1, validateYupSchemaSync, policy, traverse, setCreatorFields, isOperatorOfType, relations as relations$1, traverseEntity, pagination } from "@strapi/utils";
2
+ import { pick, omit, difference, intersection, pipe, propOr, isEqual, isEmpty, set, has, prop, assoc, mapValues, flow, uniq, uniqBy, concat, isNil as isNil$1, getOr, propEq, merge, groupBy, castArray } from "lodash/fp";
3
3
  import "@strapi/types";
4
4
  import * as yup from "yup";
5
5
  import { scheduleJob } from "node-schedule";
@@ -54,7 +54,7 @@ const createHistoryVersionController = ({ strapi: strapi2 }) => {
54
54
  return ctx.forbidden();
55
55
  }
56
56
  const query = await permissionChecker2.sanitizeQuery(ctx.query);
57
- const { results, pagination } = await getService(strapi2, "history").findVersionsPage({
57
+ const { results, pagination: pagination2 } = await getService(strapi2, "history").findVersionsPage({
58
58
  query: {
59
59
  ...query,
60
60
  ...getValidPagination({ page: query.page, pageSize: query.pageSize })
@@ -73,7 +73,7 @@ const createHistoryVersionController = ({ strapi: strapi2 }) => {
73
73
  );
74
74
  return {
75
75
  data: sanitizedResults,
76
- meta: { pagination }
76
+ meta: { pagination: pagination2 }
77
77
  };
78
78
  },
79
79
  async restoreVersion(ctx) {
@@ -112,40 +112,65 @@ const FIELDS_TO_IGNORE = [
112
112
  "strapi_stage",
113
113
  "strapi_assignee"
114
114
  ];
115
- const getSchemaAttributesDiff = (versionSchemaAttributes, contentTypeSchemaAttributes) => {
116
- const sanitizedContentTypeSchemaAttributes = omit(FIELDS_TO_IGNORE, contentTypeSchemaAttributes);
117
- const reduceDifferenceToAttributesObject = (diffKeys, source) => {
118
- return diffKeys.reduce((previousAttributesObject, diffKey) => {
119
- previousAttributesObject[diffKey] = source[diffKey];
120
- return previousAttributesObject;
121
- }, {});
122
- };
123
- const versionSchemaKeys = Object.keys(versionSchemaAttributes);
124
- const contentTypeSchemaAttributesKeys = Object.keys(sanitizedContentTypeSchemaAttributes);
125
- const uniqueToContentType = difference(contentTypeSchemaAttributesKeys, versionSchemaKeys);
126
- const added = reduceDifferenceToAttributesObject(
127
- uniqueToContentType,
128
- sanitizedContentTypeSchemaAttributes
129
- );
130
- const uniqueToVersion = difference(versionSchemaKeys, contentTypeSchemaAttributesKeys);
131
- const removed = reduceDifferenceToAttributesObject(uniqueToVersion, versionSchemaAttributes);
132
- return { added, removed };
133
- };
134
115
  const DEFAULT_RETENTION_DAYS = 90;
135
- const createHistoryService = ({ strapi: strapi2 }) => {
136
- const state = {
137
- deleteExpiredJob: null,
138
- isInitialized: false
116
+ const createServiceUtils = ({ strapi: strapi2 }) => {
117
+ const getSchemaAttributesDiff = (versionSchemaAttributes, contentTypeSchemaAttributes) => {
118
+ const sanitizedContentTypeSchemaAttributes = omit(
119
+ FIELDS_TO_IGNORE,
120
+ contentTypeSchemaAttributes
121
+ );
122
+ const reduceDifferenceToAttributesObject = (diffKeys, source) => {
123
+ return diffKeys.reduce(
124
+ (previousAttributesObject, diffKey) => {
125
+ previousAttributesObject[diffKey] = source[diffKey];
126
+ return previousAttributesObject;
127
+ },
128
+ {}
129
+ );
130
+ };
131
+ const versionSchemaKeys = Object.keys(versionSchemaAttributes);
132
+ const contentTypeSchemaAttributesKeys = Object.keys(sanitizedContentTypeSchemaAttributes);
133
+ const uniqueToContentType = difference(contentTypeSchemaAttributesKeys, versionSchemaKeys);
134
+ const added = reduceDifferenceToAttributesObject(
135
+ uniqueToContentType,
136
+ sanitizedContentTypeSchemaAttributes
137
+ );
138
+ const uniqueToVersion = difference(versionSchemaKeys, contentTypeSchemaAttributesKeys);
139
+ const removed = reduceDifferenceToAttributesObject(uniqueToVersion, versionSchemaAttributes);
140
+ return { added, removed };
139
141
  };
140
- const query = strapi2.db.query(HISTORY_VERSION_UID);
141
- const getRetentionDays = (strapi22) => {
142
- const featureConfig = strapi22.ee.features.get("cms-content-history");
143
- const licenseRetentionDays = typeof featureConfig === "object" && featureConfig?.options.retentionDays;
144
- const userRetentionDays = strapi22.config.get("admin.history.retentionDays");
145
- if (userRetentionDays && userRetentionDays < licenseRetentionDays) {
146
- return userRetentionDays;
142
+ const getRelationRestoreValue = async (versionRelationData, attribute) => {
143
+ if (Array.isArray(versionRelationData)) {
144
+ if (versionRelationData.length === 0)
145
+ return versionRelationData;
146
+ const existingAndMissingRelations = await Promise.all(
147
+ versionRelationData.map((relation) => {
148
+ return strapi2.documents(attribute.target).findOne({
149
+ documentId: relation.documentId,
150
+ locale: relation.locale || void 0
151
+ });
152
+ })
153
+ );
154
+ return existingAndMissingRelations.filter(
155
+ (relation) => relation !== null
156
+ );
147
157
  }
148
- return Math.min(licenseRetentionDays, DEFAULT_RETENTION_DAYS);
158
+ return strapi2.documents(attribute.target).findOne({
159
+ documentId: versionRelationData.documentId,
160
+ locale: versionRelationData.locale || void 0
161
+ });
162
+ };
163
+ const getMediaRestoreValue = async (versionRelationData, attribute) => {
164
+ if (attribute.multiple) {
165
+ const existingAndMissingMedias = await Promise.all(
166
+ // @ts-expect-error Fix the type definitions so this isn't any
167
+ versionRelationData.map((media) => {
168
+ return strapi2.db.query("plugin::upload.file").findOne({ where: { id: media.id } });
169
+ })
170
+ );
171
+ return existingAndMissingMedias.filter((media) => media != null);
172
+ }
173
+ return strapi2.db.query("plugin::upload.file").findOne({ where: { id: versionRelationData.id } });
149
174
  };
150
175
  const localesService = strapi2.plugin("i18n")?.service("locales");
151
176
  const getDefaultLocale = async () => localesService ? localesService.getDefaultLocale() : null;
@@ -161,6 +186,15 @@ const createHistoryService = ({ strapi: strapi2 }) => {
161
186
  {}
162
187
  );
163
188
  };
189
+ const getRetentionDays = () => {
190
+ const featureConfig = strapi2.ee.features.get("cms-content-history");
191
+ const licenseRetentionDays = typeof featureConfig === "object" && featureConfig?.options.retentionDays;
192
+ const userRetentionDays = strapi2.config.get("admin.history.retentionDays");
193
+ if (userRetentionDays && userRetentionDays < licenseRetentionDays) {
194
+ return userRetentionDays;
195
+ }
196
+ return Math.min(licenseRetentionDays, DEFAULT_RETENTION_DAYS);
197
+ };
164
198
  const getVersionStatus = async (contentTypeUid, document) => {
165
199
  const documentMetadataService = strapi2.plugin("content-manager").service("document-metadata");
166
200
  const meta = await documentMetadataService.getMetadata(contentTypeUid, document);
@@ -202,80 +236,68 @@ const createHistoryService = ({ strapi: strapi2 }) => {
202
236
  return acc;
203
237
  }, {});
204
238
  };
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();
239
+ const buildMediaResponse = async (values) => {
240
+ return values.slice(0, 25).reduce(
241
+ async (currentRelationDataPromise, entry) => {
242
+ const currentRelationData = await currentRelationDataPromise;
243
+ if (!entry) {
244
+ return currentRelationData;
213
245
  }
214
- if (context.action !== "create" && context.action !== "update" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
215
- return next();
246
+ const relatedEntry = await strapi2.db.query("plugin::upload.file").findOne({ where: { id: entry.id } });
247
+ if (relatedEntry) {
248
+ currentRelationData.results.push(relatedEntry);
249
+ } else {
250
+ currentRelationData.meta.missingCount += 1;
216
251
  }
217
- const contentTypeUid = context.contentType.uid;
218
- if (!contentTypeUid.startsWith("api::")) {
219
- return next();
252
+ return currentRelationData;
253
+ },
254
+ Promise.resolve({
255
+ results: [],
256
+ meta: { missingCount: 0 }
257
+ })
258
+ );
259
+ };
260
+ const buildRelationReponse = async (values, attributeSchema) => {
261
+ return values.slice(0, 25).reduce(
262
+ async (currentRelationDataPromise, entry) => {
263
+ const currentRelationData = await currentRelationDataPromise;
264
+ if (!entry) {
265
+ return currentRelationData;
220
266
  }
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
- });
267
+ const relatedEntry = await strapi2.documents(attributeSchema.target).findOne({ documentId: entry.documentId, locale: entry.locale || void 0 });
268
+ if (relatedEntry) {
269
+ currentRelationData.results.push({
270
+ ...relatedEntry,
271
+ status: await getVersionStatus(attributeSchema.target, relatedEntry)
256
272
  });
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
- },
273
+ } else {
274
+ currentRelationData.meta.missingCount += 1;
275
+ }
276
+ return currentRelationData;
277
+ },
278
+ Promise.resolve({
279
+ results: [],
280
+ meta: { missingCount: 0 }
281
+ })
282
+ );
283
+ };
284
+ return {
285
+ getSchemaAttributesDiff,
286
+ getRelationRestoreValue,
287
+ getMediaRestoreValue,
288
+ getDefaultLocale,
289
+ getLocaleDictionary,
290
+ getRetentionDays,
291
+ getVersionStatus,
292
+ getDeepPopulate: getDeepPopulate2,
293
+ buildMediaResponse,
294
+ buildRelationReponse
295
+ };
296
+ };
297
+ const createHistoryService = ({ strapi: strapi2 }) => {
298
+ const query = strapi2.db.query(HISTORY_VERSION_UID);
299
+ const serviceUtils = createServiceUtils({ strapi: strapi2 });
300
+ return {
279
301
  async createVersion(historyVersionData) {
280
302
  await query.create({
281
303
  data: {
@@ -286,8 +308,8 @@ const createHistoryService = ({ strapi: strapi2 }) => {
286
308
  });
287
309
  },
288
310
  async findVersionsPage(params) {
289
- const locale = params.query.locale || await getDefaultLocale();
290
- const [{ results, pagination }, localeDictionary] = await Promise.all([
311
+ const locale = params.query.locale || await serviceUtils.getDefaultLocale();
312
+ const [{ results, pagination: pagination2 }, localeDictionary] = await Promise.all([
291
313
  query.findPage({
292
314
  ...params.query,
293
315
  where: {
@@ -300,78 +322,34 @@ const createHistoryService = ({ strapi: strapi2 }) => {
300
322
  populate: ["createdBy"],
301
323
  orderBy: [{ createdAt: "desc" }]
302
324
  }),
303
- getLocaleDictionary()
325
+ serviceUtils.getLocaleDictionary()
304
326
  ]);
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
327
  const populateEntryRelations = async (entry) => {
361
328
  const entryWithRelations = await Object.entries(entry.schema).reduce(
362
329
  async (currentDataWithRelations, [attributeKey, attributeSchema]) => {
363
330
  const attributeValue = entry.data[attributeKey];
364
331
  const attributeValues = Array.isArray(attributeValue) ? attributeValue : [attributeValue];
365
332
  if (attributeSchema.type === "media") {
333
+ const permissionChecker2 = getService$1("permission-checker").create({
334
+ userAbility: params.state.userAbility,
335
+ model: "plugin::upload.file"
336
+ });
337
+ const response = await serviceUtils.buildMediaResponse(attributeValues);
338
+ const sanitizedResults = await Promise.all(
339
+ response.results.map((media) => permissionChecker2.sanitizeOutput(media))
340
+ );
366
341
  return {
367
342
  ...await currentDataWithRelations,
368
- [attributeKey]: await buildMediaResponse(attributeValues)
343
+ [attributeKey]: {
344
+ results: sanitizedResults,
345
+ meta: response.meta
346
+ }
369
347
  };
370
348
  }
371
349
  if (attributeSchema.type === "relation" && attributeSchema.relation !== "morphToOne" && attributeSchema.relation !== "morphToMany") {
372
350
  if (attributeSchema.target === "admin::user") {
373
351
  const adminUsers = await Promise.all(
374
- attributeValues.map(async (userToPopulate) => {
352
+ attributeValues.map((userToPopulate) => {
375
353
  if (userToPopulate == null) {
376
354
  return null;
377
355
  }
@@ -388,9 +366,23 @@ const createHistoryService = ({ strapi: strapi2 }) => {
388
366
  [attributeKey]: adminUsers
389
367
  };
390
368
  }
369
+ const permissionChecker2 = getService$1("permission-checker").create({
370
+ userAbility: params.state.userAbility,
371
+ model: attributeSchema.target
372
+ });
373
+ const response = await serviceUtils.buildRelationReponse(
374
+ attributeValues,
375
+ attributeSchema
376
+ );
377
+ const sanitizedResults = await Promise.all(
378
+ response.results.map((media) => permissionChecker2.sanitizeOutput(media))
379
+ );
391
380
  return {
392
381
  ...await currentDataWithRelations,
393
- [attributeKey]: await buildRelationReponse(attributeValues, attributeSchema)
382
+ [attributeKey]: {
383
+ results: sanitizedResults,
384
+ meta: response.meta
385
+ }
394
386
  };
395
387
  }
396
388
  return currentDataWithRelations;
@@ -405,7 +397,7 @@ const createHistoryService = ({ strapi: strapi2 }) => {
405
397
  ...result,
406
398
  data: await populateEntryRelations(result),
407
399
  meta: {
408
- unknownAttributes: getSchemaAttributesDiff(
400
+ unknownAttributes: serviceUtils.getSchemaAttributesDiff(
409
401
  result.schema,
410
402
  strapi2.getModel(params.query.contentType).attributes
411
403
  )
@@ -416,13 +408,16 @@ const createHistoryService = ({ strapi: strapi2 }) => {
416
408
  );
417
409
  return {
418
410
  results: formattedResults,
419
- pagination
411
+ pagination: pagination2
420
412
  };
421
413
  },
422
414
  async restoreVersion(versionId) {
423
415
  const version = await query.findOne({ where: { id: versionId } });
424
416
  const contentTypeSchemaAttributes = strapi2.getModel(version.contentType).attributes;
425
- const schemaDiff = getSchemaAttributesDiff(version.schema, contentTypeSchemaAttributes);
417
+ const schemaDiff = serviceUtils.getSchemaAttributesDiff(
418
+ version.schema,
419
+ contentTypeSchemaAttributes
420
+ );
426
421
  const dataWithoutAddedAttributes = Object.keys(schemaDiff.added).reduce(
427
422
  (currentData, addedKey) => {
428
423
  currentData[addedKey] = null;
@@ -435,61 +430,26 @@ const createHistoryService = ({ strapi: strapi2 }) => {
435
430
  FIELDS_TO_IGNORE,
436
431
  contentTypeSchemaAttributes
437
432
  );
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) {
433
+ const reducer = async.reduce(Object.entries(sanitizedSchemaAttributes));
434
+ const dataWithoutMissingRelations = await reducer(
435
+ async (previousRelationAttributes, [name, attribute]) => {
436
+ const versionRelationData = version.data[name];
437
+ if (!versionRelationData) {
443
438
  return previousRelationAttributes;
444
439
  }
445
440
  if (attribute.type === "relation" && // TODO: handle polymorphic relations
446
441
  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
- }
442
+ const data2 = await serviceUtils.getRelationRestoreValue(versionRelationData, attribute);
443
+ previousRelationAttributes[name] = data2;
471
444
  }
472
445
  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
- }
446
+ const data2 = await serviceUtils.getMediaRestoreValue(versionRelationData, attribute);
447
+ previousRelationAttributes[name] = data2;
488
448
  }
489
449
  return previousRelationAttributes;
490
450
  },
491
451
  // Clone to avoid mutating the original version data
492
- Promise.resolve(structuredClone(dataWithoutAddedAttributes))
452
+ structuredClone(dataWithoutAddedAttributes)
493
453
  );
494
454
  const data = omit(["id", ...Object.keys(schemaDiff.removed)], dataWithoutMissingRelations);
495
455
  const restoredDocument = await strapi2.documents(version.contentType).update({
@@ -504,8 +464,99 @@ const createHistoryService = ({ strapi: strapi2 }) => {
504
464
  }
505
465
  };
506
466
  };
467
+ const createLifecyclesService = ({ strapi: strapi2 }) => {
468
+ const state = {
469
+ deleteExpiredJob: null,
470
+ isInitialized: false
471
+ };
472
+ const query = strapi2.db.query(HISTORY_VERSION_UID);
473
+ const historyService = getService(strapi2, "history");
474
+ const serviceUtils = createServiceUtils({ strapi: strapi2 });
475
+ return {
476
+ async bootstrap() {
477
+ if (state.isInitialized) {
478
+ return;
479
+ }
480
+ strapi2.documents.use(async (context, next) => {
481
+ if (!strapi2.requestContext.get()?.request.url.startsWith("/content-manager")) {
482
+ return next();
483
+ }
484
+ if (context.action !== "create" && context.action !== "update" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
485
+ return next();
486
+ }
487
+ const contentTypeUid = context.contentType.uid;
488
+ if (!contentTypeUid.startsWith("api::")) {
489
+ return next();
490
+ }
491
+ const result = await next();
492
+ const documentContext = context.action === "create" ? { documentId: result.documentId, locale: context.params?.locale } : { documentId: context.params.documentId, locale: context.params?.locale };
493
+ const defaultLocale = await serviceUtils.getDefaultLocale();
494
+ const locale = documentContext.locale || defaultLocale;
495
+ if (Array.isArray(locale)) {
496
+ strapi2.log.warn(
497
+ "[Content manager history middleware]: An array of locales was provided, but only a single locale is supported for the findOne operation."
498
+ );
499
+ return next();
500
+ }
501
+ const document = await strapi2.documents(contentTypeUid).findOne({
502
+ documentId: documentContext.documentId,
503
+ locale,
504
+ populate: serviceUtils.getDeepPopulate(contentTypeUid)
505
+ });
506
+ const status = await serviceUtils.getVersionStatus(contentTypeUid, document);
507
+ const attributesSchema = strapi2.getModel(contentTypeUid).attributes;
508
+ const componentsSchemas = Object.keys(
509
+ attributesSchema
510
+ ).reduce((currentComponentSchemas, key) => {
511
+ const fieldSchema = attributesSchema[key];
512
+ if (fieldSchema.type === "component") {
513
+ const componentSchema = strapi2.getModel(fieldSchema.component).attributes;
514
+ return {
515
+ ...currentComponentSchemas,
516
+ [fieldSchema.component]: componentSchema
517
+ };
518
+ }
519
+ return currentComponentSchemas;
520
+ }, {});
521
+ await strapi2.db.transaction(async ({ onCommit }) => {
522
+ onCommit(() => {
523
+ historyService.createVersion({
524
+ contentType: contentTypeUid,
525
+ data: omit(FIELDS_TO_IGNORE, document),
526
+ schema: omit(FIELDS_TO_IGNORE, attributesSchema),
527
+ componentsSchemas,
528
+ relatedDocumentId: documentContext.documentId,
529
+ locale,
530
+ status
531
+ });
532
+ });
533
+ });
534
+ return result;
535
+ });
536
+ const retentionDays = serviceUtils.getRetentionDays();
537
+ state.deleteExpiredJob = scheduleJob("0 0 * * *", () => {
538
+ const retentionDaysInMilliseconds = retentionDays * 24 * 60 * 60 * 1e3;
539
+ const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
540
+ query.deleteMany({
541
+ where: {
542
+ created_at: {
543
+ $lt: expirationDate.toISOString()
544
+ }
545
+ }
546
+ });
547
+ });
548
+ state.isInitialized = true;
549
+ },
550
+ async destroy() {
551
+ if (state.deleteExpiredJob) {
552
+ state.deleteExpiredJob.cancel();
553
+ }
554
+ }
555
+ };
556
+ };
507
557
  const services$1 = {
508
- history: createHistoryService
558
+ history: createHistoryService,
559
+ lifecycles: createLifecyclesService
509
560
  };
510
561
  const info = { pluginName: "content-manager", type: "admin" };
511
562
  const historyVersionRouter = {
@@ -585,10 +636,10 @@ const getFeature = () => {
585
636
  strapi2.get("models").add(historyVersion);
586
637
  },
587
638
  bootstrap({ strapi: strapi2 }) {
588
- getService(strapi2, "history").bootstrap();
639
+ getService(strapi2, "lifecycles").bootstrap();
589
640
  },
590
641
  destroy({ strapi: strapi2 }) {
591
- getService(strapi2, "history").destroy();
642
+ getService(strapi2, "lifecycles").destroy();
592
643
  },
593
644
  controllers: controllers$1,
594
645
  services: services$1,
@@ -1407,7 +1458,7 @@ const { PaginationError, ValidationError } = errors;
1407
1458
  const TYPES = ["singleType", "collectionType"];
1408
1459
  const kindSchema = yup$1.string().oneOf(TYPES).nullable();
1409
1460
  const bulkActionInputSchema = yup$1.object({
1410
- ids: yup$1.array().of(yup$1.strapiID()).min(1).required()
1461
+ documentIds: yup$1.array().of(yup$1.strapiID()).min(1).required()
1411
1462
  }).required();
1412
1463
  const generateUIDInputSchema = yup$1.object({
1413
1464
  contentTypeUID: yup$1.string().required(),
@@ -1506,15 +1557,47 @@ const excludeNotCreatableFields = (uid2, permissionChecker2) => (body, path = []
1506
1557
  }
1507
1558
  }, body);
1508
1559
  };
1509
- const getDocumentLocaleAndStatus = (request) => {
1560
+ const singleLocaleSchema = yup$1.string().nullable();
1561
+ const multipleLocaleSchema = yup$1.lazy(
1562
+ (value) => Array.isArray(value) ? yup$1.array().of(singleLocaleSchema.required()) : singleLocaleSchema
1563
+ );
1564
+ const statusSchema = yup$1.mixed().oneOf(["draft", "published"], "Invalid status");
1565
+ const getDocumentLocaleAndStatus = async (request, opts = { allowMultipleLocales: false }) => {
1566
+ const { allowMultipleLocales } = opts;
1510
1567
  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}`);
1568
+ const schema = yup$1.object().shape({
1569
+ locale: allowMultipleLocales ? multipleLocaleSchema : singleLocaleSchema,
1570
+ status: statusSchema
1571
+ });
1572
+ try {
1573
+ await validateYupSchema(schema, { strict: true, abortEarly: false })(request);
1574
+ return { locale, status, ...rest };
1575
+ } catch (error) {
1576
+ throw new errors.ValidationError(`Validation error: ${error.message}`);
1516
1577
  }
1517
- return { locale, status, ...rest };
1578
+ };
1579
+ const formatDocumentWithMetadata = async (permissionChecker2, uid2, document, opts = {}) => {
1580
+ const documentMetadata2 = getService$1("document-metadata");
1581
+ const serviceOutput = await documentMetadata2.formatDocumentWithMetadata(uid2, document, opts);
1582
+ let {
1583
+ meta: { availableLocales, availableStatus }
1584
+ } = serviceOutput;
1585
+ const metadataSanitizer = permissionChecker2.sanitizeOutput;
1586
+ availableLocales = await async.map(
1587
+ availableLocales,
1588
+ async (localeDocument) => metadataSanitizer(localeDocument)
1589
+ );
1590
+ availableStatus = await async.map(
1591
+ availableStatus,
1592
+ async (statusDocument) => metadataSanitizer(statusDocument)
1593
+ );
1594
+ return {
1595
+ ...serviceOutput,
1596
+ meta: {
1597
+ availableLocales,
1598
+ availableStatus
1599
+ }
1600
+ };
1518
1601
  };
1519
1602
  const createDocument = async (ctx, opts) => {
1520
1603
  const { userAbility, user } = ctx.state;
@@ -1529,7 +1612,7 @@ const createDocument = async (ctx, opts) => {
1529
1612
  const setCreator = setCreatorFields({ user });
1530
1613
  const sanitizeFn = async.pipe(pickPermittedFields, setCreator);
1531
1614
  const sanitizedBody = await sanitizeFn(body);
1532
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(body);
1615
+ const { locale, status = "draft" } = await getDocumentLocaleAndStatus(body);
1533
1616
  return documentManager2.create(model, {
1534
1617
  data: sanitizedBody,
1535
1618
  locale,
@@ -1548,7 +1631,7 @@ const updateDocument = async (ctx, opts) => {
1548
1631
  }
1549
1632
  const permissionQuery = await permissionChecker2.sanitizedQuery.update(ctx.query);
1550
1633
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1551
- const { locale } = getDocumentLocaleAndStatus(body);
1634
+ const { locale } = await getDocumentLocaleAndStatus(body);
1552
1635
  const [documentVersion, documentExists] = await Promise.all([
1553
1636
  documentManager2.findOne(id, model, { populate, locale, status: "draft" }),
1554
1637
  documentManager2.exists(model, id)
@@ -1586,8 +1669,8 @@ const collectionTypes = {
1586
1669
  }
1587
1670
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
1588
1671
  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(
1672
+ const { locale, status } = await getDocumentLocaleAndStatus(query);
1673
+ const { results: documents, pagination: pagination2 } = await documentManager2.findPage(
1591
1674
  { ...permissionQuery, populate, locale, status },
1592
1675
  model
1593
1676
  );
@@ -1608,21 +1691,20 @@ const collectionTypes = {
1608
1691
  );
1609
1692
  ctx.body = {
1610
1693
  results,
1611
- pagination
1694
+ pagination: pagination2
1612
1695
  };
1613
1696
  },
1614
1697
  async findOne(ctx) {
1615
1698
  const { userAbility } = ctx.state;
1616
1699
  const { model, id } = ctx.params;
1617
1700
  const documentManager2 = getService$1("document-manager");
1618
- const documentMetadata2 = getService$1("document-metadata");
1619
1701
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1620
1702
  if (permissionChecker2.cannot.read()) {
1621
1703
  return ctx.forbidden();
1622
1704
  }
1623
1705
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1624
1706
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1625
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
1707
+ const { locale, status = "draft" } = await getDocumentLocaleAndStatus(ctx.query);
1626
1708
  const version = await documentManager2.findOne(id, model, {
1627
1709
  populate,
1628
1710
  locale,
@@ -1633,8 +1715,10 @@ const collectionTypes = {
1633
1715
  if (!exists) {
1634
1716
  return ctx.notFound();
1635
1717
  }
1636
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
1718
+ const { meta } = await formatDocumentWithMetadata(
1719
+ permissionChecker2,
1637
1720
  model,
1721
+ // @ts-expect-error TODO: fix
1638
1722
  { id, locale, publishedAt: null },
1639
1723
  { availableLocales: true, availableStatus: false }
1640
1724
  );
@@ -1645,12 +1729,11 @@ const collectionTypes = {
1645
1729
  return ctx.forbidden();
1646
1730
  }
1647
1731
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
1648
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1732
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1649
1733
  },
1650
1734
  async create(ctx) {
1651
1735
  const { userAbility } = ctx.state;
1652
1736
  const { model } = ctx.params;
1653
- const documentMetadata2 = getService$1("document-metadata");
1654
1737
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1655
1738
  const [totalEntries, document] = await Promise.all([
1656
1739
  strapi.db.query(model).count(),
@@ -1658,7 +1741,7 @@ const collectionTypes = {
1658
1741
  ]);
1659
1742
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
1660
1743
  ctx.status = 201;
1661
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1744
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1662
1745
  // Empty metadata as it's not relevant for a new document
1663
1746
  availableLocales: false,
1664
1747
  availableStatus: false
@@ -1672,25 +1755,23 @@ const collectionTypes = {
1672
1755
  async update(ctx) {
1673
1756
  const { userAbility } = ctx.state;
1674
1757
  const { model } = ctx.params;
1675
- const documentMetadata2 = getService$1("document-metadata");
1676
1758
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1677
1759
  const updatedVersion = await updateDocument(ctx);
1678
1760
  const sanitizedVersion = await permissionChecker2.sanitizeOutput(updatedVersion);
1679
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedVersion);
1761
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedVersion);
1680
1762
  },
1681
1763
  async clone(ctx) {
1682
1764
  const { userAbility, user } = ctx.state;
1683
1765
  const { model, sourceId: id } = ctx.params;
1684
1766
  const { body } = ctx.request;
1685
1767
  const documentManager2 = getService$1("document-manager");
1686
- const documentMetadata2 = getService$1("document-metadata");
1687
1768
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1688
1769
  if (permissionChecker2.cannot.create()) {
1689
1770
  return ctx.forbidden();
1690
1771
  }
1691
1772
  const permissionQuery = await permissionChecker2.sanitizedQuery.create(ctx.query);
1692
1773
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1693
- const { locale } = getDocumentLocaleAndStatus(body);
1774
+ const { locale } = await getDocumentLocaleAndStatus(body);
1694
1775
  const document = await documentManager2.findOne(id, model, {
1695
1776
  populate,
1696
1777
  locale,
@@ -1706,7 +1787,7 @@ const collectionTypes = {
1706
1787
  const sanitizedBody = await sanitizeFn(body);
1707
1788
  const clonedDocument = await documentManager2.clone(document.documentId, sanitizedBody, model);
1708
1789
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(clonedDocument);
1709
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1790
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1710
1791
  // Empty metadata as it's not relevant for a new document
1711
1792
  availableLocales: false,
1712
1793
  availableStatus: false
@@ -1735,7 +1816,7 @@ const collectionTypes = {
1735
1816
  }
1736
1817
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(ctx.query);
1737
1818
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1738
- const { locale } = getDocumentLocaleAndStatus(ctx.query);
1819
+ const { locale } = await getDocumentLocaleAndStatus(ctx.query);
1739
1820
  const documentLocales = await documentManager2.findLocales(id, model, { populate, locale });
1740
1821
  if (documentLocales.length === 0) {
1741
1822
  return ctx.notFound();
@@ -1757,7 +1838,6 @@ const collectionTypes = {
1757
1838
  const { id, model } = ctx.params;
1758
1839
  const { body } = ctx.request;
1759
1840
  const documentManager2 = getService$1("document-manager");
1760
- const documentMetadata2 = getService$1("document-metadata");
1761
1841
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1762
1842
  if (permissionChecker2.cannot.publish()) {
1763
1843
  return ctx.forbidden();
@@ -1769,21 +1849,25 @@ const collectionTypes = {
1769
1849
  if (permissionChecker2.cannot.publish(document)) {
1770
1850
  throw new errors.ForbiddenError();
1771
1851
  }
1772
- const { locale } = getDocumentLocaleAndStatus(body);
1773
- return documentManager2.publish(document.documentId, model, {
1852
+ const { locale } = await getDocumentLocaleAndStatus(body);
1853
+ const publishResult = await documentManager2.publish(document.documentId, model, {
1774
1854
  locale
1775
1855
  // TODO: Allow setting creator fields on publish
1776
1856
  // data: setCreatorFields({ user, isEdition: true })({}),
1777
1857
  });
1858
+ if (!publishResult || publishResult.length === 0) {
1859
+ throw new errors.NotFoundError("Document not found or already published.");
1860
+ }
1861
+ return publishResult[0];
1778
1862
  });
1779
1863
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
1780
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1864
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1781
1865
  },
1782
1866
  async bulkPublish(ctx) {
1783
1867
  const { userAbility } = ctx.state;
1784
1868
  const { model } = ctx.params;
1785
1869
  const { body } = ctx.request;
1786
- const { ids } = body;
1870
+ const { documentIds } = body;
1787
1871
  await validateBulkActionInput(body);
1788
1872
  const documentManager2 = getService$1("document-manager");
1789
1873
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1792,8 +1876,11 @@ const collectionTypes = {
1792
1876
  }
1793
1877
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1794
1878
  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);
1879
+ const { locale } = await getDocumentLocaleAndStatus(body, { allowMultipleLocales: true });
1880
+ const entityPromises = documentIds.map(
1881
+ (documentId) => documentManager2.findLocales(documentId, model, { populate, locale, isPublished: false })
1882
+ );
1883
+ const entities = (await Promise.all(entityPromises)).flat();
1797
1884
  for (const entity of entities) {
1798
1885
  if (!entity) {
1799
1886
  return ctx.notFound();
@@ -1802,24 +1889,25 @@ const collectionTypes = {
1802
1889
  return ctx.forbidden();
1803
1890
  }
1804
1891
  }
1805
- const { count } = await documentManager2.publishMany(entities, model);
1892
+ const count = await documentManager2.publishMany(model, documentIds, locale);
1806
1893
  ctx.body = { count };
1807
1894
  },
1808
1895
  async bulkUnpublish(ctx) {
1809
1896
  const { userAbility } = ctx.state;
1810
1897
  const { model } = ctx.params;
1811
1898
  const { body } = ctx.request;
1812
- const { ids } = body;
1899
+ const { documentIds } = body;
1813
1900
  await validateBulkActionInput(body);
1814
1901
  const documentManager2 = getService$1("document-manager");
1815
1902
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1816
1903
  if (permissionChecker2.cannot.unpublish()) {
1817
1904
  return ctx.forbidden();
1818
1905
  }
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);
1906
+ const { locale } = await getDocumentLocaleAndStatus(body);
1907
+ const entityPromises = documentIds.map(
1908
+ (documentId) => documentManager2.findLocales(documentId, model, { locale, isPublished: true })
1909
+ );
1910
+ const entities = (await Promise.all(entityPromises)).flat();
1823
1911
  for (const entity of entities) {
1824
1912
  if (!entity) {
1825
1913
  return ctx.notFound();
@@ -1828,7 +1916,8 @@ const collectionTypes = {
1828
1916
  return ctx.forbidden();
1829
1917
  }
1830
1918
  }
1831
- const { count } = await documentManager2.unpublishMany(entities, model);
1919
+ const entitiesIds = entities.map((document) => document.documentId);
1920
+ const { count } = await documentManager2.unpublishMany(entitiesIds, model, { locale });
1832
1921
  ctx.body = { count };
1833
1922
  },
1834
1923
  async unpublish(ctx) {
@@ -1838,7 +1927,6 @@ const collectionTypes = {
1838
1927
  body: { discardDraft, ...body }
1839
1928
  } = ctx.request;
1840
1929
  const documentManager2 = getService$1("document-manager");
1841
- const documentMetadata2 = getService$1("document-metadata");
1842
1930
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1843
1931
  if (permissionChecker2.cannot.unpublish()) {
1844
1932
  return ctx.forbidden();
@@ -1848,7 +1936,7 @@ const collectionTypes = {
1848
1936
  }
1849
1937
  const permissionQuery = await permissionChecker2.sanitizedQuery.unpublish(ctx.query);
1850
1938
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1851
- const { locale } = getDocumentLocaleAndStatus(body);
1939
+ const { locale } = await getDocumentLocaleAndStatus(body);
1852
1940
  const document = await documentManager2.findOne(id, model, {
1853
1941
  populate,
1854
1942
  locale,
@@ -1870,7 +1958,7 @@ const collectionTypes = {
1870
1958
  ctx.body = await async.pipe(
1871
1959
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
1872
1960
  permissionChecker2.sanitizeOutput,
1873
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
1961
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1874
1962
  )(document);
1875
1963
  });
1876
1964
  },
@@ -1879,14 +1967,13 @@ const collectionTypes = {
1879
1967
  const { id, model } = ctx.params;
1880
1968
  const { body } = ctx.request;
1881
1969
  const documentManager2 = getService$1("document-manager");
1882
- const documentMetadata2 = getService$1("document-metadata");
1883
1970
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1884
1971
  if (permissionChecker2.cannot.discard()) {
1885
1972
  return ctx.forbidden();
1886
1973
  }
1887
1974
  const permissionQuery = await permissionChecker2.sanitizedQuery.discard(ctx.query);
1888
1975
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1889
- const { locale } = getDocumentLocaleAndStatus(body);
1976
+ const { locale } = await getDocumentLocaleAndStatus(body);
1890
1977
  const document = await documentManager2.findOne(id, model, {
1891
1978
  populate,
1892
1979
  locale,
@@ -1901,14 +1988,14 @@ const collectionTypes = {
1901
1988
  ctx.body = await async.pipe(
1902
1989
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
1903
1990
  permissionChecker2.sanitizeOutput,
1904
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
1991
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1905
1992
  )(document);
1906
1993
  },
1907
1994
  async bulkDelete(ctx) {
1908
1995
  const { userAbility } = ctx.state;
1909
1996
  const { model } = ctx.params;
1910
1997
  const { query, body } = ctx.request;
1911
- const { ids } = body;
1998
+ const { documentIds } = body;
1912
1999
  await validateBulkActionInput(body);
1913
2000
  const documentManager2 = getService$1("document-manager");
1914
2001
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1916,14 +2003,22 @@ const collectionTypes = {
1916
2003
  return ctx.forbidden();
1917
2004
  }
1918
2005
  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 || [])
2006
+ const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2007
+ const { locale } = await getDocumentLocaleAndStatus(body);
2008
+ const documentLocales = await documentManager2.findLocales(documentIds, model, {
2009
+ populate,
2010
+ locale
2011
+ });
2012
+ if (documentLocales.length === 0) {
2013
+ return ctx.notFound();
2014
+ }
2015
+ for (const document of documentLocales) {
2016
+ if (permissionChecker2.cannot.delete(document)) {
2017
+ return ctx.forbidden();
1924
2018
  }
1925
- };
1926
- const { count } = await documentManager2.deleteMany(params, model);
2019
+ }
2020
+ const localeDocumentsIds = documentLocales.map((document) => document.documentId);
2021
+ const { count } = await documentManager2.deleteMany(localeDocumentsIds, model, { locale });
1927
2022
  ctx.body = { count };
1928
2023
  },
1929
2024
  async countDraftRelations(ctx) {
@@ -1936,7 +2031,7 @@ const collectionTypes = {
1936
2031
  }
1937
2032
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1938
2033
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1939
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
2034
+ const { locale, status = "draft" } = await getDocumentLocaleAndStatus(ctx.query);
1940
2035
  const entity = await documentManager2.findOne(id, model, { populate, locale, status });
1941
2036
  if (!entity) {
1942
2037
  return ctx.notFound();
@@ -1951,7 +2046,7 @@ const collectionTypes = {
1951
2046
  },
1952
2047
  async countManyEntriesDraftRelations(ctx) {
1953
2048
  const { userAbility } = ctx.state;
1954
- const ids = ctx.request.query.ids;
2049
+ const ids = ctx.request.query.documentIds;
1955
2050
  const locale = ctx.request.query.locale;
1956
2051
  const { model } = ctx.params;
1957
2052
  const documentManager2 = getService$1("document-manager");
@@ -1962,7 +2057,7 @@ const collectionTypes = {
1962
2057
  const entities = await documentManager2.findMany(
1963
2058
  {
1964
2059
  filters: {
1965
- id: ids
2060
+ documentId: ids
1966
2061
  },
1967
2062
  locale
1968
2063
  },
@@ -2464,7 +2559,7 @@ const createOrUpdateDocument = async (ctx, opts) => {
2464
2559
  throw new errors.ForbiddenError();
2465
2560
  }
2466
2561
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.update(query);
2467
- const { locale } = getDocumentLocaleAndStatus(body);
2562
+ const { locale } = await getDocumentLocaleAndStatus(body);
2468
2563
  const [documentVersion, otherDocumentVersion] = await Promise.all([
2469
2564
  findDocument(sanitizedQuery, model, { locale, status: "draft" }),
2470
2565
  // Find the first document to check if it exists
@@ -2501,12 +2596,11 @@ const singleTypes = {
2501
2596
  const { model } = ctx.params;
2502
2597
  const { query = {} } = ctx.request;
2503
2598
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2504
- const documentMetadata2 = getService$1("document-metadata");
2505
2599
  if (permissionChecker2.cannot.read()) {
2506
2600
  return ctx.forbidden();
2507
2601
  }
2508
2602
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
2509
- const { locale, status } = getDocumentLocaleAndStatus(query);
2603
+ const { locale, status } = await getDocumentLocaleAndStatus(query);
2510
2604
  const version = await findDocument(permissionQuery, model, { locale, status });
2511
2605
  if (!version) {
2512
2606
  if (permissionChecker2.cannot.create()) {
@@ -2516,8 +2610,10 @@ const singleTypes = {
2516
2610
  if (!document) {
2517
2611
  return ctx.notFound();
2518
2612
  }
2519
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
2613
+ const { meta } = await formatDocumentWithMetadata(
2614
+ permissionChecker2,
2520
2615
  model,
2616
+ // @ts-expect-error - fix types
2521
2617
  { id: document.documentId, locale, publishedAt: null },
2522
2618
  { availableLocales: true, availableStatus: false }
2523
2619
  );
@@ -2528,16 +2624,15 @@ const singleTypes = {
2528
2624
  return ctx.forbidden();
2529
2625
  }
2530
2626
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
2531
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2627
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2532
2628
  },
2533
2629
  async createOrUpdate(ctx) {
2534
2630
  const { userAbility } = ctx.state;
2535
2631
  const { model } = ctx.params;
2536
- const documentMetadata2 = getService$1("document-metadata");
2537
2632
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2538
2633
  const document = await createOrUpdateDocument(ctx);
2539
2634
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
2540
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2635
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2541
2636
  },
2542
2637
  async delete(ctx) {
2543
2638
  const { userAbility } = ctx.state;
@@ -2550,7 +2645,7 @@ const singleTypes = {
2550
2645
  }
2551
2646
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.delete(query);
2552
2647
  const populate = await buildPopulateFromQuery(sanitizedQuery, model);
2553
- const { locale } = getDocumentLocaleAndStatus(query);
2648
+ const { locale } = await getDocumentLocaleAndStatus(query);
2554
2649
  const documentLocales = await documentManager2.findLocales(void 0, model, {
2555
2650
  populate,
2556
2651
  locale
@@ -2573,7 +2668,6 @@ const singleTypes = {
2573
2668
  const { model } = ctx.params;
2574
2669
  const { query = {} } = ctx.request;
2575
2670
  const documentManager2 = getService$1("document-manager");
2576
- const documentMetadata2 = getService$1("document-metadata");
2577
2671
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2578
2672
  if (permissionChecker2.cannot.publish()) {
2579
2673
  return ctx.forbidden();
@@ -2588,11 +2682,12 @@ const singleTypes = {
2588
2682
  if (permissionChecker2.cannot.publish(document)) {
2589
2683
  throw new errors.ForbiddenError();
2590
2684
  }
2591
- const { locale } = getDocumentLocaleAndStatus(document);
2592
- return documentManager2.publish(document.documentId, model, { locale });
2685
+ const { locale } = await getDocumentLocaleAndStatus(document);
2686
+ const publishResult = await documentManager2.publish(document.documentId, model, { locale });
2687
+ return publishResult.at(0);
2593
2688
  });
2594
2689
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
2595
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2690
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2596
2691
  },
2597
2692
  async unpublish(ctx) {
2598
2693
  const { userAbility } = ctx.state;
@@ -2602,7 +2697,6 @@ const singleTypes = {
2602
2697
  query = {}
2603
2698
  } = ctx.request;
2604
2699
  const documentManager2 = getService$1("document-manager");
2605
- const documentMetadata2 = getService$1("document-metadata");
2606
2700
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2607
2701
  if (permissionChecker2.cannot.unpublish()) {
2608
2702
  return ctx.forbidden();
@@ -2611,7 +2705,7 @@ const singleTypes = {
2611
2705
  return ctx.forbidden();
2612
2706
  }
2613
2707
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.unpublish(query);
2614
- const { locale } = getDocumentLocaleAndStatus(body);
2708
+ const { locale } = await getDocumentLocaleAndStatus(body);
2615
2709
  const document = await findDocument(sanitizedQuery, model, { locale });
2616
2710
  if (!document) {
2617
2711
  return ctx.notFound();
@@ -2629,7 +2723,7 @@ const singleTypes = {
2629
2723
  ctx.body = await async.pipe(
2630
2724
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
2631
2725
  permissionChecker2.sanitizeOutput,
2632
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2726
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2633
2727
  )(document);
2634
2728
  });
2635
2729
  },
@@ -2638,13 +2732,12 @@ const singleTypes = {
2638
2732
  const { model } = ctx.params;
2639
2733
  const { body, query = {} } = ctx.request;
2640
2734
  const documentManager2 = getService$1("document-manager");
2641
- const documentMetadata2 = getService$1("document-metadata");
2642
2735
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2643
2736
  if (permissionChecker2.cannot.discard()) {
2644
2737
  return ctx.forbidden();
2645
2738
  }
2646
2739
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.discard(query);
2647
- const { locale } = getDocumentLocaleAndStatus(body);
2740
+ const { locale } = await getDocumentLocaleAndStatus(body);
2648
2741
  const document = await findDocument(sanitizedQuery, model, { locale, status: "published" });
2649
2742
  if (!document) {
2650
2743
  return ctx.notFound();
@@ -2655,7 +2748,7 @@ const singleTypes = {
2655
2748
  ctx.body = await async.pipe(
2656
2749
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
2657
2750
  permissionChecker2.sanitizeOutput,
2658
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2751
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2659
2752
  )(document);
2660
2753
  },
2661
2754
  async countDraftRelations(ctx) {
@@ -2664,7 +2757,7 @@ const singleTypes = {
2664
2757
  const { query } = ctx.request;
2665
2758
  const documentManager2 = getService$1("document-manager");
2666
2759
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2667
- const { locale } = getDocumentLocaleAndStatus(query);
2760
+ const { locale } = await getDocumentLocaleAndStatus(query);
2668
2761
  if (permissionChecker2.cannot.read()) {
2669
2762
  return ctx.forbidden();
2670
2763
  }
@@ -2685,7 +2778,7 @@ const uid$1 = {
2685
2778
  async generateUID(ctx) {
2686
2779
  const { contentTypeUID, field, data } = await validateGenerateUIDInput(ctx.request.body);
2687
2780
  const { query = {} } = ctx.request;
2688
- const { locale } = getDocumentLocaleAndStatus(query);
2781
+ const { locale } = await getDocumentLocaleAndStatus(query);
2689
2782
  await validateUIDField(contentTypeUID, field);
2690
2783
  const uidService = getService$1("uid");
2691
2784
  ctx.body = {
@@ -2697,7 +2790,7 @@ const uid$1 = {
2697
2790
  ctx.request.body
2698
2791
  );
2699
2792
  const { query = {} } = ctx.request;
2700
- const { locale } = getDocumentLocaleAndStatus(query);
2793
+ const { locale } = await getDocumentLocaleAndStatus(query);
2701
2794
  await validateUIDField(contentTypeUID, field);
2702
2795
  const uidService = getService$1("uid");
2703
2796
  const isAvailable = await uidService.checkUIDAvailability({
@@ -3488,7 +3581,7 @@ const permission = ({ strapi: strapi2 }) => ({
3488
3581
  await strapi2.service("admin::permission").actionProvider.registerMany(actions);
3489
3582
  }
3490
3583
  });
3491
- const { isVisibleAttribute: isVisibleAttribute$1 } = strapiUtils.contentTypes;
3584
+ const { isVisibleAttribute: isVisibleAttribute$1, isScalarAttribute, getDoesAttributeRequireValidation } = strapiUtils.contentTypes;
3492
3585
  const { isAnyToMany } = strapiUtils.relations;
3493
3586
  const { PUBLISHED_AT_ATTRIBUTE: PUBLISHED_AT_ATTRIBUTE$1 } = strapiUtils.contentTypes.constants;
3494
3587
  const isMorphToRelation = (attribute) => isRelation(attribute) && attribute.relation.includes("morphTo");
@@ -3579,6 +3672,42 @@ const getDeepPopulate = (uid2, {
3579
3672
  {}
3580
3673
  );
3581
3674
  };
3675
+ const getValidatableFieldsPopulate = (uid2, {
3676
+ initialPopulate = {},
3677
+ countMany = false,
3678
+ countOne = false,
3679
+ maxLevel = Infinity
3680
+ } = {}, level = 1) => {
3681
+ if (level > maxLevel) {
3682
+ return {};
3683
+ }
3684
+ const model = strapi.getModel(uid2);
3685
+ return Object.entries(model.attributes).reduce((populateAcc, [attributeName, attribute]) => {
3686
+ if (!getDoesAttributeRequireValidation(attribute)) {
3687
+ return populateAcc;
3688
+ }
3689
+ if (isScalarAttribute(attribute)) {
3690
+ return merge(populateAcc, {
3691
+ [attributeName]: true
3692
+ });
3693
+ }
3694
+ return merge(
3695
+ populateAcc,
3696
+ getPopulateFor(
3697
+ attributeName,
3698
+ model,
3699
+ {
3700
+ // @ts-expect-error - improve types
3701
+ initialPopulate: initialPopulate?.[attributeName],
3702
+ countMany,
3703
+ countOne,
3704
+ maxLevel
3705
+ },
3706
+ level
3707
+ )
3708
+ );
3709
+ }, {});
3710
+ };
3582
3711
  const getDeepPopulateDraftCount = (uid2) => {
3583
3712
  const model = strapi.getModel(uid2);
3584
3713
  let hasRelations = false;
@@ -3807,41 +3936,70 @@ const AVAILABLE_STATUS_FIELDS = [
3807
3936
  "updatedBy",
3808
3937
  "status"
3809
3938
  ];
3810
- const AVAILABLE_LOCALES_FIELDS = ["id", "locale", "updatedAt", "createdAt", "status"];
3939
+ const AVAILABLE_LOCALES_FIELDS = [
3940
+ "id",
3941
+ "locale",
3942
+ "updatedAt",
3943
+ "createdAt",
3944
+ "status",
3945
+ "publishedAt",
3946
+ "documentId"
3947
+ ];
3811
3948
  const CONTENT_MANAGER_STATUS = {
3812
3949
  PUBLISHED: "published",
3813
3950
  DRAFT: "draft",
3814
3951
  MODIFIED: "modified"
3815
3952
  };
3816
- const areDatesEqual = (date1, date2, threshold) => {
3817
- if (!date1 || !date2) {
3953
+ const getIsVersionLatestModification = (version, otherVersion) => {
3954
+ if (!version || !version.updatedAt) {
3818
3955
  return false;
3819
3956
  }
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;
3957
+ const versionUpdatedAt = version?.updatedAt ? new Date(version.updatedAt).getTime() : 0;
3958
+ const otherUpdatedAt = otherVersion?.updatedAt ? new Date(otherVersion.updatedAt).getTime() : 0;
3959
+ return versionUpdatedAt > otherUpdatedAt;
3824
3960
  };
3825
3961
  const documentMetadata = ({ strapi: strapi2 }) => ({
3826
3962
  /**
3827
3963
  * Returns available locales of a document for the current status
3828
3964
  */
3829
- getAvailableLocales(uid2, version, allVersions) {
3965
+ async getAvailableLocales(uid2, version, allVersions, validatableFields = []) {
3830
3966
  const versionsByLocale = groupBy("locale", allVersions);
3831
3967
  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]);
3968
+ const model = strapi2.getModel(uid2);
3969
+ const keysToKeep = [...AVAILABLE_LOCALES_FIELDS, ...validatableFields];
3970
+ const traversalFunction = async (localeVersion) => traverseEntity(
3971
+ ({ key }, { remove }) => {
3972
+ if (keysToKeep.includes(key)) {
3973
+ return;
3974
+ }
3975
+ remove(key);
3976
+ },
3977
+ { schema: model, getModel: strapi2.getModel.bind(strapi2) },
3978
+ // @ts-expect-error fix types DocumentVersion incompatible with Data
3979
+ localeVersion
3980
+ );
3981
+ const mappingResult = await async.map(
3982
+ Object.values(versionsByLocale),
3983
+ async (localeVersions) => {
3984
+ const mappedLocaleVersions = await async.map(
3985
+ localeVersions,
3986
+ traversalFunction
3987
+ );
3988
+ if (!contentTypes$1.hasDraftAndPublish(model)) {
3989
+ return mappedLocaleVersions[0];
3990
+ }
3991
+ const draftVersion = mappedLocaleVersions.find((v) => v.publishedAt === null);
3992
+ const otherVersions = mappedLocaleVersions.filter((v) => v.id !== draftVersion?.id);
3993
+ if (!draftVersion) {
3994
+ return;
3995
+ }
3996
+ return {
3997
+ ...draftVersion,
3998
+ status: this.getStatus(draftVersion, otherVersions)
3999
+ };
3835
4000
  }
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);
4001
+ );
4002
+ return mappingResult.filter(Boolean);
3845
4003
  },
3846
4004
  /**
3847
4005
  * Returns available status of a document for the current locale
@@ -3879,26 +4037,37 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3879
4037
  });
3880
4038
  },
3881
4039
  getStatus(version, otherDocumentStatuses) {
3882
- const isDraft = version.publishedAt === null;
3883
- if (!otherDocumentStatuses?.length) {
3884
- return isDraft ? CONTENT_MANAGER_STATUS.DRAFT : CONTENT_MANAGER_STATUS.PUBLISHED;
4040
+ let draftVersion;
4041
+ let publishedVersion;
4042
+ if (version.publishedAt) {
4043
+ publishedVersion = version;
4044
+ } else {
4045
+ draftVersion = version;
3885
4046
  }
3886
- if (isDraft) {
3887
- const publishedVersion = otherDocumentStatuses?.find((d) => d.publishedAt !== null);
3888
- if (!publishedVersion) {
3889
- return CONTENT_MANAGER_STATUS.DRAFT;
3890
- }
4047
+ const otherVersion = otherDocumentStatuses?.at(0);
4048
+ if (otherVersion?.publishedAt) {
4049
+ publishedVersion = otherVersion;
4050
+ } else if (otherVersion) {
4051
+ draftVersion = otherVersion;
3891
4052
  }
3892
- if (areDatesEqual(version.updatedAt, otherDocumentStatuses.at(0)?.updatedAt, 500)) {
4053
+ if (!draftVersion)
3893
4054
  return CONTENT_MANAGER_STATUS.PUBLISHED;
3894
- }
3895
- return CONTENT_MANAGER_STATUS.MODIFIED;
4055
+ if (!publishedVersion)
4056
+ return CONTENT_MANAGER_STATUS.DRAFT;
4057
+ const isDraftModified = getIsVersionLatestModification(draftVersion, publishedVersion);
4058
+ return isDraftModified ? CONTENT_MANAGER_STATUS.MODIFIED : CONTENT_MANAGER_STATUS.PUBLISHED;
3896
4059
  },
4060
+ // TODO is it necessary to return metadata on every page of the CM
4061
+ // We could refactor this so the locales are only loaded when they're
4062
+ // needed. e.g. in the bulk locale action modal.
3897
4063
  async getMetadata(uid2, version, { availableLocales = true, availableStatus = true } = {}) {
4064
+ const populate = getValidatableFieldsPopulate(uid2);
3898
4065
  const versions = await strapi2.db.query(uid2).findMany({
3899
4066
  where: { documentId: version.documentId },
3900
- select: ["createdAt", "updatedAt", "locale", "publishedAt", "documentId"],
3901
4067
  populate: {
4068
+ // Populate only fields that require validation for bulk locale actions
4069
+ ...populate,
4070
+ // NOTE: creator fields are selected in this way to avoid exposing sensitive data
3902
4071
  createdBy: {
3903
4072
  select: ["id", "firstname", "lastname", "email"]
3904
4073
  },
@@ -3907,7 +4076,7 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3907
4076
  }
3908
4077
  }
3909
4078
  });
3910
- const availableLocalesResult = availableLocales ? this.getAvailableLocales(uid2, version, versions) : [];
4079
+ const availableLocalesResult = availableLocales ? await this.getAvailableLocales(uid2, version, versions, Object.keys(populate)) : [];
3911
4080
  const availableStatusResult = availableStatus ? this.getAvailableStatus(version, versions) : null;
3912
4081
  return {
3913
4082
  availableLocales: availableLocalesResult,
@@ -3920,8 +4089,9 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3920
4089
  * - Available status of the document for the current locale
3921
4090
  */
3922
4091
  async formatDocumentWithMetadata(uid2, document, opts = {}) {
3923
- if (!document)
4092
+ if (!document) {
3924
4093
  return document;
4094
+ }
3925
4095
  const hasDraftAndPublish = contentTypes$1.hasDraftAndPublish(strapi2.getModel(uid2));
3926
4096
  if (!hasDraftAndPublish) {
3927
4097
  opts.availableStatus = false;
@@ -3971,26 +4141,9 @@ const sumDraftCounts = (entity, uid2) => {
3971
4141
  }, 0);
3972
4142
  };
3973
4143
  const { ApplicationError } = errors;
3974
- const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = ALLOWED_WEBHOOK_EVENTS;
3975
4144
  const { PUBLISHED_AT_ATTRIBUTE } = contentTypes$1.constants;
3976
4145
  const omitPublishedAtField = omit(PUBLISHED_AT_ATTRIBUTE);
3977
4146
  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
4147
  const documentManager = ({ strapi: strapi2 }) => {
3995
4148
  return {
3996
4149
  async findOne(id, uid2, opts = {}) {
@@ -4009,6 +4162,9 @@ const documentManager = ({ strapi: strapi2 }) => {
4009
4162
  } else if (opts.locale && opts.locale !== "*") {
4010
4163
  where.locale = opts.locale;
4011
4164
  }
4165
+ if (typeof opts.isPublished === "boolean") {
4166
+ where.publishedAt = { $notNull: opts.isPublished };
4167
+ }
4012
4168
  return strapi2.db.query(uid2).findMany({ populate: opts.populate, where });
4013
4169
  },
4014
4170
  async findMany(opts, uid2) {
@@ -4016,20 +4172,16 @@ const documentManager = ({ strapi: strapi2 }) => {
4016
4172
  return strapi2.documents(uid2).findMany(params);
4017
4173
  },
4018
4174
  async findPage(opts, uid2) {
4019
- const page = Number(opts?.page) || 1;
4020
- const pageSize = Number(opts?.pageSize) || 10;
4175
+ const params = pagination.withDefaultPagination(opts || {}, {
4176
+ maxLimit: 1e3
4177
+ });
4021
4178
  const [documents, total = 0] = await Promise.all([
4022
- strapi2.documents(uid2).findMany(opts),
4023
- strapi2.documents(uid2).count(opts)
4179
+ strapi2.documents(uid2).findMany(params),
4180
+ strapi2.documents(uid2).count(params)
4024
4181
  ]);
4025
4182
  return {
4026
4183
  results: documents,
4027
- pagination: {
4028
- page,
4029
- pageSize,
4030
- pageCount: Math.ceil(total / pageSize),
4031
- total
4032
- }
4184
+ pagination: pagination.transformPagedPaginationInfo(params, total)
4033
4185
  };
4034
4186
  },
4035
4187
  async create(uid2, opts = {}) {
@@ -4075,70 +4227,36 @@ const documentManager = ({ strapi: strapi2 }) => {
4075
4227
  return {};
4076
4228
  },
4077
4229
  // 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 };
4230
+ async deleteMany(documentIds, uid2, opts = {}) {
4231
+ const deletedEntries = await strapi2.db.transaction(async () => {
4232
+ return Promise.all(documentIds.map(async (id) => this.delete(id, uid2, opts)));
4233
+ });
4234
+ return { count: deletedEntries.length };
4084
4235
  },
4085
4236
  async publish(id, uid2, opts = {}) {
4086
4237
  const populate = await buildDeepPopulate(uid2);
4087
4238
  const params = { ...opts, populate };
4088
- return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries.at(0));
4239
+ return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries);
4089
4240
  },
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
4241
+ async publishMany(uid2, documentIds, locale) {
4242
+ return strapi2.db.transaction(async () => {
4243
+ const results = await Promise.all(
4244
+ documentIds.map((documentId) => this.publish(documentId, uid2, { locale }))
4245
+ );
4246
+ const publishedEntitiesCount = results.flat().filter(Boolean).length;
4247
+ return publishedEntitiesCount;
4116
4248
  });
4117
- await Promise.all(
4118
- publishedEntities.map((doc) => emitEvent(uid2, ENTRY_PUBLISH, doc))
4119
- );
4120
- return publishedEntitiesCount;
4121
4249
  },
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
4250
+ async unpublishMany(documentIds, uid2, opts = {}) {
4251
+ const unpublishedEntries = await strapi2.db.transaction(async () => {
4252
+ return Promise.all(
4253
+ documentIds.map(
4254
+ (id) => strapi2.documents(uid2).unpublish({ ...opts, documentId: id }).then((result) => result?.entries)
4255
+ )
4256
+ );
4137
4257
  });
4138
- await Promise.all(
4139
- unpublishedEntities.map((doc) => emitEvent(uid2, ENTRY_UNPUBLISH, doc))
4140
- );
4141
- return unpublishedEntitiesCount;
4258
+ const unpublishedEntitiesCount = unpublishedEntries.flat().filter(Boolean).length;
4259
+ return { count: unpublishedEntitiesCount };
4142
4260
  },
4143
4261
  async unpublish(id, uid2, opts = {}) {
4144
4262
  const populate = await buildDeepPopulate(uid2);
@@ -4163,16 +4281,20 @@ const documentManager = ({ strapi: strapi2 }) => {
4163
4281
  }
4164
4282
  return sumDraftCounts(document, uid2);
4165
4283
  },
4166
- async countManyEntriesDraftRelations(ids, uid2, locale) {
4284
+ async countManyEntriesDraftRelations(documentIds, uid2, locale) {
4167
4285
  const { populate, hasRelations } = getDeepPopulateDraftCount(uid2);
4168
4286
  if (!hasRelations) {
4169
4287
  return 0;
4170
4288
  }
4289
+ let localeFilter = {};
4290
+ if (locale) {
4291
+ localeFilter = Array.isArray(locale) ? { locale: { $in: locale } } : { locale };
4292
+ }
4171
4293
  const entities = await strapi2.db.query(uid2).findMany({
4172
4294
  populate,
4173
4295
  where: {
4174
- id: { $in: ids },
4175
- ...locale ? { locale } : {}
4296
+ documentId: { $in: documentIds },
4297
+ ...localeFilter
4176
4298
  }
4177
4299
  });
4178
4300
  const totalNumberDraftRelations = entities.reduce(