@directus/api 15.0.0 → 16.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 +6 -4
- package/dist/auth/drivers/ldap.js +7 -4
- package/dist/auth/drivers/local.js +3 -2
- package/dist/auth/drivers/oauth2.js +9 -2
- package/dist/auth/drivers/openid.js +9 -2
- package/dist/auth/drivers/saml.js +6 -4
- package/dist/auth.js +7 -4
- package/dist/bus/index.d.ts +1 -0
- package/dist/bus/index.js +1 -0
- package/dist/bus/lib/use-bus.d.ts +9 -0
- package/dist/bus/lib/use-bus.js +21 -0
- package/dist/cache.js +9 -9
- package/dist/cli/commands/bootstrap/index.js +6 -2
- package/dist/cli/commands/count/index.js +2 -1
- package/dist/cli/commands/database/install.js +2 -1
- package/dist/cli/commands/database/migrate.js +2 -1
- package/dist/cli/commands/roles/create.js +2 -1
- package/dist/cli/commands/schema/apply.js +2 -1
- package/dist/cli/commands/schema/snapshot.js +6 -5
- package/dist/cli/commands/users/create.js +4 -3
- package/dist/cli/commands/users/passwd.js +5 -4
- package/dist/cli/load-extensions.js +4 -2
- package/dist/cli/utils/create-env/env-stub.liquid +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +4 -1
- package/dist/controllers/assets.js +5 -3
- package/dist/controllers/auth.js +5 -4
- package/dist/controllers/extensions.js +18 -6
- package/dist/controllers/files.js +3 -3
- package/dist/controllers/schema.js +3 -2
- package/dist/controllers/shares.js +3 -3
- package/dist/database/helpers/index.d.ts +1 -1
- package/dist/database/index.js +9 -2
- package/dist/database/migrations/20210518A-add-foreign-key-constraints.js +3 -1
- package/dist/database/migrations/20210519A-add-system-fk-triggers.js +3 -1
- package/dist/database/migrations/20210802A-replace-groups.js +2 -1
- package/dist/database/migrations/20230721A-require-shares-fields.js +2 -1
- package/dist/database/migrations/20231215A-add-focalpoints.d.ts +3 -0
- package/dist/database/migrations/20231215A-add-focalpoints.js +12 -0
- package/dist/database/migrations/run.js +2 -1
- package/dist/database/run-ast.js +5 -2
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -7
- package/dist/database/system-data/fields/files.yaml +16 -0
- package/dist/emitter.js +3 -1
- package/dist/extensions/lib/get-extensions-path.d.ts +1 -1
- package/dist/extensions/lib/get-extensions-path.js +2 -1
- package/dist/extensions/lib/get-extensions.d.ts +1 -1
- package/dist/extensions/lib/get-extensions.js +32 -8
- package/dist/extensions/lib/get-shared-deps-mapping.js +6 -4
- package/dist/extensions/lib/sandbox/register/call-reference.js +4 -2
- package/dist/extensions/lib/sandbox/sdk/generators/log.js +2 -1
- package/dist/extensions/lib/sync-extensions.js +6 -4
- package/dist/extensions/manager.js +43 -19
- package/dist/flows.js +13 -7
- package/dist/logger.d.ts +7 -7
- package/dist/logger.js +116 -92
- package/dist/mailer.js +4 -2
- package/dist/middleware/cache.js +4 -2
- package/dist/middleware/check-ip.js +25 -6
- package/dist/middleware/cors.js +2 -1
- package/dist/middleware/error-handler.js +5 -5
- package/dist/middleware/rate-limiter-global.js +4 -2
- package/dist/middleware/rate-limiter-ip.js +2 -1
- package/dist/middleware/respond.js +4 -2
- package/dist/operations/log/index.js +2 -1
- package/dist/rate-limiter.d.ts +2 -1
- package/dist/rate-limiter.js +5 -2
- package/dist/redis/index.d.ts +3 -2
- package/dist/redis/index.js +3 -2
- package/dist/redis/{create-redis.js → lib/create-redis.js} +2 -2
- package/dist/redis/utils/redis-config-available.d.ts +4 -0
- package/dist/redis/utils/redis-config-available.js +8 -0
- package/dist/request/request-interceptor.js +7 -5
- package/dist/request/response-interceptor.js +2 -2
- package/dist/request/validate-ip.d.ts +1 -1
- package/dist/request/validate-ip.js +23 -7
- package/dist/server.js +11 -7
- package/dist/services/activity.js +5 -4
- package/dist/services/assets.d.ts +2 -0
- package/dist/services/assets.js +9 -4
- package/dist/services/authentication.js +17 -9
- package/dist/services/collections.js +5 -4
- package/dist/services/extensions.d.ts +15 -9
- package/dist/services/extensions.js +74 -39
- package/dist/services/fields.js +9 -4
- package/dist/services/files.d.ts +2 -2
- package/dist/services/files.js +22 -14
- package/dist/services/graphql/index.js +46 -3
- package/dist/services/graphql/subscription.js +2 -2
- package/dist/services/graphql/types/bigint.js +16 -5
- package/dist/services/graphql/utils/process-error.d.ts +4 -1
- package/dist/services/graphql/utils/process-error.js +10 -8
- package/dist/services/import-export/index.js +5 -3
- package/dist/services/items.js +12 -8
- package/dist/services/mail/index.js +4 -2
- package/dist/services/notifications.js +7 -3
- package/dist/services/relations.js +19 -10
- package/dist/services/server.js +5 -4
- package/dist/services/shares.js +3 -2
- package/dist/services/specifications.js +2 -1
- package/dist/services/users.js +20 -9
- package/dist/services/versions.js +6 -5
- package/dist/services/webhooks.d.ts +2 -2
- package/dist/services/webhooks.js +2 -2
- package/dist/services/websocket.d.ts +1 -1
- package/dist/services/websocket.js +4 -3
- package/dist/storage/register-drivers.js +2 -1
- package/dist/storage/register-locations.js +2 -1
- package/dist/synchronization.js +3 -1
- package/dist/telemetry/lib/get-report.js +1 -1
- package/dist/telemetry/lib/init-telemetry.js +2 -2
- package/dist/telemetry/lib/send-report.js +1 -1
- package/dist/telemetry/lib/track.js +2 -3
- package/dist/telemetry/utils/get-user-count.js +1 -1
- package/dist/types/assets.d.ts +2 -0
- package/dist/utils/apply-diff.js +2 -1
- package/dist/utils/apply-query.js +0 -11
- package/dist/utils/delete-from-require-cache.js +2 -1
- package/dist/utils/get-accountability-for-token.js +3 -2
- package/dist/utils/get-auth-providers.js +2 -1
- package/dist/utils/get-cache-headers.js +5 -2
- package/dist/utils/get-config-from-env.js +2 -1
- package/dist/utils/get-default-value.js +4 -3
- package/dist/utils/get-ip-from-req.js +4 -2
- package/dist/utils/get-permissions.js +5 -3
- package/dist/utils/get-schema.js +5 -2
- package/dist/utils/get-snapshot-diff.js +7 -9
- package/dist/utils/get-snapshot.js +4 -4
- package/dist/utils/ip-in-networks.d.ts +6 -0
- package/dist/utils/ip-in-networks.js +13 -0
- package/dist/utils/is-url-allowed.js +2 -1
- package/dist/utils/job-queue.d.ts +1 -0
- package/dist/utils/job-queue.js +3 -0
- package/dist/utils/sanitize-query.js +7 -2
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/utils/should-clear-cache.js +2 -1
- package/dist/utils/should-skip-cache.js +2 -1
- package/dist/utils/transformations.js +95 -12
- package/dist/utils/validate-env.js +4 -2
- package/dist/utils/validate-query.js +7 -3
- package/dist/utils/validate-storage.js +4 -2
- package/dist/webhooks.js +4 -3
- package/dist/websocket/controllers/base.js +12 -6
- package/dist/websocket/controllers/graphql.js +4 -2
- package/dist/websocket/controllers/hooks.js +3 -2
- package/dist/websocket/controllers/index.js +4 -2
- package/dist/websocket/controllers/rest.js +4 -2
- package/dist/websocket/errors.js +2 -1
- package/dist/websocket/handlers/heartbeat.js +4 -3
- package/dist/websocket/handlers/subscribe.d.ts +2 -2
- package/dist/websocket/handlers/subscribe.js +5 -4
- package/package.json +57 -57
- package/dist/__utils__/items-utils.d.ts +0 -2
- package/dist/__utils__/items-utils.js +0 -31
- package/dist/__utils__/mock-env.d.ts +0 -18
- package/dist/__utils__/mock-env.js +0 -41
- package/dist/__utils__/schemas.d.ts +0 -13
- package/dist/__utils__/schemas.js +0 -301
- package/dist/__utils__/snapshots.d.ts +0 -5
- package/dist/__utils__/snapshots.js +0 -903
- package/dist/env.d.ts +0 -14
- package/dist/env.js +0 -511
- package/dist/messenger.d.ts +0 -24
- package/dist/messenger.js +0 -64
- package/dist/utils/to-boolean.d.ts +0 -4
- package/dist/utils/to-boolean.js +0 -6
- /package/dist/redis/{create-redis.d.ts → lib/create-redis.d.ts} +0 -0
- /package/dist/redis/{use-redis.d.ts → lib/use-redis.d.ts} +0 -0
- /package/dist/redis/{use-redis.js → lib/use-redis.js} +0 -0
package/dist/services/files.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
import type {
|
|
2
|
+
import type { BusboyFileStream, File } from '@directus/types';
|
|
3
3
|
import type { Readable } from 'node:stream';
|
|
4
4
|
import type { AbstractServiceOptions, MutationOptions, PrimaryKey } from '../types/index.js';
|
|
5
5
|
import { ItemsService } from './items.js';
|
|
@@ -15,7 +15,7 @@ export declare class FilesService extends ItemsService {
|
|
|
15
15
|
/**
|
|
16
16
|
* Extract metadata from a buffer's content
|
|
17
17
|
*/
|
|
18
|
-
getMetadata(stream: Readable, allowList?:
|
|
18
|
+
getMetadata(stream: Readable, allowList?: string | string[]): Promise<Metadata>;
|
|
19
19
|
/**
|
|
20
20
|
* Import a single file from an external URL
|
|
21
21
|
*/
|
package/dist/services/files.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { ContentTooLargeError, ForbiddenError, InvalidPayloadError, ServiceUnavailableError } from '@directus/errors';
|
|
1
3
|
import formatTitle from '@directus/format-title';
|
|
2
4
|
import { toArray } from '@directus/utils';
|
|
3
5
|
import encodeURL from 'encodeurl';
|
|
4
|
-
import exif from 'exif-reader';
|
|
6
|
+
import exif, {} from 'exif-reader';
|
|
5
7
|
import { parse as parseIcc } from 'icc';
|
|
6
8
|
import { clone, pick } from 'lodash-es';
|
|
7
9
|
import { extension } from 'mime-types';
|
|
@@ -13,13 +15,13 @@ import sharp from 'sharp';
|
|
|
13
15
|
import url from 'url';
|
|
14
16
|
import { SUPPORTED_IMAGE_METADATA_FORMATS } from '../constants.js';
|
|
15
17
|
import emitter from '../emitter.js';
|
|
16
|
-
import
|
|
17
|
-
import { ContentTooLargeError, ForbiddenError, InvalidPayloadError, ServiceUnavailableError } from '@directus/errors';
|
|
18
|
-
import logger from '../logger.js';
|
|
18
|
+
import { useLogger } from '../logger.js';
|
|
19
19
|
import { getAxios } from '../request/index.js';
|
|
20
20
|
import { getStorage } from '../storage/index.js';
|
|
21
21
|
import { parseIptc, parseXmp } from '../utils/parse-image-metadata.js';
|
|
22
22
|
import { ItemsService } from './items.js';
|
|
23
|
+
const env = useEnv();
|
|
24
|
+
const logger = useLogger();
|
|
23
25
|
export class FilesService extends ItemsService {
|
|
24
26
|
constructor(options) {
|
|
25
27
|
super('directus_files', options);
|
|
@@ -138,7 +140,8 @@ export class FilesService extends ItemsService {
|
|
|
138
140
|
if (!payload.metadata && metadata) {
|
|
139
141
|
payload.metadata = metadata;
|
|
140
142
|
}
|
|
141
|
-
// Note that if this is a replace file upload, the below
|
|
143
|
+
// Note that if this is a replace file upload, the below properties are fetched and included in the payload above
|
|
144
|
+
// in the `existingFile` variable... so this will ONLY set the values if they're not already set
|
|
142
145
|
if (!payload.description && description) {
|
|
143
146
|
payload.description = description;
|
|
144
147
|
}
|
|
@@ -192,20 +195,25 @@ export class FilesService extends ItemsService {
|
|
|
192
195
|
const fullMetadata = {};
|
|
193
196
|
if (sharpMetadata.exif) {
|
|
194
197
|
try {
|
|
195
|
-
const {
|
|
196
|
-
if (
|
|
197
|
-
fullMetadata.ifd0 =
|
|
198
|
+
const { Image, ThumbnailTags, Iop, GPSInfo, Photo } = exif(sharpMetadata.exif);
|
|
199
|
+
if (Image) {
|
|
200
|
+
fullMetadata.ifd0 = Image;
|
|
201
|
+
}
|
|
202
|
+
if (ThumbnailTags) {
|
|
203
|
+
fullMetadata.ifd1 = ThumbnailTags;
|
|
204
|
+
}
|
|
205
|
+
if (Iop) {
|
|
206
|
+
fullMetadata.interop = Iop;
|
|
198
207
|
}
|
|
199
|
-
if (
|
|
200
|
-
fullMetadata.
|
|
208
|
+
if (GPSInfo) {
|
|
209
|
+
fullMetadata.gps = GPSInfo;
|
|
201
210
|
}
|
|
202
|
-
if (
|
|
203
|
-
fullMetadata.
|
|
211
|
+
if (Photo) {
|
|
212
|
+
fullMetadata.exif = Photo;
|
|
204
213
|
}
|
|
205
|
-
Object.assign(fullMetadata, rest);
|
|
206
214
|
}
|
|
207
215
|
catch (err) {
|
|
208
|
-
logger.warn(`Couldn't extract
|
|
216
|
+
logger.warn(`Couldn't extract Exif metadata from file`);
|
|
209
217
|
logger.warn(err);
|
|
210
218
|
}
|
|
211
219
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Action, FUNCTIONS } from '@directus/constants';
|
|
2
|
+
import { useEnv } from '@directus/env';
|
|
2
3
|
import { ErrorCode, ForbiddenError, InvalidPayloadError, isDirectusError } from '@directus/errors';
|
|
3
|
-
import { parseFilterFunctionPath } from '@directus/utils';
|
|
4
|
+
import { parseFilterFunctionPath, toBoolean } from '@directus/utils';
|
|
4
5
|
import argon2 from 'argon2';
|
|
5
6
|
import { GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLID, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType, NoSchemaIntrospectionCustomRule, execute, specifiedRules, validate, } from 'graphql';
|
|
6
7
|
import { GraphQLJSON, InputTypeComposer, ObjectTypeComposer, SchemaComposer, toInputObjectType } from 'graphql-compose';
|
|
@@ -8,14 +9,12 @@ import { assign, flatten, get, mapKeys, merge, omit, pick, set, transform, uniq
|
|
|
8
9
|
import { clearSystemCache, getCache } from '../../cache.js';
|
|
9
10
|
import { DEFAULT_AUTH_PROVIDER, GENERATE_SPECIAL } from '../../constants.js';
|
|
10
11
|
import getDatabase from '../../database/index.js';
|
|
11
|
-
import env from '../../env.js';
|
|
12
12
|
import { generateHash } from '../../utils/generate-hash.js';
|
|
13
13
|
import { getGraphQLType } from '../../utils/get-graphql-type.js';
|
|
14
14
|
import { getMilliseconds } from '../../utils/get-milliseconds.js';
|
|
15
15
|
import { getService } from '../../utils/get-service.js';
|
|
16
16
|
import { reduceSchema } from '../../utils/reduce-schema.js';
|
|
17
17
|
import { sanitizeQuery } from '../../utils/sanitize-query.js';
|
|
18
|
-
import { toBoolean } from '../../utils/to-boolean.js';
|
|
19
18
|
import { validateQuery } from '../../utils/validate-query.js';
|
|
20
19
|
import { ActivityService } from '../activity.js';
|
|
21
20
|
import { AuthenticationService } from '../authentication.js';
|
|
@@ -41,6 +40,7 @@ import { GraphQLStringOrFloat } from './types/string-or-float.js';
|
|
|
41
40
|
import { GraphQLVoid } from './types/void.js';
|
|
42
41
|
import { addPathToValidationError } from './utils/add-path-to-validation-error.js';
|
|
43
42
|
import processError from './utils/process-error.js';
|
|
43
|
+
const env = useEnv();
|
|
44
44
|
const validationRules = Array.from(specifiedRules);
|
|
45
45
|
if (env['GRAPHQL_INTROSPECTION'] === false) {
|
|
46
46
|
validationRules.push(NoSchemaIntrospectionCustomRule);
|
|
@@ -595,6 +595,47 @@ export class GraphQLService {
|
|
|
595
595
|
},
|
|
596
596
|
},
|
|
597
597
|
});
|
|
598
|
+
const BigIntFilterOperators = schemaComposer.createInputTC({
|
|
599
|
+
name: 'big_int_filter_operators',
|
|
600
|
+
fields: {
|
|
601
|
+
_eq: {
|
|
602
|
+
type: GraphQLBigInt,
|
|
603
|
+
},
|
|
604
|
+
_neq: {
|
|
605
|
+
type: GraphQLBigInt,
|
|
606
|
+
},
|
|
607
|
+
_in: {
|
|
608
|
+
type: new GraphQLList(GraphQLBigInt),
|
|
609
|
+
},
|
|
610
|
+
_nin: {
|
|
611
|
+
type: new GraphQLList(GraphQLBigInt),
|
|
612
|
+
},
|
|
613
|
+
_gt: {
|
|
614
|
+
type: GraphQLBigInt,
|
|
615
|
+
},
|
|
616
|
+
_gte: {
|
|
617
|
+
type: GraphQLBigInt,
|
|
618
|
+
},
|
|
619
|
+
_lt: {
|
|
620
|
+
type: GraphQLBigInt,
|
|
621
|
+
},
|
|
622
|
+
_lte: {
|
|
623
|
+
type: GraphQLBigInt,
|
|
624
|
+
},
|
|
625
|
+
_null: {
|
|
626
|
+
type: GraphQLBoolean,
|
|
627
|
+
},
|
|
628
|
+
_nnull: {
|
|
629
|
+
type: GraphQLBoolean,
|
|
630
|
+
},
|
|
631
|
+
_between: {
|
|
632
|
+
type: new GraphQLList(GraphQLBigInt),
|
|
633
|
+
},
|
|
634
|
+
_nbetween: {
|
|
635
|
+
type: new GraphQLList(GraphQLBigInt),
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
});
|
|
598
639
|
const GeometryFilterOperators = schemaComposer.createInputTC({
|
|
599
640
|
name: 'geometry_filter_operators',
|
|
600
641
|
fields: {
|
|
@@ -705,6 +746,8 @@ export class GraphQLService {
|
|
|
705
746
|
filterOperatorType = BooleanFilterOperators;
|
|
706
747
|
break;
|
|
707
748
|
case GraphQLBigInt:
|
|
749
|
+
filterOperatorType = BigIntFilterOperators;
|
|
750
|
+
break;
|
|
708
751
|
case GraphQLInt:
|
|
709
752
|
case GraphQLFloat:
|
|
710
753
|
filterOperatorType = NumberFilterOperators;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { EventEmitter, on } from 'events';
|
|
2
|
-
import {
|
|
2
|
+
import { useBus } from '../../bus/index.js';
|
|
3
3
|
import { getSchema } from '../../utils/get-schema.js';
|
|
4
4
|
import { refreshAccountability } from '../../websocket/authenticate.js';
|
|
5
5
|
import { getPayload } from '../../websocket/utils/items.js';
|
|
6
6
|
const messages = createPubSub(new EventEmitter());
|
|
7
7
|
export function bindPubSub() {
|
|
8
|
-
const messenger =
|
|
8
|
+
const messenger = useBus();
|
|
9
9
|
messenger.subscribe('websocket.event', (message) => {
|
|
10
10
|
messages.publish(`${message['collection']}_mutated`, message);
|
|
11
11
|
});
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { GraphQLScalarType, Kind } from 'graphql';
|
|
2
|
+
// minimum and maximum int64 values database vendors use for big integer
|
|
3
|
+
const MIN_BIG_INT = -9223372036854775808n;
|
|
4
|
+
const MAX_BIG_INT = 9223372036854775807n;
|
|
2
5
|
export const GraphQLBigInt = new GraphQLScalarType({
|
|
3
6
|
name: 'GraphQLBigInt',
|
|
4
7
|
description: 'BigInt value',
|
|
@@ -26,11 +29,19 @@ export const GraphQLBigInt = new GraphQLScalarType({
|
|
|
26
29
|
},
|
|
27
30
|
});
|
|
28
31
|
function parseNumberValue(input) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (isNaN(value) || value < Number.MIN_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER) {
|
|
32
|
+
// Attempt to parse the input as a regular integer
|
|
33
|
+
const intValue = Number(input);
|
|
34
|
+
if (isNaN(intValue)) {
|
|
33
35
|
throw new Error('Invalid GraphQLBigInt');
|
|
34
36
|
}
|
|
35
|
-
|
|
37
|
+
if (!Number.isSafeInteger(intValue)) {
|
|
38
|
+
// If the input is not a safe integer, its a big int, so return it as string,
|
|
39
|
+
// because currently string is the best way to handle big int due to knex limitations and JSON.stringify not able to serialise bigInt
|
|
40
|
+
const bigIntInput = BigInt(input);
|
|
41
|
+
if (bigIntInput < MIN_BIG_INT || bigIntInput > MAX_BIG_INT) {
|
|
42
|
+
throw new Error('Invalid GraphQLBigInt');
|
|
43
|
+
}
|
|
44
|
+
return input;
|
|
45
|
+
}
|
|
46
|
+
return intValue;
|
|
36
47
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { type DirectusError } from '@directus/errors';
|
|
1
2
|
import type { Accountability } from '@directus/types';
|
|
2
3
|
import type { GraphQLError, GraphQLFormattedError } from 'graphql';
|
|
3
|
-
declare const processError: (accountability: Accountability | null, error: Readonly<GraphQLError
|
|
4
|
+
declare const processError: (accountability: Accountability | null, error: Readonly<GraphQLError & {
|
|
5
|
+
originalError: GraphQLError | DirectusError | Error | undefined;
|
|
6
|
+
}>) => GraphQLFormattedError;
|
|
4
7
|
export default processError;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { isDirectusError } from '@directus/errors';
|
|
2
|
-
import
|
|
2
|
+
import { useLogger } from '../../../logger.js';
|
|
3
3
|
const processError = (accountability, error) => {
|
|
4
|
+
const logger = useLogger();
|
|
4
5
|
logger.error(error);
|
|
5
|
-
|
|
6
|
+
let originalError = error.originalError;
|
|
7
|
+
if (originalError && 'originalError' in originalError) {
|
|
8
|
+
originalError = originalError.originalError;
|
|
9
|
+
}
|
|
6
10
|
if (isDirectusError(originalError)) {
|
|
7
11
|
return {
|
|
8
12
|
message: originalError.message,
|
|
@@ -10,6 +14,8 @@ const processError = (accountability, error) => {
|
|
|
10
14
|
code: originalError.code,
|
|
11
15
|
...(originalError.extensions ?? {}),
|
|
12
16
|
},
|
|
17
|
+
...(error.locations && { locations: error.locations }),
|
|
18
|
+
...(error.path && { path: error.path }),
|
|
13
19
|
};
|
|
14
20
|
}
|
|
15
21
|
else {
|
|
@@ -19,13 +25,9 @@ const processError = (accountability, error) => {
|
|
|
19
25
|
extensions: {
|
|
20
26
|
code: 'INTERNAL_SERVER_ERROR',
|
|
21
27
|
},
|
|
28
|
+
...(error.locations && { locations: error.locations }),
|
|
29
|
+
...(error.path && { path: error.path }),
|
|
22
30
|
};
|
|
23
|
-
if (error.locations) {
|
|
24
|
-
graphqlFormattedError.locations = error.locations;
|
|
25
|
-
}
|
|
26
|
-
if (error.path) {
|
|
27
|
-
graphqlFormattedError.path = error.path;
|
|
28
|
-
}
|
|
29
31
|
return graphqlFormattedError;
|
|
30
32
|
}
|
|
31
33
|
else {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { ForbiddenError, InvalidPayloadError, ServiceUnavailableError, UnsupportedMediaTypeError, } from '@directus/errors';
|
|
1
3
|
import { parseJSON, toArray } from '@directus/utils';
|
|
2
4
|
import { queue } from 'async';
|
|
3
5
|
import destroyStream from 'destroy';
|
|
@@ -10,9 +12,7 @@ import Papa from 'papaparse';
|
|
|
10
12
|
import StreamArray from 'stream-json/streamers/StreamArray.js';
|
|
11
13
|
import getDatabase from '../../database/index.js';
|
|
12
14
|
import emitter from '../../emitter.js';
|
|
13
|
-
import
|
|
14
|
-
import { ForbiddenError, InvalidPayloadError, ServiceUnavailableError, UnsupportedMediaTypeError, } from '@directus/errors';
|
|
15
|
-
import logger from '../../logger.js';
|
|
15
|
+
import { useLogger } from '../../logger.js';
|
|
16
16
|
import { getDateFormatted } from '../../utils/get-date-formatted.js';
|
|
17
17
|
import { Url } from '../../utils/url.js';
|
|
18
18
|
import { userName } from '../../utils/user-name.js';
|
|
@@ -20,6 +20,8 @@ import { FilesService } from '../files.js';
|
|
|
20
20
|
import { ItemsService } from '../items.js';
|
|
21
21
|
import { NotificationsService } from '../notifications.js';
|
|
22
22
|
import { UsersService } from '../users.js';
|
|
23
|
+
const env = useEnv();
|
|
24
|
+
const logger = useLogger();
|
|
23
25
|
export class ImportService {
|
|
24
26
|
knex;
|
|
25
27
|
accountability;
|
package/dist/services/items.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { Action } from '@directus/constants';
|
|
2
|
+
import { useEnv } from '@directus/env';
|
|
3
|
+
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
2
4
|
import { assign, clone, cloneDeep, omit, pick, without } from 'lodash-es';
|
|
3
5
|
import { getCache } from '../cache.js';
|
|
6
|
+
import { translateDatabaseError } from '../database/errors/translate.js';
|
|
4
7
|
import { getHelpers } from '../database/helpers/index.js';
|
|
5
8
|
import getDatabase from '../database/index.js';
|
|
6
9
|
import runAST from '../database/run-ast.js';
|
|
7
10
|
import emitter from '../emitter.js';
|
|
8
|
-
import env from '../env.js';
|
|
9
|
-
import { ForbiddenError } from '@directus/errors';
|
|
10
|
-
import { translateDatabaseError } from '../database/errors/translate.js';
|
|
11
|
-
import { InvalidPayloadError } from '@directus/errors';
|
|
12
11
|
import getASTFromQuery from '../utils/get-ast-from-query.js';
|
|
13
12
|
import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
14
13
|
import { validateKeys } from '../utils/validate-keys.js';
|
|
15
14
|
import { AuthorizationService } from './authorization.js';
|
|
16
15
|
import { PayloadService } from './payload.js';
|
|
16
|
+
const env = useEnv();
|
|
17
17
|
export class ItemsService {
|
|
18
18
|
collection;
|
|
19
19
|
knex;
|
|
@@ -118,15 +118,19 @@ export class ItemsService {
|
|
|
118
118
|
const payloadWithTypeCasting = await payloadService.processValues('create', payloadWithoutAliases);
|
|
119
119
|
// The primary key can already exist in the payload.
|
|
120
120
|
// In case of manual string / UUID primary keys it's always provided at this point.
|
|
121
|
-
// In case of an integer primary key, it might be provided as the user can specify the value manually.
|
|
121
|
+
// In case of an (big) integer primary key, it might be provided as the user can specify the value manually.
|
|
122
122
|
let primaryKey = payloadWithTypeCasting[primaryKeyField];
|
|
123
|
+
if (primaryKey) {
|
|
124
|
+
validateKeys(this.schema, this.collection, primaryKeyField, primaryKey);
|
|
125
|
+
}
|
|
123
126
|
// If a PK of type number was provided, although the PK is set the auto_increment,
|
|
124
127
|
// depending on the database, the sequence might need to be reset to protect future PK collisions.
|
|
125
128
|
let autoIncrementSequenceNeedsToBeReset = false;
|
|
126
129
|
const pkField = this.schema.collections[this.collection].fields[primaryKeyField];
|
|
127
130
|
if (primaryKey &&
|
|
131
|
+
pkField &&
|
|
128
132
|
!opts.bypassAutoIncrementSequenceReset &&
|
|
129
|
-
pkField.type
|
|
133
|
+
['integer', 'bigInteger'].includes(pkField.type) &&
|
|
130
134
|
pkField.defaultValue === 'AUTO_INCREMENT') {
|
|
131
135
|
autoIncrementSequenceNeedsToBeReset = true;
|
|
132
136
|
}
|
|
@@ -505,7 +509,7 @@ export class ItemsService {
|
|
|
505
509
|
nestedActionEvents.push(...nestedActionEventsM2O);
|
|
506
510
|
nestedActionEvents.push(...nestedActionEventsA2O);
|
|
507
511
|
for (const key of keys) {
|
|
508
|
-
const { revisions, nestedActionEvents: nestedActionEventsO2M } = await payloadService.processO2M(
|
|
512
|
+
const { revisions, nestedActionEvents: nestedActionEventsO2M } = await payloadService.processO2M(payloadWithA2O, key, opts);
|
|
509
513
|
childrenRevisions.push(...revisions);
|
|
510
514
|
nestedActionEvents.push(...nestedActionEventsO2M);
|
|
511
515
|
}
|
|
@@ -569,7 +573,7 @@ export class ItemsService {
|
|
|
569
573
|
? ['items.update', `${this.collection}.items.update`]
|
|
570
574
|
: `${this.eventScope}.update`,
|
|
571
575
|
meta: {
|
|
572
|
-
payload,
|
|
576
|
+
payload: payloadWithPresets,
|
|
573
577
|
keys,
|
|
574
578
|
collection: this.collection,
|
|
575
579
|
},
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import { InvalidPayloadError } from '@directus/errors';
|
|
2
3
|
import fse from 'fs-extra';
|
|
3
4
|
import { Liquid } from 'liquidjs';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
import { fileURLToPath } from 'url';
|
|
6
7
|
import getDatabase from '../../database/index.js';
|
|
7
|
-
import env from '../../env.js';
|
|
8
8
|
import { getExtensionsPath } from '../../extensions/lib/get-extensions-path.js';
|
|
9
|
-
import
|
|
9
|
+
import { useLogger } from '../../logger.js';
|
|
10
10
|
import getMailer from '../../mailer.js';
|
|
11
11
|
import { Url } from '../../utils/url.js';
|
|
12
|
+
const env = useEnv();
|
|
13
|
+
const logger = useLogger();
|
|
12
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
15
|
const liquidEngine = new Liquid({
|
|
14
16
|
root: [path.resolve(getExtensionsPath(), 'templates'), path.resolve(__dirname, 'templates')],
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { useLogger } from '../logger.js';
|
|
3
3
|
import { md } from '../utils/md.js';
|
|
4
4
|
import { Url } from '../utils/url.js';
|
|
5
5
|
import { ItemsService } from './items.js';
|
|
6
6
|
import { MailService } from './mail/index.js';
|
|
7
7
|
import { UsersService } from './users.js';
|
|
8
|
+
const env = useEnv();
|
|
9
|
+
const logger = useLogger();
|
|
8
10
|
export class NotificationsService extends ItemsService {
|
|
9
11
|
usersService;
|
|
10
12
|
mailService;
|
|
@@ -30,7 +32,9 @@ export class NotificationsService extends ItemsService {
|
|
|
30
32
|
const user = await this.usersService.readOne(data.recipient, {
|
|
31
33
|
fields: ['id', 'email', 'email_notifications', 'role.app_access'],
|
|
32
34
|
});
|
|
33
|
-
const manageUserAccountUrl = new Url(env['PUBLIC_URL'])
|
|
35
|
+
const manageUserAccountUrl = new Url(env['PUBLIC_URL'])
|
|
36
|
+
.addPath('admin', 'users', user['id'])
|
|
37
|
+
.toString();
|
|
34
38
|
const html = data.message ? md(data.message) : '';
|
|
35
39
|
if (user['email'] && user['email_notifications'] === true) {
|
|
36
40
|
try {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
1
2
|
import { createInspector } from '@directus/schema';
|
|
2
3
|
import { toArray } from '@directus/utils';
|
|
3
4
|
import { clearSystemCache, getCache } from '../cache.js';
|
|
@@ -5,7 +6,6 @@ import { getHelpers } from '../database/helpers/index.js';
|
|
|
5
6
|
import getDatabase, { getSchemaInspector } from '../database/index.js';
|
|
6
7
|
import { systemRelationRows } from '../database/system-data/relations/index.js';
|
|
7
8
|
import emitter from '../emitter.js';
|
|
8
|
-
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
9
9
|
import { getDefaultIndexName } from '../utils/get-default-index-name.js';
|
|
10
10
|
import { getSchema } from '../utils/get-schema.js';
|
|
11
11
|
import { ItemsService } from './items.js';
|
|
@@ -115,16 +115,18 @@ export class RelationsService {
|
|
|
115
115
|
if (!relation.field) {
|
|
116
116
|
throw new InvalidPayloadError({ reason: '"field" is required' });
|
|
117
117
|
}
|
|
118
|
-
|
|
118
|
+
const collectionSchema = this.schema.collections[relation.collection];
|
|
119
|
+
if (!collectionSchema) {
|
|
119
120
|
throw new InvalidPayloadError({ reason: `Collection "${relation.collection}" doesn't exist` });
|
|
120
121
|
}
|
|
121
|
-
|
|
122
|
+
const fieldSchema = collectionSchema.fields[relation.field];
|
|
123
|
+
if (!fieldSchema) {
|
|
122
124
|
throw new InvalidPayloadError({
|
|
123
125
|
reason: `Field "${relation.field}" doesn't exist in collection "${relation.collection}"`,
|
|
124
126
|
});
|
|
125
127
|
}
|
|
126
128
|
// A primary key should not be a foreign key
|
|
127
|
-
if (
|
|
129
|
+
if (collectionSchema.primary === relation.field) {
|
|
128
130
|
throw new InvalidPayloadError({
|
|
129
131
|
reason: `Field "${relation.field}" in collection "${relation.collection}" is a primary key`,
|
|
130
132
|
});
|
|
@@ -151,7 +153,7 @@ export class RelationsService {
|
|
|
151
153
|
await this.knex.transaction(async (trx) => {
|
|
152
154
|
if (relation.related_collection) {
|
|
153
155
|
await trx.schema.alterTable(relation.collection, async (table) => {
|
|
154
|
-
this.alterType(table, relation);
|
|
156
|
+
this.alterType(table, relation, fieldSchema.nullable);
|
|
155
157
|
const constraintName = getDefaultIndexName('foreign', relation.collection, relation.field);
|
|
156
158
|
const builder = table
|
|
157
159
|
.foreign(relation.field, constraintName)
|
|
@@ -198,10 +200,12 @@ export class RelationsService {
|
|
|
198
200
|
if (this.accountability && this.accountability.admin !== true) {
|
|
199
201
|
throw new ForbiddenError();
|
|
200
202
|
}
|
|
201
|
-
|
|
203
|
+
const collectionSchema = this.schema.collections[collection];
|
|
204
|
+
if (!collectionSchema) {
|
|
202
205
|
throw new InvalidPayloadError({ reason: `Collection "${collection}" doesn't exist` });
|
|
203
206
|
}
|
|
204
|
-
|
|
207
|
+
const fieldSchema = collectionSchema.fields[field];
|
|
208
|
+
if (!fieldSchema) {
|
|
205
209
|
throw new InvalidPayloadError({ reason: `Field "${field}" doesn't exist in collection "${collection}"` });
|
|
206
210
|
}
|
|
207
211
|
const existingRelation = this.schema.relations.find((existingRelation) => existingRelation.collection === collection && existingRelation.field === field);
|
|
@@ -225,7 +229,7 @@ export class RelationsService {
|
|
|
225
229
|
constraintName = this.helpers.schema.constraintName(constraintName);
|
|
226
230
|
existingRelation.schema.constraint_name = constraintName;
|
|
227
231
|
}
|
|
228
|
-
this.alterType(table, relation);
|
|
232
|
+
this.alterType(table, relation, fieldSchema.nullable);
|
|
229
233
|
const builder = table
|
|
230
234
|
.foreign(field, constraintName || undefined)
|
|
231
235
|
.references(`${existingRelation.related_collection}.${this.schema.collections[existingRelation.related_collection].primary}`);
|
|
@@ -447,11 +451,16 @@ export class RelationsService {
|
|
|
447
451
|
*
|
|
448
452
|
* @TODO This is a bit of a hack, and might be better of abstracted elsewhere
|
|
449
453
|
*/
|
|
450
|
-
alterType(table, relation) {
|
|
454
|
+
alterType(table, relation, nullable) {
|
|
451
455
|
const m2oFieldDBType = this.schema.collections[relation.collection].fields[relation.field].dbType;
|
|
452
456
|
const relatedFieldDBType = this.schema.collections[relation.related_collection].fields[this.schema.collections[relation.related_collection].primary].dbType;
|
|
453
457
|
if (m2oFieldDBType !== relatedFieldDBType && m2oFieldDBType === 'int' && relatedFieldDBType === 'int unsigned') {
|
|
454
|
-
table.specificType(relation.field, 'int unsigned')
|
|
458
|
+
const alterField = table.specificType(relation.field, 'int unsigned');
|
|
459
|
+
// Maintains the non-nullable state
|
|
460
|
+
if (!nullable) {
|
|
461
|
+
alterField.notNullable();
|
|
462
|
+
}
|
|
463
|
+
alterField.alter();
|
|
455
464
|
}
|
|
456
465
|
}
|
|
457
466
|
}
|
package/dist/services/server.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { toArray, toBoolean } from '@directus/utils';
|
|
2
3
|
import { version } from 'directus/version';
|
|
3
4
|
import { merge } from 'lodash-es';
|
|
4
5
|
import { Readable } from 'node:stream';
|
|
5
6
|
import { performance } from 'perf_hooks';
|
|
6
7
|
import { getCache } from '../cache.js';
|
|
7
8
|
import getDatabase, { hasDatabaseConnection } from '../database/index.js';
|
|
8
|
-
import
|
|
9
|
-
import logger from '../logger.js';
|
|
9
|
+
import { useLogger } from '../logger.js';
|
|
10
10
|
import getMailer from '../mailer.js';
|
|
11
11
|
import { rateLimiterGlobal } from '../middleware/rate-limiter-global.js';
|
|
12
12
|
import { rateLimiter } from '../middleware/rate-limiter-ip.js';
|
|
13
13
|
import { SERVER_ONLINE } from '../server.js';
|
|
14
14
|
import { getStorage } from '../storage/index.js';
|
|
15
|
-
import { toBoolean } from '../utils/to-boolean.js';
|
|
16
15
|
import { SettingsService } from './settings.js';
|
|
16
|
+
const env = useEnv();
|
|
17
|
+
const logger = useLogger();
|
|
17
18
|
export class ServerService {
|
|
18
19
|
knex;
|
|
19
20
|
accountability;
|
package/dist/services/shares.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { ForbiddenError, InvalidCredentialsError } from '@directus/errors';
|
|
1
3
|
import argon2 from 'argon2';
|
|
2
4
|
import jwt from 'jsonwebtoken';
|
|
3
|
-
import env from '../env.js';
|
|
4
|
-
import { ForbiddenError, InvalidCredentialsError } from '@directus/errors';
|
|
5
5
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
6
6
|
import { md } from '../utils/md.js';
|
|
7
7
|
import { Url } from '../utils/url.js';
|
|
@@ -10,6 +10,7 @@ import { AuthorizationService } from './authorization.js';
|
|
|
10
10
|
import { ItemsService } from './items.js';
|
|
11
11
|
import { MailService } from './mail/index.js';
|
|
12
12
|
import { UsersService } from './users.js';
|
|
13
|
+
const env = useEnv();
|
|
13
14
|
export class SharesService extends ItemsService {
|
|
14
15
|
authorizationService;
|
|
15
16
|
constructor(options) {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import formatTitle from '@directus/format-title';
|
|
2
3
|
import { spec } from '@directus/specs';
|
|
3
4
|
import { version } from 'directus/version';
|
|
4
5
|
import { cloneDeep, mergeWith } from 'lodash-es';
|
|
5
6
|
import { OAS_REQUIRED_SCHEMAS } from '../constants.js';
|
|
6
7
|
import getDatabase from '../database/index.js';
|
|
7
|
-
import env from '../env.js';
|
|
8
8
|
import { getRelationType } from '../utils/get-relation-type.js';
|
|
9
9
|
import { reduceSchema } from '../utils/reduce-schema.js';
|
|
10
10
|
import { GraphQLService } from './graphql/index.js';
|
|
11
|
+
const env = useEnv();
|
|
11
12
|
export class SpecificationService {
|
|
12
13
|
accountability;
|
|
13
14
|
knex;
|
package/dist/services/users.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { ForbiddenError, InvalidPayloadError, RecordNotUniqueError, UnprocessableContentError } from '@directus/errors';
|
|
1
3
|
import { getSimpleHash, toArray } from '@directus/utils';
|
|
2
4
|
import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
|
|
3
5
|
import Joi from 'joi';
|
|
@@ -5,9 +7,6 @@ import jwt from 'jsonwebtoken';
|
|
|
5
7
|
import { cloneDeep, isEmpty } from 'lodash-es';
|
|
6
8
|
import { performance } from 'perf_hooks';
|
|
7
9
|
import getDatabase from '../database/index.js';
|
|
8
|
-
import env from '../env.js';
|
|
9
|
-
import { ForbiddenError } from '@directus/errors';
|
|
10
|
-
import { InvalidPayloadError, RecordNotUniqueError, UnprocessableContentError } from '@directus/errors';
|
|
11
10
|
import isUrlAllowed from '../utils/is-url-allowed.js';
|
|
12
11
|
import { verifyJWT } from '../utils/jwt.js';
|
|
13
12
|
import { stall } from '../utils/stall.js';
|
|
@@ -15,6 +14,7 @@ import { Url } from '../utils/url.js';
|
|
|
15
14
|
import { ItemsService } from './items.js';
|
|
16
15
|
import { MailService } from './mail/index.js';
|
|
17
16
|
import { SettingsService } from './settings.js';
|
|
17
|
+
const env = useEnv();
|
|
18
18
|
export class UsersService extends ItemsService {
|
|
19
19
|
constructor(options) {
|
|
20
20
|
super('directus_users', options);
|
|
@@ -213,9 +213,20 @@ export class UsersService extends ItemsService {
|
|
|
213
213
|
async updateMany(keys, data, opts) {
|
|
214
214
|
try {
|
|
215
215
|
if (data['role']) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
/*
|
|
217
|
+
* data['role'] has the following cases:
|
|
218
|
+
* - a string with existing role id
|
|
219
|
+
* - an object with existing role id for GraphQL mutations
|
|
220
|
+
* - an object with data for new role
|
|
221
|
+
*/
|
|
222
|
+
const role = data['role']?.id ?? data['role'];
|
|
223
|
+
let newRole;
|
|
224
|
+
if (typeof role === 'string') {
|
|
225
|
+
newRole = await this.knex.select('admin_access').from('directus_roles').where('id', role).first();
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
newRole = role;
|
|
229
|
+
}
|
|
219
230
|
if (!newRole?.admin_access) {
|
|
220
231
|
await this.checkRemainingAdminExistence(keys);
|
|
221
232
|
}
|
|
@@ -325,13 +336,13 @@ export class UsersService extends ItemsService {
|
|
|
325
336
|
if (isEmpty(user) || user.status === 'invited') {
|
|
326
337
|
const subjectLine = subject ?? "You've been invited";
|
|
327
338
|
await mailService.send({
|
|
328
|
-
to: user
|
|
339
|
+
to: user?.email ?? email,
|
|
329
340
|
subject: subjectLine,
|
|
330
341
|
template: {
|
|
331
342
|
name: 'user-invitation',
|
|
332
343
|
data: {
|
|
333
|
-
url: this.inviteUrl(email, url),
|
|
334
|
-
email: user
|
|
344
|
+
url: this.inviteUrl(user?.email ?? email, url),
|
|
345
|
+
email: user?.email ?? email,
|
|
335
346
|
},
|
|
336
347
|
},
|
|
337
348
|
});
|