@strapi/content-manager 5.47.0 → 5.48.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 (45) hide show
  1. package/dist/admin/hooks/useDocument.js +82 -2
  2. package/dist/admin/hooks/useDocument.js.map +1 -1
  3. package/dist/admin/hooks/useDocument.mjs +82 -2
  4. package/dist/admin/hooks/useDocument.mjs.map +1 -1
  5. package/dist/admin/pages/EditView/EditViewPage.js +3 -2
  6. package/dist/admin/pages/EditView/EditViewPage.js.map +1 -1
  7. package/dist/admin/pages/EditView/EditViewPage.mjs +3 -2
  8. package/dist/admin/pages/EditView/EditViewPage.mjs.map +1 -1
  9. package/dist/admin/pages/EditView/components/DocumentActions.js +6 -3
  10. package/dist/admin/pages/EditView/components/DocumentActions.js.map +1 -1
  11. package/dist/admin/pages/EditView/components/DocumentActions.mjs +6 -3
  12. package/dist/admin/pages/EditView/components/DocumentActions.mjs.map +1 -1
  13. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/Code.js +1 -0
  14. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/Code.js.map +1 -1
  15. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/Code.mjs +1 -0
  16. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/Code.mjs.map +1 -1
  17. package/dist/admin/pages/EditView/components/FormInputs/Component/Input.js.map +1 -1
  18. package/dist/admin/pages/EditView/components/FormInputs/Component/Input.mjs.map +1 -1
  19. package/dist/admin/pages/EditView/components/FormInputs/Component/Repeatable.js +4 -1
  20. package/dist/admin/pages/EditView/components/FormInputs/Component/Repeatable.js.map +1 -1
  21. package/dist/admin/pages/EditView/components/FormInputs/Component/Repeatable.mjs +4 -1
  22. package/dist/admin/pages/EditView/components/FormInputs/Component/Repeatable.mjs.map +1 -1
  23. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Input.d.ts +1 -1
  24. package/dist/admin/utils/relations.js +4 -0
  25. package/dist/admin/utils/relations.js.map +1 -1
  26. package/dist/admin/utils/relations.mjs +4 -0
  27. package/dist/admin/utils/relations.mjs.map +1 -1
  28. package/dist/server/homepage/services/homepage-query-utils.js +56 -0
  29. package/dist/server/homepage/services/homepage-query-utils.js.map +1 -0
  30. package/dist/server/homepage/services/homepage-query-utils.mjs +50 -0
  31. package/dist/server/homepage/services/homepage-query-utils.mjs.map +1 -0
  32. package/dist/server/homepage/services/homepage.js +50 -45
  33. package/dist/server/homepage/services/homepage.js.map +1 -1
  34. package/dist/server/homepage/services/homepage.mjs +50 -45
  35. package/dist/server/homepage/services/homepage.mjs.map +1 -1
  36. package/dist/server/services/document-metadata.js +32 -3
  37. package/dist/server/services/document-metadata.js.map +1 -1
  38. package/dist/server/services/document-metadata.mjs +32 -3
  39. package/dist/server/services/document-metadata.mjs.map +1 -1
  40. package/dist/server/src/homepage/services/homepage-query-utils.d.ts +28 -0
  41. package/dist/server/src/homepage/services/homepage-query-utils.d.ts.map +1 -0
  42. package/dist/server/src/homepage/services/homepage.d.ts.map +1 -1
  43. package/dist/server/src/services/document-metadata.d.ts +11 -1
  44. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  45. package/package.json +7 -7
@@ -11,7 +11,7 @@ interface ComponentInputProps extends Omit<Extract<EditFieldLayout, {
11
11
  * We need layout to come from the props, and not via a hook, because Content History needs
12
12
  * a way to modify the normal component layout to add hidden fields.
13
13
  */
14
- layout: EditFieldLayout[][];
14
+ layout: ReadonlyArray<ReadonlyArray<EditFieldLayout>>;
15
15
  }
16
16
  declare const MemoizedComponentInput: React.MemoExoticComponent<({ label, required, name, attribute, disabled, labelAction, ...props }: ComponentInputProps) => import("react/jsx-runtime").JSX.Element>;
17
17
  export { MemoizedComponentInput as ComponentInput };
@@ -12,6 +12,10 @@
12
12
  if (typeof label === 'string') {
13
13
  return label;
14
14
  }
15
+ // Return numeric labels except for the internal 'id' field.
16
+ if (typeof label === 'number' && mainField?.name !== 'id') {
17
+ return String(label);
18
+ }
15
19
  return relation.documentId;
16
20
  };
17
21
 
@@ -1 +1 @@
1
- {"version":3,"file":"relations.js","sources":["../../../admin/src/utils/relations.ts"],"sourcesContent":["import type { MainField } from './attributes';\nimport type { RelationResult } from '../../../shared/contracts/relations';\n\n/**\n * @internal\n * @description Get the label of a relation, the contract has [key: string]: unknown,\n * so we need to check if the mainFieldKey is defined and if the relation has a value\n * under that property. If it does, we then verify it's type of string and return it.\n *\n * We fallback to the documentId.\n */\nconst getRelationLabel = (relation: RelationResult, mainField?: MainField): string => {\n const label = mainField && relation[mainField.name] ? relation[mainField.name] : null;\n\n if (typeof label === 'string') {\n return label;\n }\n\n return relation.documentId;\n};\n\nexport { getRelationLabel };\n"],"names":["getRelationLabel","relation","mainField","label","name","documentId"],"mappings":";;AAGA;;;;;;;IAQA,MAAMA,gBAAAA,GAAmB,CAACC,QAAAA,EAA0BC,SAAAA,GAAAA;AAClD,IAAA,MAAMC,KAAAA,GAAQD,SAAAA,IAAaD,QAAQ,CAACC,SAAAA,CAAUE,IAAI,CAAC,GAAGH,QAAQ,CAACC,SAAAA,CAAUE,IAAI,CAAC,GAAG,IAAA;IAEjF,IAAI,OAAOD,UAAU,QAAA,EAAU;QAC7B,OAAOA,KAAAA;AACT,IAAA;AAEA,IAAA,OAAOF,SAASI,UAAU;AAC5B;;;;"}
1
+ {"version":3,"file":"relations.js","sources":["../../../admin/src/utils/relations.ts"],"sourcesContent":["import type { MainField } from './attributes';\nimport type { RelationResult } from '../../../shared/contracts/relations';\n\n/**\n * @internal\n * @description Get the label of a relation, the contract has [key: string]: unknown,\n * so we need to check if the mainFieldKey is defined and if the relation has a value\n * under that property. If it does, we then verify it's type of string and return it.\n *\n * We fallback to the documentId.\n */\nconst getRelationLabel = (relation: RelationResult, mainField?: MainField): string => {\n const label = mainField && relation[mainField.name] ? relation[mainField.name] : null;\n\n if (typeof label === 'string') {\n return label;\n }\n\n // Return numeric labels except for the internal 'id' field.\n if (typeof label === 'number' && mainField?.name !== 'id') {\n return String(label);\n }\n\n return relation.documentId;\n};\n\nexport { getRelationLabel };\n"],"names":["getRelationLabel","relation","mainField","label","name","String","documentId"],"mappings":";;AAGA;;;;;;;IAQA,MAAMA,gBAAAA,GAAmB,CAACC,QAAAA,EAA0BC,SAAAA,GAAAA;AAClD,IAAA,MAAMC,KAAAA,GAAQD,SAAAA,IAAaD,QAAQ,CAACC,SAAAA,CAAUE,IAAI,CAAC,GAAGH,QAAQ,CAACC,SAAAA,CAAUE,IAAI,CAAC,GAAG,IAAA;IAEjF,IAAI,OAAOD,UAAU,QAAA,EAAU;QAC7B,OAAOA,KAAAA;AACT,IAAA;;AAGA,IAAA,IAAI,OAAOA,KAAAA,KAAU,QAAA,IAAYD,SAAAA,EAAWE,SAAS,IAAA,EAAM;AACzD,QAAA,OAAOC,MAAAA,CAAOF,KAAAA,CAAAA;AAChB,IAAA;AAEA,IAAA,OAAOF,SAASK,UAAU;AAC5B;;;;"}
@@ -10,6 +10,10 @@
10
10
  if (typeof label === 'string') {
11
11
  return label;
12
12
  }
13
+ // Return numeric labels except for the internal 'id' field.
14
+ if (typeof label === 'number' && mainField?.name !== 'id') {
15
+ return String(label);
16
+ }
13
17
  return relation.documentId;
14
18
  };
15
19
 
