@directus/api 21.0.0 → 22.0.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/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/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/{utils/merge-permissions.d.ts → database/migrations/20240806A-permissions-policies.d.ts} +4 -1
- package/dist/database/migrations/20240806A-permissions-policies.js +338 -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/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/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-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/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/package.json +30 -29
- 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
|
@@ -15,6 +15,7 @@ import StreamArray from 'stream-json/streamers/StreamArray.js';
|
|
|
15
15
|
import getDatabase from '../database/index.js';
|
|
16
16
|
import emitter from '../emitter.js';
|
|
17
17
|
import { useLogger } from '../logger/index.js';
|
|
18
|
+
import { validateAccess } from '../permissions/modules/validate-access/validate-access.js';
|
|
18
19
|
import { getDateFormatted } from '../utils/get-date-formatted.js';
|
|
19
20
|
import { getService } from '../utils/get-service.js';
|
|
20
21
|
import { transaction } from '../utils/transaction.js';
|
|
@@ -23,6 +24,7 @@ import { userName } from '../utils/user-name.js';
|
|
|
23
24
|
import { FilesService } from './files.js';
|
|
24
25
|
import { NotificationsService } from './notifications.js';
|
|
25
26
|
import { UsersService } from './users.js';
|
|
27
|
+
import { parseFields } from '../database/get-ast-from-query/lib/parse-fields.js';
|
|
26
28
|
const env = useEnv();
|
|
27
29
|
const logger = useLogger();
|
|
28
30
|
export class ImportService {
|
|
@@ -37,10 +39,23 @@ export class ImportService {
|
|
|
37
39
|
async import(collection, mimetype, stream) {
|
|
38
40
|
if (this.accountability?.admin !== true && isSystemCollection(collection))
|
|
39
41
|
throw new ForbiddenError();
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
if (this.accountability) {
|
|
43
|
+
await validateAccess({
|
|
44
|
+
accountability: this.accountability,
|
|
45
|
+
action: 'create',
|
|
46
|
+
collection,
|
|
47
|
+
}, {
|
|
48
|
+
schema: this.schema,
|
|
49
|
+
knex: this.knex,
|
|
50
|
+
});
|
|
51
|
+
await validateAccess({
|
|
52
|
+
accountability: this.accountability,
|
|
53
|
+
action: 'update',
|
|
54
|
+
collection,
|
|
55
|
+
}, {
|
|
56
|
+
schema: this.schema,
|
|
57
|
+
knex: this.knex,
|
|
58
|
+
});
|
|
44
59
|
}
|
|
45
60
|
switch (mimetype) {
|
|
46
61
|
case 'application/json':
|
|
@@ -248,9 +263,26 @@ export class ExportService {
|
|
|
248
263
|
});
|
|
249
264
|
readCount += result.length;
|
|
250
265
|
if (result.length) {
|
|
266
|
+
let csvHeadings = null;
|
|
267
|
+
if (format === 'csv') {
|
|
268
|
+
if (!query.fields)
|
|
269
|
+
query.fields = ['*'];
|
|
270
|
+
// to ensure the all headings are included in the CSV file, all possible fields need to be determined.
|
|
271
|
+
const parsedFields = await parseFields({
|
|
272
|
+
parentCollection: collection,
|
|
273
|
+
fields: query.fields,
|
|
274
|
+
query: query,
|
|
275
|
+
accountability: this.accountability,
|
|
276
|
+
}, {
|
|
277
|
+
schema: this.schema,
|
|
278
|
+
knex: database,
|
|
279
|
+
});
|
|
280
|
+
csvHeadings = getHeadingsForCsvExport(parsedFields);
|
|
281
|
+
}
|
|
251
282
|
await appendFile(tmpFile.path, this.transform(result, format, {
|
|
252
283
|
includeHeader: batch === 0,
|
|
253
284
|
includeFooter: batch + 1 === batchesRequired,
|
|
285
|
+
fields: csvHeadings,
|
|
254
286
|
}));
|
|
255
287
|
}
|
|
256
288
|
}
|
|
@@ -345,11 +377,12 @@ Your export of ${collection} is ready. <a href="${href}">Click here to view.</a>
|
|
|
345
377
|
if (format === 'csv') {
|
|
346
378
|
if (input.length === 0)
|
|
347
379
|
return '';
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
380
|
+
const transforms = [CSVTransforms.flatten({ separator: '.' })];
|
|
381
|
+
const header = options?.includeHeader !== false;
|
|
382
|
+
const transformOptions = options?.fields
|
|
383
|
+
? { transforms, header, fields: options?.fields }
|
|
384
|
+
: { transforms, header };
|
|
385
|
+
let string = new CSVParser(transformOptions).parse(input);
|
|
353
386
|
if (options?.includeHeader === false) {
|
|
354
387
|
string = '\n' + string;
|
|
355
388
|
}
|
|
@@ -361,3 +394,28 @@ Your export of ${collection} is ready. <a href="${href}">Click here to view.</a>
|
|
|
361
394
|
throw new ServiceUnavailableError({ service: 'export', reason: `Illegal export type used: "${format}"` });
|
|
362
395
|
}
|
|
363
396
|
}
|
|
397
|
+
/*
|
|
398
|
+
* Recursive function to traverse the field nodes, to determine the headings for the CSV export file.
|
|
399
|
+
*
|
|
400
|
+
* Relational nodes which target a single item get expanded, which means that their nested fields get their own column in the csv file.
|
|
401
|
+
* For relational nodes which target a multiple items, the nested field names are not going to be expanded.
|
|
402
|
+
* Instead they will be stored as a single value/cell of the CSV file.
|
|
403
|
+
*/
|
|
404
|
+
export function getHeadingsForCsvExport(nodes, prefix = '') {
|
|
405
|
+
let fieldNames = [];
|
|
406
|
+
if (!nodes)
|
|
407
|
+
return fieldNames;
|
|
408
|
+
nodes.forEach((node) => {
|
|
409
|
+
switch (node.type) {
|
|
410
|
+
case 'field':
|
|
411
|
+
case 'functionField':
|
|
412
|
+
case 'o2m':
|
|
413
|
+
case 'a2o':
|
|
414
|
+
fieldNames.push(prefix ? `${prefix}.${node.fieldKey}` : node.fieldKey);
|
|
415
|
+
break;
|
|
416
|
+
case 'm2o':
|
|
417
|
+
fieldNames = fieldNames.concat(getHeadingsForCsvExport(node.children, prefix ? `${prefix}.${node.fieldKey}` : node.fieldKey));
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
return fieldNames;
|
|
421
|
+
}
|
package/dist/services/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
export * from './access.js';
|
|
1
2
|
export * from './activity.js';
|
|
2
3
|
export * from './assets.js';
|
|
3
4
|
export * from './authentication.js';
|
|
4
|
-
export * from './authorization.js';
|
|
5
5
|
export * from './collections.js';
|
|
6
6
|
export * from './dashboards.js';
|
|
7
7
|
export * from './extensions.js';
|
|
@@ -18,7 +18,8 @@ export * from './notifications.js';
|
|
|
18
18
|
export * from './operations.js';
|
|
19
19
|
export * from './panels.js';
|
|
20
20
|
export * from './payload.js';
|
|
21
|
-
export * from './permissions
|
|
21
|
+
export * from './permissions.js';
|
|
22
|
+
export * from './policies.js';
|
|
22
23
|
export * from './presets.js';
|
|
23
24
|
export * from './relations.js';
|
|
24
25
|
export * from './revisions.js';
|
package/dist/services/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
export * from './access.js';
|
|
1
2
|
export * from './activity.js';
|
|
2
3
|
export * from './assets.js';
|
|
3
4
|
export * from './authentication.js';
|
|
4
|
-
export * from './authorization.js';
|
|
5
5
|
export * from './collections.js';
|
|
6
6
|
export * from './dashboards.js';
|
|
7
7
|
export * from './extensions.js';
|
|
@@ -18,7 +18,8 @@ export * from './notifications.js';
|
|
|
18
18
|
export * from './operations.js';
|
|
19
19
|
export * from './panels.js';
|
|
20
20
|
export * from './payload.js';
|
|
21
|
-
export * from './permissions
|
|
21
|
+
export * from './permissions.js';
|
|
22
|
+
export * from './policies.js';
|
|
22
23
|
export * from './presets.js';
|
|
23
24
|
export * from './relations.js';
|
|
24
25
|
export * from './revisions.js';
|
package/dist/services/items.js
CHANGED
|
@@ -5,15 +5,18 @@ 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 {
|
|
@@ -98,17 +101,13 @@ export class ItemsService {
|
|
|
98
101
|
// that any errors thrown in any nested relational changes will bubble up and cancel the whole
|
|
99
102
|
// update tree
|
|
100
103
|
const primaryKey = await transaction(this.knex, async (trx) => {
|
|
101
|
-
|
|
102
|
-
const payloadService = new PayloadService(this.collection, {
|
|
103
|
-
accountability: this.accountability,
|
|
104
|
-
knex: trx,
|
|
105
|
-
schema: this.schema,
|
|
106
|
-
});
|
|
107
|
-
const authorizationService = new AuthorizationService({
|
|
104
|
+
const serviceOptions = {
|
|
108
105
|
accountability: this.accountability,
|
|
109
106
|
knex: trx,
|
|
110
107
|
schema: this.schema,
|
|
111
|
-
}
|
|
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);
|
|
112
111
|
// Run all hooks that are attached to this event so the end user has the chance to augment the
|
|
113
112
|
// item that is about to be saved
|
|
114
113
|
const payloadAfterHooks = opts.emitEvents !== false
|
|
@@ -123,13 +122,21 @@ export class ItemsService {
|
|
|
123
122
|
})
|
|
124
123
|
: payload;
|
|
125
124
|
const payloadWithPresets = this.accountability
|
|
126
|
-
?
|
|
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
|
+
})
|
|
127
134
|
: payloadAfterHooks;
|
|
128
135
|
if (opts.preMutationError) {
|
|
129
136
|
throw opts.preMutationError;
|
|
130
137
|
}
|
|
131
|
-
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
132
|
-
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);
|
|
133
140
|
const payloadWithoutAliases = pick(payloadWithA2O, without(fields, ...aliases));
|
|
134
141
|
const payloadWithTypeCasting = await payloadService.processValues('create', payloadWithoutAliases);
|
|
135
142
|
// The primary key can already exist in the payload.
|
|
@@ -187,10 +194,22 @@ export class ItemsService {
|
|
|
187
194
|
}
|
|
188
195
|
// At this point, the primary key is guaranteed to be set.
|
|
189
196
|
primaryKey = primaryKey;
|
|
190
|
-
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);
|
|
191
198
|
nestedActionEvents.push(...nestedActionEventsM2O);
|
|
192
199
|
nestedActionEvents.push(...nestedActionEventsA2O);
|
|
193
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
|
+
}
|
|
194
213
|
// If this is an authenticated action, and accountability tracking is enabled, save activity row
|
|
195
214
|
if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
|
|
196
215
|
const activityService = new ActivityService({
|
|
@@ -281,6 +300,7 @@ export class ItemsService {
|
|
|
281
300
|
opts.mutationTracker = this.createMutationTracker();
|
|
282
301
|
const { primaryKeys, nestedActionEvents } = await transaction(this.knex, async (knex) => {
|
|
283
302
|
const service = this.fork({ knex });
|
|
303
|
+
let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None;
|
|
284
304
|
const primaryKeys = [];
|
|
285
305
|
const nestedActionEvents = [];
|
|
286
306
|
const pkField = this.schema.collections[this.collection].primary;
|
|
@@ -294,12 +314,21 @@ export class ItemsService {
|
|
|
294
314
|
const primaryKey = await service.createOne(payload, {
|
|
295
315
|
...(opts || {}),
|
|
296
316
|
autoPurgeCache: false,
|
|
317
|
+
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
297
318
|
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
298
319
|
mutationTracker: opts.mutationTracker,
|
|
299
320
|
bypassAutoIncrementSequenceReset,
|
|
300
321
|
});
|
|
301
322
|
primaryKeys.push(primaryKey);
|
|
302
323
|
}
|
|
324
|
+
if (userIntegrityCheckFlags) {
|
|
325
|
+
if (opts.onRequireUserIntegrityCheck) {
|
|
326
|
+
opts.onRequireUserIntegrityCheck(userIntegrityCheckFlags);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
await validateUserCountIntegrity({ flags: userIntegrityCheckFlags, knex });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
303
332
|
return { primaryKeys, nestedActionEvents };
|
|
304
333
|
});
|
|
305
334
|
if (opts.emitEvents !== false) {
|
|
@@ -332,27 +361,21 @@ export class ItemsService {
|
|
|
332
361
|
accountability: this.accountability,
|
|
333
362
|
})
|
|
334
363
|
: query;
|
|
335
|
-
let ast = await
|
|
364
|
+
let ast = await getAstFromQuery({
|
|
365
|
+
collection: this.collection,
|
|
366
|
+
query: updatedQuery,
|
|
336
367
|
accountability: this.accountability,
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// access to (a) certain item(s)
|
|
340
|
-
action: opts?.permissionsAction || 'read',
|
|
368
|
+
}, {
|
|
369
|
+
schema: this.schema,
|
|
341
370
|
knex: this.knex,
|
|
342
371
|
});
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
accountability: this.accountability,
|
|
346
|
-
knex: this.knex,
|
|
347
|
-
schema: this.schema,
|
|
348
|
-
});
|
|
349
|
-
ast = await authorizationService.processAST(ast, opts?.permissionsAction);
|
|
350
|
-
}
|
|
351
|
-
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, {
|
|
352
374
|
knex: this.knex,
|
|
353
375
|
// GraphQL requires relational keys to be returned regardless
|
|
354
376
|
stripNonRequested: opts?.stripNonRequested !== undefined ? opts.stripNonRequested : true,
|
|
355
377
|
});
|
|
378
|
+
// TODO when would this happen?
|
|
356
379
|
if (records === null) {
|
|
357
380
|
throw new ForbiddenError();
|
|
358
381
|
}
|
|
@@ -450,13 +473,26 @@ export class ItemsService {
|
|
|
450
473
|
try {
|
|
451
474
|
await transaction(this.knex, async (knex) => {
|
|
452
475
|
const service = this.fork({ knex });
|
|
476
|
+
let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None;
|
|
453
477
|
for (const item of data) {
|
|
454
478
|
const primaryKey = item[primaryKeyField];
|
|
455
479
|
if (!primaryKey)
|
|
456
480
|
throw new InvalidPayloadError({ reason: `Item in update misses primary key` });
|
|
457
|
-
const combinedOpts =
|
|
481
|
+
const combinedOpts = {
|
|
482
|
+
autoPurgeCache: false,
|
|
483
|
+
...opts,
|
|
484
|
+
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
485
|
+
};
|
|
458
486
|
keys.push(await service.updateOne(primaryKey, omit(item, primaryKeyField), combinedOpts));
|
|
459
487
|
}
|
|
488
|
+
if (userIntegrityCheckFlags) {
|
|
489
|
+
if (opts.onRequireUserIntegrityCheck) {
|
|
490
|
+
opts.onRequireUserIntegrityCheck(userIntegrityCheckFlags);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
await validateUserCountIntegrity({ flags: userIntegrityCheckFlags, knex });
|
|
494
|
+
}
|
|
495
|
+
}
|
|
460
496
|
});
|
|
461
497
|
}
|
|
462
498
|
finally {
|
|
@@ -485,11 +521,6 @@ export class ItemsService {
|
|
|
485
521
|
.map((field) => field.field);
|
|
486
522
|
const payload = cloneDeep(data);
|
|
487
523
|
const nestedActionEvents = [];
|
|
488
|
-
const authorizationService = new AuthorizationService({
|
|
489
|
-
accountability: this.accountability,
|
|
490
|
-
knex: this.knex,
|
|
491
|
-
schema: this.schema,
|
|
492
|
-
});
|
|
493
524
|
// Run all hooks that are attached to this event so the end user has the chance to augment the
|
|
494
525
|
// item that is about to be saved
|
|
495
526
|
const payloadAfterHooks = opts.emitEvents !== false
|
|
@@ -507,10 +538,26 @@ export class ItemsService {
|
|
|
507
538
|
// Sort keys to ensure that the order is maintained
|
|
508
539
|
keys.sort();
|
|
509
540
|
if (this.accountability) {
|
|
510
|
-
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
|
+
});
|
|
511
550
|
}
|
|
512
551
|
const payloadWithPresets = this.accountability
|
|
513
|
-
?
|
|
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
|
+
})
|
|
514
561
|
: payloadAfterHooks;
|
|
515
562
|
if (opts.preMutationError) {
|
|
516
563
|
throw opts.preMutationError;
|
|
@@ -521,8 +568,8 @@ export class ItemsService {
|
|
|
521
568
|
knex: trx,
|
|
522
569
|
schema: this.schema,
|
|
523
570
|
});
|
|
524
|
-
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
525
|
-
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);
|
|
526
573
|
const payloadWithoutAliasAndPK = pick(payloadWithA2O, without(fields, primaryKeyField, ...aliases));
|
|
527
574
|
const payloadWithTypeCasting = await payloadService.processValues('update', payloadWithoutAliasAndPK);
|
|
528
575
|
if (Object.keys(payloadWithTypeCasting).length > 0) {
|
|
@@ -534,12 +581,25 @@ export class ItemsService {
|
|
|
534
581
|
}
|
|
535
582
|
}
|
|
536
583
|
const childrenRevisions = [...revisionsM2O, ...revisionsA2O];
|
|
584
|
+
let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ??
|
|
585
|
+
UserIntegrityCheckFlag.None | userIntegrityCheckFlagsM2O | userIntegrityCheckFlagsA2O;
|
|
537
586
|
nestedActionEvents.push(...nestedActionEventsM2O);
|
|
538
587
|
nestedActionEvents.push(...nestedActionEventsA2O);
|
|
539
588
|
for (const key of keys) {
|
|
540
|
-
const { revisions, nestedActionEvents: nestedActionEventsO2M } = await payloadService.processO2M(payloadWithA2O, key, opts);
|
|
589
|
+
const { revisions, nestedActionEvents: nestedActionEventsO2M, userIntegrityCheckFlags: userIntegrityCheckFlagsO2M, } = await payloadService.processO2M(payloadWithA2O, key, opts);
|
|
541
590
|
childrenRevisions.push(...revisions);
|
|
542
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
|
+
}
|
|
543
603
|
}
|
|
544
604
|
// If this is an authenticated action, and accountability tracking is enabled, save activity row
|
|
545
605
|
if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
|
|
@@ -708,13 +768,16 @@ export class ItemsService {
|
|
|
708
768
|
const { ActivityService } = await import('./activity.js');
|
|
709
769
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
710
770
|
validateKeys(this.schema, this.collection, primaryKeyField, keys);
|
|
711
|
-
if (this.accountability
|
|
712
|
-
|
|
771
|
+
if (this.accountability) {
|
|
772
|
+
await validateAccess({
|
|
713
773
|
accountability: this.accountability,
|
|
714
|
-
|
|
774
|
+
action: 'delete',
|
|
775
|
+
collection: this.collection,
|
|
776
|
+
primaryKeys: keys,
|
|
777
|
+
}, {
|
|
715
778
|
knex: this.knex,
|
|
779
|
+
schema: this.schema,
|
|
716
780
|
});
|
|
717
|
-
await authorizationService.checkAccess('delete', this.collection, keys);
|
|
718
781
|
}
|
|
719
782
|
if (opts.preMutationError) {
|
|
720
783
|
throw opts.preMutationError;
|
|
@@ -730,6 +793,14 @@ export class ItemsService {
|
|
|
730
793
|
}
|
|
731
794
|
await transaction(this.knex, async (trx) => {
|
|
732
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
|
+
}
|
|
733
804
|
if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
|
|
734
805
|
const activityService = new ActivityService({
|
|
735
806
|
knex: trx,
|
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
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
2
|
import { useLogger } from '../logger/index.js';
|
|
3
|
+
import { fetchRolesTree } from '../permissions/lib/fetch-roles-tree.js';
|
|
4
|
+
import { fetchGlobalAccess } from '../permissions/modules/fetch-global-access/fetch-global-access.js';
|
|
3
5
|
import { md } from '../utils/md.js';
|
|
4
6
|
import { Url } from '../utils/url.js';
|
|
5
7
|
import { ItemsService } from './items.js';
|
|
@@ -23,18 +25,24 @@ export class NotificationsService extends ItemsService {
|
|
|
23
25
|
async sendEmail(data) {
|
|
24
26
|
if (data.recipient) {
|
|
25
27
|
const user = await this.usersService.readOne(data.recipient, {
|
|
26
|
-
fields: ['id', 'email', 'email_notifications', 'role
|
|
28
|
+
fields: ['id', 'email', 'email_notifications', 'role'],
|
|
27
29
|
});
|
|
28
|
-
const manageUserAccountUrl = new Url(env['PUBLIC_URL'])
|
|
29
|
-
.addPath('admin', 'users', user['id'])
|
|
30
|
-
.toString();
|
|
31
|
-
const html = data.message ? md(data.message) : '';
|
|
32
30
|
if (user['email'] && user['email_notifications'] === true) {
|
|
31
|
+
const manageUserAccountUrl = new Url(env['PUBLIC_URL'])
|
|
32
|
+
.addPath('admin', 'users', user['id'])
|
|
33
|
+
.toString();
|
|
34
|
+
const html = data.message ? md(data.message) : '';
|
|
35
|
+
const roles = await fetchRolesTree(user['role'], this.knex);
|
|
36
|
+
const { app: app_access } = await fetchGlobalAccess({
|
|
37
|
+
user: user['id'],
|
|
38
|
+
roles,
|
|
39
|
+
ip: null,
|
|
40
|
+
}, this.knex);
|
|
33
41
|
this.mailService
|
|
34
42
|
.send({
|
|
35
43
|
template: {
|
|
36
44
|
name: 'base',
|
|
37
|
-
data:
|
|
45
|
+
data: app_access ? { url: manageUserAccountUrl, html } : { html },
|
|
38
46
|
},
|
|
39
47
|
to: user['email'],
|
|
40
48
|
subject: data.subject,
|
|
@@ -2,6 +2,7 @@ import type { Accountability, Item, PrimaryKey, SchemaOverview } from '@directus
|
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
3
|
import type { Helpers } from '../database/helpers/index.js';
|
|
4
4
|
import type { AbstractServiceOptions, ActionEventParams, MutationOptions } from '../types/index.js';
|
|
5
|
+
import { UserIntegrityCheckFlag } from '../utils/validate-user-count-integrity.js';
|
|
5
6
|
type Action = 'create' | 'read' | 'update';
|
|
6
7
|
type Transformers = {
|
|
7
8
|
[type: string]: (context: {
|
|
@@ -13,6 +14,11 @@ type Transformers = {
|
|
|
13
14
|
helpers: Helpers;
|
|
14
15
|
}) => Promise<any>;
|
|
15
16
|
};
|
|
17
|
+
type PayloadServiceProcessRelationResult = {
|
|
18
|
+
revisions: PrimaryKey[];
|
|
19
|
+
nestedActionEvents: ActionEventParams[];
|
|
20
|
+
userIntegrityCheckFlags: UserIntegrityCheckFlag;
|
|
21
|
+
};
|
|
16
22
|
/**
|
|
17
23
|
* Process a given payload for a collection to ensure the special fields (hash, uuid, date etc) are
|
|
18
24
|
* handled correctly.
|
|
@@ -46,26 +52,19 @@ export declare class PayloadService {
|
|
|
46
52
|
/**
|
|
47
53
|
* Recursively save/update all nested related Any-to-One items
|
|
48
54
|
*/
|
|
49
|
-
processA2O(data: Partial<Item>, opts?: MutationOptions): Promise<{
|
|
55
|
+
processA2O(data: Partial<Item>, opts?: MutationOptions): Promise<PayloadServiceProcessRelationResult & {
|
|
50
56
|
payload: Partial<Item>;
|
|
51
|
-
revisions: PrimaryKey[];
|
|
52
|
-
nestedActionEvents: ActionEventParams[];
|
|
53
57
|
}>;
|
|
54
58
|
/**
|
|
55
59
|
* Save/update all nested related m2o items inside the payload
|
|
56
60
|
*/
|
|
57
|
-
processM2O(data: Partial<Item>, opts?: MutationOptions): Promise<{
|
|
61
|
+
processM2O(data: Partial<Item>, opts?: MutationOptions): Promise<PayloadServiceProcessRelationResult & {
|
|
58
62
|
payload: Partial<Item>;
|
|
59
|
-
revisions: PrimaryKey[];
|
|
60
|
-
nestedActionEvents: ActionEventParams[];
|
|
61
63
|
}>;
|
|
62
64
|
/**
|
|
63
65
|
* Recursively save/update all nested related o2m items
|
|
64
66
|
*/
|
|
65
|
-
processO2M(data: Partial<Item>, parent: PrimaryKey, opts?: MutationOptions): Promise<
|
|
66
|
-
revisions: PrimaryKey[];
|
|
67
|
-
nestedActionEvents: ActionEventParams[];
|
|
68
|
-
}>;
|
|
67
|
+
processO2M(data: Partial<Item>, parent: PrimaryKey, opts?: MutationOptions): Promise<PayloadServiceProcessRelationResult>;
|
|
69
68
|
/**
|
|
70
69
|
* Transforms the input partial payload to match the output structure, to have consistency
|
|
71
70
|
* between delta and data
|