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

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 (176) 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-BMajAl1u.mjs} +3 -3
  6. package/dist/_chunks/{ComponentConfigurationPage--2aLCv-G.mjs.map → ComponentConfigurationPage-BMajAl1u.mjs.map} +1 -1
  7. package/dist/_chunks/{ComponentConfigurationPage-43KmCNQE.js → ComponentConfigurationPage-y_7iLdmB.js} +3 -3
  8. package/dist/_chunks/{ComponentConfigurationPage-43KmCNQE.js.map → ComponentConfigurationPage-y_7iLdmB.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-CPVB8Uqc.js} +3 -3
  14. package/dist/_chunks/{EditConfigurationPage-BfFzJ4Br.js.map → EditConfigurationPage-CPVB8Uqc.js.map} +1 -1
  15. package/dist/_chunks/{EditConfigurationPage-CUcGHHvQ.mjs → EditConfigurationPage-CcOoD26O.mjs} +3 -3
  16. package/dist/_chunks/{EditConfigurationPage-CUcGHHvQ.mjs.map → EditConfigurationPage-CcOoD26O.mjs.map} +1 -1
  17. package/dist/_chunks/{EditViewPage-CzOT5Kpj.js → EditViewPage-CTTDHKkQ.js} +7 -9
  18. package/dist/_chunks/EditViewPage-CTTDHKkQ.js.map +1 -0
  19. package/dist/_chunks/{EditViewPage-Bm8lgcm6.mjs → EditViewPage-DWb0DE7R.mjs} +6 -6
  20. package/dist/_chunks/EditViewPage-DWb0DE7R.mjs.map +1 -0
  21. package/dist/_chunks/{Field-Caef4JjM.js → Field-C5Z1Ivdv.js} +552 -661
  22. package/dist/_chunks/Field-C5Z1Ivdv.js.map +1 -0
  23. package/dist/_chunks/{Field-Dlh0uGnL.mjs → Field-DnStdvQw.mjs} +500 -608
  24. package/dist/_chunks/Field-DnStdvQw.mjs.map +1 -0
  25. package/dist/_chunks/{Form-BzuAjtRq.js → Form-B81OtW-k.js} +21 -19
  26. package/dist/_chunks/Form-B81OtW-k.js.map +1 -0
  27. package/dist/_chunks/{Form-EnaQL_6L.mjs → Form-DqGgE55Q.mjs} +21 -18
  28. package/dist/_chunks/Form-DqGgE55Q.mjs.map +1 -0
  29. package/dist/_chunks/{History-C17LiyRg.js → History-4NbOq2dX.js} +119 -49
  30. package/dist/_chunks/History-4NbOq2dX.js.map +1 -0
  31. package/dist/_chunks/{History-D6sbCJvo.mjs → History-DS6-HCYX.mjs} +119 -48
  32. package/dist/_chunks/History-DS6-HCYX.mjs.map +1 -0
  33. package/dist/_chunks/{ListConfigurationPage-Dks5SX6f.js → ListConfigurationPage-CpfstlYY.js} +17 -19
  34. package/dist/_chunks/ListConfigurationPage-CpfstlYY.js.map +1 -0
  35. package/dist/_chunks/{ListConfigurationPage-Ce4qs7qE.mjs → ListConfigurationPage-DQJJltko.mjs} +14 -14
  36. package/dist/_chunks/ListConfigurationPage-DQJJltko.mjs.map +1 -0
  37. package/dist/_chunks/{ListViewPage-BwrZrPsh.js → ListViewPage-CA3I75m5.js} +43 -51
  38. package/dist/_chunks/ListViewPage-CA3I75m5.js.map +1 -0
  39. package/dist/_chunks/{ListViewPage-Be7S5aKL.mjs → ListViewPage-nQrOQuVo.mjs} +37 -45
  40. package/dist/_chunks/ListViewPage-nQrOQuVo.mjs.map +1 -0
  41. package/dist/_chunks/{NoContentTypePage-CIPmYQMm.mjs → NoContentTypePage-DbnHE22g.mjs} +7 -7
  42. package/dist/_chunks/NoContentTypePage-DbnHE22g.mjs.map +1 -0
  43. package/dist/_chunks/{NoContentTypePage-Cu5r1-JT.js → NoContentTypePage-Dldu-_Mx.js} +5 -5
  44. package/dist/_chunks/NoContentTypePage-Dldu-_Mx.js.map +1 -0
  45. package/dist/_chunks/{NoPermissionsPage-C-j6TEUF.js → NoPermissionsPage-CO2MK200.js} +4 -5
  46. package/dist/_chunks/NoPermissionsPage-CO2MK200.js.map +1 -0
  47. package/dist/_chunks/{NoPermissionsPage-DhJ7LYrr.mjs → NoPermissionsPage-fOIkQM0v.mjs} +5 -6
  48. package/dist/_chunks/NoPermissionsPage-fOIkQM0v.mjs.map +1 -0
  49. package/dist/_chunks/{Relations-CY7AtkDA.mjs → Relations-BDRl99Ux.mjs} +66 -56
  50. package/dist/_chunks/Relations-BDRl99Ux.mjs.map +1 -0
  51. package/dist/_chunks/{Relations-Czs-uZ-s.js → Relations-DG2jnOcr.js} +70 -61
  52. package/dist/_chunks/Relations-DG2jnOcr.js.map +1 -0
  53. package/dist/_chunks/{en-MBPul9Su.mjs → en-Ux26r5pl.mjs} +7 -1
  54. package/dist/_chunks/{en-MBPul9Su.mjs.map → en-Ux26r5pl.mjs.map} +1 -1
  55. package/dist/_chunks/{en-C-V1_90f.js → en-fbKQxLGn.js} +7 -1
  56. package/dist/_chunks/{en-C-V1_90f.js.map → en-fbKQxLGn.js.map} +1 -1
  57. package/dist/_chunks/{index-X_2tafck.js → index-BZoNZMXL.js} +1556 -795
  58. package/dist/_chunks/index-BZoNZMXL.js.map +1 -0
  59. package/dist/_chunks/{index-DNVx8ssZ.mjs → index-Drt2DN7v.mjs} +1569 -807
  60. package/dist/_chunks/index-Drt2DN7v.mjs.map +1 -0
  61. package/dist/_chunks/{layout-Dnh0PNp9.mjs → layout-BzAbmoO6.mjs} +27 -22
  62. package/dist/_chunks/layout-BzAbmoO6.mjs.map +1 -0
  63. package/dist/_chunks/{layout-dBc7wN7L.js → layout-DEYBqgF1.js} +28 -25
  64. package/dist/_chunks/layout-DEYBqgF1.js.map +1 -0
  65. package/dist/_chunks/{relations-4pHtBrHJ.js → relations-D0eZ4VWw.js} +2 -2
  66. package/dist/_chunks/{relations-4pHtBrHJ.js.map → relations-D0eZ4VWw.js.map} +1 -1
  67. package/dist/_chunks/{relations-Dx7tMKJN.mjs → relations-D26zVRdi.mjs} +2 -2
  68. package/dist/_chunks/{relations-Dx7tMKJN.mjs.map → relations-D26zVRdi.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 +8 -7
  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/components/VersionInputRenderer.d.ts +1 -1
  82. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  83. package/dist/admin/src/hooks/useDocument.d.ts +5 -8
  84. package/dist/admin/src/hooks/useDocumentActions.d.ts +24 -3
  85. package/dist/admin/src/hooks/useDocumentLayout.d.ts +2 -2
  86. package/dist/admin/src/hooks/useDragAndDrop.d.ts +4 -4
  87. package/dist/admin/src/hooks/useKeyboardDragAndDrop.d.ts +1 -1
  88. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +3 -1
  89. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksInput.d.ts +3 -3
  90. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Input.d.ts +2 -2
  91. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.d.ts +3 -5
  92. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/Field.d.ts +1 -1
  93. package/dist/admin/src/pages/EditView/components/FormInputs/Relations.d.ts +10 -18
  94. package/dist/admin/src/pages/EditView/components/FormInputs/UID.d.ts +2 -2
  95. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +3 -49
  96. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/Field.d.ts +2 -2
  97. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +67 -52
  98. package/dist/admin/src/pages/EditView/components/InputRenderer.d.ts +2 -10
  99. package/dist/admin/src/pages/ListView/components/BulkActions/PublishAction.d.ts +9 -26
  100. package/dist/admin/src/services/api.d.ts +2 -3
  101. package/dist/admin/src/services/components.d.ts +2 -2
  102. package/dist/admin/src/services/contentTypes.d.ts +5 -5
  103. package/dist/admin/src/services/documents.d.ts +29 -17
  104. package/dist/admin/src/services/init.d.ts +2 -2
  105. package/dist/admin/src/services/relations.d.ts +3 -3
  106. package/dist/admin/src/services/uid.d.ts +3 -3
  107. package/dist/admin/src/utils/api.d.ts +4 -18
  108. package/dist/admin/src/utils/validation.d.ts +1 -6
  109. package/dist/server/index.js +540 -414
  110. package/dist/server/index.js.map +1 -1
  111. package/dist/server/index.mjs +548 -422
  112. package/dist/server/index.mjs.map +1 -1
  113. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  114. package/dist/server/src/controllers/single-types.d.ts.map +1 -1
  115. package/dist/server/src/controllers/utils/metadata.d.ts +8 -0
  116. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -0
  117. package/dist/server/src/controllers/validation/dimensions.d.ts +9 -0
  118. package/dist/server/src/controllers/validation/dimensions.d.ts.map +1 -0
  119. package/dist/server/src/controllers/validation/index.d.ts +1 -1
  120. package/dist/server/src/history/services/history.d.ts +2 -4
  121. package/dist/server/src/history/services/history.d.ts.map +1 -1
  122. package/dist/server/src/history/services/index.d.ts +6 -2
  123. package/dist/server/src/history/services/index.d.ts.map +1 -1
  124. package/dist/server/src/history/services/lifecycles.d.ts +9 -0
  125. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -0
  126. package/dist/server/src/history/services/utils.d.ts +41 -9
  127. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  128. package/dist/server/src/history/utils.d.ts +6 -2
  129. package/dist/server/src/history/utils.d.ts.map +1 -1
  130. package/dist/server/src/index.d.ts +18 -39
  131. package/dist/server/src/index.d.ts.map +1 -1
  132. package/dist/server/src/services/document-manager.d.ts +13 -12
  133. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  134. package/dist/server/src/services/document-metadata.d.ts +8 -29
  135. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  136. package/dist/server/src/services/index.d.ts +18 -39
  137. package/dist/server/src/services/index.d.ts.map +1 -1
  138. package/dist/server/src/services/utils/populate.d.ts +8 -1
  139. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  140. package/dist/shared/contracts/collection-types.d.ts +14 -6
  141. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  142. package/dist/shared/contracts/relations.d.ts +2 -2
  143. package/dist/shared/contracts/relations.d.ts.map +1 -1
  144. package/package.json +13 -14
  145. package/dist/_chunks/CardDragPreview-DSVYodBX.js.map +0 -1
  146. package/dist/_chunks/CardDragPreview-ikSG4M46.mjs.map +0 -1
  147. package/dist/_chunks/ComponentIcon-BBQsYCVn.js.map +0 -1
  148. package/dist/_chunks/ComponentIcon-BOFnK76n.mjs.map +0 -1
  149. package/dist/_chunks/EditViewPage-Bm8lgcm6.mjs.map +0 -1
  150. package/dist/_chunks/EditViewPage-CzOT5Kpj.js.map +0 -1
  151. package/dist/_chunks/Field-Caef4JjM.js.map +0 -1
  152. package/dist/_chunks/Field-Dlh0uGnL.mjs.map +0 -1
  153. package/dist/_chunks/Form-BzuAjtRq.js.map +0 -1
  154. package/dist/_chunks/Form-EnaQL_6L.mjs.map +0 -1
  155. package/dist/_chunks/History-C17LiyRg.js.map +0 -1
  156. package/dist/_chunks/History-D6sbCJvo.mjs.map +0 -1
  157. package/dist/_chunks/ListConfigurationPage-Ce4qs7qE.mjs.map +0 -1
  158. package/dist/_chunks/ListConfigurationPage-Dks5SX6f.js.map +0 -1
  159. package/dist/_chunks/ListViewPage-Be7S5aKL.mjs.map +0 -1
  160. package/dist/_chunks/ListViewPage-BwrZrPsh.js.map +0 -1
  161. package/dist/_chunks/NoContentTypePage-CIPmYQMm.mjs.map +0 -1
  162. package/dist/_chunks/NoContentTypePage-Cu5r1-JT.js.map +0 -1
  163. package/dist/_chunks/NoPermissionsPage-C-j6TEUF.js.map +0 -1
  164. package/dist/_chunks/NoPermissionsPage-DhJ7LYrr.mjs.map +0 -1
  165. package/dist/_chunks/Relations-CY7AtkDA.mjs.map +0 -1
  166. package/dist/_chunks/Relations-Czs-uZ-s.js.map +0 -1
  167. package/dist/_chunks/index-DNVx8ssZ.mjs.map +0 -1
  168. package/dist/_chunks/index-X_2tafck.js.map +0 -1
  169. package/dist/_chunks/layout-Dnh0PNp9.mjs.map +0 -1
  170. package/dist/_chunks/layout-dBc7wN7L.js.map +0 -1
  171. package/dist/_chunks/urls-CbOsUOoW.mjs +0 -7
  172. package/dist/_chunks/urls-CbOsUOoW.mjs.map +0 -1
  173. package/dist/_chunks/urls-DzZya_gm.js +0 -6
  174. package/dist/_chunks/urls-DzZya_gm.js.map +0 -1
  175. package/dist/server/src/controllers/utils/dimensions.d.ts +0 -5
  176. 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,101 @@ 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 !== "clone" && 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 = {
519
+ documentId: context.action === "create" || context.action === "clone" ? result.documentId : context.params.documentId,
520
+ locale: context.params?.locale
521
+ };
522
+ const defaultLocale = await serviceUtils.getDefaultLocale();
523
+ const locale = documentContext.locale || defaultLocale;
524
+ if (Array.isArray(locale)) {
525
+ strapi2.log.warn(
526
+ "[Content manager history middleware]: An array of locales was provided, but only a single locale is supported for the findOne operation."
527
+ );
528
+ return next();
529
+ }
530
+ const document = await strapi2.documents(contentTypeUid).findOne({
531
+ documentId: documentContext.documentId,
532
+ locale,
533
+ populate: serviceUtils.getDeepPopulate(contentTypeUid)
534
+ });
535
+ const status = await serviceUtils.getVersionStatus(contentTypeUid, document);
536
+ const attributesSchema = strapi2.getModel(contentTypeUid).attributes;
537
+ const componentsSchemas = Object.keys(
538
+ attributesSchema
539
+ ).reduce((currentComponentSchemas, key) => {
540
+ const fieldSchema = attributesSchema[key];
541
+ if (fieldSchema.type === "component") {
542
+ const componentSchema = strapi2.getModel(fieldSchema.component).attributes;
543
+ return {
544
+ ...currentComponentSchemas,
545
+ [fieldSchema.component]: componentSchema
546
+ };
547
+ }
548
+ return currentComponentSchemas;
549
+ }, {});
550
+ await strapi2.db.transaction(async ({ onCommit }) => {
551
+ onCommit(() => {
552
+ historyService.createVersion({
553
+ contentType: contentTypeUid,
554
+ data: fp.omit(FIELDS_TO_IGNORE, document),
555
+ schema: fp.omit(FIELDS_TO_IGNORE, attributesSchema),
556
+ componentsSchemas,
557
+ relatedDocumentId: documentContext.documentId,
558
+ locale,
559
+ status
560
+ });
561
+ });
562
+ });
563
+ return result;
564
+ });
565
+ state.deleteExpiredJob = nodeSchedule.scheduleJob("0 0 * * *", () => {
566
+ const retentionDaysInMilliseconds = serviceUtils.getRetentionDays() * 24 * 60 * 60 * 1e3;
567
+ const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
568
+ query.deleteMany({
569
+ where: {
570
+ created_at: {
571
+ $lt: expirationDate.toISOString()
572
+ }
573
+ }
574
+ });
575
+ });
576
+ state.isInitialized = true;
577
+ },
578
+ async destroy() {
579
+ if (state.deleteExpiredJob) {
580
+ state.deleteExpiredJob.cancel();
581
+ }
582
+ }
583
+ };
584
+ };
533
585
  const services$1 = {
534
- history: createHistoryService
586
+ history: createHistoryService,
587
+ lifecycles: createLifecyclesService
535
588
  };
