@directus/api 19.3.1 → 20.0.0-rc.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/utils/defaults.d.ts +4 -11
- package/dist/cli/utils/defaults.js +7 -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/users.js +0 -55
- package/dist/database/errors/dialects/mysql.js +23 -23
- 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 +190 -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/index.js +2 -1
- package/dist/database/migrations/20240619A-permissions-policies.d.ts +3 -0
- package/dist/database/migrations/20240619A-permissions-policies.js +163 -0
- package/dist/database/run-ast/lib/get-db-query.d.ts +4 -0
- package/dist/database/run-ast/lib/get-db-query.js +194 -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 +26 -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/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +1 -1
- package/dist/flows.js +3 -4
- package/dist/middleware/authenticate.js +2 -7
- package/dist/middleware/cache.js +1 -1
- package/dist/middleware/cors.js +4 -4
- 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 +10 -0
- package/dist/permissions/lib/fetch-permissions.js +55 -0
- package/dist/permissions/lib/fetch-policies.d.ts +7 -0
- package/dist/permissions/lib/fetch-policies.js +28 -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 +24 -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 +50 -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 +24 -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/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 +53 -24
- package/dist/services/files.d.ts +0 -4
- package/dist/services/files.js +10 -10
- package/dist/services/flows.d.ts +0 -2
- package/dist/services/flows.js +2 -14
- 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.js +23 -9
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.js +3 -2
- package/dist/services/items.d.ts +40 -14
- package/dist/services/items.js +182 -79
- package/dist/services/meta.js +60 -23
- package/dist/services/notifications.d.ts +0 -1
- package/dist/services/notifications.js +0 -7
- package/dist/services/operations.d.ts +0 -2
- package/dist/services/operations.js +2 -14
- package/dist/services/payload.d.ts +9 -10
- package/dist/services/payload.js +35 -19
- 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 +26 -29
- package/dist/services/roles.d.ts +4 -14
- package/dist/services/roles.js +56 -430
- 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 +2 -20
- package/dist/services/users.js +87 -192
- 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 +6 -3
- package/dist/telemetry/types/report.d.ts +4 -0
- package/dist/telemetry/utils/check-user-limits.d.ts +5 -0
- package/dist/telemetry/utils/check-user-limits.js +19 -0
- package/dist/telemetry/utils/get-filesize-sum.d.ts +5 -0
- package/dist/telemetry/utils/get-filesize-sum.js +7 -0
- package/dist/types/ast.d.ts +43 -1
- package/dist/types/items.d.ts +11 -0
- package/dist/utils/apply-query.d.ts +4 -3
- package/dist/utils/apply-query.js +37 -8
- package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +17 -0
- package/dist/utils/fetch-user-count/fetch-access-lookup.js +22 -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 +57 -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-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-graphql-type.js +1 -0
- package/dist/utils/get-service.d.ts +1 -1
- package/dist/utils/get-service.js +14 -10
- package/dist/utils/reduce-schema.d.ts +4 -6
- package/dist/utils/reduce-schema.js +14 -34
- 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 +1 -4
- package/dist/websocket/controllers/hooks.js +4 -0
- package/dist/websocket/controllers/rest.js +0 -2
- package/dist/websocket/handlers/subscribe.js +0 -2
- package/dist/websocket/utils/items.d.ts +1 -1
- package/dist/websocket/utils/items.js +4 -1
- package/package.json +31 -30
- package/dist/database/run-ast.js +0 -450
- 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 -22
- 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.d.ts +0 -3
- package/dist/utils/merge-permissions.js +0 -95
package/dist/services/items.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { Action } from '@directus/constants';
|
|
2
2
|
import { useEnv } from '@directus/env';
|
|
3
|
-
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
3
|
+
import { ErrorCode, ForbiddenError, InvalidPayloadError, isDirectusError } from '@directus/errors';
|
|
4
4
|
import { isSystemCollection } from '@directus/system-data';
|
|
5
5
|
import { assign, clone, cloneDeep, omit, pick, without } from 'lodash-es';
|
|
6
6
|
import { getCache } from '../cache.js';
|
|
7
7
|
import { translateDatabaseError } from '../database/errors/translate.js';
|
|
8
|
+
import { getAstFromQuery } from '../database/get-ast-from-query/get-ast-from-query.js';
|
|
8
9
|
import { getHelpers } from '../database/helpers/index.js';
|
|
9
10
|
import getDatabase from '../database/index.js';
|
|
10
|
-
import
|
|
11
|
+
import { runAst } from '../database/run-ast/run-ast.js';
|
|
11
12
|
import emitter from '../emitter.js';
|
|
12
|
-
import
|
|
13
|
+
import { processAst } from '../permissions/modules/process-ast/process-ast.js';
|
|
14
|
+
import { processPayload } from '../permissions/modules/process-payload/process-payload.js';
|
|
15
|
+
import { validateAccess } from '../permissions/modules/validate-access/validate-access.js';
|
|
13
16
|
import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
14
17
|
import { transaction } from '../utils/transaction.js';
|
|
15
18
|
import { validateKeys } from '../utils/validate-keys.js';
|
|
16
|
-
import {
|
|
19
|
+
import { UserIntegrityCheckFlag, validateUserCountIntegrity } from '../utils/validate-user-count-integrity.js';
|
|
17
20
|
import { PayloadService } from './payload.js';
|
|
18
21
|
const env = useEnv();
|
|
19
22
|
export class ItemsService {
|
|
@@ -32,6 +35,20 @@ export class ItemsService {
|
|
|
32
35
|
this.cache = getCache().cache;
|
|
33
36
|
return this;
|
|
34
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a fork of the current service, allowing instantiation with different options.
|
|
40
|
+
*/
|
|
41
|
+
fork(options) {
|
|
42
|
+
const Service = this.constructor;
|
|
43
|
+
// ItemsService expects `collection` and `options` as parameters,
|
|
44
|
+
// while the other services only expect `options`
|
|
45
|
+
const isItemsService = Service.length === 2;
|
|
46
|
+
const newOptions = { knex: this.knex, accountability: this.accountability, schema: this.schema, ...options };
|
|
47
|
+
if (isItemsService) {
|
|
48
|
+
return new ItemsService(this.collection, newOptions);
|
|
49
|
+
}
|
|
50
|
+
return new Service(newOptions);
|
|
51
|
+
}
|
|
35
52
|
createMutationTracker(initialCount = 0) {
|
|
36
53
|
const maxCount = Number(env['MAX_BATCH_MUTATION']);
|
|
37
54
|
let mutationCount = initialCount;
|
|
@@ -84,17 +101,13 @@ export class ItemsService {
|
|
|
84
101
|
// that any errors thrown in any nested relational changes will bubble up and cancel the whole
|
|
85
102
|
// update tree
|
|
86
103
|
const primaryKey = await transaction(this.knex, async (trx) => {
|
|
87
|
-
|
|
88
|
-
const payloadService = new PayloadService(this.collection, {
|
|
89
|
-
accountability: this.accountability,
|
|
90
|
-
knex: trx,
|
|
91
|
-
schema: this.schema,
|
|
92
|
-
});
|
|
93
|
-
const authorizationService = new AuthorizationService({
|
|
104
|
+
const serviceOptions = {
|
|
94
105
|
accountability: this.accountability,
|
|
95
106
|
knex: trx,
|
|
96
107
|
schema: this.schema,
|
|
97
|
-
}
|
|
108
|
+
};
|
|
109
|
+
// We're creating new services instances so they can use the transaction as their Knex interface
|
|
110
|
+
const payloadService = new PayloadService(this.collection, serviceOptions);
|
|
98
111
|
// Run all hooks that are attached to this event so the end user has the chance to augment the
|
|
99
112
|
// item that is about to be saved
|
|
100
113
|
const payloadAfterHooks = opts.emitEvents !== false
|
|
@@ -109,13 +122,21 @@ export class ItemsService {
|
|
|
109
122
|
})
|
|
110
123
|
: payload;
|
|
111
124
|
const payloadWithPresets = this.accountability
|
|
112
|
-
?
|
|
125
|
+
? await processPayload({
|
|
126
|
+
accountability: this.accountability,
|
|
127
|
+
action: 'create',
|
|
128
|
+
collection: this.collection,
|
|
129
|
+
payload: payloadAfterHooks,
|
|
130
|
+
}, {
|
|
131
|
+
knex: trx,
|
|
132
|
+
schema: this.schema,
|
|
133
|
+
})
|
|
113
134
|
: payloadAfterHooks;
|
|
114
135
|
if (opts.preMutationError) {
|
|
115
136
|
throw opts.preMutationError;
|
|
116
137
|
}
|
|
117
|
-
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
118
|
-
const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
|
|
138
|
+
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, userIntegrityCheckFlags: userIntegrityCheckFlagsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
139
|
+
const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, userIntegrityCheckFlags: userIntegrityCheckFlagsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
|
|
119
140
|
const payloadWithoutAliases = pick(payloadWithA2O, without(fields, ...aliases));
|
|
120
141
|
const payloadWithTypeCasting = await payloadService.processValues('create', payloadWithoutAliases);
|
|
121
142
|
// The primary key can already exist in the payload.
|
|
@@ -151,7 +172,14 @@ export class ItemsService {
|
|
|
151
172
|
}
|
|
152
173
|
}
|
|
153
174
|
catch (err) {
|
|
154
|
-
|
|
175
|
+
const dbError = await translateDatabaseError(err);
|
|
176
|
+
if (isDirectusError(dbError, ErrorCode.RecordNotUnique) && dbError.extensions.primaryKey) {
|
|
177
|
+
// This is a MySQL specific thing we need to handle here, since MySQL does not return the field name
|
|
178
|
+
// if the unique constraint is the primary key
|
|
179
|
+
dbError.extensions.field = pkField?.field ?? null;
|
|
180
|
+
delete dbError.extensions.primaryKey;
|
|
181
|
+
}
|
|
182
|
+
throw dbError;
|
|
155
183
|
}
|
|
156
184
|
// Most database support returning, those who don't tend to return the PK anyways
|
|
157
185
|
// (MySQL/SQLite). In case the primary key isn't know yet, we'll do a best-attempt at
|
|
@@ -166,10 +194,22 @@ export class ItemsService {
|
|
|
166
194
|
}
|
|
167
195
|
// At this point, the primary key is guaranteed to be set.
|
|
168
196
|
primaryKey = primaryKey;
|
|
169
|
-
const { revisions: revisionsO2M, nestedActionEvents: nestedActionEventsO2M } = await payloadService.processO2M(payloadWithPresets, primaryKey, opts);
|
|
197
|
+
const { revisions: revisionsO2M, nestedActionEvents: nestedActionEventsO2M, userIntegrityCheckFlags: userIntegrityCheckFlagsO2M, } = await payloadService.processO2M(payloadWithPresets, primaryKey, opts);
|
|
170
198
|
nestedActionEvents.push(...nestedActionEventsM2O);
|
|
171
199
|
nestedActionEvents.push(...nestedActionEventsA2O);
|
|
172
200
|
nestedActionEvents.push(...nestedActionEventsO2M);
|
|
201
|
+
const userIntegrityCheckFlags = (opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) |
|
|
202
|
+
userIntegrityCheckFlagsM2O |
|
|
203
|
+
userIntegrityCheckFlagsA2O |
|
|
204
|
+
userIntegrityCheckFlagsO2M;
|
|
205
|
+
if (userIntegrityCheckFlags) {
|
|
206
|
+
if (opts.onRequireUserIntegrityCheck) {
|
|
207
|
+
opts.onRequireUserIntegrityCheck(userIntegrityCheckFlags);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
await validateUserCountIntegrity({ flags: userIntegrityCheckFlags, knex: trx });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
173
213
|
// If this is an authenticated action, and accountability tracking is enabled, save activity row
|
|
174
214
|
if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
|
|
175
215
|
const activityService = new ActivityService({
|
|
@@ -252,16 +292,15 @@ export class ItemsService {
|
|
|
252
292
|
}
|
|
253
293
|
/**
|
|
254
294
|
* Create multiple new items at once. Inserts all provided records sequentially wrapped in a transaction.
|
|
295
|
+
*
|
|
296
|
+
* Uses `this.createOne` under the hood.
|
|
255
297
|
*/
|
|
256
298
|
async createMany(data, opts = {}) {
|
|
257
299
|
if (!opts.mutationTracker)
|
|
258
300
|
opts.mutationTracker = this.createMutationTracker();
|
|
259
301
|
const { primaryKeys, nestedActionEvents } = await transaction(this.knex, async (knex) => {
|
|
260
|
-
const service =
|
|
261
|
-
|
|
262
|
-
schema: this.schema,
|
|
263
|
-
knex: knex,
|
|
264
|
-
});
|
|
302
|
+
const service = this.fork({ knex });
|
|
303
|
+
let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None;
|
|
265
304
|
const primaryKeys = [];
|
|
266
305
|
const nestedActionEvents = [];
|
|
267
306
|
const pkField = this.schema.collections[this.collection].primary;
|
|
@@ -275,12 +314,21 @@ export class ItemsService {
|
|
|
275
314
|
const primaryKey = await service.createOne(payload, {
|
|
276
315
|
...(opts || {}),
|
|
277
316
|
autoPurgeCache: false,
|
|
317
|
+
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
278
318
|
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
279
319
|
mutationTracker: opts.mutationTracker,
|
|
280
320
|
bypassAutoIncrementSequenceReset,
|
|
281
321
|
});
|
|
282
322
|
primaryKeys.push(primaryKey);
|
|
283
323
|
}
|
|
324
|
+
if (userIntegrityCheckFlags) {
|
|
325
|
+
if (opts.onRequireUserIntegrityCheck) {
|
|
326
|
+
opts.onRequireUserIntegrityCheck(userIntegrityCheckFlags);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
await validateUserCountIntegrity({ flags: userIntegrityCheckFlags, knex });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
284
332
|
return { primaryKeys, nestedActionEvents };
|
|
285
333
|
});
|
|
286
334
|
if (opts.emitEvents !== false) {
|
|
@@ -299,7 +347,7 @@ export class ItemsService {
|
|
|
299
347
|
return primaryKeys;
|
|
300
348
|
}
|
|
301
349
|
/**
|
|
302
|
-
* Get items by query
|
|
350
|
+
* Get items by query.
|
|
303
351
|
*/
|
|
304
352
|
async readByQuery(query, opts) {
|
|
305
353
|
const updatedQuery = opts?.emitEvents !== false
|
|
@@ -313,27 +361,21 @@ export class ItemsService {
|
|
|
313
361
|
accountability: this.accountability,
|
|
314
362
|
})
|
|
315
363
|
: query;
|
|
316
|
-
let ast = await
|
|
364
|
+
let ast = await getAstFromQuery({
|
|
365
|
+
collection: this.collection,
|
|
366
|
+
query: updatedQuery,
|
|
317
367
|
accountability: this.accountability,
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
// access to (a) certain item(s)
|
|
321
|
-
action: opts?.permissionsAction || 'read',
|
|
368
|
+
}, {
|
|
369
|
+
schema: this.schema,
|
|
322
370
|
knex: this.knex,
|
|
323
371
|
});
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
accountability: this.accountability,
|
|
327
|
-
knex: this.knex,
|
|
328
|
-
schema: this.schema,
|
|
329
|
-
});
|
|
330
|
-
ast = await authorizationService.processAST(ast, opts?.permissionsAction);
|
|
331
|
-
}
|
|
332
|
-
const records = await runAST(ast, this.schema, {
|
|
372
|
+
ast = await processAst({ ast, action: 'read', accountability: this.accountability }, { knex: this.knex, schema: this.schema });
|
|
373
|
+
const records = await runAst(ast, this.schema, {
|
|
333
374
|
knex: this.knex,
|
|
334
375
|
// GraphQL requires relational keys to be returned regardless
|
|
335
376
|
stripNonRequested: opts?.stripNonRequested !== undefined ? opts.stripNonRequested : true,
|
|
336
377
|
});
|
|
378
|
+
// TODO when would this happen?
|
|
337
379
|
if (records === null) {
|
|
338
380
|
throw new ForbiddenError();
|
|
339
381
|
}
|
|
@@ -361,7 +403,9 @@ export class ItemsService {
|
|
|
361
403
|
return filteredRecords;
|
|
362
404
|
}
|
|
363
405
|
/**
|
|
364
|
-
* Get single item by primary key
|
|
406
|
+
* Get single item by primary key.
|
|
407
|
+
*
|
|
408
|
+
* Uses `this.readByQuery` under the hood.
|
|
365
409
|
*/
|
|
366
410
|
async readOne(key, query = {}, opts) {
|
|
367
411
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
@@ -375,7 +419,9 @@ export class ItemsService {
|
|
|
375
419
|
return results[0];
|
|
376
420
|
}
|
|
377
421
|
/**
|
|
378
|
-
* Get multiple items by primary keys
|
|
422
|
+
* Get multiple items by primary keys.
|
|
423
|
+
*
|
|
424
|
+
* Uses `this.readByQuery` under the hood.
|
|
379
425
|
*/
|
|
380
426
|
async readMany(keys, query = {}, opts) {
|
|
381
427
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
@@ -390,7 +436,9 @@ export class ItemsService {
|
|
|
390
436
|
return results;
|
|
391
437
|
}
|
|
392
438
|
/**
|
|
393
|
-
* Update multiple items by query
|
|
439
|
+
* Update multiple items by query.
|
|
440
|
+
*
|
|
441
|
+
* Uses `this.updateMany` under the hood.
|
|
394
442
|
*/
|
|
395
443
|
async updateByQuery(query, data, opts) {
|
|
396
444
|
const keys = await this.getKeysByQuery(query);
|
|
@@ -399,7 +447,9 @@ export class ItemsService {
|
|
|
399
447
|
return keys.length ? await this.updateMany(keys, data, opts) : [];
|
|
400
448
|
}
|
|
401
449
|
/**
|
|
402
|
-
* Update a single item by primary key
|
|
450
|
+
* Update a single item by primary key.
|
|
451
|
+
*
|
|
452
|
+
* Uses `this.updateMany` under the hood.
|
|
403
453
|
*/
|
|
404
454
|
async updateOne(key, data, opts) {
|
|
405
455
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
@@ -408,7 +458,9 @@ export class ItemsService {
|
|
|
408
458
|
return key;
|
|
409
459
|
}
|
|
410
460
|
/**
|
|
411
|
-
* Update multiple items in a single transaction
|
|
461
|
+
* Update multiple items in a single transaction.
|
|
462
|
+
*
|
|
463
|
+
* Uses `this.updateOne` under the hood.
|
|
412
464
|
*/
|
|
413
465
|
async updateBatch(data, opts = {}) {
|
|
414
466
|
if (!Array.isArray(data)) {
|
|
@@ -419,17 +471,27 @@ export class ItemsService {
|
|
|
419
471
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
420
472
|
const keys = [];
|
|
421
473
|
try {
|
|
422
|
-
await transaction(this.knex, async (
|
|
423
|
-
const service =
|
|
424
|
-
|
|
425
|
-
knex: trx,
|
|
426
|
-
schema: this.schema,
|
|
427
|
-
});
|
|
474
|
+
await transaction(this.knex, async (knex) => {
|
|
475
|
+
const service = this.fork({ knex });
|
|
476
|
+
let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None;
|
|
428
477
|
for (const item of data) {
|
|
429
|
-
|
|
478
|
+
const primaryKey = item[primaryKeyField];
|
|
479
|
+
if (!primaryKey)
|
|
430
480
|
throw new InvalidPayloadError({ reason: `Item in update misses primary key` });
|
|
431
|
-
const combinedOpts =
|
|
432
|
-
|
|
481
|
+
const combinedOpts = {
|
|
482
|
+
autoPurgeCache: false,
|
|
483
|
+
...opts,
|
|
484
|
+
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
485
|
+
};
|
|
486
|
+
keys.push(await service.updateOne(primaryKey, omit(item, primaryKeyField), combinedOpts));
|
|
487
|
+
}
|
|
488
|
+
if (userIntegrityCheckFlags) {
|
|
489
|
+
if (opts.onRequireUserIntegrityCheck) {
|
|
490
|
+
opts.onRequireUserIntegrityCheck(userIntegrityCheckFlags);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
await validateUserCountIntegrity({ flags: userIntegrityCheckFlags, knex });
|
|
494
|
+
}
|
|
433
495
|
}
|
|
434
496
|
});
|
|
435
497
|
}
|
|
@@ -441,7 +503,7 @@ export class ItemsService {
|
|
|
441
503
|
return keys;
|
|
442
504
|
}
|
|
443
505
|
/**
|
|
444
|
-
* Update many items by primary key, setting all items to the same change
|
|
506
|
+
* Update many items by primary key, setting all items to the same change.
|
|
445
507
|
*/
|
|
446
508
|
async updateMany(keys, data, opts = {}) {
|
|
447
509
|
if (!opts.mutationTracker)
|
|
@@ -459,11 +521,6 @@ export class ItemsService {
|
|
|
459
521
|
.map((field) => field.field);
|
|
460
522
|
const payload = cloneDeep(data);
|
|
461
523
|
const nestedActionEvents = [];
|
|
462
|
-
const authorizationService = new AuthorizationService({
|
|
463
|
-
accountability: this.accountability,
|
|
464
|
-
knex: this.knex,
|
|
465
|
-
schema: this.schema,
|
|
466
|
-
});
|
|
467
524
|
// Run all hooks that are attached to this event so the end user has the chance to augment the
|
|
468
525
|
// item that is about to be saved
|
|
469
526
|
const payloadAfterHooks = opts.emitEvents !== false
|
|
@@ -481,10 +538,26 @@ export class ItemsService {
|
|
|
481
538
|
// Sort keys to ensure that the order is maintained
|
|
482
539
|
keys.sort();
|
|
483
540
|
if (this.accountability) {
|
|
484
|
-
await
|
|
541
|
+
await validateAccess({
|
|
542
|
+
accountability: this.accountability,
|
|
543
|
+
action: 'update',
|
|
544
|
+
collection: this.collection,
|
|
545
|
+
primaryKeys: keys,
|
|
546
|
+
}, {
|
|
547
|
+
schema: this.schema,
|
|
548
|
+
knex: this.knex,
|
|
549
|
+
});
|
|
485
550
|
}
|
|
486
551
|
const payloadWithPresets = this.accountability
|
|
487
|
-
?
|
|
552
|
+
? await processPayload({
|
|
553
|
+
accountability: this.accountability,
|
|
554
|
+
action: 'update',
|
|
555
|
+
collection: this.collection,
|
|
556
|
+
payload: payloadAfterHooks,
|
|
557
|
+
}, {
|
|
558
|
+
knex: this.knex,
|
|
559
|
+
schema: this.schema,
|
|
560
|
+
})
|
|
488
561
|
: payloadAfterHooks;
|
|
489
562
|
if (opts.preMutationError) {
|
|
490
563
|
throw opts.preMutationError;
|
|
@@ -495,8 +568,8 @@ export class ItemsService {
|
|
|
495
568
|
knex: trx,
|
|
496
569
|
schema: this.schema,
|
|
497
570
|
});
|
|
498
|
-
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
499
|
-
const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
|
|
571
|
+
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, userIntegrityCheckFlags: userIntegrityCheckFlagsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
572
|
+
const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, userIntegrityCheckFlags: userIntegrityCheckFlagsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
|
|
500
573
|
const payloadWithoutAliasAndPK = pick(payloadWithA2O, without(fields, primaryKeyField, ...aliases));
|
|
501
574
|
const payloadWithTypeCasting = await payloadService.processValues('update', payloadWithoutAliasAndPK);
|
|
502
575
|
if (Object.keys(payloadWithTypeCasting).length > 0) {
|
|
@@ -508,12 +581,25 @@ export class ItemsService {
|
|
|
508
581
|
}
|
|
509
582
|
}
|
|
510
583
|
const childrenRevisions = [...revisionsM2O, ...revisionsA2O];
|
|
584
|
+
let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ??
|
|
585
|
+
UserIntegrityCheckFlag.None | userIntegrityCheckFlagsM2O | userIntegrityCheckFlagsA2O;
|
|
511
586
|
nestedActionEvents.push(...nestedActionEventsM2O);
|
|
512
587
|
nestedActionEvents.push(...nestedActionEventsA2O);
|
|
513
588
|
for (const key of keys) {
|
|
514
|
-
const { revisions, nestedActionEvents: nestedActionEventsO2M } = await payloadService.processO2M(payloadWithA2O, key, opts);
|
|
589
|
+
const { revisions, nestedActionEvents: nestedActionEventsO2M, userIntegrityCheckFlags: userIntegrityCheckFlagsO2M, } = await payloadService.processO2M(payloadWithA2O, key, opts);
|
|
515
590
|
childrenRevisions.push(...revisions);
|
|
516
591
|
nestedActionEvents.push(...nestedActionEventsO2M);
|
|
592
|
+
userIntegrityCheckFlags |= userIntegrityCheckFlagsO2M;
|
|
593
|
+
}
|
|
594
|
+
if (userIntegrityCheckFlags) {
|
|
595
|
+
if (opts?.onRequireUserIntegrityCheck) {
|
|
596
|
+
opts.onRequireUserIntegrityCheck(userIntegrityCheckFlags);
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
// Having no onRequireUserIntegrityCheck callback indicates that
|
|
600
|
+
// this is the top level invocation of the nested updates, so perform the user integrity check
|
|
601
|
+
await validateUserCountIntegrity({ flags: userIntegrityCheckFlags, knex: trx });
|
|
602
|
+
}
|
|
517
603
|
}
|
|
518
604
|
// If this is an authenticated action, and accountability tracking is enabled, save activity row
|
|
519
605
|
if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
|
|
@@ -603,7 +689,9 @@ export class ItemsService {
|
|
|
603
689
|
return keys;
|
|
604
690
|
}
|
|
605
691
|
/**
|
|
606
|
-
* Upsert a single item
|
|
692
|
+
* Upsert a single item.
|
|
693
|
+
*
|
|
694
|
+
* Uses `this.createOne` / `this.updateOne` under the hood.
|
|
607
695
|
*/
|
|
608
696
|
async upsertOne(payload, opts) {
|
|
609
697
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
@@ -625,17 +713,15 @@ export class ItemsService {
|
|
|
625
713
|
}
|
|
626
714
|
}
|
|
627
715
|
/**
|
|
628
|
-
* Upsert many items
|
|
716
|
+
* Upsert many items.
|
|
717
|
+
*
|
|
718
|
+
* Uses `this.upsertOne` under the hood.
|
|
629
719
|
*/
|
|
630
720
|
async upsertMany(payloads, opts = {}) {
|
|
631
721
|
if (!opts.mutationTracker)
|
|
632
722
|
opts.mutationTracker = this.createMutationTracker();
|
|
633
|
-
const primaryKeys = await transaction(this.knex, async (
|
|
634
|
-
const service =
|
|
635
|
-
accountability: this.accountability,
|
|
636
|
-
schema: this.schema,
|
|
637
|
-
knex: trx,
|
|
638
|
-
});
|
|
723
|
+
const primaryKeys = await transaction(this.knex, async (knex) => {
|
|
724
|
+
const service = this.fork({ knex });
|
|
639
725
|
const primaryKeys = [];
|
|
640
726
|
for (const payload of payloads) {
|
|
641
727
|
const primaryKey = await service.upsertOne(payload, { ...(opts || {}), autoPurgeCache: false });
|
|
@@ -649,7 +735,9 @@ export class ItemsService {
|
|
|
649
735
|
return primaryKeys;
|
|
650
736
|
}
|
|
651
737
|
/**
|
|
652
|
-
* Delete multiple items by query
|
|
738
|
+
* Delete multiple items by query.
|
|
739
|
+
*
|
|
740
|
+
* Uses `this.deleteMany` under the hood.
|
|
653
741
|
*/
|
|
654
742
|
async deleteByQuery(query, opts) {
|
|
655
743
|
const keys = await this.getKeysByQuery(query);
|
|
@@ -658,7 +746,9 @@ export class ItemsService {
|
|
|
658
746
|
return keys.length ? await this.deleteMany(keys, opts) : [];
|
|
659
747
|
}
|
|
660
748
|
/**
|
|
661
|
-
* Delete a single item by primary key
|
|
749
|
+
* Delete a single item by primary key.
|
|
750
|
+
*
|
|
751
|
+
* Uses `this.deleteMany` under the hood.
|
|
662
752
|
*/
|
|
663
753
|
async deleteOne(key, opts) {
|
|
664
754
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
@@ -667,7 +757,7 @@ export class ItemsService {
|
|
|
667
757
|
return key;
|
|
668
758
|
}
|
|
669
759
|
/**
|
|
670
|
-
* Delete multiple items by primary key
|
|
760
|
+
* Delete multiple items by primary key.
|
|
671
761
|
*/
|
|
672
762
|
async deleteMany(keys, opts = {}) {
|
|
673
763
|
if (!opts.mutationTracker)
|
|
@@ -678,13 +768,16 @@ export class ItemsService {
|
|
|
678
768
|
const { ActivityService } = await import('./activity.js');
|
|
679
769
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
680
770
|
validateKeys(this.schema, this.collection, primaryKeyField, keys);
|
|
681
|
-
if (this.accountability
|
|
682
|
-
|
|
771
|
+
if (this.accountability) {
|
|
772
|
+
await validateAccess({
|
|
683
773
|
accountability: this.accountability,
|
|
684
|
-
|
|
774
|
+
action: 'delete',
|
|
775
|
+
collection: this.collection,
|
|
776
|
+
primaryKeys: keys,
|
|
777
|
+
}, {
|
|
685
778
|
knex: this.knex,
|
|
779
|
+
schema: this.schema,
|
|
686
780
|
});
|
|
687
|
-
await authorizationService.checkAccess('delete', this.collection, keys);
|
|
688
781
|
}
|
|
689
782
|
if (opts.preMutationError) {
|
|
690
783
|
throw opts.preMutationError;
|
|
@@ -700,6 +793,14 @@ export class ItemsService {
|
|
|
700
793
|
}
|
|
701
794
|
await transaction(this.knex, async (trx) => {
|
|
702
795
|
await trx(this.collection).whereIn(primaryKeyField, keys).delete();
|
|
796
|
+
if (opts.userIntegrityCheckFlags) {
|
|
797
|
+
if (opts.onRequireUserIntegrityCheck) {
|
|
798
|
+
opts.onRequireUserIntegrityCheck(opts.userIntegrityCheckFlags);
|
|
799
|
+
}
|
|
800
|
+
else {
|
|
801
|
+
await validateUserCountIntegrity({ flags: opts.userIntegrityCheckFlags, knex: trx });
|
|
802
|
+
}
|
|
803
|
+
}
|
|
703
804
|
if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
|
|
704
805
|
const activityService = new ActivityService({
|
|
705
806
|
knex: trx,
|
|
@@ -745,7 +846,7 @@ export class ItemsService {
|
|
|
745
846
|
return keys;
|
|
746
847
|
}
|
|
747
848
|
/**
|
|
748
|
-
* Read/treat collection as singleton
|
|
849
|
+
* Read/treat collection as singleton.
|
|
749
850
|
*/
|
|
750
851
|
async readSingleton(query, opts) {
|
|
751
852
|
query = clone(query);
|
|
@@ -773,7 +874,9 @@ export class ItemsService {
|
|
|
773
874
|
return record;
|
|
774
875
|
}
|
|
775
876
|
/**
|
|
776
|
-
* Upsert/treat collection as singleton
|
|
877
|
+
* Upsert/treat collection as singleton.
|
|
878
|
+
*
|
|
879
|
+
* Uses `this.createOne` / `this.updateOne` under the hood.
|
|
777
880
|
*/
|
|
778
881
|
async upsertSingleton(data, opts) {
|
|
779
882
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
package/dist/services/meta.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import getDatabase from '../database/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { fetchPermissions } from '../permissions/lib/fetch-permissions.js';
|
|
3
|
+
import { fetchPolicies } from '../permissions/lib/fetch-policies.js';
|
|
4
|
+
import { dedupeAccess } from '../permissions/modules/process-ast/utils/dedupe-access.js';
|
|
5
|
+
import { validateAccess } from '../permissions/modules/validate-access/validate-access.js';
|
|
3
6
|
import { applyFilter, applySearch } from '../utils/apply-query.js';
|
|
4
7
|
export class MetaService {
|
|
5
8
|
knex;
|
|
@@ -28,39 +31,73 @@ export class MetaService {
|
|
|
28
31
|
}, {});
|
|
29
32
|
}
|
|
30
33
|
async totalCount(collection) {
|
|
31
|
-
const dbQuery = this.knex(collection)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
const dbQuery = this.knex(collection);
|
|
35
|
+
let hasJoins = false;
|
|
36
|
+
if (this.accountability && this.accountability.admin === false) {
|
|
37
|
+
const context = { knex: this.knex, schema: this.schema };
|
|
38
|
+
await validateAccess({
|
|
39
|
+
accountability: this.accountability,
|
|
40
|
+
action: 'read',
|
|
41
|
+
collection,
|
|
42
|
+
}, context);
|
|
43
|
+
const policies = await fetchPolicies(this.accountability, context);
|
|
44
|
+
const permissions = await fetchPermissions({
|
|
45
|
+
action: 'read',
|
|
46
|
+
policies,
|
|
47
|
+
accountability: this.accountability,
|
|
48
|
+
...(collection ? { collections: [collection] } : {}),
|
|
49
|
+
}, context);
|
|
50
|
+
const rules = dedupeAccess(permissions);
|
|
51
|
+
const cases = rules.map(({ rule }) => rule);
|
|
52
|
+
const filter = {
|
|
53
|
+
_or: cases,
|
|
54
|
+
};
|
|
55
|
+
const result = applyFilter(this.knex, this.schema, dbQuery, filter, collection, {}, cases);
|
|
56
|
+
hasJoins = result.hasJoins;
|
|
57
|
+
}
|
|
58
|
+
if (hasJoins) {
|
|
59
|
+
const primaryKeyName = this.schema.collections[collection].primary;
|
|
60
|
+
dbQuery.countDistinct({ count: [`${collection}.${primaryKeyName}`] });
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
dbQuery.count('*', { as: 'count' });
|
|
40
64
|
}
|
|
41
|
-
const result = await dbQuery;
|
|
65
|
+
const result = await dbQuery.first();
|
|
42
66
|
return Number(result?.count ?? 0);
|
|
43
67
|
}
|
|
44
68
|
async filterCount(collection, query) {
|
|
45
69
|
const dbQuery = this.knex(collection);
|
|
46
70
|
let filter = query.filter || {};
|
|
47
71
|
let hasJoins = false;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
72
|
+
let cases = [];
|
|
73
|
+
if (this.accountability && this.accountability.admin === false) {
|
|
74
|
+
const context = { knex: this.knex, schema: this.schema };
|
|
75
|
+
await validateAccess({
|
|
76
|
+
accountability: this.accountability,
|
|
77
|
+
action: 'read',
|
|
78
|
+
collection,
|
|
79
|
+
}, context);
|
|
80
|
+
const policies = await fetchPolicies(this.accountability, context);
|
|
81
|
+
const permissions = await fetchPermissions({
|
|
82
|
+
action: 'read',
|
|
83
|
+
policies,
|
|
84
|
+
accountability: this.accountability,
|
|
85
|
+
...(collection ? { collections: [collection] } : {}),
|
|
86
|
+
}, context);
|
|
87
|
+
const rules = dedupeAccess(permissions);
|
|
88
|
+
cases = rules.map(({ rule }) => rule);
|
|
89
|
+
const permissionsFilter = {
|
|
90
|
+
_or: cases,
|
|
91
|
+
};
|
|
55
92
|
if (Object.keys(filter).length > 0) {
|
|
56
|
-
filter = { _and: [
|
|
93
|
+
filter = { _and: [permissionsFilter, filter] };
|
|
57
94
|
}
|
|
58
95
|
else {
|
|
59
|
-
filter =
|
|
96
|
+
filter = permissionsFilter;
|
|
60
97
|
}
|
|
61
98
|
}
|
|
62
99
|
if (Object.keys(filter).length > 0) {
|
|
63
|
-
({ hasJoins } = applyFilter(this.knex, this.schema, dbQuery, filter, collection, {}));
|
|
100
|
+
({ hasJoins } = applyFilter(this.knex, this.schema, dbQuery, filter, collection, {}, cases));
|
|
64
101
|
}
|
|
65
102
|
if (query.search) {
|
|
66
103
|
applySearch(this.knex, this.schema, dbQuery, query.search, collection);
|
|
@@ -72,7 +109,7 @@ export class MetaService {
|
|
|
72
109
|
else {
|
|
73
110
|
dbQuery.count('*', { as: 'count' });
|
|
74
111
|
}
|
|
75
|
-
const
|
|
76
|
-
return Number(
|
|
112
|
+
const result = await dbQuery.first();
|
|
113
|
+
return Number(result?.count ?? 0);
|
|
77
114
|
}
|
|
78
115
|
}
|
|
@@ -8,6 +8,5 @@ export declare class NotificationsService extends ItemsService {
|
|
|
8
8
|
mailService: MailService;
|
|
9
9
|
constructor(options: AbstractServiceOptions);
|
|
10
10
|
createOne(data: Partial<Notification>, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
11
|
-
createMany(data: Partial<Notification>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
12
11
|
sendEmail(data: Partial<Notification>): Promise<void>;
|
|
13
12
|
}
|
|
@@ -20,13 +20,6 @@ export class NotificationsService extends ItemsService {
|
|
|
20
20
|
await this.sendEmail(data);
|
|
21
21
|
return response;
|
|
22
22
|
}
|
|
23
|
-
async createMany(data, opts) {
|
|
24
|
-
const response = await super.createMany(data, opts);
|
|
25
|
-
for (const notification of data) {
|
|
26
|
-
await this.sendEmail(notification);
|
|
27
|
-
}
|
|
28
|
-
return response;
|
|
29
|
-
}
|
|
30
23
|
async sendEmail(data) {
|
|
31
24
|
if (data.recipient) {
|
|
32
25
|
const user = await this.usersService.readOne(data.recipient, {
|
|
@@ -4,8 +4,6 @@ import { ItemsService } from './items.js';
|
|
|
4
4
|
export declare class OperationsService extends ItemsService<OperationRaw> {
|
|
5
5
|
constructor(options: AbstractServiceOptions);
|
|
6
6
|
createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
7
|
-
createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
8
|
-
updateBatch(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
9
7
|
updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
10
8
|
deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
11
9
|
}
|