@strapi/content-manager 5.61.1 → 5.62.0

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 (260) hide show
  1. package/dist/admin/components/ConfigurationForm/Fields.js +1 -1
  2. package/dist/admin/components/ConfigurationForm/Fields.js.map +1 -1
  3. package/dist/admin/components/ConfigurationForm/Fields.mjs +3 -3
  4. package/dist/admin/components/ConfigurationForm/Fields.mjs.map +1 -1
  5. package/dist/admin/features/I18nSharedFieldsLock.js +54 -0
  6. package/dist/admin/features/I18nSharedFieldsLock.js.map +1 -0
  7. package/dist/admin/features/I18nSharedFieldsLock.mjs +31 -0
  8. package/dist/admin/features/I18nSharedFieldsLock.mjs.map +1 -0
  9. package/dist/admin/history/components/VersionInputRenderer.js +7 -2
  10. package/dist/admin/history/components/VersionInputRenderer.js.map +1 -1
  11. package/dist/admin/history/components/VersionInputRenderer.mjs +7 -3
  12. package/dist/admin/history/components/VersionInputRenderer.mjs.map +1 -1
  13. package/dist/admin/hooks/useContentManagerInitData.js +27 -8
  14. package/dist/admin/hooks/useContentManagerInitData.js.map +1 -1
  15. package/dist/admin/hooks/useContentManagerInitData.mjs +29 -10
  16. package/dist/admin/hooks/useContentManagerInitData.mjs.map +1 -1
  17. package/dist/admin/hooks/useDocument.js +11 -13
  18. package/dist/admin/hooks/useDocument.js.map +1 -1
  19. package/dist/admin/hooks/useDocument.mjs +11 -13
  20. package/dist/admin/hooks/useDocument.mjs.map +1 -1
  21. package/dist/admin/hooks/useI18nFieldLock.js +169 -0
  22. package/dist/admin/hooks/useI18nFieldLock.js.map +1 -0
  23. package/dist/admin/hooks/useI18nFieldLock.mjs +140 -0
  24. package/dist/admin/hooks/useI18nFieldLock.mjs.map +1 -0
  25. package/dist/admin/hooks/usePersistentQueryParams.js +16 -3
  26. package/dist/admin/hooks/usePersistentQueryParams.js.map +1 -1
  27. package/dist/admin/hooks/usePersistentQueryParams.mjs +16 -3
  28. package/dist/admin/hooks/usePersistentQueryParams.mjs.map +1 -1
  29. package/dist/admin/index.js +5 -0
  30. package/dist/admin/index.js.map +1 -1
  31. package/dist/admin/index.mjs +2 -0
  32. package/dist/admin/index.mjs.map +1 -1
  33. package/dist/admin/modules/app.js +8 -1
  34. package/dist/admin/modules/app.js.map +1 -1
  35. package/dist/admin/modules/app.mjs +8 -2
  36. package/dist/admin/modules/app.mjs.map +1 -1
  37. package/dist/admin/pages/EditView/EditViewPage.js +129 -127
  38. package/dist/admin/pages/EditView/EditViewPage.js.map +1 -1
  39. package/dist/admin/pages/EditView/EditViewPage.mjs +129 -127
  40. package/dist/admin/pages/EditView/EditViewPage.mjs.map +1 -1
  41. package/dist/admin/pages/EditView/components/DocumentActions.js +11 -4
  42. package/dist/admin/pages/EditView/components/DocumentActions.js.map +1 -1
  43. package/dist/admin/pages/EditView/components/DocumentActions.mjs +12 -5
  44. package/dist/admin/pages/EditView/components/DocumentActions.mjs.map +1 -1
  45. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/EditorLayout.js +31 -21
  46. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/EditorLayout.js.map +1 -1
  47. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/EditorLayout.mjs +32 -22
  48. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/EditorLayout.mjs.map +1 -1
  49. package/dist/admin/pages/EditView/components/FormInputs/Component/AddComponentMenu.js +98 -0
  50. package/dist/admin/pages/EditView/components/FormInputs/Component/AddComponentMenu.js.map +1 -0
  51. package/dist/admin/pages/EditView/components/FormInputs/Component/AddComponentMenu.mjs +96 -0
  52. package/dist/admin/pages/EditView/components/FormInputs/Component/AddComponentMenu.mjs.map +1 -0
  53. package/dist/admin/pages/EditView/components/FormInputs/Component/ComponentCopyModal.js +1103 -0
  54. package/dist/admin/pages/EditView/components/FormInputs/Component/ComponentCopyModal.js.map +1 -0
  55. package/dist/admin/pages/EditView/components/FormInputs/Component/ComponentCopyModal.mjs +1077 -0
  56. package/dist/admin/pages/EditView/components/FormInputs/Component/ComponentCopyModal.mjs.map +1 -0
  57. package/dist/admin/pages/EditView/components/FormInputs/Component/CopiedComponents.js +211 -0
  58. package/dist/admin/pages/EditView/components/FormInputs/Component/CopiedComponents.js.map +1 -0
  59. package/dist/admin/pages/EditView/components/FormInputs/Component/CopiedComponents.mjs +183 -0
  60. package/dist/admin/pages/EditView/components/FormInputs/Component/CopiedComponents.mjs.map +1 -0
  61. package/dist/admin/pages/EditView/components/FormInputs/Component/Initializer.js +84 -41
  62. package/dist/admin/pages/EditView/components/FormInputs/Component/Initializer.js.map +1 -1
  63. package/dist/admin/pages/EditView/components/FormInputs/Component/Initializer.mjs +86 -43
  64. package/dist/admin/pages/EditView/components/FormInputs/Component/Initializer.mjs.map +1 -1
  65. package/dist/admin/pages/EditView/components/FormInputs/Component/Input.js +86 -20
  66. package/dist/admin/pages/EditView/components/FormInputs/Component/Input.js.map +1 -1
  67. package/dist/admin/pages/EditView/components/FormInputs/Component/Input.mjs +89 -23
  68. package/dist/admin/pages/EditView/components/FormInputs/Component/Input.mjs.map +1 -1
  69. package/dist/admin/pages/EditView/components/FormInputs/Component/NonRepeatable.js +5 -2
  70. package/dist/admin/pages/EditView/components/FormInputs/Component/NonRepeatable.js.map +1 -1
  71. package/dist/admin/pages/EditView/components/FormInputs/Component/NonRepeatable.mjs +5 -2
  72. package/dist/admin/pages/EditView/components/FormInputs/Component/NonRepeatable.mjs.map +1 -1
  73. package/dist/admin/pages/EditView/components/FormInputs/Component/Repeatable.js +142 -22
  74. package/dist/admin/pages/EditView/components/FormInputs/Component/Repeatable.js.map +1 -1
  75. package/dist/admin/pages/EditView/components/FormInputs/Component/Repeatable.mjs +143 -23
  76. package/dist/admin/pages/EditView/components/FormInputs/Component/Repeatable.mjs.map +1 -1
  77. package/dist/admin/pages/EditView/components/FormInputs/Component/utils/componentCopy.js +402 -0
  78. package/dist/admin/pages/EditView/components/FormInputs/Component/utils/componentCopy.js.map +1 -0
  79. package/dist/admin/pages/EditView/components/FormInputs/Component/utils/componentCopy.mjs +389 -0
  80. package/dist/admin/pages/EditView/components/FormInputs/Component/utils/componentCopy.mjs.map +1 -0
  81. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.js +84 -33
  82. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.js.map +1 -1
  83. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.mjs +85 -34
  84. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.mjs.map +1 -1
  85. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentPicker.js +5 -1
  86. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentPicker.js.map +1 -1
  87. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentPicker.mjs +5 -1
  88. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentPicker.mjs.map +1 -1
  89. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.js +17 -4
  90. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.js.map +1 -1
  91. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.mjs +18 -5
  92. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.mjs.map +1 -1
  93. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.js +54 -6
  94. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.js.map +1 -1
  95. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.mjs +54 -6
  96. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.mjs.map +1 -1
  97. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.js +11 -9
  98. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.js.map +1 -1
  99. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.mjs +12 -10
  100. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.mjs.map +1 -1
  101. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js +9 -4
  102. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js.map +1 -1
  103. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs +9 -4
  104. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs.map +1 -1
  105. package/dist/admin/pages/EditView/components/FormLayout.js +66 -63
  106. package/dist/admin/pages/EditView/components/FormLayout.js.map +1 -1
  107. package/dist/admin/pages/EditView/components/FormLayout.mjs +66 -63
  108. package/dist/admin/pages/EditView/components/FormLayout.mjs.map +1 -1
  109. package/dist/admin/pages/EditView/components/InputRenderer.js +7 -1
  110. package/dist/admin/pages/EditView/components/InputRenderer.js.map +1 -1
  111. package/dist/admin/pages/EditView/components/InputRenderer.mjs +7 -1
  112. package/dist/admin/pages/EditView/components/InputRenderer.mjs.map +1 -1
  113. package/dist/admin/pages/EditView/utils/data.js +14 -9
  114. package/dist/admin/pages/EditView/utils/data.js.map +1 -1
  115. package/dist/admin/pages/EditView/utils/data.mjs +14 -9
  116. package/dist/admin/pages/EditView/utils/data.mjs.map +1 -1
  117. package/dist/admin/pages/EditView/utils/forms.js +34 -0
  118. package/dist/admin/pages/EditView/utils/forms.js.map +1 -1
  119. package/dist/admin/pages/EditView/utils/forms.mjs +34 -1
  120. package/dist/admin/pages/EditView/utils/forms.mjs.map +1 -1
  121. package/dist/admin/pages/ListConfiguration/components/DraggableCard.js +2 -1
  122. package/dist/admin/pages/ListConfiguration/components/DraggableCard.js.map +1 -1
  123. package/dist/admin/pages/ListConfiguration/components/DraggableCard.mjs +2 -1
  124. package/dist/admin/pages/ListConfiguration/components/DraggableCard.mjs.map +1 -1
  125. package/dist/admin/pages/ListConfiguration/components/EditFieldForm.js +7 -6
  126. package/dist/admin/pages/ListConfiguration/components/EditFieldForm.js.map +1 -1
  127. package/dist/admin/pages/ListConfiguration/components/EditFieldForm.mjs +7 -6
  128. package/dist/admin/pages/ListConfiguration/components/EditFieldForm.mjs.map +1 -1
  129. package/dist/admin/pages/ListView/ListViewPage.js +11 -2
  130. package/dist/admin/pages/ListView/ListViewPage.js.map +1 -1
  131. package/dist/admin/pages/ListView/ListViewPage.mjs +13 -4
  132. package/dist/admin/pages/ListView/ListViewPage.mjs.map +1 -1
  133. package/dist/admin/pages/ListView/components/Filters.js +15 -5
  134. package/dist/admin/pages/ListView/components/Filters.js.map +1 -1
  135. package/dist/admin/pages/ListView/components/Filters.mjs +16 -7
  136. package/dist/admin/pages/ListView/components/Filters.mjs.map +1 -1
  137. package/dist/admin/preview/components/InputPopover.js +18 -8
  138. package/dist/admin/preview/components/InputPopover.js.map +1 -1
  139. package/dist/admin/preview/components/InputPopover.mjs +19 -9
  140. package/dist/admin/preview/components/InputPopover.mjs.map +1 -1
  141. package/dist/admin/preview/pages/Preview.js +5 -16
  142. package/dist/admin/preview/pages/Preview.js.map +1 -1
  143. package/dist/admin/preview/pages/Preview.mjs +5 -16
  144. package/dist/admin/preview/pages/Preview.mjs.map +1 -1
  145. package/dist/admin/preview/utils/fieldUtils.js +2 -0
  146. package/dist/admin/preview/utils/fieldUtils.js.map +1 -1
  147. package/dist/admin/preview/utils/fieldUtils.mjs +2 -1
  148. package/dist/admin/preview/utils/fieldUtils.mjs.map +1 -1
  149. package/dist/admin/services/api.js +2 -1
  150. package/dist/admin/services/api.js.map +1 -1
  151. package/dist/admin/services/api.mjs +2 -1
  152. package/dist/admin/services/api.mjs.map +1 -1
  153. package/dist/admin/services/documents.js +21 -1
  154. package/dist/admin/services/documents.js.map +1 -1
  155. package/dist/admin/services/documents.mjs +21 -2
  156. package/dist/admin/services/documents.mjs.map +1 -1
  157. package/dist/admin/services/relations.js +47 -1
  158. package/dist/admin/services/relations.js.map +1 -1
  159. package/dist/admin/services/relations.mjs +47 -2
  160. package/dist/admin/services/relations.mjs.map +1 -1
  161. package/dist/admin/src/exports.d.ts +2 -0
  162. package/dist/admin/src/features/I18nSharedFieldsLock.d.ts +22 -0
  163. package/dist/admin/src/history/components/VersionInputRenderer.d.ts +7 -1
  164. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  165. package/dist/admin/src/hooks/useI18nFieldLock.d.ts +61 -0
  166. package/dist/admin/src/modules/app.d.ts +4 -1
  167. package/dist/admin/src/pages/EditView/components/FormInputs/Component/AddComponentMenu.d.ts +15 -0
  168. package/dist/admin/src/pages/EditView/components/FormInputs/Component/ComponentCopyModal.d.ts +58 -0
  169. package/dist/admin/src/pages/EditView/components/FormInputs/Component/CopiedComponents.d.ts +41 -0
  170. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Initializer.d.ts +7 -3
  171. package/dist/admin/src/pages/EditView/components/FormInputs/Component/NonRepeatable.d.ts +1 -1
  172. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Repeatable.d.ts +2 -2
  173. package/dist/admin/src/pages/EditView/components/FormInputs/Component/utils/componentCopy.d.ts +115 -0
  174. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.d.ts +6 -3
  175. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/ComponentPicker.d.ts +2 -1
  176. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.d.ts +5 -1
  177. package/dist/admin/src/pages/EditView/utils/forms.d.ts +13 -2
  178. package/dist/admin/src/pages/ListView/components/Filters.d.ts +12 -3
  179. package/dist/admin/src/preview/services/preview.d.ts +1 -1
  180. package/dist/admin/src/preview/utils/fieldUtils.d.ts +1 -0
  181. package/dist/admin/src/services/api.d.ts +1 -1
  182. package/dist/admin/src/services/components.d.ts +2 -2
  183. package/dist/admin/src/services/contentTypes.d.ts +3 -3
  184. package/dist/admin/src/services/documents.d.ts +25 -21
  185. package/dist/admin/src/services/homepage.d.ts +1 -1
  186. package/dist/admin/src/services/init.d.ts +1 -1
  187. package/dist/admin/src/services/relations.d.ts +5 -3
  188. package/dist/admin/src/services/uid.d.ts +3 -3
  189. package/dist/admin/translations/en.json.js +41 -0
  190. package/dist/admin/translations/en.json.js.map +1 -1
  191. package/dist/admin/translations/en.json.mjs +41 -0
  192. package/dist/admin/translations/en.json.mjs.map +1 -1
  193. package/dist/server/controllers/relations.js +4 -2
  194. package/dist/server/controllers/relations.js.map +1 -1
  195. package/dist/server/controllers/relations.mjs +4 -2
  196. package/dist/server/controllers/relations.mjs.map +1 -1
  197. package/dist/server/controllers/utils/metadata.js +3 -1
  198. package/dist/server/controllers/utils/metadata.js.map +1 -1
  199. package/dist/server/controllers/utils/metadata.mjs +3 -1
  200. package/dist/server/controllers/utils/metadata.mjs.map +1 -1
  201. package/dist/server/history/services/utils.js +3 -1
  202. package/dist/server/history/services/utils.js.map +1 -1
  203. package/dist/server/history/services/utils.mjs +3 -1
  204. package/dist/server/history/services/utils.mjs.map +1 -1
  205. package/dist/server/homepage/services/homepage-query-utils.js +14 -0
  206. package/dist/server/homepage/services/homepage-query-utils.js.map +1 -1
  207. package/dist/server/homepage/services/homepage-query-utils.mjs +14 -1
  208. package/dist/server/homepage/services/homepage-query-utils.mjs.map +1 -1
  209. package/dist/server/homepage/services/homepage.js +1 -1
  210. package/dist/server/homepage/services/homepage.js.map +1 -1
  211. package/dist/server/homepage/services/homepage.mjs +2 -2
  212. package/dist/server/homepage/services/homepage.mjs.map +1 -1
  213. package/dist/server/mcp/sanitizers/shape-relations.js +1 -3
  214. package/dist/server/mcp/sanitizers/shape-relations.js.map +1 -1
  215. package/dist/server/mcp/sanitizers/shape-relations.mjs +1 -3
  216. package/dist/server/mcp/sanitizers/shape-relations.mjs.map +1 -1
  217. package/dist/server/mcp/schemas/output-schemas.js +2 -1
  218. package/dist/server/mcp/schemas/output-schemas.js.map +1 -1
  219. package/dist/server/mcp/schemas/output-schemas.mjs +2 -1
  220. package/dist/server/mcp/schemas/output-schemas.mjs.map +1 -1
  221. package/dist/server/services/document-manager.js +3 -2
  222. package/dist/server/services/document-manager.js.map +1 -1
  223. package/dist/server/services/document-manager.mjs +3 -2
  224. package/dist/server/services/document-manager.mjs.map +1 -1
  225. package/dist/server/services/document-metadata.js +92 -4
  226. package/dist/server/services/document-metadata.js.map +1 -1
  227. package/dist/server/services/document-metadata.mjs +92 -4
  228. package/dist/server/services/document-metadata.mjs.map +1 -1
  229. package/dist/server/services/permission-checker.js +20 -8
  230. package/dist/server/services/permission-checker.js.map +1 -1
  231. package/dist/server/services/permission-checker.mjs +20 -8
  232. package/dist/server/services/permission-checker.mjs.map +1 -1
  233. package/dist/server/services/uid.js +2 -1
  234. package/dist/server/services/uid.js.map +1 -1
  235. package/dist/server/services/uid.mjs +2 -1
  236. package/dist/server/services/uid.mjs.map +1 -1
  237. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  238. package/dist/server/src/controllers/utils/metadata.d.ts +1 -0
  239. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -1
  240. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  241. package/dist/server/src/homepage/services/homepage-query-utils.d.ts +6 -0
  242. package/dist/server/src/homepage/services/homepage-query-utils.d.ts.map +1 -1
  243. package/dist/server/src/homepage/services/homepage.d.ts.map +1 -1
  244. package/dist/server/src/index.d.ts +21 -7
  245. package/dist/server/src/index.d.ts.map +1 -1
  246. package/dist/server/src/mcp/sanitizers/shape-relations.d.ts.map +1 -1
  247. package/dist/server/src/mcp/schemas/output-schemas.d.ts.map +1 -1
  248. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  249. package/dist/server/src/services/document-metadata.d.ts +5 -1
  250. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  251. package/dist/server/src/services/index.d.ts +21 -7
  252. package/dist/server/src/services/index.d.ts.map +1 -1
  253. package/dist/server/src/services/permission-checker.d.ts +6 -5
  254. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  255. package/dist/server/src/services/uid.d.ts.map +1 -1
  256. package/dist/shared/contracts/collection-types.d.ts +7 -0
  257. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  258. package/dist/shared/contracts/single-types.d.ts +4 -0
  259. package/dist/shared/contracts/single-types.d.ts.map +1 -1
  260. package/package.json +9 -9
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","sources":["../../../../server/src/history/services/utils.ts"],"sourcesContent":["import { difference, omit } from 'lodash';\n\nimport { contentTypes } from '@strapi/utils';\nimport type { Core, Modules, Schema, Data, Struct, UID } from '@strapi/types';\n\nimport { FIELDS_TO_IGNORE } from '../constants';\nimport type { CreateHistoryVersion } from '../../../../shared/contracts/history-versions';\nimport type { HistoryVersions } from '../../../../shared/contracts';\nimport type { RelationResult } from '../../../../shared/contracts/relations';\n\nexport const DEFAULT_RETENTION_DAYS = 90;\n\n/**\n * The retention the history delete job applies, from the license's value and the admin setting.\n * Shared with the Plan card's resolver (../entitlements.ts) so the card shows this same figure.\n */\nexport const computeRetentionDays = (\n licenseRetentionDays: unknown,\n userRetentionDays: number | null | undefined\n): number => {\n if (licenseRetentionDays == null) {\n return userRetentionDays ?? DEFAULT_RETENTION_DAYS;\n }\n\n // The registry can ship the value as a numeric string\n const licenseDays = Number(licenseRetentionDays);\n\n // Allow users to override the license retention days, but not to increase it\n if (userRetentionDays && userRetentionDays < licenseDays) {\n return userRetentionDays;\n }\n\n // User didn't provide retention days value, use the license or fallback to default\n return Math.min(licenseDays, DEFAULT_RETENTION_DAYS);\n};\n\ntype RelationResponse = {\n results: RelationResult[];\n meta: { missingCount: number };\n};\n\nexport const createServiceUtils = ({ strapi }: { strapi: Core.Strapi }) => {\n /**\n * @description\n * Get the difference between the version schema and the content type schema\n */\n const getSchemaAttributesDiff = (\n versionSchemaAttributes: CreateHistoryVersion['schema'],\n contentTypeSchemaAttributes: Struct.SchemaAttributes\n ) => {\n // Omit the same fields that were omitted when creating a history version\n const sanitizedContentTypeSchemaAttributes = omit(\n contentTypeSchemaAttributes,\n FIELDS_TO_IGNORE\n );\n\n const reduceDifferenceToAttributesObject = (\n diffKeys: string[],\n source: CreateHistoryVersion['schema']\n ) => {\n return diffKeys.reduce<CreateHistoryVersion['schema']>(\n (previousAttributesObject, diffKey) => {\n previousAttributesObject[diffKey] = source[diffKey];\n\n return previousAttributesObject;\n },\n {}\n );\n };\n\n const versionSchemaKeys = Object.keys(versionSchemaAttributes);\n const contentTypeSchemaAttributesKeys = Object.keys(sanitizedContentTypeSchemaAttributes);\n // The attribute is new if it's on the content type schema but not on the version schema\n const uniqueToContentType = difference(contentTypeSchemaAttributesKeys, versionSchemaKeys);\n const added = reduceDifferenceToAttributesObject(\n uniqueToContentType,\n sanitizedContentTypeSchemaAttributes\n );\n // The attribute was removed or renamed if it's on the version schema but not on the content type schema\n const uniqueToVersion = difference(versionSchemaKeys, contentTypeSchemaAttributesKeys);\n const removed = reduceDifferenceToAttributesObject(uniqueToVersion, versionSchemaAttributes);\n\n return { added, removed };\n };\n\n /**\n * @description\n * Gets the value to set for a relation when restoring a document\n * @returns\n * The relation if it exists or null\n */\n const getRelationRestoreValue = async (\n versionRelationData: Modules.Documents.AnyDocument | Modules.Documents.AnyDocument[],\n attribute: Schema.Attribute.RelationWithTarget\n ) => {\n if (Array.isArray(versionRelationData)) {\n if (versionRelationData.length === 0) return versionRelationData;\n\n const existingAndMissingRelations = await Promise.all(\n versionRelationData.map((relation) => {\n return strapi.documents(attribute.target).findOne({\n documentId: relation.documentId,\n locale: relation.locale || undefined,\n });\n })\n );\n\n return existingAndMissingRelations.filter((relation) => relation !== null);\n }\n\n return strapi.documents(attribute.target).findOne({\n documentId: versionRelationData.documentId,\n locale: versionRelationData.locale || undefined,\n });\n };\n\n /**\n * @description\n * Gets the value to set for a media asset when restoring a document\n * @returns\n * The media asset if it exists or null\n */\n const getMediaRestoreValue = async (\n versionRelationData: Modules.Documents.AnyDocument | Modules.Documents.AnyDocument[]\n ) => {\n if (Array.isArray(versionRelationData)) {\n const existingAndMissingMedias = await Promise.all(\n versionRelationData.map((media) => {\n return strapi.db.query('plugin::upload.file').findOne({ where: { id: media.id } });\n })\n );\n\n return existingAndMissingMedias.filter((media) => media != null);\n }\n\n return strapi.db\n .query('plugin::upload.file')\n .findOne({ where: { id: versionRelationData.id } });\n };\n\n const getDefaultLocale = async () => strapi.localization.getDefaultLocale();\n\n const isLocalizedContentType = (model: Schema.ContentType) =>\n strapi.localization.isLocalizedContentType(model);\n\n /**\n *\n * @description\n * Creates a dictionary of all locales available\n */\n const getLocaleDictionary = async (): Promise<{\n [key: string]: { name: string; code: string };\n }> => {\n const locales = await strapi.localization.getLocales();\n\n return locales.reduce<\n Record<string, NonNullable<HistoryVersions.HistoryVersionDataResponse['locale']>>\n >((acc, locale) => {\n acc[locale.code] = { name: locale.name, code: locale.code };\n\n return acc;\n }, {});\n };\n\n /**\n *\n * @description\n * Gets the number of retention days defined on the license or configured by the user\n */\n const getRetentionDays = () => {\n const featureConfig = strapi.ee.features.get('cms-content-history');\n\n return computeRetentionDays(\n typeof featureConfig === 'object' ? featureConfig?.options?.retentionDays : undefined,\n strapi.config.get('admin.history.retentionDays')\n );\n };\n\n const getVersionStatus = async (\n contentTypeUid: HistoryVersions.CreateHistoryVersion['contentType'],\n document: Modules.Documents.AnyDocument | null\n ) => {\n const documentMetadataService = strapi.plugin('content-manager').service('document-metadata');\n const meta = await documentMetadataService.getMetadata(contentTypeUid, document);\n\n return documentMetadataService.getStatus(document, meta.availableStatus);\n };\n\n /**\n * Lists all the fields of a component, excepts those that will be populated.\n * The goal is to exclude the ID, because when restoring a component,\n * referencing an ID for a component db row that was deleted creates an error.\n * So we never store component IDs to ensure they're re-created while restoring a version.\n */\n const getComponentFields = (componentUID: UID.Component): string[] => {\n return Object.entries(strapi.getModel(componentUID).attributes).reduce<string[]>(\n (fieldsAcc, [key, attribute]) => {\n if (!['relation', 'media', 'component', 'dynamiczone'].includes(attribute.type)) {\n fieldsAcc.push(key);\n }\n\n return fieldsAcc;\n },\n []\n );\n };\n\n /**\n * @description\n * Creates a populate object that looks for all the relations that need\n * to be saved in history, and populates only the fields needed to later retrieve the content.\n *\n * @param uid - The content type UID\n * @param useDatabaseSyntax - Whether to use the database syntax for populate, defaults to false\n */\n const getDeepPopulate = (uid: UID.Schema, useDatabaseSyntax = false) => {\n const model = strapi.getModel(uid);\n const attributes = Object.entries(model.attributes);\n const fieldSelector = useDatabaseSyntax ? 'select' : 'fields';\n\n return attributes.reduce((acc: any, [attributeName, attribute]) => {\n switch (attribute.type) {\n case 'relation': {\n // TODO: Support polymorphic relations\n const isMorphRelation = attribute.relation.toLowerCase().startsWith('morph');\n if (isMorphRelation) {\n break;\n }\n\n const isVisible = contentTypes.isVisibleAttribute(model, attributeName);\n if (isVisible) {\n acc[attributeName] = { [fieldSelector]: ['documentId', 'locale', 'publishedAt'] };\n }\n break;\n }\n\n case 'media': {\n acc[attributeName] = { [fieldSelector]: ['id'] };\n break;\n }\n\n case 'component': {\n const populate = getDeepPopulate(attribute.component);\n acc[attributeName] = {\n populate,\n [fieldSelector]: getComponentFields(attribute.component),\n };\n break;\n }\n\n case 'dynamiczone': {\n // Use fragments to populate the dynamic zone components\n const populatedComponents = (attribute.components || []).reduce(\n (acc: any, componentUID: UID.Component) => {\n acc[componentUID] = {\n populate: getDeepPopulate(componentUID),\n [fieldSelector]: getComponentFields(componentUID),\n };\n return acc;\n },\n {}\n );\n\n acc[attributeName] = { on: populatedComponents };\n break;\n }\n default:\n break;\n }\n\n return acc;\n }, {});\n };\n\n /**\n * @description\n * Builds a response object for relations containing the related data and a count of missing relations\n */\n const buildMediaResponse = async (values: { id: Data.ID }[]): Promise<RelationResponse> => {\n return (\n values\n // Until we implement proper pagination, limit relations to an arbitrary amount\n .slice(0, 25)\n .reduce(\n async (currentRelationDataPromise, entry) => {\n const currentRelationData = await currentRelationDataPromise;\n\n // Entry can be null if it's a toOne relation\n if (!entry) {\n return currentRelationData;\n }\n\n const relatedEntry = await strapi.db\n .query('plugin::upload.file')\n .findOne({ where: { id: entry.id } });\n\n if (relatedEntry) {\n currentRelationData.results.push(relatedEntry);\n } else {\n // The related content has been deleted\n currentRelationData.meta.missingCount += 1;\n }\n\n return currentRelationData;\n },\n Promise.resolve<RelationResponse>({\n results: [],\n meta: { missingCount: 0 },\n })\n )\n );\n };\n\n /**\n * @description\n * Builds a response object for media containing the media assets data and a count of missing media assets\n */\n const buildRelationReponse = async (\n values: {\n documentId: string;\n locale: string | null;\n }[],\n attributeSchema: Schema.Attribute.RelationWithTarget\n ): Promise<RelationResponse> => {\n return (\n values\n // Until we implement proper pagination, limit relations to an arbitrary amount\n .slice(0, 25)\n .reduce(\n async (currentRelationDataPromise, entry) => {\n const currentRelationData = await currentRelationDataPromise;\n\n // Entry can be null if it's a toOne relation\n if (!entry) {\n return currentRelationData;\n }\n const relatedEntry = await strapi\n .documents(attributeSchema.target)\n .findOne({ documentId: entry.documentId, locale: entry.locale || undefined });\n\n if (relatedEntry) {\n currentRelationData.results.push({\n ...relatedEntry,\n status: await getVersionStatus(attributeSchema.target, relatedEntry),\n });\n } else {\n // The related content has been deleted\n currentRelationData.meta.missingCount += 1;\n }\n\n return currentRelationData;\n },\n Promise.resolve<RelationResponse>({\n results: [],\n meta: { missingCount: 0 },\n })\n )\n );\n };\n\n return {\n getSchemaAttributesDiff,\n getRelationRestoreValue,\n getMediaRestoreValue,\n getDefaultLocale,\n isLocalizedContentType,\n getLocaleDictionary,\n getRetentionDays,\n getVersionStatus,\n getDeepPopulate,\n buildMediaResponse,\n buildRelationReponse,\n };\n};\n"],"names":["DEFAULT_RETENTION_DAYS","computeRetentionDays","licenseRetentionDays","userRetentionDays","licenseDays","Number","Math","min","createServiceUtils","strapi","getSchemaAttributesDiff","versionSchemaAttributes","contentTypeSchemaAttributes","sanitizedContentTypeSchemaAttributes","omit","FIELDS_TO_IGNORE","reduceDifferenceToAttributesObject","diffKeys","source","reduce","previousAttributesObject","diffKey","versionSchemaKeys","Object","keys","contentTypeSchemaAttributesKeys","uniqueToContentType","difference","added","uniqueToVersion","removed","getRelationRestoreValue","versionRelationData","attribute","Array","isArray","length","existingAndMissingRelations","Promise","all","map","relation","documents","target","findOne","documentId","locale","undefined","filter","getMediaRestoreValue","existingAndMissingMedias","media","db","query","where","id","getDefaultLocale","localization","isLocalizedContentType","model","getLocaleDictionary","locales","getLocales","acc","code","name","getRetentionDays","featureConfig","ee","features","get","options","retentionDays","config","getVersionStatus","contentTypeUid","document","documentMetadataService","plugin","service","meta","getMetadata","getStatus","availableStatus","getComponentFields","componentUID","entries","getModel","attributes","fieldsAcc","key","includes","type","push","getDeepPopulate","uid","useDatabaseSyntax","fieldSelector","attributeName","isMorphRelation","toLowerCase","startsWith","isVisible","contentTypes","isVisibleAttribute","populate","component","populatedComponents","components","on","buildMediaResponse","values","slice","currentRelationDataPromise","entry","currentRelationData","relatedEntry","results","missingCount","resolve","buildRelationReponse","attributeSchema","status"],"mappings":"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"}
1
+ {"version":3,"file":"utils.mjs","sources":["../../../../server/src/history/services/utils.ts"],"sourcesContent":["import { difference, omit } from 'lodash';\n\nimport { contentTypes } from '@strapi/utils';\nimport type { Core, Modules, Schema, Data, Struct, UID } from '@strapi/types';\n\nimport { FIELDS_TO_IGNORE } from '../constants';\nimport type { CreateHistoryVersion } from '../../../../shared/contracts/history-versions';\nimport type { HistoryVersions } from '../../../../shared/contracts';\nimport type { RelationResult } from '../../../../shared/contracts/relations';\n\nexport const DEFAULT_RETENTION_DAYS = 90;\n\n/**\n * The retention the history delete job applies, from the license's value and the admin setting.\n * Shared with the Plan card's resolver (../entitlements.ts) so the card shows this same figure.\n */\nexport const computeRetentionDays = (\n licenseRetentionDays: unknown,\n userRetentionDays: number | null | undefined\n): number => {\n if (licenseRetentionDays == null) {\n return userRetentionDays ?? DEFAULT_RETENTION_DAYS;\n }\n\n // The registry can ship the value as a numeric string\n const licenseDays = Number(licenseRetentionDays);\n\n // Allow users to override the license retention days, but not to increase it\n if (userRetentionDays && userRetentionDays < licenseDays) {\n return userRetentionDays;\n }\n\n // User didn't provide retention days value, use the license or fallback to default\n return Math.min(licenseDays, DEFAULT_RETENTION_DAYS);\n};\n\ntype RelationResponse = {\n results: RelationResult[];\n meta: { missingCount: number };\n};\n\nexport const createServiceUtils = ({ strapi }: { strapi: Core.Strapi }) => {\n /**\n * @description\n * Get the difference between the version schema and the content type schema\n */\n const getSchemaAttributesDiff = (\n versionSchemaAttributes: CreateHistoryVersion['schema'],\n contentTypeSchemaAttributes: Struct.SchemaAttributes\n ) => {\n // Omit the same fields that were omitted when creating a history version\n const sanitizedContentTypeSchemaAttributes = omit(\n contentTypeSchemaAttributes,\n FIELDS_TO_IGNORE\n );\n\n const reduceDifferenceToAttributesObject = (\n diffKeys: string[],\n source: CreateHistoryVersion['schema']\n ) => {\n return diffKeys.reduce<CreateHistoryVersion['schema']>(\n (previousAttributesObject, diffKey) => {\n previousAttributesObject[diffKey] = source[diffKey];\n\n return previousAttributesObject;\n },\n {}\n );\n };\n\n const versionSchemaKeys = Object.keys(versionSchemaAttributes);\n const contentTypeSchemaAttributesKeys = Object.keys(sanitizedContentTypeSchemaAttributes);\n // The attribute is new if it's on the content type schema but not on the version schema\n const uniqueToContentType = difference(contentTypeSchemaAttributesKeys, versionSchemaKeys);\n const added = reduceDifferenceToAttributesObject(\n uniqueToContentType,\n sanitizedContentTypeSchemaAttributes\n );\n // The attribute was removed or renamed if it's on the version schema but not on the content type schema\n const uniqueToVersion = difference(versionSchemaKeys, contentTypeSchemaAttributesKeys);\n const removed = reduceDifferenceToAttributesObject(uniqueToVersion, versionSchemaAttributes);\n\n return { added, removed };\n };\n\n /**\n * @description\n * Gets the value to set for a relation when restoring a document\n * @returns\n * The relation if it exists or null\n */\n const getRelationRestoreValue = async (\n versionRelationData: Modules.Documents.AnyDocument | Modules.Documents.AnyDocument[],\n attribute: Schema.Attribute.RelationWithTarget\n ) => {\n if (Array.isArray(versionRelationData)) {\n if (versionRelationData.length === 0) return versionRelationData;\n\n const existingAndMissingRelations = await Promise.all(\n versionRelationData.map((relation) => {\n return strapi.documents(attribute.target).findOne({\n documentId: relation.documentId,\n locale: relation.locale || undefined,\n });\n })\n );\n\n return existingAndMissingRelations.filter((relation) => relation !== null);\n }\n\n return strapi.documents(attribute.target).findOne({\n documentId: versionRelationData.documentId,\n locale: versionRelationData.locale || undefined,\n });\n };\n\n /**\n * @description\n * Gets the value to set for a media asset when restoring a document\n * @returns\n * The media asset if it exists or null\n */\n const getMediaRestoreValue = async (\n versionRelationData: Modules.Documents.AnyDocument | Modules.Documents.AnyDocument[]\n ) => {\n if (Array.isArray(versionRelationData)) {\n const existingAndMissingMedias = await Promise.all(\n versionRelationData.map((media) => {\n return strapi.db.query('plugin::upload.file').findOne({ where: { id: media.id } });\n })\n );\n\n return existingAndMissingMedias.filter((media) => media != null);\n }\n\n return strapi.db\n .query('plugin::upload.file')\n .findOne({ where: { id: versionRelationData.id } });\n };\n\n const getDefaultLocale = async () => strapi.localization.getDefaultLocale();\n\n const isLocalizedContentType = (model: Schema.ContentType) =>\n strapi.localization.isLocalizedContentType(model);\n\n /**\n *\n * @description\n * Creates a dictionary of all locales available\n */\n const getLocaleDictionary = async (): Promise<{\n [key: string]: { name: string; code: string };\n }> => {\n const locales = await strapi.localization.getLocales();\n\n return locales.reduce<\n Record<string, NonNullable<HistoryVersions.HistoryVersionDataResponse['locale']>>\n >((acc, locale) => {\n acc[locale.code] = { name: locale.name, code: locale.code };\n\n return acc;\n }, {});\n };\n\n /**\n *\n * @description\n * Gets the number of retention days defined on the license or configured by the user\n */\n const getRetentionDays = () => {\n const featureConfig = strapi.ee.features.get('cms-content-history');\n\n return computeRetentionDays(\n typeof featureConfig === 'object' ? featureConfig?.options?.retentionDays : undefined,\n strapi.config.get('admin.history.retentionDays')\n );\n };\n\n const getVersionStatus = async (\n contentTypeUid: HistoryVersions.CreateHistoryVersion['contentType'],\n document: Modules.Documents.AnyDocument | null\n ) => {\n const documentMetadataService = strapi.plugin('content-manager').service('document-metadata');\n const meta = await documentMetadataService.getMetadata(contentTypeUid, document, {\n availableLocales: false,\n });\n\n return documentMetadataService.getStatus(document, meta.availableStatus);\n };\n\n /**\n * Lists all the fields of a component, excepts those that will be populated.\n * The goal is to exclude the ID, because when restoring a component,\n * referencing an ID for a component db row that was deleted creates an error.\n * So we never store component IDs to ensure they're re-created while restoring a version.\n */\n const getComponentFields = (componentUID: UID.Component): string[] => {\n return Object.entries(strapi.getModel(componentUID).attributes).reduce<string[]>(\n (fieldsAcc, [key, attribute]) => {\n if (!['relation', 'media', 'component', 'dynamiczone'].includes(attribute.type)) {\n fieldsAcc.push(key);\n }\n\n return fieldsAcc;\n },\n []\n );\n };\n\n /**\n * @description\n * Creates a populate object that looks for all the relations that need\n * to be saved in history, and populates only the fields needed to later retrieve the content.\n *\n * @param uid - The content type UID\n * @param useDatabaseSyntax - Whether to use the database syntax for populate, defaults to false\n */\n const getDeepPopulate = (uid: UID.Schema, useDatabaseSyntax = false) => {\n const model = strapi.getModel(uid);\n const attributes = Object.entries(model.attributes);\n const fieldSelector = useDatabaseSyntax ? 'select' : 'fields';\n\n return attributes.reduce((acc: any, [attributeName, attribute]) => {\n switch (attribute.type) {\n case 'relation': {\n // TODO: Support polymorphic relations\n const isMorphRelation = attribute.relation.toLowerCase().startsWith('morph');\n if (isMorphRelation) {\n break;\n }\n\n const isVisible = contentTypes.isVisibleAttribute(model, attributeName);\n if (isVisible) {\n acc[attributeName] = { [fieldSelector]: ['documentId', 'locale', 'publishedAt'] };\n }\n break;\n }\n\n case 'media': {\n acc[attributeName] = { [fieldSelector]: ['id'] };\n break;\n }\n\n case 'component': {\n const populate = getDeepPopulate(attribute.component);\n acc[attributeName] = {\n populate,\n [fieldSelector]: getComponentFields(attribute.component),\n };\n break;\n }\n\n case 'dynamiczone': {\n // Use fragments to populate the dynamic zone components\n const populatedComponents = (attribute.components || []).reduce(\n (acc: any, componentUID: UID.Component) => {\n acc[componentUID] = {\n populate: getDeepPopulate(componentUID),\n [fieldSelector]: getComponentFields(componentUID),\n };\n return acc;\n },\n {}\n );\n\n acc[attributeName] = { on: populatedComponents };\n break;\n }\n default:\n break;\n }\n\n return acc;\n }, {});\n };\n\n /**\n * @description\n * Builds a response object for relations containing the related data and a count of missing relations\n */\n const buildMediaResponse = async (values: { id: Data.ID }[]): Promise<RelationResponse> => {\n return (\n values\n // Until we implement proper pagination, limit relations to an arbitrary amount\n .slice(0, 25)\n .reduce(\n async (currentRelationDataPromise, entry) => {\n const currentRelationData = await currentRelationDataPromise;\n\n // Entry can be null if it's a toOne relation\n if (!entry) {\n return currentRelationData;\n }\n\n const relatedEntry = await strapi.db\n .query('plugin::upload.file')\n .findOne({ where: { id: entry.id } });\n\n if (relatedEntry) {\n currentRelationData.results.push(relatedEntry);\n } else {\n // The related content has been deleted\n currentRelationData.meta.missingCount += 1;\n }\n\n return currentRelationData;\n },\n Promise.resolve<RelationResponse>({\n results: [],\n meta: { missingCount: 0 },\n })\n )\n );\n };\n\n /**\n * @description\n * Builds a response object for media containing the media assets data and a count of missing media assets\n */\n const buildRelationReponse = async (\n values: {\n documentId: string;\n locale: string | null;\n }[],\n attributeSchema: Schema.Attribute.RelationWithTarget\n ): Promise<RelationResponse> => {\n return (\n values\n // Until we implement proper pagination, limit relations to an arbitrary amount\n .slice(0, 25)\n .reduce(\n async (currentRelationDataPromise, entry) => {\n const currentRelationData = await currentRelationDataPromise;\n\n // Entry can be null if it's a toOne relation\n if (!entry) {\n return currentRelationData;\n }\n const relatedEntry = await strapi\n .documents(attributeSchema.target)\n .findOne({ documentId: entry.documentId, locale: entry.locale || undefined });\n\n if (relatedEntry) {\n currentRelationData.results.push({\n ...relatedEntry,\n status: await getVersionStatus(attributeSchema.target, relatedEntry),\n });\n } else {\n // The related content has been deleted\n currentRelationData.meta.missingCount += 1;\n }\n\n return currentRelationData;\n },\n Promise.resolve<RelationResponse>({\n results: [],\n meta: { missingCount: 0 },\n })\n )\n );\n };\n\n return {\n getSchemaAttributesDiff,\n getRelationRestoreValue,\n getMediaRestoreValue,\n getDefaultLocale,\n isLocalizedContentType,\n getLocaleDictionary,\n getRetentionDays,\n getVersionStatus,\n getDeepPopulate,\n buildMediaResponse,\n buildRelationReponse,\n };\n};\n"],"names":["DEFAULT_RETENTION_DAYS","computeRetentionDays","licenseRetentionDays","userRetentionDays","licenseDays","Number","Math","min","createServiceUtils","strapi","getSchemaAttributesDiff","versionSchemaAttributes","contentTypeSchemaAttributes","sanitizedContentTypeSchemaAttributes","omit","FIELDS_TO_IGNORE","reduceDifferenceToAttributesObject","diffKeys","source","reduce","previousAttributesObject","diffKey","versionSchemaKeys","Object","keys","contentTypeSchemaAttributesKeys","uniqueToContentType","difference","added","uniqueToVersion","removed","getRelationRestoreValue","versionRelationData","attribute","Array","isArray","length","existingAndMissingRelations","Promise","all","map","relation","documents","target","findOne","documentId","locale","undefined","filter","getMediaRestoreValue","existingAndMissingMedias","media","db","query","where","id","getDefaultLocale","localization","isLocalizedContentType","model","getLocaleDictionary","locales","getLocales","acc","code","name","getRetentionDays","featureConfig","ee","features","get","options","retentionDays","config","getVersionStatus","contentTypeUid","document","documentMetadataService","plugin","service","meta","getMetadata","availableLocales","getStatus","availableStatus","getComponentFields","componentUID","entries","getModel","attributes","fieldsAcc","key","includes","type","push","getDeepPopulate","uid","useDatabaseSyntax","fieldSelector","attributeName","isMorphRelation","toLowerCase","startsWith","isVisible","contentTypes","isVisibleAttribute","populate","component","populatedComponents","components","on","buildMediaResponse","values","slice","currentRelationDataPromise","entry","currentRelationData","relatedEntry","results","missingCount","resolve","buildRelationReponse","attributeSchema","status"],"mappings":"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"}
@@ -47,10 +47,24 @@ const FALLBACK_MAIN_FIELD = 'documentId';
47
47
  }