@@ -1 +1 @@
1
- {"version":3,"file":"relations.mjs","sources":["../../../admin/src/utils/relations.ts"],"sourcesContent":["import type { MainField } from './attributes';\nimport type { RelationResult } from '../../../shared/contracts/relations';\n\n/**\n * @internal\n * @description Get the label of a relation, the contract has [key: string]: unknown,\n * so we need to check if the mainFieldKey is defined and if the relation has a value\n * under that property. If it does, we then verify it's type of string and return it.\n *\n * We fallback to the documentId.\n */\nconst getRelationLabel = (relation: RelationResult, mainField?: MainField): string => {\n const label = mainField && relation[mainField.name] ? relation[mainField.name] : null;\n\n if (typeof label === 'string') {\n return label;\n }\n\n return relation.documentId;\n};\n\nexport { getRelationLabel };\n"],"names":["getRelationLabel","relation","mainField","label","name","documentId"],"mappings":"AAGA;;;;;;;IAQA,MAAMA,gBAAAA,GAAmB,CAACC,QAAAA,EAA0BC,SAAAA,GAAAA;AAClD,IAAA,MAAMC,KAAAA,GAAQD,SAAAA,IAAaD,QAAQ,CAACC,SAAAA,CAAUE,IAAI,CAAC,GAAGH,QAAQ,CAACC,SAAAA,CAAUE,IAAI,CAAC,GAAG,IAAA;IAEjF,IAAI,OAAOD,UAAU,QAAA,EAAU;QAC7B,OAAOA,KAAAA;AACT,IAAA;AAEA,IAAA,OAAOF,SAASI,UAAU;AAC5B;;;;"}
1
+ {"version":3,"file":"relations.mjs","sources":["../../../admin/src/utils/relations.ts"],"sourcesContent":["import type { MainField } from './attributes';\nimport type { RelationResult } from '../../../shared/contracts/relations';\n\n/**\n * @internal\n * @description Get the label of a relation, the contract has [key: string]: unknown,\n * so we need to check if the mainFieldKey is defined and if the relation has a value\n * under that property. If it does, we then verify it's type of string and return it.\n *\n * We fallback to the documentId.\n */\nconst getRelationLabel = (relation: RelationResult, mainField?: MainField): string => {\n const label = mainField && relation[mainField.name] ? relation[mainField.name] : null;\n\n if (typeof label === 'string') {\n return label;\n }\n\n // Return numeric labels except for the internal 'id' field.\n if (typeof label === 'number' && mainField?.name !== 'id') {\n return String(label);\n }\n\n return relation.documentId;\n};\n\nexport { getRelationLabel };\n"],"names":["getRelationLabel","relation","mainField","label","name","String","documentId"],"mappings":"AAGA;;;;;;;IAQA,MAAMA,gBAAAA,GAAmB,CAACC,QAAAA,EAA0BC,SAAAA,GAAAA;AAClD,IAAA,MAAMC,KAAAA,GAAQD,SAAAA,IAAaD,QAAQ,CAACC,SAAAA,CAAUE,IAAI,CAAC,GAAGH,QAAQ,CAACC,SAAAA,CAAUE,IAAI,CAAC,GAAG,IAAA;IAEjF,IAAI,OAAOD,UAAU,QAAA,EAAU;QAC7B,OAAOA,KAAAA;AACT,IAAA;;AAGA,IAAA,IAAI,OAAOA,KAAAA,KAAU,QAAA,IAAYD,SAAAA,EAAWE,SAAS,IAAA,EAAM;AACzD,QAAA,OAAOC,MAAAA,CAAOF,KAAAA,CAAAA;AAChB,IAAA;AAEA,IAAA,OAAOF,SAASK,UAAU;AAC5B;;;;"}
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ var strapiUtils = require('@strapi/utils');
4
+ var attributes = require('../../services/utils/configuration/attributes.js');
5
+
6
+ const FALLBACK_MAIN_FIELD = 'documentId';
7
+ /**
8
+ * Removes invalid entries left in the fields array after permission sanitization.
9
+ */ const compactSanitizedFields = (fields)=>{
10
+ if (!Array.isArray(fields)) {
11
+ return undefined;
12
+ }
13
+ return fields.filter((field)=>typeof field === 'string');
14
+ };
15
+ /**
16
+ * Resolves the main field used for homepage widgets, falling back when the user cannot read it.
17
+ */ const resolveReadableMainField = (contentType, configuration, permissionChecker)=>{
18
+ const candidateMainField = configuration?.settings?.mainField ?? attributes.getDefaultMainField(contentType);
19
+ if (permissionChecker.cannot.read(null, candidateMainField)) {
20
+ return FALLBACK_MAIN_FIELD;
21
+ }
22
+ return candidateMainField;
23
+ };
24
+ /**
25
+ * Builds the fields array requested before permission sanitization.
26
+ */ const buildHomepageQueryFields = (contentType, mainField)=>{
27
+ const fields = [
28
+ FALLBACK_MAIN_FIELD,
29
+ 'updatedAt'
30
+ ];
31
+ if (strapiUtils.contentTypes.hasDraftAndPublish(contentType)) {
32
+ fields.push('publishedAt');
33
+ }
34
+ if (mainField !== FALLBACK_MAIN_FIELD && !fields.includes(mainField)) {
35
+ fields.push(mainField);
36
+ }
37
+ if (contentType.pluginOptions?.i18n?.localized) {
38
+ fields.push('locale');
39
+ }
40
+ return fields;
41
+ };
42
+ /**
43
+ * Picks a main field that is present in the sanitized fields selection.
44
+ */ const resolveTitleField = (mainField, sanitizedFields)=>{
45
+ if (sanitizedFields?.includes(mainField)) {
46
+ return mainField;
47
+ }
48
+ return FALLBACK_MAIN_FIELD;
49
+ };
50
+
51
+ exports.FALLBACK_MAIN_FIELD = FALLBACK_MAIN_FIELD;
52
+ exports.buildHomepageQueryFields = buildHomepageQueryFields;
53
+ exports.compactSanitizedFields = compactSanitizedFields;
54
+ exports.resolveReadableMainField = resolveReadableMainField;
55
+ exports.resolveTitleField = resolveTitleField;
56
+ //# sourceMappingURL=homepage-query-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"homepage-query-utils.js","sources":["../../../../server/src/homepage/services/homepage-query-utils.ts"],"sourcesContent":["import type { Schema } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\nimport { getDefaultMainField } from '../../services/utils/configuration/attributes';\n\nexport const FALLBACK_MAIN_FIELD = 'documentId';\n\nexport type HomepagePermissionChecker = {\n cannot: {\n read: (entity: null, field: string) => boolean;\n };\n};\n\n/**\n * Removes invalid entries left in the fields array after permission sanitization.\n */\nexport const compactSanitizedFields = (fields: unknown): string[] | undefined => {\n if (!Array.isArray(fields)) {\n return undefined;\n }\n\n return fields.filter((field): field is string => typeof field === 'string');\n};\n\n/**\n * Resolves the main field used for homepage widgets, falling back when the user cannot read it.\n */\nexport const resolveReadableMainField = (\n contentType: Schema.ContentType,\n configuration: { settings?: { mainField?: string } } | undefined,\n permissionChecker: HomepagePermissionChecker\n): string => {\n const candidateMainField = configuration?.settings?.mainField ?? getDefaultMainField(contentType);\n\n if (permissionChecker.cannot.read(null, candidateMainField)) {\n return FALLBACK_MAIN_FIELD;\n }\n\n return candidateMainField;\n};\n\n/**\n * Builds the fields array requested before permission sanitization.\n */\nexport const buildHomepageQueryFields = (\n contentType: Schema.ContentType,\n mainField: string\n): string[] => {\n const fields = [FALLBACK_MAIN_FIELD, 'updatedAt'];\n\n if (contentTypes.hasDraftAndPublish(contentType)) {\n fields.push('publishedAt');\n }\n\n if (mainField !== FALLBACK_MAIN_FIELD && !fields.includes(mainField)) {\n fields.push(mainField);\n }\n\n if ((contentType.pluginOptions?.i18n as { localized?: boolean } | undefined)?.localized) {\n fields.push('locale');\n }\n\n return fields;\n};\n\n/**\n * Picks a main field that is present in the sanitized fields selection.\n */\nexport const resolveTitleField = (\n mainField: string,\n sanitizedFields: string[] | undefined\n): string => {\n if (sanitizedFields?.includes(mainField)) {\n return mainField;\n }\n\n return FALLBACK_MAIN_FIELD;\n};\n"],"names":["FALLBACK_MAIN_FIELD","compactSanitizedFields","fields","Array","isArray","undefined","filter","field","resolveReadableMainField","contentType","configuration","permissionChecker","candidateMainField","settings","mainField","getDefaultMainField","cannot","read","buildHomepageQueryFields","contentTypes","hasDraftAndPublish","push","includes","pluginOptions","i18n","localized","resolveTitleField","sanitizedFields"],"mappings":";;;;;AAKO,MAAMA,sBAAsB;AAQnC;;IAGO,MAAMC,sBAAAA,GAAyB,CAACC,MAAAA,GAAAA;AACrC,IAAA,IAAI,CAACC,KAAAA,CAAMC,OAAO,CAACF,MAAAA,CAAAA,EAAS;QAC1B,OAAOG,SAAAA;AACT,IAAA;AAEA,IAAA,OAAOH,OAAOI,MAAM,CAAC,CAACC,KAAAA,GAA2B,OAAOA,KAAAA,KAAU,QAAA,CAAA;AACpE;AAEA;;AAEC,IACM,MAAMC,wBAAAA,GAA2B,CACtCC,aACAC,aAAAA,EACAC,iBAAAA,GAAAA;AAEA,IAAA,MAAMC,kBAAAA,GAAqBF,aAAAA,EAAeG,QAAAA,EAAUC,SAAAA,IAAaC,8BAAAA,CAAoBN,WAAAA,CAAAA;AAErF,IAAA,IAAIE,kBAAkBK,MAAM,CAACC,IAAI,CAAC,MAAML,kBAAAA,CAAAA,EAAqB;QAC3D,OAAOZ,mBAAAA;AACT,IAAA;IAEA,OAAOY,kBAAAA;AACT;AAEA;;AAEC,IACM,MAAMM,wBAAAA,GAA2B,CACtCT,WAAAA,EACAK,SAAAA,GAAAA;AAEA,IAAA,MAAMZ,MAAAA,GAAS;AAACF,QAAAA,mBAAAA;AAAqB,QAAA;AAAY,KAAA;IAEjD,IAAImB,wBAAAA,CAAaC,kBAAkB,CAACX,WAAAA,CAAAA,EAAc;AAChDP,QAAAA,MAAAA,CAAOmB,IAAI,CAAC,aAAA,CAAA;AACd,IAAA;AAEA,IAAA,IAAIP,cAAcd,mBAAAA,IAAuB,CAACE,MAAAA,CAAOoB,QAAQ,CAACR,SAAAA,CAAAA,EAAY;AACpEZ,QAAAA,MAAAA,CAAOmB,IAAI,CAACP,SAAAA,CAAAA;AACd,IAAA;AAEA,IAAA,IAAKL,WAAAA,CAAYc,aAAa,EAAEC,IAAAA,EAA8CC,SAAAA,EAAW;AACvFvB,QAAAA,MAAAA,CAAOmB,IAAI,CAAC,QAAA,CAAA;AACd,IAAA;IAEA,OAAOnB,MAAAA;AACT;AAEA;;AAEC,IACM,MAAMwB,iBAAAA,GAAoB,CAC/BZ,SAAAA,EACAa,eAAAA,GAAAA;IAEA,IAAIA,eAAAA,EAAiBL,SAASR,SAAAA,CAAAA,EAAY;QACxC,OAAOA,SAAAA;AACT,IAAA;IAEA,OAAOd,mBAAAA;AACT;;;;;;;;"}
@@ -0,0 +1,50 @@
1
+ import { contentTypes } from '@strapi/utils';
2
+ import { getDefaultMainField } from '../../services/utils/configuration/attributes.mjs';
3
+
4
+ const FALLBACK_MAIN_FIELD = 'documentId';
5
+ /**
6
+ * Removes invalid entries left in the fields array after permission sanitization.
7
+ */ const compactSanitizedFields = (fields)=>{
8
+ if (!Array.isArray(fields)) {
9
+ return undefined;
10
+ }
11
+ return fields.filter((field)=>typeof field === 'string');
12
+ };
13
+ /**
14
+ * Resolves the main field used for homepage widgets, falling back when the user cannot read it.
15
+ */ const resolveReadableMainField = (contentType, configuration, permissionChecker)=>{
16
+ const candidateMainField = configuration?.settings?.mainField ?? getDefaultMainField(contentType);
17
+ if (permissionChecker.cannot.read(null, candidateMainField)) {
18
+ return FALLBACK_MAIN_FIELD;
19
+ }
20
+ return candidateMainField;
21
+ };
22
+ /**
23
+ * Builds the fields array requested before permission sanitization.
24
+ */ const buildHomepageQueryFields = (contentType, mainField)=>{
25
+ const fields = [
26
+ FALLBACK_MAIN_FIELD,
27
+ 'updatedAt'
28
+ ];
29
+ if (contentTypes.hasDraftAndPublish(contentType)) {
30
+ fields.push('publishedAt');
31
+ }
32
+ if (mainField !== FALLBACK_MAIN_FIELD && !fields.includes(mainField)) {
33
+ fields.push(mainField);
34
+ }
35
+ if (contentType.pluginOptions?.i18n?.localized) {
36
+ fields.push('locale');
37
+ }
38
+ return fields;
39
+ };
40
+ /**
41
+ * Picks a main field that is present in the sanitized fields selection.
42
+ */ const resolveTitleField = (mainField, sanitizedFields)=>{
43
+ if (sanitizedFields?.includes(mainField)) {
44
+ return mainField;
45
+ }
46
+ return FALLBACK_MAIN_FIELD;
47
+ };
48
+
49
+ export { FALLBACK_MAIN_FIELD, buildHomepageQueryFields, compactSanitizedFields, resolveReadableMainField, resolveTitleField };
50
+ //# sourceMappingURL=homepage-query-utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"homepage-query-utils.mjs","sources":["../../../../server/src/homepage/services/homepage-query-utils.ts"],"sourcesContent":["import type { Schema } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\nimport { getDefaultMainField } from '../../services/utils/configuration/attributes';\n\nexport const FALLBACK_MAIN_FIELD = 'documentId';\n\nexport type HomepagePermissionChecker = {\n cannot: {\n read: (entity: null, field: string) => boolean;\n };\n};\n\n/**\n * Removes invalid entries left in the fields array after permission sanitization.\n */\nexport const compactSanitizedFields = (fields: unknown): string[] | undefined => {\n if (!Array.isArray(fields)) {\n return undefined;\n }\n\n return fields.filter((field): field is string => typeof field === 'string');\n};\n\n/**\n * Resolves the main field used for homepage widgets, falling back when the user cannot read it.\n */\nexport const resolveReadableMainField = (\n contentType: Schema.ContentType,\n configuration: { settings?: { mainField?: string } } | undefined,\n permissionChecker: HomepagePermissionChecker\n): string => {\n const candidateMainField = configuration?.settings?.mainField ?? getDefaultMainField(contentType);\n\n if (permissionChecker.cannot.read(null, candidateMainField)) {\n return FALLBACK_MAIN_FIELD;\n }\n\n return candidateMainField;\n};\n\n/**\n * Builds the fields array requested before permission sanitization.\n */\nexport const buildHomepageQueryFields = (\n contentType: Schema.ContentType,\n mainField: string\n): string[] => {\n const fields = [FALLBACK_MAIN_FIELD, 'updatedAt'];\n\n if (contentTypes.hasDraftAndPublish(contentType)) {\n fields.push('publishedAt');\n }\n\n if (mainField !== FALLBACK_MAIN_FIELD && !fields.includes(mainField)) {\n fields.push(mainField);\n }\n\n if ((contentType.pluginOptions?.i18n as { localized?: boolean } | undefined)?.localized) {\n fields.push('locale');\n }\n\n return fields;\n};\n\n/**\n * Picks a main field that is present in the sanitized fields selection.\n */\nexport const resolveTitleField = (\n mainField: string,\n sanitizedFields: string[] | undefined\n): string => {\n if (sanitizedFields?.includes(mainField)) {\n return mainField;\n }\n\n return FALLBACK_MAIN_FIELD;\n};\n"],"names":["FALLBACK_MAIN_FIELD","compactSanitizedFields","fields","Array","isArray","undefined","filter","field","resolveReadableMainField","contentType","configuration","permissionChecker","candidateMainField","settings","mainField","getDefaultMainField","cannot","read","buildHomepageQueryFields","contentTypes","hasDraftAndPublish","push","includes","pluginOptions","i18n","localized","resolveTitleField","sanitizedFields"],"mappings":";;;AAKO,MAAMA,sBAAsB;AAQnC;;IAGO,MAAMC,sBAAAA,GAAyB,CAACC,MAAAA,GAAAA;AACrC,IAAA,IAAI,CAACC,KAAAA,CAAMC,OAAO,CAACF,MAAAA,CAAAA,EAAS;QAC1B,OAAOG,SAAAA;AACT,IAAA;AAEA,IAAA,OAAOH,OAAOI,MAAM,CAAC,CAACC,KAAAA,GAA2B,OAAOA,KAAAA,KAAU,QAAA,CAAA;AACpE;AAEA;;AAEC,IACM,MAAMC,wBAAAA,GAA2B,CACtCC,aACAC,aAAAA,EACAC,iBAAAA,GAAAA;AAEA,IAAA,MAAMC,kBAAAA,GAAqBF,aAAAA,EAAeG,QAAAA,EAAUC,SAAAA,IAAaC,mBAAAA,CAAoBN,WAAAA,CAAAA;AAErF,IAAA,IAAIE,kBAAkBK,MAAM,CAACC,IAAI,CAAC,MAAML,kBAAAA,CAAAA,EAAqB;QAC3D,OAAOZ,mBAAAA;AACT,IAAA;IAEA,OAAOY,kBAAAA;AACT;AAEA;;AAEC,IACM,MAAMM,wBAAAA,GAA2B,CACtCT,WAAAA,EACAK,SAAAA,GAAAA;AAEA,IAAA,MAAMZ,MAAAA,GAAS;AAACF,QAAAA,mBAAAA;AAAqB,QAAA;AAAY,KAAA;IAEjD,IAAImB,YAAAA,CAAaC,kBAAkB,CAACX,WAAAA,CAAAA,EAAc;AAChDP,QAAAA,MAAAA,CAAOmB,IAAI,CAAC,aAAA,CAAA;AACd,IAAA;AAEA,IAAA,IAAIP,cAAcd,mBAAAA,IAAuB,CAACE,MAAAA,CAAOoB,QAAQ,CAACR,SAAAA,CAAAA,EAAY;AACpEZ,QAAAA,MAAAA,CAAOmB,IAAI,CAACP,SAAAA,CAAAA;AACd,IAAA;AAEA,IAAA,IAAKL,WAAAA,CAAYc,aAAa,EAAEC,IAAAA,EAA8CC,SAAAA,EAAW;AACvFvB,QAAAA,MAAAA,CAAOmB,IAAI,CAAC,QAAA,CAAA;AACd,IAAA;IAEA,OAAOnB,MAAAA;AACT;AAEA;;AAEC,IACM,MAAMwB,iBAAAA,GAAoB,CAC/BZ,SAAAA,EACAa,eAAAA,GAAAA;IAEA,IAAIA,eAAAA,EAAiBL,SAASR,SAAAA,CAAAA,EAAY;QACxC,OAAOA,SAAAA;AACT,IAAA;IAEA,OAAOd,mBAAAA;AACT;;;;"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var strapiUtils = require('@strapi/utils');
4
+ var homepageQueryUtils = require('./homepage-query-utils.js');
4
5
 