536
589
  const info = { pluginName: "content-manager", type: "admin" };
537
590
  const historyVersionRouter = {
@@ -611,10 +664,10 @@ const getFeature = () => {
611
664
  strapi2.get("models").add(historyVersion);
612
665
  },
613
666
  bootstrap({ strapi: strapi2 }) {
614
- getService(strapi2, "history").bootstrap();
667
+ getService(strapi2, "lifecycles").bootstrap();
615
668
  },
616
669
  destroy({ strapi: strapi2 }) {
617
- getService(strapi2, "history").destroy();
670
+ getService(strapi2, "lifecycles").destroy();
618
671
  },
619
672
  controllers: controllers$1,
620
673
  services: services$1,
@@ -1433,7 +1486,7 @@ const { PaginationError, ValidationError } = strapiUtils.errors;
1433
1486
  const TYPES = ["singleType", "collectionType"];
1434
1487
  const kindSchema = strapiUtils.yup.string().oneOf(TYPES).nullable();
1435
1488
  const bulkActionInputSchema = strapiUtils.yup.object({
1436
- ids: strapiUtils.yup.array().of(strapiUtils.yup.strapiID()).min(1).required()
1489
+ documentIds: strapiUtils.yup.array().of(strapiUtils.yup.strapiID()).min(1).required()
1437
1490
  }).required();
1438
1491
  const generateUIDInputSchema = strapiUtils.yup.object({
1439
1492
  contentTypeUID: strapiUtils.yup.string().required(),
@@ -1532,15 +1585,47 @@ const excludeNotCreatableFields = (uid2, permissionChecker2) => (body, path = []
1532
1585
  }
1533
1586
  }, body);
1534
1587
  };