48
48
  return FALLBACK_MAIN_FIELD;
49
49
  };
50
+ /**
51
+ * Formats the main field value as the recent document title.
52
+ * The main field can be any listable attribute (integer, boolean, date…), but the widgets render
53
+ * the title as text. An empty value falls back to the document id.
54
+ */ const formatRecentDocumentTitle = (value, documentId)=>{
55
+ if (value === null || value === undefined || value === '') {
56
+ return documentId;
57
+ }
58
+ if (value instanceof Date) {
59
+ return value.toISOString();
60
+ }
61
+ return String(value);
62
+ };
50
63
 
51
64
  exports.FALLBACK_MAIN_FIELD = FALLBACK_MAIN_FIELD;
52
65
  exports.buildHomepageQueryFields = buildHomepageQueryFields;
53
66
  exports.compactSanitizedFields = compactSanitizedFields;
67
+ exports.formatRecentDocumentTitle = formatRecentDocumentTitle;
54
68
  exports.resolveReadableMainField = resolveReadableMainField;
55
69
  exports.resolveTitleField = resolveTitleField;
56
70
  //# sourceMappingURL=homepage-query-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"homepage-query-utils.js","sources":["../../../../server/src/homepage/services/homepage-query-utils.ts"],"sourcesContent":["import type { Schema } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\nimport { getDefaultMainField } from '../../services/utils/configuration/attributes';\n\nexport const FALLBACK_MAIN_FIELD = 'documentId';\n\nexport type HomepagePermissionChecker = {\n cannot: {\n read: (entity: null, field: string) => boolean;\n };\n};\n\n/**\n * Removes invalid entries left in the fields array after permission sanitization.\n */\nexport const compactSanitizedFields = (fields: unknown): string[] | undefined => {\n if (!Array.isArray(fields)) {\n return undefined;\n }\n\n return fields.filter((field): field is string => typeof field === 'string');\n};\n\n/**\n * Resolves the main field used for homepage widgets, falling back when the user cannot read it.\n */\nexport const resolveReadableMainField = (\n contentType: Schema.ContentType,\n configuration: { settings?: { mainField?: string } } | undefined,\n permissionChecker: HomepagePermissionChecker\n): string => {\n const candidateMainField = configuration?.settings?.mainField ?? getDefaultMainField(contentType);\n\n if (permissionChecker.cannot.read(null, candidateMainField)) {\n return FALLBACK_MAIN_FIELD;\n }\n\n return candidateMainField;\n};\n\n/**\n * Builds the fields array requested before permission sanitization.\n */\nexport const buildHomepageQueryFields = (\n contentType: Schema.ContentType,\n mainField: string\n): string[] => {\n const fields = [FALLBACK_MAIN_FIELD, 'updatedAt'];\n\n if (contentTypes.hasDraftAndPublish(contentType)) {\n fields.push('publishedAt');\n }\n\n if (mainField !== FALLBACK_MAIN_FIELD && !fields.includes(mainField)) {\n fields.push(mainField);\n }\n\n if ((contentType.pluginOptions?.i18n as { localized?: boolean } | undefined)?.localized) {\n fields.push('locale');\n }\n\n return fields;\n};\n\n/**\n * Picks a main field that is present in the sanitized fields selection.\n */\nexport const resolveTitleField = (\n mainField: string,\n sanitizedFields: string[] | undefined\n): string => {\n if (sanitizedFields?.includes(mainField)) {\n return mainField;\n }\n\n return FALLBACK_MAIN_FIELD;\n};\n"],"names":["FALLBACK_MAIN_FIELD","compactSanitizedFields","fields","Array","isArray","undefined","filter","field","resolveReadableMainField","contentType","configuration","permissionChecker","candidateMainField","settings","mainField","getDefaultMainField","cannot","read","buildHomepageQueryFields","contentTypes","hasDraftAndPublish","push","includes","pluginOptions","i18n","localized","resolveTitleField","sanitizedFields"],"mappings":";;;;;AAKO,MAAMA,sBAAsB;AAQnC;;IAGO,MAAMC,sBAAAA,GAAyB,CAACC,MAAAA,GAAAA;AACrC,IAAA,IAAI,CAACC,KAAAA,CAAMC,OAAO,CAACF,MAAAA,CAAAA,EAAS;QAC1B,OAAOG,SAAAA;AACT,IAAA;AAEA,IAAA,OAAOH,OAAOI,MAAM,CAAC,CAACC,KAAAA,GAA2B,OAAOA,KAAAA,KAAU,QAAA,CAAA;AACpE;AAEA;;AAEC,IACM,MAAMC,wBAAAA,GAA2B,CACtCC,aACAC,aAAAA,EACAC,iBAAAA,GAAAA;AAEA,IAAA,MAAMC,kBAAAA,GAAqBF,aAAAA,EAAeG,QAAAA,EAAUC,SAAAA,IAAaC,8BAAAA,CAAoBN,WAAAA,CAAAA;AAErF,IAAA,IAAIE,kBAAkBK,MAAM,CAACC,IAAI,CAAC,MAAML,kBAAAA,CAAAA,EAAqB;QAC3D,OAAOZ,mBAAAA;AACT,IAAA;IAEA,OAAOY,kBAAAA;AACT;AAEA;;AAEC,IACM,MAAMM,wBAAAA,GAA2B,CACtCT,WAAAA,EACAK,SAAAA,GAAAA;AAEA,IAAA,MAAMZ,MAAAA,GAAS;AAACF,QAAAA,mBAAAA;AAAqB,QAAA;AAAY,KAAA;IAEjD,IAAImB,wBAAAA,CAAaC,kBAAkB,CAACX,WAAAA,CAAAA,EAAc;AAChDP,QAAAA,MAAAA,CAAOmB,IAAI,CAAC,aAAA,CAAA;AACd,IAAA;AAEA,IAAA,IAAIP,cAAcd,mBAAAA,IAAuB,CAACE,MAAAA,CAAOoB,QAAQ,CAACR,SAAAA,CAAAA,EAAY;AACpEZ,QAAAA,MAAAA,CAAOmB,IAAI,CAACP,SAAAA,CAAAA;AACd,IAAA;AAEA,IAAA,IAAKL,WAAAA,CAAYc,aAAa,EAAEC,IAAAA,EAA8CC,SAAAA,EAAW;AACvFvB,QAAAA,MAAAA,CAAOmB,IAAI,CAAC,QAAA,CAAA;AACd,IAAA;IAEA,OAAOnB,MAAAA;AACT;AAEA;;AAEC,IACM,MAAMwB,iBAAAA,GAAoB,CAC/BZ,SAAAA,EACAa,eAAAA,GAAAA;IAEA,IAAIA,eAAAA,EAAiBL,SAASR,SAAAA,CAAAA,EAAY;QACxC,OAAOA,SAAAA;AACT,IAAA;IAEA,OAAOd,mBAAAA;AACT;;;;;;;;"}
