@directus/api 11.0.1 → 12.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 +3 -4
- package/dist/auth/auth.d.ts +4 -4
- package/dist/auth/auth.js +2 -2
- package/dist/auth/drivers/ldap.js +20 -17
- package/dist/auth/drivers/local.js +5 -5
- package/dist/auth/drivers/oauth2.js +16 -16
- package/dist/auth/drivers/openid.js +18 -17
- package/dist/auth/drivers/saml.js +6 -7
- package/dist/auth.js +3 -2
- package/dist/cache.js +3 -13
- package/dist/cli/utils/create-env/env-stub.liquid +5 -7
- package/dist/controllers/activity.js +7 -6
- package/dist/controllers/assets.js +25 -12
- package/dist/controllers/auth.js +8 -7
- package/dist/controllers/collections.js +4 -3
- package/dist/controllers/dashboards.js +5 -4
- package/dist/controllers/extensions.js +3 -3
- package/dist/controllers/fields.js +9 -8
- package/dist/controllers/files.js +11 -11
- package/dist/controllers/flows.js +5 -4
- package/dist/controllers/folders.js +5 -4
- package/dist/controllers/items.js +14 -13
- package/dist/controllers/not-found.js +2 -2
- package/dist/controllers/notifications.js +5 -4
- package/dist/controllers/operations.js +5 -4
- package/dist/controllers/panels.js +5 -4
- package/dist/controllers/permissions.js +5 -4
- package/dist/controllers/presets.js +5 -4
- package/dist/controllers/relations.js +6 -5
- package/dist/controllers/roles.js +5 -4
- package/dist/controllers/schema.js +8 -8
- package/dist/controllers/server.js +2 -2
- package/dist/controllers/settings.js +3 -2
- package/dist/controllers/shares.js +7 -6
- package/dist/controllers/translations.js +6 -5
- package/dist/controllers/users.js +22 -21
- package/dist/controllers/utils.js +10 -10
- package/dist/controllers/webhooks.js +5 -4
- package/dist/{exceptions/database → database/errors}/dialects/mssql.js +8 -18
- package/dist/{exceptions/database → database/errors}/dialects/mysql.js +9 -19
- package/dist/{exceptions/database → database/errors}/dialects/oracle.js +2 -2
- package/dist/{exceptions/database → database/errors}/dialects/postgres.js +7 -18
- package/dist/{exceptions/database → database/errors}/dialects/sqlite.js +7 -10
- package/dist/{exceptions/database → database/errors}/translate.js +1 -1
- package/dist/database/migrations/run.js +10 -1
- package/dist/emitter.d.ts +3 -2
- package/dist/emitter.js +12 -4
- package/dist/env.js +21 -17
- package/dist/errors/codes.d.ts +29 -0
- package/dist/errors/codes.js +30 -0
- package/dist/errors/contains-null-values.d.ts +7 -0
- package/dist/errors/contains-null-values.js +4 -0
- package/dist/errors/content-too-large.d.ts +1 -0
- package/dist/errors/content-too-large.js +3 -0
- package/dist/errors/forbidden.d.ts +1 -0
- package/dist/errors/forbidden.js +3 -0
- package/dist/errors/hit-rate-limit.d.ts +6 -0
- package/dist/errors/hit-rate-limit.js +8 -0
- package/dist/errors/illegal-asset-transformation.d.ts +4 -0
- package/dist/errors/illegal-asset-transformation.js +3 -0
- package/dist/errors/index.d.ts +28 -0
- package/dist/errors/index.js +28 -0
- package/dist/errors/invalid-credentials.d.ts +1 -0
- package/dist/errors/invalid-credentials.js +3 -0
- package/dist/errors/invalid-foreign-key.d.ts +6 -0
- package/dist/errors/invalid-foreign-key.js +14 -0
- package/dist/errors/invalid-ip.d.ts +1 -0
- package/dist/errors/invalid-ip.js +3 -0
- package/dist/errors/invalid-otp.d.ts +1 -0
- package/dist/errors/invalid-otp.js +3 -0
- package/dist/errors/invalid-payload.d.ts +5 -0
- package/dist/errors/invalid-payload.js +4 -0
- package/dist/errors/invalid-provider-config.d.ts +5 -0
- package/dist/errors/invalid-provider-config.js +3 -0
- package/dist/errors/invalid-provider.d.ts +1 -0
- package/dist/errors/invalid-provider.js +3 -0
- package/dist/errors/invalid-query.d.ts +5 -0
- package/dist/errors/invalid-query.js +4 -0
- package/dist/errors/invalid-token.d.ts +1 -0
- package/dist/errors/invalid-token.js +3 -0
- package/dist/errors/method-not-allowed.d.ts +6 -0
- package/dist/errors/method-not-allowed.js +6 -0
- package/dist/errors/not-null-violation.d.ts +6 -0
- package/dist/errors/not-null-violation.js +14 -0
- package/dist/errors/range-not-satisfiable.d.ts +7 -0
- package/dist/errors/range-not-satisfiable.js +7 -0
- package/dist/errors/record-not-unique.d.ts +6 -0
- package/dist/errors/record-not-unique.js +14 -0
- package/dist/errors/route-not-found.d.ts +5 -0
- package/dist/errors/route-not-found.js +4 -0
- package/dist/errors/service-unavailable.d.ts +7 -0
- package/dist/errors/service-unavailable.js +4 -0
- package/dist/errors/token-expired.d.ts +1 -0
- package/dist/errors/token-expired.js +3 -0
- package/dist/errors/unexpected-response.d.ts +1 -0
- package/dist/errors/unexpected-response.js +3 -0
- package/dist/errors/unprocessable-content.d.ts +5 -0
- package/dist/errors/unprocessable-content.js +4 -0
- package/dist/errors/unsupported-media-type.d.ts +6 -0
- package/dist/errors/unsupported-media-type.js +4 -0
- package/dist/errors/user-suspended.d.ts +1 -0
- package/dist/errors/user-suspended.js +3 -0
- package/dist/errors/value-out-of-range.d.ts +6 -0
- package/dist/errors/value-out-of-range.js +14 -0
- package/dist/errors/value-too-long.d.ts +6 -0
- package/dist/errors/value-too-long.js +14 -0
- package/dist/extensions.js +0 -4
- package/dist/flows.js +6 -8
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/messenger.d.ts +3 -3
- package/dist/messenger.js +18 -9
- package/dist/middleware/authenticate.js +2 -38
- package/dist/middleware/check-ip.js +2 -2
- package/dist/middleware/collection-exists.js +2 -2
- package/dist/middleware/error-handler.js +7 -7
- package/dist/middleware/graphql.js +11 -9
- package/dist/middleware/rate-limiter-global.d.ts +2 -2
- package/dist/middleware/rate-limiter-global.js +2 -3
- package/dist/middleware/rate-limiter-ip.d.ts +2 -2
- package/dist/middleware/rate-limiter-ip.js +2 -3
- package/dist/middleware/validate-batch.js +3 -4
- package/dist/rate-limiter.js +2 -9
- package/dist/server.js +10 -0
- package/dist/services/activity.js +3 -2
- package/dist/services/assets.js +9 -10
- package/dist/services/authentication.js +12 -11
- package/dist/services/authorization.d.ts +1 -1
- package/dist/services/authorization.js +16 -16
- package/dist/services/collections.js +17 -16
- package/dist/services/fields.js +16 -14
- package/dist/services/files.js +7 -6
- package/dist/services/graphql/errors/execution.d.ts +6 -0
- package/dist/services/graphql/errors/execution.js +2 -0
- package/dist/services/graphql/errors/index.d.ts +2 -0
- package/dist/services/graphql/errors/index.js +2 -0
- package/dist/services/graphql/errors/validation.d.ts +6 -0
- package/dist/services/graphql/errors/validation.js +2 -0
- package/dist/services/graphql/index.d.ts +2 -8
- package/dist/services/graphql/index.js +125 -66
- package/dist/services/graphql/subscription.d.ts +16 -0
- package/dist/services/graphql/subscription.js +77 -0
- package/dist/services/graphql/utils/process-error.js +3 -3
- package/dist/services/import-export.js +7 -7
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/items.js +14 -13
- package/dist/services/mail/index.js +3 -3
- package/dist/services/meta.js +3 -3
- package/dist/services/payload.js +11 -7
- package/dist/services/relations.js +32 -22
- package/dist/services/revisions.js +3 -3
- package/dist/services/roles.js +10 -9
- package/dist/services/schema.js +5 -5
- package/dist/services/server.js +24 -0
- package/dist/services/shares.js +4 -4
- package/dist/services/tfa.js +6 -6
- package/dist/services/translations.d.ts +2 -2
- package/dist/services/translations.js +4 -4
- package/dist/services/users.js +26 -29
- package/dist/services/utils.js +4 -4
- package/dist/services/websocket.d.ts +14 -0
- package/dist/services/websocket.js +26 -0
- package/dist/synchronization.js +3 -3
- package/dist/types/items.d.ts +2 -2
- package/dist/utils/apply-diff.js +13 -4
- package/dist/utils/apply-query.js +22 -13
- package/dist/utils/get-accountability-for-role.js +1 -2
- package/dist/utils/get-accountability-for-token.d.ts +2 -0
- package/dist/utils/get-accountability-for-token.js +50 -0
- package/dist/utils/get-column-path.js +5 -3
- package/dist/utils/get-column.js +3 -3
- package/dist/utils/get-service.d.ts +7 -0
- package/dist/utils/get-service.js +49 -0
- package/dist/utils/jwt.js +5 -5
- package/dist/utils/redact.d.ts +4 -0
- package/dist/utils/redact.js +15 -1
- package/dist/utils/to-boolean.d.ts +4 -0
- package/dist/utils/to-boolean.js +6 -0
- package/dist/utils/validate-diff.js +23 -9
- package/dist/utils/validate-keys.js +3 -3
- package/dist/utils/validate-query.d.ts +2 -0
- package/dist/utils/validate-query.js +27 -21
- package/dist/utils/validate-snapshot.js +11 -5
- package/dist/websocket/authenticate.d.ts +6 -0
- package/dist/websocket/authenticate.js +59 -0
- package/dist/websocket/controllers/base.d.ts +42 -0
- package/dist/websocket/controllers/base.js +279 -0
- package/dist/websocket/controllers/graphql.d.ts +12 -0
- package/dist/websocket/controllers/graphql.js +102 -0
- package/dist/websocket/controllers/hooks.d.ts +1 -0
- package/dist/websocket/controllers/hooks.js +122 -0
- package/dist/websocket/controllers/index.d.ts +10 -0
- package/dist/websocket/controllers/index.js +31 -0
- package/dist/websocket/controllers/rest.d.ts +9 -0
- package/dist/websocket/controllers/rest.js +47 -0
- package/dist/websocket/errors.d.ts +16 -0
- package/dist/websocket/errors.js +55 -0
- package/dist/websocket/handlers/heartbeat.d.ts +11 -0
- package/dist/websocket/handlers/heartbeat.js +72 -0
- package/dist/websocket/handlers/index.d.ts +4 -0
- package/dist/websocket/handlers/index.js +11 -0
- package/dist/websocket/handlers/items.d.ts +6 -0
- package/dist/websocket/handlers/items.js +103 -0
- package/dist/websocket/handlers/subscribe.d.ts +43 -0
- package/dist/websocket/handlers/subscribe.js +278 -0
- package/dist/websocket/messages.d.ts +311 -0
- package/dist/websocket/messages.js +96 -0
- package/dist/websocket/types.d.ts +34 -0
- package/dist/websocket/types.js +1 -0
- package/dist/websocket/utils/get-expires-at-for-token.d.ts +1 -0
- package/dist/websocket/utils/get-expires-at-for-token.js +8 -0
- package/dist/websocket/utils/message.d.ts +4 -0
- package/dist/websocket/utils/message.js +27 -0
- package/dist/websocket/utils/wait-for-message.d.ts +4 -0
- package/dist/websocket/utils/wait-for-message.js +45 -0
- package/package.json +21 -16
- package/dist/exceptions/content-too-large.d.ts +0 -4
- package/dist/exceptions/content-too-large.js +0 -6
- package/dist/exceptions/database/contains-null-values.d.ts +0 -9
- package/dist/exceptions/database/contains-null-values.js +0 -6
- package/dist/exceptions/database/invalid-foreign-key.d.ts +0 -10
- package/dist/exceptions/database/invalid-foreign-key.js +0 -11
- package/dist/exceptions/database/not-null-violation.d.ts +0 -9
- package/dist/exceptions/database/not-null-violation.js +0 -6
- package/dist/exceptions/database/record-not-unique.d.ts +0 -10
- package/dist/exceptions/database/record-not-unique.js +0 -11
- package/dist/exceptions/database/value-out-of-range.d.ts +0 -10
- package/dist/exceptions/database/value-out-of-range.js +0 -11
- package/dist/exceptions/database/value-too-long.d.ts +0 -9
- package/dist/exceptions/database/value-too-long.js +0 -11
- package/dist/exceptions/forbidden.d.ts +0 -6
- package/dist/exceptions/forbidden.js +0 -13
- package/dist/exceptions/graphql-validation.d.ts +0 -4
- package/dist/exceptions/graphql-validation.js +0 -6
- package/dist/exceptions/hit-rate-limit.d.ts +0 -9
- package/dist/exceptions/hit-rate-limit.js +0 -6
- package/dist/exceptions/illegal-asset-transformation.d.ts +0 -4
- package/dist/exceptions/illegal-asset-transformation.js +0 -6
- package/dist/exceptions/index.d.ts +0 -21
- package/dist/exceptions/index.js +0 -21
- package/dist/exceptions/invalid-config.d.ts +0 -4
- package/dist/exceptions/invalid-config.js +0 -6
- package/dist/exceptions/invalid-credentials.d.ts +0 -4
- package/dist/exceptions/invalid-credentials.js +0 -6
- package/dist/exceptions/invalid-ip.d.ts +0 -4
- package/dist/exceptions/invalid-ip.js +0 -6
- package/dist/exceptions/invalid-otp.d.ts +0 -4
- package/dist/exceptions/invalid-otp.js +0 -6
- package/dist/exceptions/invalid-payload.d.ts +0 -4
- package/dist/exceptions/invalid-payload.js +0 -6
- package/dist/exceptions/invalid-provider.d.ts +0 -4
- package/dist/exceptions/invalid-provider.js +0 -6
- package/dist/exceptions/invalid-query.d.ts +0 -4
- package/dist/exceptions/invalid-query.js +0 -6
- package/dist/exceptions/invalid-token.d.ts +0 -4
- package/dist/exceptions/invalid-token.js +0 -6
- package/dist/exceptions/method-not-allowed.d.ts +0 -8
- package/dist/exceptions/method-not-allowed.js +0 -6
- package/dist/exceptions/range-not-satisfiable.d.ts +0 -5
- package/dist/exceptions/range-not-satisfiable.js +0 -9
- package/dist/exceptions/route-not-found.d.ts +0 -4
- package/dist/exceptions/route-not-found.js +0 -6
- package/dist/exceptions/service-unavailable.d.ts +0 -9
- package/dist/exceptions/service-unavailable.js +0 -6
- package/dist/exceptions/token-expired.d.ts +0 -4
- package/dist/exceptions/token-expired.js +0 -6
- package/dist/exceptions/unexpected-response.d.ts +0 -4
- package/dist/exceptions/unexpected-response.js +0 -6
- package/dist/exceptions/unprocessable-entity.d.ts +0 -4
- package/dist/exceptions/unprocessable-entity.js +0 -6
- package/dist/exceptions/unsupported-media-type.d.ts +0 -4
- package/dist/exceptions/unsupported-media-type.js +0 -6
- package/dist/exceptions/user-suspended.d.ts +0 -4
- package/dist/exceptions/user-suspended.js +0 -6
- /package/dist/{exceptions/database → database/errors}/dialects/mssql.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/mysql.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/oracle.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/postgres.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/sqlite.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/types.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/types.js +0 -0
- /package/dist/{exceptions/database → database/errors}/translate.d.ts +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Action, FUNCTIONS } from '@directus/constants';
|
|
2
|
+
import { isDirectusError } from '@directus/errors';
|
|
2
3
|
import { parseFilterFunctionPath } from '@directus/utils';
|
|
3
4
|
import argon2 from 'argon2';
|
|
4
5
|
import { GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLID, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType, NoSchemaIntrospectionCustomRule, execute, specifiedRules, validate, } from 'graphql';
|
|
@@ -8,37 +9,30 @@ import { clearSystemCache, getCache } from '../../cache.js';
|
|
|
8
9
|
import { DEFAULT_AUTH_PROVIDER, GENERATE_SPECIAL } from '../../constants.js';
|
|
9
10
|
import getDatabase from '../../database/index.js';
|
|
10
11
|
import env from '../../env.js';
|
|
11
|
-
import {
|
|
12
|
+
import { ErrorCode, ForbiddenError, InvalidPayloadError } from '../../errors/index.js';
|
|
12
13
|
import { getExtensionManager } from '../../extensions.js';
|
|
13
14
|
import { generateHash } from '../../utils/generate-hash.js';
|
|
14
15
|
import { getGraphQLType } from '../../utils/get-graphql-type.js';
|
|
15
16
|
import { getMilliseconds } from '../../utils/get-milliseconds.js';
|
|
17
|
+
import { getService } from '../../utils/get-service.js';
|
|
16
18
|
import { reduceSchema } from '../../utils/reduce-schema.js';
|
|
17
19
|
import { sanitizeQuery } from '../../utils/sanitize-query.js';
|
|
20
|
+
import { toBoolean } from '../../utils/to-boolean.js';
|
|
18
21
|
import { validateQuery } from '../../utils/validate-query.js';
|
|
19
22
|
import { ActivityService } from '../activity.js';
|
|
20
23
|
import { AuthenticationService } from '../authentication.js';
|
|
21
24
|
import { CollectionsService } from '../collections.js';
|
|
22
25
|
import { FieldsService } from '../fields.js';
|
|
23
26
|
import { FilesService } from '../files.js';
|
|
24
|
-
import { FlowsService } from '../flows.js';
|
|
25
|
-
import { FoldersService } from '../folders.js';
|
|
26
|
-
import { ItemsService } from '../items.js';
|
|
27
|
-
import { NotificationsService } from '../notifications.js';
|
|
28
|
-
import { OperationsService } from '../operations.js';
|
|
29
|
-
import { PermissionsService } from '../permissions.js';
|
|
30
|
-
import { PresetsService } from '../presets.js';
|
|
31
27
|
import { RelationsService } from '../relations.js';
|
|
32
28
|
import { RevisionsService } from '../revisions.js';
|
|
33
|
-
import { RolesService } from '../roles.js';
|
|
34
29
|
import { ServerService } from '../server.js';
|
|
35
|
-
import { SettingsService } from '../settings.js';
|
|
36
|
-
import { SharesService } from '../shares.js';
|
|
37
30
|
import { SpecificationService } from '../specifications.js';
|
|
38
31
|
import { TFAService } from '../tfa.js';
|
|
39
32
|
import { UsersService } from '../users.js';
|
|
40
33
|
import { UtilsService } from '../utils.js';
|
|
41
|
-
import {
|
|
34
|
+
import { GraphQLExecutionError, GraphQLValidationError } from './errors/index.js';
|
|
35
|
+
import { createSubscriptionGenerator } from './subscription.js';
|
|
42
36
|
import { GraphQLBigInt } from './types/bigint.js';
|
|
43
37
|
import { GraphQLDate } from './types/date.js';
|
|
44
38
|
import { GraphQLGeoJSON } from './types/geojson.js';
|
|
@@ -80,7 +74,7 @@ export class GraphQLService {
|
|
|
80
74
|
const schema = this.getSchema();
|
|
81
75
|
const validationErrors = validate(schema, document, validationRules).map((validationError) => addPathToValidationError(validationError));
|
|
82
76
|
if (validationErrors.length > 0) {
|
|
83
|
-
throw new
|
|
77
|
+
throw new GraphQLValidationError({ errors: validationErrors });
|
|
84
78
|
}
|
|
85
79
|
let result;
|
|
86
80
|
try {
|
|
@@ -93,7 +87,7 @@ export class GraphQLService {
|
|
|
93
87
|
});
|
|
94
88
|
}
|
|
95
89
|
catch (err) {
|
|
96
|
-
throw new
|
|
90
|
+
throw new GraphQLExecutionError({ errors: [err.message] });
|
|
97
91
|
}
|
|
98
92
|
const formattedResult = {};
|
|
99
93
|
if (result['data'])
|
|
@@ -123,6 +117,14 @@ export class GraphQLService {
|
|
|
123
117
|
? this.schema
|
|
124
118
|
: reduceSchema(this.schema, this.accountability?.permissions || null, ['delete']),
|
|
125
119
|
};
|
|
120
|
+
const subscriptionEventType = schemaComposer.createEnumTC({
|
|
121
|
+
name: 'EventEnum',
|
|
122
|
+
values: {
|
|
123
|
+
create: { value: 'create' },
|
|
124
|
+
update: { value: 'update' },
|
|
125
|
+
delete: { value: 'delete' },
|
|
126
|
+
},
|
|
127
|
+
});
|
|
126
128
|
const { ReadCollectionTypes } = getReadableTypes();
|
|
127
129
|
const { CreateCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes } = getWritableTypes();
|
|
128
130
|
const scopeFilter = (collection) => {
|
|
@@ -431,12 +433,24 @@ export class GraphQLService {
|
|
|
431
433
|
_nstarts_with: {
|
|
432
434
|
type: GraphQLString,
|
|
433
435
|
},
|
|
436
|
+
_istarts_with: {
|
|
437
|
+
type: GraphQLString,
|
|
438
|
+
},
|
|
439
|
+
_nistarts_with: {
|
|
440
|
+
type: GraphQLString,
|
|
441
|
+
},
|
|
434
442
|
_ends_with: {
|
|
435
443
|
type: GraphQLString,
|
|
436
444
|
},
|
|
437
445
|
_nends_with: {
|
|
438
446
|
type: GraphQLString,
|
|
439
447
|
},
|
|
448
|
+
_iends_with: {
|
|
449
|
+
type: GraphQLString,
|
|
450
|
+
},
|
|
451
|
+
_niends_with: {
|
|
452
|
+
type: GraphQLString,
|
|
453
|
+
},
|
|
440
454
|
_in: {
|
|
441
455
|
type: new GraphQLList(GraphQLString),
|
|
442
456
|
},
|
|
@@ -871,6 +885,26 @@ export class GraphQLService {
|
|
|
871
885
|
},
|
|
872
886
|
});
|
|
873
887
|
}
|
|
888
|
+
const eventName = `${collection.collection}_mutated`;
|
|
889
|
+
if (collection.collection in ReadCollectionTypes) {
|
|
890
|
+
const subscriptionType = schemaComposer.createObjectTC({
|
|
891
|
+
name: eventName,
|
|
892
|
+
fields: {
|
|
893
|
+
key: new GraphQLNonNull(GraphQLID),
|
|
894
|
+
event: subscriptionEventType,
|
|
895
|
+
data: ReadCollectionTypes[collection.collection],
|
|
896
|
+
},
|
|
897
|
+
});
|
|
898
|
+
schemaComposer.Subscription.addFields({
|
|
899
|
+
[eventName]: {
|
|
900
|
+
type: subscriptionType,
|
|
901
|
+
args: {
|
|
902
|
+
event: subscriptionEventType,
|
|
903
|
+
},
|
|
904
|
+
subscribe: createSubscriptionGenerator(self, eventName),
|
|
905
|
+
},
|
|
906
|
+
});
|
|
907
|
+
}
|
|
874
908
|
}
|
|
875
909
|
for (const relation of schema.read.relations) {
|
|
876
910
|
if (relation.related_collection) {
|
|
@@ -1147,7 +1181,11 @@ export class GraphQLService {
|
|
|
1147
1181
|
if (singleton && action === 'update') {
|
|
1148
1182
|
return await this.upsertSingleton(collection, args['data'], query);
|
|
1149
1183
|
}
|
|
1150
|
-
const service =
|
|
1184
|
+
const service = getService(collection, {
|
|
1185
|
+
knex: this.knex,
|
|
1186
|
+
accountability: this.accountability,
|
|
1187
|
+
schema: this.schema,
|
|
1188
|
+
});
|
|
1151
1189
|
const hasQuery = (query.fields || []).length > 0;
|
|
1152
1190
|
try {
|
|
1153
1191
|
if (single) {
|
|
@@ -1195,7 +1233,11 @@ export class GraphQLService {
|
|
|
1195
1233
|
* Execute the read action on the correct service. Checks for singleton as well.
|
|
1196
1234
|
*/
|
|
1197
1235
|
async read(collection, query) {
|
|
1198
|
-
const service =
|
|
1236
|
+
const service = getService(collection, {
|
|
1237
|
+
knex: this.knex,
|
|
1238
|
+
accountability: this.accountability,
|
|
1239
|
+
schema: this.schema,
|
|
1240
|
+
});
|
|
1199
1241
|
const result = this.schema.collections[collection].singleton
|
|
1200
1242
|
? await service.readSingleton(query, { stripNonRequested: false })
|
|
1201
1243
|
: await service.readByQuery(query, { stripNonRequested: false });
|
|
@@ -1205,7 +1247,11 @@ export class GraphQLService {
|
|
|
1205
1247
|
* Upsert and read singleton item
|
|
1206
1248
|
*/
|
|
1207
1249
|
async upsertSingleton(collection, body, query) {
|
|
1208
|
-
const service =
|
|
1250
|
+
const service = getService(collection, {
|
|
1251
|
+
knex: this.knex,
|
|
1252
|
+
accountability: this.accountability,
|
|
1253
|
+
schema: this.schema,
|
|
1254
|
+
});
|
|
1209
1255
|
try {
|
|
1210
1256
|
await service.upsertSingleton(body);
|
|
1211
1257
|
if ((query.fields || []).length > 0) {
|
|
@@ -1405,49 +1451,6 @@ export class GraphQLService {
|
|
|
1405
1451
|
set(error, 'extensions.code', error.code);
|
|
1406
1452
|
return new GraphQLError(error.message, undefined, undefined, undefined, undefined, error);
|
|
1407
1453
|
}
|
|
1408
|
-
/**
|
|
1409
|
-
* Select the correct service for the given collection. This allows the individual services to run
|
|
1410
|
-
* their custom checks (f.e. it allows UsersService to prevent updating TFA secret from outside)
|
|
1411
|
-
*/
|
|
1412
|
-
getService(collection) {
|
|
1413
|
-
const opts = {
|
|
1414
|
-
knex: this.knex,
|
|
1415
|
-
accountability: this.accountability,
|
|
1416
|
-
schema: this.schema,
|
|
1417
|
-
};
|
|
1418
|
-
switch (collection) {
|
|
1419
|
-
case 'directus_activity':
|
|
1420
|
-
return new ActivityService(opts);
|
|
1421
|
-
case 'directus_files':
|
|
1422
|
-
return new FilesService(opts);
|
|
1423
|
-
case 'directus_folders':
|
|
1424
|
-
return new FoldersService(opts);
|
|
1425
|
-
case 'directus_permissions':
|
|
1426
|
-
return new PermissionsService(opts);
|
|
1427
|
-
case 'directus_presets':
|
|
1428
|
-
return new PresetsService(opts);
|
|
1429
|
-
case 'directus_notifications':
|
|
1430
|
-
return new NotificationsService(opts);
|
|
1431
|
-
case 'directus_revisions':
|
|
1432
|
-
return new RevisionsService(opts);
|
|
1433
|
-
case 'directus_roles':
|
|
1434
|
-
return new RolesService(opts);
|
|
1435
|
-
case 'directus_settings':
|
|
1436
|
-
return new SettingsService(opts);
|
|
1437
|
-
case 'directus_users':
|
|
1438
|
-
return new UsersService(opts);
|
|
1439
|
-
case 'directus_webhooks':
|
|
1440
|
-
return new WebhooksService(opts);
|
|
1441
|
-
case 'directus_shares':
|
|
1442
|
-
return new SharesService(opts);
|
|
1443
|
-
case 'directus_flows':
|
|
1444
|
-
return new FlowsService(opts);
|
|
1445
|
-
case 'directus_operations':
|
|
1446
|
-
return new OperationsService(opts);
|
|
1447
|
-
default:
|
|
1448
|
-
return new ItemsService(collection, opts);
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
1454
|
/**
|
|
1452
1455
|
* Replace all fragments in a selectionset for the actual selection set as defined in the fragment
|
|
1453
1456
|
* Effectively merges the selections with the fragments used in those selections
|
|
@@ -1539,6 +1542,58 @@ export class GraphQLService {
|
|
|
1539
1542
|
},
|
|
1540
1543
|
}),
|
|
1541
1544
|
},
|
|
1545
|
+
websocket: toBoolean(env['WEBSOCKETS_ENABLED'])
|
|
1546
|
+
? {
|
|
1547
|
+
type: new GraphQLObjectType({
|
|
1548
|
+
name: 'server_info_websocket',
|
|
1549
|
+
fields: {
|
|
1550
|
+
rest: {
|
|
1551
|
+
type: toBoolean(env['WEBSOCKETS_REST_ENABLED'])
|
|
1552
|
+
? new GraphQLObjectType({
|
|
1553
|
+
name: 'server_info_websocket_rest',
|
|
1554
|
+
fields: {
|
|
1555
|
+
authentication: {
|
|
1556
|
+
type: new GraphQLEnumType({
|
|
1557
|
+
name: 'server_info_websocket_rest_authentication',
|
|
1558
|
+
values: {
|
|
1559
|
+
public: { value: 'public' },
|
|
1560
|
+
handshake: { value: 'handshake' },
|
|
1561
|
+
strict: { value: 'strict' },
|
|
1562
|
+
},
|
|
1563
|
+
}),
|
|
1564
|
+
},
|
|
1565
|
+
path: { type: GraphQLString },
|
|
1566
|
+
},
|
|
1567
|
+
})
|
|
1568
|
+
: GraphQLBoolean,
|
|
1569
|
+
},
|
|
1570
|
+
graphql: {
|
|
1571
|
+
type: toBoolean(env['WEBSOCKETS_GRAPHQL_ENABLED'])
|
|
1572
|
+
? new GraphQLObjectType({
|
|
1573
|
+
name: 'server_info_websocket_graphql',
|
|
1574
|
+
fields: {
|
|
1575
|
+
authentication: {
|
|
1576
|
+
type: new GraphQLEnumType({
|
|
1577
|
+
name: 'server_info_websocket_graphql_authentication',
|
|
1578
|
+
values: {
|
|
1579
|
+
public: { value: 'public' },
|
|
1580
|
+
handshake: { value: 'handshake' },
|
|
1581
|
+
strict: { value: 'strict' },
|
|
1582
|
+
},
|
|
1583
|
+
}),
|
|
1584
|
+
},
|
|
1585
|
+
path: { type: GraphQLString },
|
|
1586
|
+
},
|
|
1587
|
+
})
|
|
1588
|
+
: GraphQLBoolean,
|
|
1589
|
+
},
|
|
1590
|
+
heartbeat: {
|
|
1591
|
+
type: toBoolean(env['WEBSOCKETS_HEARTBEAT_ENABLED']) ? GraphQLInt : GraphQLBoolean,
|
|
1592
|
+
},
|
|
1593
|
+
},
|
|
1594
|
+
}),
|
|
1595
|
+
}
|
|
1596
|
+
: GraphQLBoolean,
|
|
1542
1597
|
queryLimit: {
|
|
1543
1598
|
type: new GraphQLObjectType({
|
|
1544
1599
|
name: 'server_info_query_limit',
|
|
@@ -1698,7 +1753,9 @@ export class GraphQLService {
|
|
|
1698
1753
|
});
|
|
1699
1754
|
const currentRefreshToken = args['refresh_token'] || req?.cookies[env['REFRESH_TOKEN_COOKIE_NAME']];
|
|
1700
1755
|
if (!currentRefreshToken) {
|
|
1701
|
-
throw new
|
|
1756
|
+
throw new InvalidPayloadError({
|
|
1757
|
+
reason: `"refresh_token" is required in either the JSON payload or Cookie`,
|
|
1758
|
+
});
|
|
1702
1759
|
}
|
|
1703
1760
|
const result = await authenticationService.refresh(currentRefreshToken);
|
|
1704
1761
|
if (args['mode'] === 'cookie') {
|
|
@@ -1738,7 +1795,9 @@ export class GraphQLService {
|
|
|
1738
1795
|
});
|
|
1739
1796
|
const currentRefreshToken = args['refresh_token'] || req?.cookies[env['REFRESH_TOKEN_COOKIE_NAME']];
|
|
1740
1797
|
if (!currentRefreshToken) {
|
|
1741
|
-
throw new
|
|
1798
|
+
throw new InvalidPayloadError({
|
|
1799
|
+
reason: `"refresh_token" is required in either the JSON payload or Cookie`,
|
|
1800
|
+
});
|
|
1742
1801
|
}
|
|
1743
1802
|
await authenticationService.logout(currentRefreshToken);
|
|
1744
1803
|
return true;
|
|
@@ -1765,7 +1824,7 @@ export class GraphQLService {
|
|
|
1765
1824
|
await service.requestPasswordReset(args['email'], args['reset_url'] || null);
|
|
1766
1825
|
}
|
|
1767
1826
|
catch (err) {
|
|
1768
|
-
if (err
|
|
1827
|
+
if (isDirectusError(err, ErrorCode.InvalidPayload)) {
|
|
1769
1828
|
throw err;
|
|
1770
1829
|
}
|
|
1771
1830
|
}
|
|
@@ -1851,7 +1910,7 @@ export class GraphQLService {
|
|
|
1851
1910
|
});
|
|
1852
1911
|
const otpValid = await service.verifyOTP(this.accountability.user, args['otp']);
|
|
1853
1912
|
if (otpValid === false) {
|
|
1854
|
-
throw new
|
|
1913
|
+
throw new InvalidPayloadError({ reason: `"otp" is invalid` });
|
|
1855
1914
|
}
|
|
1856
1915
|
await service.disableTFA(this.accountability.user);
|
|
1857
1916
|
return true;
|
|
@@ -1865,7 +1924,7 @@ export class GraphQLService {
|
|
|
1865
1924
|
resolve: async (_, args) => {
|
|
1866
1925
|
const { nanoid } = await import('nanoid');
|
|
1867
1926
|
if (args['length'] && Number(args['length']) > 500) {
|
|
1868
|
-
throw new
|
|
1927
|
+
throw new InvalidPayloadError({ reason: `"length" can't be more than 500 characters` });
|
|
1869
1928
|
}
|
|
1870
1929
|
return nanoid(args['length'] ? Number(args['length']) : 32);
|
|
1871
1930
|
},
|
|
@@ -1924,7 +1983,7 @@ export class GraphQLService {
|
|
|
1924
1983
|
type: GraphQLVoid,
|
|
1925
1984
|
resolve: async () => {
|
|
1926
1985
|
if (this.accountability?.admin !== true) {
|
|
1927
|
-
throw new
|
|
1986
|
+
throw new ForbiddenError();
|
|
1928
1987
|
}
|
|
1929
1988
|
const { cache } = getCache();
|
|
1930
1989
|
await cache?.clear();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GraphQLService } from './index.js';
|
|
2
|
+
import type { GraphQLResolveInfo } from 'graphql';
|
|
3
|
+
export declare function bindPubSub(): void;
|
|
4
|
+
export declare function createSubscriptionGenerator(self: GraphQLService, event: string): (_x: unknown, _y: unknown, _z: unknown, request: GraphQLResolveInfo) => AsyncGenerator<{
|
|
5
|
+
[x: string]: {
|
|
6
|
+
key: any;
|
|
7
|
+
data: import("../../types/items.js").Item;
|
|
8
|
+
event: string;
|
|
9
|
+
};
|
|
10
|
+
} | {
|
|
11
|
+
[x: string]: {
|
|
12
|
+
key: any;
|
|
13
|
+
data: null;
|
|
14
|
+
event: string;
|
|
15
|
+
};
|
|
16
|
+
}, void, unknown>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { EventEmitter, on } from 'events';
|
|
2
|
+
import { getMessenger } from '../../messenger.js';
|
|
3
|
+
import { getSchema } from '../../utils/get-schema.js';
|
|
4
|
+
import { ItemsService } from '../items.js';
|
|
5
|
+
const messages = createPubSub(new EventEmitter());
|
|
6
|
+
export function bindPubSub() {
|
|
7
|
+
const messenger = getMessenger();
|
|
8
|
+
messenger.subscribe('websocket.event', (message) => {
|
|
9
|
+
messages.publish(`${message['collection']}_mutated`, message);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function createSubscriptionGenerator(self, event) {
|
|
13
|
+
return async function* (_x, _y, _z, request) {
|
|
14
|
+
const fields = parseFields(self, request);
|
|
15
|
+
const args = parseArguments(request);
|
|
16
|
+
for await (const payload of messages.subscribe(event)) {
|
|
17
|
+
const eventData = payload;
|
|
18
|
+
if ('event' in args && eventData['action'] !== args['event']) {
|
|
19
|
+
continue; // skip filtered events
|
|
20
|
+
}
|
|
21
|
+
const schema = await getSchema();
|
|
22
|
+
if (eventData['action'] === 'create') {
|
|
23
|
+
const { collection, key } = eventData;
|
|
24
|
+
const service = new ItemsService(collection, { schema });
|
|
25
|
+
const data = await service.readOne(key, { fields });
|
|
26
|
+
yield { [event]: { key, data, event: 'create' } };
|
|
27
|
+
}
|
|
28
|
+
if (eventData['action'] === 'update') {
|
|
29
|
+
const { collection, keys } = eventData;
|
|
30
|
+
const service = new ItemsService(collection, { schema });
|
|
31
|
+
for (const key of keys) {
|
|
32
|
+
const data = await service.readOne(key, { fields });
|
|
33
|
+
yield { [event]: { key, data, event: 'update' } };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (eventData['action'] === 'delete') {
|
|
37
|
+
const { keys } = eventData;
|
|
38
|
+
for (const key of keys) {
|
|
39
|
+
yield { [event]: { key, data: null, event: 'delete' } };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function createPubSub(emitter) {
|
|
46
|
+
return {
|
|
47
|
+
publish: (event, payload) => void emitter.emit(event, payload),
|
|
48
|
+
subscribe: async function* (event) {
|
|
49
|
+
const asyncIterator = on(emitter, event);
|
|
50
|
+
for await (const [value] of asyncIterator) {
|
|
51
|
+
yield value;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function parseFields(service, request) {
|
|
57
|
+
const selections = request.fieldNodes[0]?.selectionSet?.selections ?? [];
|
|
58
|
+
const dataSelections = selections.reduce((result, selection) => {
|
|
59
|
+
if (selection.kind === 'Field' &&
|
|
60
|
+
selection.name.value === 'data' &&
|
|
61
|
+
selection.selectionSet?.kind === 'SelectionSet') {
|
|
62
|
+
return selection.selectionSet.selections;
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}, []);
|
|
66
|
+
const { fields } = service.getQuery({}, dataSelections, request.variableValues);
|
|
67
|
+
return fields ?? [];
|
|
68
|
+
}
|
|
69
|
+
function parseArguments(request) {
|
|
70
|
+
const args = request.fieldNodes[0]?.arguments ?? [];
|
|
71
|
+
return args.reduce((result, current) => {
|
|
72
|
+
if ('value' in current.value && typeof current.value.value === 'string') {
|
|
73
|
+
result[current.name.value] = current.value.value;
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}, {});
|
|
77
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isDirectusError } from '@directus/errors';
|
|
2
2
|
import logger from '../../../logger.js';
|
|
3
3
|
const processError = (accountability, error) => {
|
|
4
4
|
logger.error(error);
|
|
5
5
|
const { originalError } = error;
|
|
6
|
-
if (originalError
|
|
6
|
+
if (isDirectusError(originalError)) {
|
|
7
7
|
return {
|
|
8
8
|
message: originalError.message,
|
|
9
9
|
extensions: {
|
|
10
10
|
code: originalError.code,
|
|
11
|
-
...originalError.extensions,
|
|
11
|
+
...(originalError.extensions ?? {}),
|
|
12
12
|
},
|
|
13
13
|
};
|
|
14
14
|
}
|
|
@@ -14,7 +14,7 @@ import { file as createTmpFile } from 'tmp-promise';
|
|
|
14
14
|
import getDatabase from '../database/index.js';
|
|
15
15
|
import emitter from '../emitter.js';
|
|
16
16
|
import env from '../env.js';
|
|
17
|
-
import {
|
|
17
|
+
import { ForbiddenError, InvalidPayloadError, ServiceUnavailableError, UnsupportedMediaTypeError, } from '../errors/index.js';
|
|
18
18
|
import logger from '../logger.js';
|
|
19
19
|
import { getDateFormatted } from '../utils/get-date-formatted.js';
|
|
20
20
|
import { FilesService } from './files.js';
|
|
@@ -31,11 +31,11 @@ export class ImportService {
|
|
|
31
31
|
}
|
|
32
32
|
async import(collection, mimetype, stream) {
|
|
33
33
|
if (this.accountability?.admin !== true && collection.startsWith('directus_'))
|
|
34
|
-
throw new
|
|
34
|
+
throw new ForbiddenError();
|
|
35
35
|
const createPermissions = this.accountability?.permissions?.find((permission) => permission.collection === collection && permission.action === 'create');
|
|
36
36
|
const updatePermissions = this.accountability?.permissions?.find((permission) => permission.collection === collection && permission.action === 'update');
|
|
37
37
|
if (this.accountability?.admin !== true && (!createPermissions || !updatePermissions)) {
|
|
38
|
-
throw new
|
|
38
|
+
throw new ForbiddenError();
|
|
39
39
|
}
|
|
40
40
|
switch (mimetype) {
|
|
41
41
|
case 'application/json':
|
|
@@ -44,7 +44,7 @@ export class ImportService {
|
|
|
44
44
|
case 'application/vnd.ms-excel':
|
|
45
45
|
return await this.importCSV(collection, stream);
|
|
46
46
|
default:
|
|
47
|
-
throw new
|
|
47
|
+
throw new UnsupportedMediaTypeError({ mediaType: mimetype, where: 'file import' });
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
importJSON(collection, stream) {
|
|
@@ -67,7 +67,7 @@ export class ImportService {
|
|
|
67
67
|
extractJSON.on('error', (err) => {
|
|
68
68
|
destroyStream(stream);
|
|
69
69
|
destroyStream(extractJSON);
|
|
70
|
-
reject(new
|
|
70
|
+
reject(new InvalidPayloadError({ reason: err.message }));
|
|
71
71
|
});
|
|
72
72
|
saveQueue.error((err) => {
|
|
73
73
|
reject(err);
|
|
@@ -122,7 +122,7 @@ export class ImportService {
|
|
|
122
122
|
})
|
|
123
123
|
.on('error', (err) => {
|
|
124
124
|
destroyStream(stream);
|
|
125
|
-
reject(new
|
|
125
|
+
reject(new InvalidPayloadError({ reason: err.message }));
|
|
126
126
|
})
|
|
127
127
|
.on('end', () => {
|
|
128
128
|
saveQueue.drain(() => {
|
|
@@ -288,6 +288,6 @@ export class ExportService {
|
|
|
288
288
|
if (format === 'yaml') {
|
|
289
289
|
return toYAML(input);
|
|
290
290
|
}
|
|
291
|
-
throw new
|
|
291
|
+
throw new ServiceUnavailableError({ service: 'export', reason: `Illegal export type used: "${format}"` });
|
|
292
292
|
}
|
|
293
293
|
}
|
package/dist/services/index.d.ts
CHANGED
package/dist/services/index.js
CHANGED
package/dist/services/items.js
CHANGED
|
@@ -6,8 +6,9 @@ import getDatabase from '../database/index.js';
|
|
|
6
6
|
import runAST from '../database/run-ast.js';
|
|
7
7
|
import emitter from '../emitter.js';
|
|
8
8
|
import env from '../env.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { ForbiddenError } from '../errors/index.js';
|
|
10
|
+
import { translateDatabaseError } from '../database/errors/translate.js';
|
|
11
|
+
import { InvalidPayloadError } from '../errors/index.js';
|
|
11
12
|
import getASTFromQuery from '../utils/get-ast-from-query.js';
|
|
12
13
|
import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
13
14
|
import { validateKeys } from '../utils/validate-keys.js';
|
|
@@ -36,7 +37,7 @@ export class ItemsService {
|
|
|
36
37
|
trackMutations(count) {
|
|
37
38
|
mutationCount += count;
|
|
38
39
|
if (mutationCount > maxCount) {
|
|
39
|
-
throw new
|
|
40
|
+
throw new InvalidPayloadError({ reason: `Exceeded max batch mutation limit of ${maxCount}` });
|
|
40
41
|
}
|
|
41
42
|
},
|
|
42
43
|
getCount() {
|
|
@@ -108,8 +109,8 @@ export class ItemsService {
|
|
|
108
109
|
const payloadWithPresets = this.accountability
|
|
109
110
|
? authorizationService.validatePayload('create', this.collection, payloadAfterHooks)
|
|
110
111
|
: payloadAfterHooks;
|
|
111
|
-
if (opts.
|
|
112
|
-
throw opts.
|
|
112
|
+
if (opts.preMutationError) {
|
|
113
|
+
throw opts.preMutationError;
|
|
113
114
|
}
|
|
114
115
|
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
115
116
|
const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
|
|
@@ -303,7 +304,7 @@ export class ItemsService {
|
|
|
303
304
|
stripNonRequested: opts?.stripNonRequested !== undefined ? opts.stripNonRequested : true,
|
|
304
305
|
});
|
|
305
306
|
if (records === null) {
|
|
306
|
-
throw new
|
|
307
|
+
throw new ForbiddenError();
|
|
307
308
|
}
|
|
308
309
|
const filteredRecords = opts?.emitEvents !== false
|
|
309
310
|
? await emitter.emitFilter(this.eventScope === 'items' ? ['items.read', `${this.collection}.items.read`] : `${this.eventScope}.read`, records, {
|
|
@@ -338,7 +339,7 @@ export class ItemsService {
|
|
|
338
339
|
const queryWithKey = assign({}, query, { filter: filterWithKey });
|
|
339
340
|
const results = await this.readByQuery(queryWithKey, opts);
|
|
340
341
|
if (results.length === 0) {
|
|
341
|
-
throw new
|
|
342
|
+
throw new ForbiddenError();
|
|
342
343
|
}
|
|
343
344
|
return results[0];
|
|
344
345
|
}
|
|
@@ -380,7 +381,7 @@ export class ItemsService {
|
|
|
380
381
|
*/
|
|
381
382
|
async updateBatch(data, opts = {}) {
|
|
382
383
|
if (!Array.isArray(data)) {
|
|
383
|
-
throw new
|
|
384
|
+
throw new InvalidPayloadError({ reason: 'Input should be an array of items' });
|
|
384
385
|
}
|
|
385
386
|
if (!opts.mutationTracker)
|
|
386
387
|
opts.mutationTracker = this.createMutationTracker();
|
|
@@ -395,7 +396,7 @@ export class ItemsService {
|
|
|
395
396
|
});
|
|
396
397
|
for (const item of data) {
|
|
397
398
|
if (!item[primaryKeyField])
|
|
398
|
-
throw new
|
|
399
|
+
throw new InvalidPayloadError({ reason: `Item in update misses primary key` });
|
|
399
400
|
const combinedOpts = Object.assign({ autoPurgeCache: false }, opts);
|
|
400
401
|
keys.push(await service.updateOne(item[primaryKeyField], omit(item, primaryKeyField), combinedOpts));
|
|
401
402
|
}
|
|
@@ -454,8 +455,8 @@ export class ItemsService {
|
|
|
454
455
|
const payloadWithPresets = this.accountability
|
|
455
456
|
? authorizationService.validatePayload('update', this.collection, payloadAfterHooks)
|
|
456
457
|
: payloadAfterHooks;
|
|
457
|
-
if (opts.
|
|
458
|
-
throw opts.
|
|
458
|
+
if (opts.preMutationError) {
|
|
459
|
+
throw opts.preMutationError;
|
|
459
460
|
}
|
|
460
461
|
await this.knex.transaction(async (trx) => {
|
|
461
462
|
const payloadService = new PayloadService(this.collection, {
|
|
@@ -654,8 +655,8 @@ export class ItemsService {
|
|
|
654
655
|
});
|
|
655
656
|
await authorizationService.checkAccess('delete', this.collection, keys);
|
|
656
657
|
}
|
|
657
|
-
if (opts.
|
|
658
|
-
throw opts.
|
|
658
|
+
if (opts.preMutationError) {
|
|
659
|
+
throw opts.preMutationError;
|
|
659
660
|
}
|
|
660
661
|
if (opts.emitEvents !== false) {
|
|
661
662
|
await emitter.emitFilter(this.eventScope === 'items' ? ['items.delete', `${this.collection}.items.delete`] : `${this.eventScope}.delete`, keys, {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import fse from 'fs-extra';
|
|
2
2
|
import { Liquid } from 'liquidjs';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
4
5
|
import getDatabase from '../../database/index.js';
|
|
5
6
|
import env from '../../env.js';
|
|
6
|
-
import {
|
|
7
|
+
import { InvalidPayloadError } from '../../errors/index.js';
|
|
7
8
|
import logger from '../../logger.js';
|
|
8
9
|
import getMailer from '../../mailer.js';
|
|
9
10
|
import { Url } from '../../utils/url.js';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
const liquidEngine = new Liquid({
|
|
13
13
|
root: [path.resolve(env['EXTENSIONS_PATH'], 'templates'), path.resolve(__dirname, 'templates')],
|
|
@@ -60,7 +60,7 @@ export class MailService {
|
|
|
60
60
|
const systemTemplatePath = path.join(__dirname, 'templates', template + '.liquid');
|
|
61
61
|
const templatePath = (await fse.pathExists(customTemplatePath)) ? customTemplatePath : systemTemplatePath;
|
|
62
62
|
if ((await fse.pathExists(templatePath)) === false) {
|
|
63
|
-
throw new
|
|
63
|
+
throw new InvalidPayloadError({ reason: `Template "${template}" doesn't exist` });
|
|
64
64
|
}
|
|
65
65
|
const templateString = await fse.readFile(templatePath, 'utf8');
|
|
66
66
|
const html = await liquidEngine.parseAndRender(templateString, variables);
|
package/dist/services/meta.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import getDatabase from '../database/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { ForbiddenError } from '../errors/index.js';
|
|
3
3
|
import { applyFilter, applySearch } from '../utils/apply-query.js';
|
|
4
4
|
export class MetaService {
|
|
5
5
|
knex;
|
|
@@ -35,7 +35,7 @@ export class MetaService {
|
|
|
35
35
|
return permission.action === 'read' && permission.collection === collection;
|
|
36
36
|
});
|
|
37
37
|
if (!permissionsRecord)
|
|
38
|
-
throw new
|
|
38
|
+
throw new ForbiddenError();
|
|
39
39
|
const permissions = permissionsRecord.permissions ?? {};
|
|
40
40
|
applyFilter(this.knex, this.schema, dbQuery, permissions, collection, {});
|
|
41
41
|
}
|
|
@@ -50,7 +50,7 @@ export class MetaService {
|
|
|
50
50
|
return permission.action === 'read' && permission.collection === collection;
|
|
51
51
|
});
|
|
52
52
|
if (!permissionsRecord)
|
|
53
|
-
throw new
|
|
53
|
+
throw new ForbiddenError();
|
|
54
54
|
const permissions = permissionsRecord.permissions ?? {};
|
|
55
55
|
if (Object.keys(filter).length > 0) {
|
|
56
56
|
filter = { _and: [permissions, filter] };
|