@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
@@ -138,40 +138,65 @@ const FIELDS_TO_IGNORE = [
138
138
  "strapi_stage",
139
139
  "strapi_assignee"
140
140
  ];
141
- const getSchemaAttributesDiff = (versionSchemaAttributes, contentTypeSchemaAttributes) => {
142
- const sanitizedContentTypeSchemaAttributes = fp.omit(FIELDS_TO_IGNORE, contentTypeSchemaAttributes);
143
- const reduceDifferenceToAttributesObject = (diffKeys, source) => {
144
- return diffKeys.reduce((previousAttributesObject, diffKey) => {
145
- previousAttributesObject[diffKey] = source[diffKey];
146
- return previousAttributesObject;
147
- }, {});
148
- };
149
- const versionSchemaKeys = Object.keys(versionSchemaAttributes);
150
- const contentTypeSchemaAttributesKeys = Object.keys(sanitizedContentTypeSchemaAttributes);
151
- const uniqueToContentType = fp.difference(contentTypeSchemaAttributesKeys, versionSchemaKeys);
152
- const added = reduceDifferenceToAttributesObject(
153
- uniqueToContentType,
154
- sanitizedContentTypeSchemaAttributes
155
- );
156
- const uniqueToVersion = fp.difference(versionSchemaKeys, contentTypeSchemaAttributesKeys);
157
- const removed = reduceDifferenceToAttributesObject(uniqueToVersion, versionSchemaAttributes);
158
- return { added, removed };
159
- };
160
141
  const DEFAULT_RETENTION_DAYS = 90;