1
+ {"version":3,"file":"homepage-query-utils.js","sources":["../../../../server/src/homepage/services/homepage-query-utils.ts"],"sourcesContent":["import type { Schema } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\nimport { getDefaultMainField } from '../../services/utils/configuration/attributes';\n\nexport const FALLBACK_MAIN_FIELD = 'documentId';\n\nexport type HomepagePermissionChecker = {\n cannot: {\n read: (entity: null, field: string) => boolean;\n };\n};\n\n/**\n * Removes invalid entries left in the fields array after permission sanitization.\n */\nexport const compactSanitizedFields = (fields: unknown): string[] | undefined => {\n if (!Array.isArray(fields)) {\n return undefined;\n }\n\n return fields.filter((field): field is string => typeof field === 'string');\n};\n\n/**\n * Resolves the main field used for homepage widgets, falling back when the user cannot read it.\n */\nexport const resolveReadableMainField = (\n contentType: Schema.ContentType,\n configuration: { settings?: { mainField?: string } } | undefined,\n permissionChecker: HomepagePermissionChecker\n): string => {\n const candidateMainField = configuration?.settings?.mainField ?? getDefaultMainField(contentType);\n\n if (permissionChecker.cannot.read(null, candidateMainField)) {\n return FALLBACK_MAIN_FIELD;\n }\n\n return candidateMainField;\n};\n\n/**\n * Builds the fields array requested before permission sanitization.\n */\nexport const buildHomepageQueryFields = (\n contentType: Schema.ContentType,\n mainField: string\n): string[] => {\n const fields = [FALLBACK_MAIN_FIELD, 'updatedAt'];\n\n if (contentTypes.hasDraftAndPublish(contentType)) {\n fields.push('publishedAt');\n }\n\n if (mainField !== FALLBACK_MAIN_FIELD && !fields.includes(mainField)) {\n fields.push(mainField);\n }\n\n if ((contentType.pluginOptions?.i18n as { localized?: boolean } | undefined)?.localized) {\n fields.push('locale');\n }\n\n return fields;\n};\n\n/**\n * Picks a main field that is present in the sanitized fields selection.\n */\nexport const resolveTitleField = (\n mainField: string,\n sanitizedFields: string[] | undefined\n): string => {\n if (sanitizedFields?.includes(mainField)) {\n return mainField;\n }\n\n return FALLBACK_MAIN_FIELD;\n};\n\n/**\n * Formats the main field value as the recent document title.\n * The main field can be any listable attribute (integer, boolean, date…), but the widgets render\n * the title as text. An empty value falls back to the document id.\n */\nexport const formatRecentDocumentTitle = (value: unknown, documentId: string): string => {\n if (value === null || value === undefined || value === '') {\n return documentId;\n }\n\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n return String(value);\n};\n"],"names":["FALLBACK_MAIN_FIELD","compactSanitizedFields","fields","Array","isArray","undefined","filter","field","resolveReadableMainField","contentType","configuration","permissionChecker","candidateMainField","settings","mainField","getDefaultMainField","cannot","read","buildHomepageQueryFields","contentTypes","hasDraftAndPublish","push","includes","pluginOptions","i18n","localized","resolveTitleField","sanitizedFields","formatRecentDocumentTitle","value","documentId","Date","toISOString","String"],"mappings":";;;;;AAKO,MAAMA,sBAAsB;AAQnC;;IAGO,MAAMC,sBAAAA,GAAyB,CAACC,MAAAA,GAAAA;AACrC,IAAA,IAAI,CAACC,KAAAA,CAAMC,OAAO,CAACF,MAAAA,CAAAA,EAAS;QAC1B,OAAOG,SAAAA;AACT,IAAA;AAEA,IAAA,OAAOH,OAAOI,MAAM,CAAC,CAACC,KAAAA,GAA2B,OAAOA,KAAAA,KAAU,QAAA,CAAA;AACpE;AAEA;;AAEC,IACM,MAAMC,wBAAAA,GAA2B,CACtCC,aACAC,aAAAA,EACAC,iBAAAA,GAAAA;AAEA,IAAA,MAAMC,kBAAAA,GAAqBF,aAAAA,EAAeG,QAAAA,EAAUC,SAAAA,IAAaC,8BAAAA,CAAoBN,WAAAA,CAAAA;AAErF,IAAA,IAAIE,kBAAkBK,MAAM,CAACC,IAAI,CAAC,MAAML,kBAAAA,CAAAA,EAAqB;QAC3D,OAAOZ,mBAAAA;AACT,IAAA;IAEA,OAAOY,kBAAAA;AACT;AAEA;;AAEC,IACM,MAAMM,wBAAAA,GAA2B,CACtCT,WAAAA,EACAK,SAAAA,GAAAA;AAEA,IAAA,MAAMZ,MAAAA,GAAS;AAACF,QAAAA,mBAAAA;AAAqB,QAAA;AAAY,KAAA;IAEjD,IAAImB,wBAAAA,CAAaC,kBAAkB,CAACX,WAAAA,CAAAA,EAAc;AAChDP,QAAAA,MAAAA,CAAOmB,IAAI,CAAC,aAAA,CAAA;AACd,IAAA;AAEA,IAAA,IAAIP,cAAcd,mBAAAA,IAAuB,CAACE,MAAAA,CAAOoB,QAAQ,CAACR,SAAAA,CAAAA,EAAY;AACpEZ,QAAAA,MAAAA,CAAOmB,IAAI,CAACP,SAAAA,CAAAA;AACd,IAAA;AAEA,IAAA,IAAKL,WAAAA,CAAYc,aAAa,EAAEC,IAAAA,EAA8CC,SAAAA,EAAW;AACvFvB,QAAAA,MAAAA,CAAOmB,IAAI,CAAC,QAAA,CAAA;AACd,IAAA;IAEA,OAAOnB,MAAAA;AACT;AAEA;;AAEC,IACM,MAAMwB,iBAAAA,GAAoB,CAC/BZ,SAAAA,EACAa,eAAAA,GAAAA;IAEA,IAAIA,eAAAA,EAAiBL,SAASR,SAAAA,CAAAA,EAAY;QACxC,OAAOA,SAAAA;AACT,IAAA;IAEA,OAAOd,mBAAAA;AACT;AAEA;;;;AAIC,IACM,MAAM4B,yBAAAA,GAA4B,CAACC,KAAAA,EAAgBC,UAAAA,GAAAA;AACxD,IAAA,IAAID,KAAAA,KAAU,IAAA,IAAQA,KAAAA,KAAUxB,SAAAA,IAAawB,UAAU,EAAA,EAAI;QACzD,OAAOC,UAAAA;AACT,IAAA;AAEA,IAAA,IAAID,iBAAiBE,IAAAA,EAAM;AACzB,QAAA,OAAOF,MAAMG,WAAW,EAAA;AAC1B,IAAA;AAEA,IAAA,OAAOC,MAAAA,CAAOJ,KAAAA,CAAAA;AAChB;;;;;;;;;"}
@@ -45,6 +45,19 @@ const FALLBACK_MAIN_FIELD = 'documentId';
45
45
  }
