@strapi/content-manager 5.56.0 → 5.57.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 (203) hide show
  1. package/dist/admin/components/ConfigurationForm/Form.js +3 -3
  2. package/dist/admin/components/ConfigurationForm/Form.js.map +1 -1
  3. package/dist/admin/components/ConfigurationForm/Form.mjs +2 -2
  4. package/dist/admin/components/ConfigurationForm/Form.mjs.map +1 -1
  5. package/dist/admin/components/RelativeTime.js +9 -0
  6. package/dist/admin/components/RelativeTime.js.map +1 -1
  7. package/dist/admin/components/RelativeTime.mjs +10 -1
  8. package/dist/admin/components/RelativeTime.mjs.map +1 -1
  9. package/dist/admin/features/DocumentRBAC.js +0 -3
  10. package/dist/admin/features/DocumentRBAC.js.map +1 -1
  11. package/dist/admin/features/DocumentRBAC.mjs +1 -4
  12. package/dist/admin/features/DocumentRBAC.mjs.map +1 -1
  13. package/dist/admin/history/components/VersionContent.js +3 -3
  14. package/dist/admin/history/components/VersionContent.js.map +1 -1
  15. package/dist/admin/history/components/VersionContent.mjs +2 -2
  16. package/dist/admin/history/components/VersionContent.mjs.map +1 -1
  17. package/dist/admin/history/pages/History.js +2 -1
  18. package/dist/admin/history/pages/History.js.map +1 -1
  19. package/dist/admin/history/pages/History.mjs +3 -2
  20. package/dist/admin/history/pages/History.mjs.map +1 -1
  21. package/dist/admin/pages/EditView/EditViewPage.js +2 -2
  22. package/dist/admin/pages/EditView/EditViewPage.js.map +1 -1
  23. package/dist/admin/pages/EditView/EditViewPage.mjs +2 -2
  24. package/dist/admin/pages/EditView/EditViewPage.mjs.map +1 -1
  25. package/dist/admin/pages/EditView/components/DocumentActions.js +37 -2
  26. package/dist/admin/pages/EditView/components/DocumentActions.js.map +1 -1
  27. package/dist/admin/pages/EditView/components/DocumentActions.mjs +37 -3
  28. package/dist/admin/pages/EditView/components/DocumentActions.mjs.map +1 -1
  29. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksToolbar.js +1 -0
  30. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksToolbar.js.map +1 -1
  31. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksToolbar.mjs +1 -0
  32. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksToolbar.mjs.map +1 -1
  33. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.js +3 -3
  34. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.js.map +1 -1
  35. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.mjs +2 -2
  36. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.mjs.map +1 -1
  37. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.js +30 -2
  38. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.js.map +1 -1
  39. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.mjs +31 -4
  40. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.mjs.map +1 -1
  41. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js +14 -2
  42. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js.map +1 -1
  43. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs +14 -2
  44. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs.map +1 -1
  45. package/dist/admin/pages/EditView/utils/data.js +3 -3
  46. package/dist/admin/pages/EditView/utils/data.js.map +1 -1
  47. package/dist/admin/pages/EditView/utils/data.mjs +2 -2
  48. package/dist/admin/pages/EditView/utils/data.mjs.map +1 -1
  49. package/dist/admin/pages/ListView/ListViewPage.js +4 -3
  50. package/dist/admin/pages/ListView/ListViewPage.js.map +1 -1
  51. package/dist/admin/pages/ListView/ListViewPage.mjs +4 -3
  52. package/dist/admin/pages/ListView/ListViewPage.mjs.map +1 -1
  53. package/dist/admin/preview/pages/Preview.js +2 -1
  54. package/dist/admin/preview/pages/Preview.js.map +1 -1
  55. package/dist/admin/preview/pages/Preview.mjs +3 -2
  56. package/dist/admin/preview/pages/Preview.mjs.map +1 -1
  57. package/dist/admin/services/documents.js +3 -2
  58. package/dist/admin/services/documents.js.map +1 -1
  59. package/dist/admin/services/documents.mjs +2 -2
  60. package/dist/admin/services/documents.mjs.map +1 -1
  61. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +2 -1
  62. package/dist/admin/src/pages/EditView/components/FormInputs/Relations/RelationModal.d.ts +19 -2
  63. package/dist/admin/src/services/documents.d.ts +10 -2
  64. package/dist/admin/translations/en.json.js +2 -0
  65. package/dist/admin/translations/en.json.js.map +1 -1
  66. package/dist/admin/translations/en.json.mjs +2 -0
  67. package/dist/admin/translations/en.json.mjs.map +1 -1
  68. package/dist/admin/utils/validation.js +3 -3
  69. package/dist/admin/utils/validation.js.map +1 -1
  70. package/dist/admin/utils/validation.mjs +2 -2
  71. package/dist/admin/utils/validation.mjs.map +1 -1
  72. package/dist/server/controllers/collection-types.js +4 -4
  73. package/dist/server/controllers/collection-types.js.map +1 -1
  74. package/dist/server/controllers/collection-types.mjs +4 -4
  75. package/dist/server/controllers/collection-types.mjs.map +1 -1
  76. package/dist/server/controllers/content-types.js +5 -7
  77. package/dist/server/controllers/content-types.js.map +1 -1
  78. package/dist/server/controllers/content-types.mjs +5 -7
  79. package/dist/server/controllers/content-types.mjs.map +1 -1
  80. package/dist/server/controllers/relations.js +19 -15
  81. package/dist/server/controllers/relations.js.map +1 -1
  82. package/dist/server/controllers/relations.mjs +17 -13
  83. package/dist/server/controllers/relations.mjs.map +1 -1
  84. package/dist/server/controllers/utils/clone.js +3 -4
  85. package/dist/server/controllers/utils/clone.js.map +1 -1
  86. package/dist/server/controllers/utils/clone.mjs +4 -5
  87. package/dist/server/controllers/utils/clone.mjs.map +1 -1
  88. package/dist/server/controllers/validation/index.js +1 -1
  89. package/dist/server/controllers/validation/index.mjs +1 -1
  90. package/dist/server/history/controllers/history-version.js +3 -3
  91. package/dist/server/history/controllers/history-version.js.map +1 -1
  92. package/dist/server/history/controllers/history-version.mjs +3 -3
  93. package/dist/server/history/controllers/history-version.mjs.map +1 -1
  94. package/dist/server/history/services/history.js +4 -4
  95. package/dist/server/history/services/history.js.map +1 -1
  96. package/dist/server/history/services/history.mjs +4 -4
  97. package/dist/server/history/services/history.mjs.map +1 -1
  98. package/dist/server/history/services/lifecycles.js +8 -8
  99. package/dist/server/history/services/lifecycles.js.map +1 -1
  100. package/dist/server/history/services/lifecycles.mjs +3 -3
  101. package/dist/server/history/services/lifecycles.mjs.map +1 -1
  102. package/dist/server/history/services/utils.js +7 -10
  103. package/dist/server/history/services/utils.js.map +1 -1
  104. package/dist/server/history/services/utils.mjs +5 -8
  105. package/dist/server/history/services/utils.mjs.map +1 -1
  106. package/dist/server/mcp/permissions.js +2 -2
  107. package/dist/server/mcp/permissions.js.map +1 -1
  108. package/dist/server/mcp/permissions.mjs +2 -2
  109. package/dist/server/mcp/permissions.mjs.map +1 -1
  110. package/dist/server/mcp/register-content-manager-mcp-tools.js +7 -5
  111. package/dist/server/mcp/register-content-manager-mcp-tools.js.map +1 -1
  112. package/dist/server/mcp/register-content-manager-mcp-tools.mjs +7 -5
  113. package/dist/server/mcp/register-content-manager-mcp-tools.mjs.map +1 -1
  114. package/dist/server/preview/controllers/previewScript.js +30 -15
  115. package/dist/server/preview/controllers/validation/preview.js +3 -3
  116. package/dist/server/preview/controllers/validation/preview.js.map +1 -1
  117. package/dist/server/preview/controllers/validation/preview.mjs +3 -3
  118. package/dist/server/preview/controllers/validation/preview.mjs.map +1 -1
  119. package/dist/server/services/components.js +3 -3
  120. package/dist/server/services/components.js.map +1 -1
  121. package/dist/server/services/components.mjs +3 -3
  122. package/dist/server/services/components.mjs.map +1 -1
  123. package/dist/server/services/content-structure.js +2 -2
  124. package/dist/server/services/content-structure.js.map +1 -1
  125. package/dist/server/services/content-structure.mjs +2 -2
  126. package/dist/server/services/content-structure.mjs.map +1 -1
  127. package/dist/server/services/content-types.js +3 -3
  128. package/dist/server/services/content-types.js.map +1 -1
  129. package/dist/server/services/content-types.mjs +3 -3
  130. package/dist/server/services/content-types.mjs.map +1 -1
  131. package/dist/server/services/data-mapper.js +3 -3
  132. package/dist/server/services/data-mapper.js.map +1 -1
  133. package/dist/server/services/data-mapper.mjs +3 -3
  134. package/dist/server/services/data-mapper.mjs.map +1 -1
  135. package/dist/server/services/document-manager.js +6 -6
  136. package/dist/server/services/document-manager.js.map +1 -1
  137. package/dist/server/services/document-manager.mjs +6 -6
  138. package/dist/server/services/document-manager.mjs.map +1 -1
  139. package/dist/server/services/document-metadata.js +29 -31
  140. package/dist/server/services/document-metadata.js.map +1 -1
  141. package/dist/server/services/document-metadata.mjs +29 -31
  142. package/dist/server/services/document-metadata.mjs.map +1 -1
  143. package/dist/server/services/metrics.js +3 -3
  144. package/dist/server/services/metrics.js.map +1 -1
  145. package/dist/server/services/metrics.mjs +3 -3
  146. package/dist/server/services/metrics.mjs.map +1 -1
  147. package/dist/server/services/permission-checker.js +2 -2
  148. package/dist/server/services/permission-checker.js.map +1 -1
  149. package/dist/server/services/permission-checker.mjs +1 -1
  150. package/dist/server/services/permission-checker.mjs.map +1 -1
  151. package/dist/server/services/permission.js +2 -3
  152. package/dist/server/services/permission.js.map +1 -1
  153. package/dist/server/services/permission.mjs +2 -3
  154. package/dist/server/services/permission.mjs.map +1 -1
  155. package/dist/server/services/populate-builder.js +4 -4
  156. package/dist/server/services/populate-builder.js.map +1 -1
  157. package/dist/server/services/populate-builder.mjs +4 -4
  158. package/dist/server/services/populate-builder.mjs.map +1 -1
  159. package/dist/server/services/utils/configuration/attributes.js +1 -2
  160. package/dist/server/services/utils/configuration/attributes.js.map +1 -1
  161. package/dist/server/services/utils/configuration/attributes.mjs +2 -3
  162. package/dist/server/services/utils/configuration/attributes.mjs.map +1 -1
  163. package/dist/server/services/utils/configuration/settings.js +4 -7
  164. package/dist/server/services/utils/configuration/settings.js.map +1 -1
  165. package/dist/server/services/utils/configuration/settings.mjs +2 -5
  166. package/dist/server/services/utils/configuration/settings.mjs.map +1 -1
  167. package/dist/server/services/utils/draft.js +8 -7
  168. package/dist/server/services/utils/draft.js.map +1 -1
  169. package/dist/server/services/utils/draft.mjs +6 -5
  170. package/dist/server/services/utils/draft.mjs.map +1 -1
  171. package/dist/server/services/utils/populate.js +10 -11
  172. package/dist/server/services/utils/populate.js.map +1 -1
  173. package/dist/server/services/utils/populate.mjs +9 -10
  174. package/dist/server/services/utils/populate.mjs.map +1 -1
  175. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  176. package/dist/server/src/history/controllers/history-version.d.ts.map +1 -1
  177. package/dist/server/src/history/services/utils.d.ts +2 -2
  178. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  179. package/dist/server/src/index.d.ts +1 -1
  180. package/dist/server/src/index.d.ts.map +1 -1
  181. package/dist/server/src/mcp/permissions.d.ts +1 -1
  182. package/dist/server/src/mcp/permissions.d.ts.map +1 -1
  183. package/dist/server/src/mcp/register-content-manager-mcp-tools.d.ts +3 -1
  184. package/dist/server/src/mcp/register-content-manager-mcp-tools.d.ts.map +1 -1
  185. package/dist/server/src/mcp/types.d.ts +2 -2
  186. package/dist/server/src/services/content-types.d.ts.map +1 -1
  187. package/dist/server/src/services/data-mapper.d.ts +1 -1
  188. package/dist/server/src/services/data-mapper.d.ts.map +1 -1
  189. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  190. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  191. package/dist/server/src/services/index.d.ts +1 -1
  192. package/dist/server/src/services/index.d.ts.map +1 -1
  193. package/dist/server/src/services/metrics.d.ts.map +1 -1
  194. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  195. package/dist/server/src/services/permission.d.ts.map +1 -1
  196. package/dist/server/src/services/populate-builder.d.ts.map +1 -1
  197. package/dist/server/src/services/utils/configuration/index.d.ts +10 -1
  198. package/dist/server/src/services/utils/configuration/index.d.ts.map +1 -1
  199. package/dist/server/src/services/utils/configuration/settings.d.ts +10 -1
  200. package/dist/server/src/services/utils/configuration/settings.d.ts.map +1 -1
  201. package/dist/server/src/services/utils/draft.d.ts.map +1 -1
  202. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  203. package/package.json +6 -6
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var fp = require('lodash/fp');
3
+ var _ = require('lodash');
4
4
  var strapiUtils = require('@strapi/utils');
