@directus/api 19.0.1 → 19.1.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 +8 -7
- package/dist/auth/drivers/oauth2.js +3 -2
- package/dist/auth/drivers/openid.js +3 -2
- package/dist/cli/utils/create-env/env-stub.liquid +0 -3
- package/dist/cli/utils/create-env/index.js +0 -2
- package/dist/controllers/auth.js +3 -2
- package/dist/controllers/extensions.js +30 -19
- package/dist/controllers/users.js +25 -0
- package/dist/controllers/utils.js +2 -1
- package/dist/database/helpers/fn/types.js +4 -3
- package/dist/database/helpers/index.d.ts +2 -0
- package/dist/database/helpers/index.js +2 -0
- package/dist/database/helpers/number/dialects/default.d.ts +3 -0
- package/dist/database/helpers/number/dialects/default.js +3 -0
- package/dist/database/helpers/number/dialects/mssql.d.ts +7 -0
- package/dist/database/helpers/number/dialects/mssql.js +11 -0
- package/dist/database/helpers/number/dialects/oracle.d.ts +6 -0
- package/dist/database/helpers/number/dialects/oracle.js +7 -0
- package/dist/database/helpers/number/dialects/postgres.d.ts +5 -0
- package/dist/database/helpers/number/dialects/postgres.js +15 -0
- package/dist/database/helpers/number/dialects/sqlite.d.ts +6 -0
- package/dist/database/helpers/number/dialects/sqlite.js +7 -0
- package/dist/database/helpers/number/index.d.ts +7 -0
- package/dist/database/helpers/number/index.js +7 -0
- package/dist/database/helpers/number/types.d.ts +12 -0
- package/dist/database/helpers/number/types.js +9 -0
- package/dist/database/helpers/number/utils/decimal-limit.d.ts +4 -0
- package/dist/database/helpers/number/utils/decimal-limit.js +10 -0
- package/dist/database/helpers/number/utils/maybe-stringify-big-int.d.ts +1 -0
- package/dist/database/helpers/number/utils/maybe-stringify-big-int.js +6 -0
- package/dist/database/helpers/number/utils/number-in-range.d.ts +3 -0
- package/dist/database/helpers/number/utils/number-in-range.js +20 -0
- package/dist/database/migrations/20240422A-public-registration.d.ts +3 -0
- package/dist/database/migrations/20240422A-public-registration.js +14 -0
- package/dist/database/run-ast.js +5 -4
- package/dist/extensions/lib/get-extensions-settings.js +48 -11
- package/dist/extensions/lib/installation/manager.js +2 -2
- package/dist/middleware/rate-limiter-global.js +1 -1
- package/dist/middleware/rate-limiter-registration.d.ts +5 -0
- package/dist/middleware/rate-limiter-registration.js +32 -0
- package/dist/services/authentication.js +3 -2
- package/dist/services/authorization.js +4 -4
- package/dist/services/fields.js +2 -2
- package/dist/services/graphql/index.js +41 -2
- package/dist/services/mail/templates/user-registration.liquid +37 -0
- package/dist/services/meta.js +1 -1
- package/dist/services/payload.d.ts +2 -0
- package/dist/services/payload.js +16 -4
- package/dist/services/server.js +3 -1
- package/dist/services/shares.js +2 -1
- package/dist/services/users.d.ts +3 -1
- package/dist/services/users.js +92 -5
- package/dist/services/utils.d.ts +3 -1
- package/dist/services/utils.js +7 -3
- package/dist/utils/apply-query.d.ts +1 -1
- package/dist/utils/apply-query.js +54 -28
- package/dist/utils/get-accountability-for-token.js +6 -3
- package/dist/utils/get-cache-headers.js +3 -0
- package/dist/utils/get-schema.js +21 -12
- package/dist/utils/get-secret.d.ts +4 -0
- package/dist/utils/get-secret.js +14 -0
- package/dist/utils/parse-filter-key.d.ts +7 -0
- package/dist/utils/parse-filter-key.js +22 -0
- package/dist/utils/parse-numeric-string.d.ts +2 -0
- package/dist/utils/parse-numeric-string.js +21 -0
- package/dist/utils/sanitize-query.js +10 -5
- package/dist/utils/transaction.d.ts +1 -1
- package/dist/utils/transaction.js +39 -2
- package/dist/utils/validate-query.js +0 -2
- package/dist/utils/verify-session-jwt.d.ts +7 -0
- package/dist/utils/verify-session-jwt.js +22 -0
- package/dist/websocket/messages.d.ts +78 -50
- package/package.json +60 -61
- package/dist/utils/strip-function.d.ts +0 -4
- package/dist/utils/strip-function.js +0 -12
package/dist/app.js
CHANGED
|
@@ -58,20 +58,13 @@ import schema from './middleware/schema.js';
|
|
|
58
58
|
import { initTelemetry } from './telemetry/index.js';
|
|
59
59
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
60
60
|
import { Url } from './utils/url.js';
|
|
61
|
-
import { validateEnv } from './utils/validate-env.js';
|
|
62
61
|
import { validateStorage } from './utils/validate-storage.js';
|
|
63
62
|
const require = createRequire(import.meta.url);
|
|
64
63
|
export default async function createApp() {
|
|
65
64
|
const env = useEnv();
|
|
66
65
|
const logger = useLogger();
|
|
67
66
|
const helmet = await import('helmet');
|
|
68
|
-
validateEnv(['KEY', 'SECRET']);
|
|
69
|
-
if (!new Url(env['PUBLIC_URL']).isAbsolute()) {
|
|
70
|
-
logger.warn('PUBLIC_URL should be a full URL');
|
|
71
|
-
}
|
|
72
|
-
await validateStorage();
|
|
73
67
|
await validateDatabaseConnection();
|
|
74
|
-
await validateDatabaseExtensions();
|
|
75
68
|
if ((await isInstalled()) === false) {
|
|
76
69
|
logger.error(`Database doesn't have Directus tables installed.`);
|
|
77
70
|
process.exit(1);
|
|
@@ -79,6 +72,14 @@ export default async function createApp() {
|
|
|
79
72
|
if ((await validateMigrations()) === false) {
|
|
80
73
|
logger.warn(`Database migrations have not all been run`);
|
|
81
74
|
}
|
|
75
|
+
if (!env['SECRET']) {
|
|
76
|
+
logger.warn(`"SECRET" env variable is missing. Using a random value instead. Tokens will not persist between restarts. This is not appropriate for production usage.`);
|
|
77
|
+
}
|
|
78
|
+
if (!new Url(env['PUBLIC_URL']).isAbsolute()) {
|
|
79
|
+
logger.warn('"PUBLIC_URL" should be a full URL');
|
|
80
|
+
}
|
|
81
|
+
await validateDatabaseExtensions();
|
|
82
|
+
await validateStorage();
|
|
82
83
|
await registerAuthProviders();
|
|
83
84
|
const extensionManager = getExtensionManager();
|
|
84
85
|
const flowManager = getFlowManager();
|
|
@@ -19,6 +19,7 @@ import { getIPFromReq } from '../../utils/get-ip-from-req.js';
|
|
|
19
19
|
import { isLoginRedirectAllowed } from '../../utils/is-login-redirect-allowed.js';
|
|
20
20
|
import { Url } from '../../utils/url.js';
|
|
21
21
|
import { LocalAuthDriver } from './local.js';
|
|
22
|
+
import { getSecret } from '../../utils/get-secret.js';
|
|
22
23
|
export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
23
24
|
client;
|
|
24
25
|
redirectUrl;
|
|
@@ -224,7 +225,7 @@ export function createOAuth2AuthRouter(providerName) {
|
|
|
224
225
|
if (isLoginRedirectAllowed(redirect, providerName) === false) {
|
|
225
226
|
throw new InvalidPayloadError({ reason: `URL "${redirect}" can't be used to redirect after login` });
|
|
226
227
|
}
|
|
227
|
-
const token = jwt.sign({ verifier: codeVerifier, redirect, prompt },
|
|
228
|
+
const token = jwt.sign({ verifier: codeVerifier, redirect, prompt }, getSecret(), {
|
|
228
229
|
expiresIn: '5m',
|
|
229
230
|
issuer: 'directus',
|
|
230
231
|
});
|
|
@@ -241,7 +242,7 @@ export function createOAuth2AuthRouter(providerName) {
|
|
|
241
242
|
const logger = useLogger();
|
|
242
243
|
let tokenData;
|
|
243
244
|
try {
|
|
244
|
-
tokenData = jwt.verify(req.cookies[`oauth2.${providerName}`],
|
|
245
|
+
tokenData = jwt.verify(req.cookies[`oauth2.${providerName}`], getSecret(), {
|
|
245
246
|
issuer: 'directus',
|
|
246
247
|
});
|
|
247
248
|
}
|
|
@@ -16,6 +16,7 @@ import { UsersService } from '../../services/users.js';
|
|
|
16
16
|
import asyncHandler from '../../utils/async-handler.js';
|
|
17
17
|
import { getConfigFromEnv } from '../../utils/get-config-from-env.js';
|
|
18
18
|
import { getIPFromReq } from '../../utils/get-ip-from-req.js';
|
|
19
|
+
import { getSecret } from '../../utils/get-secret.js';
|
|
19
20
|
import { isLoginRedirectAllowed } from '../../utils/is-login-redirect-allowed.js';
|
|
20
21
|
import { Url } from '../../utils/url.js';
|
|
21
22
|
import { LocalAuthDriver } from './local.js';
|
|
@@ -245,7 +246,7 @@ export function createOpenIDAuthRouter(providerName) {
|
|
|
245
246
|
if (isLoginRedirectAllowed(redirect, providerName) === false) {
|
|
246
247
|
throw new InvalidPayloadError({ reason: `URL "${redirect}" can't be used to redirect after login` });
|
|
247
248
|
}
|
|
248
|
-
const token = jwt.sign({ verifier: codeVerifier, redirect, prompt },
|
|
249
|
+
const token = jwt.sign({ verifier: codeVerifier, redirect, prompt }, getSecret(), {
|
|
249
250
|
expiresIn: '5m',
|
|
250
251
|
issuer: 'directus',
|
|
251
252
|
});
|
|
@@ -262,7 +263,7 @@ export function createOpenIDAuthRouter(providerName) {
|
|
|
262
263
|
const logger = useLogger();
|
|
263
264
|
let tokenData;
|
|
264
265
|
try {
|
|
265
|
-
tokenData = jwt.verify(req.cookies[`openid.${providerName}`],
|
|
266
|
+
tokenData = jwt.verify(req.cookies[`openid.${providerName}`], getSecret(), {
|
|
266
267
|
issuer: 'directus',
|
|
267
268
|
});
|
|
268
269
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import { Liquid } from 'liquidjs';
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
4
3
|
import { dirname } from 'node:path';
|
|
5
4
|
import { fileURLToPath } from 'node:url';
|
|
6
5
|
import path from 'path';
|
|
@@ -17,7 +16,6 @@ export default async function createEnv(client, credentials, directory) {
|
|
|
17
16
|
const { nanoid } = await import('nanoid');
|
|
18
17
|
const config = {
|
|
19
18
|
security: {
|
|
20
|
-
KEY: randomUUID(),
|
|
21
19
|
SECRET: nanoid(32),
|
|
22
20
|
},
|
|
23
21
|
database: {
|
package/dist/controllers/auth.js
CHANGED
|
@@ -2,7 +2,7 @@ import { useEnv } from '@directus/env';
|
|
|
2
2
|
import { ErrorCode, InvalidPayloadError, isDirectusError } from '@directus/errors';
|
|
3
3
|
import { Router } from 'express';
|
|
4
4
|
import { createLDAPAuthRouter, createLocalAuthRouter, createOAuth2AuthRouter, createOpenIDAuthRouter, createSAMLAuthRouter, } from '../auth/drivers/index.js';
|
|
5
|
-
import {
|
|
5
|
+
import { DEFAULT_AUTH_PROVIDER, REFRESH_COOKIE_OPTIONS, SESSION_COOKIE_OPTIONS } from '../constants.js';
|
|
6
6
|
import { useLogger } from '../logger.js';
|
|
7
7
|
import { respond } from '../middleware/respond.js';
|
|
8
8
|
import { AuthenticationService } from '../services/authentication.js';
|
|
@@ -10,6 +10,7 @@ import { UsersService } from '../services/users.js';
|
|
|
10
10
|
import asyncHandler from '../utils/async-handler.js';
|
|
11
11
|
import { getAuthProviders } from '../utils/get-auth-providers.js';
|
|
12
12
|
import { getIPFromReq } from '../utils/get-ip-from-req.js';
|
|
13
|
+
import { getSecret } from '../utils/get-secret.js';
|
|
13
14
|
import isDirectusJWT from '../utils/is-directus-jwt.js';
|
|
14
15
|
import { verifyAccessJWT } from '../utils/jwt.js';
|
|
15
16
|
const router = Router();
|
|
@@ -63,7 +64,7 @@ function getCurrentRefreshToken(req, mode) {
|
|
|
63
64
|
if (mode === 'session') {
|
|
64
65
|
const token = req.cookies[env['SESSION_COOKIE_NAME']];
|
|
65
66
|
if (isDirectusJWT(token)) {
|
|
66
|
-
const payload = verifyAccessJWT(token,
|
|
67
|
+
const payload = verifyAccessJWT(token, getSecret());
|
|
67
68
|
return payload.session;
|
|
68
69
|
}
|
|
69
70
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
|
-
import { ErrorCode, ForbiddenError,
|
|
2
|
+
import { ErrorCode, ForbiddenError, isDirectusError, RouteNotFoundError } from '@directus/errors';
|
|
3
3
|
import { EXTENSION_TYPES } from '@directus/extensions';
|
|
4
4
|
import { account, describe, list, } from '@directus/extensions-registry';
|
|
5
5
|
import { isIn } from '@directus/utils';
|
|
6
6
|
import express from 'express';
|
|
7
|
+
import { isNil } from 'lodash-es';
|
|
7
8
|
import { UUID_REGEX } from '../constants.js';
|
|
8
9
|
import { getExtensionManager } from '../extensions/index.js';
|
|
9
10
|
import { respond } from '../middleware/respond.js';
|
|
@@ -28,28 +29,38 @@ router.get('/registry', asyncHandler(async (req, res, next) => {
|
|
|
28
29
|
if (req.accountability && req.accountability.admin !== true) {
|
|
29
30
|
throw new ForbiddenError();
|
|
30
31
|
}
|
|
31
|
-
const { search, limit, offset,
|
|
32
|
+
const { search, limit, offset, sort, filter } = req.sanitizedQuery;
|
|
32
33
|
const query = {};
|
|
33
|
-
if (
|
|
34
|
+
if (!isNil(search)) {
|
|
34
35
|
query.search = search;
|
|
35
36
|
}
|
|
36
|
-
if (
|
|
37
|
-
query.limit =
|
|
38
|
-
}
|
|
39
|
-
if (
|
|
40
|
-
query.offset =
|
|
41
|
-
}
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
37
|
+
if (!isNil(limit)) {
|
|
38
|
+
query.limit = limit;
|
|
39
|
+
}
|
|
40
|
+
if (!isNil(offset)) {
|
|
41
|
+
query.offset = offset;
|
|
42
|
+
}
|
|
43
|
+
if (filter) {
|
|
44
|
+
const getFilterValue = (key) => {
|
|
45
|
+
const field = filter[key];
|
|
46
|
+
if (!field || !('_eq' in field) || typeof field._eq !== 'string')
|
|
47
|
+
return;
|
|
48
|
+
return field._eq;
|
|
49
|
+
};
|
|
50
|
+
const by = getFilterValue('by');
|
|
51
|
+
const type = getFilterValue('type');
|
|
52
|
+
if (by) {
|
|
53
|
+
query.by = by;
|
|
51
54
|
}
|
|
52
|
-
|
|
55
|
+
if (type) {
|
|
56
|
+
if (isIn(type, EXTENSION_TYPES) === false) {
|
|
57
|
+
throw new ForbiddenError();
|
|
58
|
+
}
|
|
59
|
+
query.type = type;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!isNil(sort) && sort[0] && isIn(sort[0], ['popular', 'recent', 'downloads'])) {
|
|
63
|
+
query.sort = sort[0];
|
|
53
64
|
}
|
|
54
65
|
if (env['MARKETPLACE_TRUST'] === 'sandbox') {
|
|
55
66
|
query.sandbox = true;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ErrorCode, ForbiddenError, InvalidCredentialsError, InvalidPayloadError, isDirectusError, } from '@directus/errors';
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import Joi from 'joi';
|
|
4
|
+
import checkRateLimit from '../middleware/rate-limiter-registration.js';
|
|
4
5
|
import { respond } from '../middleware/respond.js';
|
|
5
6
|
import useCollection from '../middleware/use-collection.js';
|
|
6
7
|
import { validateBatch } from '../middleware/validate-batch.js';
|
|
@@ -354,4 +355,28 @@ router.post('/:pk/tfa/disable', asyncHandler(async (req, _res, next) => {
|
|
|
354
355
|
await service.disableTFA(req.params['pk']);
|
|
355
356
|
return next();
|
|
356
357
|
}), respond);
|
|
358
|
+
const registerSchema = Joi.object({
|
|
359
|
+
email: Joi.string().email().required(),
|
|
360
|
+
password: Joi.string().required(),
|
|
361
|
+
first_name: Joi.string(),
|
|
362
|
+
last_name: Joi.string(),
|
|
363
|
+
});
|
|
364
|
+
router.post('/register', checkRateLimit, asyncHandler(async (req, _res, next) => {
|
|
365
|
+
const { error, value } = registerSchema.validate(req.body);
|
|
366
|
+
if (error)
|
|
367
|
+
throw new InvalidPayloadError({ reason: error.message });
|
|
368
|
+
const usersService = new UsersService({ accountability: null, schema: req.schema });
|
|
369
|
+
await usersService.registerUser(value);
|
|
370
|
+
return next();
|
|
371
|
+
}), respond);
|
|
372
|
+
const verifyRegistrationSchema = Joi.string();
|
|
373
|
+
router.get('/register/verify-email', asyncHandler(async (req, res, _next) => {
|
|
374
|
+
const { error, value } = verifyRegistrationSchema.validate(req.query['token']);
|
|
375
|
+
if (error) {
|
|
376
|
+
return res.redirect('/admin/login');
|
|
377
|
+
}
|
|
378
|
+
const service = new UsersService({ accountability: null, schema: req.schema });
|
|
379
|
+
const id = await service.verifyRegistration(value);
|
|
380
|
+
return res.redirect(`/admin/users/${id}`);
|
|
381
|
+
}), respond);
|
|
357
382
|
export default router;
|
|
@@ -114,7 +114,8 @@ router.post('/cache/clear', asyncHandler(async (req, res) => {
|
|
|
114
114
|
accountability: req.accountability,
|
|
115
115
|
schema: req.schema,
|
|
116
116
|
});
|
|
117
|
-
|
|
117
|
+
const clearSystemCache = 'system' in req.query && (req.query['system'] === '' || Boolean(req.query['system']));
|
|
118
|
+
await service.clearCache({ system: clearSystemCache });
|
|
118
119
|
res.status(200).end();
|
|
119
120
|
}));
|
|
120
121
|
export default router;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyFilter } from '../../../utils/apply-query.js';
|
|
1
|
+
import { applyFilter, generateAlias } from '../../../utils/apply-query.js';
|
|
2
2
|
import { DatabaseHelper } from '../types.js';
|
|
3
3
|
export class FnHelper extends DatabaseHelper {
|
|
4
4
|
schema;
|
|
@@ -14,10 +14,11 @@ export class FnHelper extends DatabaseHelper {
|
|
|
14
14
|
if (!relation) {
|
|
15
15
|
throw new Error(`Field ${collectionName}.${column} isn't a nested relational collection`);
|
|
16
16
|
}
|
|
17
|
+
const alias = generateAlias();
|
|
17
18
|
let countQuery = this.knex
|
|
18
19
|
.count('*')
|
|
19
|
-
.from(relation.collection)
|
|
20
|
-
.where(relation.field, '=', this.knex.raw(`??.??`, [table, currentPrimary]));
|
|
20
|
+
.from({ [alias]: relation.collection })
|
|
21
|
+
.where(this.knex.raw(`??.??`, [alias, relation.field]), '=', this.knex.raw(`??.??`, [table, currentPrimary]));
|
|
21
22
|
if (options?.query?.filter) {
|
|
22
23
|
countQuery = applyFilter(this.knex, this.schema, countQuery, options.query.filter, relation.collection, {}).query;
|
|
23
24
|
}
|
|
@@ -5,11 +5,13 @@ import * as fnHelpers from './fn/index.js';
|
|
|
5
5
|
import * as geometryHelpers from './geometry/index.js';
|
|
6
6
|
import * as schemaHelpers from './schema/index.js';
|
|
7
7
|
import * as sequenceHelpers from './sequence/index.js';
|
|
8
|
+
import * as numberHelpers from './number/index.js';
|
|
8
9
|
export declare function getHelpers(database: Knex): {
|
|
9
10
|
date: dateHelpers.postgres | dateHelpers.oracle | dateHelpers.mysql | dateHelpers.mssql | dateHelpers.sqlite;
|
|
10
11
|
st: geometryHelpers.mysql | geometryHelpers.postgres | geometryHelpers.mssql | geometryHelpers.sqlite | geometryHelpers.oracle | geometryHelpers.redshift;
|
|
11
12
|
schema: schemaHelpers.mysql | schemaHelpers.cockroachdb | schemaHelpers.mssql | schemaHelpers.postgres | schemaHelpers.sqlite | schemaHelpers.oracle;
|
|
12
13
|
sequence: sequenceHelpers.mysql | sequenceHelpers.postgres;
|
|
14
|
+
number: numberHelpers.cockroachdb | numberHelpers.mssql | numberHelpers.postgres | numberHelpers.sqlite | numberHelpers.oracle;
|
|
13
15
|
};
|
|
14
16
|
export declare function getFunctions(database: Knex, schema: SchemaOverview): fnHelpers.mysql | fnHelpers.postgres | fnHelpers.mssql | fnHelpers.sqlite | fnHelpers.oracle;
|
|
15
17
|
export type Helpers = ReturnType<typeof getHelpers>;
|
|
@@ -4,6 +4,7 @@ import * as fnHelpers from './fn/index.js';
|
|
|
4
4
|
import * as geometryHelpers from './geometry/index.js';
|
|
5
5
|
import * as schemaHelpers from './schema/index.js';
|
|
6
6
|
import * as sequenceHelpers from './sequence/index.js';
|
|
7
|
+
import * as numberHelpers from './number/index.js';
|
|
7
8
|
export function getHelpers(database) {
|
|
8
9
|
const client = getDatabaseClient(database);
|
|
9
10
|
return {
|
|
@@ -11,6 +12,7 @@ export function getHelpers(database) {
|
|
|
11
12
|
st: new geometryHelpers[client](database),
|
|
12
13
|
schema: new schemaHelpers[client](database),
|
|
13
14
|
sequence: new sequenceHelpers[client](database),
|
|
15
|
+
number: new numberHelpers[client](database),
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
18
|
export function getFunctions(database, schema) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
import { NumberDatabaseHelper, type NumberInfo } from '../types.js';
|
|
3
|
+
import type { NumericValue } from '@directus/types';
|
|
4
|
+
export declare class NumberHelperMSSQL extends NumberDatabaseHelper {
|
|
5
|
+
addSearchCondition(dbQuery: Knex.QueryBuilder, collection: string, name: string, value: NumericValue): Knex.QueryBuilder;
|
|
6
|
+
isNumberValid(value: NumericValue, info: NumberInfo): boolean;
|
|
7
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { maybeStringifyBigInt } from '../utils/maybe-stringify-big-int.js';
|
|
2
|
+
import { numberInRange } from '../utils/number-in-range.js';
|
|
3
|
+
import { NumberDatabaseHelper } from '../types.js';
|
|
4
|
+
export class NumberHelperMSSQL extends NumberDatabaseHelper {
|
|
5
|
+
addSearchCondition(dbQuery, collection, name, value) {
|
|
6
|
+
return dbQuery.orWhere({ [`${collection}.${name}`]: maybeStringifyBigInt(value) });
|
|
7
|
+
}
|
|
8
|
+
isNumberValid(value, info) {
|
|
9
|
+
return numberInRange(value, info);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
import { NumberDatabaseHelper } from '../types.js';
|
|
3
|
+
import type { NumericValue } from '@directus/types';
|
|
4
|
+
export declare class NumberHelperOracle extends NumberDatabaseHelper {
|
|
5
|
+
addSearchCondition(dbQuery: Knex.QueryBuilder, collection: string, name: string, value: NumericValue): Knex.QueryBuilder;
|
|
6
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { NumberDatabaseHelper } from '../types.js';
|
|
2
|
+
import { maybeStringifyBigInt } from '../utils/maybe-stringify-big-int.js';
|
|
3
|
+
export class NumberHelperOracle extends NumberDatabaseHelper {
|
|
4
|
+
addSearchCondition(dbQuery, collection, name, value) {
|
|
5
|
+
return dbQuery.orWhere({ [`${collection}.${name}`]: maybeStringifyBigInt(value) });
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NumberDatabaseHelper } from '../types.js';
|
|
2
|
+
import { numberInRange } from '../utils/number-in-range.js';
|
|
3
|
+
export class NumberHelperPostgres extends NumberDatabaseHelper {
|
|
4
|
+
isNumberValid(value, info) {
|
|
5
|
+
// Check that number is within the range of the provided type
|
|
6
|
+
if (numberInRange(value, info)) {
|
|
7
|
+
// Ensure that only integer values are used for integer types
|
|
8
|
+
if (typeof value !== 'bigint' && ['integer', 'bigInteger'].includes(info.type)) {
|
|
9
|
+
return value % 1 === 0;
|
|
10
|
+
}
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
import { NumberDatabaseHelper } from '../types.js';
|
|
3
|
+
import type { NumericValue } from '@directus/types';
|
|
4
|
+
export declare class NumberHelperSQLite extends NumberDatabaseHelper {
|
|
5
|
+
addSearchCondition(dbQuery: Knex.QueryBuilder, collection: string, name: string, value: NumericValue): Knex.QueryBuilder;
|
|
6
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { NumberDatabaseHelper } from '../types.js';
|
|
2
|
+
import { maybeStringifyBigInt } from '../utils/maybe-stringify-big-int.js';
|
|
3
|
+
export class NumberHelperSQLite extends NumberDatabaseHelper {
|
|
4
|
+
addSearchCondition(dbQuery, collection, name, value) {
|
|
5
|
+
return dbQuery.orWhere({ [`${collection}.${name}`]: maybeStringifyBigInt(value) });
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { NumberHelperPostgres as postgres } from './dialects/postgres.js';
|
|
2
|
+
export { NumberHelperPostgres as redshift } from './dialects/postgres.js';
|
|
3
|
+
export { NumberHelperDefault as cockroachdb } from './dialects/default.js';
|
|
4
|
+
export { NumberHelperOracle as oracle } from './dialects/oracle.js';
|
|
5
|
+
export { NumberHelperSQLite as sqlite } from './dialects/sqlite.js';
|
|
6
|
+
export { NumberHelperDefault as mysql } from './dialects/default.js';
|
|
7
|
+
export { NumberHelperMSSQL as mssql } from './dialects/mssql.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { NumberHelperPostgres as postgres } from './dialects/postgres.js';
|
|
2
|
+
export { NumberHelperPostgres as redshift } from './dialects/postgres.js';
|
|
3
|
+
export { NumberHelperDefault as cockroachdb } from './dialects/default.js';
|
|
4
|
+
export { NumberHelperOracle as oracle } from './dialects/oracle.js';
|
|
5
|
+
export { NumberHelperSQLite as sqlite } from './dialects/sqlite.js';
|
|
6
|
+
export { NumberHelperDefault as mysql } from './dialects/default.js';
|
|
7
|
+
export { NumberHelperMSSQL as mssql } from './dialects/mssql.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
import { DatabaseHelper } from '../types.js';
|
|
3
|
+
import type { NumericType, NumericValue } from '@directus/types';
|
|
4
|
+
export type NumberInfo = {
|
|
5
|
+
type: NumericType;
|
|
6
|
+
precision: number | null;
|
|
7
|
+
scale: number | null;
|
|
8
|
+
};
|
|
9
|
+
export declare abstract class NumberDatabaseHelper extends DatabaseHelper {
|
|
10
|
+
addSearchCondition(dbQuery: Knex.QueryBuilder, collection: string, name: string, value: NumericValue): Knex.QueryBuilder;
|
|
11
|
+
isNumberValid(_value: NumericValue, _info: NumberInfo): boolean;
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DatabaseHelper } from '../types.js';
|
|
2
|
+
export class NumberDatabaseHelper extends DatabaseHelper {
|
|
3
|
+
addSearchCondition(dbQuery, collection, name, value) {
|
|
4
|
+
return dbQuery.orWhere({ [`${collection}.${name}`]: value });
|
|
5
|
+
}
|
|
6
|
+
isNumberValid(_value, _info) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DEFAULT_NUMERIC_PRECISION, DEFAULT_NUMERIC_SCALE } from '@directus/constants';
|
|
2
|
+
export function calculateDecimalLimit(precision, scale) {
|
|
3
|
+
if (precision === null || scale === null) {
|
|
4
|
+
precision = DEFAULT_NUMERIC_PRECISION;
|
|
5
|
+
scale = DEFAULT_NUMERIC_SCALE;
|
|
6
|
+
}
|
|
7
|
+
const max = 10 ** (precision - scale) - 10 ** -scale;
|
|
8
|
+
const min = -(10 ** (precision - scale)) + 10 ** -scale;
|
|
9
|
+
return { max, min };
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function maybeStringifyBigInt(value: number | bigint): string | number;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { MAX_SAFE_INT32, MAX_SAFE_INT64, MIN_SAFE_INT32, MIN_SAFE_INT64 } from '@directus/constants';
|
|
2
|
+
import { calculateDecimalLimit } from './decimal-limit.js';
|
|
3
|
+
export function numberInRange(value, info) {
|
|
4
|
+
switch (info.type) {
|
|
5
|
+
case 'bigInteger':
|
|
6
|
+
return value >= MIN_SAFE_INT64 && value <= MAX_SAFE_INT64;
|
|
7
|
+
case 'decimal': {
|
|
8
|
+
const { min, max } = calculateDecimalLimit(info.precision, info.scale);
|
|
9
|
+
return value >= min && value <= max;
|
|
10
|
+
}
|
|
11
|
+
case 'integer':
|
|
12
|
+
return value >= MIN_SAFE_INT32 && value <= MAX_SAFE_INT32;
|
|
13
|
+
case 'float':
|
|
14
|
+
// Not sure how to calculate the logical limits of float
|
|
15
|
+
// Let the database decide and error;
|
|
16
|
+
return true;
|
|
17
|
+
default:
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export async function up(knex) {
|
|
2
|
+
await knex.schema.alterTable('directus_settings', (table) => {
|
|
3
|
+
table.boolean('public_registration').notNullable().defaultTo(false);
|
|
4
|
+
table.boolean('public_registration_verify_email').notNullable().defaultTo(true);
|
|
5
|
+
table.uuid('public_registration_role').nullable();
|
|
6
|
+
table.foreign('public_registration_role').references('directus_roles.id').onDelete('SET NULL');
|
|
7
|
+
table.json('public_registration_email_filter').nullable();
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export async function down(knex) {
|
|
11
|
+
await knex.schema.alterTable('directus_settings', (table) => {
|
|
12
|
+
table.dropColumns('public_registration', 'public_registration_verify_email', 'public_registration_role', 'public_registration_email_filter');
|
|
13
|
+
});
|
|
14
|
+
}
|
package/dist/database/run-ast.js
CHANGED
|
@@ -6,7 +6,7 @@ import { applyFunctionToColumnName } from '../utils/apply-function-to-column-nam
|
|
|
6
6
|
import applyQuery, { applyLimit, applySort, generateAlias } from '../utils/apply-query.js';
|
|
7
7
|
import { getCollectionFromAlias } from '../utils/get-collection-from-alias.js';
|
|
8
8
|
import { getColumn } from '../utils/get-column.js';
|
|
9
|
-
import {
|
|
9
|
+
import { parseFilterKey } from '../utils/parse-filter-key.js';
|
|
10
10
|
import { getHelpers } from './helpers/index.js';
|
|
11
11
|
import getDatabase from './index.js';
|
|
12
12
|
/**
|
|
@@ -36,7 +36,7 @@ export default async function runAST(originalAST, schema, options) {
|
|
|
36
36
|
return null;
|
|
37
37
|
// Run the items through the special transforms
|
|
38
38
|
const payloadService = new PayloadService(collection, { knex, schema });
|
|
39
|
-
let items = await payloadService.processValues('read', rawItems);
|
|
39
|
+
let items = await payloadService.processValues('read', rawItems, query.alias ?? {});
|
|
40
40
|
if (!items || (Array.isArray(items) && items.length === 0))
|
|
41
41
|
return items;
|
|
42
42
|
// Apply the `_in` filters to the nested collection batches
|
|
@@ -92,7 +92,7 @@ async function parseCurrentLevel(schema, collection, children, query) {
|
|
|
92
92
|
const nestedCollectionNodes = [];
|
|
93
93
|
for (const child of children) {
|
|
94
94
|
if (child.type === 'field' || child.type === 'functionField') {
|
|
95
|
-
const fieldName =
|
|
95
|
+
const { fieldName } = parseFilterKey(child.name);
|
|
96
96
|
if (columnsInCollection.includes(fieldName)) {
|
|
97
97
|
columnsToSelectInternal.push(child.fieldKey);
|
|
98
98
|
}
|
|
@@ -134,7 +134,8 @@ function getColumnPreprocessor(knex, schema, table) {
|
|
|
134
134
|
}
|
|
135
135
|
let field;
|
|
136
136
|
if (fieldNode.type === 'field' || fieldNode.type === 'functionField') {
|
|
137
|
-
|
|
137
|
+
const { fieldName } = parseFilterKey(fieldNode.name);
|
|
138
|
+
field = schema.collections[table].fields[fieldName];
|
|
138
139
|
}
|
|
139
140
|
else {
|
|
140
141
|
field = schema.collections[fieldNode.relation.collection].fields[fieldNode.relation.field];
|