161
- const createHistoryService = ({ strapi: strapi2 }) => {
162
- const state = {
163
- deleteExpiredJob: null,
164
- isInitialized: false
142
+ const createServiceUtils = ({ strapi: strapi2 }) => {
143
+ const getSchemaAttributesDiff = (versionSchemaAttributes, contentTypeSchemaAttributes) => {
144
+ const sanitizedContentTypeSchemaAttributes = fp.omit(
145
+ FIELDS_TO_IGNORE,
146
+ contentTypeSchemaAttributes
147
+ );
148
+ const reduceDifferenceToAttributesObject = (diffKeys, source) => {
149
+ return diffKeys.reduce(
150
+ (previousAttributesObject, diffKey) => {
151
+ previousAttributesObject[diffKey] = source[diffKey];
152
+ return previousAttributesObject;
153
+ },
154
+ {}
155
+ );
156
+ };
157
+ const versionSchemaKeys = Object.keys(versionSchemaAttributes);
158
+ const contentTypeSchemaAttributesKeys = Object.keys(sanitizedContentTypeSchemaAttributes);
159
+ const uniqueToContentType = fp.difference(contentTypeSchemaAttributesKeys, versionSchemaKeys);
160
+ const added = reduceDifferenceToAttributesObject(
161
+ uniqueToContentType,
162
+ sanitizedContentTypeSchemaAttributes
163
+ );
164
+ const uniqueToVersion = fp.difference(versionSchemaKeys, contentTypeSchemaAttributesKeys);
165
+ const removed = reduceDifferenceToAttributesObject(uniqueToVersion, versionSchemaAttributes);
166
+ return { added, removed };
165
167
  };
166
- const query = strapi2.db.query(HISTORY_VERSION_UID);
167
- const getRetentionDays = (strapi22) => {
168
- const featureConfig = strapi22.ee.features.get("cms-content-history");
169
- const licenseRetentionDays = typeof featureConfig === "object" && featureConfig?.options.retentionDays;
170
- const userRetentionDays = strapi22.config.get("admin.history.retentionDays");
171
- if (userRetentionDays && userRetentionDays < licenseRetentionDays) {
172
- return userRetentionDays;
168
+ const getRelationRestoreValue = async (versionRelationData, attribute) => {
169
+ if (Array.isArray(versionRelationData)) {
170
+ if (versionRelationData.length === 0)
171
+ return versionRelationData;
172
+ const existingAndMissingRelations = await Promise.all(
173
+ versionRelationData.map((relation) => {
174
+ return strapi2.documents(attribute.target).findOne({
175
+ documentId: relation.documentId,
176
+ locale: relation.locale || void 0
177
+ });
178
+ })
179
+ );
180
+ return existingAndMissingRelations.filter(
181
+ (relation) => relation !== null
182
+ );
173
183
  }
174
- return Math.min(licenseRetentionDays, DEFAULT_RETENTION_DAYS);
184
+ return strapi2.documents(attribute.target).findOne({
185
+ documentId: versionRelationData.documentId,
186
+ locale: versionRelationData.locale || void 0
187
+ });
188
+ };
189
+ const getMediaRestoreValue = async (versionRelationData, attribute) => {
190
+ if (attribute.multiple) {
191
+ const existingAndMissingMedias = await Promise.all(
192
+ // @ts-expect-error Fix the type definitions so this isn't any
193
+ versionRelationData.map((media) => {
194
+ return strapi2.db.query("plugin::upload.file").findOne({ where: { id: media.id } });
195
+ })
196
+ );
197
+ return existingAndMissingMedias.filter((media) => media != null);
198
+ }
199
+ return strapi2.db.query("plugin::upload.file").findOne({ where: { id: versionRelationData.id } });
175
200
  };
176
201
  const localesService = strapi2.plugin("i18n")?.service("locales");
177
202
  const getDefaultLocale = async () => localesService ? localesService.getDefaultLocale() : null;
@@ -187,25 +212,39 @@ const createHistoryService = ({ strapi: strapi2 }) => {
187
212
  {}
188
213
  );
189
214
  };
215
+ const getRetentionDays = () => {
216
+ const featureConfig = strapi2.ee.features.get("cms-content-history");
217
+ const licenseRetentionDays = typeof featureConfig === "object" && featureConfig?.options.retentionDays;
218
+ const userRetentionDays = strapi2.config.get("admin.history.retentionDays");
219
+ if (userRetentionDays && userRetentionDays < licenseRetentionDays) {
220
+ return userRetentionDays;
221
+ }
222
+ return Math.min(licenseRetentionDays, DEFAULT_RETENTION_DAYS);
223
+ };
190
224
  const getVersionStatus = async (contentTypeUid, document) => {
191
225
  const documentMetadataService = strapi2.plugin("content-manager").service("document-metadata");
192
226
  const meta = await documentMetadataService.getMetadata(contentTypeUid, document);
193
227
  return documentMetadataService.getStatus(document, meta.availableStatus);
194
228
  };
195
- const getDeepPopulate2 = (uid2) => {
229
+ const getDeepPopulate2 = (uid2, useDatabaseSyntax = false) => {
196
230
  const model = strapi2.getModel(uid2);
197
231
  const attributes = Object.entries(model.attributes);
232
+ const fieldSelector = useDatabaseSyntax ? "select" : "fields";
198
233
  return attributes.reduce((acc, [attributeName, attribute]) => {
199
234
  switch (attribute.type) {
200
235
  case "relation": {
236
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
237
+ if (isMorphRelation) {
238
+ break;
239
+ }
201
240
  const isVisible2 = strapiUtils.contentTypes.isVisibleAttribute(model, attributeName);
202
241
  if (isVisible2) {
203
- acc[attributeName] = { fields: ["documentId", "locale", "publishedAt"] };
242
+ acc[attributeName] = { [fieldSelector]: ["documentId", "locale", "publishedAt"] };
204
243
  }
205
244
  break;
206
245
  }
207
246
  case "media": {
208
- acc[attributeName] = { fields: ["id"] };
247
+ acc[attributeName] = { [fieldSelector]: ["id"] };
209
248
  break;
210
249
  }
211
250
  case "component": {
@@ -228,80 +267,68 @@ const createHistoryService = ({ strapi: strapi2 }) => {
228
267
  return acc;
229
268
  }, {});
230
269
  };
231
- return {
232
- async bootstrap() {
233
- if (state.isInitialized) {
234
- return;
235
- }
236
- strapi2.documents.use(async (context, next) => {
237
- if (!strapi2.requestContext.get()?.request.url.startsWith("/content-manager")) {
238
- return next();
270
+ const buildMediaResponse = async (values) => {
271
+ return values.slice(0, 25).reduce(
272
+ async (currentRelationDataPromise, entry) => {
273
+ const currentRelationData = await currentRelationDataPromise;
274
+ if (!entry) {
275
+ return currentRelationData;
239
276
  }
240
- if (context.action !== "create" && context.action !== "update" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
241
- return next();
277
+ const relatedEntry = await strapi2.db.query("plugin::upload.file").findOne({ where: { id: entry.id } });
278
+ if (relatedEntry) {
279
+ currentRelationData.results.push(relatedEntry);
280
+ } else {
281
+ currentRelationData.meta.missingCount += 1;
242
282
  }
243
- const contentTypeUid = context.contentType.uid;
244
- if (!contentTypeUid.startsWith("api::")) {
245
- return next();
283
+ return currentRelationData;
284
+ },
285
+ Promise.resolve({
286
+ results: [],
287
+ meta: { missingCount: 0 }
288
+ })
289
+ );
290
+ };
291
+ const buildRelationReponse = async (values, attributeSchema) => {
292
+ return values.slice(0, 25).reduce(
293
+ async (currentRelationDataPromise, entry) => {
294
+ const currentRelationData = await currentRelationDataPromise;
295
+ if (!entry) {
296
+ return currentRelationData;
246
297
  }
247
- const result = await next();
248
- const documentContext = context.action === "create" ? { documentId: result.documentId, locale: context.params?.locale } : { documentId: context.params.documentId, locale: context.params?.locale };
249
- const defaultLocale = await getDefaultLocale();
250
- const locale = documentContext.locale || defaultLocale;
251
- const document = await strapi2.documents(contentTypeUid).findOne({
252
- documentId: documentContext.documentId,
253
- locale,
254
- populate: getDeepPopulate2(contentTypeUid)
255
- });
256
- const status = await getVersionStatus(contentTypeUid, document);
257
- const attributesSchema = strapi2.getModel(contentTypeUid).attributes;
258
- const componentsSchemas = Object.keys(
259
- attributesSchema
260
- ).reduce((currentComponentSchemas, key) => {
261
- const fieldSchema = attributesSchema[key];
262
- if (fieldSchema.type === "component") {
263
- const componentSchema = strapi2.getModel(fieldSchema.component).attributes;
264
- return {
265
- ...currentComponentSchemas,
266
- [fieldSchema.component]: componentSchema
267
- };
268
- }
269
- return currentComponentSchemas;
270
- }, {});
271
- await strapi2.db.transaction(async ({ onCommit }) => {
272
- onCommit(() => {
273
- this.createVersion({
274
- contentType: contentTypeUid,
275
- data: fp.omit(FIELDS_TO_IGNORE, document),
276
- schema: fp.omit(FIELDS_TO_IGNORE, attributesSchema),
277
- componentsSchemas,
278
- relatedDocumentId: documentContext.documentId,
279
- locale,
280
- status
281
- });
298
+ const relatedEntry = await strapi2.documents(attributeSchema.target).findOne({ documentId: entry.documentId, locale: entry.locale || void 0 });
299
+ if (relatedEntry) {
300
+ currentRelationData.results.push({
301
+ ...relatedEntry,
302
+ status: await getVersionStatus(attributeSchema.target, relatedEntry)
282
303
  });
283
- });
284
- return result;
285
- });
286
- const retentionDays = getRetentionDays(strapi2);
287
- state.deleteExpiredJob = nodeSchedule.scheduleJob("0 0 * * *", () => {
288
- const retentionDaysInMilliseconds = retentionDays * 24 * 60 * 60 * 1e3;
289
- const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
290
- query.deleteMany({
291
- where: {
292
- created_at: {
293
- $lt: expirationDate.toISOString()
294
- }
295
- }
296
- });
297
- });
298
- state.isInitialized = true;
299
- },
300
- async destroy() {
301
- if (state.deleteExpiredJob) {
302
- state.deleteExpiredJob.cancel();
303
- }
304
- },
304
+ } else {
305
+ currentRelationData.meta.missingCount += 1;
306
+ }
307
+ return currentRelationData;
308
+ },
309
+ Promise.resolve({
310
+ results: [],
311
+ meta: { missingCount: 0 }
312
+ })
313
+ );
314
+ };
315
+ return {
316
+ getSchemaAttributesDiff,
317
+ getRelationRestoreValue,
318
+ getMediaRestoreValue,
319
+ getDefaultLocale,
320
+ getLocaleDictionary,
321
+ getRetentionDays,
322
+ getVersionStatus,
323
+ getDeepPopulate: getDeepPopulate2,
324
+ buildMediaResponse,
325
+ buildRelationReponse
326
+ };
327
+ };
328
+ const createHistoryService = ({ strapi: strapi2 }) => {
329
+ const query = strapi2.db.query(HISTORY_VERSION_UID);
330
+ const serviceUtils = createServiceUtils({ strapi: strapi2 });
331
+ return {
305
332
  async createVersion(historyVersionData) {
306
333
  await query.create({
307
334
  data: {
@@ -312,7 +339,7 @@ const createHistoryService = ({ strapi: strapi2 }) => {
312
339
  });
313
340
  },
314
341
  async findVersionsPage(params) {
315
- const locale = params.query.locale || await getDefaultLocale();
342
+ const locale = params.query.locale || await serviceUtils.getDefaultLocale();
316
343
  const [{ results, pagination }, localeDictionary] = await Promise.all([
317
344
  query.findPage({
318
345
  ...params.query,
@@ -326,78 +353,34 @@ const createHistoryService = ({ strapi: strapi2 }) => {
326
353
  populate: ["createdBy"],
327
354
  orderBy: [{ createdAt: "desc" }]
328
355
  }),
329
- getLocaleDictionary()
356
+ serviceUtils.getLocaleDictionary()
330
357
  ]);
331
- const buildRelationReponse = async (values, attributeSchema) => {
332
- return values.slice(0, 25).reduce(
333
- async (currentRelationDataPromise, entry) => {
334
- const currentRelationData = await currentRelationDataPromise;
335
- if (!entry) {
336
- return currentRelationData;
337
- }
338
- const relatedEntry = await strapi2.documents(attributeSchema.target).findOne({ documentId: entry.documentId, locale: entry.locale || void 0 });
339
- const permissionChecker2 = getService$1("permission-checker").create({
340
- userAbility: params.state.userAbility,
341
- model: attributeSchema.target
342
- });
343
- const sanitizedEntry = await permissionChecker2.sanitizeOutput(relatedEntry);
344
- if (sanitizedEntry) {
345
- currentRelationData.results.push({
346
- ...sanitizedEntry,
347
- status: await getVersionStatus(attributeSchema.target, sanitizedEntry)
348
- });
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
- const buildMediaResponse = async (values) => {
361
- return values.slice(0, 25).reduce(
362
- async (currentRelationDataPromise, entry) => {
363
- const currentRelationData = await currentRelationDataPromise;
364
- if (!entry) {
365
- return currentRelationData;
366
- }
367
- const permissionChecker2 = getService$1("permission-checker").create({
368
- userAbility: params.state.userAbility,
369
- model: "plugin::upload.file"
370
- });
371
- const relatedEntry = await strapi2.db.query("plugin::upload.file").findOne({ where: { id: entry.id } });
372
- const sanitizedEntry = await permissionChecker2.sanitizeOutput(relatedEntry);
373
- if (sanitizedEntry) {
374
- currentRelationData.results.push(sanitizedEntry);
375
- } else {
376
- currentRelationData.meta.missingCount += 1;
377
- }
378
- return currentRelationData;
379
- },
380
- Promise.resolve({
381
- results: [],
382
- meta: { missingCount: 0 }
383
- })
384
- );
385
- };
386
358
  const populateEntryRelations = async (entry) => {
387
359
  const entryWithRelations = await Object.entries(entry.schema).reduce(
388
360
  async (currentDataWithRelations, [attributeKey, attributeSchema]) => {
389
361
  const attributeValue = entry.data[attributeKey];
390
362
  const attributeValues = Array.isArray(attributeValue) ? attributeValue : [attributeValue];
391
363
  if (attributeSchema.type === "media") {
364
+ const permissionChecker2 = getService$1("permission-checker").create({
365
+ userAbility: params.state.userAbility,
366
+ model: "plugin::upload.file"
367
+ });
368
+ const response = await serviceUtils.buildMediaResponse(attributeValues);
369
+ const sanitizedResults = await Promise.all(
370
+ response.results.map((media) => permissionChecker2.sanitizeOutput(media))
371
+ );
392
372
  return {
393
373
  ...await currentDataWithRelations,
394
- [attributeKey]: await buildMediaResponse(attributeValues)
374
+ [attributeKey]: {
375
+ results: sanitizedResults,
376
+ meta: response.meta
377
+ }
395
378
  };
396
379
  }
397
380
  if (attributeSchema.type === "relation" && attributeSchema.relation !== "morphToOne" && attributeSchema.relation !== "morphToMany") {
398
381
  if (attributeSchema.target === "admin::user") {
399
382
  const adminUsers = await Promise.all(
400
- attributeValues.map(async (userToPopulate) => {
383
+ attributeValues.map((userToPopulate) => {
401
384
  if (userToPopulate == null) {
402
385
  return null;
403
386
  }
@@ -414,9 +397,23 @@ const createHistoryService = ({ strapi: strapi2 }) => {
414
397
  [attributeKey]: adminUsers
415
398
  };
416
399
  }
400
+ const permissionChecker2 = getService$1("permission-checker").create({
401
+ userAbility: params.state.userAbility,
402
+ model: attributeSchema.target
403
+ });
404
+ const response = await serviceUtils.buildRelationReponse(
405
+ attributeValues,
406
+ attributeSchema
407
+ );
408
+ const sanitizedResults = await Promise.all(
409
+ response.results.map((media) => permissionChecker2.sanitizeOutput(media))
410
+ );
417
411
  return {
418
412
  ...await currentDataWithRelations,
419
- [attributeKey]: await buildRelationReponse(attributeValues, attributeSchema)
413
+ [attributeKey]: {
414
+ results: sanitizedResults,
415
+ meta: response.meta
416
+ }
420
417
  };
421
418
  }
422
419
  return currentDataWithRelations;
@@ -431,7 +428,7 @@ const createHistoryService = ({ strapi: strapi2 }) => {
431
428
  ...result,
432
429
  data: await populateEntryRelations(result),
433
430
  meta: {
434
- unknownAttributes: getSchemaAttributesDiff(
431
+ unknownAttributes: serviceUtils.getSchemaAttributesDiff(
435
432
  result.schema,
436
433
  strapi2.getModel(params.query.contentType).attributes
437
434
  )
@@ -448,7 +445,10 @@ const createHistoryService = ({ strapi: strapi2 }) => {
448
445
  async restoreVersion(versionId) {
449
446
  const version = await query.findOne({ where: { id: versionId } });
450
447
  const contentTypeSchemaAttributes = strapi2.getModel(version.contentType).attributes;
451
- const schemaDiff = getSchemaAttributesDiff(version.schema, contentTypeSchemaAttributes);
448
+ const schemaDiff = serviceUtils.getSchemaAttributesDiff(
449
+ version.schema,
450
+ contentTypeSchemaAttributes
451
+ );
452
452
  const dataWithoutAddedAttributes = Object.keys(schemaDiff.added).reduce(
453
453
  (currentData, addedKey) => {
454
454
  currentData[addedKey] = null;
@@ -461,61 +461,26 @@ const createHistoryService = ({ strapi: strapi2 }) => {
461
461
  FIELDS_TO_IGNORE,
462
462
  contentTypeSchemaAttributes
463
463
  );
464
- const dataWithoutMissingRelations = await Object.entries(sanitizedSchemaAttributes).reduce(
465
- async (previousRelationAttributesPromise, [name, attribute]) => {
466
- const previousRelationAttributes = await previousRelationAttributesPromise;
467
- const relationData = version.data[name];
468
- if (relationData === null) {
464
+ const reducer = strapiUtils.async.reduce(Object.entries(sanitizedSchemaAttributes));
465
+ const dataWithoutMissingRelations = await reducer(
466
+ async (previousRelationAttributes, [name, attribute]) => {
467
+ const versionRelationData = version.data[name];
468
+ if (!versionRelationData) {
469
469
  return previousRelationAttributes;
470
470
  }
471
471
  if (attribute.type === "relation" && // TODO: handle polymorphic relations
472
472
  attribute.relation !== "morphToOne" && attribute.relation !== "morphToMany") {
473
- if (Array.isArray(relationData)) {
474
- if (relationData.length === 0)
475
- return previousRelationAttributes;
476
- const existingAndMissingRelations = await Promise.all(
477
- relationData.map((relation) => {
478
- return strapi2.documents(attribute.target).findOne({
479
- documentId: relation.documentId,
480
- locale: relation.locale || void 0
481
- });
482
- })
483
- );
484
- const existingRelations = existingAndMissingRelations.filter(
485
- (relation) => relation !== null
486
- );
487
- previousRelationAttributes[name] = existingRelations;
488
- } else {
489
- const existingRelation = await strapi2.documents(attribute.target).findOne({
490
- documentId: relationData.documentId,
491
- locale: relationData.locale || void 0
492
- });
493
- if (!existingRelation) {
494
- previousRelationAttributes[name] = null;
495
- }
496
- }
473
+ const data2 = await serviceUtils.getRelationRestoreValue(versionRelationData, attribute);
474
+ previousRelationAttributes[name] = data2;
497
475
  }
498
476
  if (attribute.type === "media") {
499
- if (attribute.multiple) {
500
- const existingAndMissingMedias = await Promise.all(
501
- // @ts-expect-error Fix the type definitions so this isn't any
502
- relationData.map((media) => {
503
- return strapi2.db.query("plugin::upload.file").findOne({ where: { id: media.id } });
504
- })
505
- );
506
- const existingMedias = existingAndMissingMedias.filter((media) => media != null);
507
- previousRelationAttributes[name] = existingMedias;
508
- } else {
509
- const existingMedia = await strapi2.db.query("plugin::upload.file").findOne({ where: { id: version.data[name].id } });
510
- if (!existingMedia) {
511
- previousRelationAttributes[name] = null;
512
- }
513
- }
477
+ const data2 = await serviceUtils.getMediaRestoreValue(versionRelationData, attribute);
478
+ previousRelationAttributes[name] = data2;
514
479
  }
515
480
  return previousRelationAttributes;
516
481
  },
517
482
  // Clone to avoid mutating the original version data
518
- Promise.resolve(structuredClone(dataWithoutAddedAttributes))
483
+ structuredClone(dataWithoutAddedAttributes)
519
484
  );
520
485
  const data = fp.omit(["id", ...Object.keys(schemaDiff.removed)], dataWithoutMissingRelations);
521
486
  const restoredDocument = await strapi2.documents(version.contentType).update({
@@ -530,8 +495,118 @@ const createHistoryService = ({ strapi: strapi2 }) => {
530
495
  }
531
496
  };
532
497
  };
498
+ const shouldCreateHistoryVersion = (context) => {
499
+ if (!strapi.requestContext.get()?.request.url.startsWith("/content-manager")) {
500
+ return false;
501
+ }
502
+ if (context.action !== "create" && context.action !== "update" && context.action !== "clone" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
503
+ return false;
504
+ }
505
+ if (context.action === "update" && strapi.requestContext.get()?.request.url.endsWith("/actions/publish")) {
506
+ return false;
507
+ }
508
+ if (!context.contentType.uid.startsWith("api::")) {
509
+ return false;
510
+ }
511
+ return true;
512
+ };
513
+ const getSchemas = (uid2) => {
514
+ const attributesSchema = strapi.getModel(uid2).attributes;
515
+ const componentsSchemas = Object.keys(attributesSchema).reduce(
516
+ (currentComponentSchemas, key) => {
517
+ const fieldSchema = attributesSchema[key];
518
+ if (fieldSchema.type === "component") {
519
+ const componentSchema = strapi.getModel(fieldSchema.component).attributes;
520
+ return {
521
+ ...currentComponentSchemas,
522
+ [fieldSchema.component]: componentSchema
523
+ };
524
+ }
525
+ return currentComponentSchemas;
526
+ },
527
+ {}
528
+ );
529
+ return {
530
+ schema: fp.omit(FIELDS_TO_IGNORE, attributesSchema),
531
+ componentsSchemas
532
+ };
533
+ };
534
+ const createLifecyclesService = ({ strapi: strapi2 }) => {
535
+ const state = {
536
+ deleteExpiredJob: null,
537
+ isInitialized: false
538
+ };
539
+ const serviceUtils = createServiceUtils({ strapi: strapi2 });
540
+ return {
541
+ async bootstrap() {
542
+ if (state.isInitialized) {
543
+ return;
544
+ }
545
+ strapi2.documents.use(async (context, next) => {
546
+ const result = await next();
547
+ if (!shouldCreateHistoryVersion(context)) {
548
+ return result;
549
+ }
550
+ const documentId = context.action === "create" || context.action === "clone" ? result.documentId : context.params.documentId;
551
+ const defaultLocale = await serviceUtils.getDefaultLocale();
552
+ const locales = fp.castArray(context.params?.locale || defaultLocale);
553
+ if (!locales.length) {
554
+ return result;
555
+ }
556
+ const uid2 = context.contentType.uid;
557
+ const schemas = getSchemas(uid2);
558
+ const localeEntries = await strapi2.db.query(uid2).findMany({
559
+ where: {
560
+ documentId,
561
+ locale: { $in: locales },
562
+ publishedAt: null
563
+ },
564
+ populate: serviceUtils.getDeepPopulate(
565
+ uid2,
566
+ true
567
+ /* use database syntax */
568
+ )
569
+ });
570
+ await strapi2.db.transaction(async ({ onCommit }) => {
571
+ onCommit(async () => {
572
+ for (const entry of localeEntries) {
573
+ const status = await serviceUtils.getVersionStatus(uid2, entry);
574
+ await getService(strapi2, "history").createVersion({
575
+ contentType: uid2,
576
+ data: fp.omit(FIELDS_TO_IGNORE, entry),
577
+ relatedDocumentId: documentId,
578
+ locale: entry.locale,
579
+ status,
580
+ ...schemas
581
+ });
582
+ }
583
+ });
584
+ });
585
+ return result;
586
+ });
587
+ state.deleteExpiredJob = nodeSchedule.scheduleJob("0 0 * * *", () => {
588
+ const retentionDaysInMilliseconds = serviceUtils.getRetentionDays() * 24 * 60 * 60 * 1e3;
589
+ const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
590
+ strapi2.db.query(HISTORY_VERSION_UID).deleteMany({
591
+ where: {
592
+ created_at: {
593
+ $lt: expirationDate.toISOString()
594
+ }
595
+ }
596
+ });
597
+ });
598
+ state.isInitialized = true;
599
+ },
600
+ async destroy() {
601
+ if (state.deleteExpiredJob) {
602
+ state.deleteExpiredJob.cancel();
603
+ }
604
+ }
605
+ };
606
+ };
533
607
  const services$1 = {
534
- history: createHistoryService
608
+ history: createHistoryService,
609
+ lifecycles: createLifecyclesService
535
610
  };
536
611
  const info = { pluginName: "content-manager", type: "admin" };
537
612
  const historyVersionRouter = {
@@ -611,10 +686,10 @@ const getFeature = () => {
611
686
  strapi2.get("models").add(historyVersion);
612
687
  },
613
688
  bootstrap({ strapi: strapi2 }) {
614
- getService(strapi2, "history").bootstrap();
689
+ getService(strapi2, "lifecycles").bootstrap();
615
690
  },
616
691
  destroy({ strapi: strapi2 }) {
617
- getService(strapi2, "history").destroy();
692
+ getService(strapi2, "lifecycles").destroy();
618
693
  },
619
694
  controllers: controllers$1,
620
695
  services: services$1,
@@ -1433,7 +1508,7 @@ const { PaginationError, ValidationError } = strapiUtils.errors;
1433
1508
  const TYPES = ["singleType", "collectionType"];
1434
1509
  const kindSchema = strapiUtils.yup.string().oneOf(TYPES).nullable();
1435
1510
  const bulkActionInputSchema = strapiUtils.yup.object({
1436
- ids: strapiUtils.yup.array().of(strapiUtils.yup.strapiID()).min(1).required()
1511
+ documentIds: strapiUtils.yup.array().of(strapiUtils.yup.strapiID()).min(1).required()
1437
1512
  }).required();
1438
1513
  const generateUIDInputSchema = strapiUtils.yup.object({
1439
1514
  contentTypeUID: strapiUtils.yup.string().required(),
@@ -1532,15 +1607,49 @@ const excludeNotCreatableFields = (uid2, permissionChecker2) => (body, path = []
1532
1607
  }
1533
1608
  }, body);
1534
1609
  };
1535
- const getDocumentLocaleAndStatus = (request) => {
1536
- const { locale, status, ...rest } = request || {};
1537
- if (!fp.isNil(locale) && typeof locale !== "string") {
1538
- throw new strapiUtils.errors.ValidationError(`Invalid locale: ${locale}`);
1539
- }
1540
- if (!fp.isNil(status) && !["draft", "published"].includes(status)) {
1541
- throw new strapiUtils.errors.ValidationError(`Invalid status: ${status}`);
1610
+ const singleLocaleSchema = strapiUtils.yup.string().nullable();
1611
+ const multipleLocaleSchema = strapiUtils.yup.lazy(
1612
+ (value) => Array.isArray(value) ? strapiUtils.yup.array().of(singleLocaleSchema.required()) : singleLocaleSchema
1613
+ );
1614
+ const statusSchema = strapiUtils.yup.mixed().oneOf(["draft", "published"], "Invalid status");
1615
+ const getDocumentLocaleAndStatus = async (request, model, opts = { allowMultipleLocales: false }) => {
1616
+ const { allowMultipleLocales } = opts;
1617
+ const { locale, status: providedStatus, ...rest } = request || {};
1618
+ const defaultStatus = strapiUtils.contentTypes.hasDraftAndPublish(strapi.getModel(model)) ? void 0 : "published";
1619
+ const status = providedStatus !== void 0 ? providedStatus : defaultStatus;
1620
+ const schema = strapiUtils.yup.object().shape({
1621
+ locale: allowMultipleLocales ? multipleLocaleSchema : singleLocaleSchema,
1622
+ status: statusSchema
1623
+ });
1624
+ try {
1625
+ await strapiUtils.validateYupSchema(schema, { strict: true, abortEarly: false })(request);
1626
+ return { locale, status, ...rest };
1627
+ } catch (error) {
1628
+ throw new strapiUtils.errors.ValidationError(`Validation error: ${error.message}`);
1542
1629
  }
1543
- return { locale, status, ...rest };
1630
+ };
1631
+ const formatDocumentWithMetadata = async (permissionChecker2, uid2, document, opts = {}) => {
1632
+ const documentMetadata2 = getService$1("document-metadata");
1633
+ const serviceOutput = await documentMetadata2.formatDocumentWithMetadata(uid2, document, opts);
1634
+ let {
1635
+ meta: { availableLocales, availableStatus }
1636
+ } = serviceOutput;
1637
+ const metadataSanitizer = permissionChecker2.sanitizeOutput;
1638
+ availableLocales = await strapiUtils.async.map(
1639
+ availableLocales,
1640
+ async (localeDocument) => metadataSanitizer(localeDocument)
1641
+ );
1642
+ availableStatus = await strapiUtils.async.map(
1643
+ availableStatus,
1644
+ async (statusDocument) => metadataSanitizer(statusDocument)
1645
+ );
1646
+ return {
1647
+ ...serviceOutput,
1648
+ meta: {
1649
+ availableLocales,
1650
+ availableStatus
1651
+ }
1652
+ };
1544
1653
  };
1545
1654
  const createDocument = async (ctx, opts) => {
1546
1655
  const { userAbility, user } = ctx.state;
@@ -1555,7 +1664,7 @@ const createDocument = async (ctx, opts) => {
1555
1664
  const setCreator = strapiUtils.setCreatorFields({ user });
1556
1665
  const sanitizeFn = strapiUtils.async.pipe(pickPermittedFields, setCreator);
1557
1666
  const sanitizedBody = await sanitizeFn(body);
1558
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(body);
1667
+ const { locale, status } = await getDocumentLocaleAndStatus(body, model);
1559
1668
  return documentManager2.create(model, {
1560
1669
  data: sanitizedBody,
1561
1670
  locale,
@@ -1574,7 +1683,7 @@ const updateDocument = async (ctx, opts) => {
1574
1683
  }
1575
1684
  const permissionQuery = await permissionChecker2.sanitizedQuery.update(ctx.query);
1576
1685
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1577
- const { locale } = getDocumentLocaleAndStatus(body);
1686
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1578
1687
  const [documentVersion, documentExists] = await Promise.all([
1579
1688
  documentManager2.findOne(id, model, { populate, locale, status: "draft" }),
1580
1689
  documentManager2.exists(model, id)
@@ -1612,7 +1721,7 @@ const collectionTypes = {
1612
1721
  }
1613
1722
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
1614
1723
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(1).countRelations({ toOne: false, toMany: true }).build();
1615
- const { locale, status } = getDocumentLocaleAndStatus(query);
1724
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
1616
1725
  const { results: documents, pagination } = await documentManager2.findPage(
1617
1726
  { ...permissionQuery, populate, locale, status },
1618
1727
  model
@@ -1641,14 +1750,13 @@ const collectionTypes = {
1641
1750
  const { userAbility } = ctx.state;
1642
1751
  const { model, id } = ctx.params;
1643
1752
  const documentManager2 = getService$1("document-manager");
1644
- const documentMetadata2 = getService$1("document-metadata");
1645
1753
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1646
1754
  if (permissionChecker2.cannot.read()) {
1647
1755
  return ctx.forbidden();
1648
1756
  }
1649
1757
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1650
1758
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1651
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
1759
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
1652
1760
  const version = await documentManager2.findOne(id, model, {
1653
1761
  populate,
1654
1762
  locale,
@@ -1659,8 +1767,10 @@ const collectionTypes = {
1659
1767
  if (!exists) {
1660
1768
  return ctx.notFound();
1661
1769
  }
1662
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
1770
+ const { meta } = await formatDocumentWithMetadata(
1771
+ permissionChecker2,
1663
1772
  model,
1773
+ // @ts-expect-error TODO: fix
1664
1774
  { id, locale, publishedAt: null },
1665
1775
  { availableLocales: true, availableStatus: false }
1666
1776
  );
@@ -1671,12 +1781,11 @@ const collectionTypes = {
1671
1781
  return ctx.forbidden();
1672
1782
  }
1673
1783
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
1674
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1784
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1675
1785
  },
1676
1786
  async create(ctx) {
1677
1787
  const { userAbility } = ctx.state;
1678
1788
  const { model } = ctx.params;
1679
- const documentMetadata2 = getService$1("document-metadata");
1680
1789
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1681
1790
  const [totalEntries, document] = await Promise.all([
1682
1791
  strapi.db.query(model).count(),
@@ -1684,7 +1793,7 @@ const collectionTypes = {
1684
1793
  ]);
1685
1794
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
1686
1795
  ctx.status = 201;
1687
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1796
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1688
1797
  // Empty metadata as it's not relevant for a new document
1689
1798
  availableLocales: false,
1690
1799
  availableStatus: false
@@ -1698,25 +1807,23 @@ const collectionTypes = {
1698
1807
  async update(ctx) {
1699
1808
  const { userAbility } = ctx.state;
1700
1809
  const { model } = ctx.params;
1701
- const documentMetadata2 = getService$1("document-metadata");
1702
1810
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1703
1811
  const updatedVersion = await updateDocument(ctx);
1704
1812
  const sanitizedVersion = await permissionChecker2.sanitizeOutput(updatedVersion);
1705
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedVersion);
1813
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedVersion);
1706
1814
  },
1707
1815
  async clone(ctx) {
1708
1816
  const { userAbility, user } = ctx.state;
1709
1817
  const { model, sourceId: id } = ctx.params;
1710
1818
  const { body } = ctx.request;
1711
1819
  const documentManager2 = getService$1("document-manager");
1712
- const documentMetadata2 = getService$1("document-metadata");
1713
1820
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1714
1821
  if (permissionChecker2.cannot.create()) {
1715
1822
  return ctx.forbidden();
1716
1823
  }
1717
1824
  const permissionQuery = await permissionChecker2.sanitizedQuery.create(ctx.query);
1718
1825
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1719
- const { locale } = getDocumentLocaleAndStatus(body);
1826
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1720
1827
  const document = await documentManager2.findOne(id, model, {
1721
1828
  populate,
1722
1829
  locale,
@@ -1732,7 +1839,7 @@ const collectionTypes = {
1732
1839
  const sanitizedBody = await sanitizeFn(body);
1733
1840
  const clonedDocument = await documentManager2.clone(document.documentId, sanitizedBody, model);
1734
1841
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(clonedDocument);
1735
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1842
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1736
1843
  // Empty metadata as it's not relevant for a new document
1737
1844
  availableLocales: false,
1738
1845
  availableStatus: false
@@ -1761,7 +1868,7 @@ const collectionTypes = {
1761
1868
  }
1762
1869
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(ctx.query);
1763
1870
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1764
- const { locale } = getDocumentLocaleAndStatus(ctx.query);
1871
+ const { locale } = await getDocumentLocaleAndStatus(ctx.query, model);
1765
1872
  const documentLocales = await documentManager2.findLocales(id, model, { populate, locale });
1766
1873
  if (documentLocales.length === 0) {
1767
1874
  return ctx.notFound();
@@ -1783,7 +1890,6 @@ const collectionTypes = {
1783
1890
  const { id, model } = ctx.params;
1784
1891
  const { body } = ctx.request;
1785
1892
  const documentManager2 = getService$1("document-manager");
1786
- const documentMetadata2 = getService$1("document-metadata");
1787
1893
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1788
1894
  if (permissionChecker2.cannot.publish()) {
1789
1895
  return ctx.forbidden();
@@ -1791,25 +1897,46 @@ const collectionTypes = {
1791
1897
  const publishedDocument = await strapi.db.transaction(async () => {
1792
1898
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1793
1899
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1794
- const document = id ? await updateDocument(ctx, { populate }) : await createDocument(ctx, { populate });
1900
+ let document;
1901
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1902
+ const isCreate = fp.isNil(id);
1903
+ if (isCreate) {
1904
+ if (permissionChecker2.cannot.create()) {
1905
+ throw new strapiUtils.errors.ForbiddenError();
1906
+ }
1907
+ document = await createDocument(ctx, { populate });
1908
+ }
1909
+ const isUpdate = !isCreate;
1910
+ if (isUpdate) {
1911
+ document = await documentManager2.findOne(id, model, { populate, locale });
1912
+ if (!document) {
1913
+ throw new strapiUtils.errors.NotFoundError("Document not found");
1914
+ }
1915
+ if (permissionChecker2.can.update(document)) {
1916
+ await updateDocument(ctx);
1917
+ }
1918
+ }
1795
1919
  if (permissionChecker2.cannot.publish(document)) {
1796
1920
  throw new strapiUtils.errors.ForbiddenError();
1797
1921
  }
1798
- const { locale } = getDocumentLocaleAndStatus(body);
1799
- return documentManager2.publish(document.documentId, model, {
1922
+ const publishResult = await documentManager2.publish(document.documentId, model, {
1800
1923
  locale
1801
1924
  // TODO: Allow setting creator fields on publish
1802
1925
  // data: setCreatorFields({ user, isEdition: true })({}),
1803
1926
  });
1927
+ if (!publishResult || publishResult.length === 0) {
1928
+ throw new strapiUtils.errors.NotFoundError("Document not found or already published.");
1929
+ }
1930
+ return publishResult[0];
1804
1931
  });
1805
1932
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
1806
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1933
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1807
1934
  },
1808
1935
  async bulkPublish(ctx) {
1809
1936
  const { userAbility } = ctx.state;
1810
1937
  const { model } = ctx.params;
1811
1938
  const { body } = ctx.request;
1812
- const { ids } = body;
1939
+ const { documentIds } = body;
1813
1940
  await validateBulkActionInput(body);
1814
1941
  const documentManager2 = getService$1("document-manager");
1815
1942
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1818,8 +1945,13 @@ const collectionTypes = {
1818
1945
  }
1819
1946
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1820
1947
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1821
- const entityPromises = ids.map((id) => documentManager2.findOne(id, model, { populate }));
1822
- const entities = await Promise.all(entityPromises);
1948
+ const { locale } = await getDocumentLocaleAndStatus(body, model, {
1949
+ allowMultipleLocales: true
1950
+ });
1951
+ const entityPromises = documentIds.map(
1952
+ (documentId) => documentManager2.findLocales(documentId, model, { populate, locale, isPublished: false })
1953
+ );
1954
+ const entities = (await Promise.all(entityPromises)).flat();
1823
1955
  for (const entity of entities) {
1824
1956
  if (!entity) {
1825
1957
  return ctx.notFound();
@@ -1828,24 +1960,25 @@ const collectionTypes = {
1828
1960
  return ctx.forbidden();
1829
1961
  }
1830
1962
  }
1831
- const { count } = await documentManager2.publishMany(entities, model);
1963
+ const count = await documentManager2.publishMany(model, documentIds, locale);
1832
1964
  ctx.body = { count };
1833
1965
  },
1834
1966
  async bulkUnpublish(ctx) {
1835
1967
  const { userAbility } = ctx.state;
1836
1968
  const { model } = ctx.params;
1837
1969
  const { body } = ctx.request;
1838
- const { ids } = body;
1970
+ const { documentIds } = body;
1839
1971
  await validateBulkActionInput(body);
1840
1972
  const documentManager2 = getService$1("document-manager");
1841
1973
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1842
1974
  if (permissionChecker2.cannot.unpublish()) {
1843
1975
  return ctx.forbidden();
1844
1976
  }
1845
- const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1846
- const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1847
- const entityPromises = ids.map((id) => documentManager2.findOne(id, model, { populate }));
1848
- const entities = await Promise.all(entityPromises);
1977
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1978
+ const entityPromises = documentIds.map(
1979
+ (documentId) => documentManager2.findLocales(documentId, model, { locale, isPublished: true })
1980
+ );
1981
+ const entities = (await Promise.all(entityPromises)).flat();
1849
1982
  for (const entity of entities) {
1850
1983
  if (!entity) {
1851
1984
  return ctx.notFound();
@@ -1854,7 +1987,8 @@ const collectionTypes = {
1854
1987
  return ctx.forbidden();
1855
1988
  }
1856
1989
  }
1857
- const { count } = await documentManager2.unpublishMany(entities, model);
1990
+ const entitiesIds = entities.map((document) => document.documentId);
1991
+ const { count } = await documentManager2.unpublishMany(entitiesIds, model, { locale });
1858
1992
  ctx.body = { count };
1859
1993
  },
1860
1994
  async unpublish(ctx) {
@@ -1864,7 +1998,6 @@ const collectionTypes = {
1864
1998
  body: { discardDraft, ...body }
1865
1999
  } = ctx.request;
1866
2000
  const documentManager2 = getService$1("document-manager");
1867
- const documentMetadata2 = getService$1("document-metadata");
1868
2001
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1869
2002
  if (permissionChecker2.cannot.unpublish()) {
1870
2003
  return ctx.forbidden();
@@ -1874,7 +2007,7 @@ const collectionTypes = {
1874
2007
  }
1875
2008
  const permissionQuery = await permissionChecker2.sanitizedQuery.unpublish(ctx.query);
1876
2009
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1877
- const { locale } = getDocumentLocaleAndStatus(body);
2010
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1878
2011
  const document = await documentManager2.findOne(id, model, {
1879
2012
  populate,
1880
2013
  locale,
@@ -1896,7 +2029,7 @@ const collectionTypes = {
1896
2029
  ctx.body = await strapiUtils.async.pipe(
1897
2030
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
1898
2031
  permissionChecker2.sanitizeOutput,
1899
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2032
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1900
2033
  )(document);
1901
2034
  });
1902
2035
  },
@@ -1905,14 +2038,13 @@ const collectionTypes = {
1905
2038
  const { id, model } = ctx.params;
1906
2039
  const { body } = ctx.request;
1907
2040
  const documentManager2 = getService$1("document-manager");
1908
- const documentMetadata2 = getService$1("document-metadata");
1909
2041
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1910
2042
  if (permissionChecker2.cannot.discard()) {
1911
2043
  return ctx.forbidden();
1912
2044
  }
1913
2045
  const permissionQuery = await permissionChecker2.sanitizedQuery.discard(ctx.query);
1914
2046
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1915
- const { locale } = getDocumentLocaleAndStatus(body);
2047
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1916
2048
  const document = await documentManager2.findOne(id, model, {
1917
2049
  populate,
1918
2050
  locale,
@@ -1927,14 +2059,14 @@ const collectionTypes = {
1927
2059
  ctx.body = await strapiUtils.async.pipe(
1928
2060
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
1929
2061
  permissionChecker2.sanitizeOutput,
1930
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2062
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1931
2063
  )(document);
1932
2064
  },
1933
2065
  async bulkDelete(ctx) {
1934
2066
  const { userAbility } = ctx.state;
1935
2067
  const { model } = ctx.params;
1936
2068
  const { query, body } = ctx.request;
1937
- const { ids } = body;
2069
+ const { documentIds } = body;
1938
2070
  await validateBulkActionInput(body);
1939
2071
  const documentManager2 = getService$1("document-manager");
1940
2072
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1942,14 +2074,22 @@ const collectionTypes = {
1942
2074
  return ctx.forbidden();
1943
2075
  }
1944
2076
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(query);
1945
- const idsWhereClause = { id: { $in: ids } };
1946
- const params = {
1947
- ...permissionQuery,
1948
- filters: {
1949
- $and: [idsWhereClause].concat(permissionQuery.filters || [])
2077
+ const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2078
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2079
+ const documentLocales = await documentManager2.findLocales(documentIds, model, {
2080
+ populate,
2081
+ locale
2082
+ });
2083
+ if (documentLocales.length === 0) {
2084
+ return ctx.notFound();
2085
+ }
2086
+ for (const document of documentLocales) {
2087
+ if (permissionChecker2.cannot.delete(document)) {
2088
+ return ctx.forbidden();
1950
2089
  }
1951
- };
1952
- const { count } = await documentManager2.deleteMany(params, model);
2090
+ }
2091
+ const localeDocumentsIds = documentLocales.map((document) => document.documentId);
2092
+ const { count } = await documentManager2.deleteMany(localeDocumentsIds, model, { locale });
1953
2093
  ctx.body = { count };
1954
2094
  },
1955
2095
  async countDraftRelations(ctx) {
@@ -1962,7 +2102,7 @@ const collectionTypes = {
1962
2102
  }
1963
2103
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1964
2104
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1965
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
2105
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
1966
2106
  const entity = await documentManager2.findOne(id, model, { populate, locale, status });
1967
2107
  if (!entity) {
1968
2108
  return ctx.notFound();
@@ -1977,7 +2117,7 @@ const collectionTypes = {
1977
2117
  },
1978
2118
  async countManyEntriesDraftRelations(ctx) {
1979
2119
  const { userAbility } = ctx.state;
1980
- const ids = ctx.request.query.ids;
2120
+ const ids = ctx.request.query.documentIds;
1981
2121
  const locale = ctx.request.query.locale;
1982
2122
  const { model } = ctx.params;
1983
2123
  const documentManager2 = getService$1("document-manager");
@@ -1985,16 +2125,16 @@ const collectionTypes = {
1985
2125
  if (permissionChecker2.cannot.read()) {
1986
2126
  return ctx.forbidden();
1987
2127
  }
1988
- const entities = await documentManager2.findMany(
2128
+ const documents = await documentManager2.findMany(
1989
2129
  {
1990
2130
  filters: {
1991
- id: ids
2131
+ documentId: ids
1992
2132
  },
1993
2133
  locale
1994
2134
  },
1995
2135
  model
1996
2136
  );
1997
- if (!entities) {
2137
+ if (!documents) {
1998
2138
  return ctx.notFound();
1999
2139
  }
2000
2140
  const number = await documentManager2.countManyEntriesDraftRelations(ids, model, locale);
@@ -2490,7 +2630,7 @@ const createOrUpdateDocument = async (ctx, opts) => {
2490
2630
  throw new strapiUtils.errors.ForbiddenError();
2491
2631
  }
2492
2632
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.update(query);
2493
- const { locale } = getDocumentLocaleAndStatus(body);
2633
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2494
2634
  const [documentVersion, otherDocumentVersion] = await Promise.all([
2495
2635
  findDocument(sanitizedQuery, model, { locale, status: "draft" }),
2496
2636
  // Find the first document to check if it exists
@@ -2527,12 +2667,11 @@ const singleTypes = {
2527
2667
  const { model } = ctx.params;
2528
2668
  const { query = {} } = ctx.request;
2529
2669
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2530
- const documentMetadata2 = getService$1("document-metadata");
2531
2670
  if (permissionChecker2.cannot.read()) {
2532
2671
  return ctx.forbidden();
2533
2672
  }
2534
2673
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
2535
- const { locale, status } = getDocumentLocaleAndStatus(query);
2674
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
2536
2675
  const version = await findDocument(permissionQuery, model, { locale, status });
2537
2676
  if (!version) {
2538
2677
  if (permissionChecker2.cannot.create()) {
@@ -2542,8 +2681,10 @@ const singleTypes = {
2542
2681
  if (!document) {
2543
2682
  return ctx.notFound();
2544
2683
  }
2545
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
2684
+ const { meta } = await formatDocumentWithMetadata(
2685
+ permissionChecker2,
2546
2686
  model,
2687
+ // @ts-expect-error - fix types
2547
2688
  { id: document.documentId, locale, publishedAt: null },
2548
2689
  { availableLocales: true, availableStatus: false }
2549
2690
  );
@@ -2554,16 +2695,15 @@ const singleTypes = {
2554
2695
  return ctx.forbidden();
2555
2696
  }
2556
2697
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
2557
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2698
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2558
2699
  },
2559
2700
  async createOrUpdate(ctx) {
2560
2701
  const { userAbility } = ctx.state;
2561
2702
  const { model } = ctx.params;
2562
- const documentMetadata2 = getService$1("document-metadata");
2563
2703
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2564
2704
  const document = await createOrUpdateDocument(ctx);
2565
2705
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
2566
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2706
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2567
2707
  },
2568
2708
  async delete(ctx) {
2569
2709
  const { userAbility } = ctx.state;
@@ -2576,7 +2716,7 @@ const singleTypes = {
2576
2716
  }
2577
2717
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.delete(query);
2578
2718
  const populate = await buildPopulateFromQuery(sanitizedQuery, model);
2579
- const { locale } = getDocumentLocaleAndStatus(query);
2719
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2580
2720
  const documentLocales = await documentManager2.findLocales(void 0, model, {
2581
2721
  populate,
2582
2722
  locale
@@ -2599,7 +2739,6 @@ const singleTypes = {
2599
2739
  const { model } = ctx.params;
2600
2740
  const { query = {} } = ctx.request;
2601
2741
  const documentManager2 = getService$1("document-manager");
2602
- const documentMetadata2 = getService$1("document-metadata");
2603
2742
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2604
2743
  if (permissionChecker2.cannot.publish()) {
2605
2744
  return ctx.forbidden();
@@ -2614,11 +2753,12 @@ const singleTypes = {
2614
2753
  if (permissionChecker2.cannot.publish(document)) {
2615
2754
  throw new strapiUtils.errors.ForbiddenError();
2616
2755
  }
2617
- const { locale } = getDocumentLocaleAndStatus(document);
2618
- return documentManager2.publish(document.documentId, model, { locale });
2756
+ const { locale } = await getDocumentLocaleAndStatus(document, model);
2757
+ const publishResult = await documentManager2.publish(document.documentId, model, { locale });
2758
+ return publishResult.at(0);
2619
2759
  });
2620
2760
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
2621
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2761
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2622
2762
  },
2623
2763
  async unpublish(ctx) {
2624
2764
  const { userAbility } = ctx.state;
@@ -2628,7 +2768,6 @@ const singleTypes = {
2628
2768
  query = {}
2629
2769
  } = ctx.request;
2630
2770
  const documentManager2 = getService$1("document-manager");
2631
- const documentMetadata2 = getService$1("document-metadata");
2632
2771
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2633
2772
  if (permissionChecker2.cannot.unpublish()) {
2634
2773
  return ctx.forbidden();
@@ -2637,7 +2776,7 @@ const singleTypes = {
2637
2776
  return ctx.forbidden();
2638
2777
  }
2639
2778
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.unpublish(query);
2640
- const { locale } = getDocumentLocaleAndStatus(body);
2779
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2641
2780
  const document = await findDocument(sanitizedQuery, model, { locale });
2642
2781
  if (!document) {
2643
2782
  return ctx.notFound();
@@ -2655,7 +2794,7 @@ const singleTypes = {
2655
2794
  ctx.body = await strapiUtils.async.pipe(
2656
2795
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
2657
2796
  permissionChecker2.sanitizeOutput,
2658
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2797
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2659
2798
  )(document);
2660
2799
  });
2661
2800
  },
@@ -2664,13 +2803,12 @@ const singleTypes = {
2664
2803
  const { model } = ctx.params;
2665
2804
  const { body, query = {} } = ctx.request;
2666
2805
  const documentManager2 = getService$1("document-manager");
2667
- const documentMetadata2 = getService$1("document-metadata");
2668
2806
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2669
2807
  if (permissionChecker2.cannot.discard()) {
2670
2808
  return ctx.forbidden();
2671
2809
  }
2672
2810
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.discard(query);
2673
- const { locale } = getDocumentLocaleAndStatus(body);
2811
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2674
2812
  const document = await findDocument(sanitizedQuery, model, { locale, status: "published" });
2675
2813
  if (!document) {
2676
2814
  return ctx.notFound();
@@ -2681,7 +2819,7 @@ const singleTypes = {
2681
2819
  ctx.body = await strapiUtils.async.pipe(
2682
2820
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
2683
2821
  permissionChecker2.sanitizeOutput,
2684
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2822
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2685
2823
  )(document);
2686
2824
  },
2687
2825
  async countDraftRelations(ctx) {
@@ -2690,7 +2828,7 @@ const singleTypes = {
2690
2828
  const { query } = ctx.request;
2691
2829
  const documentManager2 = getService$1("document-manager");
2692
2830
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2693
- const { locale } = getDocumentLocaleAndStatus(query);
2831
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2694
2832
  if (permissionChecker2.cannot.read()) {
2695
2833
  return ctx.forbidden();
2696
2834
  }
@@ -2711,7 +2849,7 @@ const uid$1 = {
2711
2849
  async generateUID(ctx) {
2712
2850
  const { contentTypeUID, field, data } = await validateGenerateUIDInput(ctx.request.body);
2713
2851
  const { query = {} } = ctx.request;
2714
- const { locale } = getDocumentLocaleAndStatus(query);
2852
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2715
2853
  await validateUIDField(contentTypeUID, field);
2716
2854
  const uidService = getService$1("uid");
2717
2855
  ctx.body = {
@@ -2723,7 +2861,7 @@ const uid$1 = {
2723
2861
  ctx.request.body
2724
2862
  );
2725
2863
  const { query = {} } = ctx.request;
2726
- const { locale } = getDocumentLocaleAndStatus(query);
2864
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2727
2865
  await validateUIDField(contentTypeUID, field);
2728
2866
  const uidService = getService$1("uid");
2729
2867
  const isAvailable = await uidService.checkUIDAvailability({
@@ -3514,7 +3652,7 @@ const permission = ({ strapi: strapi2 }) => ({
3514
3652
  await strapi2.service("admin::permission").actionProvider.registerMany(actions);
3515
3653
  }
3516
3654
  });
3517
- const { isVisibleAttribute: isVisibleAttribute$1 } = strapiUtils__default.default.contentTypes;
3655
+ const { isVisibleAttribute: isVisibleAttribute$1, isScalarAttribute, getDoesAttributeRequireValidation } = strapiUtils__default.default.contentTypes;
3518
3656
  const { isAnyToMany } = strapiUtils__default.default.relations;
3519
3657
  const { PUBLISHED_AT_ATTRIBUTE: PUBLISHED_AT_ATTRIBUTE$1 } = strapiUtils__default.default.contentTypes.constants;
3520
3658
  const isMorphToRelation = (attribute) => isRelation(attribute) && attribute.relation.includes("morphTo");
@@ -3605,6 +3743,42 @@ const getDeepPopulate = (uid2, {
3605
3743
  {}
3606
3744
  );
3607
3745
  };
3746
+ const getValidatableFieldsPopulate = (uid2, {
3747
+ initialPopulate = {},
3748
+ countMany = false,
3749
+ countOne = false,
3750
+ maxLevel = Infinity
3751
+ } = {}, level = 1) => {
3752
+ if (level > maxLevel) {
3753
+ return {};
3754
+ }
3755
+ const model = strapi.getModel(uid2);
3756
+ return Object.entries(model.attributes).reduce((populateAcc, [attributeName, attribute]) => {
3757
+ if (!getDoesAttributeRequireValidation(attribute)) {
3758
+ return populateAcc;
3759
+ }
3760
+ if (isScalarAttribute(attribute)) {
3761
+ return fp.merge(populateAcc, {
3762
+ [attributeName]: true
3763
+ });
3764
+ }
3765
+ return fp.merge(
3766
+ populateAcc,
3767
+ getPopulateFor(
3768
+ attributeName,
3769
+ model,
3770
+ {
3771
+ // @ts-expect-error - improve types
3772
+ initialPopulate: initialPopulate?.[attributeName],
3773
+ countMany,
3774
+ countOne,
3775
+ maxLevel
3776
+ },
3777
+ level
3778
+ )
3779
+ );
3780
+ }, {});
3781
+ };
3608
3782
  const getDeepPopulateDraftCount = (uid2) => {
3609
3783
  const model = strapi.getModel(uid2);
3610
3784
  let hasRelations = false;
@@ -3612,6 +3786,10 @@ const getDeepPopulateDraftCount = (uid2) => {
3612
3786
  const attribute = model.attributes[attributeName];
3613
3787
  switch (attribute.type) {
3614
3788
  case "relation": {
3789
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
3790
+ if (isMorphRelation) {
3791
+ break;
3792
+ }
3615
3793
  if (isVisibleAttribute$1(model, attributeName)) {
3616
3794
  populateAcc[attributeName] = {
3617
3795
  count: true,
@@ -3626,22 +3804,24 @@ const getDeepPopulateDraftCount = (uid2) => {
3626
3804
  attribute.component
3627
3805
  );
3628
3806
  if (childHasRelations) {
3629
- populateAcc[attributeName] = { populate: populate2 };
3807
+ populateAcc[attributeName] = {
3808
+ populate: populate2
3809
+ };
3630
3810
  hasRelations = true;
3631
3811
  }
3632
3812
  break;
3633
3813
  }
3634
3814
  case "dynamiczone": {
3635
- const dzPopulate = (attribute.components || []).reduce((acc, componentUID) => {
3636
- const { populate: populate2, hasRelations: childHasRelations } = getDeepPopulateDraftCount(componentUID);
3637
- if (childHasRelations) {
3815
+ const dzPopulateFragment = attribute.components?.reduce((acc, componentUID) => {
3816
+ const { populate: componentPopulate, hasRelations: componentHasRelations } = getDeepPopulateDraftCount(componentUID);
3817
+ if (componentHasRelations) {
3638
3818
  hasRelations = true;
3639
- return fp.merge(acc, populate2);
3819
+ return { ...acc, [componentUID]: { populate: componentPopulate } };
3640
3820
  }
3641
3821
  return acc;
3642
3822
  }, {});
3643
- if (!fp.isEmpty(dzPopulate)) {
3644
- populateAcc[attributeName] = { populate: dzPopulate };
3823
+ if (!fp.isEmpty(dzPopulateFragment)) {
3824
+ populateAcc[attributeName] = { on: dzPopulateFragment };
3645
3825
  }
3646
3826
  break;
3647
3827
  }
@@ -3833,41 +4013,70 @@ const AVAILABLE_STATUS_FIELDS = [
3833
4013
  "updatedBy",
3834
4014
  "status"
3835
4015
  ];
3836
- const AVAILABLE_LOCALES_FIELDS = ["id", "locale", "updatedAt", "createdAt", "status"];
4016
+ const AVAILABLE_LOCALES_FIELDS = [
4017
+ "id",
4018
+ "locale",
4019
+ "updatedAt",
4020
+ "createdAt",
4021
+ "status",
4022
+ "publishedAt",
4023
+ "documentId"
4024
+ ];
3837
4025
  const CONTENT_MANAGER_STATUS = {
3838
4026
  PUBLISHED: "published",
3839
4027
  DRAFT: "draft",
3840
4028
  MODIFIED: "modified"
3841
4029
  };
3842
- const areDatesEqual = (date1, date2, threshold) => {
3843
- if (!date1 || !date2) {
4030
+ const getIsVersionLatestModification = (version, otherVersion) => {
4031
+ if (!version || !version.updatedAt) {
3844
4032
  return false;
3845
4033
  }
3846
- const time1 = new Date(date1).getTime();
3847
- const time2 = new Date(date2).getTime();
3848
- const difference = Math.abs(time1 - time2);
3849
- return difference <= threshold;
4034
+ const versionUpdatedAt = version?.updatedAt ? new Date(version.updatedAt).getTime() : 0;
4035
+ const otherUpdatedAt = otherVersion?.updatedAt ? new Date(otherVersion.updatedAt).getTime() : 0;
4036
+ return versionUpdatedAt > otherUpdatedAt;
3850
4037
  };
3851
4038
  const documentMetadata = ({ strapi: strapi2 }) => ({
3852
4039
  /**
3853
4040
  * Returns available locales of a document for the current status
3854
4041
  */
3855
- getAvailableLocales(uid2, version, allVersions) {
4042
+ async getAvailableLocales(uid2, version, allVersions, validatableFields = []) {
3856
4043
  const versionsByLocale = fp.groupBy("locale", allVersions);
3857
4044
  delete versionsByLocale[version.locale];
3858
- return Object.values(versionsByLocale).map((localeVersions) => {
3859
- if (!strapiUtils.contentTypes.hasDraftAndPublish(strapi2.getModel(uid2))) {
3860
- return fp.pick(AVAILABLE_LOCALES_FIELDS, localeVersions[0]);
4045
+ const model = strapi2.getModel(uid2);
4046
+ const keysToKeep = [...AVAILABLE_LOCALES_FIELDS, ...validatableFields];
4047
+ const traversalFunction = async (localeVersion) => strapiUtils.traverseEntity(
4048
+ ({ key }, { remove }) => {
4049
+ if (keysToKeep.includes(key)) {
4050
+ return;
4051
+ }
4052
+ remove(key);
4053
+ },
4054
+ { schema: model, getModel: strapi2.getModel.bind(strapi2) },
4055
+ // @ts-expect-error fix types DocumentVersion incompatible with Data
4056
+ localeVersion
4057
+ );
4058
+ const mappingResult = await strapiUtils.async.map(
4059
+ Object.values(versionsByLocale),
4060
+ async (localeVersions) => {
4061
+ const mappedLocaleVersions = await strapiUtils.async.map(
4062
+ localeVersions,
4063
+ traversalFunction
4064
+ );
4065
+ if (!strapiUtils.contentTypes.hasDraftAndPublish(model)) {
4066
+ return mappedLocaleVersions[0];
4067
+ }
4068
+ const draftVersion = mappedLocaleVersions.find((v) => v.publishedAt === null);
4069
+ const otherVersions = mappedLocaleVersions.filter((v) => v.id !== draftVersion?.id);
4070
+ if (!draftVersion) {
4071
+ return;
4072
+ }
4073
+ return {
4074
+ ...draftVersion,
4075
+ status: this.getStatus(draftVersion, otherVersions)
4076
+ };
3861
4077
  }
3862
- const draftVersion = localeVersions.find((v) => v.publishedAt === null);
3863
- const otherVersions = localeVersions.filter((v) => v.id !== draftVersion?.id);
3864
- if (!draftVersion)
3865
- return;
3866
- return {
3867
- ...fp.pick(AVAILABLE_LOCALES_FIELDS, draftVersion),
3868
- status: this.getStatus(draftVersion, otherVersions)
3869
- };
3870
- }).filter(Boolean);
4078
+ );
4079
+ return mappingResult.filter(Boolean);
3871
4080
  },
3872
4081
  /**
3873
4082
  * Returns available status of a document for the current locale
@@ -3905,26 +4114,37 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3905
4114
  });
3906
4115
  },
3907
4116
  getStatus(version, otherDocumentStatuses) {
3908
- const isDraft = version.publishedAt === null;
3909
- if (!otherDocumentStatuses?.length) {
3910
- return isDraft ? CONTENT_MANAGER_STATUS.DRAFT : CONTENT_MANAGER_STATUS.PUBLISHED;
4117
+ let draftVersion;
4118
+ let publishedVersion;
4119
+ if (version.publishedAt) {
4120
+ publishedVersion = version;
4121
+ } else {
4122
+ draftVersion = version;
3911
4123
  }
3912
- if (isDraft) {
3913
- const publishedVersion = otherDocumentStatuses?.find((d) => d.publishedAt !== null);
3914
- if (!publishedVersion) {
3915
- return CONTENT_MANAGER_STATUS.DRAFT;
3916
- }
4124
+ const otherVersion = otherDocumentStatuses?.at(0);
4125
+ if (otherVersion?.publishedAt) {
4126
+ publishedVersion = otherVersion;
4127
+ } else if (otherVersion) {
4128
+ draftVersion = otherVersion;
3917
4129
  }
3918
- if (areDatesEqual(version.updatedAt, otherDocumentStatuses.at(0)?.updatedAt, 500)) {
4130
+ if (!draftVersion)
3919
4131
  return CONTENT_MANAGER_STATUS.PUBLISHED;
3920
- }
3921
- return CONTENT_MANAGER_STATUS.MODIFIED;
4132
+ if (!publishedVersion)
4133
+ return CONTENT_MANAGER_STATUS.DRAFT;
4134
+ const isDraftModified = getIsVersionLatestModification(draftVersion, publishedVersion);
4135
+ return isDraftModified ? CONTENT_MANAGER_STATUS.MODIFIED : CONTENT_MANAGER_STATUS.PUBLISHED;
3922
4136
  },
4137
+ // TODO is it necessary to return metadata on every page of the CM
4138
+ // We could refactor this so the locales are only loaded when they're
4139
+ // needed. e.g. in the bulk locale action modal.
3923
4140
  async getMetadata(uid2, version, { availableLocales = true, availableStatus = true } = {}) {
4141
+ const populate = getValidatableFieldsPopulate(uid2);
3924
4142
  const versions = await strapi2.db.query(uid2).findMany({
3925
4143
  where: { documentId: version.documentId },
3926
- select: ["createdAt", "updatedAt", "locale", "publishedAt", "documentId"],
3927
4144
  populate: {
4145
+ // Populate only fields that require validation for bulk locale actions
4146
+ ...populate,
4147
+ // NOTE: creator fields are selected in this way to avoid exposing sensitive data
3928
4148
  createdBy: {
3929
4149
  select: ["id", "firstname", "lastname", "email"]
3930
4150
  },
@@ -3933,7 +4153,7 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3933
4153
  }
3934
4154
  }
3935
4155
  });
3936
- const availableLocalesResult = availableLocales ? this.getAvailableLocales(uid2, version, versions) : [];
4156
+ const availableLocalesResult = availableLocales ? await this.getAvailableLocales(uid2, version, versions, Object.keys(populate)) : [];
3937
4157
  const availableStatusResult = availableStatus ? this.getAvailableStatus(version, versions) : null;
3938
4158
  return {
3939
4159
  availableLocales: availableLocalesResult,
@@ -3946,8 +4166,15 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3946
4166
  * - Available status of the document for the current locale
3947
4167
  */
3948
4168
  async formatDocumentWithMetadata(uid2, document, opts = {}) {
3949
- if (!document)
3950
- return document;
4169
+ if (!document) {
4170
+ return {
4171
+ data: document,
4172
+ meta: {
4173
+ availableLocales: [],
4174
+ availableStatus: []
4175
+ }
4176
+ };
4177
+ }
3951
4178
  const hasDraftAndPublish = strapiUtils.contentTypes.hasDraftAndPublish(strapi2.getModel(uid2));
3952
4179
  if (!hasDraftAndPublish) {
3953
4180
  opts.availableStatus = false;
@@ -3997,26 +4224,9 @@ const sumDraftCounts = (entity, uid2) => {
3997
4224
  }, 0);
3998
4225
  };
3999
4226
  const { ApplicationError } = strapiUtils.errors;
4000
- const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = ALLOWED_WEBHOOK_EVENTS;
4001
4227
  const { PUBLISHED_AT_ATTRIBUTE } = strapiUtils.contentTypes.constants;
4002
4228
  const omitPublishedAtField = fp.omit(PUBLISHED_AT_ATTRIBUTE);
4003
4229
  const omitIdField = fp.omit("id");
4004
- const emitEvent = async (uid2, event, document) => {
4005
- const modelDef = strapi.getModel(uid2);
4006
- const sanitizedDocument = await strapiUtils.sanitize.sanitizers.defaultSanitizeOutput(
4007
- {
4008
- schema: modelDef,
4009
- getModel(uid22) {
4010
- return strapi.getModel(uid22);
4011
- }
4012
- },
4013
- document
4014
- );
4015
- strapi.eventHub.emit(event, {
4016
- model: modelDef.modelName,
4017
- entry: sanitizedDocument
4018
- });
4019
- };
4020
4230
  const documentManager = ({ strapi: strapi2 }) => {
4021
4231
  return {
4022
4232
  async findOne(id, uid2, opts = {}) {
@@ -4035,6 +4245,9 @@ const documentManager = ({ strapi: strapi2 }) => {
4035
4245
  } else if (opts.locale && opts.locale !== "*") {
4036
4246
  where.locale = opts.locale;
4037
4247
  }
4248
+ if (typeof opts.isPublished === "boolean") {
4249
+ where.publishedAt = { $notNull: opts.isPublished };
4250
+ }
4038
4251
  return strapi2.db.query(uid2).findMany({ populate: opts.populate, where });
4039
4252
  },
4040
4253
  async findMany(opts, uid2) {
@@ -4042,20 +4255,16 @@ const documentManager = ({ strapi: strapi2 }) => {
4042
4255
  return strapi2.documents(uid2).findMany(params);
4043
4256
  },
4044
4257
  async findPage(opts, uid2) {
4045
- const page = Number(opts?.page) || 1;
4046
- const pageSize = Number(opts?.pageSize) || 10;
4258
+ const params = strapiUtils.pagination.withDefaultPagination(opts || {}, {
4259
+ maxLimit: 1e3
4260
+ });
4047
4261
  const [documents, total = 0] = await Promise.all([
4048
- strapi2.documents(uid2).findMany(opts),
4049
- strapi2.documents(uid2).count(opts)
4262
+ strapi2.documents(uid2).findMany(params),
4263
+ strapi2.documents(uid2).count(params)
4050
4264
  ]);
4051
4265
  return {
4052
4266
  results: documents,
4053
- pagination: {
4054
- page,
4055
- pageSize,
4056
- pageCount: Math.ceil(total / pageSize),
4057
- total
4058
- }
4267
+ pagination: strapiUtils.pagination.transformPagedPaginationInfo(params, total)
4059
4268
  };
4060
4269
  },
4061
4270
  async create(uid2, opts = {}) {
@@ -4072,10 +4281,7 @@ const documentManager = ({ strapi: strapi2 }) => {
4072
4281
  async clone(id, body, uid2) {
4073
4282
  const populate = await buildDeepPopulate(uid2);
4074
4283
  const params = {
4075
- data: {
4076
- ...omitIdField(body),
4077
- [PUBLISHED_AT_ATTRIBUTE]: null
4078
- },
4284
+ data: omitIdField(body),
4079
4285
  populate
4080
4286
  };
4081
4287
  return strapi2.documents(uid2).clone({ ...params, documentId: id }).then((result) => result?.entries.at(0));
@@ -4101,70 +4307,36 @@ const documentManager = ({ strapi: strapi2 }) => {
4101
4307
  return {};
4102
4308
  },
4103
4309
  // FIXME: handle relations
4104
- async deleteMany(opts, uid2) {
4105
- const docs = await strapi2.documents(uid2).findMany(opts);
4106
- for (const doc of docs) {
4107
- await strapi2.documents(uid2).delete({ documentId: doc.documentId });
4108
- }
4109
- return { count: docs.length };
4310
+ async deleteMany(documentIds, uid2, opts = {}) {
4311
+ const deletedEntries = await strapi2.db.transaction(async () => {
4312
+ return Promise.all(documentIds.map(async (id) => this.delete(id, uid2, opts)));
4313
+ });
4314
+ return { count: deletedEntries.length };
4110
4315
  },
4111
4316
  async publish(id, uid2, opts = {}) {
4112
4317
  const populate = await buildDeepPopulate(uid2);
4113
4318
  const params = { ...opts, populate };
4114
- return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries.at(0));
4319
+ return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries);
4115
4320
  },
4116
- async publishMany(entities, uid2) {
4117
- if (!entities.length) {
4118
- return null;
4119
- }
4120
- await Promise.all(
4121
- entities.map((document) => {
4122
- return strapi2.entityValidator.validateEntityCreation(
4123
- strapi2.getModel(uid2),
4124
- document,
4125
- void 0,
4126
- // @ts-expect-error - FIXME: entity here is unnecessary
4127
- document
4128
- );
4129
- })
4130
- );
4131
- const entitiesToPublish = entities.filter((doc) => !doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4132
- const filters = { id: { $in: entitiesToPublish } };
4133
- const data = { [PUBLISHED_AT_ATTRIBUTE]: /* @__PURE__ */ new Date() };
4134
- const populate = await buildDeepPopulate(uid2);
4135
- const publishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4136
- where: filters,
4137
- data
4138
- });
4139
- const publishedEntities = await strapi2.db.query(uid2).findMany({
4140
- where: filters,
4141
- populate
4321
+ async publishMany(uid2, documentIds, locale) {
4322
+ return strapi2.db.transaction(async () => {
4323
+ const results = await Promise.all(
4324
+ documentIds.map((documentId) => this.publish(documentId, uid2, { locale }))
4325
+ );
4326
+ const publishedEntitiesCount = results.flat().filter(Boolean).length;
4327
+ return publishedEntitiesCount;
4142
4328
  });
4143
- await Promise.all(
4144
- publishedEntities.map((doc) => emitEvent(uid2, ENTRY_PUBLISH, doc))
4145
- );
4146
- return publishedEntitiesCount;
4147
4329
  },
4148
- async unpublishMany(documents, uid2) {
4149
- if (!documents.length) {
4150
- return null;
4151
- }
4152
- const entitiesToUnpublish = documents.filter((doc) => doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4153
- const filters = { id: { $in: entitiesToUnpublish } };
4154
- const data = { [PUBLISHED_AT_ATTRIBUTE]: null };
4155
- const populate = await buildDeepPopulate(uid2);
4156
- const unpublishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4157
- where: filters,
4158
- data
4159
- });
4160
- const unpublishedEntities = await strapi2.db.query(uid2).findMany({
4161
- where: filters,
4162
- populate
4330
+ async unpublishMany(documentIds, uid2, opts = {}) {
4331
+ const unpublishedEntries = await strapi2.db.transaction(async () => {
4332
+ return Promise.all(
4333
+ documentIds.map(
4334
+ (id) => strapi2.documents(uid2).unpublish({ ...opts, documentId: id }).then((result) => result?.entries)
4335
+ )
4336
+ );
4163
4337
  });
4164
- await Promise.all(
4165
- unpublishedEntities.map((doc) => emitEvent(uid2, ENTRY_UNPUBLISH, doc))
4166
- );
4167
- return unpublishedEntitiesCount;
4338
+ const unpublishedEntitiesCount = unpublishedEntries.flat().filter(Boolean).length;
4339
+ return { count: unpublishedEntitiesCount };
4168
4340
  },
4169
4341
  async unpublish(id, uid2, opts = {}) {
4170
4342
  const populate = await buildDeepPopulate(uid2);
@@ -4189,16 +4361,20 @@ const documentManager = ({ strapi: strapi2 }) => {
4189
4361
  }
4190
4362
  return sumDraftCounts(document, uid2);
4191
4363
  },
4192
- async countManyEntriesDraftRelations(ids, uid2, locale) {
4364
+ async countManyEntriesDraftRelations(documentIds, uid2, locale) {
4193
4365
  const { populate, hasRelations } = getDeepPopulateDraftCount(uid2);
4194
4366
  if (!hasRelations) {
4195
4367
  return 0;
4196
4368
  }
4369
+ let localeFilter = {};
4370
+ if (locale) {
4371
+ localeFilter = Array.isArray(locale) ? { locale: { $in: locale } } : { locale };
4372
+ }
4197
4373
  const entities = await strapi2.db.query(uid2).findMany({
4198
4374
  populate,
4199
4375
  where: {
4200
- id: { $in: ids },
4201
- ...locale ? { locale } : {}
4376
+ documentId: { $in: documentIds },
4377
+ ...localeFilter
4202
4378
  }
4203
4379
  });
4204
4380
  const totalNumberDraftRelations = entities.reduce(