5
6
  const createHomepageService = ({ strapi })=>{
6
7
  const MAX_DOCUMENTS = 4;
@@ -35,31 +36,21 @@ const createHomepageService = ({ strapi })=>{
35
36
  });
36
37
  return readPermissions.map((permission)=>permission.subject).filter(Boolean);
37
38
  };
39
+ const permissionCheckerService = strapi.plugin('content-manager').service('permission-checker');
40
+ const getPermissionChecker = (uid)=>permissionCheckerService.create({
41
+ userAbility: strapi.requestContext.get()?.state.userAbility,
42
+ model: uid
43
+ });
38
44
  const getContentTypesMeta = (allowedContentTypeUids, configurations)=>{
39
45
  return allowedContentTypeUids.map((uid)=>{
40
46
  const configuration = configurations.find((config)=>config.uid === uid);
41
47
  const contentType = strapi.contentType(uid);
42
- const fields = [
43
- 'documentId',
44
- 'updatedAt'
45
- ];
46
- // Add fields required to get the status if D&P is enabled
48
+ const mainField = homepageQueryUtils.resolveReadableMainField(contentType, configuration, getPermissionChecker(uid));
49
+ const fields = homepageQueryUtils.buildHomepageQueryFields(contentType, mainField);
47
50
  const hasDraftAndPublish = strapiUtils.contentTypes.hasDraftAndPublish(contentType);
48
- if (hasDraftAndPublish) {
49
- fields.push('publishedAt');
50
- }
51
- // Only add the main field if it's defined
52
- if (configuration?.settings.mainField) {
53
- fields.push(configuration.settings.mainField);
54
- }
55
- // Only add locale if it's localized
56
- const isLocalized = contentType.pluginOptions?.i18n?.localized;
57
- if (isLocalized) {
58
- fields.push('locale');
59
- }
60
51
  return {
61
52
  fields,
62
- mainField: configuration.settings.mainField,
53
+ mainField,
63
54
  contentType,
64
55
  hasDraftAndPublish,
65
56
  uid
@@ -85,11 +76,22 @@ const createHomepageService = ({ strapi })=>{
85
76
  };
86
77
  });
87
78
  };
88
- const permissionCheckerService = strapi.plugin('content-manager').service('permission-checker');
89
- const getPermissionChecker = (uid)=>permissionCheckerService.create({
90
- userAbility: strapi.requestContext.get()?.state.userAbility,
91
- model: uid
79
+ const sanitizeHomepageQuery = async (meta, additionalQueryParams)=>{
80
+ const permissionQuery = await getPermissionChecker(meta.uid).sanitizedQuery.read({
81
+ limit: MAX_DOCUMENTS,
82
+ fields: meta.fields,
83
+ ...additionalQueryParams,
84
+ locale: '*'
92
85
  });
86
+ const sanitizedFields = homepageQueryUtils.compactSanitizedFields(permissionQuery.fields);
87
+ if (sanitizedFields !== undefined) {
88
+ permissionQuery.fields = sanitizedFields;
89
+ }
90
+ return {
91
+ permissionQuery,
92
+ titleField: homepageQueryUtils.resolveTitleField(meta.mainField, sanitizedFields)
93
+ };
94
+ };
93
95
  return {
94
96
  async addStatusToDocuments (documents) {
95
97
  return Promise.all(documents.map(async (recentDocument)=>{
@@ -118,15 +120,13 @@ const createHomepageService = ({ strapi })=>{
118
120
  // Get the necessary metadata for the documents
119
121
  const contentTypesMeta = getContentTypesMeta(allowedContentTypeUids, configurations);
120
122
  const recentDocuments = await Promise.all(contentTypesMeta.map(async (meta)=>{
121
- const permissionQuery = await getPermissionChecker(meta.uid).sanitizedQuery.read({
122
- limit: MAX_DOCUMENTS,
123
- fields: meta.fields,
124
- ...additionalQueryParams,
125
- locale: '*'
126
- });
123
+ const { permissionQuery, titleField } = await sanitizeHomepageQuery(meta, additionalQueryParams);
127
124
  const docs = await strapi.documents(meta.uid).findMany(permissionQuery);
128
125
  const populate = additionalQueryParams?.populate;
129
- return formatDocuments(docs, meta, populate);
126
+ return formatDocuments(docs, {
127
+ ...meta,
128
+ mainField: titleField
129
+ }, populate);
130
130
  }));
131
131
  return recentDocuments.flat().sort((a, b)=>{
132
132
  switch(additionalQueryParams?.sort){
@@ -174,24 +174,29 @@ const createHomepageService = ({ strapi })=>{
174
174
  await Promise.all(contentTypesMeta.map(async (meta)=>{
175
175
  const strapiDBConnection = strapi.db.connection;
176
176
  const tableName = strapi.contentType(meta.uid).collectionName;
177
- if (tableName) {
178
- if (meta.hasDraftAndPublish) {
179
- const draftDocuments = await strapiDBConnection(tableName).whereNull('published_at').whereIn('document_id', function() {
180
- this.select('document_id').from(tableName).groupBy('document_id').havingRaw('COUNT(*) = 1');
181
- }).count('* as count').first();
182
- countDocuments.draft += Number(draftDocuments?.count) || 0;
183
- }
184
- const publishedDocuments = meta.hasDraftAndPublish ? await strapiDBConnection(tableName).countDistinct('draft.document_id as count').from(`${tableName} as draft`).join(`${tableName} as published`, function() {
185
- this.on('draft.document_id', '=', 'published.document_id').andOn('draft.updated_at', '=', 'published.updated_at').andOnNull('draft.published_at').andOnNotNull('published.published_at');
186
- }).first() : await strapiDBConnection(tableName).countDistinct('document_id as count').from(`${tableName}`).first();
177
+ if (!tableName) return;
178
+ if (!meta.hasDraftAndPublish) {
179
+ const publishedDocuments = await strapiDBConnection(tableName).countDistinct('document_id as count').first();
187
180
  countDocuments.published += Number(publishedDocuments?.count) || 0;
188
- if (meta.hasDraftAndPublish) {
189
- const modifiedDocuments = await strapiDBConnection(tableName).select('draft.document_id').from(`${tableName} as draft`).join(`${tableName} as published`, function() {
190
- this.on('draft.document_id', '=', 'published.document_id').andOn('draft.updated_at', '!=', 'published.updated_at').andOnNull('draft.published_at').andOnNotNull('published.published_at');
191
- }).countDistinct('draft.document_id as count').groupBy('draft.document_id').first();
192
- countDocuments.modified += Number(modifiedDocuments?.count) || 0;
193
- }
181
+ return;
194
182
  }
183
+ // Classify each document_id into a single bucket (draft / published / modified)
184
+ // in one pass. Replaces three separate self-join queries that scaled poorly on
185
+ // large tables — see https://github.com/strapi/strapi/issues/25200.
186
+ const classified = strapiDBConnection(tableName).select('document_id').select(strapiDBConnection.raw(`CASE
187
+ WHEN MAX(CASE WHEN published_at IS NOT NULL THEN 1 ELSE 0 END) = 0
188
+ THEN 'draft'
189
+ WHEN MAX(CASE WHEN published_at IS NULL THEN updated_at END) =
190
+ MAX(CASE WHEN published_at IS NOT NULL THEN updated_at END)
191
+ THEN 'published'
192
+ ELSE 'modified'
193
+ END AS bucket`)).groupBy('document_id');
194
+ const counts = await strapiDBConnection.from(classified.as('classified')).select(strapiDBConnection.raw(`COUNT(CASE WHEN bucket = 'draft' THEN 1 END) AS draft,
195
+ COUNT(CASE WHEN bucket = 'published' THEN 1 END) AS published,
196
+ COUNT(CASE WHEN bucket = 'modified' THEN 1 END) AS modified`)).first();
197
+ countDocuments.draft += Number(counts?.draft) || 0;
198
+ countDocuments.published += Number(counts?.published) || 0;
199
+ countDocuments.modified += Number(counts?.modified) || 0;
195
200
  }));
196
201
  return countDocuments;
197
202
  }
@@ -1 +1 @@
1
- {"version":3,"file":"homepage.js","sources":["../../../../server/src/homepage/services/homepage.ts"],"sourcesContent":["/* eslint-disable func-names */\nimport type { Core, Modules, Schema } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\nimport type {\n GetCountDocuments,\n GetRecentDocuments,\n RecentDocument,\n} from '../../../../shared/contracts/homepage';\n\nconst createHomepageService = ({ strapi }: { strapi: Core.Strapi }) => {\n const MAX_DOCUMENTS = 4;\n\n const metadataService = strapi.plugin('content-manager').service('document-metadata');\n const permissionService = strapi.admin.services.permission;\n\n type ContentTypeConfiguration = {\n uid: RecentDocument['contentTypeUid'];\n settings: { mainField: string };\n };\n const getConfiguration = async (\n contentTypeUids: RecentDocument['contentTypeUid'][]\n ): Promise<ContentTypeConfiguration[]> => {\n /**\n * Don't use the strapi.store util because we need to make\n * more precise queries than exact key matches, in order to make as few queries as possible.\n */\n const coreStore = strapi.db.query('strapi::core-store');\n const rawConfigurations = await coreStore.findMany({\n where: {\n key: {\n $in: contentTypeUids.map(\n (contentType) => `plugin_content_manager_configuration_content_types::${contentType}`\n ),\n },\n },\n });\n\n return rawConfigurations.map((rawConfiguration) => {\n return JSON.parse(rawConfiguration.value);\n });\n };\n\n const getPermittedContentTypes = async () => {\n const readPermissions: Modules.Permissions.PermissionRule[] = await permissionService.findMany({\n where: {\n role: { users: { id: strapi.requestContext.get()?.state?.user.id } },\n action: 'plugin::content-manager.explorer.read',\n },\n });\n\n return readPermissions\n .map((permission) => permission.subject)\n .filter(Boolean) as RecentDocument['contentTypeUid'][];\n };\n\n type ContentTypeMeta = {\n fields: string[];\n mainField: string;\n contentType: Schema.ContentType;\n hasDraftAndPublish: boolean;\n uid: RecentDocument['contentTypeUid'];\n };\n\n const getContentTypesMeta = (\n allowedContentTypeUids: RecentDocument['contentTypeUid'][],\n configurations: ContentTypeConfiguration[]\n ): ContentTypeMeta[] => {\n return allowedContentTypeUids.map((uid) => {\n const configuration = configurations.find((config) => config.uid === uid);\n const contentType = strapi.contentType(uid);\n const fields = ['documentId', 'updatedAt'];\n\n // Add fields required to get the status if D&P is enabled\n const hasDraftAndPublish = contentTypes.hasDraftAndPublish(contentType);\n if (hasDraftAndPublish) {\n fields.push('publishedAt');\n }\n\n // Only add the main field if it's defined\n if (configuration?.settings.mainField) {\n fields.push(configuration.settings.mainField);\n }\n\n // Only add locale if it's localized\n const isLocalized = (contentType.pluginOptions?.i18n as any)?.localized;\n if (isLocalized) {\n fields.push('locale');\n }\n\n return {\n fields,\n mainField: configuration!.settings.mainField,\n contentType,\n hasDraftAndPublish,\n uid,\n };\n });\n };\n\n const formatDocuments = (\n documents: Modules.Documents.AnyDocument[],\n meta: ContentTypeMeta,\n populate?: string[]\n ) => {\n return documents.map((document) => {\n const additionalFields =\n populate?.reduce(\n (acc, key) => {\n acc[key] = document[key];\n return acc;\n },\n {} as Record<string, any>\n ) || {};\n return {\n documentId: document.documentId,\n locale: document.locale ?? null,\n updatedAt: new Date(document.updatedAt),\n title: document[meta.mainField ?? 'documentId'],\n publishedAt:\n meta.hasDraftAndPublish && document.publishedAt ? new Date(document.publishedAt) : null,\n contentTypeUid: meta.uid,\n contentTypeDisplayName: meta.contentType.info.displayName,\n kind: meta.contentType.kind,\n ...additionalFields,\n };\n });\n };\n\n const permissionCheckerService = strapi.plugin('content-manager').service('permission-checker');\n const getPermissionChecker = (uid: string) =>\n permissionCheckerService.create({\n userAbility: strapi.requestContext.get()?.state.userAbility,\n model: uid,\n });\n\n return {\n async addStatusToDocuments(documents: RecentDocument[]): Promise<RecentDocument[]> {\n return Promise.all(\n documents.map(async (recentDocument) => {\n const hasDraftAndPublish = contentTypes.hasDraftAndPublish(\n strapi.contentType(recentDocument.contentTypeUid)\n );\n /**\n * Tries to query the other version of the document if draft and publish is enabled,\n * so that we know when to give the \"modified\" status.\n */\n const { availableStatus } = await metadataService.getMetadata(\n recentDocument.contentTypeUid,\n recentDocument,\n {\n availableStatus: hasDraftAndPublish,\n availableLocales: false,\n }\n );\n const status: RecentDocument['status'] = metadataService.getStatus(\n recentDocument,\n availableStatus\n );\n\n return {\n ...recentDocument,\n status: hasDraftAndPublish ? status : undefined,\n };\n })\n );\n },\n\n async queryLastDocuments(\n additionalQueryParams?: Record<string, unknown>,\n draftAndPublishOnly?: boolean\n ): Promise<RecentDocument[]> {\n const permittedContentTypes = await getPermittedContentTypes();\n const allowedContentTypeUids = draftAndPublishOnly\n ? permittedContentTypes.filter((uid) => {\n return contentTypes.hasDraftAndPublish(strapi.contentType(uid));\n })\n : permittedContentTypes;\n // Fetch the configuration for each content type in a single query\n const configurations = await getConfiguration(allowedContentTypeUids);\n // Get the necessary metadata for the documents\n const contentTypesMeta = getContentTypesMeta(allowedContentTypeUids, configurations);\n\n const recentDocuments = await Promise.all(\n contentTypesMeta.map(async (meta) => {\n const permissionQuery = await getPermissionChecker(meta.uid).sanitizedQuery.read({\n limit: MAX_DOCUMENTS,\n fields: meta.fields,\n ...additionalQueryParams,\n locale: '*',\n });\n\n const docs = await strapi.documents(meta.uid).findMany(permissionQuery);\n const populate = additionalQueryParams?.populate as string[];\n\n return formatDocuments(docs, meta, populate);\n })\n );\n\n return recentDocuments\n .flat()\n .sort((a, b) => {\n switch (additionalQueryParams?.sort) {\n case 'publishedAt:desc':\n if (!a.publishedAt || !b.publishedAt) return 0;\n return b.publishedAt.valueOf() - a.publishedAt.valueOf();\n case 'publishedAt:asc':\n if (!a.publishedAt || !b.publishedAt) return 0;\n return a.publishedAt.valueOf() - b.publishedAt.valueOf();\n case 'updatedAt:desc':\n if (!a.updatedAt || !b.updatedAt) return 0;\n return b.updatedAt.valueOf() - a.updatedAt.valueOf();\n case 'updatedAt:asc':\n if (!a.updatedAt || !b.updatedAt) return 0;\n return a.updatedAt.valueOf() - b.updatedAt.valueOf();\n default:\n return 0;\n }\n })\n .slice(0, MAX_DOCUMENTS);\n },\n\n async getRecentlyPublishedDocuments(): Promise<GetRecentDocuments.Response['data']> {\n const recentlyPublishedDocuments = await this.queryLastDocuments(\n {\n sort: 'publishedAt:desc',\n status: 'published',\n },\n true\n );\n\n return this.addStatusToDocuments(recentlyPublishedDocuments);\n },\n\n async getRecentlyUpdatedDocuments(): Promise<GetRecentDocuments.Response['data']> {\n const recentlyUpdatedDocuments = await this.queryLastDocuments({\n sort: 'updatedAt:desc',\n });\n\n return this.addStatusToDocuments(recentlyUpdatedDocuments);\n },\n\n async getCountDocuments(): Promise<GetCountDocuments.Response['data']> {\n const permittedContentTypes = await getPermittedContentTypes();\n // Fetch the configuration for each content type in a single query\n const configurations = await getConfiguration(permittedContentTypes);\n // Get the necessary metadata for the documents\n const contentTypesMeta = getContentTypesMeta(permittedContentTypes, configurations);\n\n const countDocuments = {\n draft: 0,\n published: 0,\n modified: 0,\n };\n\n await Promise.all(\n contentTypesMeta.map(async (meta) => {\n const strapiDBConnection = strapi.db.connection;\n const tableName = strapi.contentType(meta.uid).collectionName;\n if (tableName) {\n if (meta.hasDraftAndPublish) {\n const draftDocuments = await strapiDBConnection(tableName)\n .whereNull('published_at')\n .whereIn('document_id', function () {\n this.select('document_id')\n .from(tableName)\n .groupBy('document_id')\n .havingRaw('COUNT(*) = 1');\n })\n .count('* as count')\n .first();\n countDocuments.draft += Number(draftDocuments?.count) || 0;\n }\n\n const publishedDocuments = meta.hasDraftAndPublish\n ? await strapiDBConnection(tableName)\n .countDistinct('draft.document_id as count')\n .from(`${tableName} as draft`)\n .join(`${tableName} as published`, function () {\n this.on('draft.document_id', '=', 'published.document_id')\n .andOn('draft.updated_at', '=', 'published.updated_at')\n .andOnNull('draft.published_at')\n .andOnNotNull('published.published_at');\n })\n .first()\n : await strapiDBConnection(tableName)\n .countDistinct('document_id as count')\n .from(`${tableName}`)\n .first();\n countDocuments.published += Number(publishedDocuments?.count) || 0;\n\n if (meta.hasDraftAndPublish) {\n const modifiedDocuments = await strapiDBConnection(tableName)\n .select('draft.document_id')\n .from(`${tableName} as draft`)\n .join(`${tableName} as published`, function () {\n this.on('draft.document_id', '=', 'published.document_id')\n .andOn('draft.updated_at', '!=', 'published.updated_at')\n .andOnNull('draft.published_at')\n .andOnNotNull('published.published_at');\n })\n .countDistinct('draft.document_id as count')\n .groupBy('draft.document_id')\n .first();\n countDocuments.modified += Number(modifiedDocuments?.count) || 0;\n }\n }\n })\n );\n\n return countDocuments;\n },\n };\n};\n\nexport { createHomepageService };\n"],"names":["createHomepageService","strapi","MAX_DOCUMENTS","metadataService","plugin","service","permissionService","admin","services","permission","getConfiguration","contentTypeUids","coreStore","db","query","rawConfigurations","findMany","where","key","$in","map","contentType","rawConfiguration","JSON","parse","value","getPermittedContentTypes","readPermissions","role","users","id","requestContext","get","state","user","action","subject","filter","Boolean","getContentTypesMeta","allowedContentTypeUids","configurations","uid","configuration","find","config","fields","hasDraftAndPublish","contentTypes","push","settings","mainField","isLocalized","pluginOptions","i18n","localized","formatDocuments","documents","meta","populate","document","additionalFields","reduce","acc","documentId","locale","updatedAt","Date","title","publishedAt","contentTypeUid","contentTypeDisplayName","info","displayName","kind","permissionCheckerService","getPermissionChecker","create","userAbility","model","addStatusToDocuments","Promise","all","recentDocument","availableStatus","getMetadata","availableLocales","status","getStatus","undefined","queryLastDocuments","additionalQueryParams","draftAndPublishOnly","permittedContentTypes","contentTypesMeta","recentDocuments","permissionQuery","sanitizedQuery","read","limit","docs","flat","sort","a","b","valueOf","slice","getRecentlyPublishedDocuments","recentlyPublishedDocuments","getRecentlyUpdatedDocuments","recentlyUpdatedDocuments","getCountDocuments","countDocuments","draft","published","modified","strapiDBConnection","connection","tableName","collectionName","draftDocuments","whereNull","whereIn","select","from","groupBy","havingRaw","count","first","Number","publishedDocuments","countDistinct","join","on","andOn","andOnNull","andOnNotNull","modifiedDocuments"],"mappings":"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"}
1
+ {"version":3,"file":"homepage.js","sources":["../../../../server/src/homepage/services/homepage.ts"],"sourcesContent":["/* eslint-disable func-names */\nimport type { Core, Modules, Schema } from '@strapi/types';\nimport { contentTypes } from '@strapi/utils';\n\nimport type {\n GetCountDocuments,\n GetRecentDocuments,\n RecentDocument,\n} from '../../../../shared/contracts/homepage';\n\nimport {\n buildHomepageQueryFields,\n compactSanitizedFields,\n resolveReadableMainField,\n resolveTitleField,\n} from './homepage-query-utils';\n\nconst createHomepageService = ({ strapi }: { strapi: Core.Strapi }) => {\n const MAX_DOCUMENTS = 4;\n\n const metadataService = strapi.plugin('content-manager').service('document-metadata');\n const permissionService = strapi.admin.services.permission;\n\n type ContentTypeConfiguration = {\n uid: RecentDocument['contentTypeUid'];\n settings: { mainField: string };\n };\n const getConfiguration = async (\n contentTypeUids: RecentDocument['contentTypeUid'][]\n ): Promise<ContentTypeConfiguration[]> => {\n /**\n * Don't use the strapi.store util because we need to make\n * more precise queries than exact key matches, in order to make as few queries as possible.\n */\n const coreStore = strapi.db.query('strapi::core-store');\n const rawConfigurations = await coreStore.findMany({\n where: {\n key: {\n $in: contentTypeUids.map(\n (contentType) => `plugin_content_manager_configuration_content_types::${contentType}`\n ),\n },\n },\n });\n\n return rawConfigurations.map((rawConfiguration) => {\n return JSON.parse(rawConfiguration.value);\n });\n };\n\n const getPermittedContentTypes = async () => {\n const readPermissions: Modules.Permissions.PermissionRule[] = await permissionService.findMany({\n where: {\n role: { users: { id: strapi.requestContext.get()?.state?.user.id } },\n action: 'plugin::content-manager.explorer.read',\n },\n });\n\n return readPermissions\n .map((permission) => permission.subject)\n .filter(Boolean) as RecentDocument['contentTypeUid'][];\n };\n\n type ContentTypeMeta = {\n fields: string[];\n mainField: string;\n contentType: Schema.ContentType;\n hasDraftAndPublish: boolean;\n uid: RecentDocument['contentTypeUid'];\n };\n\n const permissionCheckerService = strapi.plugin('content-manager').service('permission-checker');\n const getPermissionChecker = (uid: string) =>\n permissionCheckerService.create({\n userAbility: strapi.requestContext.get()?.state.userAbility,\n model: uid,\n });\n\n const getContentTypesMeta = (\n allowedContentTypeUids: RecentDocument['contentTypeUid'][],\n configurations: ContentTypeConfiguration[]\n ): ContentTypeMeta[] => {\n return allowedContentTypeUids.map((uid) => {\n const configuration = configurations.find((config) => config.uid === uid);\n const contentType = strapi.contentType(uid);\n const mainField = resolveReadableMainField(\n contentType,\n configuration,\n getPermissionChecker(uid)\n );\n const fields = buildHomepageQueryFields(contentType, mainField);\n const hasDraftAndPublish = contentTypes.hasDraftAndPublish(contentType);\n\n return {\n fields,\n mainField,\n contentType,\n hasDraftAndPublish,\n uid,\n };\n });\n };\n\n const formatDocuments = (\n documents: Modules.Documents.AnyDocument[],\n meta: ContentTypeMeta,\n populate?: string[]\n ) => {\n return documents.map((document) => {\n const additionalFields =\n populate?.reduce(\n (acc, key) => {\n acc[key] = document[key];\n return acc;\n },\n {} as Record<string, any>\n ) || {};\n return {\n documentId: document.documentId,\n locale: document.locale ?? null,\n updatedAt: new Date(document.updatedAt),\n title: document[meta.mainField ?? 'documentId'],\n publishedAt:\n meta.hasDraftAndPublish && document.publishedAt ? new Date(document.publishedAt) : null,\n contentTypeUid: meta.uid,\n contentTypeDisplayName: meta.contentType.info.displayName,\n kind: meta.contentType.kind,\n ...additionalFields,\n };\n });\n };\n\n const sanitizeHomepageQuery = async (\n meta: ContentTypeMeta,\n additionalQueryParams?: Record<string, unknown>\n ) => {\n const permissionQuery = await getPermissionChecker(meta.uid).sanitizedQuery.read({\n limit: MAX_DOCUMENTS,\n fields: meta.fields,\n ...additionalQueryParams,\n locale: '*',\n });\n\n const sanitizedFields = compactSanitizedFields(permissionQuery.fields);\n if (sanitizedFields !== undefined) {\n permissionQuery.fields = sanitizedFields;\n }\n\n return {\n permissionQuery,\n titleField: resolveTitleField(meta.mainField, sanitizedFields),\n };\n };\n\n return {\n async addStatusToDocuments(documents: RecentDocument[]): Promise<RecentDocument[]> {\n return Promise.all(\n documents.map(async (recentDocument) => {\n const hasDraftAndPublish = contentTypes.hasDraftAndPublish(\n strapi.contentType(recentDocument.contentTypeUid)\n );\n /**\n * Tries to query the other version of the document if draft and publish is enabled,\n * so that we know when to give the \"modified\" status.\n */\n const { availableStatus } = await metadataService.getMetadata(\n recentDocument.contentTypeUid,\n recentDocument,\n {\n availableStatus: hasDraftAndPublish,\n availableLocales: false,\n }\n );\n const status: RecentDocument['status'] = metadataService.getStatus(\n recentDocument,\n availableStatus\n );\n\n return {\n ...recentDocument,\n status: hasDraftAndPublish ? status : undefined,\n };\n })\n );\n },\n\n async queryLastDocuments(\n additionalQueryParams?: Record<string, unknown>,\n draftAndPublishOnly?: boolean\n ): Promise<RecentDocument[]> {\n const permittedContentTypes = await getPermittedContentTypes();\n const allowedContentTypeUids = draftAndPublishOnly\n ? permittedContentTypes.filter((uid) => {\n return contentTypes.hasDraftAndPublish(strapi.contentType(uid));\n })\n : permittedContentTypes;\n // Fetch the configuration for each content type in a single query\n const configurations = await getConfiguration(allowedContentTypeUids);\n // Get the necessary metadata for the documents\n const contentTypesMeta = getContentTypesMeta(allowedContentTypeUids, configurations);\n\n const recentDocuments = await Promise.all(\n contentTypesMeta.map(async (meta) => {\n const { permissionQuery, titleField } = await sanitizeHomepageQuery(\n meta,\n additionalQueryParams\n );\n\n const docs = await strapi.documents(meta.uid).findMany(permissionQuery);\n const populate = additionalQueryParams?.populate as string[];\n\n return formatDocuments(docs, { ...meta, mainField: titleField }, populate);\n })\n );\n\n return recentDocuments\n .flat()\n .sort((a, b) => {\n switch (additionalQueryParams?.sort) {\n case 'publishedAt:desc':\n if (!a.publishedAt || !b.publishedAt) return 0;\n return b.publishedAt.valueOf() - a.publishedAt.valueOf();\n case 'publishedAt:asc':\n if (!a.publishedAt || !b.publishedAt) return 0;\n return a.publishedAt.valueOf() - b.publishedAt.valueOf();\n case 'updatedAt:desc':\n if (!a.updatedAt || !b.updatedAt) return 0;\n return b.updatedAt.valueOf() - a.updatedAt.valueOf();\n case 'updatedAt:asc':\n if (!a.updatedAt || !b.updatedAt) return 0;\n return a.updatedAt.valueOf() - b.updatedAt.valueOf();\n default:\n return 0;\n }\n })\n .slice(0, MAX_DOCUMENTS);\n },\n\n async getRecentlyPublishedDocuments(): Promise<GetRecentDocuments.Response['data']> {\n const recentlyPublishedDocuments = await this.queryLastDocuments(\n {\n sort: 'publishedAt:desc',\n status: 'published',\n },\n true\n );\n\n return this.addStatusToDocuments(recentlyPublishedDocuments);\n },\n\n async getRecentlyUpdatedDocuments(): Promise<GetRecentDocuments.Response['data']> {\n const recentlyUpdatedDocuments = await this.queryLastDocuments({\n sort: 'updatedAt:desc',\n });\n\n return this.addStatusToDocuments(recentlyUpdatedDocuments);\n },\n\n async getCountDocuments(): Promise<GetCountDocuments.Response['data']> {\n const permittedContentTypes = await getPermittedContentTypes();\n // Fetch the configuration for each content type in a single query\n const configurations = await getConfiguration(permittedContentTypes);\n // Get the necessary metadata for the documents\n const contentTypesMeta = getContentTypesMeta(permittedContentTypes, configurations);\n\n const countDocuments = {\n draft: 0,\n published: 0,\n modified: 0,\n };\n\n await Promise.all(\n contentTypesMeta.map(async (meta) => {\n const strapiDBConnection = strapi.db.connection;\n const tableName = strapi.contentType(meta.uid).collectionName;\n if (!tableName) return;\n\n if (!meta.hasDraftAndPublish) {\n const publishedDocuments = await strapiDBConnection(tableName)\n .countDistinct('document_id as count')\n .first();\n countDocuments.published += Number(publishedDocuments?.count) || 0;\n return;\n }\n\n // Classify each document_id into a single bucket (draft / published / modified)\n // in one pass. Replaces three separate self-join queries that scaled poorly on\n // large tables — see https://github.com/strapi/strapi/issues/25200.\n const classified = strapiDBConnection(tableName)\n .select('document_id')\n .select(\n strapiDBConnection.raw(\n `CASE\n WHEN MAX(CASE WHEN published_at IS NOT NULL THEN 1 ELSE 0 END) = 0\n THEN 'draft'\n WHEN MAX(CASE WHEN published_at IS NULL THEN updated_at END) =\n MAX(CASE WHEN published_at IS NOT NULL THEN updated_at END)\n THEN 'published'\n ELSE 'modified'\n END AS bucket`\n )\n )\n .groupBy('document_id');\n\n const counts = await strapiDBConnection\n .from(classified.as('classified'))\n .select(\n strapiDBConnection.raw(\n `COUNT(CASE WHEN bucket = 'draft' THEN 1 END) AS draft,\n COUNT(CASE WHEN bucket = 'published' THEN 1 END) AS published,\n COUNT(CASE WHEN bucket = 'modified' THEN 1 END) AS modified`\n )\n )\n .first();\n\n countDocuments.draft += Number(counts?.draft) || 0;\n countDocuments.published += Number(counts?.published) || 0;\n countDocuments.modified += Number(counts?.modified) || 0;\n })\n );\n\n return countDocuments;\n },\n };\n};\n\nexport { createHomepageService };\n"],"names":["createHomepageService","strapi","MAX_DOCUMENTS","metadataService","plugin","service","permissionService","admin","services","permission","getConfiguration","contentTypeUids","coreStore","db","query","rawConfigurations","findMany","where","key","$in","map","contentType","rawConfiguration","JSON","parse","value","getPermittedContentTypes","readPermissions","role","users","id","requestContext","get","state","user","action","subject","filter","Boolean","permissionCheckerService","getPermissionChecker","uid","create","userAbility","model","getContentTypesMeta","allowedContentTypeUids","configurations","configuration","find","config","mainField","resolveReadableMainField","fields","buildHomepageQueryFields","hasDraftAndPublish","contentTypes","formatDocuments","documents","meta","populate","document","additionalFields","reduce","acc","documentId","locale","updatedAt","Date","title","publishedAt","contentTypeUid","contentTypeDisplayName","info","displayName","kind","sanitizeHomepageQuery","additionalQueryParams","permissionQuery","sanitizedQuery","read","limit","sanitizedFields","compactSanitizedFields","undefined","titleField","resolveTitleField","addStatusToDocuments","Promise","all","recentDocument","availableStatus","getMetadata","availableLocales","status","getStatus","queryLastDocuments","draftAndPublishOnly","permittedContentTypes","contentTypesMeta","recentDocuments","docs","flat","sort","a","b","valueOf","slice","getRecentlyPublishedDocuments","recentlyPublishedDocuments","getRecentlyUpdatedDocuments","recentlyUpdatedDocuments","getCountDocuments","countDocuments","draft","published","modified","strapiDBConnection","connection","tableName","collectionName","publishedDocuments","countDistinct","first","Number","count","classified","select","raw","groupBy","counts","from","as"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { contentTypes } from '@strapi/utils';
2
+ import { resolveReadableMainField, buildHomepageQueryFields, compactSanitizedFields, resolveTitleField } from './homepage-query-utils.mjs';
2
3
 
3
4
  const createHomepageService = ({ strapi })=>{
4
5
  const MAX_DOCUMENTS = 4;
@@ -33,31 +34,21 @@ const createHomepageService = ({ strapi })=>{
33
34
  });
34
35
  return readPermissions.map((permission)=>permission.subject).filter(Boolean);
35
36
  };
37
+ const permissionCheckerService = strapi.plugin('content-manager').service('permission-checker');
38
+ const getPermissionChecker = (uid)=>permissionCheckerService.create({
39
+ userAbility: strapi.requestContext.get()?.state.userAbility,
40
+ model: uid
41
+ });
36
42
  const getContentTypesMeta = (allowedContentTypeUids, configurations)=>{
37
43
  return allowedContentTypeUids.map((uid)=>{
38
44
  const configuration = configurations.find((config)=>config.uid === uid);
39
45
  const contentType = strapi.contentType(uid);
40
- const fields = [
41
- 'documentId',
42
- 'updatedAt'
43
- ];
44
- // Add fields required to get the status if D&P is enabled
46
+ const mainField = resolveReadableMainField(contentType, configuration, getPermissionChecker(uid));
47
+ const fields = buildHomepageQueryFields(contentType, mainField);
45
48
  const hasDraftAndPublish = contentTypes.hasDraftAndPublish(contentType);
46
- if (hasDraftAndPublish) {
47
- fields.push('publishedAt');
48
- }
49
- // Only add the main field if it's defined
50
- if (configuration?.settings.mainField) {
51
- fields.push(configuration.settings.mainField);
52
- }
53
- // Only add locale if it's localized
54
- const isLocalized = contentType.pluginOptions?.i18n?.localized;
55
- if (isLocalized) {
56
- fields.push('locale');
57
- }
58
49
  return {
59
50
  fields,
60
- mainField: configuration.settings.mainField,
51
+ mainField,
61
52
  contentType,
62
53
  hasDraftAndPublish,
63
54
  uid
@@ -83,11 +74,22 @@ const createHomepageService = ({ strapi })=>{
83
74
  };
84
75
  });
85
76
  };
86
- const permissionCheckerService = strapi.plugin('content-manager').service('permission-checker');
87
- const getPermissionChecker = (uid)=>permissionCheckerService.create({
88
- userAbility: strapi.requestContext.get()?.state.userAbility,
89
- model: uid
77
+ const sanitizeHomepageQuery = async (meta, additionalQueryParams)=>{
78
+ const permissionQuery = await getPermissionChecker(meta.uid).sanitizedQuery.read({
79
+ limit: MAX_DOCUMENTS,
80
+ fields: meta.fields,
81
+ ...additionalQueryParams,
82
+ locale: '*'
90
83
  });
84
+ const sanitizedFields = compactSanitizedFields(permissionQuery.fields);
85
+ if (sanitizedFields !== undefined) {
86
+ permissionQuery.fields = sanitizedFields;
87
+ }
88
+ return {
89
+ permissionQuery,
90
+ titleField: resolveTitleField(meta.mainField, sanitizedFields)
91
+ };
92
+ };
91
93
  return {
92
94
  async addStatusToDocuments (documents) {
93
95
  return Promise.all(documents.map(async (recentDocument)=>{
@@ -116,15 +118,13 @@ const createHomepageService = ({ strapi })=>{
116
118
  // Get the necessary metadata for the documents
117
119
  const contentTypesMeta = getContentTypesMeta(allowedContentTypeUids, configurations);
118
120
  const recentDocuments = await Promise.all(contentTypesMeta.map(async (meta)=>{
119
- const permissionQuery = await getPermissionChecker(meta.uid).sanitizedQuery.read({
120
- limit: MAX_DOCUMENTS,
121
- fields: meta.fields,
122
- ...additionalQueryParams,
123
- locale: '*'
124
- });
121
+ const { permissionQuery, titleField } = await sanitizeHomepageQuery(meta, additionalQueryParams);
125
122
  const docs = await strapi.documents(meta.uid).findMany(permissionQuery);
126
123
  const populate = additionalQueryParams?.populate;
127
- return formatDocuments(docs, meta, populate);
124
+ return formatDocuments(docs, {
125
+ ...meta,
126
+ mainField: titleField
127
+ }, populate);
128
128
  }));
129
129
  return recentDocuments.flat().sort((a, b)=>{
130
130
  switch(additionalQueryParams?.sort){
@@ -172,24 +172,29 @@ const createHomepageService = ({ strapi })=>{
172
172
  await Promise.all(contentTypesMeta.map(async (meta)=>{
173
173
  const strapiDBConnection = strapi.db.connection;
174
174
  const tableName = strapi.contentType(meta.uid).collectionName;
175
- if (tableName) {
176
- if (meta.hasDraftAndPublish) {
177
- const draftDocuments = await strapiDBConnection(tableName).whereNull('published_at').whereIn('document_id', function() {
178
- this.select('document_id').from(tableName).groupBy('document_id').havingRaw('COUNT(*) = 1');
179
- }).count('* as count').first();
180
- countDocuments.draft += Number(draftDocuments?.count) || 0;
181
- }
182
- const publishedDocuments = meta.hasDraftAndPublish ? await strapiDBConnection(tableName).countDistinct('draft.document_id as count').from(`${tableName} as draft`).join(`${tableName} as published`, function() {
183
- this.on('draft.document_id', '=', 'published.document_id').andOn('draft.updated_at', '=', 'published.updated_at').andOnNull('draft.published_at').andOnNotNull('published.published_at');
184
- }).first() : await strapiDBConnection(tableName).countDistinct('document_id as count').from(`${tableName}`).first();
175
+ if (!tableName) return;
176
+ if (!meta.hasDraftAndPublish) {
177
+ const publishedDocuments = await strapiDBConnection(tableName).countDistinct('document_id as count').first();
185
178
  countDocuments.published += Number(publishedDocuments?.count) || 0;
186
- if (meta.hasDraftAndPublish) {
187
- const modifiedDocuments = await strapiDBConnection(tableName).select('draft.document_id').from(`${tableName} as draft`).join(`${tableName} as published`, function() {
188
- this.on('draft.document_id', '=', 'published.document_id').andOn('draft.updated_at', '!=', 'published.updated_at').andOnNull('draft.published_at').andOnNotNull('published.published_at');
189
- }).countDistinct('draft.document_id as count').groupBy('draft.document_id').first();
190
- countDocuments.modified += Number(modifiedDocuments?.count) || 0;
191
- }
179
+ return;
192
180
  }
181
+ // Classify each document_id into a single bucket (draft / published / modified)
182
+ // in one pass. Replaces three separate self-join queries that scaled poorly on
183
+ // large tables — see https://github.com/strapi/strapi/issues/25200.
184
+ const classified = strapiDBConnection(tableName).select('document_id').select(strapiDBConnection.raw(`CASE
185
+ WHEN MAX(CASE WHEN published_at IS NOT NULL THEN 1 ELSE 0 END) = 0
186
+ THEN 'draft'
187
+ WHEN MAX(CASE WHEN published_at IS NULL THEN updated_at END) =
188
+ MAX(CASE WHEN published_at IS NOT NULL THEN updated_at END)
189
+ THEN 'published'
190
+ ELSE 'modified'
191
+ END AS bucket`)).groupBy('document_id');
192
+ const counts = await strapiDBConnection.from(classified.as('classified')).select(strapiDBConnection.raw(`COUNT(CASE WHEN bucket = 'draft' THEN 1 END) AS draft,
193
+ COUNT(CASE WHEN bucket = 'published' THEN 1 END) AS published,
194
+ COUNT(CASE WHEN bucket = 'modified' THEN 1 END) AS modified`)).first();
195
+ countDocuments.draft += Number(counts?.draft) || 0;
196
+ countDocuments.published += Number(counts?.published) || 0;
197
+ countDocuments.modified += Number(counts?.modified) || 0;
193
198
  }));
194
199
  return countDocuments;
195
200
  }