5
5
  var constants = require('../constants.js');
6
6
 
@@ -11,7 +11,7 @@ const createServiceUtils = ({ strapi })=>{
11
11
  * Get the difference between the version schema and the content type schema
12
12
  */ const getSchemaAttributesDiff = (versionSchemaAttributes, contentTypeSchemaAttributes)=>{
13
13
  // Omit the same fields that were omitted when creating a history version
14
- const sanitizedContentTypeSchemaAttributes = fp.omit(constants.FIELDS_TO_IGNORE, contentTypeSchemaAttributes);
14
+ const sanitizedContentTypeSchemaAttributes = _.omit(contentTypeSchemaAttributes, constants.FIELDS_TO_IGNORE);
15
15
  const reduceDifferenceToAttributesObject = (diffKeys, source)=>{
16
16
  return diffKeys.reduce((previousAttributesObject, diffKey)=>{
17
17
  previousAttributesObject[diffKey] = source[diffKey];
@@ -21,10 +21,10 @@ const createServiceUtils = ({ strapi })=>{
21
21
  const versionSchemaKeys = Object.keys(versionSchemaAttributes);
22
22
  const contentTypeSchemaAttributesKeys = Object.keys(sanitizedContentTypeSchemaAttributes);
23
23
  // The attribute is new if it's on the content type schema but not on the version schema
24
- const uniqueToContentType = fp.difference(contentTypeSchemaAttributesKeys, versionSchemaKeys);
24
+ const uniqueToContentType = _.difference(contentTypeSchemaAttributesKeys, versionSchemaKeys);
25
25
  const added = reduceDifferenceToAttributesObject(uniqueToContentType, sanitizedContentTypeSchemaAttributes);
26
26
  // The attribute was removed or renamed if it's on the version schema but not on the content type schema
27
- const uniqueToVersion = fp.difference(versionSchemaKeys, contentTypeSchemaAttributesKeys);
27
+ const uniqueToVersion = _.difference(versionSchemaKeys, contentTypeSchemaAttributesKeys);
28
28
  const removed = reduceDifferenceToAttributesObject(uniqueToVersion, versionSchemaAttributes);
29
29
  return {
30
30
  added,
@@ -74,17 +74,14 @@ const createServiceUtils = ({ strapi })=>{
74
74
  }
75
75
  });
76
76
  };
77
- const localesService = strapi.plugin('i18n')?.service('locales');
78
- const i18nContentTypeService = strapi.plugin('i18n')?.service('content-types');
79
- const getDefaultLocale = async ()=>localesService ? localesService.getDefaultLocale() : null;
80
- const isLocalizedContentType = (model)=>i18nContentTypeService ? i18nContentTypeService.isLocalizedContentType(model) : false;
77
+ const getDefaultLocale = async ()=>strapi.localization.getDefaultLocale();
78
+ const isLocalizedContentType = (model)=>strapi.localization.isLocalizedContentType(model);
81
79
  /**
82
80
  *
83
81
  * @description
84
82
  * Creates a dictionary of all locales available
85
83
  */ const getLocaleDictionary = async ()=>{
86
- if (!localesService) return {};
87
- const locales = await localesService.find() || [];
84
+ const locales = await strapi.localization.getLocales();
88
85
  return locales.reduce((acc, locale)=>{
89
86
  acc[locale.code] = {
90
87
  name: locale.name,
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sources":["../../../../server/src/history/services/utils.ts"],"sourcesContent":["import { difference, omit } from 'lodash/fp';\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\nconst DEFAULT_RETENTION_DAYS = 90;\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 FIELDS_TO_IGNORE,\n contentTypeSchemaAttributes\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 localesService = strapi.plugin('i18n')?.service('locales');\n const i18nContentTypeService = strapi.plugin('i18n')?.service('content-types');\n\n const getDefaultLocale = async () => (localesService ? localesService.getDefaultLocale() : null);\n\n const isLocalizedContentType = (model: Schema.ContentType) =>\n i18nContentTypeService ? i18nContentTypeService.isLocalizedContentType(model) : false;\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 if (!localesService) return {};\n\n const locales = (await localesService.find()) || [];\n return locales.reduce(\n (\n acc: Record<string, NonNullable<HistoryVersions.HistoryVersionDataResponse['locale']>>,\n locale: NonNullable<HistoryVersions.HistoryVersionDataResponse['locale']>\n ) => {\n acc[locale.code] = { name: locale.name, code: locale.code };\n\n return acc;\n },\n {}\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 const licenseRetentionDays =\n typeof featureConfig === 'object' && featureConfig?.options.retentionDays;\n const userRetentionDays: number = strapi.config.get('admin.history.retentionDays');\n\n // Allow users to override the license retention days, but not to increase it\n if (userRetentionDays && userRetentionDays < licenseRetentionDays) {\n return userRetentionDays;\n }\n\n // User didn't provide retention days value, use the license or fallback to default\n return Math.min(licenseRetentionDays, DEFAULT_RETENTION_DAYS);\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","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","localesService","plugin","service","i18nContentTypeService","getDefaultLocale","isLocalizedContentType","model","getLocaleDictionary","locales","find","acc","code","name","getRetentionDays","featureConfig","ee","features","get","licenseRetentionDays","options","retentionDays","userRetentionDays","config","Math","min","getVersionStatus","contentTypeUid","document","documentMetadataService","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.js","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\nconst DEFAULT_RETENTION_DAYS = 90;\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 const licenseRetentionDays =\n typeof featureConfig === 'object' && featureConfig?.options.retentionDays;\n const userRetentionDays: number = strapi.config.get('admin.history.retentionDays');\n\n // Allow users to override the license retention days, but not to increase it\n if (userRetentionDays && userRetentionDays < licenseRetentionDays) {\n return userRetentionDays;\n }\n\n // User didn't provide retention days value, use the license or fallback to default\n return Math.min(licenseRetentionDays, DEFAULT_RETENTION_DAYS);\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","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","licenseRetentionDays","options","retentionDays","userRetentionDays","config","Math","min","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,4 +1,4 @@
1
- import { omit, difference } from 'lodash/fp';
1
+ import { omit, difference } from 'lodash';
2
2
  import { contentTypes } from '@strapi/utils';
3
3
  import { FIELDS_TO_IGNORE } from '../constants.mjs';
4
4
 
@@ -9,7 +9,7 @@ const createServiceUtils = ({ strapi })=>{
9
9
  * Get the difference between the version schema and the content type schema
10
10
  */ const getSchemaAttributesDiff = (versionSchemaAttributes, contentTypeSchemaAttributes)=>{
11
11
  // Omit the same fields that were omitted when creating a history version
12
- const sanitizedContentTypeSchemaAttributes = omit(FIELDS_TO_IGNORE, contentTypeSchemaAttributes);
12
+ const sanitizedContentTypeSchemaAttributes = omit(contentTypeSchemaAttributes, FIELDS_TO_IGNORE);
13
13
  const reduceDifferenceToAttributesObject = (diffKeys, source)=>{
14
14
  return diffKeys.reduce((previousAttributesObject, diffKey)=>{
15
15
  previousAttributesObject[diffKey] = source[diffKey];
@@ -72,17 +72,14 @@ const createServiceUtils = ({ strapi })=>{
72
72
  }
73
73
  });
74
74
  };
75
- const localesService = strapi.plugin('i18n')?.service('locales');
76
- const i18nContentTypeService = strapi.plugin('i18n')?.service('content-types');
77
- const getDefaultLocale = async ()=>localesService ? localesService.getDefaultLocale() : null;
78
- const isLocalizedContentType = (model)=>i18nContentTypeService ? i18nContentTypeService.isLocalizedContentType(model) : false;
75
+ const getDefaultLocale = async ()=>strapi.localization.getDefaultLocale();
76
+ const isLocalizedContentType = (model)=>strapi.localization.isLocalizedContentType(model);
79
77
  /**
80
78
  *
81
79
  * @description
82
80
  * Creates a dictionary of all locales available
83
81
  */ const getLocaleDictionary = async ()=>{
84
- if (!localesService) return {};
85
- const locales = await localesService.find() || [];
82
+ const locales = await strapi.localization.getLocales();
86
83
  return locales.reduce((acc, locale)=>{
87
84
  acc[locale.code] = {
88
85
  name: locale.name,
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","sources":["../../../../server/src/history/services/utils.ts"],"sourcesContent":["import { difference, omit } from 'lodash/fp';\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\nconst DEFAULT_RETENTION_DAYS = 90;\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 FIELDS_TO_IGNORE,\n contentTypeSchemaAttributes\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 localesService = strapi.plugin('i18n')?.service('locales');\n const i18nContentTypeService = strapi.plugin('i18n')?.service('content-types');\n\n const getDefaultLocale = async () => (localesService ? localesService.getDefaultLocale() : null);\n\n const isLocalizedContentType = (model: Schema.ContentType) =>\n i18nContentTypeService ? i18nContentTypeService.isLocalizedContentType(model) : false;\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 if (!localesService) return {};\n\n const locales = (await localesService.find()) || [];\n return locales.reduce(\n (\n acc: Record<string, NonNullable<HistoryVersions.HistoryVersionDataResponse['locale']>>,\n locale: NonNullable<HistoryVersions.HistoryVersionDataResponse['locale']>\n ) => {\n acc[locale.code] = { name: locale.name, code: locale.code };\n\n return acc;\n },\n {}\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 const licenseRetentionDays =\n typeof featureConfig === 'object' && featureConfig?.options.retentionDays;\n const userRetentionDays: number = strapi.config.get('admin.history.retentionDays');\n\n // Allow users to override the license retention days, but not to increase it\n if (userRetentionDays && userRetentionDays < licenseRetentionDays) {\n return userRetentionDays;\n }\n\n // User didn't provide retention days value, use the license or fallback to default\n return Math.min(licenseRetentionDays, DEFAULT_RETENTION_DAYS);\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","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","localesService","plugin","service","i18nContentTypeService","getDefaultLocale","isLocalizedContentType","model","getLocaleDictionary","locales","find","acc","code","name","getRetentionDays","featureConfig","ee","features","get","licenseRetentionDays","options","retentionDays","userRetentionDays","config","Math","min","getVersionStatus","contentTypeUid","document","documentMetadataService","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\nconst DEFAULT_RETENTION_DAYS = 90;\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 const licenseRetentionDays =\n typeof featureConfig === 'object' && featureConfig?.options.retentionDays;\n const userRetentionDays: number = strapi.config.get('admin.history.retentionDays');\n\n // Allow users to override the license retention days, but not to increase it\n if (userRetentionDays && userRetentionDays < licenseRetentionDays) {\n return userRetentionDays;\n }\n\n // User didn't provide retention days value, use the license or fallback to default\n return Math.min(licenseRetentionDays, DEFAULT_RETENTION_DAYS);\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","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","licenseRetentionDays","options","retentionDays","userRetentionDays","config","Math","min","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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"}
@@ -3,10 +3,10 @@
3
3
  var strapiUtils = require('@strapi/utils');
4
4
  var index = require('../utils/index.js');
5
5
 
6
- /** Returns true if the content type identified by `uid` has the i18n `localized` plugin option enabled. */ const isContentTypeLocalized = (strapi, uid)=>{
6
+ /** Returns true if the registered localization provider reports the content type identified by `uid` as localized. */ const isContentTypeLocalized = (strapi, uid)=>{
7
7
  const ct = strapi.contentTypes?.[uid];
8
8
  if (ct === undefined) return false;
9
- return ct.pluginOptions?.i18n?.localized === true;
9
+ return strapi.localization.isLocalizedContentType(ct);
10
10
  };
11
11
  const localeDefaultDescription = (defaultLocale, allowedLocales)=>{
12
12
  if (defaultLocale !== null && allowedLocales.includes(defaultLocale)) {
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.js","sources":["../../../server/src/mcp/permissions.ts"],"sourcesContent":["import { z } from '@strapi/utils';\nimport type { Core, Modules, Struct, UID } from '@strapi/types';\n\nimport { getService } from '../utils';\nimport type { ContentManagerModelForMcp } from './types';\n\n/** Returns true if the content type identified by `uid` has the i18n `localized` plugin option enabled. */\nexport const isContentTypeLocalized = (strapi: Core.Strapi, uid: string): boolean => {\n const ct = strapi.contentTypes?.[uid as UID.ContentType];\n if (ct === undefined) return false;\n return (\n (ct as { pluginOptions?: { i18n?: { localized?: boolean } } }).pluginOptions?.i18n\n ?.localized === true\n );\n};\n\nconst localeDefaultDescription = (\n defaultLocale: string | null,\n allowedLocales: readonly string[]\n): string => {\n if (defaultLocale !== null && allowedLocales.includes(defaultLocale)) {\n return `Defaults to \"${defaultLocale}\".`;\n }\n\n return 'Defaults to the default locale.';\n};\n\n/**\n * Builds the base locale Zod schema for a derived MCP tool input.\n * When `localeCodes` is provided, constrains the field to a `z.enum` of available codes\n * with an optional default; otherwise falls back to a plain optional string.\n */\nexport const buildLocaleSchema = (\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null\n): z.ZodTypeAny => {\n if (localeCodes !== null && localeCodes.length > 0) {\n let schema: z.ZodTypeAny = z.enum(localeCodes).optional();\n\n if (defaultLocale !== null && localeCodes.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Available: ${localeCodes.join(', ')}. ${localeDefaultDescription(defaultLocale, localeCodes)}`\n );\n }\n\n return z\n .string()\n .optional()\n .describe('Locale code (e.g. \"en\", \"fr\"). Defaults to the default locale.');\n};\n\n/**\n * Narrows the base locale schema to only locales the session is permitted to access\n * for the given action + uid combination.\n *\n * Returns the base schema unchanged when:\n * - localeCodes is null (i18n not installed)\n * - the content type is not localized\n * - all installed locales are permitted for this action\n *\n * Returns z.never().optional() when no locales are permitted, keeping the tool\n * registered but signalling no valid locale input.\n */\nexport const resolvePermittedLocaleSchema = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext,\n action: string,\n uid: string,\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null,\n baseLocaleSchema: z.ZodTypeAny\n): z.ZodTypeAny => {\n if (localeCodes === null) return baseLocaleSchema;\n\n const isLocalized = isContentTypeLocalized(strapi, uid);\n if (isLocalized === false) {\n return z.string().optional().describe('This content type is not localized. Locale is ignored.');\n }\n\n const permissionChecker = getService('permission-checker').create({\n userAbility: context.userAbility,\n model: uid,\n });\n const permitted = getPermittedLocales(permissionChecker, action, localeCodes);\n if (permitted === null) return baseLocaleSchema;\n if (permitted.length === 0) {\n return z.never().optional().describe('No locale access for this action.');\n }\n\n let schema: z.ZodTypeAny = z.enum(permitted).optional();\n\n if (defaultLocale !== null && permitted.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Permitted: ${permitted.join(', ')}. ${localeDefaultDescription(defaultLocale, permitted)}`\n );\n};\n\n/**\n * Recursively resolves leaf field paths for a component, matching the nested\n * path format used by CASL rules (e.g. 'SEO.title', 'SEO.og.image').\n *\n * The admin RBAC system decomposes component attrs into nested paths and removes\n * the parent key — so checking `ability.can(action, uid, 'SEO')` returns false\n * even when the user has full access to the component's sub-fields.\n */\nexport const getComponentLeafPaths = (\n strapi: Core.Strapi,\n componentUid: string,\n prefix: string,\n visited: Set<string> = new Set()\n): string[] => {\n if (visited.has(componentUid) === true) return [prefix];\n\n type ComponentEntry = { attributes: Record<string, { type: string; component?: string }> };\n const component = (strapi.components as unknown as Record<string, ComponentEntry | undefined>)[\n componentUid\n ];\n if (component === undefined) return [prefix];\n\n visited.add(componentUid);\n const paths: string[] = [];\n\n for (const [key, attr] of Object.entries(component.attributes)) {\n if (key === 'id') {\n // skip system id field — it is not a user-facing permission path\n // eslint-disable-next-line no-continue\n continue;\n }\n const fieldPath = `${prefix}.${key}`;\n\n if (attr.type === 'component' && attr.component !== undefined) {\n paths.push(...getComponentLeafPaths(strapi, attr.component, fieldPath, visited));\n } else {\n paths.push(fieldPath);\n }\n }\n\n visited.delete(componentUid);\n\n return paths.length > 0 ? paths : [prefix];\n};\n\n/**\n * Returns the subset of attribute keys the session may access for `action` on `uid`.\n * Returns `null` when all fields are permitted (caller should skip field filtering).\n * Component attributes are resolved to their nested leaf paths before checking CASL rules.\n */\nexport const getPermittedFields = (\n strapi: Core.Strapi,\n userAbility: Modules.MCP.McpHandlerContext['userAbility'],\n action: string,\n uid: string,\n attributes: Struct.SchemaAttributes\n): Set<string> | null => {\n const allKeys = Object.keys(attributes);\n const permitted = allKeys.filter((key) => {\n if (userAbility.can(action, uid, key) === true) return true;\n\n // Component attrs: CASL rules use nested paths (e.g. 'SEO.title').\n // Check if at least one sub-field path is permitted.\n const attr = attributes[key] as { type: string; component?: string };\n if (attr.type === 'component' && attr.component !== undefined) {\n const leafPaths = getComponentLeafPaths(strapi, attr.component, key);\n return leafPaths.some((path) => userAbility.can(action, uid, path) === true);\n }\n\n return false;\n });\n\n if (permitted.length === allKeys.length) {\n return null;\n }\n\n return new Set(permitted);\n};\n\n/**\n * Filters `localeCodes` to only those the session may access for `action`.\n * Returns `null` when all locales are permitted (caller should use the unfiltered base schema).\n * Returns an empty tuple-like array when no locale is permitted.\n */\nexport const getPermittedLocales = (\n permissionChecker: { cannot: (action: string, entity?: unknown) => boolean },\n action: string,\n localeCodes: [string, ...string[]]\n): [string, ...string[]] | null => {\n const permitted = localeCodes.filter(\n (code) => permissionChecker.cannot(action, { locale: code }) === false\n );\n\n if (permitted.length === localeCodes.length) {\n return null;\n }\n\n return permitted.length > 0\n ? (permitted as [string, ...string[]])\n : ([] as unknown as [string, ...string[]]);\n};\n\n// Re-export for use in handler files (avoids needing to import from permissions in each handler)\nexport type { ContentManagerModelForMcp };\n"],"names":["isContentTypeLocalized","strapi","uid","ct","contentTypes","undefined","pluginOptions","i18n","localized","localeDefaultDescription","defaultLocale","allowedLocales","includes","buildLocaleSchema","localeCodes","length","schema","z","enum","optional","default","describe","join","string","resolvePermittedLocaleSchema","context","action","baseLocaleSchema","isLocalized","permissionChecker","getService","create","userAbility","model","permitted","getPermittedLocales","never","getComponentLeafPaths","componentUid","prefix","visited","Set","has","component","components","add","paths","key","attr","Object","entries","attributes","fieldPath","type","push","delete","getPermittedFields","allKeys","keys","filter","can","leafPaths","some","path","code","cannot","locale"],"mappings":"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"}
1
+ {"version":3,"file":"permissions.js","sources":["../../../server/src/mcp/permissions.ts"],"sourcesContent":["import { z } from '@strapi/utils';\nimport type { Core, Modules, Struct, UID } from '@strapi/types';\n\nimport { getService } from '../utils';\nimport type { ContentManagerModelForMcp } from './types';\n\n/** Returns true if the registered localization provider reports the content type identified by `uid` as localized. */\nexport const isContentTypeLocalized = (strapi: Core.Strapi, uid: string): boolean => {\n const ct = strapi.contentTypes?.[uid as UID.ContentType];\n if (ct === undefined) return false;\n return strapi.localization.isLocalizedContentType(ct);\n};\n\nconst localeDefaultDescription = (\n defaultLocale: string | null,\n allowedLocales: readonly string[]\n): string => {\n if (defaultLocale !== null && allowedLocales.includes(defaultLocale)) {\n return `Defaults to \"${defaultLocale}\".`;\n }\n\n return 'Defaults to the default locale.';\n};\n\n/**\n * Builds the base locale Zod schema for a derived MCP tool input.\n * When `localeCodes` is provided, constrains the field to a `z.enum` of available codes\n * with an optional default; otherwise falls back to a plain optional string.\n */\nexport const buildLocaleSchema = (\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null\n): z.ZodTypeAny => {\n if (localeCodes !== null && localeCodes.length > 0) {\n let schema: z.ZodTypeAny = z.enum(localeCodes).optional();\n\n if (defaultLocale !== null && localeCodes.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Available: ${localeCodes.join(', ')}. ${localeDefaultDescription(defaultLocale, localeCodes)}`\n );\n }\n\n return z\n .string()\n .optional()\n .describe('Locale code (e.g. \"en\", \"fr\"). Defaults to the default locale.');\n};\n\n/**\n * Narrows the base locale schema to only locales the session is permitted to access\n * for the given action + uid combination.\n *\n * Returns the base schema unchanged when:\n * - localeCodes is null (i18n not installed)\n * - the content type is not localized\n * - all installed locales are permitted for this action\n *\n * Returns z.never().optional() when no locales are permitted, keeping the tool\n * registered but signalling no valid locale input.\n */\nexport const resolvePermittedLocaleSchema = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext,\n action: string,\n uid: string,\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null,\n baseLocaleSchema: z.ZodTypeAny\n): z.ZodTypeAny => {\n if (localeCodes === null) return baseLocaleSchema;\n\n const isLocalized = isContentTypeLocalized(strapi, uid);\n if (isLocalized === false) {\n return z.string().optional().describe('This content type is not localized. Locale is ignored.');\n }\n\n const permissionChecker = getService('permission-checker').create({\n userAbility: context.userAbility,\n model: uid,\n });\n const permitted = getPermittedLocales(permissionChecker, action, localeCodes);\n if (permitted === null) return baseLocaleSchema;\n if (permitted.length === 0) {\n return z.never().optional().describe('No locale access for this action.');\n }\n\n let schema: z.ZodTypeAny = z.enum(permitted).optional();\n\n if (defaultLocale !== null && permitted.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Permitted: ${permitted.join(', ')}. ${localeDefaultDescription(defaultLocale, permitted)}`\n );\n};\n\n/**\n * Recursively resolves leaf field paths for a component, matching the nested\n * path format used by CASL rules (e.g. 'SEO.title', 'SEO.og.image').\n *\n * The admin RBAC system decomposes component attrs into nested paths and removes\n * the parent key — so checking `ability.can(action, uid, 'SEO')` returns false\n * even when the user has full access to the component's sub-fields.\n */\nexport const getComponentLeafPaths = (\n strapi: Core.Strapi,\n componentUid: string,\n prefix: string,\n visited: Set<string> = new Set()\n): string[] => {\n if (visited.has(componentUid) === true) return [prefix];\n\n type ComponentEntry = { attributes: Record<string, { type: string; component?: string }> };\n const component = (strapi.components as unknown as Record<string, ComponentEntry | undefined>)[\n componentUid\n ];\n if (component === undefined) return [prefix];\n\n visited.add(componentUid);\n const paths: string[] = [];\n\n for (const [key, attr] of Object.entries(component.attributes)) {\n if (key === 'id') {\n // skip system id field — it is not a user-facing permission path\n // eslint-disable-next-line no-continue\n continue;\n }\n const fieldPath = `${prefix}.${key}`;\n\n if (attr.type === 'component' && attr.component !== undefined) {\n paths.push(...getComponentLeafPaths(strapi, attr.component, fieldPath, visited));\n } else {\n paths.push(fieldPath);\n }\n }\n\n visited.delete(componentUid);\n\n return paths.length > 0 ? paths : [prefix];\n};\n\n/**\n * Returns the subset of attribute keys the session may access for `action` on `uid`.\n * Returns `null` when all fields are permitted (caller should skip field filtering).\n * Component attributes are resolved to their nested leaf paths before checking CASL rules.\n */\nexport const getPermittedFields = (\n strapi: Core.Strapi,\n userAbility: Modules.MCP.McpHandlerContext['userAbility'],\n action: string,\n uid: string,\n attributes: Struct.SchemaAttributes\n): Set<string> | null => {\n const allKeys = Object.keys(attributes);\n const permitted = allKeys.filter((key) => {\n if (userAbility.can(action, uid, key) === true) return true;\n\n // Component attrs: CASL rules use nested paths (e.g. 'SEO.title').\n // Check if at least one sub-field path is permitted.\n const attr = attributes[key] as { type: string; component?: string };\n if (attr.type === 'component' && attr.component !== undefined) {\n const leafPaths = getComponentLeafPaths(strapi, attr.component, key);\n return leafPaths.some((path) => userAbility.can(action, uid, path) === true);\n }\n\n return false;\n });\n\n if (permitted.length === allKeys.length) {\n return null;\n }\n\n return new Set(permitted);\n};\n\n/**\n * Filters `localeCodes` to only those the session may access for `action`.\n * Returns `null` when all locales are permitted (caller should use the unfiltered base schema).\n * Returns an empty tuple-like array when no locale is permitted.\n */\nexport const getPermittedLocales = (\n permissionChecker: { cannot: (action: string, entity?: unknown) => boolean },\n action: string,\n localeCodes: [string, ...string[]]\n): [string, ...string[]] | null => {\n const permitted = localeCodes.filter(\n (code) => permissionChecker.cannot(action, { locale: code }) === false\n );\n\n if (permitted.length === localeCodes.length) {\n return null;\n }\n\n return permitted.length > 0\n ? (permitted as [string, ...string[]])\n : ([] as unknown as [string, ...string[]]);\n};\n\n// Re-export for use in handler files (avoids needing to import from permissions in each handler)\nexport type { ContentManagerModelForMcp };\n"],"names":["isContentTypeLocalized","strapi","uid","ct","contentTypes","undefined","localization","isLocalizedContentType","localeDefaultDescription","defaultLocale","allowedLocales","includes","buildLocaleSchema","localeCodes","length","schema","z","enum","optional","default","describe","join","string","resolvePermittedLocaleSchema","context","action","baseLocaleSchema","isLocalized","permissionChecker","getService","create","userAbility","model","permitted","getPermittedLocales","never","getComponentLeafPaths","componentUid","prefix","visited","Set","has","component","components","add","paths","key","attr","Object","entries","attributes","fieldPath","type","push","delete","getPermittedFields","allKeys","keys","filter","can","leafPaths","some","path","code","cannot","locale"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  import { z } from '@strapi/utils';
2
2
  import { getService } from '../utils/index.mjs';
3
3
 
4
- /** Returns true if the content type identified by `uid` has the i18n `localized` plugin option enabled. */ const isContentTypeLocalized = (strapi, uid)=>{
4
+ /** Returns true if the registered localization provider reports the content type identified by `uid` as localized. */ const isContentTypeLocalized = (strapi, uid)=>{
5
5
  const ct = strapi.contentTypes?.[uid];
6
6
  if (ct === undefined) return false;
7
- return ct.pluginOptions?.i18n?.localized === true;
7
+ return strapi.localization.isLocalizedContentType(ct);
8
8
  };
9
9
  const localeDefaultDescription = (defaultLocale, allowedLocales)=>{
10
10
  if (defaultLocale !== null && allowedLocales.includes(defaultLocale)) {
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.mjs","sources":["../../../server/src/mcp/permissions.ts"],"sourcesContent":["import { z } from '@strapi/utils';\nimport type { Core, Modules, Struct, UID } from '@strapi/types';\n\nimport { getService } from '../utils';\nimport type { ContentManagerModelForMcp } from './types';\n\n/** Returns true if the content type identified by `uid` has the i18n `localized` plugin option enabled. */\nexport const isContentTypeLocalized = (strapi: Core.Strapi, uid: string): boolean => {\n const ct = strapi.contentTypes?.[uid as UID.ContentType];\n if (ct === undefined) return false;\n return (\n (ct as { pluginOptions?: { i18n?: { localized?: boolean } } }).pluginOptions?.i18n\n ?.localized === true\n );\n};\n\nconst localeDefaultDescription = (\n defaultLocale: string | null,\n allowedLocales: readonly string[]\n): string => {\n if (defaultLocale !== null && allowedLocales.includes(defaultLocale)) {\n return `Defaults to \"${defaultLocale}\".`;\n }\n\n return 'Defaults to the default locale.';\n};\n\n/**\n * Builds the base locale Zod schema for a derived MCP tool input.\n * When `localeCodes` is provided, constrains the field to a `z.enum` of available codes\n * with an optional default; otherwise falls back to a plain optional string.\n */\nexport const buildLocaleSchema = (\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null\n): z.ZodTypeAny => {\n if (localeCodes !== null && localeCodes.length > 0) {\n let schema: z.ZodTypeAny = z.enum(localeCodes).optional();\n\n if (defaultLocale !== null && localeCodes.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Available: ${localeCodes.join(', ')}. ${localeDefaultDescription(defaultLocale, localeCodes)}`\n );\n }\n\n return z\n .string()\n .optional()\n .describe('Locale code (e.g. \"en\", \"fr\"). Defaults to the default locale.');\n};\n\n/**\n * Narrows the base locale schema to only locales the session is permitted to access\n * for the given action + uid combination.\n *\n * Returns the base schema unchanged when:\n * - localeCodes is null (i18n not installed)\n * - the content type is not localized\n * - all installed locales are permitted for this action\n *\n * Returns z.never().optional() when no locales are permitted, keeping the tool\n * registered but signalling no valid locale input.\n */\nexport const resolvePermittedLocaleSchema = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext,\n action: string,\n uid: string,\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null,\n baseLocaleSchema: z.ZodTypeAny\n): z.ZodTypeAny => {\n if (localeCodes === null) return baseLocaleSchema;\n\n const isLocalized = isContentTypeLocalized(strapi, uid);\n if (isLocalized === false) {\n return z.string().optional().describe('This content type is not localized. Locale is ignored.');\n }\n\n const permissionChecker = getService('permission-checker').create({\n userAbility: context.userAbility,\n model: uid,\n });\n const permitted = getPermittedLocales(permissionChecker, action, localeCodes);\n if (permitted === null) return baseLocaleSchema;\n if (permitted.length === 0) {\n return z.never().optional().describe('No locale access for this action.');\n }\n\n let schema: z.ZodTypeAny = z.enum(permitted).optional();\n\n if (defaultLocale !== null && permitted.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Permitted: ${permitted.join(', ')}. ${localeDefaultDescription(defaultLocale, permitted)}`\n );\n};\n\n/**\n * Recursively resolves leaf field paths for a component, matching the nested\n * path format used by CASL rules (e.g. 'SEO.title', 'SEO.og.image').\n *\n * The admin RBAC system decomposes component attrs into nested paths and removes\n * the parent key — so checking `ability.can(action, uid, 'SEO')` returns false\n * even when the user has full access to the component's sub-fields.\n */\nexport const getComponentLeafPaths = (\n strapi: Core.Strapi,\n componentUid: string,\n prefix: string,\n visited: Set<string> = new Set()\n): string[] => {\n if (visited.has(componentUid) === true) return [prefix];\n\n type ComponentEntry = { attributes: Record<string, { type: string; component?: string }> };\n const component = (strapi.components as unknown as Record<string, ComponentEntry | undefined>)[\n componentUid\n ];\n if (component === undefined) return [prefix];\n\n visited.add(componentUid);\n const paths: string[] = [];\n\n for (const [key, attr] of Object.entries(component.attributes)) {\n if (key === 'id') {\n // skip system id field — it is not a user-facing permission path\n // eslint-disable-next-line no-continue\n continue;\n }\n const fieldPath = `${prefix}.${key}`;\n\n if (attr.type === 'component' && attr.component !== undefined) {\n paths.push(...getComponentLeafPaths(strapi, attr.component, fieldPath, visited));\n } else {\n paths.push(fieldPath);\n }\n }\n\n visited.delete(componentUid);\n\n return paths.length > 0 ? paths : [prefix];\n};\n\n/**\n * Returns the subset of attribute keys the session may access for `action` on `uid`.\n * Returns `null` when all fields are permitted (caller should skip field filtering).\n * Component attributes are resolved to their nested leaf paths before checking CASL rules.\n */\nexport const getPermittedFields = (\n strapi: Core.Strapi,\n userAbility: Modules.MCP.McpHandlerContext['userAbility'],\n action: string,\n uid: string,\n attributes: Struct.SchemaAttributes\n): Set<string> | null => {\n const allKeys = Object.keys(attributes);\n const permitted = allKeys.filter((key) => {\n if (userAbility.can(action, uid, key) === true) return true;\n\n // Component attrs: CASL rules use nested paths (e.g. 'SEO.title').\n // Check if at least one sub-field path is permitted.\n const attr = attributes[key] as { type: string; component?: string };\n if (attr.type === 'component' && attr.component !== undefined) {\n const leafPaths = getComponentLeafPaths(strapi, attr.component, key);\n return leafPaths.some((path) => userAbility.can(action, uid, path) === true);\n }\n\n return false;\n });\n\n if (permitted.length === allKeys.length) {\n return null;\n }\n\n return new Set(permitted);\n};\n\n/**\n * Filters `localeCodes` to only those the session may access for `action`.\n * Returns `null` when all locales are permitted (caller should use the unfiltered base schema).\n * Returns an empty tuple-like array when no locale is permitted.\n */\nexport const getPermittedLocales = (\n permissionChecker: { cannot: (action: string, entity?: unknown) => boolean },\n action: string,\n localeCodes: [string, ...string[]]\n): [string, ...string[]] | null => {\n const permitted = localeCodes.filter(\n (code) => permissionChecker.cannot(action, { locale: code }) === false\n );\n\n if (permitted.length === localeCodes.length) {\n return null;\n }\n\n return permitted.length > 0\n ? (permitted as [string, ...string[]])\n : ([] as unknown as [string, ...string[]]);\n};\n\n// Re-export for use in handler files (avoids needing to import from permissions in each handler)\nexport type { ContentManagerModelForMcp };\n"],"names":["isContentTypeLocalized","strapi","uid","ct","contentTypes","undefined","pluginOptions","i18n","localized","localeDefaultDescription","defaultLocale","allowedLocales","includes","buildLocaleSchema","localeCodes","length","schema","z","enum","optional","default","describe","join","string","resolvePermittedLocaleSchema","context","action","baseLocaleSchema","isLocalized","permissionChecker","getService","create","userAbility","model","permitted","getPermittedLocales","never","getComponentLeafPaths","componentUid","prefix","visited","Set","has","component","components","add","paths","key","attr","Object","entries","attributes","fieldPath","type","push","delete","getPermittedFields","allKeys","keys","filter","can","leafPaths","some","path","code","cannot","locale"],"mappings":"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"}
1
+ {"version":3,"file":"permissions.mjs","sources":["../../../server/src/mcp/permissions.ts"],"sourcesContent":["import { z } from '@strapi/utils';\nimport type { Core, Modules, Struct, UID } from '@strapi/types';\n\nimport { getService } from '../utils';\nimport type { ContentManagerModelForMcp } from './types';\n\n/** Returns true if the registered localization provider reports the content type identified by `uid` as localized. */\nexport const isContentTypeLocalized = (strapi: Core.Strapi, uid: string): boolean => {\n const ct = strapi.contentTypes?.[uid as UID.ContentType];\n if (ct === undefined) return false;\n return strapi.localization.isLocalizedContentType(ct);\n};\n\nconst localeDefaultDescription = (\n defaultLocale: string | null,\n allowedLocales: readonly string[]\n): string => {\n if (defaultLocale !== null && allowedLocales.includes(defaultLocale)) {\n return `Defaults to \"${defaultLocale}\".`;\n }\n\n return 'Defaults to the default locale.';\n};\n\n/**\n * Builds the base locale Zod schema for a derived MCP tool input.\n * When `localeCodes` is provided, constrains the field to a `z.enum` of available codes\n * with an optional default; otherwise falls back to a plain optional string.\n */\nexport const buildLocaleSchema = (\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null\n): z.ZodTypeAny => {\n if (localeCodes !== null && localeCodes.length > 0) {\n let schema: z.ZodTypeAny = z.enum(localeCodes).optional();\n\n if (defaultLocale !== null && localeCodes.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Available: ${localeCodes.join(', ')}. ${localeDefaultDescription(defaultLocale, localeCodes)}`\n );\n }\n\n return z\n .string()\n .optional()\n .describe('Locale code (e.g. \"en\", \"fr\"). Defaults to the default locale.');\n};\n\n/**\n * Narrows the base locale schema to only locales the session is permitted to access\n * for the given action + uid combination.\n *\n * Returns the base schema unchanged when:\n * - localeCodes is null (i18n not installed)\n * - the content type is not localized\n * - all installed locales are permitted for this action\n *\n * Returns z.never().optional() when no locales are permitted, keeping the tool\n * registered but signalling no valid locale input.\n */\nexport const resolvePermittedLocaleSchema = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext,\n action: string,\n uid: string,\n localeCodes: [string, ...string[]] | null,\n defaultLocale: string | null,\n baseLocaleSchema: z.ZodTypeAny\n): z.ZodTypeAny => {\n if (localeCodes === null) return baseLocaleSchema;\n\n const isLocalized = isContentTypeLocalized(strapi, uid);\n if (isLocalized === false) {\n return z.string().optional().describe('This content type is not localized. Locale is ignored.');\n }\n\n const permissionChecker = getService('permission-checker').create({\n userAbility: context.userAbility,\n model: uid,\n });\n const permitted = getPermittedLocales(permissionChecker, action, localeCodes);\n if (permitted === null) return baseLocaleSchema;\n if (permitted.length === 0) {\n return z.never().optional().describe('No locale access for this action.');\n }\n\n let schema: z.ZodTypeAny = z.enum(permitted).optional();\n\n if (defaultLocale !== null && permitted.includes(defaultLocale)) {\n schema = schema.default(defaultLocale);\n }\n\n return schema.describe(\n `Locale code. Permitted: ${permitted.join(', ')}. ${localeDefaultDescription(defaultLocale, permitted)}`\n );\n};\n\n/**\n * Recursively resolves leaf field paths for a component, matching the nested\n * path format used by CASL rules (e.g. 'SEO.title', 'SEO.og.image').\n *\n * The admin RBAC system decomposes component attrs into nested paths and removes\n * the parent key — so checking `ability.can(action, uid, 'SEO')` returns false\n * even when the user has full access to the component's sub-fields.\n */\nexport const getComponentLeafPaths = (\n strapi: Core.Strapi,\n componentUid: string,\n prefix: string,\n visited: Set<string> = new Set()\n): string[] => {\n if (visited.has(componentUid) === true) return [prefix];\n\n type ComponentEntry = { attributes: Record<string, { type: string; component?: string }> };\n const component = (strapi.components as unknown as Record<string, ComponentEntry | undefined>)[\n componentUid\n ];\n if (component === undefined) return [prefix];\n\n visited.add(componentUid);\n const paths: string[] = [];\n\n for (const [key, attr] of Object.entries(component.attributes)) {\n if (key === 'id') {\n // skip system id field — it is not a user-facing permission path\n // eslint-disable-next-line no-continue\n continue;\n }\n const fieldPath = `${prefix}.${key}`;\n\n if (attr.type === 'component' && attr.component !== undefined) {\n paths.push(...getComponentLeafPaths(strapi, attr.component, fieldPath, visited));\n } else {\n paths.push(fieldPath);\n }\n }\n\n visited.delete(componentUid);\n\n return paths.length > 0 ? paths : [prefix];\n};\n\n/**\n * Returns the subset of attribute keys the session may access for `action` on `uid`.\n * Returns `null` when all fields are permitted (caller should skip field filtering).\n * Component attributes are resolved to their nested leaf paths before checking CASL rules.\n */\nexport const getPermittedFields = (\n strapi: Core.Strapi,\n userAbility: Modules.MCP.McpHandlerContext['userAbility'],\n action: string,\n uid: string,\n attributes: Struct.SchemaAttributes\n): Set<string> | null => {\n const allKeys = Object.keys(attributes);\n const permitted = allKeys.filter((key) => {\n if (userAbility.can(action, uid, key) === true) return true;\n\n // Component attrs: CASL rules use nested paths (e.g. 'SEO.title').\n // Check if at least one sub-field path is permitted.\n const attr = attributes[key] as { type: string; component?: string };\n if (attr.type === 'component' && attr.component !== undefined) {\n const leafPaths = getComponentLeafPaths(strapi, attr.component, key);\n return leafPaths.some((path) => userAbility.can(action, uid, path) === true);\n }\n\n return false;\n });\n\n if (permitted.length === allKeys.length) {\n return null;\n }\n\n return new Set(permitted);\n};\n\n/**\n * Filters `localeCodes` to only those the session may access for `action`.\n * Returns `null` when all locales are permitted (caller should use the unfiltered base schema).\n * Returns an empty tuple-like array when no locale is permitted.\n */\nexport const getPermittedLocales = (\n permissionChecker: { cannot: (action: string, entity?: unknown) => boolean },\n action: string,\n localeCodes: [string, ...string[]]\n): [string, ...string[]] | null => {\n const permitted = localeCodes.filter(\n (code) => permissionChecker.cannot(action, { locale: code }) === false\n );\n\n if (permitted.length === localeCodes.length) {\n return null;\n }\n\n return permitted.length > 0\n ? (permitted as [string, ...string[]])\n : ([] as unknown as [string, ...string[]]);\n};\n\n// Re-export for use in handler files (avoids needing to import from permissions in each handler)\nexport type { ContentManagerModelForMcp };\n"],"names":["isContentTypeLocalized","strapi","uid","ct","contentTypes","undefined","localization","isLocalizedContentType","localeDefaultDescription","defaultLocale","allowedLocales","includes","buildLocaleSchema","localeCodes","length","schema","z","enum","optional","default","describe","join","string","resolvePermittedLocaleSchema","context","action","baseLocaleSchema","isLocalized","permissionChecker","getService","create","userAbility","model","permitted","getPermittedLocales","never","getComponentLeafPaths","componentUid","prefix","visited","Set","has","component","components","add","paths","key","attr","Object","entries","attributes","fieldPath","type","push","delete","getPermittedFields","allKeys","keys","filter","can","leafPaths","some","path","code","cannot","locale"],"mappings":"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"}
@@ -5,19 +5,21 @@ var index = require('../utils/index.js');
5
5
 
6
6
  /**
7
7
  * Registers derived content-type MCP tools via strapi.ai.mcp.registerTool().
8
- * Must be called from the plugin register phase, before the MCP HTTP server starts.
8
+ * Must be called from the plugin bootstrap phase: after every plugin's register (so the
9
+ * localization provider is installed and locale rows are readable) and before the MCP HTTP
10
+ * server starts.
9
11
  */ const registerContentManagerMcpTools = async ({ strapi })=>{
10
12
  // Performance only: registerTool() is safe when MCP is disabled (definitions are stored but
11
13
  // never exposed). Skip the expensive derivation below when the MCP server will not start.
12
14
  if (strapi.ai.mcp.isEnabled() !== true) {
13
15
  return;
14
16
  }
15
- const i18nPlugin = strapi.plugin('i18n');
16
17
  let localeCodes = null;
17
18
  let defaultLocale = null;
18
- if (i18nPlugin !== undefined) {
19
- localeCodes = (await i18nPlugin.service('locales').find()).map((locale)=>locale.code);
20
- defaultLocale = await i18nPlugin.service('locales').getDefaultLocale();
19
+ if (strapi.localization.isEnabled() === true) {
20
+ // The tuple type is a lie for zero locales; consumers guard on length > 0 before z.enum.
21
+ localeCodes = (await strapi.localization.getLocales()).map((locale)=>locale.code);
22
+ defaultLocale = await strapi.localization.getDefaultLocale();
21
23
  }
22
24
  const models = index.getService('content-types').findDisplayedContentTypes();
23
25
  const tools = deriveContentTypeMcpTools.deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {
@@ -1 +1 @@
1
- {"version":3,"file":"register-content-manager-mcp-tools.js","sources":["../../../server/src/mcp/register-content-manager-mcp-tools.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\nimport { deriveDisplayedContentTypeMcpToolDefinitions } from './derive-content-type-mcp-tools';\nimport { getService } from '../utils';\n\n/**\n * Registers derived content-type MCP tools via strapi.ai.mcp.registerTool().\n * Must be called from the plugin register phase, before the MCP HTTP server starts.\n */\nexport const registerContentManagerMcpTools = async ({\n strapi,\n}: {\n strapi: Core.Strapi;\n}): Promise<void> => {\n // Performance only: registerTool() is safe when MCP is disabled (definitions are stored but\n // never exposed). Skip the expensive derivation below when the MCP server will not start.\n if (strapi.ai.mcp.isEnabled() !== true) {\n return;\n }\n\n const i18nPlugin = strapi.plugin('i18n');\n\n let localeCodes = null;\n let defaultLocale: string | null = null;\n if (i18nPlugin !== undefined) {\n localeCodes = (await i18nPlugin.service('locales').find()).map(\n (locale: { code: string }) => locale.code\n ) as [string, ...string[]];\n defaultLocale = await i18nPlugin.service('locales').getDefaultLocale();\n }\n\n const models = getService('content-types').findDisplayedContentTypes();\n const tools = deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {\n localeCodes,\n defaultLocale,\n });\n\n for (const tool of tools) {\n strapi.ai.mcp.registerTool(tool);\n }\n};\n"],"names":["registerContentManagerMcpTools","strapi","ai","mcp","isEnabled","i18nPlugin","plugin","localeCodes","defaultLocale","undefined","service","find","map","locale","code","getDefaultLocale","models","getService","findDisplayedContentTypes","tools","deriveDisplayedContentTypeMcpToolDefinitions","tool","registerTool"],"mappings":";;;;;AAKA;;;AAGC,IACM,MAAMA,8BAAAA,GAAiC,OAAO,EACnDC,MAAM,EAGP,GAAA;;;AAGC,IAAA,IAAIA,OAAOC,EAAE,CAACC,GAAG,CAACC,SAAS,OAAO,IAAA,EAAM;AACtC,QAAA;AACF,IAAA;IAEA,MAAMC,UAAAA,GAAaJ,MAAAA,CAAOK,MAAM,CAAC,MAAA,CAAA;AAEjC,IAAA,IAAIC,WAAAA,GAAc,IAAA;AAClB,IAAA,IAAIC,aAAAA,GAA+B,IAAA;AACnC,IAAA,IAAIH,eAAeI,SAAAA,EAAW;AAC5BF,QAAAA,WAAAA,GAAc,CAAC,MAAMF,UAAAA,CAAWK,OAAO,CAAC,SAAA,CAAA,CAAWC,IAAI,EAAC,EAAGC,GAAG,CAC5D,CAACC,MAAAA,GAA6BA,OAAOC,IAAI,CAAA;AAE3CN,QAAAA,aAAAA,GAAgB,MAAMH,UAAAA,CAAWK,OAAO,CAAC,WAAWK,gBAAgB,EAAA;AACtE,IAAA;IAEA,MAAMC,MAAAA,GAASC,gBAAAA,CAAW,eAAA,CAAA,CAAiBC,yBAAyB,EAAA;IACpE,MAAMC,KAAAA,GAAQC,sEAAAA,CAA6CnB,MAAAA,EAAQe,MAAAA,EAAQ;AACzET,QAAAA,WAAAA;AACAC,QAAAA;AACF,KAAA,CAAA;IAEA,KAAK,MAAMa,QAAQF,KAAAA,CAAO;AACxBlB,QAAAA,MAAAA,CAAOC,EAAE,CAACC,GAAG,CAACmB,YAAY,CAACD,IAAAA,CAAAA;AAC7B,IAAA;AACF;;;;"}
1
+ {"version":3,"file":"register-content-manager-mcp-tools.js","sources":["../../../server/src/mcp/register-content-manager-mcp-tools.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\nimport { deriveDisplayedContentTypeMcpToolDefinitions } from './derive-content-type-mcp-tools';\nimport { getService } from '../utils';\n\n/**\n * Registers derived content-type MCP tools via strapi.ai.mcp.registerTool().\n * Must be called from the plugin bootstrap phase: after every plugin's register (so the\n * localization provider is installed and locale rows are readable) and before the MCP HTTP\n * server starts.\n */\nexport const registerContentManagerMcpTools = async ({\n strapi,\n}: {\n strapi: Core.Strapi;\n}): Promise<void> => {\n // Performance only: registerTool() is safe when MCP is disabled (definitions are stored but\n // never exposed). Skip the expensive derivation below when the MCP server will not start.\n if (strapi.ai.mcp.isEnabled() !== true) {\n return;\n }\n\n let localeCodes: [string, ...string[]] | null = null;\n let defaultLocale: string | null = null;\n if (strapi.localization.isEnabled() === true) {\n // The tuple type is a lie for zero locales; consumers guard on length > 0 before z.enum.\n localeCodes = (await strapi.localization.getLocales()).map((locale) => locale.code) as [\n string,\n ...string[],\n ];\n defaultLocale = await strapi.localization.getDefaultLocale();\n }\n\n const models = getService('content-types').findDisplayedContentTypes();\n const tools = deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {\n localeCodes,\n defaultLocale,\n });\n\n for (const tool of tools) {\n strapi.ai.mcp.registerTool(tool);\n }\n};\n"],"names":["registerContentManagerMcpTools","strapi","ai","mcp","isEnabled","localeCodes","defaultLocale","localization","getLocales","map","locale","code","getDefaultLocale","models","getService","findDisplayedContentTypes","tools","deriveDisplayedContentTypeMcpToolDefinitions","tool","registerTool"],"mappings":";;;;;AAKA;;;;;AAKC,IACM,MAAMA,8BAAAA,GAAiC,OAAO,EACnDC,MAAM,EAGP,GAAA;;;AAGC,IAAA,IAAIA,OAAOC,EAAE,CAACC,GAAG,CAACC,SAAS,OAAO,IAAA,EAAM;AACtC,QAAA;AACF,IAAA;AAEA,IAAA,IAAIC,WAAAA,GAA4C,IAAA;AAChD,IAAA,IAAIC,aAAAA,GAA+B,IAAA;AACnC,IAAA,IAAIL,MAAAA,CAAOM,YAAY,CAACH,SAAS,OAAO,IAAA,EAAM;;AAE5CC,QAAAA,WAAAA,GAAc,CAAC,MAAMJ,MAAAA,CAAOM,YAAY,CAACC,UAAU,EAAC,EAAGC,GAAG,CAAC,CAACC,MAAAA,GAAWA,OAAOC,IAAI,CAAA;AAIlFL,QAAAA,aAAAA,GAAgB,MAAML,MAAAA,CAAOM,YAAY,CAACK,gBAAgB,EAAA;AAC5D,IAAA;IAEA,MAAMC,MAAAA,GAASC,gBAAAA,CAAW,eAAA,CAAA,CAAiBC,yBAAyB,EAAA;IACpE,MAAMC,KAAAA,GAAQC,sEAAAA,CAA6ChB,MAAAA,EAAQY,MAAAA,EAAQ;AACzER,QAAAA,WAAAA;AACAC,QAAAA;AACF,KAAA,CAAA;IAEA,KAAK,MAAMY,QAAQF,KAAAA,CAAO;AACxBf,QAAAA,MAAAA,CAAOC,EAAE,CAACC,GAAG,CAACgB,YAAY,CAACD,IAAAA,CAAAA;AAC7B,IAAA;AACF;;;;"}
@@ -3,19 +3,21 @@ import { getService } from '../utils/index.mjs';
3
3
 
4
4
  /**
5
5
  * Registers derived content-type MCP tools via strapi.ai.mcp.registerTool().
6
- * Must be called from the plugin register phase, before the MCP HTTP server starts.
6
+ * Must be called from the plugin bootstrap phase: after every plugin's register (so the
7
+ * localization provider is installed and locale rows are readable) and before the MCP HTTP
8
+ * server starts.
7
9
  */ const registerContentManagerMcpTools = async ({ strapi })=>{
8
10
  // Performance only: registerTool() is safe when MCP is disabled (definitions are stored but
9
11
  // never exposed). Skip the expensive derivation below when the MCP server will not start.
10
12
  if (strapi.ai.mcp.isEnabled() !== true) {
11
13
  return;
12
14
  }
13
- const i18nPlugin = strapi.plugin('i18n');
14
15
  let localeCodes = null;
15
16
  let defaultLocale = null;
16
- if (i18nPlugin !== undefined) {
17
- localeCodes = (await i18nPlugin.service('locales').find()).map((locale)=>locale.code);
18
- defaultLocale = await i18nPlugin.service('locales').getDefaultLocale();
17
+ if (strapi.localization.isEnabled() === true) {
18
+ // The tuple type is a lie for zero locales; consumers guard on length > 0 before z.enum.
19
+ localeCodes = (await strapi.localization.getLocales()).map((locale)=>locale.code);
20
+ defaultLocale = await strapi.localization.getDefaultLocale();
19
21
  }
20
22
  const models = getService('content-types').findDisplayedContentTypes();
21
23
  const tools = deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {
@@ -1 +1 @@
1
- {"version":3,"file":"register-content-manager-mcp-tools.mjs","sources":["../../../server/src/mcp/register-content-manager-mcp-tools.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\nimport { deriveDisplayedContentTypeMcpToolDefinitions } from './derive-content-type-mcp-tools';\nimport { getService } from '../utils';\n\n/**\n * Registers derived content-type MCP tools via strapi.ai.mcp.registerTool().\n * Must be called from the plugin register phase, before the MCP HTTP server starts.\n */\nexport const registerContentManagerMcpTools = async ({\n strapi,\n}: {\n strapi: Core.Strapi;\n}): Promise<void> => {\n // Performance only: registerTool() is safe when MCP is disabled (definitions are stored but\n // never exposed). Skip the expensive derivation below when the MCP server will not start.\n if (strapi.ai.mcp.isEnabled() !== true) {\n return;\n }\n\n const i18nPlugin = strapi.plugin('i18n');\n\n let localeCodes = null;\n let defaultLocale: string | null = null;\n if (i18nPlugin !== undefined) {\n localeCodes = (await i18nPlugin.service('locales').find()).map(\n (locale: { code: string }) => locale.code\n ) as [string, ...string[]];\n defaultLocale = await i18nPlugin.service('locales').getDefaultLocale();\n }\n\n const models = getService('content-types').findDisplayedContentTypes();\n const tools = deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {\n localeCodes,\n defaultLocale,\n });\n\n for (const tool of tools) {\n strapi.ai.mcp.registerTool(tool);\n }\n};\n"],"names":["registerContentManagerMcpTools","strapi","ai","mcp","isEnabled","i18nPlugin","plugin","localeCodes","defaultLocale","undefined","service","find","map","locale","code","getDefaultLocale","models","getService","findDisplayedContentTypes","tools","deriveDisplayedContentTypeMcpToolDefinitions","tool","registerTool"],"mappings":";;;AAKA;;;AAGC,IACM,MAAMA,8BAAAA,GAAiC,OAAO,EACnDC,MAAM,EAGP,GAAA;;;AAGC,IAAA,IAAIA,OAAOC,EAAE,CAACC,GAAG,CAACC,SAAS,OAAO,IAAA,EAAM;AACtC,QAAA;AACF,IAAA;IAEA,MAAMC,UAAAA,GAAaJ,MAAAA,CAAOK,MAAM,CAAC,MAAA,CAAA;AAEjC,IAAA,IAAIC,WAAAA,GAAc,IAAA;AAClB,IAAA,IAAIC,aAAAA,GAA+B,IAAA;AACnC,IAAA,IAAIH,eAAeI,SAAAA,EAAW;AAC5BF,QAAAA,WAAAA,GAAc,CAAC,MAAMF,UAAAA,CAAWK,OAAO,CAAC,SAAA,CAAA,CAAWC,IAAI,EAAC,EAAGC,GAAG,CAC5D,CAACC,MAAAA,GAA6BA,OAAOC,IAAI,CAAA;AAE3CN,QAAAA,aAAAA,GAAgB,MAAMH,UAAAA,CAAWK,OAAO,CAAC,WAAWK,gBAAgB,EAAA;AACtE,IAAA;IAEA,MAAMC,MAAAA,GAASC,UAAAA,CAAW,eAAA,CAAA,CAAiBC,yBAAyB,EAAA;IACpE,MAAMC,KAAAA,GAAQC,4CAAAA,CAA6CnB,MAAAA,EAAQe,MAAAA,EAAQ;AACzET,QAAAA,WAAAA;AACAC,QAAAA;AACF,KAAA,CAAA;IAEA,KAAK,MAAMa,QAAQF,KAAAA,CAAO;AACxBlB,QAAAA,MAAAA,CAAOC,EAAE,CAACC,GAAG,CAACmB,YAAY,CAACD,IAAAA,CAAAA;AAC7B,IAAA;AACF;;;;"}
1
+ {"version":3,"file":"register-content-manager-mcp-tools.mjs","sources":["../../../server/src/mcp/register-content-manager-mcp-tools.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\nimport { deriveDisplayedContentTypeMcpToolDefinitions } from './derive-content-type-mcp-tools';\nimport { getService } from '../utils';\n\n/**\n * Registers derived content-type MCP tools via strapi.ai.mcp.registerTool().\n * Must be called from the plugin bootstrap phase: after every plugin's register (so the\n * localization provider is installed and locale rows are readable) and before the MCP HTTP\n * server starts.\n */\nexport const registerContentManagerMcpTools = async ({\n strapi,\n}: {\n strapi: Core.Strapi;\n}): Promise<void> => {\n // Performance only: registerTool() is safe when MCP is disabled (definitions are stored but\n // never exposed). Skip the expensive derivation below when the MCP server will not start.\n if (strapi.ai.mcp.isEnabled() !== true) {\n return;\n }\n\n let localeCodes: [string, ...string[]] | null = null;\n let defaultLocale: string | null = null;\n if (strapi.localization.isEnabled() === true) {\n // The tuple type is a lie for zero locales; consumers guard on length > 0 before z.enum.\n localeCodes = (await strapi.localization.getLocales()).map((locale) => locale.code) as [\n string,\n ...string[],\n ];\n defaultLocale = await strapi.localization.getDefaultLocale();\n }\n\n const models = getService('content-types').findDisplayedContentTypes();\n const tools = deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {\n localeCodes,\n defaultLocale,\n });\n\n for (const tool of tools) {\n strapi.ai.mcp.registerTool(tool);\n }\n};\n"],"names":["registerContentManagerMcpTools","strapi","ai","mcp","isEnabled","localeCodes","defaultLocale","localization","getLocales","map","locale","code","getDefaultLocale","models","getService","findDisplayedContentTypes","tools","deriveDisplayedContentTypeMcpToolDefinitions","tool","registerTool"],"mappings":";;;AAKA;;;;;AAKC,IACM,MAAMA,8BAAAA,GAAiC,OAAO,EACnDC,MAAM,EAGP,GAAA;;;AAGC,IAAA,IAAIA,OAAOC,EAAE,CAACC,GAAG,CAACC,SAAS,OAAO,IAAA,EAAM;AACtC,QAAA;AACF,IAAA;AAEA,IAAA,IAAIC,WAAAA,GAA4C,IAAA;AAChD,IAAA,IAAIC,aAAAA,GAA+B,IAAA;AACnC,IAAA,IAAIL,MAAAA,CAAOM,YAAY,CAACH,SAAS,OAAO,IAAA,EAAM;;AAE5CC,QAAAA,WAAAA,GAAc,CAAC,MAAMJ,MAAAA,CAAOM,YAAY,CAACC,UAAU,EAAC,EAAGC,GAAG,CAAC,CAACC,MAAAA,GAAWA,OAAOC,IAAI,CAAA;AAIlFL,QAAAA,aAAAA,GAAgB,MAAML,MAAAA,CAAOM,YAAY,CAACK,gBAAgB,EAAA;AAC5D,IAAA;IAEA,MAAMC,MAAAA,GAASC,UAAAA,CAAW,eAAA,CAAA,CAAiBC,yBAAyB,EAAA;IACpE,MAAMC,KAAAA,GAAQC,4CAAAA,CAA6ChB,MAAAA,EAAQY,MAAAA,EAAQ;AACzER,QAAAA,WAAAA;AACAC,QAAAA;AACF,KAAA,CAAA;IAEA,KAAK,MAAMY,QAAQF,KAAAA,CAAO;AACxBf,QAAAA,MAAAA,CAAOC,EAAE,CAACC,GAAG,CAACgB,YAAY,CAACD,IAAAA,CAAAA;AAC7B,IAAA;AACF;;;;"}