@directus/api 21.0.0 → 22.1.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.
- package/dist/app.js +4 -4
- package/dist/auth/drivers/ldap.js +4 -4
- package/dist/auth/drivers/local.js +4 -4
- package/dist/auth/drivers/oauth2.js +4 -4
- package/dist/auth/drivers/openid.js +2 -4
- package/dist/cache.js +3 -0
- package/dist/cli/commands/bootstrap/index.js +8 -2
- package/dist/cli/commands/init/index.js +9 -10
- package/dist/cli/commands/init/questions.d.ts +7 -6
- package/dist/cli/commands/init/questions.js +2 -2
- package/dist/cli/utils/create-env/index.d.ts +2 -2
- package/dist/cli/utils/create-env/index.js +3 -1
- package/dist/cli/utils/defaults.d.ts +4 -11
- package/dist/cli/utils/defaults.js +7 -1
- package/dist/cli/utils/drivers.js +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/controllers/access.d.ts +2 -0
- package/dist/controllers/access.js +148 -0
- package/dist/controllers/auth.js +5 -16
- package/dist/controllers/permissions.js +14 -2
- package/dist/controllers/policies.d.ts +2 -0
- package/dist/controllers/policies.js +169 -0
- package/dist/controllers/roles.js +22 -1
- package/dist/controllers/tus.js +14 -26
- package/dist/controllers/users.js +0 -55
- package/dist/database/get-ast-from-query/get-ast-from-query.d.ts +16 -0
- package/dist/database/get-ast-from-query/get-ast-from-query.js +82 -0
- package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +13 -0
- package/dist/database/get-ast-from-query/lib/convert-wildcards.js +69 -0
- package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +15 -0
- package/dist/database/get-ast-from-query/lib/parse-fields.js +200 -0
- package/dist/database/get-ast-from-query/utils/get-deep-query.d.ts +14 -0
- package/dist/database/get-ast-from-query/utils/get-deep-query.js +17 -0
- package/dist/database/get-ast-from-query/utils/get-related-collection.d.ts +2 -0
- package/dist/database/get-ast-from-query/utils/get-related-collection.js +13 -0
- package/dist/database/get-ast-from-query/utils/get-relation.d.ts +2 -0
- package/dist/database/get-ast-from-query/utils/get-relation.js +7 -0
- package/dist/database/helpers/fn/types.d.ts +2 -1
- package/dist/database/helpers/fn/types.js +1 -1
- package/dist/database/helpers/geometry/dialects/mssql.d.ts +1 -1
- package/dist/database/helpers/geometry/dialects/mssql.js +4 -2
- package/dist/database/helpers/geometry/dialects/mysql.js +1 -1
- package/dist/database/helpers/geometry/dialects/oracle.d.ts +1 -1
- package/dist/database/helpers/geometry/dialects/oracle.js +5 -3
- package/dist/database/helpers/geometry/types.d.ts +1 -1
- package/dist/database/helpers/geometry/types.js +4 -2
- package/dist/database/helpers/index.d.ts +3 -3
- package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/cockroachdb.js +4 -0
- package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/mssql.js +4 -0
- package/dist/database/helpers/schema/dialects/oracle.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/oracle.js +4 -0
- package/dist/database/helpers/schema/dialects/postgres.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/postgres.js +4 -0
- package/dist/database/helpers/schema/types.d.ts +5 -0
- package/dist/database/helpers/schema/types.js +3 -0
- package/dist/database/helpers/schema/utils/preprocess-bindings.d.ts +8 -0
- package/dist/database/helpers/schema/utils/preprocess-bindings.js +30 -0
- package/dist/database/index.js +6 -1
- package/dist/database/migrations/20210519A-add-system-fk-triggers.js +3 -2
- package/dist/database/migrations/20230721A-require-shares-fields.js +3 -5
- package/dist/database/migrations/20240716A-update-files-date-fields.js +3 -7
- package/dist/{utils/merge-permissions.d.ts → database/migrations/20240806A-permissions-policies.d.ts} +4 -1
- package/dist/database/migrations/20240806A-permissions-policies.js +352 -0
- package/dist/database/run-ast/lib/get-db-query.d.ts +4 -0
- package/dist/database/run-ast/lib/get-db-query.js +218 -0
- package/dist/database/run-ast/lib/parse-current-level.d.ts +7 -0
- package/dist/database/run-ast/lib/parse-current-level.js +41 -0
- package/dist/database/run-ast/run-ast.d.ts +7 -0
- package/dist/database/run-ast/run-ast.js +107 -0
- package/dist/database/{run-ast.d.ts → run-ast/types.d.ts} +3 -9
- package/dist/database/run-ast/types.js +1 -0
- package/dist/database/run-ast/utils/apply-case-when.d.ts +16 -0
- package/dist/database/run-ast/utils/apply-case-when.js +27 -0
- package/dist/database/run-ast/utils/apply-parent-filters.d.ts +3 -0
- package/dist/database/run-ast/utils/apply-parent-filters.js +55 -0
- package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +10 -0
- package/dist/database/run-ast/utils/get-column-pre-processor.js +57 -0
- package/dist/database/run-ast/utils/get-field-alias.d.ts +2 -0
- package/dist/database/run-ast/utils/get-field-alias.js +4 -0
- package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.d.ts +5 -0
- package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.js +23 -0
- package/dist/database/run-ast/utils/merge-with-parent-items.d.ts +3 -0
- package/dist/database/run-ast/utils/merge-with-parent-items.js +87 -0
- package/dist/database/run-ast/utils/remove-temporary-fields.d.ts +3 -0
- package/dist/database/run-ast/utils/remove-temporary-fields.js +73 -0
- package/dist/database/run-ast/utils/with-preprocess-bindings.d.ts +2 -0
- package/dist/database/run-ast/utils/with-preprocess-bindings.js +14 -0
- package/dist/flows.js +3 -4
- package/dist/middleware/authenticate.js +2 -7
- package/dist/middleware/cache.js +1 -1
- package/dist/middleware/respond.js +1 -1
- package/dist/permissions/cache.d.ts +2 -0
- package/dist/permissions/cache.js +23 -0
- package/dist/permissions/lib/fetch-permissions.d.ts +11 -0
- package/dist/permissions/lib/fetch-permissions.js +56 -0
- package/dist/permissions/lib/fetch-policies.d.ts +14 -0
- package/dist/permissions/lib/fetch-policies.js +43 -0
- package/dist/permissions/lib/fetch-roles-tree.d.ts +3 -0
- package/dist/permissions/lib/fetch-roles-tree.js +28 -0
- package/dist/{services/permissions → permissions}/lib/with-app-minimal-permissions.d.ts +1 -1
- package/dist/permissions/lib/with-app-minimal-permissions.js +10 -0
- package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.d.ts +7 -0
- package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js +56 -0
- package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.d.ts +3 -0
- package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js +16 -0
- package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.d.ts +8 -0
- package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +24 -0
- package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.d.ts +9 -0
- package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +31 -0
- package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.d.ts +16 -0
- package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js +27 -0
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +10 -0
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +23 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +5 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +7 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +5 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +10 -0
- package/dist/permissions/modules/fetch-global-access/types.d.ts +4 -0
- package/dist/permissions/modules/fetch-global-access/types.js +1 -0
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +4 -0
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +27 -0
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts +12 -0
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +32 -0
- package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +4 -0
- package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js +29 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.d.ts +4 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.js +49 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.d.ts +3 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.js +56 -0
- package/dist/permissions/modules/process-ast/lib/field-map-from-ast.d.ts +4 -0
- package/dist/permissions/modules/process-ast/lib/field-map-from-ast.js +8 -0
- package/dist/permissions/modules/process-ast/lib/inject-cases.d.ts +9 -0
- package/dist/permissions/modules/process-ast/lib/inject-cases.js +93 -0
- package/dist/permissions/modules/process-ast/process-ast.d.ts +9 -0
- package/dist/permissions/modules/process-ast/process-ast.js +39 -0
- package/dist/permissions/modules/process-ast/types.d.ts +18 -0
- package/dist/permissions/modules/process-ast/types.js +1 -0
- package/dist/permissions/modules/process-ast/utils/collections-in-field-map.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/collections-in-field-map.js +7 -0
- package/dist/permissions/modules/process-ast/utils/dedupe-access.d.ts +12 -0
- package/dist/permissions/modules/process-ast/utils/dedupe-access.js +30 -0
- package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.d.ts +15 -0
- package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +60 -0
- package/dist/permissions/modules/process-ast/utils/find-related-collection.d.ts +3 -0
- package/dist/permissions/modules/process-ast/utils/find-related-collection.js +9 -0
- package/dist/permissions/modules/process-ast/utils/flatten-filter.d.ts +3 -0
- package/dist/permissions/modules/process-ast/utils/flatten-filter.js +34 -0
- package/dist/permissions/modules/process-ast/utils/format-a2o-key.d.ts +1 -0
- package/dist/permissions/modules/process-ast/utils/format-a2o-key.js +3 -0
- package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +5 -0
- package/dist/permissions/modules/process-ast/utils/get-info-for-path.js +7 -0
- package/dist/permissions/modules/process-ast/utils/has-item-permissions.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/has-item-permissions.js +3 -0
- package/dist/permissions/modules/process-ast/utils/stringify-query-path.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/stringify-query-path.js +3 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/create-error.d.ts +3 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/create-error.js +16 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.js +12 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.js +28 -0
- package/dist/permissions/modules/process-payload/lib/is-field-nullable.d.ts +5 -0
- package/dist/permissions/modules/process-payload/lib/is-field-nullable.js +12 -0
- package/dist/permissions/modules/process-payload/process-payload.d.ts +13 -0
- package/dist/permissions/modules/process-payload/process-payload.js +77 -0
- package/dist/permissions/modules/validate-access/lib/validate-collection-access.d.ts +12 -0
- package/dist/permissions/modules/validate-access/lib/validate-collection-access.js +11 -0
- package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +9 -0
- package/dist/permissions/modules/validate-access/lib/validate-item-access.js +33 -0
- package/dist/permissions/modules/validate-access/validate-access.d.ts +14 -0
- package/dist/permissions/modules/validate-access/validate-access.js +28 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.d.ts +1 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.js +8 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +5 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js +10 -0
- package/dist/permissions/types.d.ts +6 -0
- package/dist/permissions/types.js +1 -0
- package/dist/permissions/utils/create-default-accountability.d.ts +2 -0
- package/dist/permissions/utils/create-default-accountability.js +11 -0
- package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +8 -0
- package/dist/permissions/utils/extract-required-dynamic-variable-context.js +27 -0
- package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +9 -0
- package/dist/permissions/utils/fetch-dynamic-variable-context.js +43 -0
- package/dist/permissions/utils/filter-policies-by-ip.d.ts +2 -0
- package/dist/permissions/utils/filter-policies-by-ip.js +15 -0
- package/dist/permissions/utils/get-unaliased-field-key.d.ts +5 -0
- package/dist/permissions/utils/get-unaliased-field-key.js +17 -0
- package/dist/permissions/utils/process-permissions.d.ts +7 -0
- package/dist/permissions/utils/process-permissions.js +9 -0
- package/dist/permissions/utils/with-cache.d.ts +10 -0
- package/dist/permissions/utils/with-cache.js +25 -0
- package/dist/server.js +17 -4
- package/dist/services/access.d.ts +10 -0
- package/dist/services/access.js +43 -0
- package/dist/services/activity.js +22 -10
- package/dist/services/assets.d.ts +2 -3
- package/dist/services/assets.js +10 -5
- package/dist/services/authentication.js +18 -18
- package/dist/services/collections.js +18 -17
- package/dist/services/fields.d.ts +0 -1
- package/dist/services/fields.js +54 -25
- package/dist/services/files.js +10 -3
- package/dist/services/graphql/index.d.ts +3 -3
- package/dist/services/graphql/index.js +126 -22
- package/dist/services/graphql/subscription.js +2 -4
- package/dist/services/import-export.d.ts +3 -1
- package/dist/services/import-export.js +67 -9
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.js +3 -2
- package/dist/services/items.js +115 -44
- package/dist/services/meta.js +60 -23
- package/dist/services/notifications.js +14 -6
- package/dist/services/payload.d.ts +9 -10
- package/dist/services/payload.js +18 -3
- package/dist/services/{permissions/index.d.ts → permissions.d.ts} +5 -7
- package/dist/services/{permissions/index.js → permissions.js} +30 -54
- package/dist/services/policies.d.ts +12 -0
- package/dist/services/policies.js +87 -0
- package/dist/services/relations.d.ts +0 -6
- package/dist/services/relations.js +27 -30
- package/dist/services/roles.d.ts +4 -12
- package/dist/services/roles.js +57 -424
- package/dist/services/shares.d.ts +0 -2
- package/dist/services/shares.js +12 -8
- package/dist/services/specifications.d.ts +2 -2
- package/dist/services/specifications.js +39 -27
- package/dist/services/users.d.ts +1 -5
- package/dist/services/users.js +78 -161
- package/dist/services/utils.js +11 -7
- package/dist/services/versions.d.ts +0 -2
- package/dist/services/versions.js +34 -10
- package/dist/telemetry/lib/get-report.js +2 -2
- package/dist/telemetry/utils/check-user-limits.d.ts +5 -0
- package/dist/telemetry/utils/check-user-limits.js +19 -0
- package/dist/types/ast.d.ts +43 -1
- package/dist/types/database.d.ts +1 -1
- package/dist/types/items.d.ts +11 -0
- package/dist/utils/apply-query.d.ts +11 -7
- package/dist/utils/apply-query.js +69 -11
- package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +19 -0
- package/dist/utils/fetch-user-count/fetch-access-lookup.js +23 -0
- package/dist/utils/fetch-user-count/fetch-access-roles.d.ts +16 -0
- package/dist/utils/fetch-user-count/fetch-access-roles.js +37 -0
- package/dist/utils/fetch-user-count/fetch-active-users.d.ts +6 -0
- package/dist/utils/fetch-user-count/fetch-active-users.js +3 -0
- package/dist/utils/fetch-user-count/fetch-user-count.d.ts +12 -0
- package/dist/utils/fetch-user-count/fetch-user-count.js +64 -0
- package/dist/utils/fetch-user-count/get-user-count-query.d.ts +20 -0
- package/dist/utils/fetch-user-count/get-user-count-query.js +17 -0
- package/dist/utils/get-accountability-for-role.js +16 -25
- package/dist/utils/get-accountability-for-token.js +17 -16
- package/dist/utils/get-address.d.ts +5 -0
- package/dist/utils/get-address.js +13 -0
- package/dist/utils/get-cache-key.d.ts +1 -1
- package/dist/utils/get-cache-key.js +12 -1
- package/dist/utils/get-column.d.ts +2 -1
- package/dist/utils/get-column.js +1 -0
- package/dist/utils/get-service.js +5 -1
- package/dist/utils/reduce-schema.d.ts +4 -6
- package/dist/utils/reduce-schema.js +16 -32
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/utils/transaction.js +28 -11
- package/dist/utils/validate-user-count-integrity.d.ts +13 -0
- package/dist/utils/validate-user-count-integrity.js +29 -0
- package/dist/websocket/authenticate.d.ts +0 -2
- package/dist/websocket/authenticate.js +0 -12
- package/dist/websocket/controllers/graphql.js +3 -7
- package/dist/websocket/controllers/hooks.js +4 -0
- package/dist/websocket/controllers/rest.js +2 -5
- package/dist/websocket/handlers/subscribe.js +0 -2
- package/dist/websocket/utils/items.d.ts +1 -1
- package/package.json +31 -30
- package/dist/database/run-ast.js +0 -458
- package/dist/middleware/check-ip.d.ts +0 -2
- package/dist/middleware/check-ip.js +0 -37
- package/dist/middleware/get-permissions.d.ts +0 -3
- package/dist/middleware/get-permissions.js +0 -10
- package/dist/services/authorization.d.ts +0 -17
- package/dist/services/authorization.js +0 -456
- package/dist/services/permissions/lib/with-app-minimal-permissions.js +0 -13
- package/dist/telemetry/utils/check-increased-user-limits.d.ts +0 -7
- package/dist/telemetry/utils/check-increased-user-limits.js +0 -25
- package/dist/telemetry/utils/get-role-counts-by-roles.d.ts +0 -6
- package/dist/telemetry/utils/get-role-counts-by-roles.js +0 -27
- package/dist/telemetry/utils/get-role-counts-by-users.d.ts +0 -11
- package/dist/telemetry/utils/get-role-counts-by-users.js +0 -34
- package/dist/telemetry/utils/get-user-count.d.ts +0 -8
- package/dist/telemetry/utils/get-user-count.js +0 -33
- package/dist/telemetry/utils/get-user-counts-by-roles.d.ts +0 -7
- package/dist/telemetry/utils/get-user-counts-by-roles.js +0 -35
- package/dist/utils/get-ast-from-query.d.ts +0 -13
- package/dist/utils/get-ast-from-query.js +0 -297
- package/dist/utils/get-permissions.d.ts +0 -2
- package/dist/utils/get-permissions.js +0 -150
- package/dist/utils/merge-permissions-for-share.d.ts +0 -4
- package/dist/utils/merge-permissions-for-share.js +0 -109
- package/dist/utils/merge-permissions.js +0 -95
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { parseFilterKey } from '../../../utils/parse-filter-key.js';
|
|
2
|
+
export async function parseCurrentLevel(schema, collection, children, query) {
|
|
3
|
+
const primaryKeyField = schema.collections[collection].primary;
|
|
4
|
+
const columnsInCollection = Object.keys(schema.collections[collection].fields);
|
|
5
|
+
const columnsToSelectInternal = [];
|
|
6
|
+
const nestedCollectionNodes = [];
|
|
7
|
+
for (const child of children) {
|
|
8
|
+
if (child.type === 'field' || child.type === 'functionField') {
|
|
9
|
+
const { fieldName } = parseFilterKey(child.name);
|
|
10
|
+
if (columnsInCollection.includes(fieldName)) {
|
|
11
|
+
columnsToSelectInternal.push(child.fieldKey);
|
|
12
|
+
}
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (!child.relation)
|
|
16
|
+
continue;
|
|
17
|
+
if (child.type === 'm2o') {
|
|
18
|
+
columnsToSelectInternal.push(child.relation.field);
|
|
19
|
+
}
|
|
20
|
+
if (child.type === 'a2o') {
|
|
21
|
+
columnsToSelectInternal.push(child.relation.field);
|
|
22
|
+
columnsToSelectInternal.push(child.relation.meta.one_collection_field);
|
|
23
|
+
}
|
|
24
|
+
nestedCollectionNodes.push(child);
|
|
25
|
+
}
|
|
26
|
+
const isAggregate = (query.group || (query.aggregate && Object.keys(query.aggregate).length > 0)) ?? false;
|
|
27
|
+
/** Always fetch primary key in case there's a nested relation that needs it. Aggregate payloads
|
|
28
|
+
* can't have nested relational fields
|
|
29
|
+
*/
|
|
30
|
+
if (isAggregate === false && columnsToSelectInternal.includes(primaryKeyField) === false) {
|
|
31
|
+
columnsToSelectInternal.push(primaryKeyField);
|
|
32
|
+
}
|
|
33
|
+
/** Make sure select list has unique values */
|
|
34
|
+
const columnsToSelect = [...new Set(columnsToSelectInternal)];
|
|
35
|
+
const fieldNodes = columnsToSelect.map((column) => children.find((childNode) => (childNode.type === 'field' || childNode.type === 'functionField') && childNode.fieldKey === column) ?? {
|
|
36
|
+
type: 'field',
|
|
37
|
+
name: column,
|
|
38
|
+
fieldKey: column,
|
|
39
|
+
});
|
|
40
|
+
return { fieldNodes, nestedCollectionNodes, primaryKeyField };
|
|
41
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Item, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { AST, NestedCollectionNode } from '../../types/ast.js';
|
|
3
|
+
import type { RunASTOptions } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Execute a given AST using Knex. Returns array of items based on requested AST.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runAst(originalAST: AST | NestedCollectionNode, schema: SchemaOverview, options?: RunASTOptions): Promise<null | Item | Item[]>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { cloneDeep, merge } from 'lodash-es';
|
|
3
|
+
import { PayloadService } from '../../services/payload.js';
|
|
4
|
+
import getDatabase from '../index.js';
|
|
5
|
+
import { getDBQuery } from './lib/get-db-query.js';
|
|
6
|
+
import { parseCurrentLevel } from './lib/parse-current-level.js';
|
|
7
|
+
import { applyParentFilters } from './utils/apply-parent-filters.js';
|
|
8
|
+
import { mergeWithParentItems } from './utils/merge-with-parent-items.js';
|
|
9
|
+
import { removeTemporaryFields } from './utils/remove-temporary-fields.js';
|
|
10
|
+
/**
|
|
11
|
+
* Execute a given AST using Knex. Returns array of items based on requested AST.
|
|
12
|
+
*/
|
|
13
|
+
export async function runAst(originalAST, schema, options) {
|
|
14
|
+
const ast = cloneDeep(originalAST);
|
|
15
|
+
const knex = options?.knex || getDatabase();
|
|
16
|
+
if (ast.type === 'a2o') {
|
|
17
|
+
const results = {};
|
|
18
|
+
for (const collection of ast.names) {
|
|
19
|
+
results[collection] = await run(collection, ast.children[collection], ast.query[collection], ast.cases[collection] ?? []);
|
|
20
|
+
}
|
|
21
|
+
return results;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
return await run(ast.name, ast.children, options?.query || ast.query, ast.cases);
|
|
25
|
+
}
|
|
26
|
+
async function run(collection, children, query, cases) {
|
|
27
|
+
const env = useEnv();
|
|
28
|
+
// Retrieve the database columns to select in the current AST
|
|
29
|
+
const { fieldNodes, primaryKeyField, nestedCollectionNodes } = await parseCurrentLevel(schema, collection, children, query);
|
|
30
|
+
const o2mNodes = nestedCollectionNodes.filter((node) => node.type === 'o2m');
|
|
31
|
+
// The actual knex query builder instance. This is a promise that resolves with the raw items from the db
|
|
32
|
+
const dbQuery = getDBQuery(schema, knex, collection, fieldNodes, o2mNodes, query, cases);
|
|
33
|
+
const rawItems = await dbQuery;
|
|
34
|
+
if (!rawItems)
|
|
35
|
+
return null;
|
|
36
|
+
// Run the items through the special transforms
|
|
37
|
+
const payloadService = new PayloadService(collection, { knex, schema });
|
|
38
|
+
let items = await payloadService.processValues('read', rawItems, query.alias ?? {});
|
|
39
|
+
if (!items || (Array.isArray(items) && items.length === 0))
|
|
40
|
+
return items;
|
|
41
|
+
// Apply the `_in` filters to the nested collection batches
|
|
42
|
+
const nestedNodes = applyParentFilters(schema, nestedCollectionNodes, items);
|
|
43
|
+
for (const nestedNode of nestedNodes) {
|
|
44
|
+
let nestedItems = [];
|
|
45
|
+
if (nestedNode.type === 'o2m') {
|
|
46
|
+
let hasMore = true;
|
|
47
|
+
let batchCount = 0;
|
|
48
|
+
// If a nested node has a whenCase it indicates that the user might not be able to access the field for all items.
|
|
49
|
+
// In that case the queried item includes a flag under the fieldKey that is populated in the db and indicates
|
|
50
|
+
// if the user has access to that field for that specific item.
|
|
51
|
+
const hasWhenCase = nestedNode.whenCase && nestedNode.whenCase.length > 0;
|
|
52
|
+
let fieldAllowed = true;
|
|
53
|
+
if (hasWhenCase) {
|
|
54
|
+
// Extract flag and remove field from item, so it can be populated with the actual items
|
|
55
|
+
if (Array.isArray(items)) {
|
|
56
|
+
fieldAllowed = [];
|
|
57
|
+
for (const item of items) {
|
|
58
|
+
fieldAllowed.push(!!item[nestedNode.fieldKey]);
|
|
59
|
+
delete item[nestedNode.fieldKey];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
fieldAllowed = !!items[nestedNode.fieldKey];
|
|
64
|
+
delete items[nestedNode.fieldKey];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
while (hasMore) {
|
|
68
|
+
const node = merge({}, nestedNode, {
|
|
69
|
+
query: {
|
|
70
|
+
limit: env['RELATIONAL_BATCH_SIZE'],
|
|
71
|
+
offset: batchCount * env['RELATIONAL_BATCH_SIZE'],
|
|
72
|
+
page: null,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
nestedItems = (await runAst(node, schema, { knex, nested: true }));
|
|
76
|
+
if (nestedItems) {
|
|
77
|
+
items = mergeWithParentItems(schema, nestedItems, items, nestedNode, fieldAllowed);
|
|
78
|
+
}
|
|
79
|
+
if (!nestedItems || nestedItems.length < env['RELATIONAL_BATCH_SIZE']) {
|
|
80
|
+
hasMore = false;
|
|
81
|
+
}
|
|
82
|
+
batchCount++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const node = merge({}, nestedNode, {
|
|
87
|
+
query: { limit: -1 },
|
|
88
|
+
});
|
|
89
|
+
nestedItems = (await runAst(node, schema, { knex, nested: true }));
|
|
90
|
+
if (nestedItems) {
|
|
91
|
+
// Merge all fetched nested records with the parent items
|
|
92
|
+
items = mergeWithParentItems(schema, nestedItems, items, nestedNode, true);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// During the fetching of data, we have to inject a couple of required fields for the child nesting
|
|
97
|
+
// to work (primary / foreign keys) even if they're not explicitly requested. After all fetching
|
|
98
|
+
// and nesting is done, we parse through the output structure, and filter out all non-requested
|
|
99
|
+
// fields
|
|
100
|
+
// The field allowed flags injected in `getDBQuery` are already removed while processing the nested nodes in
|
|
101
|
+
// the previous step.
|
|
102
|
+
if (options?.nested !== true && options?.stripNonRequested !== false) {
|
|
103
|
+
items = removeTemporaryFields(schema, items, originalAST, primaryKeyField);
|
|
104
|
+
}
|
|
105
|
+
return items;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { Item, SchemaOverview } from '@directus/types';
|
|
2
1
|
import type { Knex } from 'knex';
|
|
3
|
-
import type { AST
|
|
4
|
-
|
|
2
|
+
import type { AST } from '../../types/ast.js';
|
|
3
|
+
export interface RunASTOptions {
|
|
5
4
|
/**
|
|
6
5
|
* Query override for the current level
|
|
7
6
|
*/
|
|
@@ -18,9 +17,4 @@ type RunASTOptions = {
|
|
|
18
17
|
* Whether or not to strip out non-requested required fields automatically (eg IDs / FKs)
|
|
19
18
|
*/
|
|
20
19
|
stripNonRequested?: boolean;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Execute a given AST using Knex. Returns array of items based on requested AST.
|
|
24
|
-
*/
|
|
25
|
-
export default function runAST(originalAST: AST | NestedCollectionNode, schema: SchemaOverview, options?: RunASTOptions): Promise<null | Item | Item[]>;
|
|
26
|
-
export {};
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Filter, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import type { AliasMap } from '../../../utils/get-column-path.js';
|
|
4
|
+
export interface ApplyCaseWhenOptions {
|
|
5
|
+
column: Knex.Raw;
|
|
6
|
+
columnCases: Filter[];
|
|
7
|
+
table: string;
|
|
8
|
+
cases: Filter[];
|
|
9
|
+
aliasMap: AliasMap;
|
|
10
|
+
alias?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ApplyCaseWhenContext {
|
|
13
|
+
knex: Knex;
|
|
14
|
+
schema: SchemaOverview;
|
|
15
|
+
}
|
|
16
|
+
export declare function applyCaseWhen({ columnCases, table, aliasMap, cases, column, alias }: ApplyCaseWhenOptions, { knex, schema }: ApplyCaseWhenContext): Knex.Raw;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { applyFilter } from '../../../utils/apply-query.js';
|
|
2
|
+
export function applyCaseWhen({ columnCases, table, aliasMap, cases, column, alias }, { knex, schema }) {
|
|
3
|
+
const caseQuery = knex.queryBuilder();
|
|
4
|
+
applyFilter(knex, schema, caseQuery, { _or: columnCases }, table, aliasMap, cases);
|
|
5
|
+
const compiler = knex.client.queryCompiler(caseQuery);
|
|
6
|
+
const sqlParts = [];
|
|
7
|
+
// Only empty filters, so no where was generated, skip it
|
|
8
|
+
if (!compiler.grouped.where)
|
|
9
|
+
return column;
|
|
10
|
+
for (const statement of compiler.grouped.where) {
|
|
11
|
+
const val = compiler[statement.type](statement);
|
|
12
|
+
if (val) {
|
|
13
|
+
if (sqlParts.length > 0) {
|
|
14
|
+
sqlParts.push(statement.bool);
|
|
15
|
+
}
|
|
16
|
+
sqlParts.push(val);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const sql = sqlParts.join(' ');
|
|
20
|
+
const bindings = [...caseQuery.toSQL().bindings, column];
|
|
21
|
+
let rawCase = `(CASE WHEN ${sql} THEN ?? END)`;
|
|
22
|
+
if (alias) {
|
|
23
|
+
rawCase += ' AS ??';
|
|
24
|
+
bindings.push(alias);
|
|
25
|
+
}
|
|
26
|
+
return knex.raw(rawCase, bindings);
|
|
27
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Item, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { NestedCollectionNode } from '../../../types/ast.js';
|
|
3
|
+
export declare function applyParentFilters(schema: SchemaOverview, nestedCollectionNodes: NestedCollectionNode[], parentItem: Item | Item[]): NestedCollectionNode[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { toArray } from '@directus/utils';
|
|
2
|
+
import { isNil, merge, uniq } from 'lodash-es';
|
|
3
|
+
export function applyParentFilters(schema, nestedCollectionNodes, parentItem) {
|
|
4
|
+
const parentItems = toArray(parentItem);
|
|
5
|
+
for (const nestedNode of nestedCollectionNodes) {
|
|
6
|
+
if (!nestedNode.relation)
|
|
7
|
+
continue;
|
|
8
|
+
if (nestedNode.type === 'm2o') {
|
|
9
|
+
const foreignField = schema.collections[nestedNode.relation.related_collection].primary;
|
|
10
|
+
const foreignIds = uniq(parentItems.map((res) => res[nestedNode.relation.field])).filter((id) => !isNil(id));
|
|
11
|
+
merge(nestedNode, { query: { filter: { [foreignField]: { _in: foreignIds } } } });
|
|
12
|
+
}
|
|
13
|
+
else if (nestedNode.type === 'o2m') {
|
|
14
|
+
const relatedM2OisFetched = !!nestedNode.children.find((child) => {
|
|
15
|
+
return child.type === 'field' && child.name === nestedNode.relation.field;
|
|
16
|
+
});
|
|
17
|
+
if (relatedM2OisFetched === false) {
|
|
18
|
+
nestedNode.children.push({
|
|
19
|
+
type: 'field',
|
|
20
|
+
name: nestedNode.relation.field,
|
|
21
|
+
fieldKey: nestedNode.relation.field,
|
|
22
|
+
whenCase: [],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (nestedNode.relation.meta?.sort_field) {
|
|
26
|
+
nestedNode.children.push({
|
|
27
|
+
type: 'field',
|
|
28
|
+
name: nestedNode.relation.meta.sort_field,
|
|
29
|
+
fieldKey: nestedNode.relation.meta.sort_field,
|
|
30
|
+
whenCase: [],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const foreignField = nestedNode.relation.field;
|
|
34
|
+
const foreignIds = uniq(parentItems.map((res) => res[nestedNode.parentKey])).filter((id) => !isNil(id));
|
|
35
|
+
merge(nestedNode, { query: { filter: { [foreignField]: { _in: foreignIds } } } });
|
|
36
|
+
}
|
|
37
|
+
else if (nestedNode.type === 'a2o') {
|
|
38
|
+
const keysPerCollection = {};
|
|
39
|
+
for (const parentItem of parentItems) {
|
|
40
|
+
const collection = parentItem[nestedNode.relation.meta.one_collection_field];
|
|
41
|
+
if (!keysPerCollection[collection])
|
|
42
|
+
keysPerCollection[collection] = [];
|
|
43
|
+
keysPerCollection[collection].push(parentItem[nestedNode.relation.field]);
|
|
44
|
+
}
|
|
45
|
+
for (const relatedCollection of nestedNode.names) {
|
|
46
|
+
const foreignField = nestedNode.relatedKey[relatedCollection];
|
|
47
|
+
const foreignIds = uniq(keysPerCollection[relatedCollection]);
|
|
48
|
+
merge(nestedNode, {
|
|
49
|
+
query: { [relatedCollection]: { filter: { [foreignField]: { _in: foreignIds } }, limit: foreignIds.length } },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return nestedCollectionNodes;
|
|
55
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Filter, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import type { FieldNode, FunctionFieldNode, M2ONode } from '../../../types/ast.js';
|
|
4
|
+
import type { AliasMap } from '../../../utils/get-column-path.js';
|
|
5
|
+
interface NodePreProcessOptions {
|
|
6
|
+
/** Don't assign an alias to the column but instead return the column as is */
|
|
7
|
+
noAlias?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function getColumnPreprocessor(knex: Knex, schema: SchemaOverview, table: string, cases: Filter[], aliasMap: AliasMap): (fieldNode: FieldNode | FunctionFieldNode | M2ONode, options?: NodePreProcessOptions) => Knex.Raw<string>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { joinFilterWithCases } from '../../../utils/apply-query.js';
|
|
2
|
+
import { getColumn } from '../../../utils/get-column.js';
|
|
3
|
+
import { parseFilterKey } from '../../../utils/parse-filter-key.js';
|
|
4
|
+
import { getHelpers } from '../../helpers/index.js';
|
|
5
|
+
import { applyCaseWhen } from './apply-case-when.js';
|
|
6
|
+
import { getNodeAlias } from './get-field-alias.js';
|
|
7
|
+
export function getColumnPreprocessor(knex, schema, table, cases, aliasMap) {
|
|
8
|
+
const helpers = getHelpers(knex);
|
|
9
|
+
return function (fieldNode, options) {
|
|
10
|
+
// Don't assign an alias to the column expression if the field has a whenCase
|
|
11
|
+
// (since the alias will be assigned in applyCaseWhen) or if the noAlias option is set
|
|
12
|
+
const hasWhenCase = fieldNode.whenCase && fieldNode.whenCase.length > 0;
|
|
13
|
+
const noAlias = options?.noAlias || hasWhenCase;
|
|
14
|
+
const alias = getNodeAlias(fieldNode);
|
|
15
|
+
const rawColumnAlias = noAlias ? false : alias;
|
|
16
|
+
let field;
|
|
17
|
+
if (fieldNode.type === 'field' || fieldNode.type === 'functionField') {
|
|
18
|
+
const { fieldName } = parseFilterKey(fieldNode.name);
|
|
19
|
+
field = schema.collections[table].fields[fieldName];
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
field = schema.collections[fieldNode.relation.collection].fields[fieldNode.relation.field];
|
|
23
|
+
}
|
|
24
|
+
let column;
|
|
25
|
+
if (field?.type?.startsWith('geometry')) {
|
|
26
|
+
column = helpers.st.asText(table, field.field, rawColumnAlias);
|
|
27
|
+
}
|
|
28
|
+
else if (fieldNode.type === 'functionField') {
|
|
29
|
+
// Include the field cases in the functionField query filter
|
|
30
|
+
column = getColumn(knex, table, fieldNode.name, rawColumnAlias, schema, {
|
|
31
|
+
query: {
|
|
32
|
+
...fieldNode.query,
|
|
33
|
+
filter: joinFilterWithCases(fieldNode.query.filter, fieldNode.cases),
|
|
34
|
+
},
|
|
35
|
+
cases: fieldNode.cases,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
column = getColumn(knex, table, fieldNode.name, rawColumnAlias, schema);
|
|
40
|
+
}
|
|
41
|
+
if (hasWhenCase) {
|
|
42
|
+
const columnCases = [];
|
|
43
|
+
for (const index of fieldNode.whenCase) {
|
|
44
|
+
columnCases.push(cases[index]);
|
|
45
|
+
}
|
|
46
|
+
column = applyCaseWhen({
|
|
47
|
+
column,
|
|
48
|
+
columnCases,
|
|
49
|
+
aliasMap,
|
|
50
|
+
cases,
|
|
51
|
+
table,
|
|
52
|
+
alias,
|
|
53
|
+
}, { knex, schema });
|
|
54
|
+
}
|
|
55
|
+
return column;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Filter, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import type { FieldNode, FunctionFieldNode, M2ONode, O2MNode } from '../../../types/index.js';
|
|
4
|
+
import type { AliasMap } from '../../../utils/get-column-path.js';
|
|
5
|
+
export declare function getInnerQueryColumnPreProcessor(knex: Knex, schema: SchemaOverview, table: string, cases: Filter[], aliasMap: AliasMap, aliasPrefix: string): (fieldNode: FieldNode | FunctionFieldNode | M2ONode | O2MNode) => Knex.Raw<string> | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { applyCaseWhen } from './apply-case-when.js';
|
|
2
|
+
import { getNodeAlias } from './get-field-alias.js';
|
|
3
|
+
export function getInnerQueryColumnPreProcessor(knex, schema, table, cases, aliasMap, aliasPrefix) {
|
|
4
|
+
return function (fieldNode) {
|
|
5
|
+
const alias = getNodeAlias(fieldNode);
|
|
6
|
+
if (fieldNode.whenCase && fieldNode.whenCase.length > 0) {
|
|
7
|
+
const columnCases = [];
|
|
8
|
+
for (const index of fieldNode.whenCase) {
|
|
9
|
+
columnCases.push(cases[index]);
|
|
10
|
+
}
|
|
11
|
+
// Don't pass in the alias as we need to wrap the whole case/when in a count() an alias that
|
|
12
|
+
const caseWhen = applyCaseWhen({
|
|
13
|
+
column: knex.raw(1),
|
|
14
|
+
columnCases,
|
|
15
|
+
aliasMap,
|
|
16
|
+
cases,
|
|
17
|
+
table,
|
|
18
|
+
}, { knex, schema });
|
|
19
|
+
return knex.raw('COUNT(??) AS ??', [caseWhen, `${aliasPrefix}_${alias}`]);
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Item, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { NestedCollectionNode } from '../../../types/ast.js';
|
|
3
|
+
export declare function mergeWithParentItems(schema: SchemaOverview, nestedItem: Item | Item[], parentItem: Item | Item[], nestedNode: NestedCollectionNode, fieldAllowed: boolean | boolean[]): Item | Item[] | undefined;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { toArray } from '@directus/utils';
|
|
3
|
+
import { clone, isArray } from 'lodash-es';
|
|
4
|
+
export function mergeWithParentItems(schema, nestedItem, parentItem, nestedNode, fieldAllowed) {
|
|
5
|
+
const env = useEnv();
|
|
6
|
+
const nestedItems = toArray(nestedItem);
|
|
7
|
+
const parentItems = clone(toArray(parentItem));
|
|
8
|
+
if (nestedNode.type === 'm2o') {
|
|
9
|
+
for (const parentItem of parentItems) {
|
|
10
|
+
const itemChild = nestedItems.find((nestedItem) => {
|
|
11
|
+
return (nestedItem[schema.collections[nestedNode.relation.related_collection].primary] ==
|
|
12
|
+
parentItem[nestedNode.relation.field]);
|
|
13
|
+
});
|
|
14
|
+
parentItem[nestedNode.fieldKey] = itemChild || null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
else if (nestedNode.type === 'o2m') {
|
|
18
|
+
for (const [index, parentItem] of parentItems.entries()) {
|
|
19
|
+
if (fieldAllowed === false || (isArray(fieldAllowed) && !fieldAllowed[index])) {
|
|
20
|
+
parentItem[nestedNode.fieldKey] = null;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (!parentItem[nestedNode.fieldKey])
|
|
24
|
+
parentItem[nestedNode.fieldKey] = [];
|
|
25
|
+
const itemChildren = nestedItems.filter((nestedItem) => {
|
|
26
|
+
if (nestedItem === null)
|
|
27
|
+
return false;
|
|
28
|
+
if (Array.isArray(nestedItem[nestedNode.relation.field]))
|
|
29
|
+
return true;
|
|
30
|
+
return (nestedItem[nestedNode.relation.field] ==
|
|
31
|
+
parentItem[schema.collections[nestedNode.relation.related_collection].primary] ||
|
|
32
|
+
nestedItem[nestedNode.relation.field]?.[schema.collections[nestedNode.relation.related_collection].primary] == parentItem[schema.collections[nestedNode.relation.related_collection].primary]);
|
|
33
|
+
});
|
|
34
|
+
parentItem[nestedNode.fieldKey].push(...itemChildren);
|
|
35
|
+
const limit = nestedNode.query.limit ?? Number(env['QUERY_LIMIT_DEFAULT']);
|
|
36
|
+
if (nestedNode.query.page && nestedNode.query.page > 1) {
|
|
37
|
+
parentItem[nestedNode.fieldKey] = parentItem[nestedNode.fieldKey].slice(limit * (nestedNode.query.page - 1));
|
|
38
|
+
}
|
|
39
|
+
if (nestedNode.query.offset && nestedNode.query.offset >= 0) {
|
|
40
|
+
parentItem[nestedNode.fieldKey] = parentItem[nestedNode.fieldKey].slice(nestedNode.query.offset);
|
|
41
|
+
}
|
|
42
|
+
if (limit !== -1) {
|
|
43
|
+
parentItem[nestedNode.fieldKey] = parentItem[nestedNode.fieldKey].slice(0, limit);
|
|
44
|
+
}
|
|
45
|
+
parentItem[nestedNode.fieldKey] = parentItem[nestedNode.fieldKey].sort((a, b) => {
|
|
46
|
+
// This is pre-filled in get-ast-from-query
|
|
47
|
+
const sortField = nestedNode.query.sort[0];
|
|
48
|
+
let column = sortField;
|
|
49
|
+
let order = 'asc';
|
|
50
|
+
if (sortField.startsWith('-')) {
|
|
51
|
+
column = sortField.substring(1);
|
|
52
|
+
order = 'desc';
|
|
53
|
+
}
|
|
54
|
+
if (a[column] === b[column])
|
|
55
|
+
return 0;
|
|
56
|
+
if (a[column] === null)
|
|
57
|
+
return 1;
|
|
58
|
+
if (b[column] === null)
|
|
59
|
+
return -1;
|
|
60
|
+
if (order === 'asc') {
|
|
61
|
+
return a[column] < b[column] ? -1 : 1;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
return a[column] < b[column] ? 1 : -1;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (nestedNode.type === 'a2o') {
|
|
70
|
+
for (const parentItem of parentItems) {
|
|
71
|
+
if (!nestedNode.relation.meta?.one_collection_field) {
|
|
72
|
+
parentItem[nestedNode.fieldKey] = null;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const relatedCollection = parentItem[nestedNode.relation.meta.one_collection_field];
|
|
76
|
+
if (!nestedItem[relatedCollection]) {
|
|
77
|
+
parentItem[nestedNode.fieldKey] = null;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const itemChild = nestedItem[relatedCollection].find((nestedItem) => {
|
|
81
|
+
return nestedItem[nestedNode.relatedKey[relatedCollection]] == parentItem[nestedNode.fieldKey];
|
|
82
|
+
});
|
|
83
|
+
parentItem[nestedNode.fieldKey] = itemChild || null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return Array.isArray(parentItem) ? parentItems : parentItems[0];
|
|
87
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Item, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { AST, NestedCollectionNode } from '../../../types/ast.js';
|
|
3
|
+
export declare function removeTemporaryFields(schema: SchemaOverview, rawItem: Item | Item[], ast: AST | NestedCollectionNode, primaryKeyField: string, parentItem?: Item): null | Item | Item[];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { toArray } from '@directus/utils';
|
|
2
|
+
import { cloneDeep, pick } from 'lodash-es';
|
|
3
|
+
import { applyFunctionToColumnName } from '../../../utils/apply-function-to-column-name.js';
|
|
4
|
+
export function removeTemporaryFields(schema, rawItem, ast, primaryKeyField, parentItem) {
|
|
5
|
+
const rawItems = cloneDeep(toArray(rawItem));
|
|
6
|
+
const items = [];
|
|
7
|
+
if (ast.type === 'a2o') {
|
|
8
|
+
const fields = {};
|
|
9
|
+
const nestedCollectionNodes = {};
|
|
10
|
+
for (const relatedCollection of ast.names) {
|
|
11
|
+
if (!fields[relatedCollection])
|
|
12
|
+
fields[relatedCollection] = [];
|
|
13
|
+
if (!nestedCollectionNodes[relatedCollection])
|
|
14
|
+
nestedCollectionNodes[relatedCollection] = [];
|
|
15
|
+
for (const child of ast.children[relatedCollection]) {
|
|
16
|
+
if (child.type === 'field' || child.type === 'functionField') {
|
|
17
|
+
fields[relatedCollection].push(child.name);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
fields[relatedCollection].push(child.fieldKey);
|
|
21
|
+
nestedCollectionNodes[relatedCollection].push(child);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const rawItem of rawItems) {
|
|
26
|
+
const relatedCollection = parentItem?.[ast.relation.meta.one_collection_field];
|
|
27
|
+
if (rawItem === null || rawItem === undefined)
|
|
28
|
+
return rawItem;
|
|
29
|
+
let item = rawItem;
|
|
30
|
+
for (const nestedNode of nestedCollectionNodes[relatedCollection]) {
|
|
31
|
+
item[nestedNode.fieldKey] = removeTemporaryFields(schema, item[nestedNode.fieldKey], nestedNode, schema.collections[nestedNode.relation.collection].primary, item);
|
|
32
|
+
}
|
|
33
|
+
const fieldsWithFunctionsApplied = fields[relatedCollection].map((field) => applyFunctionToColumnName(field));
|
|
34
|
+
item =
|
|
35
|
+
fields[relatedCollection].length > 0 ? pick(rawItem, fieldsWithFunctionsApplied) : rawItem[primaryKeyField];
|
|
36
|
+
items.push(item);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const fields = [];
|
|
41
|
+
const nestedCollectionNodes = [];
|
|
42
|
+
for (const child of ast.children) {
|
|
43
|
+
fields.push(child.fieldKey);
|
|
44
|
+
if (child.type !== 'field' && child.type !== 'functionField') {
|
|
45
|
+
nestedCollectionNodes.push(child);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Make sure any requested aggregate fields are included
|
|
49
|
+
if (ast.query?.aggregate) {
|
|
50
|
+
for (const [operation, aggregateFields] of Object.entries(ast.query.aggregate)) {
|
|
51
|
+
if (!fields)
|
|
52
|
+
continue;
|
|
53
|
+
if (operation === 'count' && aggregateFields.includes('*'))
|
|
54
|
+
fields.push('count');
|
|
55
|
+
fields.push(...aggregateFields.map((field) => `${operation}.${field}`));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (const rawItem of rawItems) {
|
|
59
|
+
if (rawItem === null || rawItem === undefined)
|
|
60
|
+
return rawItem;
|
|
61
|
+
let item = rawItem;
|
|
62
|
+
for (const nestedNode of nestedCollectionNodes) {
|
|
63
|
+
item[nestedNode.fieldKey] = removeTemporaryFields(schema, item[nestedNode.fieldKey], nestedNode, nestedNode.type === 'm2o'
|
|
64
|
+
? schema.collections[nestedNode.relation.related_collection].primary
|
|
65
|
+
: schema.collections[nestedNode.relation.collection].primary, item);
|
|
66
|
+
}
|
|
67
|
+
const fieldsWithFunctionsApplied = fields.map((field) => applyFunctionToColumnName(field));
|
|
68
|
+
item = fields.length > 0 ? pick(rawItem, fieldsWithFunctionsApplied) : rawItem[primaryKeyField];
|
|
69
|
+
items.push(item);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return Array.isArray(rawItem) ? items : items[0];
|
|
73
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getHelpers } from '../../helpers/index.js';
|
|
2
|
+
export function withPreprocessBindings(knex, dbQuery) {
|
|
3
|
+
const schemaHelper = getHelpers(knex).schema;
|
|
4
|
+
dbQuery.client = new Proxy(dbQuery.client, {
|
|
5
|
+
get(target, prop, receiver) {
|
|
6
|
+
if (prop === 'query') {
|
|
7
|
+
return (connection, queryParam) => {
|
|
8
|
+
return Reflect.get(target, prop, receiver).bind(target)(connection, schemaHelper.preprocessBindings(queryParam));
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return Reflect.get(target, prop, receiver);
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
}
|
package/dist/flows.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Action } from '@directus/constants';
|
|
2
2
|
import { useEnv } from '@directus/env';
|
|
3
3
|
import { ForbiddenError } from '@directus/errors';
|
|
4
|
+
import { isSystemCollection } from '@directus/system-data';
|
|
4
5
|
import { applyOptionsData, getRedactedString, isValidJSON, parseJSON, toArray } from '@directus/utils';
|
|
5
|
-
import {
|
|
6
|
+
import { pick } from 'lodash-es';
|
|
6
7
|
import { get } from 'micromustache';
|
|
7
8
|
import { useBus } from './bus/index.js';
|
|
8
9
|
import getDatabase from './database/index.js';
|
|
@@ -18,7 +19,6 @@ import { JobQueue } from './utils/job-queue.js';
|
|
|
18
19
|
import { mapValuesDeep } from './utils/map-values-deep.js';
|
|
19
20
|
import { redactObject } from './utils/redact-object.js';
|
|
20
21
|
import { scheduleSynchronizedJob, validateCron } from './utils/schedule.js';
|
|
21
|
-
import { isSystemCollection } from '@directus/system-data';
|
|
22
22
|
let flowManager;
|
|
23
23
|
export function getFlowManager() {
|
|
24
24
|
if (flowManager) {
|
|
@@ -284,8 +284,7 @@ class FlowManager {
|
|
|
284
284
|
item: flow.id,
|
|
285
285
|
data: {
|
|
286
286
|
steps: steps.map((step) => redactObject(step, { values: this.envs }, getRedactedString)),
|
|
287
|
-
data: redactObject(
|
|
288
|
-
{
|
|
287
|
+
data: redactObject(keyedData, {
|
|
289
288
|
keys: [
|
|
290
289
|
['**', 'headers', 'authorization'],
|
|
291
290
|
['**', 'headers', 'cookie'],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isEqual } from 'lodash-es';
|
|
2
2
|
import getDatabase from '../database/index.js';
|
|
3
3
|
import emitter from '../emitter.js';
|
|
4
|
+
import { createDefaultAccountability } from '../permissions/utils/create-default-accountability.js';
|
|
4
5
|
import asyncHandler from '../utils/async-handler.js';
|
|
5
6
|
import { getAccountabilityForToken } from '../utils/get-accountability-for-token.js';
|
|
6
7
|
import { getIPFromReq } from '../utils/get-ip-from-req.js';
|
|
@@ -12,13 +13,7 @@ import { SESSION_COOKIE_OPTIONS } from '../constants.js';
|
|
|
12
13
|
*/
|
|
13
14
|
export const handler = async (req, res, next) => {
|
|
14
15
|
const env = useEnv();
|
|
15
|
-
const defaultAccountability = {
|
|
16
|
-
user: null,
|
|
17
|
-
role: null,
|
|
18
|
-
admin: false,
|
|
19
|
-
app: false,
|
|
20
|
-
ip: getIPFromReq(req),
|
|
21
|
-
};
|
|
16
|
+
const defaultAccountability = createDefaultAccountability({ ip: getIPFromReq(req) });
|
|
22
17
|
const userAgent = req.get('user-agent')?.substring(0, 1024);
|
|
23
18
|
if (userAgent)
|
|
24
19
|
defaultAccountability.userAgent = userAgent;
|