@directus/api 15.0.0 → 17.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/permissions.js +11 -2
- package/dist/controllers/schema.js +3 -2
- package/dist/controllers/shares.js +3 -3
- package/dist/controllers/utils.js +13 -32
- 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 +5 -3
- 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 -6
- package/dist/services/authentication.js +17 -9
- package/dist/services/authorization.d.ts +1 -1
- package/dist/services/authorization.js +15 -3
- 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.d.ts → import-export.d.ts} +1 -1
- package/dist/services/{import-export/index.js → import-export.js} +14 -12
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +1 -1
- 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/permissions.d.ts +3 -2
- package/dist/services/permissions.js +76 -1
- package/dist/services/relations.js +19 -10
- package/dist/services/roles.js +83 -15
- package/dist/services/server.js +7 -5
- 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/types/items.d.ts +4 -12
- package/dist/types/items.js +0 -4
- 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/services/import-export/import-worker.d.ts +0 -9
- package/dist/services/import-export/import-worker.js +0 -9
- package/dist/utils/to-boolean.d.ts +0 -4
- package/dist/utils/to-boolean.js +0 -6
- package/dist/worker-pool.d.ts +0 -2
- package/dist/worker-pool.js +0 -19
- /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
|
@@ -1,50 +1,39 @@
|
|
|
1
|
-
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
2
|
-
import {
|
|
3
|
-
import Joi from 'joi';
|
|
1
|
+
import { ForbiddenError, InvalidPayloadError, UnprocessableContentError } from '@directus/errors';
|
|
2
|
+
import { isObject } from '@directus/utils';
|
|
4
3
|
import { omit, pick } from 'lodash-es';
|
|
5
|
-
import
|
|
6
|
-
import { getHelpers } from '../database/helpers/index.js';
|
|
7
|
-
import getDatabase, { getSchemaInspector } from '../database/index.js';
|
|
4
|
+
import getDatabase from '../database/index.js';
|
|
8
5
|
import { getExtensionManager } from '../extensions/index.js';
|
|
9
6
|
import { ItemsService } from './items.js';
|
|
10
|
-
|
|
7
|
+
export class ExtensionReadError extends Error {
|
|
8
|
+
originalError;
|
|
9
|
+
constructor(originalError) {
|
|
10
|
+
super();
|
|
11
|
+
this.originalError = originalError;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
11
14
|
export class ExtensionsService {
|
|
12
15
|
knex;
|
|
13
|
-
permissionsService;
|
|
14
|
-
schemaInspector;
|
|
15
16
|
accountability;
|
|
16
17
|
schema;
|
|
17
18
|
extensionsItemService;
|
|
18
|
-
systemCache;
|
|
19
|
-
helpers;
|
|
20
19
|
extensionsManager;
|
|
21
20
|
constructor(options) {
|
|
22
21
|
this.knex = options.knex || getDatabase();
|
|
23
|
-
this.permissionsService = new PermissionsService(options);
|
|
24
|
-
this.schemaInspector = options.knex ? createInspector(options.knex) : getSchemaInspector();
|
|
25
22
|
this.schema = options.schema;
|
|
26
23
|
this.accountability = options.accountability || null;
|
|
27
24
|
this.extensionsManager = getExtensionManager();
|
|
28
25
|
this.extensionsItemService = new ItemsService('directus_extensions', {
|
|
29
26
|
knex: this.knex,
|
|
30
27
|
schema: this.schema,
|
|
31
|
-
|
|
28
|
+
accountability: this.accountability,
|
|
32
29
|
});
|
|
33
|
-
this.systemCache = getCache().systemCache;
|
|
34
|
-
this.helpers = getHelpers(this.knex);
|
|
35
30
|
}
|
|
36
31
|
async readAll() {
|
|
37
|
-
if (this.accountability?.admin !== true) {
|
|
38
|
-
throw new ForbiddenError();
|
|
39
|
-
}
|
|
40
32
|
const installedExtensions = this.extensionsManager.getExtensions();
|
|
41
33
|
const configuredExtensions = await this.extensionsItemService.readByQuery({ limit: -1 });
|
|
42
34
|
return this.stitch(installedExtensions, configuredExtensions);
|
|
43
35
|
}
|
|
44
36
|
async readOne(bundle, name) {
|
|
45
|
-
if (this.accountability?.admin !== true) {
|
|
46
|
-
throw new ForbiddenError();
|
|
47
|
-
}
|
|
48
37
|
const key = this.getKey(bundle, name);
|
|
49
38
|
const schema = this.extensionsManager.getExtensions().find((extension) => extension.name === (bundle ?? name));
|
|
50
39
|
const meta = await this.extensionsItemService.readOne(key);
|
|
@@ -54,27 +43,73 @@ export class ExtensionsService {
|
|
|
54
43
|
throw new ForbiddenError();
|
|
55
44
|
}
|
|
56
45
|
async updateOne(bundle, name, data) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
46
|
+
const result = await this.knex.transaction(async (trx) => {
|
|
47
|
+
if (!isObject(data.meta)) {
|
|
48
|
+
throw new InvalidPayloadError({ reason: `"meta" is required` });
|
|
49
|
+
}
|
|
50
|
+
const service = new ExtensionsService({
|
|
51
|
+
knex: trx,
|
|
52
|
+
accountability: this.accountability,
|
|
53
|
+
schema: this.schema,
|
|
54
|
+
});
|
|
55
|
+
const key = this.getKey(bundle, name);
|
|
56
|
+
await service.extensionsItemService.updateOne(key, data.meta);
|
|
57
|
+
let extension;
|
|
58
|
+
try {
|
|
59
|
+
extension = await service.readOne(bundle, name);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw new ExtensionReadError(error);
|
|
63
|
+
}
|
|
64
|
+
if ('enabled' in data.meta) {
|
|
65
|
+
await service.checkBundleAndSyncStatus(trx, extension);
|
|
66
|
+
}
|
|
67
|
+
return extension;
|
|
65
68
|
});
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
throw new InvalidPayloadError({ reason: error.message });
|
|
69
|
-
}
|
|
70
|
-
if ('meta' in data && 'enabled' in data.meta) {
|
|
71
|
-
await this.knex('directus_extensions').update({ enabled: data.meta.enabled }).where({ name: key });
|
|
72
|
-
this.extensionsManager.reload();
|
|
73
|
-
}
|
|
69
|
+
this.extensionsManager.reload();
|
|
70
|
+
return result;
|
|
74
71
|
}
|
|
75
72
|
getKey(bundle, name) {
|
|
76
73
|
return bundle ? `${bundle}/${name}` : name;
|
|
77
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Sync a bundles enabled status
|
|
77
|
+
* - If the extension or extensions parent is not a bundle changes are skipped
|
|
78
|
+
* - If a bundles status is toggled, all children are set to that status
|
|
79
|
+
* - If an entries status is toggled, then if the:
|
|
80
|
+
* - Parent bundle is non-partial throws UnprocessableContentError
|
|
81
|
+
* - Entry status change resulted in all children being disabled then the parent bundle is disabled
|
|
82
|
+
* - Entry status change resulted in at least one child being enabled then the parent bundle is enabled
|
|
83
|
+
*/
|
|
84
|
+
async checkBundleAndSyncStatus(trx, extension) {
|
|
85
|
+
if (extension.bundle === null) {
|
|
86
|
+
if (extension.schema?.type === 'bundle') {
|
|
87
|
+
await trx('directus_extensions')
|
|
88
|
+
.update({ enabled: extension.meta.enabled })
|
|
89
|
+
.where('name', 'LIKE', this.getKey(extension.name, '%'));
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const parent = await this.readOne(null, extension.bundle);
|
|
94
|
+
if (parent.schema?.type !== 'bundle') {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (parent.schema.partial === false) {
|
|
98
|
+
throw new UnprocessableContentError({
|
|
99
|
+
reason: 'Unable to toggle status of an entry for a bundle marked as non partial',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const child = await trx('directus_extensions')
|
|
103
|
+
.where('name', 'LIKE', this.getKey(extension.bundle, '%'))
|
|
104
|
+
.where({ enabled: true })
|
|
105
|
+
.first();
|
|
106
|
+
if (!child && parent.meta.enabled) {
|
|
107
|
+
await trx('directus_extensions').update({ enabled: false }).where({ name: parent.name });
|
|
108
|
+
}
|
|
109
|
+
else if (child && !parent.meta.enabled) {
|
|
110
|
+
await trx('directus_extensions').update({ enabled: true }).where({ name: parent.name });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
78
113
|
/**
|
|
79
114
|
* Combine the settings stored in the database with the information available from the installed
|
|
80
115
|
* extensions into the standardized extensions api output
|
|
@@ -128,7 +163,7 @@ export class ExtensionsService {
|
|
|
128
163
|
return {
|
|
129
164
|
name,
|
|
130
165
|
bundle: bundleName,
|
|
131
|
-
schema: schema ? pick(schema, 'type', 'local', 'version') : null,
|
|
166
|
+
schema: schema ? pick(schema, 'type', 'local', 'version', 'partial') : null,
|
|
132
167
|
meta: omit(meta, 'name'),
|
|
133
168
|
};
|
|
134
169
|
});
|
package/dist/services/fields.js
CHANGED
|
@@ -319,14 +319,19 @@ export class FieldsService {
|
|
|
319
319
|
}
|
|
320
320
|
if (hookAdjustedField.schema) {
|
|
321
321
|
const existingColumn = await this.schemaInspector.columnInfo(collection, hookAdjustedField.field);
|
|
322
|
+
if (hookAdjustedField.schema?.is_nullable === true && existingColumn.is_primary_key) {
|
|
323
|
+
throw new InvalidPayloadError({ reason: 'Primary key cannot be null' });
|
|
324
|
+
}
|
|
322
325
|
// Sanitize column only when applying snapshot diff as opts is only passed from /utils/apply-diff.ts
|
|
323
326
|
const columnToCompare = opts?.bypassLimits && opts.autoPurgeSystemCache === false ? sanitizeColumn(existingColumn) : existingColumn;
|
|
324
327
|
if (!isEqual(columnToCompare, hookAdjustedField.schema)) {
|
|
325
328
|
try {
|
|
326
|
-
await this.knex.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
329
|
+
await this.knex.transaction(async (trx) => {
|
|
330
|
+
await trx.schema.alterTable(collection, async (table) => {
|
|
331
|
+
if (!hookAdjustedField.schema)
|
|
332
|
+
return;
|
|
333
|
+
this.addColumnToTable(table, field, existingColumn);
|
|
334
|
+
});
|
|
330
335
|
});
|
|
331
336
|
}
|
|
332
337
|
catch (err) {
|
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 {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { Accountability, File, Query, SchemaOverview } from '@directus/types';
|
|
3
3
|
import type { Knex } from 'knex';
|
|
4
4
|
import type { Readable } from 'node:stream';
|
|
5
|
-
import type { AbstractServiceOptions } from '
|
|
5
|
+
import type { AbstractServiceOptions } from '../types/index.js';
|
|
6
6
|
type ExportFormat = 'csv' | 'json' | 'xml' | 'yaml';
|
|
7
7
|
export declare class ImportService {
|
|
8
8
|
knex: Knex;
|
|
@@ -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';
|
|
@@ -8,18 +10,18 @@ import { createReadStream } from 'node:fs';
|
|
|
8
10
|
import { appendFile } from 'node:fs/promises';
|
|
9
11
|
import Papa from 'papaparse';
|
|
10
12
|
import StreamArray from 'stream-json/streamers/StreamArray.js';
|
|
11
|
-
import getDatabase from '
|
|
12
|
-
import emitter from '
|
|
13
|
-
import
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
import getDatabase from '../database/index.js';
|
|
14
|
+
import emitter from '../emitter.js';
|
|
15
|
+
import { useLogger } from '../logger.js';
|
|
16
|
+
import { getDateFormatted } from '../utils/get-date-formatted.js';
|
|
17
|
+
import { Url } from '../utils/url.js';
|
|
18
|
+
import { userName } from '../utils/user-name.js';
|
|
19
|
+
import { FilesService } from './files.js';
|
|
20
|
+
import { ItemsService } from './items.js';
|
|
21
|
+
import { NotificationsService } from './notifications.js';
|
|
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/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export * from './files.js';
|
|
|
10
10
|
export * from './flows.js';
|
|
11
11
|
export * from './folders.js';
|
|
12
12
|
export * from './graphql/index.js';
|
|
13
|
-
export * from './import-export
|
|
13
|
+
export * from './import-export.js';
|
|
14
14
|
export * from './items.js';
|
|
15
15
|
export * from './mail/index.js';
|
|
16
16
|
export * from './meta.js';
|
package/dist/services/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export * from './files.js';
|
|
|
10
10
|
export * from './flows.js';
|
|
11
11
|
export * from './folders.js';
|
|
12
12
|
export * from './graphql/index.js';
|
|
13
|
-
export * from './import-export
|
|
13
|
+
export * from './import-export.js';
|
|
14
14
|
export * from './items.js';
|
|
15
15
|
export * from './mail/index.js';
|
|
16
16
|
export * from './meta.js';
|
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 {
|