@directus/api 20.1.0 → 21.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 +2 -2
- package/dist/auth/drivers/ldap.js +1 -1
- package/dist/auth/drivers/oauth2.js +1 -1
- package/dist/auth/drivers/openid.js +1 -1
- package/dist/auth/drivers/saml.js +1 -1
- package/dist/auth.js +1 -1
- package/dist/cache.d.ts +0 -1
- package/dist/cache.js +8 -23
- package/dist/cli/commands/bootstrap/index.js +1 -1
- package/dist/cli/commands/count/index.js +1 -1
- package/dist/cli/commands/database/install.js +1 -1
- package/dist/cli/commands/database/migrate.js +1 -1
- package/dist/cli/commands/roles/create.js +1 -1
- package/dist/cli/commands/schema/apply.js +1 -1
- package/dist/cli/commands/schema/snapshot.js +1 -1
- package/dist/cli/commands/users/create.js +1 -1
- package/dist/cli/commands/users/passwd.js +1 -1
- package/dist/cli/load-extensions.js +1 -1
- package/dist/constants.js +2 -2
- package/dist/controllers/assets.js +1 -1
- package/dist/controllers/auth.js +1 -1
- package/dist/controllers/files.js +1 -1
- package/dist/controllers/schema.js +1 -1
- package/dist/controllers/tus.js +5 -3
- package/dist/database/index.js +11 -7
- package/dist/database/migrations/20210518A-add-foreign-key-constraints.js +1 -1
- package/dist/database/migrations/20210519A-add-system-fk-triggers.js +1 -1
- package/dist/database/migrations/20210802A-replace-groups.js +1 -1
- package/dist/database/migrations/20230721A-require-shares-fields.js +1 -1
- package/dist/database/migrations/20240305A-change-useragent-type.js +1 -1
- package/dist/database/migrations/20240716A-update-files-date-fields.d.ts +3 -0
- package/dist/database/migrations/20240716A-update-files-date-fields.js +33 -0
- package/dist/database/migrations/run.js +1 -1
- package/dist/emitter.js +1 -1
- package/dist/extensions/lib/get-shared-deps-mapping.js +1 -1
- package/dist/extensions/lib/installation/manager.js +1 -1
- package/dist/extensions/lib/sandbox/register/call-reference.js +1 -1
- package/dist/extensions/lib/sandbox/sdk/generators/log.js +1 -1
- package/dist/extensions/lib/sync-extensions.js +1 -1
- package/dist/extensions/manager.js +1 -1
- package/dist/flows.js +1 -1
- package/dist/{logger.js → logger/index.js} +3 -9
- package/dist/logger/redact-query.d.ts +1 -0
- package/dist/logger/redact-query.js +13 -0
- package/dist/mailer.js +1 -1
- package/dist/middleware/cache.js +1 -1
- package/dist/middleware/check-ip.js +1 -1
- package/dist/middleware/error-handler.d.ts +2 -2
- package/dist/middleware/error-handler.js +55 -52
- package/dist/middleware/rate-limiter-global.js +1 -1
- package/dist/middleware/respond.js +1 -1
- package/dist/operations/log/index.js +1 -1
- package/dist/operations/mail/index.js +1 -1
- package/dist/request/is-denied-ip.js +1 -1
- package/dist/server.js +1 -1
- package/dist/services/activity.js +1 -1
- package/dist/services/assets.js +3 -6
- package/dist/services/fields.d.ts +3 -0
- package/dist/services/fields.js +29 -5
- package/dist/services/files/lib/get-sharp-instance.d.ts +2 -0
- package/dist/services/files/lib/get-sharp-instance.js +10 -0
- package/dist/services/files/utils/get-metadata.js +8 -7
- package/dist/services/files.js +6 -1
- package/dist/services/graphql/utils/process-error.js +1 -1
- package/dist/services/graphql/utils/sanitize-gql-schema.js +1 -1
- package/dist/services/import-export.js +1 -1
- package/dist/services/mail/index.d.ts +1 -1
- package/dist/services/mail/index.js +10 -2
- package/dist/services/notifications.js +1 -1
- package/dist/services/relations.d.ts +3 -1
- package/dist/services/relations.js +27 -5
- package/dist/services/server.js +1 -1
- package/dist/services/shares.js +1 -1
- package/dist/services/tus/data-store.js +5 -6
- package/dist/services/tus/server.d.ts +1 -1
- package/dist/services/tus/server.js +9 -2
- package/dist/services/users.js +1 -1
- package/dist/services/webhooks.js +1 -1
- package/dist/telemetry/lib/track.js +1 -1
- package/dist/utils/apply-diff.js +1 -1
- package/dist/utils/apply-query.js +8 -2
- package/dist/utils/delete-from-require-cache.js +1 -1
- package/dist/utils/get-default-value.js +1 -1
- package/dist/utils/get-ip-from-req.js +1 -1
- package/dist/utils/get-permissions.js +1 -1
- package/dist/utils/get-schema.js +4 -4
- package/dist/utils/is-url-allowed.js +1 -1
- package/dist/utils/sanitize-query.js +1 -1
- package/dist/utils/transaction.js +1 -1
- package/dist/utils/validate-env.js +1 -1
- package/dist/utils/validate-storage.js +1 -1
- package/dist/websocket/controllers/base.js +1 -1
- package/dist/websocket/controllers/graphql.js +1 -1
- package/dist/websocket/controllers/rest.js +1 -1
- package/dist/websocket/errors.js +1 -1
- package/package.json +32 -32
- /package/dist/{logger.d.ts → logger/index.d.ts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InvalidIpError } from '@directus/errors';
|
|
2
2
|
import getDatabase from '../database/index.js';
|
|
3
|
-
import { useLogger } from '../logger.js';
|
|
3
|
+
import { useLogger } from '../logger/index.js';
|
|
4
4
|
import asyncHandler from '../utils/async-handler.js';
|
|
5
5
|
import { ipInNetworks } from '../utils/ip-in-networks.js';
|
|
6
6
|
export const checkIP = asyncHandler(async (req, _res, next) => {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
+
/// <reference types="qs" />
|
|
1
2
|
import type { ErrorRequestHandler } from 'express';
|
|
2
|
-
declare const errorHandler: ErrorRequestHandler
|
|
3
|
-
export default errorHandler;
|
|
3
|
+
export declare const errorHandler: (err: any, req: import("express-serve-static-core").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: import("express-serve-static-core").Response<any, Record<string, any>, number>, next: import("express-serve-static-core").NextFunction) => Promise<ReturnType<ErrorRequestHandler>>;
|
|
@@ -1,40 +1,38 @@
|
|
|
1
|
-
import { ErrorCode,
|
|
2
|
-
import { isObject
|
|
1
|
+
import { ErrorCode, InternalServerError, isDirectusError } from '@directus/errors';
|
|
2
|
+
import { isObject } from '@directus/utils';
|
|
3
3
|
import { getNodeEnv } from '@directus/utils/node';
|
|
4
4
|
import getDatabase from '../database/index.js';
|
|
5
5
|
import emitter from '../emitter.js';
|
|
6
|
-
import { useLogger } from '../logger.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const errorHandler = (err, req, res, _next) => {
|
|
6
|
+
import { useLogger } from '../logger/index.js';
|
|
7
|
+
const FALLBACK_ERROR = new InternalServerError();
|
|
8
|
+
export const errorHandler = asyncErrorHandler(async (err, req, res) => {
|
|
10
9
|
const logger = useLogger();
|
|
11
|
-
let
|
|
12
|
-
errors: [],
|
|
13
|
-
};
|
|
14
|
-
const errors = toArray(err);
|
|
10
|
+
let errors = [];
|
|
15
11
|
let status = null;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
}
|
|
12
|
+
// It can be assumed that at least one error is given
|
|
13
|
+
const receivedErrors = Array.isArray(err) ? err : [err];
|
|
14
|
+
for (const error of receivedErrors) {
|
|
15
|
+
// In dev mode, if available, expose stack trace under error's extensions data
|
|
16
|
+
if (getNodeEnv() === 'development' && error instanceof Error && error.stack) {
|
|
17
|
+
(error.extensions ??= {})['stack'] = error.stack;
|
|
24
18
|
}
|
|
25
19
|
if (isDirectusError(error)) {
|
|
26
20
|
logger.debug(error);
|
|
27
|
-
if (
|
|
21
|
+
if (status === null) {
|
|
22
|
+
// Use current error status as response status
|
|
28
23
|
status = error.status;
|
|
29
24
|
}
|
|
30
25
|
else if (status !== error.status) {
|
|
31
|
-
status
|
|
26
|
+
// Fallback if status has already been set by a preceding error
|
|
27
|
+
// and doesn't match the current one
|
|
28
|
+
status = FALLBACK_ERROR.status;
|
|
32
29
|
}
|
|
33
|
-
|
|
30
|
+
errors.push({
|
|
34
31
|
message: error.message,
|
|
35
32
|
extensions: {
|
|
36
|
-
code: error.code,
|
|
37
33
|
...(error.extensions ?? {}),
|
|
34
|
+
// Expose error code under error's extensions data
|
|
35
|
+
code: error.code,
|
|
38
36
|
},
|
|
39
37
|
});
|
|
40
38
|
if (isDirectusError(error, ErrorCode.MethodNotAllowed)) {
|
|
@@ -43,45 +41,50 @@ const errorHandler = (err, req, res, _next) => {
|
|
|
43
41
|
}
|
|
44
42
|
else {
|
|
45
43
|
logger.error(error);
|
|
46
|
-
status =
|
|
44
|
+
status = FALLBACK_ERROR.status;
|
|
47
45
|
if (req.accountability?.admin === true) {
|
|
48
46
|
const localError = isObject(error) ? error : {};
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
},
|
|
47
|
+
// Use 'message' prop if available, otherwise if 'error' is a string use that
|
|
48
|
+
const message = (typeof localError['message'] === 'string' ? localError['message'] : null) ??
|
|
49
|
+
(typeof error === 'string' ? error : null);
|
|
50
|
+
errors = [
|
|
51
|
+
{
|
|
52
|
+
message: message || FALLBACK_ERROR.message,
|
|
53
|
+
extensions: {
|
|
54
|
+
code: FALLBACK_ERROR.code,
|
|
55
|
+
...(localError['extensions'] ?? {}),
|
|
58
56
|
},
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
},
|
|
58
|
+
];
|
|
61
59
|
}
|
|
62
60
|
else {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
message: 'An unexpected error occurred.',
|
|
67
|
-
extensions: {
|
|
68
|
-
code: 'INTERNAL_SERVER_ERROR',
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
};
|
|
61
|
+
// Don't expose unknown errors to non-admin users
|
|
62
|
+
errors = [{ message: FALLBACK_ERROR.message, extensions: { code: FALLBACK_ERROR.code } }];
|
|
73
63
|
}
|
|
74
64
|
}
|
|
75
65
|
}
|
|
76
|
-
res.status(status ??
|
|
77
|
-
emitter
|
|
78
|
-
.emitFilter('request.error', payload.errors, {}, {
|
|
66
|
+
res.status(status ?? FALLBACK_ERROR.status);
|
|
67
|
+
const updatedErrors = await emitter.emitFilter('request.error', errors, {}, {
|
|
79
68
|
database: getDatabase(),
|
|
80
69
|
schema: req.schema,
|
|
81
70
|
accountability: req.accountability ?? null,
|
|
82
|
-
})
|
|
83
|
-
.then((updatedErrors) => {
|
|
84
|
-
return res.json({ ...payload, errors: updatedErrors });
|
|
85
71
|
});
|
|
86
|
-
};
|
|
87
|
-
|
|
72
|
+
return res.json({ errors: updatedErrors });
|
|
73
|
+
});
|
|
74
|
+
function asyncErrorHandler(fn) {
|
|
75
|
+
return (err, req, res, next) => fn(err, req, res, next).catch((error) => {
|
|
76
|
+
// To be on the safe side and ensure this doesn't lead to an unhandled (potentially crashing) error
|
|
77
|
+
try {
|
|
78
|
+
const logger = useLogger();
|
|
79
|
+
logger.error(error, 'Unexpected error in error handler');
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Ignore
|
|
83
|
+
}
|
|
84
|
+
// Delegate to default error handler to close the connection
|
|
85
|
+
if (res.headersSent)
|
|
86
|
+
return next(err);
|
|
87
|
+
res.status(FALLBACK_ERROR.status);
|
|
88
|
+
return res.json({ errors: [{ message: FALLBACK_ERROR.message, extensions: { code: FALLBACK_ERROR.code } }] });
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
2
|
import { HitRateLimitError } from '@directus/errors';
|
|
3
|
-
import { useLogger } from '../logger.js';
|
|
3
|
+
import { useLogger } from '../logger/index.js';
|
|
4
4
|
import { createRateLimiter } from '../rate-limiter.js';
|
|
5
5
|
import asyncHandler from '../utils/async-handler.js';
|
|
6
6
|
import { validateEnv } from '../utils/validate-env.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
2
|
import { parse as parseBytesConfiguration } from 'bytes';
|
|
3
3
|
import { getCache, setCacheValue } from '../cache.js';
|
|
4
|
-
import { useLogger } from '../logger.js';
|
|
4
|
+
import { useLogger } from '../logger/index.js';
|
|
5
5
|
import { ExportService } from '../services/import-export.js';
|
|
6
6
|
import asyncHandler from '../utils/async-handler.js';
|
|
7
7
|
import { getCacheControlHeader } from '../utils/get-cache-headers.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineOperationApi } from '@directus/extensions';
|
|
2
2
|
import { optionToString } from '@directus/utils';
|
|
3
|
-
import { useLogger } from '../../logger.js';
|
|
3
|
+
import { useLogger } from '../../logger/index.js';
|
|
4
4
|
export default defineOperationApi({
|
|
5
5
|
id: 'log',
|
|
6
6
|
handler: ({ message }) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineOperationApi } from '@directus/extensions';
|
|
2
2
|
import { MailService } from '../../services/mail/index.js';
|
|
3
3
|
import { md } from '../../utils/md.js';
|
|
4
|
-
import { useLogger } from '../../logger.js';
|
|
4
|
+
import { useLogger } from '../../logger/index.js';
|
|
5
5
|
const logger = useLogger();
|
|
6
6
|
export default defineOperationApi({
|
|
7
7
|
id: 'mail',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
2
|
import os from 'node:os';
|
|
3
|
-
import { useLogger } from '../logger.js';
|
|
3
|
+
import { useLogger } from '../logger/index.js';
|
|
4
4
|
import { ipInNetworks } from '../utils/ip-in-networks.js';
|
|
5
5
|
export function isDeniedIp(ip) {
|
|
6
6
|
const env = useEnv();
|
package/dist/server.js
CHANGED
|
@@ -10,7 +10,7 @@ import url from 'url';
|
|
|
10
10
|
import createApp from './app.js';
|
|
11
11
|
import getDatabase from './database/index.js';
|
|
12
12
|
import emitter from './emitter.js';
|
|
13
|
-
import { useLogger } from './logger.js';
|
|
13
|
+
import { useLogger } from './logger/index.js';
|
|
14
14
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
15
15
|
import { getIPFromReq } from './utils/get-ip-from-req.js';
|
|
16
16
|
import { createSubscriptionController, createWebSocketController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
|
|
@@ -2,7 +2,7 @@ import { Action } from '@directus/constants';
|
|
|
2
2
|
import { useEnv } from '@directus/env';
|
|
3
3
|
import { ErrorCode, isDirectusError } from '@directus/errors';
|
|
4
4
|
import { uniq } from 'lodash-es';
|
|
5
|
-
import { useLogger } from '../logger.js';
|
|
5
|
+
import { useLogger } from '../logger/index.js';
|
|
6
6
|
import { getPermissions } from '../utils/get-permissions.js';
|
|
7
7
|
import { isValidUuid } from '../utils/is-valid-uuid.js';
|
|
8
8
|
import { Url } from '../utils/url.js';
|
package/dist/services/assets.js
CHANGED
|
@@ -7,13 +7,14 @@ import path from 'path';
|
|
|
7
7
|
import sharp from 'sharp';
|
|
8
8
|
import { SUPPORTED_IMAGE_TRANSFORM_FORMATS } from '../constants.js';
|
|
9
9
|
import getDatabase from '../database/index.js';
|
|
10
|
-
import { useLogger } from '../logger.js';
|
|
10
|
+
import { useLogger } from '../logger/index.js';
|
|
11
11
|
import { getStorage } from '../storage/index.js';
|
|
12
12
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
13
13
|
import { isValidUuid } from '../utils/is-valid-uuid.js';
|
|
14
14
|
import * as TransformationUtils from '../utils/transformations.js';
|
|
15
15
|
import { AuthorizationService } from './authorization.js';
|
|
16
16
|
import { FilesService } from './files.js';
|
|
17
|
+
import { getSharpInstance } from './files/lib/get-sharp-instance.js';
|
|
17
18
|
const env = useEnv();
|
|
18
19
|
const logger = useLogger();
|
|
19
20
|
export class AssetsService {
|
|
@@ -116,11 +117,7 @@ export class AssetsService {
|
|
|
116
117
|
});
|
|
117
118
|
}
|
|
118
119
|
const readStream = await storage.location(file.storage).read(file.filename_disk, range);
|
|
119
|
-
const transformer =
|
|
120
|
-
limitInputPixels: Math.pow(env['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION'], 2),
|
|
121
|
-
sequentialRead: true,
|
|
122
|
-
failOn: env['ASSETS_INVALID_IMAGE_SENSITIVITY_LEVEL'],
|
|
123
|
-
});
|
|
120
|
+
const transformer = getSharpInstance();
|
|
124
121
|
transformer.timeout({
|
|
125
122
|
seconds: clamp(Math.round(getMilliseconds(env['ASSETS_TRANSFORM_TIMEOUT'], 0) / 1000), 1, 3600),
|
|
126
123
|
});
|
|
@@ -16,8 +16,11 @@ export declare class FieldsService {
|
|
|
16
16
|
schema: SchemaOverview;
|
|
17
17
|
cache: Keyv<any> | null;
|
|
18
18
|
systemCache: Keyv<any>;
|
|
19
|
+
schemaCache: Keyv<any>;
|
|
19
20
|
constructor(options: AbstractServiceOptions);
|
|
20
21
|
private get hasReadAccess();
|
|
22
|
+
columnInfo(collection?: string): Promise<Column[]>;
|
|
23
|
+
columnInfo(collection: string, field: string): Promise<Column>;
|
|
21
24
|
readAll(collection?: string): Promise<Field[]>;
|
|
22
25
|
readOne(collection: string, field: string): Promise<Record<string, any>>;
|
|
23
26
|
createField(collection: string, field: Partial<Field> & {
|
package/dist/services/fields.js
CHANGED
|
@@ -3,7 +3,7 @@ import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
|
3
3
|
import { createInspector } from '@directus/schema';
|
|
4
4
|
import { addFieldFlag, toArray } from '@directus/utils';
|
|
5
5
|
import { isEqual, isNil, merge } from 'lodash-es';
|
|
6
|
-
import { clearSystemCache, getCache } from '../cache.js';
|
|
6
|
+
import { clearSystemCache, getCache, getCacheValue, setCacheValue } from '../cache.js';
|
|
7
7
|
import { ALIAS_TYPES } from '../constants.js';
|
|
8
8
|
import { translateDatabaseError } from '../database/errors/translate.js';
|
|
9
9
|
import { getHelpers } from '../database/helpers/index.js';
|
|
@@ -19,7 +19,9 @@ import { transaction } from '../utils/transaction.js';
|
|
|
19
19
|
import { ItemsService } from './items.js';
|
|
20
20
|
import { PayloadService } from './payload.js';
|
|
21
21
|
import { RelationsService } from './relations.js';
|
|
22
|
+
import { useEnv } from '@directus/env';
|
|
22
23
|
const systemFieldRows = getSystemFieldRowsWithAuthProviders();
|
|
24
|
+
const env = useEnv();
|
|
23
25
|
export class FieldsService {
|
|
24
26
|
knex;
|
|
25
27
|
helpers;
|
|
@@ -30,6 +32,7 @@ export class FieldsService {
|
|
|
30
32
|
schema;
|
|
31
33
|
cache;
|
|
32
34
|
systemCache;
|
|
35
|
+
schemaCache;
|
|
33
36
|
constructor(options) {
|
|
34
37
|
this.knex = options.knex || getDatabase();
|
|
35
38
|
this.helpers = getHelpers(this.knex);
|
|
@@ -38,15 +41,36 @@ export class FieldsService {
|
|
|
38
41
|
this.itemsService = new ItemsService('directus_fields', options);
|
|
39
42
|
this.payloadService = new PayloadService('directus_fields', options);
|
|
40
43
|
this.schema = options.schema;
|
|
41
|
-
const { cache, systemCache } = getCache();
|
|
44
|
+
const { cache, systemCache, localSchemaCache } = getCache();
|
|
42
45
|
this.cache = cache;
|
|
43
46
|
this.systemCache = systemCache;
|
|
47
|
+
this.schemaCache = localSchemaCache;
|
|
44
48
|
}
|
|
45
49
|
get hasReadAccess() {
|
|
46
50
|
return !!this.accountability?.permissions?.find((permission) => {
|
|
47
51
|
return permission.collection === 'directus_fields' && permission.action === 'read';
|
|
48
52
|
});
|
|
49
53
|
}
|
|
54
|
+
async columnInfo(collection, field) {
|
|
55
|
+
const schemaCacheIsEnabled = Boolean(env['CACHE_SCHEMA']);
|
|
56
|
+
let columnInfo = null;
|
|
57
|
+
if (schemaCacheIsEnabled) {
|
|
58
|
+
columnInfo = await getCacheValue(this.schemaCache, 'columnInfo');
|
|
59
|
+
}
|
|
60
|
+
if (!columnInfo) {
|
|
61
|
+
columnInfo = await this.schemaInspector.columnInfo();
|
|
62
|
+
if (schemaCacheIsEnabled) {
|
|
63
|
+
setCacheValue(this.schemaCache, 'columnInfo', columnInfo);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (collection) {
|
|
67
|
+
columnInfo = columnInfo.filter((column) => column.table === collection);
|
|
68
|
+
}
|
|
69
|
+
if (field) {
|
|
70
|
+
return columnInfo.find((column) => column.name === field);
|
|
71
|
+
}
|
|
72
|
+
return columnInfo;
|
|
73
|
+
}
|
|
50
74
|
async readAll(collection) {
|
|
51
75
|
let fields;
|
|
52
76
|
if (this.accountability && this.accountability.admin !== true && this.hasReadAccess === false) {
|
|
@@ -67,7 +91,7 @@ export class FieldsService {
|
|
|
67
91
|
fields = (await nonAuthorizedItemsService.readByQuery({ limit: -1 }));
|
|
68
92
|
fields.push(...systemFieldRows);
|
|
69
93
|
}
|
|
70
|
-
const columns = (await this.
|
|
94
|
+
const columns = (await this.columnInfo(collection)).map((column) => ({
|
|
71
95
|
...column,
|
|
72
96
|
default_value: getDefaultValue(column, fields.find((field) => field.collection === column.table && field.field === column.name)),
|
|
73
97
|
}));
|
|
@@ -175,7 +199,7 @@ export class FieldsService {
|
|
|
175
199
|
fieldInfo ||
|
|
176
200
|
systemFieldRows.find((fieldMeta) => fieldMeta.collection === collection && fieldMeta.field === field);
|
|
177
201
|
try {
|
|
178
|
-
column = await this.
|
|
202
|
+
column = await this.columnInfo(collection, field);
|
|
179
203
|
}
|
|
180
204
|
catch {
|
|
181
205
|
// Do nothing
|
|
@@ -330,7 +354,7 @@ export class FieldsService {
|
|
|
330
354
|
throw new InvalidPayloadError({ reason: 'Alias type cannot be changed' });
|
|
331
355
|
}
|
|
332
356
|
if (hookAdjustedField.schema) {
|
|
333
|
-
const existingColumn = await this.
|
|
357
|
+
const existingColumn = await this.columnInfo(collection, hookAdjustedField.field);
|
|
334
358
|
if (hookAdjustedField.schema?.is_nullable === true && existingColumn.is_primary_key) {
|
|
335
359
|
throw new InvalidPayloadError({ reason: 'Primary key cannot be null' });
|
|
336
360
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import sharp, {} from 'sharp';
|
|
3
|
+
export function getSharpInstance() {
|
|
4
|
+
const env = useEnv();
|
|
5
|
+
return sharp({
|
|
6
|
+
limitInputPixels: Math.trunc(Math.pow(env['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION'], 2)),
|
|
7
|
+
sequentialRead: true,
|
|
8
|
+
failOn: env['ASSETS_INVALID_IMAGE_SENSITIVITY_LEVEL'],
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import exif, {} from 'exif-reader';
|
|
2
3
|
import { parse as parseIcc } from 'icc';
|
|
3
4
|
import { pick } from 'lodash-es';
|
|
4
5
|
import { pipeline } from 'node:stream/promises';
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import { useLogger } from '../../../logger.js';
|
|
6
|
+
import { useLogger } from '../../../logger/index.js';
|
|
7
|
+
import { getSharpInstance } from '../lib/get-sharp-instance.js';
|
|
8
8
|
import { parseIptc, parseXmp } from './parse-image-metadata.js';
|
|
9
9
|
const env = useEnv();
|
|
10
10
|
const logger = useLogger();
|
|
11
11
|
export async function getMetadata(stream, allowList = env['FILE_METADATA_ALLOW_LIST']) {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const transformer = getSharpInstance();
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
pipeline(stream, transformer.metadata(async (err, sharpMetadata) => {
|
|
14
15
|
if (err) {
|
|
15
|
-
|
|
16
|
-
return;
|
|
16
|
+
logger.error(err);
|
|
17
|
+
return resolve({});
|
|
17
18
|
}
|
|
18
19
|
const metadata = {};
|
|
19
20
|
if (sharpMetadata.orientation && sharpMetadata.orientation >= 5) {
|
package/dist/services/files.js
CHANGED
|
@@ -11,7 +11,7 @@ import path from 'path';
|
|
|
11
11
|
import url from 'url';
|
|
12
12
|
import { RESUMABLE_UPLOADS } from '../constants.js';
|
|
13
13
|
import emitter from '../emitter.js';
|
|
14
|
-
import { useLogger } from '../logger.js';
|
|
14
|
+
import { useLogger } from '../logger/index.js';
|
|
15
15
|
import { getAxios } from '../request/index.js';
|
|
16
16
|
import { getStorage } from '../storage/index.js';
|
|
17
17
|
import { extractMetadata } from './files/lib/extract-metadata.js';
|
|
@@ -57,6 +57,10 @@ export class FilesService extends ItemsService {
|
|
|
57
57
|
const fileExtension = path.extname(payload.filename_download) || (payload.type && '.' + extension(payload.type)) || '';
|
|
58
58
|
// The filename_disk is the FINAL filename on disk
|
|
59
59
|
payload.filename_disk ||= primaryKey + (fileExtension || '');
|
|
60
|
+
// If the filename_disk extension doesn't match the new mimetype, update it
|
|
61
|
+
if (isReplacement === true && path.extname(payload.filename_disk) !== fileExtension) {
|
|
62
|
+
payload.filename_disk = primaryKey + (fileExtension || '');
|
|
63
|
+
}
|
|
60
64
|
// Temp filename is used for replacements
|
|
61
65
|
const tempFilenameDisk = 'temp_' + payload.filename_disk;
|
|
62
66
|
if (!payload.type) {
|
|
@@ -125,6 +129,7 @@ export class FilesService extends ItemsService {
|
|
|
125
129
|
const { size } = await storage.location(data.storage).stat(payload.filename_disk);
|
|
126
130
|
payload.filesize = size;
|
|
127
131
|
const metadata = await extractMetadata(data.storage, payload);
|
|
132
|
+
payload.uploaded_on = new Date().toISOString();
|
|
128
133
|
// We do this in a service without accountability. Even if you don't have update permissions to the file,
|
|
129
134
|
// we still want to be able to set the extracted values from the file on create
|
|
130
135
|
const sudoService = new ItemsService('directus_files', {
|
|
@@ -14,7 +14,7 @@ import Papa from 'papaparse';
|
|
|
14
14
|
import StreamArray from 'stream-json/streamers/StreamArray.js';
|
|
15
15
|
import getDatabase from '../database/index.js';
|
|
16
16
|
import emitter from '../emitter.js';
|
|
17
|
-
import { useLogger } from '../logger.js';
|
|
17
|
+
import { useLogger } from '../logger/index.js';
|
|
18
18
|
import { getDateFormatted } from '../utils/get-date-formatted.js';
|
|
19
19
|
import { getService } from '../utils/get-service.js';
|
|
20
20
|
import { transaction } from '../utils/transaction.js';
|
|
@@ -14,7 +14,7 @@ export declare class MailService {
|
|
|
14
14
|
knex: Knex;
|
|
15
15
|
mailer: Transporter;
|
|
16
16
|
constructor(opts: AbstractServiceOptions);
|
|
17
|
-
send<T>(options: EmailOptions): Promise<T>;
|
|
17
|
+
send<T>(options: EmailOptions): Promise<T | null>;
|
|
18
18
|
private renderTemplate;
|
|
19
19
|
private getDefaultTemplateData;
|
|
20
20
|
}
|
|
@@ -5,9 +5,10 @@ import { Liquid } from 'liquidjs';
|
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import getDatabase from '../../database/index.js';
|
|
8
|
-
import { useLogger } from '../../logger.js';
|
|
8
|
+
import { useLogger } from '../../logger/index.js';
|
|
9
9
|
import getMailer from '../../mailer.js';
|
|
10
10
|
import { Url } from '../../utils/url.js';
|
|
11
|
+
import emitter from '../../emitter.js';
|
|
11
12
|
const env = useEnv();
|
|
12
13
|
const logger = useLogger();
|
|
13
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -35,7 +36,14 @@ export class MailService {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
async send(options) {
|
|
38
|
-
const
|
|
39
|
+
const payload = await emitter.emitFilter(`email.send`, options, {
|
|
40
|
+
database: getDatabase(),
|
|
41
|
+
schema: null,
|
|
42
|
+
accountability: null,
|
|
43
|
+
});
|
|
44
|
+
if (!payload)
|
|
45
|
+
return null;
|
|
46
|
+
const { template, ...emailOptions } = payload;
|
|
39
47
|
let { html } = options;
|
|
40
48
|
const defaultTemplateData = await this.getDefaultTemplateData();
|
|
41
49
|
const from = `${defaultTemplateData.projectName} <${options.from || env['EMAIL_FROM']}>`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SchemaInspector } from '@directus/schema';
|
|
1
|
+
import type { ForeignKey, SchemaInspector } from '@directus/schema';
|
|
2
2
|
import type { Accountability, Relation, RelationMeta, SchemaOverview } from '@directus/types';
|
|
3
3
|
import type Keyv from 'keyv';
|
|
4
4
|
import type { Knex } from 'knex';
|
|
@@ -14,8 +14,10 @@ export declare class RelationsService {
|
|
|
14
14
|
schema: SchemaOverview;
|
|
15
15
|
relationsItemService: ItemsService<RelationMeta>;
|
|
16
16
|
systemCache: Keyv<any>;
|
|
17
|
+
schemaCache: Keyv<any>;
|
|
17
18
|
helpers: Helpers;
|
|
18
19
|
constructor(options: AbstractServiceOptions);
|
|
20
|
+
foreignKeys(collection?: string): Promise<ForeignKey[]>;
|
|
19
21
|
readAll(collection?: string, opts?: QueryOptions): Promise<Relation[]>;
|
|
20
22
|
readOne(collection: string, field: string): Promise<Relation>;
|
|
21
23
|
/**
|
|
@@ -2,7 +2,7 @@ import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
|
2
2
|
import { createInspector } from '@directus/schema';
|
|
3
3
|
import { systemRelationRows } from '@directus/system-data';
|
|
4
4
|
import { toArray } from '@directus/utils';
|
|
5
|
-
import { clearSystemCache, getCache } from '../cache.js';
|
|
5
|
+
import { clearSystemCache, getCache, getCacheValue, setCacheValue } from '../cache.js';
|
|
6
6
|
import { getHelpers } from '../database/helpers/index.js';
|
|
7
7
|
import getDatabase, { getSchemaInspector } from '../database/index.js';
|
|
8
8
|
import emitter from '../emitter.js';
|
|
@@ -11,6 +11,8 @@ import { getSchema } from '../utils/get-schema.js';
|
|
|
11
11
|
import { transaction } from '../utils/transaction.js';
|
|
12
12
|
import { ItemsService } from './items.js';
|
|
13
13
|
import { PermissionsService } from './permissions/index.js';
|
|
14
|
+
import { useEnv } from '@directus/env';
|
|
15
|
+
const env = useEnv();
|
|
14
16
|
export class RelationsService {
|
|
15
17
|
knex;
|
|
16
18
|
permissionsService;
|
|
@@ -19,6 +21,7 @@ export class RelationsService {
|
|
|
19
21
|
schema;
|
|
20
22
|
relationsItemService;
|
|
21
23
|
systemCache;
|
|
24
|
+
schemaCache;
|
|
22
25
|
helpers;
|
|
23
26
|
constructor(options) {
|
|
24
27
|
this.knex = options.knex || getDatabase();
|
|
@@ -33,9 +36,28 @@ export class RelationsService {
|
|
|
33
36
|
// allowed to extract the relations regardless of permissions to directus_relations. This
|
|
34
37
|
// happens in `filterForbidden` down below
|
|
35
38
|
});
|
|
36
|
-
|
|
39
|
+
const cache = getCache();
|
|
40
|
+
this.systemCache = cache.systemCache;
|
|
41
|
+
this.schemaCache = cache.localSchemaCache;
|
|
37
42
|
this.helpers = getHelpers(this.knex);
|
|
38
43
|
}
|
|
44
|
+
async foreignKeys(collection) {
|
|
45
|
+
const schemaCacheIsEnabled = Boolean(env['CACHE_SCHEMA']);
|
|
46
|
+
let foreignKeys = null;
|
|
47
|
+
if (schemaCacheIsEnabled) {
|
|
48
|
+
foreignKeys = await getCacheValue(this.schemaCache, 'foreignKeys');
|
|
49
|
+
}
|
|
50
|
+
if (!foreignKeys) {
|
|
51
|
+
foreignKeys = await this.schemaInspector.foreignKeys();
|
|
52
|
+
if (schemaCacheIsEnabled) {
|
|
53
|
+
setCacheValue(this.schemaCache, 'foreignKeys', foreignKeys);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (collection) {
|
|
57
|
+
return foreignKeys.filter((row) => row.table === collection);
|
|
58
|
+
}
|
|
59
|
+
return foreignKeys;
|
|
60
|
+
}
|
|
39
61
|
async readAll(collection, opts) {
|
|
40
62
|
if (this.accountability && this.accountability.admin !== true && this.hasReadAccess === false) {
|
|
41
63
|
throw new ForbiddenError();
|
|
@@ -58,7 +80,7 @@ export class RelationsService {
|
|
|
58
80
|
return true;
|
|
59
81
|
return metaRow.many_collection === collection;
|
|
60
82
|
});
|
|
61
|
-
const schemaRows = await this.
|
|
83
|
+
const schemaRows = await this.foreignKeys(collection);
|
|
62
84
|
const results = this.stitchRelations(metaRows, schemaRows);
|
|
63
85
|
return await this.filterForbidden(results);
|
|
64
86
|
}
|
|
@@ -95,7 +117,7 @@ export class RelationsService {
|
|
|
95
117
|
],
|
|
96
118
|
},
|
|
97
119
|
});
|
|
98
|
-
const schemaRow = (await this.
|
|
120
|
+
const schemaRow = (await this.foreignKeys(collection)).find((foreignKey) => foreignKey.column === field);
|
|
99
121
|
const stitched = this.stitchRelations(metaRow, schemaRow ? [schemaRow] : []);
|
|
100
122
|
const results = await this.filterForbidden(stitched);
|
|
101
123
|
if (results.length === 0) {
|
|
@@ -310,7 +332,7 @@ export class RelationsService {
|
|
|
310
332
|
const nestedActionEvents = [];
|
|
311
333
|
try {
|
|
312
334
|
await transaction(this.knex, async (trx) => {
|
|
313
|
-
const existingConstraints = await this.
|
|
335
|
+
const existingConstraints = await this.foreignKeys();
|
|
314
336
|
const constraintNames = existingConstraints.map((key) => key.constraint_name);
|
|
315
337
|
if (existingRelation.schema?.constraint_name &&
|
|
316
338
|
constraintNames.includes(existingRelation.schema.constraint_name)) {
|
package/dist/services/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Readable } from 'node:stream';
|
|
|
6
6
|
import { performance } from 'perf_hooks';
|
|
7
7
|
import { getCache } from '../cache.js';
|
|
8
8
|
import getDatabase, { hasDatabaseConnection } from '../database/index.js';
|
|
9
|
-
import { useLogger } from '../logger.js';
|
|
9
|
+
import { useLogger } from '../logger/index.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';
|
package/dist/services/shares.js
CHANGED
|
@@ -2,7 +2,7 @@ import { useEnv } from '@directus/env';
|
|
|
2
2
|
import { ForbiddenError, InvalidCredentialsError } from '@directus/errors';
|
|
3
3
|
import argon2 from 'argon2';
|
|
4
4
|
import jwt from 'jsonwebtoken';
|
|
5
|
-
import { useLogger } from '../logger.js';
|
|
5
|
+
import { useLogger } from '../logger/index.js';
|
|
6
6
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
7
7
|
import { getSecret } from '../utils/get-secret.js';
|
|
8
8
|
import { md } from '../utils/md.js';
|