@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
@@ -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,6 +212,15 @@ 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);
@@ -228,80 +262,68 @@ const createHistoryService = ({ strapi: strapi2 }) => {
228
262
  return acc;
229
263
  }, {});
230
264
  };
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();
265
+ const buildMediaResponse = async (values) => {
266
+ return values.slice(0, 25).reduce(
267
+ async (currentRelationDataPromise, entry) => {
268
+ const currentRelationData = await currentRelationDataPromise;
269
+ if (!entry) {
270
+ return currentRelationData;
239
271
  }
240
- if (context.action !== "create" && context.action !== "update" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
241
- return next();
272
+ const relatedEntry = await strapi2.db.query("plugin::upload.file").findOne({ where: { id: entry.id } });
273
+ if (relatedEntry) {
274
+ currentRelationData.results.push(relatedEntry);
275
+ } else {
276
+ currentRelationData.meta.missingCount += 1;
242
277
  }
243
- const contentTypeUid = context.contentType.uid;
244
- if (!contentTypeUid.startsWith("api::")) {
245
- return next();
278
+ return currentRelationData;
279
+ },
280
+ Promise.resolve({
281
+ results: [],
282
+ meta: { missingCount: 0 }
283
+ })
284
+ );
285
+ };
286
+ const buildRelationReponse = async (values, attributeSchema) => {
287
+ return values.slice(0, 25).reduce(
288
+ async (currentRelationDataPromise, entry) => {
289
+ const currentRelationData = await currentRelationDataPromise;
290
+ if (!entry) {
291
+ return currentRelationData;
246
292
  }
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
- });
293
+ const relatedEntry = await strapi2.documents(attributeSchema.target).findOne({ documentId: entry.documentId, locale: entry.locale || void 0 });
294
+ if (relatedEntry) {
295
+ currentRelationData.results.push({
296
+ ...relatedEntry,
297
+ status: await getVersionStatus(attributeSchema.target, relatedEntry)
282
298
  });
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
- },
299
+ } else {
300
+ currentRelationData.meta.missingCount += 1;
301
+ }
302
+ return currentRelationData;
303
+ },
304
+ Promise.resolve({
305
+ results: [],
306
+ meta: { missingCount: 0 }
307
+ })
308
+ );
309
+ };
310
+ return {
311
+ getSchemaAttributesDiff,
312
+ getRelationRestoreValue,
313
+ getMediaRestoreValue,
314
+ getDefaultLocale,
315
+ getLocaleDictionary,
316
+ getRetentionDays,
317
+ getVersionStatus,
318
+ getDeepPopulate: getDeepPopulate2,
319
+ buildMediaResponse,
320
+ buildRelationReponse
321
+ };
322
+ };
323
+ const createHistoryService = ({ strapi: strapi2 }) => {
324
+ const query = strapi2.db.query(HISTORY_VERSION_UID);
325
+ const serviceUtils = createServiceUtils({ strapi: strapi2 });
326
+ return {
305
327
  async createVersion(historyVersionData) {
306
328
  await query.create({
307
329
  data: {
@@ -312,7 +334,7 @@ const createHistoryService = ({ strapi: strapi2 }) => {
312
334
  });
313
335
  },
314
336
  async findVersionsPage(params) {
315
- const locale = params.query.locale || await getDefaultLocale();
337
+ const locale = params.query.locale || await serviceUtils.getDefaultLocale();
316
338
  const [{ results, pagination }, localeDictionary] = await Promise.all([
317
339
  query.findPage({
318
340
  ...params.query,
@@ -326,78 +348,34 @@ const createHistoryService = ({ strapi: strapi2 }) => {
326
348
  populate: ["createdBy"],
327
349
  orderBy: [{ createdAt: "desc" }]
328
350
  }),
329
- getLocaleDictionary()
351
+ serviceUtils.getLocaleDictionary()
330
352
  ]);
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
353
  const populateEntryRelations = async (entry) => {
387
354
  const entryWithRelations = await Object.entries(entry.schema).reduce(
388
355
  async (currentDataWithRelations, [attributeKey, attributeSchema]) => {
389
356
  const attributeValue = entry.data[attributeKey];
390
357
  const attributeValues = Array.isArray(attributeValue) ? attributeValue : [attributeValue];
391
358
  if (attributeSchema.type === "media") {
359
+ const permissionChecker2 = getService$1("permission-checker").create({
360
+ userAbility: params.state.userAbility,
361
+ model: "plugin::upload.file"
362
+ });
363
+ const response = await serviceUtils.buildMediaResponse(attributeValues);
364
+ const sanitizedResults = await Promise.all(
365
+ response.results.map((media) => permissionChecker2.sanitizeOutput(media))
366
+ );
392
367
  return {
393
368
  ...await currentDataWithRelations,
394
- [attributeKey]: await buildMediaResponse(attributeValues)
369
+ [attributeKey]: {
370
+ results: sanitizedResults,
371
+ meta: response.meta
372
+ }
395
373
  };
396
374
  }
397
375
  if (attributeSchema.type === "relation" && attributeSchema.relation !== "morphToOne" && attributeSchema.relation !== "morphToMany") {
398
376
  if (attributeSchema.target === "admin::user") {
399
377
  const adminUsers = await Promise.all(
400
- attributeValues.map(async (userToPopulate) => {
378
+ attributeValues.map((userToPopulate) => {
401
379
  if (userToPopulate == null) {
402
380
  return null;
403
381
  }
@@ -414,9 +392,23 @@ const createHistoryService = ({ strapi: strapi2 }) => {
414
392
  [attributeKey]: adminUsers
415
393
  };
416
394
  }
395
+ const permissionChecker2 = getService$1("permission-checker").create({
396
+ userAbility: params.state.userAbility,
397
+ model: attributeSchema.target
398
+ });
399
+ const response = await serviceUtils.buildRelationReponse(
400
+ attributeValues,
401
+ attributeSchema
402
+ );
403
+ const sanitizedResults = await Promise.all(
404
+ response.results.map((media) => permissionChecker2.sanitizeOutput(media))
405
+ );
417
406
  return {
418
407
  ...await currentDataWithRelations,
419
- [attributeKey]: await buildRelationReponse(attributeValues, attributeSchema)
408
+ [attributeKey]: {
409
+ results: sanitizedResults,
410
+ meta: response.meta
411
+ }
420
412
  };
421
413
  }
422
414
  return currentDataWithRelations;
@@ -431,7 +423,7 @@ const createHistoryService = ({ strapi: strapi2 }) => {
431
423
  ...result,
432
424
  data: await populateEntryRelations(result),
433
425
  meta: {
434
- unknownAttributes: getSchemaAttributesDiff(
426
+ unknownAttributes: serviceUtils.getSchemaAttributesDiff(
435
427
  result.schema,
436
428
  strapi2.getModel(params.query.contentType).attributes
437
429
  )
@@ -448,7 +440,10 @@ const createHistoryService = ({ strapi: strapi2 }) => {
448
440
  async restoreVersion(versionId) {
449
441
  const version = await query.findOne({ where: { id: versionId } });
450
442
  const contentTypeSchemaAttributes = strapi2.getModel(version.contentType).attributes;
451
- const schemaDiff = getSchemaAttributesDiff(version.schema, contentTypeSchemaAttributes);
443
+ const schemaDiff = serviceUtils.getSchemaAttributesDiff(
444
+ version.schema,
445
+ contentTypeSchemaAttributes
446
+ );
452
447
  const dataWithoutAddedAttributes = Object.keys(schemaDiff.added).reduce(
453
448
  (currentData, addedKey) => {
454
449
  currentData[addedKey] = null;
@@ -461,61 +456,26 @@ const createHistoryService = ({ strapi: strapi2 }) => {
461
456
  FIELDS_TO_IGNORE,
462
457
  contentTypeSchemaAttributes
463
458
  );
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) {
459
+ const reducer = strapiUtils.async.reduce(Object.entries(sanitizedSchemaAttributes));
460
+ const dataWithoutMissingRelations = await reducer(
461
+ async (previousRelationAttributes, [name, attribute]) => {
462
+ const versionRelationData = version.data[name];
463
+ if (!versionRelationData) {
469
464
  return previousRelationAttributes;
470
465
  }
471
466
  if (attribute.type === "relation" && // TODO: handle polymorphic relations
472
467
  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
- }
468
+ const data2 = await serviceUtils.getRelationRestoreValue(versionRelationData, attribute);
469
+ previousRelationAttributes[name] = data2;
497
470
  }
498
471
  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
- }
472
+ const data2 = await serviceUtils.getMediaRestoreValue(versionRelationData, attribute);
473
+ previousRelationAttributes[name] = data2;
514
474
  }
515
475
  return previousRelationAttributes;
516
476
  },
517
477
  // Clone to avoid mutating the original version data
518
- Promise.resolve(structuredClone(dataWithoutAddedAttributes))
478
+ structuredClone(dataWithoutAddedAttributes)
519
479
  );
520
480
  const data = fp.omit(["id", ...Object.keys(schemaDiff.removed)], dataWithoutMissingRelations);
521
481
  const restoredDocument = await strapi2.documents(version.contentType).update({
@@ -530,8 +490,99 @@ const createHistoryService = ({ strapi: strapi2 }) => {
530
490
  }
531
491
  };
532
492
  };
493
+ const createLifecyclesService = ({ strapi: strapi2 }) => {
494
+ const state = {
495
+ deleteExpiredJob: null,
496
+ isInitialized: false
497
+ };
498
+ const query = strapi2.db.query(HISTORY_VERSION_UID);
499
+ const historyService = getService(strapi2, "history");
500
+ const serviceUtils = createServiceUtils({ strapi: strapi2 });
501
+ return {
502
+ async bootstrap() {
503
+ if (state.isInitialized) {
504
+ return;
505
+ }
506
+ strapi2.documents.use(async (context, next) => {
507
+ if (!strapi2.requestContext.get()?.request.url.startsWith("/content-manager")) {
508
+ return next();
509
+ }
510
+ if (context.action !== "create" && context.action !== "update" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
511
+ return next();
512
+ }
513
+ const contentTypeUid = context.contentType.uid;
514
+ if (!contentTypeUid.startsWith("api::")) {
515
+ return next();
516
+ }
517
+ const result = await next();
518
+ const documentContext = context.action === "create" ? { documentId: result.documentId, locale: context.params?.locale } : { documentId: context.params.documentId, locale: context.params?.locale };
519
+ const defaultLocale = await serviceUtils.getDefaultLocale();
520
+ const locale = documentContext.locale || defaultLocale;
521
+ if (Array.isArray(locale)) {
522
+ strapi2.log.warn(
523
+ "[Content manager history middleware]: An array of locales was provided, but only a single locale is supported for the findOne operation."
524
+ );
525
+ return next();
526
+ }
527
+ const document = await strapi2.documents(contentTypeUid).findOne({
528
+ documentId: documentContext.documentId,
529
+ locale,
530
+ populate: serviceUtils.getDeepPopulate(contentTypeUid)
531
+ });
532
+ const status = await serviceUtils.getVersionStatus(contentTypeUid, document);
533
+ const attributesSchema = strapi2.getModel(contentTypeUid).attributes;
534
+ const componentsSchemas = Object.keys(
535
+ attributesSchema
536
+ ).reduce((currentComponentSchemas, key) => {
537
+ const fieldSchema = attributesSchema[key];
538
+ if (fieldSchema.type === "component") {
539
+ const componentSchema = strapi2.getModel(fieldSchema.component).attributes;
540
+ return {
541
+ ...currentComponentSchemas,
542
+ [fieldSchema.component]: componentSchema
543
+ };
544
+ }
545
+ return currentComponentSchemas;
546
+ }, {});
547
+ await strapi2.db.transaction(async ({ onCommit }) => {
548
+ onCommit(() => {
549
+ historyService.createVersion({
550
+ contentType: contentTypeUid,
551
+ data: fp.omit(FIELDS_TO_IGNORE, document),
552
+ schema: fp.omit(FIELDS_TO_IGNORE, attributesSchema),
553
+ componentsSchemas,
554
+ relatedDocumentId: documentContext.documentId,
555
+ locale,
556
+ status
557
+ });
558
+ });
559
+ });
560
+ return result;
561
+ });
562
+ const retentionDays = serviceUtils.getRetentionDays();
563
+ state.deleteExpiredJob = nodeSchedule.scheduleJob("0 0 * * *", () => {
564
+ const retentionDaysInMilliseconds = retentionDays * 24 * 60 * 60 * 1e3;
565
+ const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
566
+ query.deleteMany({
567
+ where: {
568
+ created_at: {
569
+ $lt: expirationDate.toISOString()
570
+ }
571
+ }
572
+ });
573
+ });
574
+ state.isInitialized = true;
575
+ },
576
+ async destroy() {
577
+ if (state.deleteExpiredJob) {
578
+ state.deleteExpiredJob.cancel();
579
+ }
580
+ }
581
+ };
582
+ };
533
583
  const services$1 = {
534
- history: createHistoryService
584
+ history: createHistoryService,
585
+ lifecycles: createLifecyclesService
535
586
  };
536
587
  const info = { pluginName: "content-manager", type: "admin" };
537
588
  const historyVersionRouter = {
@@ -611,10 +662,10 @@ const getFeature = () => {
611
662
  strapi2.get("models").add(historyVersion);
612
663
  },
613
664
  bootstrap({ strapi: strapi2 }) {
614
- getService(strapi2, "history").bootstrap();
665
+ getService(strapi2, "lifecycles").bootstrap();
615
666
  },
616
667
  destroy({ strapi: strapi2 }) {
617
- getService(strapi2, "history").destroy();
668
+ getService(strapi2, "lifecycles").destroy();
618
669
  },
619
670
  controllers: controllers$1,
620
671
  services: services$1,
@@ -1433,7 +1484,7 @@ const { PaginationError, ValidationError } = strapiUtils.errors;
1433
1484
  const TYPES = ["singleType", "collectionType"];
1434
1485
  const kindSchema = strapiUtils.yup.string().oneOf(TYPES).nullable();
1435
1486
  const bulkActionInputSchema = strapiUtils.yup.object({
1436
- ids: strapiUtils.yup.array().of(strapiUtils.yup.strapiID()).min(1).required()
1487
+ documentIds: strapiUtils.yup.array().of(strapiUtils.yup.strapiID()).min(1).required()
1437
1488
  }).required();
1438
1489
  const generateUIDInputSchema = strapiUtils.yup.object({
1439
1490
  contentTypeUID: strapiUtils.yup.string().required(),
@@ -1532,15 +1583,47 @@ const excludeNotCreatableFields = (uid2, permissionChecker2) => (body, path = []
1532
1583
  }
1533
1584
  }, body);
1534
1585
  };
1535
- const getDocumentLocaleAndStatus = (request) => {
1586
+ const singleLocaleSchema = strapiUtils.yup.string().nullable();
1587
+ const multipleLocaleSchema = strapiUtils.yup.lazy(
1588
+ (value) => Array.isArray(value) ? strapiUtils.yup.array().of(singleLocaleSchema.required()) : singleLocaleSchema
1589
+ );
1590
+ const statusSchema = strapiUtils.yup.mixed().oneOf(["draft", "published"], "Invalid status");
1591
+ const getDocumentLocaleAndStatus = async (request, opts = { allowMultipleLocales: false }) => {
1592
+ const { allowMultipleLocales } = opts;
1536
1593
  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}`);
1594
+ const schema = strapiUtils.yup.object().shape({
1595
+ locale: allowMultipleLocales ? multipleLocaleSchema : singleLocaleSchema,
1596
+ status: statusSchema
1597
+ });
1598
+ try {
1599
+ await strapiUtils.validateYupSchema(schema, { strict: true, abortEarly: false })(request);
1600
+ return { locale, status, ...rest };
1601
+ } catch (error) {
1602
+ throw new strapiUtils.errors.ValidationError(`Validation error: ${error.message}`);
1542
1603
  }
1543
- return { locale, status, ...rest };
1604
+ };
1605
+ const formatDocumentWithMetadata = async (permissionChecker2, uid2, document, opts = {}) => {
1606
+ const documentMetadata2 = getService$1("document-metadata");
1607
+ const serviceOutput = await documentMetadata2.formatDocumentWithMetadata(uid2, document, opts);
1608
+ let {
1609
+ meta: { availableLocales, availableStatus }
1610
+ } = serviceOutput;
1611
+ const metadataSanitizer = permissionChecker2.sanitizeOutput;
1612
+ availableLocales = await strapiUtils.async.map(
1613
+ availableLocales,
1614
+ async (localeDocument) => metadataSanitizer(localeDocument)
1615
+ );
1616
+ availableStatus = await strapiUtils.async.map(
1617
+ availableStatus,
1618
+ async (statusDocument) => metadataSanitizer(statusDocument)
1619
+ );
1620
+ return {
1621
+ ...serviceOutput,
1622
+ meta: {
1623
+ availableLocales,
1624
+ availableStatus
1625
+ }
1626
+ };
1544
1627
  };
1545
1628
  const createDocument = async (ctx, opts) => {
1546
1629
  const { userAbility, user } = ctx.state;
@@ -1555,7 +1638,7 @@ const createDocument = async (ctx, opts) => {
1555
1638
  const setCreator = strapiUtils.setCreatorFields({ user });
1556
1639
  const sanitizeFn = strapiUtils.async.pipe(pickPermittedFields, setCreator);
1557
1640
  const sanitizedBody = await sanitizeFn(body);
1558
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(body);
1641
+ const { locale, status = "draft" } = await getDocumentLocaleAndStatus(body);
1559
1642
  return documentManager2.create(model, {
1560
1643
  data: sanitizedBody,
1561
1644
  locale,
@@ -1574,7 +1657,7 @@ const updateDocument = async (ctx, opts) => {
1574
1657
  }
1575
1658
  const permissionQuery = await permissionChecker2.sanitizedQuery.update(ctx.query);
1576
1659
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1577
- const { locale } = getDocumentLocaleAndStatus(body);
1660
+ const { locale } = await getDocumentLocaleAndStatus(body);
1578
1661
  const [documentVersion, documentExists] = await Promise.all([
1579
1662
  documentManager2.findOne(id, model, { populate, locale, status: "draft" }),
1580
1663
  documentManager2.exists(model, id)
@@ -1612,7 +1695,7 @@ const collectionTypes = {
1612
1695
  }
1613
1696
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
1614
1697
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(1).countRelations({ toOne: false, toMany: true }).build();
1615
- const { locale, status } = getDocumentLocaleAndStatus(query);
1698
+ const { locale, status } = await getDocumentLocaleAndStatus(query);
1616
1699
  const { results: documents, pagination } = await documentManager2.findPage(
1617
1700
  { ...permissionQuery, populate, locale, status },
1618
1701
  model
@@ -1641,14 +1724,13 @@ const collectionTypes = {
1641
1724
  const { userAbility } = ctx.state;
1642
1725
  const { model, id } = ctx.params;
1643
1726
  const documentManager2 = getService$1("document-manager");
1644
- const documentMetadata2 = getService$1("document-metadata");
1645
1727
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1646
1728
  if (permissionChecker2.cannot.read()) {
1647
1729
  return ctx.forbidden();
1648
1730
  }
1649
1731
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1650
1732
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1651
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
1733
+ const { locale, status = "draft" } = await getDocumentLocaleAndStatus(ctx.query);
1652
1734
  const version = await documentManager2.findOne(id, model, {
1653
1735
  populate,
1654
1736
  locale,
@@ -1659,8 +1741,10 @@ const collectionTypes = {
1659
1741
  if (!exists) {
1660
1742
  return ctx.notFound();
1661
1743
  }
1662
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
1744
+ const { meta } = await formatDocumentWithMetadata(
1745
+ permissionChecker2,
1663
1746
  model,
1747
+ // @ts-expect-error TODO: fix
1664
1748
  { id, locale, publishedAt: null },
1665
1749
  { availableLocales: true, availableStatus: false }
1666
1750
  );
@@ -1671,12 +1755,11 @@ const collectionTypes = {
1671
1755
  return ctx.forbidden();
1672
1756
  }
1673
1757
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
1674
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1758
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1675
1759
  },
1676
1760
  async create(ctx) {
1677
1761
  const { userAbility } = ctx.state;
1678
1762
  const { model } = ctx.params;
1679
- const documentMetadata2 = getService$1("document-metadata");
1680
1763
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1681
1764
  const [totalEntries, document] = await Promise.all([
1682
1765
  strapi.db.query(model).count(),
@@ -1684,7 +1767,7 @@ const collectionTypes = {
1684
1767
  ]);
1685
1768
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
1686
1769
  ctx.status = 201;
1687
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1770
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1688
1771
  // Empty metadata as it's not relevant for a new document
1689
1772
  availableLocales: false,
1690
1773
  availableStatus: false
@@ -1698,25 +1781,23 @@ const collectionTypes = {
1698
1781
  async update(ctx) {
1699
1782
  const { userAbility } = ctx.state;
1700
1783
  const { model } = ctx.params;
1701
- const documentMetadata2 = getService$1("document-metadata");
1702
1784
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1703
1785
  const updatedVersion = await updateDocument(ctx);
1704
1786
  const sanitizedVersion = await permissionChecker2.sanitizeOutput(updatedVersion);
1705
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedVersion);
1787
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedVersion);
1706
1788
  },
1707
1789
  async clone(ctx) {
1708
1790
  const { userAbility, user } = ctx.state;
1709
1791
  const { model, sourceId: id } = ctx.params;
1710
1792
  const { body } = ctx.request;
1711
1793
  const documentManager2 = getService$1("document-manager");
1712
- const documentMetadata2 = getService$1("document-metadata");
1713
1794
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1714
1795
  if (permissionChecker2.cannot.create()) {
1715
1796
  return ctx.forbidden();
1716
1797
  }
1717
1798
  const permissionQuery = await permissionChecker2.sanitizedQuery.create(ctx.query);
1718
1799
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1719
- const { locale } = getDocumentLocaleAndStatus(body);
1800
+ const { locale } = await getDocumentLocaleAndStatus(body);
1720
1801
  const document = await documentManager2.findOne(id, model, {
1721
1802
  populate,
1722
1803
  locale,
@@ -1732,7 +1813,7 @@ const collectionTypes = {
1732
1813
  const sanitizedBody = await sanitizeFn(body);
1733
1814
  const clonedDocument = await documentManager2.clone(document.documentId, sanitizedBody, model);
1734
1815
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(clonedDocument);
1735
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1816
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1736
1817
  // Empty metadata as it's not relevant for a new document
1737
1818
  availableLocales: false,
1738
1819
  availableStatus: false
@@ -1761,7 +1842,7 @@ const collectionTypes = {
1761
1842
  }
1762
1843
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(ctx.query);
1763
1844
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1764
- const { locale } = getDocumentLocaleAndStatus(ctx.query);
1845
+ const { locale } = await getDocumentLocaleAndStatus(ctx.query);
1765
1846
  const documentLocales = await documentManager2.findLocales(id, model, { populate, locale });
1766
1847
  if (documentLocales.length === 0) {
1767
1848
  return ctx.notFound();
@@ -1783,7 +1864,6 @@ const collectionTypes = {
1783
1864
  const { id, model } = ctx.params;
1784
1865
  const { body } = ctx.request;
1785
1866
  const documentManager2 = getService$1("document-manager");
1786
- const documentMetadata2 = getService$1("document-metadata");
1787
1867
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1788
1868
  if (permissionChecker2.cannot.publish()) {
1789
1869
  return ctx.forbidden();
@@ -1795,21 +1875,25 @@ const collectionTypes = {
1795
1875
  if (permissionChecker2.cannot.publish(document)) {
1796
1876
  throw new strapiUtils.errors.ForbiddenError();
1797
1877
  }
1798
- const { locale } = getDocumentLocaleAndStatus(body);
1799
- return documentManager2.publish(document.documentId, model, {
1878
+ const { locale } = await getDocumentLocaleAndStatus(body);
1879
+ const publishResult = await documentManager2.publish(document.documentId, model, {
1800
1880
  locale
1801
1881
  // TODO: Allow setting creator fields on publish
1802
1882
  // data: setCreatorFields({ user, isEdition: true })({}),
1803
1883
  });
1884
+ if (!publishResult || publishResult.length === 0) {
1885
+ throw new strapiUtils.errors.NotFoundError("Document not found or already published.");
1886
+ }
1887
+ return publishResult[0];
1804
1888
  });
1805
1889
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
1806
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1890
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1807
1891
  },
1808
1892
  async bulkPublish(ctx) {
1809
1893
  const { userAbility } = ctx.state;
1810
1894
  const { model } = ctx.params;
1811
1895
  const { body } = ctx.request;
1812
- const { ids } = body;
1896
+ const { documentIds } = body;
1813
1897
  await validateBulkActionInput(body);
1814
1898
  const documentManager2 = getService$1("document-manager");
1815
1899
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1818,8 +1902,11 @@ const collectionTypes = {
1818
1902
  }
1819
1903
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1820
1904
  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);
1905
+ const { locale } = await getDocumentLocaleAndStatus(body, { allowMultipleLocales: true });
1906
+ const entityPromises = documentIds.map(
1907
+ (documentId) => documentManager2.findLocales(documentId, model, { populate, locale, isPublished: false })
1908
+ );
1909
+ const entities = (await Promise.all(entityPromises)).flat();
1823
1910
  for (const entity of entities) {
1824
1911
  if (!entity) {
1825
1912
  return ctx.notFound();
@@ -1828,24 +1915,25 @@ const collectionTypes = {
1828
1915
  return ctx.forbidden();
1829
1916
  }
1830
1917
  }
1831
- const { count } = await documentManager2.publishMany(entities, model);
1918
+ const count = await documentManager2.publishMany(model, documentIds, locale);
1832
1919
  ctx.body = { count };
1833
1920
  },
1834
1921
  async bulkUnpublish(ctx) {
1835
1922
  const { userAbility } = ctx.state;
1836
1923
  const { model } = ctx.params;
1837
1924
  const { body } = ctx.request;
1838
- const { ids } = body;
1925
+ const { documentIds } = body;
1839
1926
  await validateBulkActionInput(body);
1840
1927
  const documentManager2 = getService$1("document-manager");
1841
1928
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1842
1929
  if (permissionChecker2.cannot.unpublish()) {
1843
1930
  return ctx.forbidden();
1844
1931
  }
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);
1932
+ const { locale } = await getDocumentLocaleAndStatus(body);
1933
+ const entityPromises = documentIds.map(
1934
+ (documentId) => documentManager2.findLocales(documentId, model, { locale, isPublished: true })
1935
+ );
1936
+ const entities = (await Promise.all(entityPromises)).flat();
1849
1937
  for (const entity of entities) {
1850
1938
  if (!entity) {
1851
1939
  return ctx.notFound();
@@ -1854,7 +1942,8 @@ const collectionTypes = {
1854
1942
  return ctx.forbidden();
1855
1943
  }
1856
1944
  }
1857
- const { count } = await documentManager2.unpublishMany(entities, model);
1945
+ const entitiesIds = entities.map((document) => document.documentId);
1946
+ const { count } = await documentManager2.unpublishMany(entitiesIds, model, { locale });
1858
1947
  ctx.body = { count };
1859
1948
  },
1860
1949
  async unpublish(ctx) {
@@ -1864,7 +1953,6 @@ const collectionTypes = {
1864
1953
  body: { discardDraft, ...body }
1865
1954
  } = ctx.request;
1866
1955
  const documentManager2 = getService$1("document-manager");
1867
- const documentMetadata2 = getService$1("document-metadata");
1868
1956
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1869
1957
  if (permissionChecker2.cannot.unpublish()) {
1870
1958
  return ctx.forbidden();
@@ -1874,7 +1962,7 @@ const collectionTypes = {
1874
1962
  }
1875
1963
  const permissionQuery = await permissionChecker2.sanitizedQuery.unpublish(ctx.query);
1876
1964
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1877
- const { locale } = getDocumentLocaleAndStatus(body);
1965
+ const { locale } = await getDocumentLocaleAndStatus(body);
1878
1966
  const document = await documentManager2.findOne(id, model, {
1879
1967
  populate,
1880
1968
  locale,
@@ -1896,7 +1984,7 @@ const collectionTypes = {
1896
1984
  ctx.body = await strapiUtils.async.pipe(
1897
1985
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
1898
1986
  permissionChecker2.sanitizeOutput,
1899
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
1987
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1900
1988
  )(document);
1901
1989
  });
1902
1990
  },
@@ -1905,14 +1993,13 @@ const collectionTypes = {
1905
1993
  const { id, model } = ctx.params;
1906
1994
  const { body } = ctx.request;
1907
1995
  const documentManager2 = getService$1("document-manager");
1908
- const documentMetadata2 = getService$1("document-metadata");
1909
1996
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1910
1997
  if (permissionChecker2.cannot.discard()) {
1911
1998
  return ctx.forbidden();
1912
1999
  }
1913
2000
  const permissionQuery = await permissionChecker2.sanitizedQuery.discard(ctx.query);
1914
2001
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1915
- const { locale } = getDocumentLocaleAndStatus(body);
2002
+ const { locale } = await getDocumentLocaleAndStatus(body);
1916
2003
  const document = await documentManager2.findOne(id, model, {
1917
2004
  populate,
1918
2005
  locale,
@@ -1927,14 +2014,14 @@ const collectionTypes = {
1927
2014
  ctx.body = await strapiUtils.async.pipe(
1928
2015
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
1929
2016
  permissionChecker2.sanitizeOutput,
1930
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2017
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1931
2018
  )(document);
1932
2019
  },
1933
2020
  async bulkDelete(ctx) {
1934
2021
  const { userAbility } = ctx.state;
1935
2022
  const { model } = ctx.params;
1936
2023
  const { query, body } = ctx.request;
1937
- const { ids } = body;
2024
+ const { documentIds } = body;
1938
2025
  await validateBulkActionInput(body);
1939
2026
  const documentManager2 = getService$1("document-manager");
1940
2027
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1942,14 +2029,22 @@ const collectionTypes = {
1942
2029
  return ctx.forbidden();
1943
2030
  }
1944
2031
  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 || [])
2032
+ const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2033
+ const { locale } = await getDocumentLocaleAndStatus(body);
2034
+ const documentLocales = await documentManager2.findLocales(documentIds, model, {
2035
+ populate,
2036
+ locale
2037
+ });
2038
+ if (documentLocales.length === 0) {
2039
+ return ctx.notFound();
2040
+ }
2041
+ for (const document of documentLocales) {
2042
+ if (permissionChecker2.cannot.delete(document)) {
2043
+ return ctx.forbidden();
1950
2044
  }
1951
- };
1952
- const { count } = await documentManager2.deleteMany(params, model);
2045
+ }
2046
+ const localeDocumentsIds = documentLocales.map((document) => document.documentId);
2047
+ const { count } = await documentManager2.deleteMany(localeDocumentsIds, model, { locale });
1953
2048
  ctx.body = { count };
1954
2049
  },
1955
2050
  async countDraftRelations(ctx) {
@@ -1962,7 +2057,7 @@ const collectionTypes = {
1962
2057
  }
1963
2058
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1964
2059
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1965
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
2060
+ const { locale, status = "draft" } = await getDocumentLocaleAndStatus(ctx.query);
1966
2061
  const entity = await documentManager2.findOne(id, model, { populate, locale, status });
1967
2062
  if (!entity) {
1968
2063
  return ctx.notFound();
@@ -1977,7 +2072,7 @@ const collectionTypes = {
1977
2072
  },
1978
2073
  async countManyEntriesDraftRelations(ctx) {
1979
2074
  const { userAbility } = ctx.state;
1980
- const ids = ctx.request.query.ids;
2075
+ const ids = ctx.request.query.documentIds;
1981
2076
  const locale = ctx.request.query.locale;
1982
2077
  const { model } = ctx.params;
1983
2078
  const documentManager2 = getService$1("document-manager");
@@ -1988,7 +2083,7 @@ const collectionTypes = {
1988
2083
  const entities = await documentManager2.findMany(
1989
2084
  {
1990
2085
  filters: {
1991
- id: ids
2086
+ documentId: ids
1992
2087
  },
1993
2088
  locale
1994
2089
  },
@@ -2490,7 +2585,7 @@ const createOrUpdateDocument = async (ctx, opts) => {
2490
2585
  throw new strapiUtils.errors.ForbiddenError();
2491
2586
  }
2492
2587
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.update(query);
2493
- const { locale } = getDocumentLocaleAndStatus(body);
2588
+ const { locale } = await getDocumentLocaleAndStatus(body);
2494
2589
  const [documentVersion, otherDocumentVersion] = await Promise.all([
2495
2590
  findDocument(sanitizedQuery, model, { locale, status: "draft" }),
2496
2591
  // Find the first document to check if it exists
@@ -2527,12 +2622,11 @@ const singleTypes = {
2527
2622
  const { model } = ctx.params;
2528
2623
  const { query = {} } = ctx.request;
2529
2624
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2530
- const documentMetadata2 = getService$1("document-metadata");
2531
2625
  if (permissionChecker2.cannot.read()) {
2532
2626
  return ctx.forbidden();
2533
2627
  }
2534
2628
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
2535
- const { locale, status } = getDocumentLocaleAndStatus(query);
2629
+ const { locale, status } = await getDocumentLocaleAndStatus(query);
2536
2630
  const version = await findDocument(permissionQuery, model, { locale, status });
2537
2631
  if (!version) {
2538
2632
  if (permissionChecker2.cannot.create()) {
@@ -2542,8 +2636,10 @@ const singleTypes = {
2542
2636
  if (!document) {
2543
2637
  return ctx.notFound();
2544
2638
  }
2545
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
2639
+ const { meta } = await formatDocumentWithMetadata(
2640
+ permissionChecker2,
2546
2641
  model,
2642
+ // @ts-expect-error - fix types
2547
2643
  { id: document.documentId, locale, publishedAt: null },
2548
2644
  { availableLocales: true, availableStatus: false }
2549
2645
  );
@@ -2554,16 +2650,15 @@ const singleTypes = {
2554
2650
  return ctx.forbidden();
2555
2651
  }
2556
2652
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
2557
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2653
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2558
2654
  },
2559
2655
  async createOrUpdate(ctx) {
2560
2656
  const { userAbility } = ctx.state;
2561
2657
  const { model } = ctx.params;
2562
- const documentMetadata2 = getService$1("document-metadata");
2563
2658
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2564
2659
  const document = await createOrUpdateDocument(ctx);
2565
2660
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
2566
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2661
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2567
2662
  },
2568
2663
  async delete(ctx) {
2569
2664
  const { userAbility } = ctx.state;
@@ -2576,7 +2671,7 @@ const singleTypes = {
2576
2671
  }
2577
2672
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.delete(query);
2578
2673
  const populate = await buildPopulateFromQuery(sanitizedQuery, model);
2579
- const { locale } = getDocumentLocaleAndStatus(query);
2674
+ const { locale } = await getDocumentLocaleAndStatus(query);
2580
2675
  const documentLocales = await documentManager2.findLocales(void 0, model, {
2581
2676
  populate,
2582
2677
  locale
@@ -2599,7 +2694,6 @@ const singleTypes = {
2599
2694
  const { model } = ctx.params;
2600
2695
  const { query = {} } = ctx.request;
2601
2696
  const documentManager2 = getService$1("document-manager");
2602
- const documentMetadata2 = getService$1("document-metadata");
2603
2697
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2604
2698
  if (permissionChecker2.cannot.publish()) {
2605
2699
  return ctx.forbidden();
@@ -2614,11 +2708,12 @@ const singleTypes = {
2614
2708
  if (permissionChecker2.cannot.publish(document)) {
2615
2709
  throw new strapiUtils.errors.ForbiddenError();
2616
2710
  }
2617
- const { locale } = getDocumentLocaleAndStatus(document);
2618
- return documentManager2.publish(document.documentId, model, { locale });
2711
+ const { locale } = await getDocumentLocaleAndStatus(document);
2712
+ const publishResult = await documentManager2.publish(document.documentId, model, { locale });
2713
+ return publishResult.at(0);
2619
2714
  });
2620
2715
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
2621
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2716
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2622
2717
  },
2623
2718
  async unpublish(ctx) {
2624
2719
  const { userAbility } = ctx.state;
@@ -2628,7 +2723,6 @@ const singleTypes = {
2628
2723
  query = {}
2629
2724
  } = ctx.request;
2630
2725
  const documentManager2 = getService$1("document-manager");
2631
- const documentMetadata2 = getService$1("document-metadata");
2632
2726
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2633
2727
  if (permissionChecker2.cannot.unpublish()) {
2634
2728
  return ctx.forbidden();
@@ -2637,7 +2731,7 @@ const singleTypes = {
2637
2731
  return ctx.forbidden();
2638
2732
  }
2639
2733
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.unpublish(query);
2640
- const { locale } = getDocumentLocaleAndStatus(body);
2734
+ const { locale } = await getDocumentLocaleAndStatus(body);
2641
2735
  const document = await findDocument(sanitizedQuery, model, { locale });
2642
2736
  if (!document) {
2643
2737
  return ctx.notFound();
@@ -2655,7 +2749,7 @@ const singleTypes = {
2655
2749
  ctx.body = await strapiUtils.async.pipe(
2656
2750
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
2657
2751
  permissionChecker2.sanitizeOutput,
2658
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2752
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2659
2753
  )(document);
2660
2754
  });
2661
2755
  },
@@ -2664,13 +2758,12 @@ const singleTypes = {
2664
2758
  const { model } = ctx.params;
2665
2759
  const { body, query = {} } = ctx.request;
2666
2760
  const documentManager2 = getService$1("document-manager");
2667
- const documentMetadata2 = getService$1("document-metadata");
2668
2761
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2669
2762
  if (permissionChecker2.cannot.discard()) {
2670
2763
  return ctx.forbidden();
2671
2764
  }
2672
2765
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.discard(query);
2673
- const { locale } = getDocumentLocaleAndStatus(body);
2766
+ const { locale } = await getDocumentLocaleAndStatus(body);
2674
2767
  const document = await findDocument(sanitizedQuery, model, { locale, status: "published" });
2675
2768
  if (!document) {
2676
2769
  return ctx.notFound();
@@ -2681,7 +2774,7 @@ const singleTypes = {
2681
2774
  ctx.body = await strapiUtils.async.pipe(
2682
2775
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
2683
2776
  permissionChecker2.sanitizeOutput,
2684
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2777
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2685
2778
  )(document);
2686
2779
  },
2687
2780
  async countDraftRelations(ctx) {
@@ -2690,7 +2783,7 @@ const singleTypes = {
2690
2783
  const { query } = ctx.request;
2691
2784
  const documentManager2 = getService$1("document-manager");
2692
2785
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2693
- const { locale } = getDocumentLocaleAndStatus(query);
2786
+ const { locale } = await getDocumentLocaleAndStatus(query);
2694
2787
  if (permissionChecker2.cannot.read()) {
2695
2788
  return ctx.forbidden();
2696
2789
  }
@@ -2711,7 +2804,7 @@ const uid$1 = {
2711
2804
  async generateUID(ctx) {
2712
2805
  const { contentTypeUID, field, data } = await validateGenerateUIDInput(ctx.request.body);
2713
2806
  const { query = {} } = ctx.request;
2714
- const { locale } = getDocumentLocaleAndStatus(query);
2807
+ const { locale } = await getDocumentLocaleAndStatus(query);
2715
2808
  await validateUIDField(contentTypeUID, field);
2716
2809
  const uidService = getService$1("uid");
2717
2810
  ctx.body = {
@@ -2723,7 +2816,7 @@ const uid$1 = {
2723
2816
  ctx.request.body
2724
2817
  );
2725
2818
  const { query = {} } = ctx.request;
2726
- const { locale } = getDocumentLocaleAndStatus(query);
2819
+ const { locale } = await getDocumentLocaleAndStatus(query);
2727
2820
  await validateUIDField(contentTypeUID, field);
2728
2821
  const uidService = getService$1("uid");
2729
2822
  const isAvailable = await uidService.checkUIDAvailability({
@@ -3514,7 +3607,7 @@ const permission = ({ strapi: strapi2 }) => ({
3514
3607
  await strapi2.service("admin::permission").actionProvider.registerMany(actions);
3515
3608
  }
3516
3609
  });
3517
- const { isVisibleAttribute: isVisibleAttribute$1 } = strapiUtils__default.default.contentTypes;
3610
+ const { isVisibleAttribute: isVisibleAttribute$1, isScalarAttribute, getDoesAttributeRequireValidation } = strapiUtils__default.default.contentTypes;
3518
3611
  const { isAnyToMany } = strapiUtils__default.default.relations;
3519
3612
  const { PUBLISHED_AT_ATTRIBUTE: PUBLISHED_AT_ATTRIBUTE$1 } = strapiUtils__default.default.contentTypes.constants;
3520
3613
  const isMorphToRelation = (attribute) => isRelation(attribute) && attribute.relation.includes("morphTo");
@@ -3605,6 +3698,42 @@ const getDeepPopulate = (uid2, {
3605
3698
  {}
3606
3699
  );
3607
3700
  };
3701
+ const getValidatableFieldsPopulate = (uid2, {
3702
+ initialPopulate = {},
3703
+ countMany = false,
3704
+ countOne = false,
3705
+ maxLevel = Infinity
3706
+ } = {}, level = 1) => {
3707
+ if (level > maxLevel) {
3708
+ return {};
3709
+ }
3710
+ const model = strapi.getModel(uid2);
3711
+ return Object.entries(model.attributes).reduce((populateAcc, [attributeName, attribute]) => {
3712
+ if (!getDoesAttributeRequireValidation(attribute)) {
3713
+ return populateAcc;
3714
+ }
3715
+ if (isScalarAttribute(attribute)) {
3716
+ return fp.merge(populateAcc, {
3717
+ [attributeName]: true
3718
+ });
3719
+ }
3720
+ return fp.merge(
3721
+ populateAcc,
3722
+ getPopulateFor(
3723
+ attributeName,
3724
+ model,
3725
+ {
3726
+ // @ts-expect-error - improve types
3727
+ initialPopulate: initialPopulate?.[attributeName],
3728
+ countMany,
3729
+ countOne,
3730
+ maxLevel
3731
+ },
3732
+ level
3733
+ )
3734
+ );
3735
+ }, {});
3736
+ };
3608
3737
  const getDeepPopulateDraftCount = (uid2) => {
3609
3738
  const model = strapi.getModel(uid2);
3610
3739
  let hasRelations = false;
@@ -3833,41 +3962,70 @@ const AVAILABLE_STATUS_FIELDS = [
3833
3962
  "updatedBy",
3834
3963
  "status"
3835
3964
  ];
3836
- const AVAILABLE_LOCALES_FIELDS = ["id", "locale", "updatedAt", "createdAt", "status"];
3965
+ const AVAILABLE_LOCALES_FIELDS = [
3966
+ "id",
3967
+ "locale",
3968
+ "updatedAt",
3969
+ "createdAt",
3970
+ "status",
3971
+ "publishedAt",
3972
+ "documentId"
3973
+ ];
3837
3974
  const CONTENT_MANAGER_STATUS = {
3838
3975
  PUBLISHED: "published",
3839
3976
  DRAFT: "draft",
3840
3977
  MODIFIED: "modified"
3841
3978
  };
3842
- const areDatesEqual = (date1, date2, threshold) => {
3843
- if (!date1 || !date2) {
3979
+ const getIsVersionLatestModification = (version, otherVersion) => {
3980
+ if (!version || !version.updatedAt) {
3844
3981
  return false;
3845
3982
  }
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;
3983
+ const versionUpdatedAt = version?.updatedAt ? new Date(version.updatedAt).getTime() : 0;
3984
+ const otherUpdatedAt = otherVersion?.updatedAt ? new Date(otherVersion.updatedAt).getTime() : 0;
3985
+ return versionUpdatedAt > otherUpdatedAt;
3850
3986
  };
3851
3987
  const documentMetadata = ({ strapi: strapi2 }) => ({
3852
3988
  /**
3853
3989
  * Returns available locales of a document for the current status
3854
3990
  */
3855
- getAvailableLocales(uid2, version, allVersions) {
3991
+ async getAvailableLocales(uid2, version, allVersions, validatableFields = []) {
3856
3992
  const versionsByLocale = fp.groupBy("locale", allVersions);
3857
3993
  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]);
3994
+ const model = strapi2.getModel(uid2);
3995
+ const keysToKeep = [...AVAILABLE_LOCALES_FIELDS, ...validatableFields];
3996
+ const traversalFunction = async (localeVersion) => strapiUtils.traverseEntity(
3997
+ ({ key }, { remove }) => {
3998
+ if (keysToKeep.includes(key)) {
3999
+ return;
4000
+ }
4001
+ remove(key);
4002
+ },
4003
+ { schema: model, getModel: strapi2.getModel.bind(strapi2) },
4004
+ // @ts-expect-error fix types DocumentVersion incompatible with Data
4005
+ localeVersion
4006
+ );
4007
+ const mappingResult = await strapiUtils.async.map(
4008
+ Object.values(versionsByLocale),
4009
+ async (localeVersions) => {
4010
+ const mappedLocaleVersions = await strapiUtils.async.map(
4011
+ localeVersions,
4012
+ traversalFunction
4013
+ );
4014
+ if (!strapiUtils.contentTypes.hasDraftAndPublish(model)) {
4015
+ return mappedLocaleVersions[0];
4016
+ }
4017
+ const draftVersion = mappedLocaleVersions.find((v) => v.publishedAt === null);
4018
+ const otherVersions = mappedLocaleVersions.filter((v) => v.id !== draftVersion?.id);
4019
+ if (!draftVersion) {
4020
+ return;
4021
+ }
4022
+ return {
4023
+ ...draftVersion,
4024
+ status: this.getStatus(draftVersion, otherVersions)
4025
+ };
3861
4026
  }
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);
4027
+ );
4028
+ return mappingResult.filter(Boolean);
3871
4029
  },
3872
4030
  /**
3873
4031
  * Returns available status of a document for the current locale
@@ -3905,26 +4063,37 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3905
4063
  });
3906
4064
  },
3907
4065
  getStatus(version, otherDocumentStatuses) {
3908
- const isDraft = version.publishedAt === null;
3909
- if (!otherDocumentStatuses?.length) {
3910
- return isDraft ? CONTENT_MANAGER_STATUS.DRAFT : CONTENT_MANAGER_STATUS.PUBLISHED;
4066
+ let draftVersion;
4067
+ let publishedVersion;
4068
+ if (version.publishedAt) {
4069
+ publishedVersion = version;
4070
+ } else {
4071
+ draftVersion = version;
3911
4072
  }
3912
- if (isDraft) {
3913
- const publishedVersion = otherDocumentStatuses?.find((d) => d.publishedAt !== null);
3914
- if (!publishedVersion) {
3915
- return CONTENT_MANAGER_STATUS.DRAFT;
3916
- }
4073
+ const otherVersion = otherDocumentStatuses?.at(0);
4074
+ if (otherVersion?.publishedAt) {
4075
+ publishedVersion = otherVersion;
4076
+ } else if (otherVersion) {
4077
+ draftVersion = otherVersion;
3917
4078
  }
3918
- if (areDatesEqual(version.updatedAt, otherDocumentStatuses.at(0)?.updatedAt, 500)) {
4079
+ if (!draftVersion)
3919
4080
  return CONTENT_MANAGER_STATUS.PUBLISHED;
3920
- }
3921
- return CONTENT_MANAGER_STATUS.MODIFIED;
4081
+ if (!publishedVersion)
4082
+ return CONTENT_MANAGER_STATUS.DRAFT;
4083
+ const isDraftModified = getIsVersionLatestModification(draftVersion, publishedVersion);
4084
+ return isDraftModified ? CONTENT_MANAGER_STATUS.MODIFIED : CONTENT_MANAGER_STATUS.PUBLISHED;
3922
4085
  },
4086
+ // TODO is it necessary to return metadata on every page of the CM
4087
+ // We could refactor this so the locales are only loaded when they're
4088
+ // needed. e.g. in the bulk locale action modal.
3923
4089
  async getMetadata(uid2, version, { availableLocales = true, availableStatus = true } = {}) {
4090
+ const populate = getValidatableFieldsPopulate(uid2);
3924
4091
  const versions = await strapi2.db.query(uid2).findMany({
3925
4092
  where: { documentId: version.documentId },
3926
- select: ["createdAt", "updatedAt", "locale", "publishedAt", "documentId"],
3927
4093
  populate: {
4094
+ // Populate only fields that require validation for bulk locale actions
4095
+ ...populate,
4096
+ // NOTE: creator fields are selected in this way to avoid exposing sensitive data
3928
4097
  createdBy: {
3929
4098
  select: ["id", "firstname", "lastname", "email"]
3930
4099
  },
@@ -3933,7 +4102,7 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3933
4102
  }
3934
4103
  }
3935
4104
  });
3936
- const availableLocalesResult = availableLocales ? this.getAvailableLocales(uid2, version, versions) : [];
4105
+ const availableLocalesResult = availableLocales ? await this.getAvailableLocales(uid2, version, versions, Object.keys(populate)) : [];
3937
4106
  const availableStatusResult = availableStatus ? this.getAvailableStatus(version, versions) : null;
3938
4107
  return {
3939
4108
  availableLocales: availableLocalesResult,
@@ -3946,8 +4115,9 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3946
4115
  * - Available status of the document for the current locale
3947
4116
  */
3948
4117
  async formatDocumentWithMetadata(uid2, document, opts = {}) {
3949
- if (!document)
4118
+ if (!document) {
3950
4119
  return document;
4120
+ }
3951
4121
  const hasDraftAndPublish = strapiUtils.contentTypes.hasDraftAndPublish(strapi2.getModel(uid2));
3952
4122
  if (!hasDraftAndPublish) {
3953
4123
  opts.availableStatus = false;
@@ -3997,26 +4167,9 @@ const sumDraftCounts = (entity, uid2) => {
3997
4167
  }, 0);
3998
4168
  };
3999
4169
  const { ApplicationError } = strapiUtils.errors;
4000
- const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = ALLOWED_WEBHOOK_EVENTS;
4001
4170
  const { PUBLISHED_AT_ATTRIBUTE } = strapiUtils.contentTypes.constants;
4002
4171
  const omitPublishedAtField = fp.omit(PUBLISHED_AT_ATTRIBUTE);
4003
4172
  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
4173
  const documentManager = ({ strapi: strapi2 }) => {
4021
4174
  return {
4022
4175
  async findOne(id, uid2, opts = {}) {
@@ -4035,6 +4188,9 @@ const documentManager = ({ strapi: strapi2 }) => {
4035
4188
  } else if (opts.locale && opts.locale !== "*") {
4036
4189
  where.locale = opts.locale;
4037
4190
  }
4191
+ if (typeof opts.isPublished === "boolean") {
4192
+ where.publishedAt = { $notNull: opts.isPublished };
4193
+ }
4038
4194
  return strapi2.db.query(uid2).findMany({ populate: opts.populate, where });
4039
4195
  },
4040
4196
  async findMany(opts, uid2) {
@@ -4042,20 +4198,16 @@ const documentManager = ({ strapi: strapi2 }) => {
4042
4198
  return strapi2.documents(uid2).findMany(params);
4043
4199
  },
4044
4200
  async findPage(opts, uid2) {
4045
- const page = Number(opts?.page) || 1;
4046
- const pageSize = Number(opts?.pageSize) || 10;
4201
+ const params = strapiUtils.pagination.withDefaultPagination(opts || {}, {
4202
+ maxLimit: 1e3
4203
+ });
4047
4204
  const [documents, total = 0] = await Promise.all([
4048
- strapi2.documents(uid2).findMany(opts),
4049
- strapi2.documents(uid2).count(opts)
4205
+ strapi2.documents(uid2).findMany(params),
4206
+ strapi2.documents(uid2).count(params)
4050
4207
  ]);
4051
4208
  return {
4052
4209
  results: documents,
4053
- pagination: {
4054
- page,
4055
- pageSize,
4056
- pageCount: Math.ceil(total / pageSize),
4057
- total
4058
- }
4210
+ pagination: strapiUtils.pagination.transformPagedPaginationInfo(params, total)
4059
4211
  };
4060
4212
  },
4061
4213
  async create(uid2, opts = {}) {
@@ -4101,70 +4253,36 @@ const documentManager = ({ strapi: strapi2 }) => {
4101
4253
  return {};
4102
4254
  },
4103
4255
  // 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 };
4256
+ async deleteMany(documentIds, uid2, opts = {}) {
4257
+ const deletedEntries = await strapi2.db.transaction(async () => {
4258
+ return Promise.all(documentIds.map(async (id) => this.delete(id, uid2, opts)));
4259
+ });
4260
+ return { count: deletedEntries.length };
4110
4261
  },
4111
4262
  async publish(id, uid2, opts = {}) {
4112
4263
  const populate = await buildDeepPopulate(uid2);
4113
4264
  const params = { ...opts, populate };
4114
- return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries.at(0));
4265
+ return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries);
4115
4266
  },
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
4267
+ async publishMany(uid2, documentIds, locale) {
4268
+ return strapi2.db.transaction(async () => {
4269
+ const results = await Promise.all(
4270
+ documentIds.map((documentId) => this.publish(documentId, uid2, { locale }))
4271
+ );
4272
+ const publishedEntitiesCount = results.flat().filter(Boolean).length;
4273
+ return publishedEntitiesCount;
4142
4274
  });
4143
- await Promise.all(
4144
- publishedEntities.map((doc) => emitEvent(uid2, ENTRY_PUBLISH, doc))
4145
- );
4146
- return publishedEntitiesCount;
4147
4275
  },
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
4276
+ async unpublishMany(documentIds, uid2, opts = {}) {
4277
+ const unpublishedEntries = await strapi2.db.transaction(async () => {
4278
+ return Promise.all(
4279
+ documentIds.map(
4280
+ (id) => strapi2.documents(uid2).unpublish({ ...opts, documentId: id }).then((result) => result?.entries)
4281
+ )
4282
+ );
4163
4283
  });
4164
- await Promise.all(
4165
- unpublishedEntities.map((doc) => emitEvent(uid2, ENTRY_UNPUBLISH, doc))
4166
- );
4167
- return unpublishedEntitiesCount;
4284
+ const unpublishedEntitiesCount = unpublishedEntries.flat().filter(Boolean).length;
4285
+ return { count: unpublishedEntitiesCount };
4168
4286
  },
4169
4287
  async unpublish(id, uid2, opts = {}) {
4170
4288
  const populate = await buildDeepPopulate(uid2);
@@ -4189,16 +4307,20 @@ const documentManager = ({ strapi: strapi2 }) => {
4189
4307
  }
4190
4308
  return sumDraftCounts(document, uid2);
4191
4309
  },
4192
- async countManyEntriesDraftRelations(ids, uid2, locale) {
4310
+ async countManyEntriesDraftRelations(documentIds, uid2, locale) {
4193
4311
  const { populate, hasRelations } = getDeepPopulateDraftCount(uid2);
4194
4312
  if (!hasRelations) {
4195
4313
  return 0;
4196
4314
  }
4315
+ let localeFilter = {};
4316
+ if (locale) {
4317
+ localeFilter = Array.isArray(locale) ? { locale: { $in: locale } } : { locale };
4318
+ }
4197
4319
  const entities = await strapi2.db.query(uid2).findMany({
4198
4320
  populate,
4199
4321
  where: {
4200
- id: { $in: ids },
4201
- ...locale ? { locale } : {}
4322
+ documentId: { $in: documentIds },
4323
+ ...localeFilter
4202
4324
  }
4203
4325
  });
4204
4326
  const totalNumberDraftRelations = entities.reduce(