46
46
  return FALLBACK_MAIN_FIELD;
47
47
  };
48
+ /**
49
+ * Formats the main field value as the recent document title.
50
+ * The main field can be any listable attribute (integer, boolean, date…), but the widgets render
51
+ * the title as text. An empty value falls back to the document id.
52
+ */ const formatRecentDocumentTitle = (value, documentId)=>{
53
+ if (value === null || value === undefined || value === '') {
54
+ return documentId;
55
+ }
56
+ if (value instanceof Date) {
57
+ return value.toISOString();
58
+ }
59
+ return String(value);
60
+ };
48
61
 
49
- export { FALLBACK_MAIN_FIELD, buildHomepageQueryFields, compactSanitizedFields, resolveReadableMainField, resolveTitleField };
62
+ export { FALLBACK_MAIN_FIELD, buildHomepageQueryFields, compactSanitizedFields, formatRecentDocumentTitle, resolveReadableMainField, resolveTitleField };
50
63
  //# sourceMappingURL=homepage-query-utils.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"homepage-query-utils.mjs","sources":["../../../../server/src/homepage/services/homepage-query-utils.ts"],"sourcesContent":["import type { Schema } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\nimport { getDefaultMainField } from '../../services/utils/configuration/attributes';\n\nexport const FALLBACK_MAIN_FIELD = 'documentId';\n\nexport type HomepagePermissionChecker = {\n cannot: {\n read: (entity: null, field: string) => boolean;\n };\n};\n\n/**\n * Removes invalid entries left in the fields array after permission sanitization.\n */\nexport const compactSanitizedFields = (fields: unknown): string[] | undefined => {\n if (!Array.isArray(fields)) {\n return undefined;\n }\n\n return fields.filter((field): field is string => typeof field === 'string');\n};\n\n/**\n * Resolves the main field used for homepage widgets, falling back when the user cannot read it.\n */\nexport const resolveReadableMainField = (\n contentType: Schema.ContentType,\n configuration: { settings?: { mainField?: string } } | undefined,\n permissionChecker: HomepagePermissionChecker\n): string => {\n const candidateMainField = configuration?.settings?.mainField ?? getDefaultMainField(contentType);\n\n if (permissionChecker.cannot.read(null, candidateMainField)) {\n return FALLBACK_MAIN_FIELD;\n }\n\n return candidateMainField;\n};\n\n/**\n * Builds the fields array requested before permission sanitization.\n */\nexport const buildHomepageQueryFields = (\n contentType: Schema.ContentType,\n mainField: string\n): string[] => {\n const fields = [FALLBACK_MAIN_FIELD, 'updatedAt'];\n\n if (contentTypes.hasDraftAndPublish(contentType)) {\n fields.push('publishedAt');\n }\n\n if (mainField !== FALLBACK_MAIN_FIELD && !fields.includes(mainField)) {\n fields.push(mainField);\n }\n\n if ((contentType.pluginOptions?.i18n as { localized?: boolean } | undefined)?.localized) {\n fields.push('locale');\n }\n\n return fields;\n};\n\n/**\n * Picks a main field that is present in the sanitized fields selection.\n */\nexport const resolveTitleField = (\n mainField: string,\n sanitizedFields: string[] | undefined\n): string => {\n if (sanitizedFields?.includes(mainField)) {\n return mainField;\n }\n\n return FALLBACK_MAIN_FIELD;\n};\n"],"names":["FALLBACK_MAIN_FIELD","compactSanitizedFields","fields","Array","isArray","undefined","filter","field","resolveReadableMainField","contentType","configuration","permissionChecker","candidateMainField","settings","mainField","getDefaultMainField","cannot","read","buildHomepageQueryFields","contentTypes","hasDraftAndPublish","push","includes","pluginOptions","i18n","localized","resolveTitleField","sanitizedFields"],"mappings":";;;AAKO,MAAMA,sBAAsB;AAQnC;;IAGO,MAAMC,sBAAAA,GAAyB,CAACC,MAAAA,GAAAA;AACrC,IAAA,IAAI,CAACC,KAAAA,CAAMC,OAAO,CAACF,MAAAA,CAAAA,EAAS;QAC1B,OAAOG,SAAAA;AACT,IAAA;AAEA,IAAA,OAAOH,OAAOI,MAAM,CAAC,CAACC,KAAAA,GAA2B,OAAOA,KAAAA,KAAU,QAAA,CAAA;AACpE;AAEA;;AAEC,IACM,MAAMC,wBAAAA,GAA2B,CACtCC,aACAC,aAAAA,EACAC,iBAAAA,GAAAA;AAEA,IAAA,MAAMC,kBAAAA,GAAqBF,aAAAA,EAAeG,QAAAA,EAAUC,SAAAA,IAAaC,mBAAAA,CAAoBN,WAAAA,CAAAA;AAErF,IAAA,IAAIE,kBAAkBK,MAAM,CAACC,IAAI,CAAC,MAAML,kBAAAA,CAAAA,EAAqB;QAC3D,OAAOZ,mBAAAA;AACT,IAAA;IAEA,OAAOY,kBAAAA;AACT;AAEA;;AAEC,IACM,MAAMM,wBAAAA,GAA2B,CACtCT,WAAAA,EACAK,SAAAA,GAAAA;AAEA,IAAA,MAAMZ,MAAAA,GAAS;AAACF,QAAAA,mBAAAA;AAAqB,QAAA;AAAY,KAAA;IAEjD,IAAImB,YAAAA,CAAaC,kBAAkB,CAACX,WAAAA,CAAAA,EAAc;AAChDP,QAAAA,MAAAA,CAAOmB,IAAI,CAAC,aAAA,CAAA;AACd,IAAA;AAEA,IAAA,IAAIP,cAAcd,mBAAAA,IAAuB,CAACE,MAAAA,CAAOoB,QAAQ,CAACR,SAAAA,CAAAA,EAAY;AACpEZ,QAAAA,MAAAA,CAAOmB,IAAI,CAACP,SAAAA,CAAAA;AACd,IAAA;AAEA,IAAA,IAAKL,WAAAA,CAAYc,aAAa,EAAEC,IAAAA,EAA8CC,SAAAA,EAAW;AACvFvB,QAAAA,MAAAA,CAAOmB,IAAI,CAAC,QAAA,CAAA;AACd,IAAA;IAEA,OAAOnB,MAAAA;AACT;AAEA;;AAEC,IACM,MAAMwB,iBAAAA,GAAoB,CAC/BZ,SAAAA,EACAa,eAAAA,GAAAA;IAEA,IAAIA,eAAAA,EAAiBL,SAASR,SAAAA,CAAAA,EAAY;QACxC,OAAOA,SAAAA;AACT,IAAA;IAEA,OAAOd,mBAAAA;AACT;;;;"}