1535
- const getDocumentLocaleAndStatus = (request) => {
1588
+ const singleLocaleSchema = strapiUtils.yup.string().nullable();
1589
+ const multipleLocaleSchema = strapiUtils.yup.lazy(
1590
+ (value) => Array.isArray(value) ? strapiUtils.yup.array().of(singleLocaleSchema.required()) : singleLocaleSchema
1591
+ );
1592
+ const statusSchema = strapiUtils.yup.mixed().oneOf(["draft", "published"], "Invalid status");
1593
+ const getDocumentLocaleAndStatus = async (request, opts = { allowMultipleLocales: false }) => {
1594
+ const { allowMultipleLocales } = opts;
1536
1595
  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}`);
1596
+ const schema = strapiUtils.yup.object().shape({
1597
+ locale: allowMultipleLocales ? multipleLocaleSchema : singleLocaleSchema,
1598
+ status: statusSchema
1599
+ });
1600
+ try {
1601
+ await strapiUtils.validateYupSchema(schema, { strict: true, abortEarly: false })(request);
1602
+ return { locale, status, ...rest };
1603
+ } catch (error) {
1604
+ throw new strapiUtils.errors.ValidationError(`Validation error: ${error.message}`);
1542
1605
  }
1543
- return { locale, status, ...rest };
1606
+ };
1607
+ const formatDocumentWithMetadata = async (permissionChecker2, uid2, document, opts = {}) => {
1608
+ const documentMetadata2 = getService$1("document-metadata");
1609
+ const serviceOutput = await documentMetadata2.formatDocumentWithMetadata(uid2, document, opts);
1610
+ let {
1611
+ meta: { availableLocales, availableStatus }
1612
+ } = serviceOutput;
1613
+ const metadataSanitizer = permissionChecker2.sanitizeOutput;
1614
+ availableLocales = await strapiUtils.async.map(
1615
+ availableLocales,
1616
+ async (localeDocument) => metadataSanitizer(localeDocument)
1617
+ );
1618
+ availableStatus = await strapiUtils.async.map(
1619
+ availableStatus,
1620
+ async (statusDocument) => metadataSanitizer(statusDocument)
1621
+ );
1622
+ return {
1623
+ ...serviceOutput,
1624
+ meta: {
1625
+ availableLocales,
1626
+ availableStatus
1627
+ }
1628
+ };
1544
1629
  };
1545
1630
  const createDocument = async (ctx, opts) => {
1546
1631
  const { userAbility, user } = ctx.state;
@@ -1555,7 +1640,7 @@ const createDocument = async (ctx, opts) => {
1555
1640
  const setCreator = strapiUtils.setCreatorFields({ user });
1556
1641
  const sanitizeFn = strapiUtils.async.pipe(pickPermittedFields, setCreator);
1557
1642
  const sanitizedBody = await sanitizeFn(body);
1558
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(body);
1643
+ const { locale, status = "draft" } = await getDocumentLocaleAndStatus(body);
1559
1644
  return documentManager2.create(model, {
1560
1645
  data: sanitizedBody,
1561
1646
  locale,
@@ -1574,7 +1659,7 @@ const updateDocument = async (ctx, opts) => {
1574
1659
  }
1575
1660
  const permissionQuery = await permissionChecker2.sanitizedQuery.update(ctx.query);
1576
1661
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1577
- const { locale } = getDocumentLocaleAndStatus(body);
1662
+ const { locale } = await getDocumentLocaleAndStatus(body);
1578
1663
  const [documentVersion, documentExists] = await Promise.all([
1579
1664
  documentManager2.findOne(id, model, { populate, locale, status: "draft" }),
1580
1665
  documentManager2.exists(model, id)
@@ -1612,7 +1697,7 @@ const collectionTypes = {
1612
1697
  }
1613
1698
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
1614
1699
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(1).countRelations({ toOne: false, toMany: true }).build();
1615
- const { locale, status } = getDocumentLocaleAndStatus(query);
1700
+ const { locale, status } = await getDocumentLocaleAndStatus(query);
1616
1701
  const { results: documents, pagination } = await documentManager2.findPage(
1617
1702
  { ...permissionQuery, populate, locale, status },
1618
1703
  model
@@ -1641,14 +1726,13 @@ const collectionTypes = {
1641
1726
  const { userAbility } = ctx.state;
1642
1727
  const { model, id } = ctx.params;
1643
1728
  const documentManager2 = getService$1("document-manager");
1644
- const documentMetadata2 = getService$1("document-metadata");
1645
1729
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1646
1730
  if (permissionChecker2.cannot.read()) {
1647
1731
  return ctx.forbidden();
1648
1732
  }
1649
1733
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1650
1734
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1651
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
1735
+ const { locale, status = "draft" } = await getDocumentLocaleAndStatus(ctx.query);
1652
1736
  const version = await documentManager2.findOne(id, model, {
1653
1737
  populate,
1654
1738
  locale,
@@ -1659,8 +1743,10 @@ const collectionTypes = {
1659
1743
  if (!exists) {
1660
1744
  return ctx.notFound();
1661
1745
  }
1662
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
1746
+ const { meta } = await formatDocumentWithMetadata(
1747
+ permissionChecker2,
1663
1748
  model,
1749
+ // @ts-expect-error TODO: fix
1664
1750
  { id, locale, publishedAt: null },
1665
1751
  { availableLocales: true, availableStatus: false }
1666
1752
  );
@@ -1671,12 +1757,11 @@ const collectionTypes = {
1671
1757
  return ctx.forbidden();
1672
1758
  }
1673
1759
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
1674
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1760
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1675
1761
  },
1676
1762
  async create(ctx) {
1677
1763
  const { userAbility } = ctx.state;
1678
1764
  const { model } = ctx.params;
1679
- const documentMetadata2 = getService$1("document-metadata");
1680
1765
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1681
1766
  const [totalEntries, document] = await Promise.all([
1682
1767
  strapi.db.query(model).count(),
@@ -1684,7 +1769,7 @@ const collectionTypes = {
1684
1769
  ]);
1685
1770
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
1686
1771
  ctx.status = 201;
1687
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1772
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1688
1773
  // Empty metadata as it's not relevant for a new document
1689
1774
  availableLocales: false,
1690
1775
  availableStatus: false
@@ -1698,25 +1783,23 @@ const collectionTypes = {
1698
1783
  async update(ctx) {
1699
1784
  const { userAbility } = ctx.state;
1700
1785
  const { model } = ctx.params;
1701
- const documentMetadata2 = getService$1("document-metadata");
1702
1786
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1703
1787
  const updatedVersion = await updateDocument(ctx);
1704
1788
  const sanitizedVersion = await permissionChecker2.sanitizeOutput(updatedVersion);
1705
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedVersion);
1789
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedVersion);
1706
1790
  },
1707
1791
  async clone(ctx) {
1708
1792
  const { userAbility, user } = ctx.state;
1709
1793
  const { model, sourceId: id } = ctx.params;
1710
1794
  const { body } = ctx.request;
1711
1795
  const documentManager2 = getService$1("document-manager");
1712
- const documentMetadata2 = getService$1("document-metadata");
1713
1796
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1714
1797
  if (permissionChecker2.cannot.create()) {
1715
1798
  return ctx.forbidden();
1716
1799
  }
1717
1800
  const permissionQuery = await permissionChecker2.sanitizedQuery.create(ctx.query);
1718
1801
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1719
- const { locale } = getDocumentLocaleAndStatus(body);
1802
+ const { locale } = await getDocumentLocaleAndStatus(body);
1720
1803
  const document = await documentManager2.findOne(id, model, {
1721
1804
  populate,
1722
1805
  locale,
@@ -1732,7 +1815,7 @@ const collectionTypes = {
1732
1815
  const sanitizedBody = await sanitizeFn(body);
1733
1816
  const clonedDocument = await documentManager2.clone(document.documentId, sanitizedBody, model);
1734
1817
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(clonedDocument);
1735
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1818
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1736
1819
  // Empty metadata as it's not relevant for a new document
1737
1820
  availableLocales: false,
1738
1821
  availableStatus: false
@@ -1761,7 +1844,7 @@ const collectionTypes = {
1761
1844
  }
1762
1845
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(ctx.query);
1763
1846
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1764
- const { locale } = getDocumentLocaleAndStatus(ctx.query);
1847
+ const { locale } = await getDocumentLocaleAndStatus(ctx.query);
1765
1848
  const documentLocales = await documentManager2.findLocales(id, model, { populate, locale });
1766
1849
  if (documentLocales.length === 0) {
1767
1850
  return ctx.notFound();
@@ -1783,7 +1866,6 @@ const collectionTypes = {
1783
1866
  const { id, model } = ctx.params;
1784
1867
  const { body } = ctx.request;
1785
1868
  const documentManager2 = getService$1("document-manager");
1786
- const documentMetadata2 = getService$1("document-metadata");
1787
1869
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1788
1870
  if (permissionChecker2.cannot.publish()) {
1789
1871
  return ctx.forbidden();
@@ -1795,21 +1877,25 @@ const collectionTypes = {
1795
1877
  if (permissionChecker2.cannot.publish(document)) {
1796
1878
  throw new strapiUtils.errors.ForbiddenError();
1797
1879
  }
1798
- const { locale } = getDocumentLocaleAndStatus(body);
1799
- return documentManager2.publish(document.documentId, model, {
1880
+ const { locale } = await getDocumentLocaleAndStatus(body);
1881
+ const publishResult = await documentManager2.publish(document.documentId, model, {
1800
1882
  locale
1801
1883
  // TODO: Allow setting creator fields on publish
1802
1884
  // data: setCreatorFields({ user, isEdition: true })({}),
1803
1885
  });
1886
+ if (!publishResult || publishResult.length === 0) {
1887
+ throw new strapiUtils.errors.NotFoundError("Document not found or already published.");
1888
+ }
1889
+ return publishResult[0];
1804
1890
  });
1805
1891
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
1806
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1892
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1807
1893
  },
1808
1894
  async bulkPublish(ctx) {
1809
1895
  const { userAbility } = ctx.state;
1810
1896
  const { model } = ctx.params;
1811
1897
  const { body } = ctx.request;
1812
- const { ids } = body;
1898
+ const { documentIds } = body;
1813
1899
  await validateBulkActionInput(body);
1814
1900
  const documentManager2 = getService$1("document-manager");
1815
1901
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1818,8 +1904,11 @@ const collectionTypes = {
1818
1904
  }
1819
1905
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1820
1906
  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);
1907
+ const { locale } = await getDocumentLocaleAndStatus(body, { allowMultipleLocales: true });
1908
+ const entityPromises = documentIds.map(
1909
+ (documentId) => documentManager2.findLocales(documentId, model, { populate, locale, isPublished: false })
1910
+ );
1911
+ const entities = (await Promise.all(entityPromises)).flat();
1823
1912
  for (const entity of entities) {
1824
1913
  if (!entity) {
1825
1914
  return ctx.notFound();
@@ -1828,24 +1917,25 @@ const collectionTypes = {
1828
1917
  return ctx.forbidden();
1829
1918
  }
1830
1919
  }
1831
- const { count } = await documentManager2.publishMany(entities, model);
1920
+ const count = await documentManager2.publishMany(model, documentIds, locale);
1832
1921
  ctx.body = { count };
1833
1922
  },
1834
1923
  async bulkUnpublish(ctx) {
1835
1924
  const { userAbility } = ctx.state;
1836
1925
  const { model } = ctx.params;
1837
1926
  const { body } = ctx.request;
1838
- const { ids } = body;
1927
+ const { documentIds } = body;
1839
1928
  await validateBulkActionInput(body);
1840
1929
  const documentManager2 = getService$1("document-manager");
1841
1930
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1842
1931
  if (permissionChecker2.cannot.unpublish()) {
1843
1932
  return ctx.forbidden();
1844
1933
  }
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);
1934
+ const { locale } = await getDocumentLocaleAndStatus(body);
1935
+ const entityPromises = documentIds.map(
1936
+ (documentId) => documentManager2.findLocales(documentId, model, { locale, isPublished: true })
1937
+ );
1938
+ const entities = (await Promise.all(entityPromises)).flat();
1849
1939
  for (const entity of entities) {
1850
1940
  if (!entity) {
1851
1941
  return ctx.notFound();
@@ -1854,7 +1944,8 @@ const collectionTypes = {
1854
1944
  return ctx.forbidden();
1855
1945
  }
1856
1946
  }
1857
- const { count } = await documentManager2.unpublishMany(entities, model);
1947
+ const entitiesIds = entities.map((document) => document.documentId);
1948
+ const { count } = await documentManager2.unpublishMany(entitiesIds, model, { locale });
1858
1949
  ctx.body = { count };
1859
1950
  },
1860
1951
  async unpublish(ctx) {
@@ -1864,7 +1955,6 @@ const collectionTypes = {
1864
1955
  body: { discardDraft, ...body }
1865
1956
  } = ctx.request;
1866
1957
  const documentManager2 = getService$1("document-manager");
1867
- const documentMetadata2 = getService$1("document-metadata");
1868
1958
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1869
1959
  if (permissionChecker2.cannot.unpublish()) {
1870
1960
  return ctx.forbidden();
@@ -1874,7 +1964,7 @@ const collectionTypes = {
1874
1964
  }
1875
1965
  const permissionQuery = await permissionChecker2.sanitizedQuery.unpublish(ctx.query);
1876
1966
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1877
- const { locale } = getDocumentLocaleAndStatus(body);
1967
+ const { locale } = await getDocumentLocaleAndStatus(body);
1878
1968
  const document = await documentManager2.findOne(id, model, {
1879
1969
  populate,
1880
1970
  locale,
@@ -1896,7 +1986,7 @@ const collectionTypes = {
1896
1986
  ctx.body = await strapiUtils.async.pipe(
1897
1987
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
1898
1988
  permissionChecker2.sanitizeOutput,
1899
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
1989
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1900
1990
  )(document);
1901
1991
  });
1902
1992
  },
@@ -1905,14 +1995,13 @@ const collectionTypes = {
1905
1995
  const { id, model } = ctx.params;
1906
1996
  const { body } = ctx.request;
1907
1997
  const documentManager2 = getService$1("document-manager");
1908
- const documentMetadata2 = getService$1("document-metadata");
1909
1998
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1910
1999
  if (permissionChecker2.cannot.discard()) {
1911
2000
  return ctx.forbidden();
1912
2001
  }
1913
2002
  const permissionQuery = await permissionChecker2.sanitizedQuery.discard(ctx.query);
1914
2003
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1915
- const { locale } = getDocumentLocaleAndStatus(body);
2004
+ const { locale } = await getDocumentLocaleAndStatus(body);
1916
2005
  const document = await documentManager2.findOne(id, model, {
1917
2006
  populate,
1918
2007
  locale,
@@ -1927,14 +2016,14 @@ const collectionTypes = {
1927
2016
  ctx.body = await strapiUtils.async.pipe(
1928
2017
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
1929
2018
  permissionChecker2.sanitizeOutput,
1930
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2019
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1931
2020
  )(document);
1932
2021
  },
1933
2022
  async bulkDelete(ctx) {
1934
2023
  const { userAbility } = ctx.state;
1935
2024
  const { model } = ctx.params;
1936
2025
  const { query, body } = ctx.request;
1937
- const { ids } = body;
2026
+ const { documentIds } = body;
1938
2027
  await validateBulkActionInput(body);
1939
2028
  const documentManager2 = getService$1("document-manager");
1940
2029
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1942,14 +2031,22 @@ const collectionTypes = {
1942
2031
  return ctx.forbidden();
1943
2032
  }
1944
2033
  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 || [])
2034
+ const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2035
+ const { locale } = await getDocumentLocaleAndStatus(body);
2036
+ const documentLocales = await documentManager2.findLocales(documentIds, model, {
2037
+ populate,
2038
+ locale
2039
+ });
2040
+ if (documentLocales.length === 0) {
2041
+ return ctx.notFound();
2042
+ }
2043
+ for (const document of documentLocales) {
2044
+ if (permissionChecker2.cannot.delete(document)) {
2045
+ return ctx.forbidden();
1950
2046
  }
1951
- };
1952
- const { count } = await documentManager2.deleteMany(params, model);
2047
+ }
2048
+ const localeDocumentsIds = documentLocales.map((document) => document.documentId);
2049
+ const { count } = await documentManager2.deleteMany(localeDocumentsIds, model, { locale });
1953
2050
  ctx.body = { count };
1954
2051
  },
1955
2052
  async countDraftRelations(ctx) {
@@ -1962,7 +2059,7 @@ const collectionTypes = {
1962
2059
  }
1963
2060
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1964
2061
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1965
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
2062
+ const { locale, status = "draft" } = await getDocumentLocaleAndStatus(ctx.query);
1966
2063
  const entity = await documentManager2.findOne(id, model, { populate, locale, status });
1967
2064
  if (!entity) {
1968
2065
  return ctx.notFound();
@@ -1977,7 +2074,7 @@ const collectionTypes = {
1977
2074
  },
1978
2075
  async countManyEntriesDraftRelations(ctx) {
1979
2076
  const { userAbility } = ctx.state;
1980
- const ids = ctx.request.query.ids;
2077
+ const ids = ctx.request.query.documentIds;
1981
2078
  const locale = ctx.request.query.locale;
1982
2079
  const { model } = ctx.params;
1983
2080
  const documentManager2 = getService$1("document-manager");
@@ -1988,7 +2085,7 @@ const collectionTypes = {
1988
2085
  const entities = await documentManager2.findMany(
1989
2086
  {
1990
2087
  filters: {
1991
- id: ids
2088
+ documentId: ids
1992
2089
  },
1993
2090
  locale
1994
2091
  },
@@ -2490,7 +2587,7 @@ const createOrUpdateDocument = async (ctx, opts) => {
2490
2587
  throw new strapiUtils.errors.ForbiddenError();
2491
2588
  }
2492
2589
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.update(query);
2493
- const { locale } = getDocumentLocaleAndStatus(body);
2590
+ const { locale } = await getDocumentLocaleAndStatus(body);
2494
2591
  const [documentVersion, otherDocumentVersion] = await Promise.all([
2495
2592
  findDocument(sanitizedQuery, model, { locale, status: "draft" }),
2496
2593
  // Find the first document to check if it exists
@@ -2527,12 +2624,11 @@ const singleTypes = {
2527
2624
  const { model } = ctx.params;
2528
2625
  const { query = {} } = ctx.request;
2529
2626
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2530
- const documentMetadata2 = getService$1("document-metadata");
2531
2627
  if (permissionChecker2.cannot.read()) {
2532
2628
  return ctx.forbidden();
2533
2629
  }
2534
2630
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
2535
- const { locale, status } = getDocumentLocaleAndStatus(query);
2631
+ const { locale, status } = await getDocumentLocaleAndStatus(query);
2536
2632
  const version = await findDocument(permissionQuery, model, { locale, status });
2537
2633
  if (!version) {
2538
2634
  if (permissionChecker2.cannot.create()) {
@@ -2542,8 +2638,10 @@ const singleTypes = {
2542
2638
  if (!document) {
2543
2639
  return ctx.notFound();
2544
2640
  }
2545
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
2641
+ const { meta } = await formatDocumentWithMetadata(
2642
+ permissionChecker2,
2546
2643
  model,
2644
+ // @ts-expect-error - fix types
2547
2645
  { id: document.documentId, locale, publishedAt: null },
2548
2646
  { availableLocales: true, availableStatus: false }
2549
2647
  );
@@ -2554,16 +2652,15 @@ const singleTypes = {
2554
2652
  return ctx.forbidden();
2555
2653
  }
2556
2654
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
2557
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2655
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2558
2656
  },
2559
2657
  async createOrUpdate(ctx) {
2560
2658
  const { userAbility } = ctx.state;
2561
2659
  const { model } = ctx.params;
2562
- const documentMetadata2 = getService$1("document-metadata");
2563
2660
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2564
2661
  const document = await createOrUpdateDocument(ctx);
2565
2662
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
2566
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2663
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2567
2664
  },
2568
2665
  async delete(ctx) {
2569
2666
  const { userAbility } = ctx.state;
@@ -2576,7 +2673,7 @@ const singleTypes = {
2576
2673
  }
2577
2674
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.delete(query);
2578
2675
  const populate = await buildPopulateFromQuery(sanitizedQuery, model);
2579
- const { locale } = getDocumentLocaleAndStatus(query);
2676
+ const { locale } = await getDocumentLocaleAndStatus(query);
2580
2677
  const documentLocales = await documentManager2.findLocales(void 0, model, {
2581
2678
  populate,
2582
2679
  locale
@@ -2599,7 +2696,6 @@ const singleTypes = {
2599
2696
  const { model } = ctx.params;
2600
2697
  const { query = {} } = ctx.request;
2601
2698
  const documentManager2 = getService$1("document-manager");
2602
- const documentMetadata2 = getService$1("document-metadata");
2603
2699
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2604
2700
  if (permissionChecker2.cannot.publish()) {
2605
2701
  return ctx.forbidden();
@@ -2614,11 +2710,12 @@ const singleTypes = {
2614
2710
  if (permissionChecker2.cannot.publish(document)) {
2615
2711
  throw new strapiUtils.errors.ForbiddenError();
2616
2712
  }
2617
- const { locale } = getDocumentLocaleAndStatus(document);
2618
- return documentManager2.publish(document.documentId, model, { locale });
2713
+ const { locale } = await getDocumentLocaleAndStatus(document);
2714
+ const publishResult = await documentManager2.publish(document.documentId, model, { locale });
2715
+ return publishResult.at(0);
2619
2716
  });
2620
2717
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
2621
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2718
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2622
2719
  },
2623
2720
  async unpublish(ctx) {
2624
2721
  const { userAbility } = ctx.state;
@@ -2628,7 +2725,6 @@ const singleTypes = {
2628
2725
  query = {}
2629
2726
  } = ctx.request;
2630
2727
  const documentManager2 = getService$1("document-manager");
2631
- const documentMetadata2 = getService$1("document-metadata");
2632
2728
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2633
2729
  if (permissionChecker2.cannot.unpublish()) {
2634
2730
  return ctx.forbidden();
@@ -2637,7 +2733,7 @@ const singleTypes = {
2637
2733
  return ctx.forbidden();
2638
2734
  }
2639
2735
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.unpublish(query);
2640
- const { locale } = getDocumentLocaleAndStatus(body);
2736
+ const { locale } = await getDocumentLocaleAndStatus(body);
2641
2737
  const document = await findDocument(sanitizedQuery, model, { locale });
2642
2738
  if (!document) {
2643
2739
  return ctx.notFound();
@@ -2655,7 +2751,7 @@ const singleTypes = {
2655
2751
  ctx.body = await strapiUtils.async.pipe(
2656
2752
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
2657
2753
  permissionChecker2.sanitizeOutput,
2658
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2754
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2659
2755
  )(document);
2660
2756
  });
2661
2757
  },
@@ -2664,13 +2760,12 @@ const singleTypes = {
2664
2760
  const { model } = ctx.params;
2665
2761
  const { body, query = {} } = ctx.request;
2666
2762
  const documentManager2 = getService$1("document-manager");
2667
- const documentMetadata2 = getService$1("document-metadata");
2668
2763
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2669
2764
  if (permissionChecker2.cannot.discard()) {
2670
2765
  return ctx.forbidden();
2671
2766
  }
2672
2767
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.discard(query);
2673
- const { locale } = getDocumentLocaleAndStatus(body);
2768
+ const { locale } = await getDocumentLocaleAndStatus(body);
2674
2769
  const document = await findDocument(sanitizedQuery, model, { locale, status: "published" });
2675
2770
  if (!document) {
2676
2771
  return ctx.notFound();
@@ -2681,7 +2776,7 @@ const singleTypes = {
2681
2776
  ctx.body = await strapiUtils.async.pipe(
2682
2777
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
2683
2778
  permissionChecker2.sanitizeOutput,
2684
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2779
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2685
2780
  )(document);
2686
2781
  },
2687
2782
  async countDraftRelations(ctx) {
@@ -2690,7 +2785,7 @@ const singleTypes = {
2690
2785
  const { query } = ctx.request;
2691
2786
  const documentManager2 = getService$1("document-manager");
2692
2787
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2693
- const { locale } = getDocumentLocaleAndStatus(query);
2788
+ const { locale } = await getDocumentLocaleAndStatus(query);
2694
2789
  if (permissionChecker2.cannot.read()) {
2695
2790
  return ctx.forbidden();
2696
2791
  }
@@ -2711,7 +2806,7 @@ const uid$1 = {
2711
2806
  async generateUID(ctx) {
2712
2807
  const { contentTypeUID, field, data } = await validateGenerateUIDInput(ctx.request.body);
2713
2808
  const { query = {} } = ctx.request;
2714
- const { locale } = getDocumentLocaleAndStatus(query);
2809
+ const { locale } = await getDocumentLocaleAndStatus(query);
2715
2810
  await validateUIDField(contentTypeUID, field);
2716
2811
  const uidService = getService$1("uid");
2717
2812
  ctx.body = {
@@ -2723,7 +2818,7 @@ const uid$1 = {
2723
2818
  ctx.request.body
2724
2819
  );
2725
2820
  const { query = {} } = ctx.request;
2726
- const { locale } = getDocumentLocaleAndStatus(query);
2821
+ const { locale } = await getDocumentLocaleAndStatus(query);
2727
2822
  await validateUIDField(contentTypeUID, field);
2728
2823
  const uidService = getService$1("uid");
2729
2824
  const isAvailable = await uidService.checkUIDAvailability({
@@ -3514,7 +3609,7 @@ const permission = ({ strapi: strapi2 }) => ({
3514
3609
  await strapi2.service("admin::permission").actionProvider.registerMany(actions);
3515
3610
  }
3516
3611
  });
3517
- const { isVisibleAttribute: isVisibleAttribute$1 } = strapiUtils__default.default.contentTypes;
3612
+ const { isVisibleAttribute: isVisibleAttribute$1, isScalarAttribute, getDoesAttributeRequireValidation } = strapiUtils__default.default.contentTypes;
3518
3613
  const { isAnyToMany } = strapiUtils__default.default.relations;
3519
3614
  const { PUBLISHED_AT_ATTRIBUTE: PUBLISHED_AT_ATTRIBUTE$1 } = strapiUtils__default.default.contentTypes.constants;
3520
3615
  const isMorphToRelation = (attribute) => isRelation(attribute) && attribute.relation.includes("morphTo");
@@ -3605,6 +3700,42 @@ const getDeepPopulate = (uid2, {
3605
3700
  {}
3606
3701
  );
3607
3702
  };
3703
+ const getValidatableFieldsPopulate = (uid2, {
3704
+ initialPopulate = {},
3705
+ countMany = false,
3706
+ countOne = false,
3707
+ maxLevel = Infinity
3708
+ } = {}, level = 1) => {
3709
+ if (level > maxLevel) {
3710
+ return {};
3711
+ }
3712
+ const model = strapi.getModel(uid2);
3713
+ return Object.entries(model.attributes).reduce((populateAcc, [attributeName, attribute]) => {
3714
+ if (!getDoesAttributeRequireValidation(attribute)) {
3715
+ return populateAcc;
3716
+ }
3717
+ if (isScalarAttribute(attribute)) {
3718
+ return fp.merge(populateAcc, {
3719
+ [attributeName]: true
3720
+ });
3721
+ }
3722
+ return fp.merge(
3723
+ populateAcc,
3724
+ getPopulateFor(
3725
+ attributeName,
3726
+ model,
3727
+ {
3728
+ // @ts-expect-error - improve types
3729
+ initialPopulate: initialPopulate?.[attributeName],
3730
+ countMany,
3731
+ countOne,
3732
+ maxLevel
3733
+ },
3734
+ level
3735
+ )
3736
+ );
3737
+ }, {});
3738
+ };
3608
3739
  const getDeepPopulateDraftCount = (uid2) => {
3609
3740
  const model = strapi.getModel(uid2);
3610
3741
  let hasRelations = false;
@@ -3626,22 +3757,24 @@ const getDeepPopulateDraftCount = (uid2) => {
3626
3757
  attribute.component
3627
3758
  );
3628
3759
  if (childHasRelations) {
3629
- populateAcc[attributeName] = { populate: populate2 };
3760
+ populateAcc[attributeName] = {
3761
+ populate: populate2
3762
+ };
3630
3763
  hasRelations = true;
3631
3764
  }
3632
3765
  break;
3633
3766
  }
3634
3767
  case "dynamiczone": {
3635
- const dzPopulate = (attribute.components || []).reduce((acc, componentUID) => {
3636
- const { populate: populate2, hasRelations: childHasRelations } = getDeepPopulateDraftCount(componentUID);
3637
- if (childHasRelations) {
3768
+ const dzPopulateFragment = attribute.components?.reduce((acc, componentUID) => {
3769
+ const { populate: componentPopulate, hasRelations: componentHasRelations } = getDeepPopulateDraftCount(componentUID);
3770
+ if (componentHasRelations) {
3638
3771
  hasRelations = true;
3639
- return fp.merge(acc, populate2);
3772
+ return { ...acc, [componentUID]: { populate: componentPopulate } };
3640
3773
  }
3641
3774
  return acc;
3642
3775
  }, {});
3643
- if (!fp.isEmpty(dzPopulate)) {
3644
- populateAcc[attributeName] = { populate: dzPopulate };
3776
+ if (!fp.isEmpty(dzPopulateFragment)) {
3777
+ populateAcc[attributeName] = { on: dzPopulateFragment };
3645
3778
  }
3646
3779
  break;
3647
3780
  }
@@ -3833,41 +3966,70 @@ const AVAILABLE_STATUS_FIELDS = [
3833
3966
  "updatedBy",
3834
3967
  "status"
3835
3968
  ];
3836
- const AVAILABLE_LOCALES_FIELDS = ["id", "locale", "updatedAt", "createdAt", "status"];
3969
+ const AVAILABLE_LOCALES_FIELDS = [
3970
+ "id",
3971
+ "locale",
3972
+ "updatedAt",
3973
+ "createdAt",
3974
+ "status",
3975
+ "publishedAt",
3976
+ "documentId"
3977
+ ];
3837
3978
  const CONTENT_MANAGER_STATUS = {
3838
3979
  PUBLISHED: "published",
3839
3980
  DRAFT: "draft",
3840
3981
  MODIFIED: "modified"
3841
3982
  };
3842
- const areDatesEqual = (date1, date2, threshold) => {
3843
- if (!date1 || !date2) {
3983
+ const getIsVersionLatestModification = (version, otherVersion) => {
3984
+ if (!version || !version.updatedAt) {
3844
3985
  return false;
3845
3986
  }
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;
3987
+ const versionUpdatedAt = version?.updatedAt ? new Date(version.updatedAt).getTime() : 0;
3988
+ const otherUpdatedAt = otherVersion?.updatedAt ? new Date(otherVersion.updatedAt).getTime() : 0;
3989
+ return versionUpdatedAt > otherUpdatedAt;
3850
3990
  };
3851
3991
  const documentMetadata = ({ strapi: strapi2 }) => ({
3852
3992
  /**
3853
3993
  * Returns available locales of a document for the current status
3854
3994
  */
3855
- getAvailableLocales(uid2, version, allVersions) {
3995
+ async getAvailableLocales(uid2, version, allVersions, validatableFields = []) {
3856
3996
  const versionsByLocale = fp.groupBy("locale", allVersions);
3857
3997
  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]);
3998
+ const model = strapi2.getModel(uid2);
3999
+ const keysToKeep = [...AVAILABLE_LOCALES_FIELDS, ...validatableFields];
4000
+ const traversalFunction = async (localeVersion) => strapiUtils.traverseEntity(
4001
+ ({ key }, { remove }) => {
4002
+ if (keysToKeep.includes(key)) {
4003
+ return;
4004
+ }
4005
+ remove(key);
4006
+ },
4007
+ { schema: model, getModel: strapi2.getModel.bind(strapi2) },
4008
+ // @ts-expect-error fix types DocumentVersion incompatible with Data
4009
+ localeVersion
4010
+ );
4011
+ const mappingResult = await strapiUtils.async.map(
4012
+ Object.values(versionsByLocale),
4013
+ async (localeVersions) => {
4014
+ const mappedLocaleVersions = await strapiUtils.async.map(
4015
+ localeVersions,
4016
+ traversalFunction
4017
+ );
4018
+ if (!strapiUtils.contentTypes.hasDraftAndPublish(model)) {
4019
+ return mappedLocaleVersions[0];
4020
+ }
4021
+ const draftVersion = mappedLocaleVersions.find((v) => v.publishedAt === null);
4022
+ const otherVersions = mappedLocaleVersions.filter((v) => v.id !== draftVersion?.id);
4023
+ if (!draftVersion) {
4024
+ return;
4025
+ }
4026
+ return {
4027
+ ...draftVersion,
4028
+ status: this.getStatus(draftVersion, otherVersions)
4029
+ };
3861
4030
  }
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);
4031
+ );
4032
+ return mappingResult.filter(Boolean);
3871
4033
  },
3872
4034
  /**
3873
4035
  * Returns available status of a document for the current locale
@@ -3905,26 +4067,37 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3905
4067
  });
3906
4068
  },
3907
4069
  getStatus(version, otherDocumentStatuses) {
3908
- const isDraft = version.publishedAt === null;
3909
- if (!otherDocumentStatuses?.length) {
3910
- return isDraft ? CONTENT_MANAGER_STATUS.DRAFT : CONTENT_MANAGER_STATUS.PUBLISHED;
4070
+ let draftVersion;
4071
+ let publishedVersion;
4072
+ if (version.publishedAt) {
4073
+ publishedVersion = version;
4074
+ } else {
4075
+ draftVersion = version;
3911
4076
  }
3912
- if (isDraft) {
3913
- const publishedVersion = otherDocumentStatuses?.find((d) => d.publishedAt !== null);
3914
- if (!publishedVersion) {
3915
- return CONTENT_MANAGER_STATUS.DRAFT;
3916
- }
4077
+ const otherVersion = otherDocumentStatuses?.at(0);
4078
+ if (otherVersion?.publishedAt) {
4079
+ publishedVersion = otherVersion;
4080
+ } else if (otherVersion) {
4081
+ draftVersion = otherVersion;
3917
4082
  }
3918
- if (areDatesEqual(version.updatedAt, otherDocumentStatuses.at(0)?.updatedAt, 500)) {
4083
+ if (!draftVersion)
3919
4084
  return CONTENT_MANAGER_STATUS.PUBLISHED;
3920
- }
3921
- return CONTENT_MANAGER_STATUS.MODIFIED;
4085
+ if (!publishedVersion)
4086
+ return CONTENT_MANAGER_STATUS.DRAFT;
4087
+ const isDraftModified = getIsVersionLatestModification(draftVersion, publishedVersion);
4088
+ return isDraftModified ? CONTENT_MANAGER_STATUS.MODIFIED : CONTENT_MANAGER_STATUS.PUBLISHED;
3922
4089
  },
4090
+ // TODO is it necessary to return metadata on every page of the CM
4091
+ // We could refactor this so the locales are only loaded when they're
4092
+ // needed. e.g. in the bulk locale action modal.
3923
4093
  async getMetadata(uid2, version, { availableLocales = true, availableStatus = true } = {}) {
4094
+ const populate = getValidatableFieldsPopulate(uid2);
3924
4095
  const versions = await strapi2.db.query(uid2).findMany({
3925
4096
  where: { documentId: version.documentId },
3926
- select: ["createdAt", "updatedAt", "locale", "publishedAt", "documentId"],
3927
4097
  populate: {
4098
+ // Populate only fields that require validation for bulk locale actions
4099
+ ...populate,
4100
+ // NOTE: creator fields are selected in this way to avoid exposing sensitive data
3928
4101
  createdBy: {
3929
4102
  select: ["id", "firstname", "lastname", "email"]
3930
4103
  },
@@ -3933,7 +4106,7 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3933
4106
  }
3934
4107
  }
3935
4108
  });
3936
- const availableLocalesResult = availableLocales ? this.getAvailableLocales(uid2, version, versions) : [];
4109
+ const availableLocalesResult = availableLocales ? await this.getAvailableLocales(uid2, version, versions, Object.keys(populate)) : [];
3937
4110
  const availableStatusResult = availableStatus ? this.getAvailableStatus(version, versions) : null;
3938
4111
  return {
3939
4112
  availableLocales: availableLocalesResult,
@@ -3946,8 +4119,9 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3946
4119
  * - Available status of the document for the current locale
3947
4120
  */
3948
4121
  async formatDocumentWithMetadata(uid2, document, opts = {}) {
3949
- if (!document)
4122
+ if (!document) {
3950
4123
  return document;
4124
+ }
3951
4125
  const hasDraftAndPublish = strapiUtils.contentTypes.hasDraftAndPublish(strapi2.getModel(uid2));
3952
4126
  if (!hasDraftAndPublish) {
3953
4127
  opts.availableStatus = false;
@@ -3997,26 +4171,9 @@ const sumDraftCounts = (entity, uid2) => {
3997
4171
  }, 0);
3998
4172
  };
3999
4173
  const { ApplicationError } = strapiUtils.errors;
4000
- const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = ALLOWED_WEBHOOK_EVENTS;
4001
4174
  const { PUBLISHED_AT_ATTRIBUTE } = strapiUtils.contentTypes.constants;
4002
4175
  const omitPublishedAtField = fp.omit(PUBLISHED_AT_ATTRIBUTE);
4003
4176
  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
4177
  const documentManager = ({ strapi: strapi2 }) => {
4021
4178
  return {
4022
4179
  async findOne(id, uid2, opts = {}) {
@@ -4035,6 +4192,9 @@ const documentManager = ({ strapi: strapi2 }) => {
4035
4192
  } else if (opts.locale && opts.locale !== "*") {
4036
4193
  where.locale = opts.locale;
4037
4194
  }
4195
+ if (typeof opts.isPublished === "boolean") {
4196
+ where.publishedAt = { $notNull: opts.isPublished };
4197
+ }
4038
4198
  return strapi2.db.query(uid2).findMany({ populate: opts.populate, where });
4039
4199
  },
4040
4200
  async findMany(opts, uid2) {
@@ -4042,20 +4202,16 @@ const documentManager = ({ strapi: strapi2 }) => {
4042
4202
  return strapi2.documents(uid2).findMany(params);
4043
4203
  },
4044
4204
  async findPage(opts, uid2) {
4045
- const page = Number(opts?.page) || 1;
4046
- const pageSize = Number(opts?.pageSize) || 10;
4205
+ const params = strapiUtils.pagination.withDefaultPagination(opts || {}, {
4206
+ maxLimit: 1e3
4207
+ });
4047
4208
  const [documents, total = 0] = await Promise.all([
4048
- strapi2.documents(uid2).findMany(opts),
4049
- strapi2.documents(uid2).count(opts)
4209
+ strapi2.documents(uid2).findMany(params),
4210
+ strapi2.documents(uid2).count(params)
4050
4211
  ]);
4051
4212
  return {
4052
4213
  results: documents,
4053
- pagination: {
4054
- page,
4055
- pageSize,
4056
- pageCount: Math.ceil(total / pageSize),
4057
- total
4058
- }
4214
+ pagination: strapiUtils.pagination.transformPagedPaginationInfo(params, total)
4059
4215
  };
4060
4216
  },
4061
4217
  async create(uid2, opts = {}) {
@@ -4101,70 +4257,36 @@ const documentManager = ({ strapi: strapi2 }) => {
4101
4257
  return {};
4102
4258
  },
4103
4259
  // 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 };
4260
+ async deleteMany(documentIds, uid2, opts = {}) {
4261
+ const deletedEntries = await strapi2.db.transaction(async () => {
4262
+ return Promise.all(documentIds.map(async (id) => this.delete(id, uid2, opts)));
4263
+ });
4264
+ return { count: deletedEntries.length };
4110
4265
  },
4111
4266
  async publish(id, uid2, opts = {}) {
4112
4267
  const populate = await buildDeepPopulate(uid2);
4113
4268
  const params = { ...opts, populate };
4114
- return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries.at(0));
4269
+ return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries);
4115
4270
  },
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
4271
+ async publishMany(uid2, documentIds, locale) {
4272
+ return strapi2.db.transaction(async () => {
4273
+ const results = await Promise.all(
4274
+ documentIds.map((documentId) => this.publish(documentId, uid2, { locale }))
4275
+ );
4276
+ const publishedEntitiesCount = results.flat().filter(Boolean).length;
4277
+ return publishedEntitiesCount;
4142
4278
  });
4143
- await Promise.all(
4144
- publishedEntities.map((doc) => emitEvent(uid2, ENTRY_PUBLISH, doc))
4145
- );
4146
- return publishedEntitiesCount;
4147
4279
  },
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
4280
+ async unpublishMany(documentIds, uid2, opts = {}) {
4281
+ const unpublishedEntries = await strapi2.db.transaction(async () => {
4282
+ return Promise.all(
4283
+ documentIds.map(
4284
+ (id) => strapi2.documents(uid2).unpublish({ ...opts, documentId: id }).then((result) => result?.entries)
4285
+ )
4286
+ );
4163
4287
  });
4164
- await Promise.all(
4165
- unpublishedEntities.map((doc) => emitEvent(uid2, ENTRY_UNPUBLISH, doc))
4166
- );
4167
- return unpublishedEntitiesCount;
4288
+ const unpublishedEntitiesCount = unpublishedEntries.flat().filter(Boolean).length;
4289
+ return { count: unpublishedEntitiesCount };
4168
4290
  },
4169
4291
  async unpublish(id, uid2, opts = {}) {
4170
4292
  const populate = await buildDeepPopulate(uid2);
@@ -4189,16 +4311,20 @@ const documentManager = ({ strapi: strapi2 }) => {
4189
4311
  }
4190
4312
  return sumDraftCounts(document, uid2);
4191
4313
  },
4192
- async countManyEntriesDraftRelations(ids, uid2, locale) {
4314
+ async countManyEntriesDraftRelations(documentIds, uid2, locale) {
4193
4315
  const { populate, hasRelations } = getDeepPopulateDraftCount(uid2);
4194
4316
  if (!hasRelations) {
4195
4317
  return 0;
4196
4318
  }
4319
+ let localeFilter = {};
4320
+ if (locale) {
4321
+ localeFilter = Array.isArray(locale) ? { locale: { $in: locale } } : { locale };
4322
+ }
4197
4323
  const entities = await strapi2.db.query(uid2).findMany({
4198
4324
  populate,
4199
4325
  where: {
4200
- id: { $in: ids },
4201
- ...locale ? { locale } : {}
4326
+ documentId: { $in: documentIds },
4327
+ ...localeFilter
4202
4328
  }
4203
4329
  });
4204
4330
  const totalNumberDraftRelations = entities.reduce(