1
+ {"version":3,"file":"homepage-query-utils.mjs","sources":["../../../../server/src/homepage/services/homepage-query-utils.ts"],"sourcesContent":["import type { Schema } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\nimport { getDefaultMainField } from '../../services/utils/configuration/attributes';\n\nexport const FALLBACK_MAIN_FIELD = 'documentId';\n\nexport type HomepagePermissionChecker = {\n cannot: {\n read: (entity: null, field: string) => boolean;\n };\n};\n\n/**\n * Removes invalid entries left in the fields array after permission sanitization.\n */\nexport const compactSanitizedFields = (fields: unknown): string[] | undefined => {\n if (!Array.isArray(fields)) {\n return undefined;\n }\n\n return fields.filter((field): field is string => typeof field === 'string');\n};\n\n/**\n * Resolves the main field used for homepage widgets, falling back when the user cannot read it.\n */\nexport const resolveReadableMainField = (\n contentType: Schema.ContentType,\n configuration: { settings?: { mainField?: string } } | undefined,\n permissionChecker: HomepagePermissionChecker\n): string => {\n const candidateMainField = configuration?.settings?.mainField ?? getDefaultMainField(contentType);\n\n if (permissionChecker.cannot.read(null, candidateMainField)) {\n return FALLBACK_MAIN_FIELD;\n }\n\n return candidateMainField;\n};\n\n/**\n * Builds the fields array requested before permission sanitization.\n */\nexport const buildHomepageQueryFields = (\n contentType: Schema.ContentType,\n mainField: string\n): string[] => {\n const fields = [FALLBACK_MAIN_FIELD, 'updatedAt'];\n\n if (contentTypes.hasDraftAndPublish(contentType)) {\n fields.push('publishedAt');\n }\n\n if (mainField !== FALLBACK_MAIN_FIELD && !fields.includes(mainField)) {\n fields.push(mainField);\n }\n\n if ((contentType.pluginOptions?.i18n as { localized?: boolean } | undefined)?.localized) {\n fields.push('locale');\n }\n\n return fields;\n};\n\n/**\n * Picks a main field that is present in the sanitized fields selection.\n */\nexport const resolveTitleField = (\n mainField: string,\n sanitizedFields: string[] | undefined\n): string => {\n if (sanitizedFields?.includes(mainField)) {\n return mainField;\n }\n\n return FALLBACK_MAIN_FIELD;\n};\n\n/**\n * Formats the main field value as the recent document title.\n * The main field can be any listable attribute (integer, boolean, date…), but the widgets render\n * the title as text. An empty value falls back to the document id.\n */\nexport const formatRecentDocumentTitle = (value: unknown, documentId: string): string => {\n if (value === null || value === undefined || value === '') {\n return documentId;\n }\n\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n return String(value);\n};\n"],"names":["FALLBACK_MAIN_FIELD","compactSanitizedFields","fields","Array","isArray","undefined","filter","field","resolveReadableMainField","contentType","configuration","permissionChecker","candidateMainField","settings","mainField","getDefaultMainField","cannot","read","buildHomepageQueryFields","contentTypes","hasDraftAndPublish","push","includes","pluginOptions","i18n","localized","resolveTitleField","sanitizedFields","formatRecentDocumentTitle","value","documentId","Date","toISOString","String"],"mappings":";;;AAKO,MAAMA,sBAAsB;AAQnC;;IAGO,MAAMC,sBAAAA,GAAyB,CAACC,MAAAA,GAAAA;AACrC,IAAA,IAAI,CAACC,KAAAA,CAAMC,OAAO,CAACF,MAAAA,CAAAA,EAAS;QAC1B,OAAOG,SAAAA;AACT,IAAA;AAEA,IAAA,OAAOH,OAAOI,MAAM,CAAC,CAACC,KAAAA,GAA2B,OAAOA,KAAAA,KAAU,QAAA,CAAA;AACpE;AAEA;;AAEC,IACM,MAAMC,wBAAAA,GAA2B,CACtCC,aACAC,aAAAA,EACAC,iBAAAA,GAAAA;AAEA,IAAA,MAAMC,kBAAAA,GAAqBF,aAAAA,EAAeG,QAAAA,EAAUC,SAAAA,IAAaC,mBAAAA,CAAoBN,WAAAA,CAAAA;AAErF,IAAA,IAAIE,kBAAkBK,MAAM,CAACC,IAAI,CAAC,MAAML,kBAAAA,CAAAA,EAAqB;QAC3D,OAAOZ,mBAAAA;AACT,IAAA;IAEA,OAAOY,kBAAAA;AACT;AAEA;;AAEC,IACM,MAAMM,wBAAAA,GAA2B,CACtCT,WAAAA,EACAK,SAAAA,GAAAA;AAEA,IAAA,MAAMZ,MAAAA,GAAS;AAACF,QAAAA,mBAAAA;AAAqB,QAAA;AAAY,KAAA;IAEjD,IAAImB,YAAAA,CAAaC,kBAAkB,CAACX,WAAAA,CAAAA,EAAc;AAChDP,QAAAA,MAAAA,CAAOmB,IAAI,CAAC,aAAA,CAAA;AACd,IAAA;AAEA,IAAA,IAAIP,cAAcd,mBAAAA,IAAuB,CAACE,MAAAA,CAAOoB,QAAQ,CAACR,SAAAA,CAAAA,EAAY;AACpEZ,QAAAA,MAAAA,CAAOmB,IAAI,CAACP,SAAAA,CAAAA;AACd,IAAA;AAEA,IAAA,IAAKL,WAAAA,CAAYc,aAAa,EAAEC,IAAAA,EAA8CC,SAAAA,EAAW;AACvFvB,QAAAA,MAAAA,CAAOmB,IAAI,CAAC,QAAA,CAAA;AACd,IAAA;IAEA,OAAOnB,MAAAA;AACT;AAEA;;AAEC,IACM,MAAMwB,iBAAAA,GAAoB,CAC/BZ,SAAAA,EACAa,eAAAA,GAAAA;IAEA,IAAIA,eAAAA,EAAiBL,SAASR,SAAAA,CAAAA,EAAY;QACxC,OAAOA,SAAAA;AACT,IAAA;IAEA,OAAOd,mBAAAA;AACT;AAEA;;;;AAIC,IACM,MAAM4B,yBAAAA,GAA4B,CAACC,KAAAA,EAAgBC,UAAAA,GAAAA;AACxD,IAAA,IAAID,KAAAA,KAAU,IAAA,IAAQA,KAAAA,KAAUxB,SAAAA,IAAawB,UAAU,EAAA,EAAI;QACzD,OAAOC,UAAAA;AACT,IAAA;AAEA,IAAA,IAAID,iBAAiBE,IAAAA,EAAM;AACzB,QAAA,OAAOF,MAAMG,WAAW,EAAA;AAC1B,IAAA;AAEA,IAAA,OAAOC,MAAAA,CAAOJ,KAAAA,CAAAA;AAChB;;;;"}
@@ -100,7 +100,7 @@ const createHomepageService = ({ strapi })=>{
100
100
  return {
101
101
  documentId: document.documentId,
102
102
  locale: document.locale ?? null,
103
- title: document[meta.mainField ?? 'documentId'],
103
+ title: homepageQueryUtils.formatRecentDocumentTitle(document[meta.mainField ?? 'documentId'], document.documentId),
104
104
  contentTypeUid: meta.uid,
105
105
  contentTypeDisplayName: meta.contentType.info.displayName,
106
106
  kind: meta.contentType.kind,
@@ -1 +1 @@
1
- {"version":3,"file":"homepage.js","sources":["../../../../server/src/homepage/services/homepage.ts"],"sourcesContent":["/* eslint-disable func-names */\nimport type { Core, Modules, Schema } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\nimport type {\n GetCountDocuments,\n GetRecentDocuments,\n RecentDocument,\n} from '../../../../shared/contracts/homepage';\n\nimport {\n buildHomepageQueryFields,\n compactSanitizedFields,\n resolveReadableMainField,\n resolveTitleField,\n} from './homepage-query-utils';\n\nconst createHomepageService = ({ strapi }: { strapi: Core.Strapi }) => {\n const MAX_DOCUMENTS = 4;\n\n const metadataService = strapi.plugin('content-manager').service('document-metadata');\n const permissionService = strapi.admin.services.permission;\n\n const getRegisteredContentType = (uid: RecentDocument['contentTypeUid']) => {\n const contentType = strapi.contentTypes[uid];\n\n if (contentType === undefined) {\n strapi.log.warn(\n `Skipping homepage content type \"${uid}\" because it is no longer registered.`\n );\n return undefined;\n }\n\n return contentType;\n };\n\n type ContentTypeConfiguration = {\n uid: RecentDocument['contentTypeUid'];\n settings: { mainField: string };\n };\n const getConfiguration = async (\n contentTypeUids: RecentDocument['contentTypeUid'][]\n ): Promise<ContentTypeConfiguration[]> => {\n /**\n * Don't use the strapi.store util because we need to make\n * more precise queries than exact key matches, in order to make as few queries as possible.\n */\n const coreStore = strapi.db.query('strapi::core-store');\n const rawConfigurations = await coreStore.findMany({\n where: {\n key: {\n $in: contentTypeUids.map(\n (contentType) => `plugin_content_manager_configuration_content_types::${contentType}`\n ),\n },\n },\n });\n\n return rawConfigurations.map((rawConfiguration) => {\n return JSON.parse(rawConfiguration.value);\n });\n };\n\n const getPermittedContentTypes = async () => {\n const readPermissions: Modules.Permissions.PermissionRule[] = await permissionService.findMany({\n where: {\n role: { users: { id: strapi.requestContext.get()?.state?.user.id } },\n action: 'plugin::content-manager.explorer.read',\n },\n });\n\n // Deduplicate subjects using a Set: the JOIN across permission -> role -> users produces one row\n // per role the user belongs to, so a multi-role user gets duplicate subjects.\n // Using a Set collapses them to unique content type UID.\n return [\n ...new Set(\n readPermissions\n .map((permission) => permission.subject)\n .filter((subject): subject is RecentDocument['contentTypeUid'] => {\n if (!subject) {\n return false;\n }\n\n const contentType = strapi.contentTypes[subject as keyof typeof strapi.contentTypes];\n const contentTypeOptions = contentType?.pluginOptions?.['content-manager'] as\n { visible?: boolean } | undefined;\n\n return contentTypeOptions?.visible !== false;\n })\n ),\n ];\n };\n\n type ContentTypeMeta = {\n fields: string[];\n mainField: string;\n contentType: Schema.ContentType;\n hasDraftAndPublish: boolean;\n uid: RecentDocument['contentTypeUid'];\n };\n\n const permissionCheckerService = strapi.plugin('content-manager').service('permission-checker');\n const getPermissionChecker = (uid: string) =>\n permissionCheckerService.create({\n userAbility: strapi.requestContext.get()?.state.userAbility,\n model: uid,\n });\n\n const getContentTypesMeta = (\n allowedContentTypeUids: RecentDocument['contentTypeUid'][],\n configurations: ContentTypeConfiguration[]\n ): ContentTypeMeta[] => {\n return allowedContentTypeUids.reduce<ContentTypeMeta[]>((acc, uid) => {\n const configuration = configurations.find((config) => config.uid === uid);\n const contentType = getRegisteredContentType(uid);\n\n if (contentType === undefined) {\n return acc;\n }\n\n const mainField = resolveReadableMainField(\n contentType,\n configuration,\n getPermissionChecker(uid)\n );\n const fields = buildHomepageQueryFields(contentType, mainField);\n const hasDraftAndPublish = contentTypes.hasDraftAndPublish(contentType);\n\n acc.push({\n fields,\n mainField,\n contentType,\n hasDraftAndPublish,\n uid,\n });\n\n return acc;\n }, []);\n };\n\n /**\n * Homepage widgets expect JSON-safe ISO strings. Returning `Date` objects can\n * serialize as `{}` after spread/clone (see https://github.com/strapi/strapi/issues/27013).\n */\n const toIsoDateString = (value: unknown): string | null => {\n if (value == null || value === '') {\n return null;\n }\n\n const date = value instanceof Date ? value : new Date(String(value));\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n };\n\n const formatDocuments = (\n documents: Modules.Documents.AnyDocument[],\n meta: ContentTypeMeta,\n populate?: string[]\n ) => {\n return documents.map((document) => {\n const additionalFields =\n populate?.reduce(\n (acc, key) => {\n acc[key] = document[key];\n return acc;\n },\n {} as Record<string, any>\n ) || {};\n return {\n documentId: document.documentId,\n locale: document.locale ?? null,\n title: document[meta.mainField ?? 'documentId'],\n contentTypeUid: meta.uid,\n contentTypeDisplayName: meta.contentType.info.displayName,\n kind: meta.contentType.kind,\n ...additionalFields,\n // Keep dates last so populate cannot overwrite with non-JSON-safe values\n updatedAt: toIsoDateString(document.updatedAt) ?? '',\n publishedAt:\n meta.hasDraftAndPublish && document.publishedAt\n ? toIsoDateString(document.publishedAt)\n : null,\n };\n });\n };\n\n const sanitizeHomepageQuery = async (\n meta: ContentTypeMeta,\n additionalQueryParams?: Record<string, unknown>\n ) => {\n const permissionQuery = await getPermissionChecker(meta.uid).sanitizedQuery.read({\n limit: MAX_DOCUMENTS,\n fields: meta.fields,\n ...additionalQueryParams,\n locale: '*',\n });\n\n const sanitizedFields = compactSanitizedFields(permissionQuery.fields);\n if (sanitizedFields !== undefined) {\n permissionQuery.fields = sanitizedFields;\n }\n\n return {\n permissionQuery,\n titleField: resolveTitleField(meta.mainField, sanitizedFields),\n };\n };\n\n return {\n async addStatusToDocuments(documents: RecentDocument[]): Promise<RecentDocument[]> {\n return Promise.all(\n documents.map(async (recentDocument) => {\n const hasDraftAndPublish = contentTypes.hasDraftAndPublish(\n strapi.contentType(recentDocument.contentTypeUid)\n );\n /**\n * Tries to query the other version of the document if draft and publish is enabled,\n * so that we know when to give the \"modified\" status.\n */\n const { availableStatus } = await metadataService.getMetadata(\n recentDocument.contentTypeUid,\n recentDocument,\n {\n availableStatus: hasDraftAndPublish,\n availableLocales: false,\n }\n );\n const status: RecentDocument['status'] = metadataService.getStatus(\n recentDocument,\n availableStatus\n );\n\n return {\n ...recentDocument,\n status: hasDraftAndPublish ? status : undefined,\n };\n })\n );\n },\n\n async queryLastDocuments(\n additionalQueryParams?: Record<string, unknown>,\n draftAndPublishOnly?: boolean\n ): Promise<RecentDocument[]> {\n const permittedContentTypes = await getPermittedContentTypes();\n const allowedContentTypeUids = draftAndPublishOnly\n ? permittedContentTypes.filter((uid) => {\n const contentType = getRegisteredContentType(uid);\n\n return contentType !== undefined && contentTypes.hasDraftAndPublish(contentType);\n })\n : permittedContentTypes;\n // Fetch the configuration for each content type in a single query\n const configurations = await getConfiguration(allowedContentTypeUids);\n // Get the necessary metadata for the documents\n const contentTypesMeta = getContentTypesMeta(allowedContentTypeUids, configurations);\n\n const recentDocuments = await Promise.all(\n contentTypesMeta.map(async (meta) => {\n const { permissionQuery, titleField } = await sanitizeHomepageQuery(\n meta,\n additionalQueryParams\n );\n\n const docs = await strapi.documents(meta.uid).findMany(permissionQuery);\n const populate = additionalQueryParams?.populate as string[];\n\n return formatDocuments(docs, { ...meta, mainField: titleField }, populate);\n })\n );\n\n return recentDocuments\n .flat()\n .sort((a, b) => {\n // ISO-8601 strings compare lexicographically in chronological order\n const compareIso = (left: string, right: string, direction: 1 | -1) => {\n if (left < right) return -1 * direction;\n if (left > right) return 1 * direction;\n return 0;\n };\n\n switch (additionalQueryParams?.sort) {\n case 'publishedAt:desc':\n if (!a.publishedAt || !b.publishedAt) return 0;\n return compareIso(a.publishedAt, b.publishedAt, -1);\n case 'publishedAt:asc':\n if (!a.publishedAt || !b.publishedAt) return 0;\n return compareIso(a.publishedAt, b.publishedAt, 1);\n case 'updatedAt:desc':\n if (!a.updatedAt || !b.updatedAt) return 0;\n return compareIso(a.updatedAt, b.updatedAt, -1);\n case 'updatedAt:asc':\n if (!a.updatedAt || !b.updatedAt) return 0;\n return compareIso(a.updatedAt, b.updatedAt, 1);\n default:\n return 0;\n }\n })\n .slice(0, MAX_DOCUMENTS);\n },\n\n async getRecentlyPublishedDocuments(): Promise<GetRecentDocuments.Response['data']> {\n const recentlyPublishedDocuments = await this.queryLastDocuments(\n {\n sort: 'publishedAt:desc',\n status: 'published',\n },\n true\n );\n\n return this.addStatusToDocuments(recentlyPublishedDocuments);\n },\n\n async getRecentlyUpdatedDocuments(): Promise<GetRecentDocuments.Response['data']> {\n const recentlyUpdatedDocuments = await this.queryLastDocuments({\n sort: 'updatedAt:desc',\n });\n\n return this.addStatusToDocuments(recentlyUpdatedDocuments);\n },\n\n async getCountDocuments(): Promise<GetCountDocuments.Response['data']> {\n const permittedContentTypes = await getPermittedContentTypes();\n // Fetch the configuration for each content type in a single query\n const configurations = await getConfiguration(permittedContentTypes);\n // Get the necessary metadata for the documents\n const contentTypesMeta = getContentTypesMeta(permittedContentTypes, configurations);\n\n const countDocuments = {\n draft: 0,\n published: 0,\n modified: 0,\n };\n\n await Promise.all(\n contentTypesMeta.map(async (meta) => {\n const strapiDBConnection = strapi.db.connection;\n const tableName = strapi.contentType(meta.uid).collectionName;\n if (!tableName) return;\n\n if (!meta.hasDraftAndPublish) {\n const publishedDocuments = await strapiDBConnection(tableName)\n .countDistinct('document_id as count')\n .first();\n countDocuments.published += Number(publishedDocuments?.count) || 0;\n return;\n }\n\n // Classify each document_id into a single bucket (draft / published / modified)\n // in one pass. Replaces three separate self-join queries that scaled poorly on\n // large tables — see https://github.com/strapi/strapi/issues/25200.\n const classified = strapiDBConnection(tableName)\n .select('document_id')\n .select(\n strapiDBConnection.raw(\n `CASE\n WHEN MAX(CASE WHEN published_at IS NOT NULL THEN 1 ELSE 0 END) = 0\n THEN 'draft'\n WHEN MAX(CASE WHEN published_at IS NULL THEN updated_at END) =\n MAX(CASE WHEN published_at IS NOT NULL THEN updated_at END)\n THEN 'published'\n ELSE 'modified'\n END AS bucket`\n )\n )\n .groupBy('document_id');\n\n const counts = await strapiDBConnection\n .from(classified.as('classified'))\n .select(\n strapiDBConnection.raw(\n `COUNT(CASE WHEN bucket = 'draft' THEN 1 END) AS draft,\n COUNT(CASE WHEN bucket = 'published' THEN 1 END) AS published,\n COUNT(CASE WHEN bucket = 'modified' THEN 1 END) AS modified`\n )\n )\n .first();\n\n countDocuments.draft += Number(counts?.draft) || 0;\n countDocuments.published += Number(counts?.published) || 0;\n countDocuments.modified += Number(counts?.modified) || 0;\n })\n );\n\n return countDocuments;\n },\n };\n};\n\nexport { createHomepageService };\n"],"names":["createHomepageService","strapi","MAX_DOCUMENTS","metadataService","plugin","service","permissionService","admin","services","permission","getRegisteredContentType","uid","contentType","contentTypes","undefined","log","warn","getConfiguration","contentTypeUids","coreStore","db","query","rawConfigurations","findMany","where","key","$in","map","rawConfiguration","JSON","parse","value","getPermittedContentTypes","readPermissions","role","users","id","requestContext","get","state","user","action","Set","subject","filter","contentTypeOptions","pluginOptions","visible","permissionCheckerService","getPermissionChecker","create","userAbility","model","getContentTypesMeta","allowedContentTypeUids","configurations","reduce","acc","configuration","find","config","mainField","resolveReadableMainField","fields","buildHomepageQueryFields","hasDraftAndPublish","push","toIsoDateString","date","Date","String","Number","isNaN","getTime","toISOString","formatDocuments","documents","meta","populate","document","additionalFields","documentId","locale","title","contentTypeUid","contentTypeDisplayName","info","displayName","kind","updatedAt","publishedAt","sanitizeHomepageQuery","additionalQueryParams","permissionQuery","sanitizedQuery","read","limit","sanitizedFields","compactSanitizedFields","titleField","resolveTitleField","addStatusToDocuments","Promise","all","recentDocument","availableStatus","getMetadata","availableLocales","status","getStatus","queryLastDocuments","draftAndPublishOnly","permittedContentTypes","contentTypesMeta","recentDocuments","docs","flat","sort","a","b","compareIso","left","right","direction","slice","getRecentlyPublishedDocuments","recentlyPublishedDocuments","getRecentlyUpdatedDocuments","recentlyUpdatedDocuments","getCountDocuments","countDocuments","draft","published","modified","strapiDBConnection","connection","tableName","collectionName","publishedDocuments","countDistinct","first","count","classified","select","raw","groupBy","counts","from","as"],"mappings":"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"}
1
+ {"version":3,"file":"homepage.js","sources":["../../../../server/src/homepage/services/homepage.ts"],"sourcesContent":["/* eslint-disable func-names */\nimport type { Core, Modules, Schema } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\nimport type {\n GetCountDocuments,\n GetRecentDocuments,\n RecentDocument,\n} from '../../../../shared/contracts/homepage';\n\nimport {\n buildHomepageQueryFields,\n compactSanitizedFields,\n formatRecentDocumentTitle,\n resolveReadableMainField,\n resolveTitleField,\n} from './homepage-query-utils';\n\nconst createHomepageService = ({ strapi }: { strapi: Core.Strapi }) => {\n const MAX_DOCUMENTS = 4;\n\n const metadataService = strapi.plugin('content-manager').service('document-metadata');\n const permissionService = strapi.admin.services.permission;\n\n const getRegisteredContentType = (uid: RecentDocument['contentTypeUid']) => {\n const contentType = strapi.contentTypes[uid];\n\n if (contentType === undefined) {\n strapi.log.warn(\n `Skipping homepage content type \"${uid}\" because it is no longer registered.`\n );\n return undefined;\n }\n\n return contentType;\n };\n\n type ContentTypeConfiguration = {\n uid: RecentDocument['contentTypeUid'];\n settings: { mainField: string };\n };\n const getConfiguration = async (\n contentTypeUids: RecentDocument['contentTypeUid'][]\n ): Promise<ContentTypeConfiguration[]> => {\n /**\n * Don't use the strapi.store util because we need to make\n * more precise queries than exact key matches, in order to make as few queries as possible.\n */\n const coreStore = strapi.db.query('strapi::core-store');\n const rawConfigurations = await coreStore.findMany({\n where: {\n key: {\n $in: contentTypeUids.map(\n (contentType) => `plugin_content_manager_configuration_content_types::${contentType}`\n ),\n },\n },\n });\n\n return rawConfigurations.map((rawConfiguration) => {\n return JSON.parse(rawConfiguration.value);\n });\n };\n\n const getPermittedContentTypes = async () => {\n const readPermissions: Modules.Permissions.PermissionRule[] = await permissionService.findMany({\n where: {\n role: { users: { id: strapi.requestContext.get()?.state?.user.id } },\n action: 'plugin::content-manager.explorer.read',\n },\n });\n\n // Deduplicate subjects using a Set: the JOIN across permission -> role -> users produces one row\n // per role the user belongs to, so a multi-role user gets duplicate subjects.\n // Using a Set collapses them to unique content type UID.\n return [\n ...new Set(\n readPermissions\n .map((permission) => permission.subject)\n .filter((subject): subject is RecentDocument['contentTypeUid'] => {\n if (!subject) {\n return false;\n }\n\n const contentType = strapi.contentTypes[subject as keyof typeof strapi.contentTypes];\n const contentTypeOptions = contentType?.pluginOptions?.['content-manager'] as\n { visible?: boolean } | undefined;\n\n return contentTypeOptions?.visible !== false;\n })\n ),\n ];\n };\n\n type ContentTypeMeta = {\n fields: string[];\n mainField: string;\n contentType: Schema.ContentType;\n hasDraftAndPublish: boolean;\n uid: RecentDocument['contentTypeUid'];\n };\n\n const permissionCheckerService = strapi.plugin('content-manager').service('permission-checker');\n const getPermissionChecker = (uid: string) =>\n permissionCheckerService.create({\n userAbility: strapi.requestContext.get()?.state.userAbility,\n model: uid,\n });\n\n const getContentTypesMeta = (\n allowedContentTypeUids: RecentDocument['contentTypeUid'][],\n configurations: ContentTypeConfiguration[]\n ): ContentTypeMeta[] => {\n return allowedContentTypeUids.reduce<ContentTypeMeta[]>((acc, uid) => {\n const configuration = configurations.find((config) => config.uid === uid);\n const contentType = getRegisteredContentType(uid);\n\n if (contentType === undefined) {\n return acc;\n }\n\n const mainField = resolveReadableMainField(\n contentType,\n configuration,\n getPermissionChecker(uid)\n );\n const fields = buildHomepageQueryFields(contentType, mainField);\n const hasDraftAndPublish = contentTypes.hasDraftAndPublish(contentType);\n\n acc.push({\n fields,\n mainField,\n contentType,\n hasDraftAndPublish,\n uid,\n });\n\n return acc;\n }, []);\n };\n\n /**\n * Homepage widgets expect JSON-safe ISO strings. Returning `Date` objects can\n * serialize as `{}` after spread/clone (see https://github.com/strapi/strapi/issues/27013).\n */\n const toIsoDateString = (value: unknown): string | null => {\n if (value == null || value === '') {\n return null;\n }\n\n const date = value instanceof Date ? value : new Date(String(value));\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n };\n\n const formatDocuments = (\n documents: Modules.Documents.AnyDocument[],\n meta: ContentTypeMeta,\n populate?: string[]\n ) => {\n return documents.map((document) => {\n const additionalFields =\n populate?.reduce(\n (acc, key) => {\n acc[key] = document[key];\n return acc;\n },\n {} as Record<string, any>\n ) || {};\n return {\n documentId: document.documentId,\n locale: document.locale ?? null,\n title: formatRecentDocumentTitle(\n document[meta.mainField ?? 'documentId'],\n document.documentId\n ),\n contentTypeUid: meta.uid,\n contentTypeDisplayName: meta.contentType.info.displayName,\n kind: meta.contentType.kind,\n ...additionalFields,\n // Keep dates last so populate cannot overwrite with non-JSON-safe values\n updatedAt: toIsoDateString(document.updatedAt) ?? '',\n publishedAt:\n meta.hasDraftAndPublish && document.publishedAt\n ? toIsoDateString(document.publishedAt)\n : null,\n };\n });\n };\n\n const sanitizeHomepageQuery = async (\n meta: ContentTypeMeta,\n additionalQueryParams?: Record<string, unknown>\n ) => {\n const permissionQuery = await getPermissionChecker(meta.uid).sanitizedQuery.read({\n limit: MAX_DOCUMENTS,\n fields: meta.fields,\n ...additionalQueryParams,\n locale: '*',\n });\n\n const sanitizedFields = compactSanitizedFields(permissionQuery.fields);\n if (sanitizedFields !== undefined) {\n permissionQuery.fields = sanitizedFields;\n }\n\n return {\n permissionQuery,\n titleField: resolveTitleField(meta.mainField, sanitizedFields),\n };\n };\n\n return {\n async addStatusToDocuments(documents: RecentDocument[]): Promise<RecentDocument[]> {\n return Promise.all(\n documents.map(async (recentDocument) => {\n const hasDraftAndPublish = contentTypes.hasDraftAndPublish(\n strapi.contentType(recentDocument.contentTypeUid)\n );\n /**\n * Tries to query the other version of the document if draft and publish is enabled,\n * so that we know when to give the \"modified\" status.\n */\n const { availableStatus } = await metadataService.getMetadata(\n recentDocument.contentTypeUid,\n recentDocument,\n {\n availableStatus: hasDraftAndPublish,\n availableLocales: false,\n }\n );\n const status: RecentDocument['status'] = metadataService.getStatus(\n recentDocument,\n availableStatus\n );\n\n return {\n ...recentDocument,\n status: hasDraftAndPublish ? status : undefined,\n };\n })\n );\n },\n\n async queryLastDocuments(\n additionalQueryParams?: Record<string, unknown>,\n draftAndPublishOnly?: boolean\n ): Promise<RecentDocument[]> {\n const permittedContentTypes = await getPermittedContentTypes();\n const allowedContentTypeUids = draftAndPublishOnly\n ? permittedContentTypes.filter((uid) => {\n const contentType = getRegisteredContentType(uid);\n\n return contentType !== undefined && contentTypes.hasDraftAndPublish(contentType);\n })\n : permittedContentTypes;\n // Fetch the configuration for each content type in a single query\n const configurations = await getConfiguration(allowedContentTypeUids);\n // Get the necessary metadata for the documents\n const contentTypesMeta = getContentTypesMeta(allowedContentTypeUids, configurations);\n\n const recentDocuments = await Promise.all(\n contentTypesMeta.map(async (meta) => {\n const { permissionQuery, titleField } = await sanitizeHomepageQuery(\n meta,\n additionalQueryParams\n );\n\n const docs = await strapi.documents(meta.uid).findMany(permissionQuery);\n const populate = additionalQueryParams?.populate as string[];\n\n return formatDocuments(docs, { ...meta, mainField: titleField }, populate);\n })\n );\n\n return recentDocuments\n .flat()\n .sort((a, b) => {\n // ISO-8601 strings compare lexicographically in chronological order\n const compareIso = (left: string, right: string, direction: 1 | -1) => {\n if (left < right) return -1 * direction;\n if (left > right) return 1 * direction;\n return 0;\n };\n\n switch (additionalQueryParams?.sort) {\n case 'publishedAt:desc':\n if (!a.publishedAt || !b.publishedAt) return 0;\n return compareIso(a.publishedAt, b.publishedAt, -1);\n case 'publishedAt:asc':\n if (!a.publishedAt || !b.publishedAt) return 0;\n return compareIso(a.publishedAt, b.publishedAt, 1);\n case 'updatedAt:desc':\n if (!a.updatedAt || !b.updatedAt) return 0;\n return compareIso(a.updatedAt, b.updatedAt, -1);\n case 'updatedAt:asc':\n if (!a.updatedAt || !b.updatedAt) return 0;\n return compareIso(a.updatedAt, b.updatedAt, 1);\n default:\n return 0;\n }\n })\n .slice(0, MAX_DOCUMENTS);\n },\n\n async getRecentlyPublishedDocuments(): Promise<GetRecentDocuments.Response['data']> {\n const recentlyPublishedDocuments = await this.queryLastDocuments(\n {\n sort: 'publishedAt:desc',\n status: 'published',\n },\n true\n );\n\n return this.addStatusToDocuments(recentlyPublishedDocuments);\n },\n\n async getRecentlyUpdatedDocuments(): Promise<GetRecentDocuments.Response['data']> {\n const recentlyUpdatedDocuments = await this.queryLastDocuments({\n sort: 'updatedAt:desc',\n });\n\n return this.addStatusToDocuments(recentlyUpdatedDocuments);\n },\n\n async getCountDocuments(): Promise<GetCountDocuments.Response['data']> {\n const permittedContentTypes = await getPermittedContentTypes();\n // Fetch the configuration for each content type in a single query\n const configurations = await getConfiguration(permittedContentTypes);\n // Get the necessary metadata for the documents\n const contentTypesMeta = getContentTypesMeta(permittedContentTypes, configurations);\n\n const countDocuments = {\n draft: 0,\n published: 0,\n modified: 0,\n };\n\n await Promise.all(\n contentTypesMeta.map(async (meta) => {\n const strapiDBConnection = strapi.db.connection;\n const tableName = strapi.contentType(meta.uid).collectionName;\n if (!tableName) return;\n\n if (!meta.hasDraftAndPublish) {\n const publishedDocuments = await strapiDBConnection(tableName)\n .countDistinct('document_id as count')\n .first();\n countDocuments.published += Number(publishedDocuments?.count) || 0;\n return;\n }\n\n // Classify each document_id into a single bucket (draft / published / modified)\n // in one pass. Replaces three separate self-join queries that scaled poorly on\n // large tables — see https://github.com/strapi/strapi/issues/25200.\n const classified = strapiDBConnection(tableName)\n .select('document_id')\n .select(\n strapiDBConnection.raw(\n `CASE\n WHEN MAX(CASE WHEN published_at IS NOT NULL THEN 1 ELSE 0 END) = 0\n THEN 'draft'\n WHEN MAX(CASE WHEN published_at IS NULL THEN updated_at END) =\n MAX(CASE WHEN published_at IS NOT NULL THEN updated_at END)\n THEN 'published'\n ELSE 'modified'\n END AS bucket`\n )\n )\n .groupBy('document_id');\n\n const counts = await strapiDBConnection\n .from(classified.as('classified'))\n .select(\n strapiDBConnection.raw(\n `COUNT(CASE WHEN bucket = 'draft' THEN 1 END) AS draft,\n COUNT(CASE WHEN bucket = 'published' THEN 1 END) AS published,\n COUNT(CASE WHEN bucket = 'modified' THEN 1 END) AS modified`\n )\n )\n .first();\n\n countDocuments.draft += Number(counts?.draft) || 0;\n countDocuments.published += Number(counts?.published) || 0;\n countDocuments.modified += Number(counts?.modified) || 0;\n })\n );\n\n return countDocuments;\n },\n };\n};\n\nexport { createHomepageService };\n"],"names":["createHomepageService","strapi","MAX_DOCUMENTS","metadataService","plugin","service","permissionService","admin","services","permission","getRegisteredContentType","uid","contentType","contentTypes","undefined","log","warn","getConfiguration","contentTypeUids","coreStore","db","query","rawConfigurations","findMany","where","key","$in","map","rawConfiguration","JSON","parse","value","getPermittedContentTypes","readPermissions","role","users","id","requestContext","get","state","user","action","Set","subject","filter","contentTypeOptions","pluginOptions","visible","permissionCheckerService","getPermissionChecker","create","userAbility","model","getContentTypesMeta","allowedContentTypeUids","configurations","reduce","acc","configuration","find","config","mainField","resolveReadableMainField","fields","buildHomepageQueryFields","hasDraftAndPublish","push","toIsoDateString","date","Date","String","Number","isNaN","getTime","toISOString","formatDocuments","documents","meta","populate","document","additionalFields","documentId","locale","title","formatRecentDocumentTitle","contentTypeUid","contentTypeDisplayName","info","displayName","kind","updatedAt","publishedAt","sanitizeHomepageQuery","additionalQueryParams","permissionQuery","sanitizedQuery","read","limit","sanitizedFields","compactSanitizedFields","titleField","resolveTitleField","addStatusToDocuments","Promise","all","recentDocument","availableStatus","getMetadata","availableLocales","status","getStatus","queryLastDocuments","draftAndPublishOnly","permittedContentTypes","contentTypesMeta","recentDocuments","docs","flat","sort","a","b","compareIso","left","right","direction","slice","getRecentlyPublishedDocuments","recentlyPublishedDocuments","getRecentlyUpdatedDocuments","recentlyUpdatedDocuments","getCountDocuments","countDocuments","draft","published","modified","strapiDBConnection","connection","tableName","collectionName","publishedDocuments","countDistinct","first","count","classified","select","raw","groupBy","counts","from","as"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { contentTypes } from '@strapi/utils';
2
- import { resolveReadableMainField, buildHomepageQueryFields, compactSanitizedFields, resolveTitleField } from './homepage-query-utils.mjs';
2
+ import { resolveReadableMainField, buildHomepageQueryFields, compactSanitizedFields, resolveTitleField, formatRecentDocumentTitle } from './homepage-query-utils.mjs';
3
3
 
4
4
  const createHomepageService = ({ strapi })=>{
5
5
  const MAX_DOCUMENTS = 4;
@@ -98,7 +98,7 @@ const createHomepageService = ({ strapi })=>{
98
98
  return {
99
99
  documentId: document.documentId,
100
100
  locale: document.locale ?? null,
101
- title: document[meta.mainField ?? 'documentId'],
101
+ title: formatRecentDocumentTitle(document[meta.mainField ?? 'documentId'], document.documentId),
102
102
  contentTypeUid: meta.uid,
103
103
  contentTypeDisplayName: meta.contentType.info.displayName,
104
104
  kind: meta.contentType.kind,
@@ -1 +1 @@
1
- {"version":3,"file":"homepage.mjs","sources":["../../../../server/src/homepage/services/homepage.ts"],"sourcesContent":["/* eslint-disable func-names */\nimport type { Core, Modules, Schema } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\nimport type {\n GetCountDocuments,\n GetRecentDocuments,\n RecentDocument,\n} from '../../../../shared/contracts/homepage';\n\nimport {\n buildHomepageQueryFields,\n compactSanitizedFields,\n resolveReadableMainField,\n resolveTitleField,\n} from './homepage-query-utils';\n\nconst createHomepageService = ({ strapi }: { strapi: Core.Strapi }) => {\n const MAX_DOCUMENTS = 4;\n\n const metadataService = strapi.plugin('content-manager').service('document-metadata');\n const permissionService = strapi.admin.services.permission;\n\n const getRegisteredContentType = (uid: RecentDocument['contentTypeUid']) => {\n const contentType = strapi.contentTypes[uid];\n\n if (contentType === undefined) {\n strapi.log.warn(\n `Skipping homepage content type \"${uid}\" because it is no longer registered.`\n );\n return undefined;\n }\n\n return contentType;\n };\n\n type ContentTypeConfiguration = {\n uid: RecentDocument['contentTypeUid'];\n settings: { mainField: string };\n };\n const getConfiguration = async (\n contentTypeUids: RecentDocument['contentTypeUid'][]\n ): Promise<ContentTypeConfiguration[]> => {\n /**\n * Don't use the strapi.store util because we need to make\n * more precise queries than exact key matches, in order to make as few queries as possible.\n */\n const coreStore = strapi.db.query('strapi::core-store');\n const rawConfigurations = await coreStore.findMany({\n where: {\n key: {\n $in: contentTypeUids.map(\n (contentType) => `plugin_content_manager_configuration_content_types::${contentType}`\n ),\n },\n },\n });\n\n return rawConfigurations.map((rawConfiguration) => {\n return JSON.parse(rawConfiguration.value);\n });\n };\n\n const getPermittedContentTypes = async () => {\n const readPermissions: Modules.Permissions.PermissionRule[] = await permissionService.findMany({\n where: {\n role: { users: { id: strapi.requestContext.get()?.state?.user.id } },\n action: 'plugin::content-manager.explorer.read',\n },\n });\n\n // Deduplicate subjects using a Set: the JOIN across permission -> role -> users produces one row\n // per role the user belongs to, so a multi-role user gets duplicate subjects.\n // Using a Set collapses them to unique content type UID.\n return [\n ...new Set(\n readPermissions\n .map((permission) => permission.subject)\n .filter((subject): subject is RecentDocument['contentTypeUid'] => {\n if (!subject) {\n return false;\n }\n\n const contentType = strapi.contentTypes[subject as keyof typeof strapi.contentTypes];\n const contentTypeOptions = contentType?.pluginOptions?.['content-manager'] as\n { visible?: boolean } | undefined;\n\n return contentTypeOptions?.visible !== false;\n })\n ),\n ];\n };\n\n type ContentTypeMeta = {\n fields: string[];\n mainField: string;\n contentType: Schema.ContentType;\n hasDraftAndPublish: boolean;\n uid: RecentDocument['contentTypeUid'];\n };\n\n const permissionCheckerService = strapi.plugin('content-manager').service('permission-checker');\n const getPermissionChecker = (uid: string) =>\n permissionCheckerService.create({\n userAbility: strapi.requestContext.get()?.state.userAbility,\n model: uid,\n });\n\n const getContentTypesMeta = (\n allowedContentTypeUids: RecentDocument['contentTypeUid'][],\n configurations: ContentTypeConfiguration[]\n ): ContentTypeMeta[] => {\n return allowedContentTypeUids.reduce<ContentTypeMeta[]>((acc, uid) => {\n const configuration = configurations.find((config) => config.uid === uid);\n const contentType = getRegisteredContentType(uid);\n\n if (contentType === undefined) {\n return acc;\n }\n\n const mainField = resolveReadableMainField(\n contentType,\n configuration,\n getPermissionChecker(uid)\n );\n const fields = buildHomepageQueryFields(contentType, mainField);\n const hasDraftAndPublish = contentTypes.hasDraftAndPublish(contentType);\n\n acc.push({\n fields,\n mainField,\n contentType,\n hasDraftAndPublish,\n uid,\n });\n\n return acc;\n }, []);\n };\n\n /**\n * Homepage widgets expect JSON-safe ISO strings. Returning `Date` objects can\n * serialize as `{}` after spread/clone (see https://github.com/strapi/strapi/issues/27013).\n */\n const toIsoDateString = (value: unknown): string | null => {\n if (value == null || value === '') {\n return null;\n }\n\n const date = value instanceof Date ? value : new Date(String(value));\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n };\n\n const formatDocuments = (\n documents: Modules.Documents.AnyDocument[],\n meta: ContentTypeMeta,\n populate?: string[]\n ) => {\n return documents.map((document) => {\n const additionalFields =\n populate?.reduce(\n (acc, key) => {\n acc[key] = document[key];\n return acc;\n },\n {} as Record<string, any>\n ) || {};\n return {\n documentId: document.documentId,\n locale: document.locale ?? null,\n title: document[meta.mainField ?? 'documentId'],\n contentTypeUid: meta.uid,\n contentTypeDisplayName: meta.contentType.info.displayName,\n kind: meta.contentType.kind,\n ...additionalFields,\n // Keep dates last so populate cannot overwrite with non-JSON-safe values\n updatedAt: toIsoDateString(document.updatedAt) ?? '',\n publishedAt:\n meta.hasDraftAndPublish && document.publishedAt\n ? toIsoDateString(document.publishedAt)\n : null,\n };\n });\n };\n\n const sanitizeHomepageQuery = async (\n meta: ContentTypeMeta,\n additionalQueryParams?: Record<string, unknown>\n ) => {\n const permissionQuery = await getPermissionChecker(meta.uid).sanitizedQuery.read({\n limit: MAX_DOCUMENTS,\n fields: meta.fields,\n ...additionalQueryParams,\n locale: '*',\n });\n\n const sanitizedFields = compactSanitizedFields(permissionQuery.fields);\n if (sanitizedFields !== undefined) {\n permissionQuery.fields = sanitizedFields;\n }\n\n return {\n permissionQuery,\n titleField: resolveTitleField(meta.mainField, sanitizedFields),\n };\n };\n\n return {\n async addStatusToDocuments(documents: RecentDocument[]): Promise<RecentDocument[]> {\n return Promise.all(\n documents.map(async (recentDocument) => {\n const hasDraftAndPublish = contentTypes.hasDraftAndPublish(\n strapi.contentType(recentDocument.contentTypeUid)\n );\n /**\n * Tries to query the other version of the document if draft and publish is enabled,\n * so that we know when to give the \"modified\" status.\n */\n const { availableStatus } = await metadataService.getMetadata(\n recentDocument.contentTypeUid,\n recentDocument,\n {\n availableStatus: hasDraftAndPublish,\n availableLocales: false,\n }\n );\n const status: RecentDocument['status'] = metadataService.getStatus(\n recentDocument,\n availableStatus\n );\n\n return {\n ...recentDocument,\n status: hasDraftAndPublish ? status : undefined,\n };\n })\n );\n },\n\n async queryLastDocuments(\n additionalQueryParams?: Record<string, unknown>,\n draftAndPublishOnly?: boolean\n ): Promise<RecentDocument[]> {\n const permittedContentTypes = await getPermittedContentTypes();\n const allowedContentTypeUids = draftAndPublishOnly\n ? permittedContentTypes.filter((uid) => {\n const contentType = getRegisteredContentType(uid);\n\n return contentType !== undefined && contentTypes.hasDraftAndPublish(contentType);\n })\n : permittedContentTypes;\n // Fetch the configuration for each content type in a single query\n const configurations = await getConfiguration(allowedContentTypeUids);\n // Get the necessary metadata for the documents\n const contentTypesMeta = getContentTypesMeta(allowedContentTypeUids, configurations);\n\n const recentDocuments = await Promise.all(\n contentTypesMeta.map(async (meta) => {\n const { permissionQuery, titleField } = await sanitizeHomepageQuery(\n meta,\n additionalQueryParams\n );\n\n const docs = await strapi.documents(meta.uid).findMany(permissionQuery);\n const populate = additionalQueryParams?.populate as string[];\n\n return formatDocuments(docs, { ...meta, mainField: titleField }, populate);\n })\n );\n\n return recentDocuments\n .flat()\n .sort((a, b) => {\n // ISO-8601 strings compare lexicographically in chronological order\n const compareIso = (left: string, right: string, direction: 1 | -1) => {\n if (left < right) return -1 * direction;\n if (left > right) return 1 * direction;\n return 0;\n };\n\n switch (additionalQueryParams?.sort) {\n case 'publishedAt:desc':\n if (!a.publishedAt || !b.publishedAt) return 0;\n return compareIso(a.publishedAt, b.publishedAt, -1);\n case 'publishedAt:asc':\n if (!a.publishedAt || !b.publishedAt) return 0;\n return compareIso(a.publishedAt, b.publishedAt, 1);\n case 'updatedAt:desc':\n if (!a.updatedAt || !b.updatedAt) return 0;\n return compareIso(a.updatedAt, b.updatedAt, -1);\n case 'updatedAt:asc':\n if (!a.updatedAt || !b.updatedAt) return 0;\n return compareIso(a.updatedAt, b.updatedAt, 1);\n default:\n return 0;\n }\n })\n .slice(0, MAX_DOCUMENTS);\n },\n\n async getRecentlyPublishedDocuments(): Promise<GetRecentDocuments.Response['data']> {\n const recentlyPublishedDocuments = await this.queryLastDocuments(\n {\n sort: 'publishedAt:desc',\n status: 'published',\n },\n true\n );\n\n return this.addStatusToDocuments(recentlyPublishedDocuments);\n },\n\n async getRecentlyUpdatedDocuments(): Promise<GetRecentDocuments.Response['data']> {\n const recentlyUpdatedDocuments = await this.queryLastDocuments({\n sort: 'updatedAt:desc',\n });\n\n return this.addStatusToDocuments(recentlyUpdatedDocuments);\n },\n\n async getCountDocuments(): Promise<GetCountDocuments.Response['data']> {\n const permittedContentTypes = await getPermittedContentTypes();\n // Fetch the configuration for each content type in a single query\n const configurations = await getConfiguration(permittedContentTypes);\n // Get the necessary metadata for the documents\n const contentTypesMeta = getContentTypesMeta(permittedContentTypes, configurations);\n\n const countDocuments = {\n draft: 0,\n published: 0,\n modified: 0,\n };\n\n await Promise.all(\n contentTypesMeta.map(async (meta) => {\n const strapiDBConnection = strapi.db.connection;\n const tableName = strapi.contentType(meta.uid).collectionName;\n if (!tableName) return;\n\n if (!meta.hasDraftAndPublish) {\n const publishedDocuments = await strapiDBConnection(tableName)\n .countDistinct('document_id as count')\n .first();\n countDocuments.published += Number(publishedDocuments?.count) || 0;\n return;\n }\n\n // Classify each document_id into a single bucket (draft / published / modified)\n // in one pass. Replaces three separate self-join queries that scaled poorly on\n // large tables — see https://github.com/strapi/strapi/issues/25200.\n const classified = strapiDBConnection(tableName)\n .select('document_id')\n .select(\n strapiDBConnection.raw(\n `CASE\n WHEN MAX(CASE WHEN published_at IS NOT NULL THEN 1 ELSE 0 END) = 0\n THEN 'draft'\n WHEN MAX(CASE WHEN published_at IS NULL THEN updated_at END) =\n MAX(CASE WHEN published_at IS NOT NULL THEN updated_at END)\n THEN 'published'\n ELSE 'modified'\n END AS bucket`\n )\n )\n .groupBy('document_id');\n\n const counts = await strapiDBConnection\n .from(classified.as('classified'))\n .select(\n strapiDBConnection.raw(\n `COUNT(CASE WHEN bucket = 'draft' THEN 1 END) AS draft,\n COUNT(CASE WHEN bucket = 'published' THEN 1 END) AS published,\n COUNT(CASE WHEN bucket = 'modified' THEN 1 END) AS modified`\n )\n )\n .first();\n\n countDocuments.draft += Number(counts?.draft) || 0;\n countDocuments.published += Number(counts?.published) || 0;\n countDocuments.modified += Number(counts?.modified) || 0;\n })\n );\n\n return countDocuments;\n },\n };\n};\n\nexport { createHomepageService };\n"],"names":["createHomepageService","strapi","MAX_DOCUMENTS","metadataService","plugin","service","permissionService","admin","services","permission","getRegisteredContentType","uid","contentType","contentTypes","undefined","log","warn","getConfiguration","contentTypeUids","coreStore","db","query","rawConfigurations","findMany","where","key","$in","map","rawConfiguration","JSON","parse","value","getPermittedContentTypes","readPermissions","role","users","id","requestContext","get","state","user","action","Set","subject","filter","contentTypeOptions","pluginOptions","visible","permissionCheckerService","getPermissionChecker","create","userAbility","model","getContentTypesMeta","allowedContentTypeUids","configurations","reduce","acc","configuration","find","config","mainField","resolveReadableMainField","fields","buildHomepageQueryFields","hasDraftAndPublish","push","toIsoDateString","date","Date","String","Number","isNaN","getTime","toISOString","formatDocuments","documents","meta","populate","document","additionalFields","documentId","locale","title","contentTypeUid","contentTypeDisplayName","info","displayName","kind","updatedAt","publishedAt","sanitizeHomepageQuery","additionalQueryParams","permissionQuery","sanitizedQuery","read","limit","sanitizedFields","compactSanitizedFields","titleField","resolveTitleField","addStatusToDocuments","Promise","all","recentDocument","availableStatus","getMetadata","availableLocales","status","getStatus","queryLastDocuments","draftAndPublishOnly","permittedContentTypes","contentTypesMeta","recentDocuments","docs","flat","sort","a","b","compareIso","left","right","direction","slice","getRecentlyPublishedDocuments","recentlyPublishedDocuments","getRecentlyUpdatedDocuments","recentlyUpdatedDocuments","getCountDocuments","countDocuments","draft","published","modified","strapiDBConnection","connection","tableName","collectionName","publishedDocuments","countDistinct","first","count","classified","select","raw","groupBy","counts","from","as"],"mappings":"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"}
1
+ {"version":3,"file":"homepage.mjs","sources":["../../../../server/src/homepage/services/homepage.ts"],"sourcesContent":["/* eslint-disable func-names */\nimport type { Core, Modules, Schema } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\nimport type {\n GetCountDocuments,\n GetRecentDocuments,\n RecentDocument,\n} from '../../../../shared/contracts/homepage';\n\nimport {\n buildHomepageQueryFields,\n compactSanitizedFields,\n formatRecentDocumentTitle,\n resolveReadableMainField,\n resolveTitleField,\n} from './homepage-query-utils';\n\nconst createHomepageService = ({ strapi }: { strapi: Core.Strapi }) => {\n const MAX_DOCUMENTS = 4;\n\n const metadataService = strapi.plugin('content-manager').service('document-metadata');\n const permissionService = strapi.admin.services.permission;\n\n const getRegisteredContentType = (uid: RecentDocument['contentTypeUid']) => {\n const contentType = strapi.contentTypes[uid];\n\n if (contentType === undefined) {\n strapi.log.warn(\n `Skipping homepage content type \"${uid}\" because it is no longer registered.`\n );\n return undefined;\n }\n\n return contentType;\n };\n\n type ContentTypeConfiguration = {\n uid: RecentDocument['contentTypeUid'];\n settings: { mainField: string };\n };\n const getConfiguration = async (\n contentTypeUids: RecentDocument['contentTypeUid'][]\n ): Promise<ContentTypeConfiguration[]> => {\n /**\n * Don't use the strapi.store util because we need to make\n * more precise queries than exact key matches, in order to make as few queries as possible.\n */\n const coreStore = strapi.db.query('strapi::core-store');\n const rawConfigurations = await coreStore.findMany({\n where: {\n key: {\n $in: contentTypeUids.map(\n (contentType) => `plugin_content_manager_configuration_content_types::${contentType}`\n ),\n },\n },\n });\n\n return rawConfigurations.map((rawConfiguration) => {\n return JSON.parse(rawConfiguration.value);\n });\n };\n\n const getPermittedContentTypes = async () => {\n const readPermissions: Modules.Permissions.PermissionRule[] = await permissionService.findMany({\n where: {\n role: { users: { id: strapi.requestContext.get()?.state?.user.id } },\n action: 'plugin::content-manager.explorer.read',\n },\n });\n\n // Deduplicate subjects using a Set: the JOIN across permission -> role -> users produces one row\n // per role the user belongs to, so a multi-role user gets duplicate subjects.\n // Using a Set collapses them to unique content type UID.\n return [\n ...new Set(\n readPermissions\n .map((permission) => permission.subject)\n .filter((subject): subject is RecentDocument['contentTypeUid'] => {\n if (!subject) {\n return false;\n }\n\n const contentType = strapi.contentTypes[subject as keyof typeof strapi.contentTypes];\n const contentTypeOptions = contentType?.pluginOptions?.['content-manager'] as\n { visible?: boolean } | undefined;\n\n return contentTypeOptions?.visible !== false;\n })\n ),\n ];\n };\n\n type ContentTypeMeta = {\n fields: string[];\n mainField: string;\n contentType: Schema.ContentType;\n hasDraftAndPublish: boolean;\n uid: RecentDocument['contentTypeUid'];\n };\n\n const permissionCheckerService = strapi.plugin('content-manager').service('permission-checker');\n const getPermissionChecker = (uid: string) =>\n permissionCheckerService.create({\n userAbility: strapi.requestContext.get()?.state.userAbility,\n model: uid,\n });\n\n const getContentTypesMeta = (\n allowedContentTypeUids: RecentDocument['contentTypeUid'][],\n configurations: ContentTypeConfiguration[]\n ): ContentTypeMeta[] => {\n return allowedContentTypeUids.reduce<ContentTypeMeta[]>((acc, uid) => {\n const configuration = configurations.find((config) => config.uid === uid);\n const contentType = getRegisteredContentType(uid);\n\n if (contentType === undefined) {\n return acc;\n }\n\n const mainField = resolveReadableMainField(\n contentType,\n configuration,\n getPermissionChecker(uid)\n );\n const fields = buildHomepageQueryFields(contentType, mainField);\n const hasDraftAndPublish = contentTypes.hasDraftAndPublish(contentType);\n\n acc.push({\n fields,\n mainField,\n contentType,\n hasDraftAndPublish,\n uid,\n });\n\n return acc;\n }, []);\n };\n\n /**\n * Homepage widgets expect JSON-safe ISO strings. Returning `Date` objects can\n * serialize as `{}` after spread/clone (see https://github.com/strapi/strapi/issues/27013).\n */\n const toIsoDateString = (value: unknown): string | null => {\n if (value == null || value === '') {\n return null;\n }\n\n const date = value instanceof Date ? value : new Date(String(value));\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n };\n\n const formatDocuments = (\n documents: Modules.Documents.AnyDocument[],\n meta: ContentTypeMeta,\n populate?: string[]\n ) => {\n return documents.map((document) => {\n const additionalFields =\n populate?.reduce(\n (acc, key) => {\n acc[key] = document[key];\n return acc;\n },\n {} as Record<string, any>\n ) || {};\n return {\n documentId: document.documentId,\n locale: document.locale ?? null,\n title: formatRecentDocumentTitle(\n document[meta.mainField ?? 'documentId'],\n document.documentId\n ),\n contentTypeUid: meta.uid,\n contentTypeDisplayName: meta.contentType.info.displayName,\n kind: meta.contentType.kind,\n ...additionalFields,\n // Keep dates last so populate cannot overwrite with non-JSON-safe values\n updatedAt: toIsoDateString(document.updatedAt) ?? '',\n publishedAt:\n meta.hasDraftAndPublish && document.publishedAt\n ? toIsoDateString(document.publishedAt)\n : null,\n };\n });\n };\n\n const sanitizeHomepageQuery = async (\n meta: ContentTypeMeta,\n additionalQueryParams?: Record<string, unknown>\n ) => {\n const permissionQuery = await getPermissionChecker(meta.uid).sanitizedQuery.read({\n limit: MAX_DOCUMENTS,\n fields: meta.fields,\n ...additionalQueryParams,\n locale: '*',\n });\n\n const sanitizedFields = compactSanitizedFields(permissionQuery.fields);\n if (sanitizedFields !== undefined) {\n permissionQuery.fields = sanitizedFields;\n }\n\n return {\n permissionQuery,\n titleField: resolveTitleField(meta.mainField, sanitizedFields),\n };\n };\n\n return {\n async addStatusToDocuments(documents: RecentDocument[]): Promise<RecentDocument[]> {\n return Promise.all(\n documents.map(async (recentDocument) => {\n const hasDraftAndPublish = contentTypes.hasDraftAndPublish(\n strapi.contentType(recentDocument.contentTypeUid)\n );\n /**\n * Tries to query the other version of the document if draft and publish is enabled,\n * so that we know when to give the \"modified\" status.\n */\n const { availableStatus } = await metadataService.getMetadata(\n recentDocument.contentTypeUid,\n recentDocument,\n {\n availableStatus: hasDraftAndPublish,\n availableLocales: false,\n }\n );\n const status: RecentDocument['status'] = metadataService.getStatus(\n recentDocument,\n availableStatus\n );\n\n return {\n ...recentDocument,\n status: hasDraftAndPublish ? status : undefined,\n };\n })\n );\n },\n\n async queryLastDocuments(\n additionalQueryParams?: Record<string, unknown>,\n draftAndPublishOnly?: boolean\n ): Promise<RecentDocument[]> {\n const permittedContentTypes = await getPermittedContentTypes();\n const allowedContentTypeUids = draftAndPublishOnly\n ? permittedContentTypes.filter((uid) => {\n const contentType = getRegisteredContentType(uid);\n\n return contentType !== undefined && contentTypes.hasDraftAndPublish(contentType);\n })\n : permittedContentTypes;\n // Fetch the configuration for each content type in a single query\n const configurations = await getConfiguration(allowedContentTypeUids);\n // Get the necessary metadata for the documents\n const contentTypesMeta = getContentTypesMeta(allowedContentTypeUids, configurations);\n\n const recentDocuments = await Promise.all(\n contentTypesMeta.map(async (meta) => {\n const { permissionQuery, titleField } = await sanitizeHomepageQuery(\n meta,\n additionalQueryParams\n );\n\n const docs = await strapi.documents(meta.uid).findMany(permissionQuery);\n const populate = additionalQueryParams?.populate as string[];\n\n return formatDocuments(docs, { ...meta, mainField: titleField }, populate);\n })\n );\n\n return recentDocuments\n .flat()\n .sort((a, b) => {\n // ISO-8601 strings compare lexicographically in chronological order\n const compareIso = (left: string, right: string, direction: 1 | -1) => {\n if (left < right) return -1 * direction;\n if (left > right) return 1 * direction;\n return 0;\n };\n\n switch (additionalQueryParams?.sort) {\n case 'publishedAt:desc':\n if (!a.publishedAt || !b.publishedAt) return 0;\n return compareIso(a.publishedAt, b.publishedAt, -1);\n case 'publishedAt:asc':\n if (!a.publishedAt || !b.publishedAt) return 0;\n return compareIso(a.publishedAt, b.publishedAt, 1);\n case 'updatedAt:desc':\n if (!a.updatedAt || !b.updatedAt) return 0;\n return compareIso(a.updatedAt, b.updatedAt, -1);\n case 'updatedAt:asc':\n if (!a.updatedAt || !b.updatedAt) return 0;\n return compareIso(a.updatedAt, b.updatedAt, 1);\n default:\n return 0;\n }\n })\n .slice(0, MAX_DOCUMENTS);\n },\n\n async getRecentlyPublishedDocuments(): Promise<GetRecentDocuments.Response['data']> {\n const recentlyPublishedDocuments = await this.queryLastDocuments(\n {\n sort: 'publishedAt:desc',\n status: 'published',\n },\n true\n );\n\n return this.addStatusToDocuments(recentlyPublishedDocuments);\n },\n\n async getRecentlyUpdatedDocuments(): Promise<GetRecentDocuments.Response['data']> {\n const recentlyUpdatedDocuments = await this.queryLastDocuments({\n sort: 'updatedAt:desc',\n });\n\n return this.addStatusToDocuments(recentlyUpdatedDocuments);\n },\n\n async getCountDocuments(): Promise<GetCountDocuments.Response['data']> {\n const permittedContentTypes = await getPermittedContentTypes();\n // Fetch the configuration for each content type in a single query\n const configurations = await getConfiguration(permittedContentTypes);\n // Get the necessary metadata for the documents\n const contentTypesMeta = getContentTypesMeta(permittedContentTypes, configurations);\n\n const countDocuments = {\n draft: 0,\n published: 0,\n modified: 0,\n };\n\n await Promise.all(\n contentTypesMeta.map(async (meta) => {\n const strapiDBConnection = strapi.db.connection;\n const tableName = strapi.contentType(meta.uid).collectionName;\n if (!tableName) return;\n\n if (!meta.hasDraftAndPublish) {\n const publishedDocuments = await strapiDBConnection(tableName)\n .countDistinct('document_id as count')\n .first();\n countDocuments.published += Number(publishedDocuments?.count) || 0;\n return;\n }\n\n // Classify each document_id into a single bucket (draft / published / modified)\n // in one pass. Replaces three separate self-join queries that scaled poorly on\n // large tables — see https://github.com/strapi/strapi/issues/25200.\n const classified = strapiDBConnection(tableName)\n .select('document_id')\n .select(\n strapiDBConnection.raw(\n `CASE\n WHEN MAX(CASE WHEN published_at IS NOT NULL THEN 1 ELSE 0 END) = 0\n THEN 'draft'\n WHEN MAX(CASE WHEN published_at IS NULL THEN updated_at END) =\n MAX(CASE WHEN published_at IS NOT NULL THEN updated_at END)\n THEN 'published'\n ELSE 'modified'\n END AS bucket`\n )\n )\n .groupBy('document_id');\n\n const counts = await strapiDBConnection\n .from(classified.as('classified'))\n .select(\n strapiDBConnection.raw(\n `COUNT(CASE WHEN bucket = 'draft' THEN 1 END) AS draft,\n COUNT(CASE WHEN bucket = 'published' THEN 1 END) AS published,\n COUNT(CASE WHEN bucket = 'modified' THEN 1 END) AS modified`\n )\n )\n .first();\n\n countDocuments.draft += Number(counts?.draft) || 0;\n countDocuments.published += Number(counts?.published) || 0;\n countDocuments.modified += Number(counts?.modified) || 0;\n })\n );\n\n return countDocuments;\n },\n };\n};\n\nexport { createHomepageService };\n"],"names":["createHomepageService","strapi","MAX_DOCUMENTS","metadataService","plugin","service","permissionService","admin","services","permission","getRegisteredContentType","uid","contentType","contentTypes","undefined","log","warn","getConfiguration","contentTypeUids","coreStore","db","query","rawConfigurations","findMany","where","key","$in","map","rawConfiguration","JSON","parse","value","getPermittedContentTypes","readPermissions","role","users","id","requestContext","get","state","user","action","Set","subject","filter","contentTypeOptions","pluginOptions","visible","permissionCheckerService","getPermissionChecker","create","userAbility","model","getContentTypesMeta","allowedContentTypeUids","configurations","reduce","acc","configuration","find","config","mainField","resolveReadableMainField","fields","buildHomepageQueryFields","hasDraftAndPublish","push","toIsoDateString","date","Date","String","Number","isNaN","getTime","toISOString","formatDocuments","documents","meta","populate","document","additionalFields","documentId","locale","title","formatRecentDocumentTitle","contentTypeUid","contentTypeDisplayName","info","displayName","kind","updatedAt","publishedAt","sanitizeHomepageQuery","additionalQueryParams","permissionQuery","sanitizedQuery","read","limit","sanitizedFields","compactSanitizedFields","titleField","resolveTitleField","addStatusToDocuments","Promise","all","recentDocument","availableStatus","getMetadata","availableLocales","status","getStatus","queryLastDocuments","draftAndPublishOnly","permittedContentTypes","contentTypesMeta","recentDocuments","docs","flat","sort","a","b","compareIso","left","right","direction","slice","getRecentlyPublishedDocuments","recentlyPublishedDocuments","getRecentlyUpdatedDocuments","recentlyUpdatedDocuments","getCountDocuments","countDocuments","draft","published","modified","strapiDBConnection","connection","tableName","collectionName","publishedDocuments","countDistinct","first","count","classified","select","raw","groupBy","counts","from","as"],"mappings":"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"}
@@ -84,7 +84,6 @@ const MANY_RELATION_TYPES = new Set([
84
84
  if (target === 'admin::user') {
85
85
  return;
86
86
  }
87
- // @ts-expect-error — reduceToIdentity returns a narrower type; traverseEntity Data allows unknown
88
87
  set(key, reduceToIdentity(attribute, value));
89
88
  };
90
89
  /**
@@ -97,8 +96,7 @@ const MANY_RELATION_TYPES = new Set([
97
96
  return strapiUtils.traverseEntity(shapeRelationToIdentity, {
98
97
  schema: strapi.getModel(uid),
99
98
  getModel: strapi.getModel.bind(strapi)
100
- }, // @ts-expect-error — traverseEntity expects Data; post-sanitize documents are compatible at runtime
101
- data);
99
+ }, data);
102
100
  };
103
101
 
104
102
  exports.MANY_RELATION_TYPES = MANY_RELATION_TYPES;
@@ -1 +1 @@
1
- {"version":3,"file":"shape-relations.js","sources":["../../../../server/src/mcp/sanitizers/shape-relations.ts"],"sourcesContent":["import { traverseEntity } from '@strapi/utils';\nimport type { UID } from '@strapi/types';\n\n// Relations whose cardinality is \"many\" — maps to Array output.\n// Includes non-owning variants and morph-many.\nconst MANY_RELATION_TYPES = new Set([\n 'oneToMany',\n 'manyToMany',\n 'manyWay',\n 'morphToMany',\n 'morphMany',\n]);\n\n/**\n * Canonical \"is this relation a list\" predicate for the MCP output boundary.\n * Both the runtime shaping (reduceToIdentity) and the registered output schemas\n * (output-schemas.ts) MUST use this single definition — the MCP SDK validates\n * structuredContent against the schema, so any divergence fails the tool call.\n * Intentionally broader than `relations.isAnyToMany` from @strapi/utils, which\n * ignores `manyWay` and the morph variants.\n */\nexport const isManyRelationForMcp = (attribute: { relation?: string }): boolean =>\n MANY_RELATION_TYPES.has(attribute.relation ?? '');\n\ntype RelationIdentity = {\n documentId: string;\n locale?: string;\n __type?: string;\n status?: string;\n};\n\n/**\n * Extracts identity fields from a single relation entry.\n * Returns undefined when the entry has no documentId (e.g. a bare {count} object).\n */\nconst pickIdentity = (entry: unknown): RelationIdentity | undefined => {\n if (entry === null || typeof entry !== 'object') {\n return undefined;\n }\n\n const obj = entry as Record<string, unknown>;\n\n if (typeof obj.documentId !== 'string') {\n return undefined;\n }\n\n const identity: RelationIdentity = { documentId: obj.documentId };\n\n if (typeof obj.locale === 'string') {\n identity.locale = obj.locale;\n }\n\n if (typeof obj.__type === 'string') {\n identity.__type = obj.__type;\n }\n\n // Preserve the computed publish status on `localizations` entries — it is\n // calculated by formatDocumentWithMetadata BEFORE shaping (calculate, then strip).\n // Non-localization relation entries never carry a string `status`, so this is a no-op for them.\n if (typeof obj.status === 'string') {\n identity.status = obj.status;\n }\n\n return identity;\n};\n\n/**\n * Reduces a populated relation value to identity-only shape.\n *\n * to-one → { documentId, locale?, __type? } | null\n * to-many → Array<{ documentId, locale?, __type? }> ([] when empty)\n */\nconst reduceToIdentity = (\n attribute: { relation?: string },\n value: unknown\n): RelationIdentity | RelationIdentity[] | null => {\n const isMany = isManyRelationForMcp(attribute);\n\n if (isMany) {\n if (Array.isArray(value)) {\n return value.map(pickIdentity).filter((v): v is RelationIdentity => v !== undefined);\n }\n // defensive: { count: N } or null/undefined → empty array\n return [];\n }\n\n // to-one\n if (value === null || value === undefined) {\n return null;\n }\n\n if (Array.isArray(value)) {\n // should not happen, but guard: treat first element\n return pickIdentity(value[0]) ?? null;\n }\n\n return pickIdentity(value) ?? null;\n};\n\n/**\n * Post-sanitize shaping visitor.\n * Reduces every relation attribute to identity-only (documentId + locale? + __type?).\n * Skips admin::user relations — those are out of scope and preserved as-is.\n */\nconst shapeRelationToIdentity: Parameters<typeof traverseEntity>[0] = (\n { key, value, attribute },\n { set }\n) => {\n if (attribute?.type !== 'relation') {\n return;\n }\n\n const target = (attribute as { target?: string }).target;\n\n // Out of scope — admin user records are preserved untouched (tracked separately).\n if (target === 'admin::user') {\n return;\n }\n\n // @ts-expect-error — reduceToIdentity returns a narrower type; traverseEntity Data allows unknown\n set(key, reduceToIdentity(attribute as { relation?: string }, value));\n};\n\n/**\n * Applies identity shaping to all relation fields in a document, including\n * nested fields inside components, dynamic zones, and `localizations`.\n *\n * This covers leak family #1 (relation targets) and #2 (localizations full draft rows)\n * with the same single mechanism.\n */\nexport const shapeRelationsForMcp = async (\n uid: UID.Schema,\n data: Record<string, unknown>\n): Promise<Record<string, unknown>> => {\n return traverseEntity(\n shapeRelationToIdentity,\n {\n schema: strapi.getModel(uid),\n getModel: strapi.getModel.bind(strapi),\n },\n // @ts-expect-error — traverseEntity expects Data; post-sanitize documents are compatible at runtime\n data\n );\n};\n\n// Export for unit-testing without a full strapi instance\nexport { reduceToIdentity, MANY_RELATION_TYPES };\n"],"names":["MANY_RELATION_TYPES","Set","isManyRelationForMcp","attribute","has","relation","pickIdentity","entry","undefined","obj","documentId","identity","locale","__type","status","reduceToIdentity","value","isMany","Array","isArray","map","filter","v","shapeRelationToIdentity","key","set","type","target","shapeRelationsForMcp","uid","data","traverseEntity","schema","strapi","getModel","bind"],"mappings":"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"}
1
+ {"version":3,"file":"shape-relations.js","sources":["../../../../server/src/mcp/sanitizers/shape-relations.ts"],"sourcesContent":["import { traverseEntity } from '@strapi/utils';\nimport type { UID } from '@strapi/types';\n\n// Relations whose cardinality is \"many\" — maps to Array output.\n// Includes non-owning variants and morph-many.\nconst MANY_RELATION_TYPES = new Set([\n 'oneToMany',\n 'manyToMany',\n 'manyWay',\n 'morphToMany',\n 'morphMany',\n]);\n\n/**\n * Canonical \"is this relation a list\" predicate for the MCP output boundary.\n * Both the runtime shaping (reduceToIdentity) and the registered output schemas\n * (output-schemas.ts) MUST use this single definition — the MCP SDK validates\n * structuredContent against the schema, so any divergence fails the tool call.\n * Intentionally broader than `relations.isAnyToMany` from @strapi/utils, which\n * ignores `manyWay` and the morph variants.\n */\nexport const isManyRelationForMcp = (attribute: { relation?: string }): boolean =>\n MANY_RELATION_TYPES.has(attribute.relation ?? '');\n\ntype RelationIdentity = {\n documentId: string;\n locale?: string;\n __type?: string;\n status?: string;\n};\n\n/**\n * Extracts identity fields from a single relation entry.\n * Returns undefined when the entry has no documentId (e.g. a bare {count} object).\n */\nconst pickIdentity = (entry: unknown): RelationIdentity | undefined => {\n if (entry === null || typeof entry !== 'object') {\n return undefined;\n }\n\n const obj = entry as Record<string, unknown>;\n\n if (typeof obj.documentId !== 'string') {\n return undefined;\n }\n\n const identity: RelationIdentity = { documentId: obj.documentId };\n\n if (typeof obj.locale === 'string') {\n identity.locale = obj.locale;\n }\n\n if (typeof obj.__type === 'string') {\n identity.__type = obj.__type;\n }\n\n // Preserve the computed publish status on `localizations` entries — it is\n // calculated by formatDocumentWithMetadata BEFORE shaping (calculate, then strip).\n // Non-localization relation entries never carry a string `status`, so this is a no-op for them.\n if (typeof obj.status === 'string') {\n identity.status = obj.status;\n }\n\n return identity;\n};\n\n/**\n * Reduces a populated relation value to identity-only shape.\n *\n * to-one → { documentId, locale?, __type? } | null\n * to-many → Array<{ documentId, locale?, __type? }> ([] when empty)\n */\nconst reduceToIdentity = (\n attribute: { relation?: string },\n value: unknown\n): RelationIdentity | RelationIdentity[] | null => {\n const isMany = isManyRelationForMcp(attribute);\n\n if (isMany) {\n if (Array.isArray(value)) {\n return value.map(pickIdentity).filter((v): v is RelationIdentity => v !== undefined);\n }\n // defensive: { count: N } or null/undefined → empty array\n return [];\n }\n\n // to-one\n if (value === null || value === undefined) {\n return null;\n }\n\n if (Array.isArray(value)) {\n // should not happen, but guard: treat first element\n return pickIdentity(value[0]) ?? null;\n }\n\n return pickIdentity(value) ?? null;\n};\n\n/**\n * Post-sanitize shaping visitor.\n * Reduces every relation attribute to identity-only (documentId + locale? + __type?).\n * Skips admin::user relations — those are out of scope and preserved as-is.\n */\nconst shapeRelationToIdentity: Parameters<typeof traverseEntity>[0] = (\n { key, value, attribute },\n { set }\n) => {\n if (attribute?.type !== 'relation') {\n return;\n }\n\n const target = (attribute as { target?: string }).target;\n\n // Out of scope — admin user records are preserved untouched (tracked separately).\n if (target === 'admin::user') {\n return;\n }\n\n set(key, reduceToIdentity(attribute as { relation?: string }, value));\n};\n\n/**\n * Applies identity shaping to all relation fields in a document, including\n * nested fields inside components, dynamic zones, and `localizations`.\n *\n * This covers leak family #1 (relation targets) and #2 (localizations full draft rows)\n * with the same single mechanism.\n */\nexport const shapeRelationsForMcp = async (\n uid: UID.Schema,\n data: Record<string, unknown>\n): Promise<Record<string, unknown>> => {\n return traverseEntity(\n shapeRelationToIdentity,\n {\n schema: strapi.getModel(uid),\n getModel: strapi.getModel.bind(strapi),\n },\n data\n );\n};\n\n// Export for unit-testing without a full strapi instance\nexport { reduceToIdentity, MANY_RELATION_TYPES };\n"],"names":["MANY_RELATION_TYPES","Set","isManyRelationForMcp","attribute","has","relation","pickIdentity","entry","undefined","obj","documentId","identity","locale","__type","status","reduceToIdentity","value","isMany","Array","isArray","map","filter","v","shapeRelationToIdentity","key","set","type","target","shapeRelationsForMcp","uid","data","traverseEntity","schema","strapi","getModel","bind"],"mappings":"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"}
@@ -82,7 +82,6 @@ const MANY_RELATION_TYPES = new Set([
82
82
  if (target === 'admin::user') {
83
83
  return;
84
84
  }
85
- // @ts-expect-error — reduceToIdentity returns a narrower type; traverseEntity Data allows unknown
86
85
  set(key, reduceToIdentity(attribute, value));
87
86
  };
88
87
  /**
@@ -95,8 +94,7 @@ const MANY_RELATION_TYPES = new Set([
95
94
  return traverseEntity(shapeRelationToIdentity, {
96
95
  schema: strapi.getModel(uid),
97
96
  getModel: strapi.getModel.bind(strapi)
98
- }, // @ts-expect-error — traverseEntity expects Data; post-sanitize documents are compatible at runtime
99
- data);
97
+ }, data);
100
98
  };
101
99
 
102
100
  export { MANY_RELATION_TYPES, isManyRelationForMcp, reduceToIdentity, shapeRelationsForMcp };