directus 9.11.0 → 9.11.1
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/database/index.js +5 -0
- package/dist/database/system-data/fields/activity.yaml +3 -0
- package/dist/database/system-data/fields/dashboards.yaml +3 -1
- package/dist/database/system-data/fields/notifications.yaml +3 -1
- package/dist/database/system-data/fields/panels.yaml +3 -1
- package/dist/database/system-data/fields/shares.yaml +3 -1
- package/dist/env.js +188 -10
- package/dist/services/items.js +16 -0
- package/dist/utils/{apply-query/index.d.ts → apply-query.d.ts} +0 -0
- package/dist/utils/{apply-query/index.js → apply-query.js} +147 -48
- package/dist/utils/validate-keys.d.ts +6 -0
- package/dist/utils/validate-keys.js +28 -0
- package/dist/utils/validate-query.js +1 -1
- package/package.json +12 -12
- package/dist/utils/apply-query/operators/between.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/between.operator.js +0 -16
- package/dist/utils/apply-query/operators/contains.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/contains.operator.js +0 -9
- package/dist/utils/apply-query/operators/ends-with.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/ends-with.operator.js +0 -9
- package/dist/utils/apply-query/operators/equals.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/equals.operator.js +0 -9
- package/dist/utils/apply-query/operators/greather-than-equals.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/greather-than-equals.operator.js +0 -9
- package/dist/utils/apply-query/operators/greather-than.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/greather-than.operator.js +0 -9
- package/dist/utils/apply-query/operators/in.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/in.operator.js +0 -14
- package/dist/utils/apply-query/operators/index.d.ts +0 -3
- package/dist/utils/apply-query/operators/index.js +0 -72
- package/dist/utils/apply-query/operators/insensitive-contains.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/insensitive-contains.operator.js +0 -9
- package/dist/utils/apply-query/operators/insensitive-ends-with.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/insensitive-ends-with.operator.js +0 -9
- package/dist/utils/apply-query/operators/insensitive-equals.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/insensitive-equals.operator.js +0 -9
- package/dist/utils/apply-query/operators/insensitive-not-contains.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/insensitive-not-contains.operator.js +0 -9
- package/dist/utils/apply-query/operators/insensitive-not-ends-with.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/insensitive-not-ends-with.operator.js +0 -9
- package/dist/utils/apply-query/operators/insensitive-not-equals.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/insensitive-not-equals.operator.js +0 -9
- package/dist/utils/apply-query/operators/insensitive-not-starts-with.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/insensitive-not-starts-with.operator.js +0 -9
- package/dist/utils/apply-query/operators/insensitive-starts-with.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/insensitive-starts-with.operator.js +0 -9
- package/dist/utils/apply-query/operators/intersects-bbox.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/intersects-bbox.operator.js +0 -9
- package/dist/utils/apply-query/operators/intersects.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/intersects.operator.js +0 -9
- package/dist/utils/apply-query/operators/is-empty.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/is-empty.operator.js +0 -14
- package/dist/utils/apply-query/operators/is-not-empty.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/is-not-empty.operator.js +0 -14
- package/dist/utils/apply-query/operators/is-not-null.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/is-not-null.operator.js +0 -14
- package/dist/utils/apply-query/operators/is-null.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/is-null.operator.js +0 -14
- package/dist/utils/apply-query/operators/less-than-equals.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/less-than-equals.operator.js +0 -9
- package/dist/utils/apply-query/operators/less-than.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/less-than.operator.js +0 -9
- package/dist/utils/apply-query/operators/not-between.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/not-between.operator.js +0 -16
- package/dist/utils/apply-query/operators/not-contains.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/not-contains.operator.js +0 -9
- package/dist/utils/apply-query/operators/not-ends-with.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/not-ends-with.operator.js +0 -9
- package/dist/utils/apply-query/operators/not-equals.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/not-equals.operator.js +0 -9
- package/dist/utils/apply-query/operators/not-in.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/not-in.operator.js +0 -14
- package/dist/utils/apply-query/operators/not-intersects-bbox.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/not-intersects-bbox.operator.js +0 -9
- package/dist/utils/apply-query/operators/not-intersects.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/not-intersects.operator.js +0 -9
- package/dist/utils/apply-query/operators/not-starts-with.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/not-starts-with.operator.js +0 -9
- package/dist/utils/apply-query/operators/operator-register.d.ts +0 -13
- package/dist/utils/apply-query/operators/operator-register.js +0 -7
- package/dist/utils/apply-query/operators/starts-with.operator.d.ts +0 -2
- package/dist/utils/apply-query/operators/starts-with.operator.js +0 -9
package/dist/database/index.js
CHANGED
|
@@ -54,6 +54,11 @@ function getDatabase() {
|
|
|
54
54
|
requiredEnvVars.push('DB_CONNECTION_STRING');
|
|
55
55
|
}
|
|
56
56
|
break;
|
|
57
|
+
case 'mssql':
|
|
58
|
+
if (!env_1.default.DB_TYPE || env_1.default.DB_TYPE === 'default') {
|
|
59
|
+
requiredEnvVars.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD');
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
57
62
|
default:
|
|
58
63
|
requiredEnvVars.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD');
|
|
59
64
|
}
|
package/dist/env.js
CHANGED
|
@@ -15,6 +15,179 @@ const path_1 = __importDefault(require("path"));
|
|
|
15
15
|
const require_yaml_1 = require("./utils/require-yaml");
|
|
16
16
|
const utils_1 = require("@directus/shared/utils");
|
|
17
17
|
const parse_json_1 = require("./utils/parse-json");
|
|
18
|
+
// keeping this here for now to prevent a circular import to constants.ts
|
|
19
|
+
const allowedEnvironmentVars = [
|
|
20
|
+
// general
|
|
21
|
+
'CONFIG_PATH',
|
|
22
|
+
'HOST',
|
|
23
|
+
'PORT',
|
|
24
|
+
'PUBLIC_URL',
|
|
25
|
+
'LOG_LEVEL',
|
|
26
|
+
'LOG_STYLE',
|
|
27
|
+
'MAX_PAYLOAD_SIZE',
|
|
28
|
+
'ROOT_REDIRECT',
|
|
29
|
+
'SERVE_APP',
|
|
30
|
+
'GRAPHQL_INTROSPECTION',
|
|
31
|
+
// server
|
|
32
|
+
'SERVER_KEEP_ALIVE_TIMEOUT',
|
|
33
|
+
'SERVER_HEADERS_TIMEOUT',
|
|
34
|
+
// database
|
|
35
|
+
'DB_CLIENT',
|
|
36
|
+
'DB_HOST',
|
|
37
|
+
'DB_PORT',
|
|
38
|
+
'DB_DATABASE',
|
|
39
|
+
'DB_USER',
|
|
40
|
+
'DB_PASSWORD',
|
|
41
|
+
'DB_FILENAME',
|
|
42
|
+
'DB_CONNECTION_STRING',
|
|
43
|
+
'DB_POOL_.+',
|
|
44
|
+
'DB_EXCLUDE_TABLES',
|
|
45
|
+
'DB_CHARSET',
|
|
46
|
+
'DB_VERSION',
|
|
47
|
+
// security
|
|
48
|
+
'KEY',
|
|
49
|
+
'SECRET',
|
|
50
|
+
'ACCESS_TOKEN_TTL',
|
|
51
|
+
'REFRESH_TOKEN_TTL',
|
|
52
|
+
'REFRESH_TOKEN_COOKIE_DOMAIN',
|
|
53
|
+
'REFRESH_TOKEN_COOKIE_SECURE',
|
|
54
|
+
'REFRESH_TOKEN_COOKIE_SAME_SITE',
|
|
55
|
+
'REFRESH_TOKEN_COOKIE_NAME',
|
|
56
|
+
'PASSWORD_RESET_URL_ALLOW_LIST',
|
|
57
|
+
'USER_INVITE_URL_ALLOW_LIST',
|
|
58
|
+
'IP_TRUST_PROXY',
|
|
59
|
+
'IP_CUSTOM_HEADER',
|
|
60
|
+
'ASSETS_CONTENT_SECURITY_POLICY',
|
|
61
|
+
'IMPORT_IP_DENY_LIST',
|
|
62
|
+
'CONTENT_SECURITY_POLICY_.+',
|
|
63
|
+
'HSTS_.+',
|
|
64
|
+
// hashing
|
|
65
|
+
'HASH_MEMORY_COST',
|
|
66
|
+
'HASH_LENGTH',
|
|
67
|
+
'HASH_TIME_COST',
|
|
68
|
+
'HASH_PARALLELISM',
|
|
69
|
+
'HASH_TYPE',
|
|
70
|
+
'HASH_ASSOCIATED_DATA',
|
|
71
|
+
// cors
|
|
72
|
+
'CORS_ENABLED',
|
|
73
|
+
'CORS_ORIGIN',
|
|
74
|
+
'CORS_METHODS',
|
|
75
|
+
'CORS_ALLOWED_HEADERS',
|
|
76
|
+
'CORS_EXPOSED_HEADERS',
|
|
77
|
+
'CORS_CREDENTIALS',
|
|
78
|
+
'CORS_MAX_AGE',
|
|
79
|
+
// rate limiting
|
|
80
|
+
'RATE_LIMITER_ENABLED',
|
|
81
|
+
'RATE_LIMITER_POINTS',
|
|
82
|
+
'RATE_LIMITER_DURATION',
|
|
83
|
+
'RATE_LIMITER_STORE',
|
|
84
|
+
'RATE_LIMITER_REDIS',
|
|
85
|
+
'RATE_LIMITER_REDIS_HOST',
|
|
86
|
+
'RATE_LIMITER_REDIS_PORT',
|
|
87
|
+
'RATE_LIMITER_REDIS_PASSWORD',
|
|
88
|
+
'RATE_LIMITER_MEMCACHE',
|
|
89
|
+
// cache
|
|
90
|
+
'CACHE_ENABLED',
|
|
91
|
+
'CACHE_TTL',
|
|
92
|
+
'CACHE_CONTROL_S_MAXAGE',
|
|
93
|
+
'CACHE_AUTO_PURGE',
|
|
94
|
+
'CACHE_SYSTEM_TTL',
|
|
95
|
+
'CACHE_SCHEMA',
|
|
96
|
+
'CACHE_PERMISSIONS',
|
|
97
|
+
'CACHE_NAMESPACE',
|
|
98
|
+
'CACHE_STORE',
|
|
99
|
+
'CACHE_STATUS_HEADER',
|
|
100
|
+
'CACHE_REDIS',
|
|
101
|
+
'CACHE_REDIS_HOST',
|
|
102
|
+
'CACHE_REDIS_PORT',
|
|
103
|
+
'CACHE_REDIS_PASSWORD',
|
|
104
|
+
'CACHE_MEMCACHE',
|
|
105
|
+
// storage
|
|
106
|
+
'STORAGE_LOCATIONS',
|
|
107
|
+
'STORAGE_.+_DRIVER',
|
|
108
|
+
'STORAGE_.+_ROOT',
|
|
109
|
+
'STORAGE_.+_KEY',
|
|
110
|
+
'STORAGE_.+_SECRET',
|
|
111
|
+
'STORAGE_.+_BUCKET',
|
|
112
|
+
'STORAGE_.+_REGION',
|
|
113
|
+
'STORAGE_.+_ENDPOINT',
|
|
114
|
+
'STORAGE_.+_ACL',
|
|
115
|
+
'STORAGE_.+_CONTAINER_NAME',
|
|
116
|
+
'STORAGE_.+_ACCOUNT_NAME',
|
|
117
|
+
'STORAGE_.+_ACCOUNT_KEY',
|
|
118
|
+
'STORAGE_.+_ENDPOINT',
|
|
119
|
+
'STORAGE_.+_KEY_FILENAME',
|
|
120
|
+
'STORAGE_.+_BUCKET',
|
|
121
|
+
// metadata
|
|
122
|
+
'FILE_METADATA_ALLOW_LIST',
|
|
123
|
+
// assets
|
|
124
|
+
'ASSETS_CACHE_TTL',
|
|
125
|
+
'ASSETS_TRANSFORM_MAX_CONCURRENT',
|
|
126
|
+
'ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION',
|
|
127
|
+
'ASSETS_TRANSFORM_MAX_OPERATIONS',
|
|
128
|
+
'ASSETS_CONTENT_SECURITY_POLICY',
|
|
129
|
+
// auth
|
|
130
|
+
'AUTH_PROVIDERS',
|
|
131
|
+
'AUTH_DISABLE_DEFAULT',
|
|
132
|
+
'AUTH_.+_DRIVER',
|
|
133
|
+
'AUTH_.+_CLIENT_ID',
|
|
134
|
+
'AUTH_.+_CLIENT_SECRET',
|
|
135
|
+
'AUTH_.+_SCOPE',
|
|
136
|
+
'AUTH_.+_AUTHORIZE_URL',
|
|
137
|
+
'AUTH_.+_ACCESS_URL',
|
|
138
|
+
'AUTH_.+_PROFILE_URL',
|
|
139
|
+
'AUTH_.+_IDENTIFIER_KEY',
|
|
140
|
+
'AUTH_.+_EMAIL_KEY',
|
|
141
|
+
'AUTH_.+_FIRST_NAME_KEY',
|
|
142
|
+
'AUTH_.+_LAST_NAME_KEY',
|
|
143
|
+
'AUTH_.+_ALLOW_PUBLIC_REGISTRATION',
|
|
144
|
+
'AUTH_.+_DEFAULT_ROLE_ID',
|
|
145
|
+
'AUTH_.+_ICON',
|
|
146
|
+
'AUTH_.+_PARAMS',
|
|
147
|
+
'AUTH_.+_ISSUER_URL',
|
|
148
|
+
'AUTH_.+_AUTH_REQUIRE_VERIFIED_EMAIL',
|
|
149
|
+
'AUTH_.+_CLIENT_URL',
|
|
150
|
+
'AUTH_.+_BIND_DN',
|
|
151
|
+
'AUTH_.+_BIND_PASSWORD',
|
|
152
|
+
'AUTH_.+_USER_DN',
|
|
153
|
+
'AUTH_.+_USER_ATTRIBUTE',
|
|
154
|
+
'AUTH_.+_USER_SCOPE',
|
|
155
|
+
'AUTH_.+_MAIL_ATTRIBUTE',
|
|
156
|
+
'AUTH_.+_FIRST_NAME_ATTRIBUTE',
|
|
157
|
+
'AUTH_.+_LAST_NAME_ATTRIBUTE',
|
|
158
|
+
'AUTH_.+_GROUP_DN',
|
|
159
|
+
'AUTH_.+_GROUP_ATTRIBUTE',
|
|
160
|
+
'AUTH_.+_GROUP_SCOPE',
|
|
161
|
+
// extensions
|
|
162
|
+
'EXTENSIONS_PATH',
|
|
163
|
+
'EXTENSIONS_AUTO_RELOAD',
|
|
164
|
+
// emails
|
|
165
|
+
'EMAIL_FROM',
|
|
166
|
+
'EMAIL_TRANSPORT',
|
|
167
|
+
'EMAIL_SENDMAIL_NEW_LINE',
|
|
168
|
+
'EMAIL_SENDMAIL_PATH',
|
|
169
|
+
'EMAIL_SMTP_HOST',
|
|
170
|
+
'EMAIL_SMTP_PORT',
|
|
171
|
+
'EMAIL_SMTP_USER',
|
|
172
|
+
'EMAIL_SMTP_PASSWORD',
|
|
173
|
+
'EMAIL_SMTP_POOL',
|
|
174
|
+
'EMAIL_SMTP_SECURE',
|
|
175
|
+
'EMAIL_SMTP_IGNORE_TLS',
|
|
176
|
+
'EMAIL_MAILGUN_API_KEY',
|
|
177
|
+
'EMAIL_MAILGUN_DOMAIN',
|
|
178
|
+
'EMAIL_MAILGUN_HOST',
|
|
179
|
+
'EMAIL_SES_CREDENTIALS__ACCESS_KEY_ID',
|
|
180
|
+
'EMAIL_SES_CREDENTIALS__SECRET_ACCESS_KEY',
|
|
181
|
+
'EMAIL_SES_REGION',
|
|
182
|
+
// admin account
|
|
183
|
+
'ADMIN_EMAIL',
|
|
184
|
+
'ADMIN_PASSWORD',
|
|
185
|
+
// telemetry
|
|
186
|
+
'TELEMETRY',
|
|
187
|
+
// limits & optimization
|
|
188
|
+
'RELATIONAL_BATCH_SIZE',
|
|
189
|
+
'EXPORT_BATCH_SIZE',
|
|
190
|
+
].map((name) => new RegExp(`^${name}$`));
|
|
18
191
|
const acceptedEnvTypes = ['string', 'number', 'regex', 'array', 'json'];
|
|
19
192
|
const defaults = {
|
|
20
193
|
CONFIG_PATH: path_1.default.resolve(process.cwd(), '.env'),
|
|
@@ -180,15 +353,17 @@ function processValues(env) {
|
|
|
180
353
|
let newKey;
|
|
181
354
|
if (key.length > 5 && key.endsWith('_FILE')) {
|
|
182
355
|
newKey = key.slice(0, -5);
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
356
|
+
if (allowedEnvironmentVars.some((pattern) => pattern.test(newKey))) {
|
|
357
|
+
if (newKey in env) {
|
|
358
|
+
throw new Error(`Duplicate environment variable encountered: you can't use "${newKey}" and "${key}" simultaneously.`);
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
value = fs_1.default.readFileSync(value, { encoding: 'utf8' });
|
|
362
|
+
key = newKey;
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
throw new Error(`Failed to read value from file "${value}", defined in environment variable "${key}".`);
|
|
366
|
+
}
|
|
192
367
|
}
|
|
193
368
|
}
|
|
194
369
|
// Convert values with a type prefix
|
|
@@ -213,7 +388,7 @@ function processValues(env) {
|
|
|
213
388
|
env[key] = tryJSON(value);
|
|
214
389
|
break;
|
|
215
390
|
case 'boolean':
|
|
216
|
-
env[key] = value
|
|
391
|
+
env[key] = toBoolean(value);
|
|
217
392
|
}
|
|
218
393
|
continue;
|
|
219
394
|
}
|
|
@@ -262,3 +437,6 @@ function tryJSON(value) {
|
|
|
262
437
|
return value;
|
|
263
438
|
}
|
|
264
439
|
}
|
|
440
|
+
function toBoolean(value) {
|
|
441
|
+
return value === 'true' || value === true || value === '1' || value === 1;
|
|
442
|
+
}
|
package/dist/services/items.js
CHANGED
|
@@ -17,6 +17,7 @@ const get_ast_from_query_1 = __importDefault(require("../utils/get-ast-from-quer
|
|
|
17
17
|
const authorization_1 = require("./authorization");
|
|
18
18
|
const index_1 = require("./index");
|
|
19
19
|
const payload_1 = require("./payload");
|
|
20
|
+
const validate_keys_1 = require("../utils/validate-keys");
|
|
20
21
|
class ItemsService {
|
|
21
22
|
constructor(collection, options) {
|
|
22
23
|
this.collection = collection;
|
|
@@ -244,6 +245,7 @@ class ItemsService {
|
|
|
244
245
|
*/
|
|
245
246
|
async readOne(key, query = {}, opts) {
|
|
246
247
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
248
|
+
(0, validate_keys_1.validateKeys)(this.schema, this.collection, primaryKeyField, key);
|
|
247
249
|
const filterWithKey = (0, lodash_1.assign)({}, query.filter, { [primaryKeyField]: { _eq: key } });
|
|
248
250
|
const queryWithKey = (0, lodash_1.assign)({}, query, { filter: filterWithKey });
|
|
249
251
|
const results = await this.readByQuery(queryWithKey, opts);
|
|
@@ -258,6 +260,7 @@ class ItemsService {
|
|
|
258
260
|
async readMany(keys, query = {}, opts) {
|
|
259
261
|
var _a;
|
|
260
262
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
263
|
+
(0, validate_keys_1.validateKeys)(this.schema, this.collection, primaryKeyField, keys);
|
|
261
264
|
const filterWithKey = { _and: [{ [primaryKeyField]: { _in: keys } }, (_a = query.filter) !== null && _a !== void 0 ? _a : {}] };
|
|
262
265
|
const queryWithKey = (0, lodash_1.assign)({}, query, { filter: filterWithKey });
|
|
263
266
|
// Set query limit as the number of keys
|
|
@@ -272,12 +275,16 @@ class ItemsService {
|
|
|
272
275
|
*/
|
|
273
276
|
async updateByQuery(query, data, opts) {
|
|
274
277
|
const keys = await this.getKeysByQuery(query);
|
|
278
|
+
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
279
|
+
(0, validate_keys_1.validateKeys)(this.schema, this.collection, primaryKeyField, keys);
|
|
275
280
|
return keys.length ? await this.updateMany(keys, data, opts) : [];
|
|
276
281
|
}
|
|
277
282
|
/**
|
|
278
283
|
* Update a single item by primary key
|
|
279
284
|
*/
|
|
280
285
|
async updateOne(key, data, opts) {
|
|
286
|
+
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
287
|
+
(0, validate_keys_1.validateKeys)(this.schema, this.collection, primaryKeyField, key);
|
|
281
288
|
await this.updateMany([key], data, opts);
|
|
282
289
|
return key;
|
|
283
290
|
}
|
|
@@ -286,6 +293,7 @@ class ItemsService {
|
|
|
286
293
|
*/
|
|
287
294
|
async updateMany(keys, data, opts) {
|
|
288
295
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
296
|
+
(0, validate_keys_1.validateKeys)(this.schema, this.collection, primaryKeyField, keys);
|
|
289
297
|
const fields = Object.keys(this.schema.collections[this.collection].fields);
|
|
290
298
|
const aliases = Object.values(this.schema.collections[this.collection].fields)
|
|
291
299
|
.filter((field) => field.alias === true)
|
|
@@ -415,6 +423,9 @@ class ItemsService {
|
|
|
415
423
|
async upsertOne(payload, opts) {
|
|
416
424
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
417
425
|
const primaryKey = payload[primaryKeyField];
|
|
426
|
+
if (primaryKey) {
|
|
427
|
+
(0, validate_keys_1.validateKeys)(this.schema, this.collection, primaryKeyField, primaryKey);
|
|
428
|
+
}
|
|
418
429
|
const exists = primaryKey &&
|
|
419
430
|
!!(await this.knex
|
|
420
431
|
.select(primaryKeyField)
|
|
@@ -455,12 +466,16 @@ class ItemsService {
|
|
|
455
466
|
*/
|
|
456
467
|
async deleteByQuery(query, opts) {
|
|
457
468
|
const keys = await this.getKeysByQuery(query);
|
|
469
|
+
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
470
|
+
(0, validate_keys_1.validateKeys)(this.schema, this.collection, primaryKeyField, keys);
|
|
458
471
|
return keys.length ? await this.deleteMany(keys, opts) : [];
|
|
459
472
|
}
|
|
460
473
|
/**
|
|
461
474
|
* Delete a single item by primary key
|
|
462
475
|
*/
|
|
463
476
|
async deleteOne(key, opts) {
|
|
477
|
+
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
478
|
+
(0, validate_keys_1.validateKeys)(this.schema, this.collection, primaryKeyField, key);
|
|
464
479
|
await this.deleteMany([key], opts);
|
|
465
480
|
return key;
|
|
466
481
|
}
|
|
@@ -469,6 +484,7 @@ class ItemsService {
|
|
|
469
484
|
*/
|
|
470
485
|
async deleteMany(keys, opts) {
|
|
471
486
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
487
|
+
(0, validate_keys_1.validateKeys)(this.schema, this.collection, primaryKeyField, keys);
|
|
472
488
|
if (this.accountability && this.accountability.admin !== true) {
|
|
473
489
|
const authorizationService = new authorization_1.AuthorizationService({
|
|
474
490
|
accountability: this.accountability,
|
|
File without changes
|
|
@@ -7,12 +7,12 @@ exports.applyAggregate = exports.applySearch = exports.applyFilter = exports.app
|
|
|
7
7
|
const lodash_1 = require("lodash");
|
|
8
8
|
const nanoid_1 = require("nanoid");
|
|
9
9
|
const uuid_validate_1 = __importDefault(require("uuid-validate"));
|
|
10
|
-
const helpers_1 = require("
|
|
11
|
-
const invalid_query_1 = require("
|
|
12
|
-
const get_column_1 = require("
|
|
13
|
-
const get_column_path_1 = require("
|
|
14
|
-
const get_relation_info_1 = require("
|
|
15
|
-
const
|
|
10
|
+
const helpers_1 = require("../database/helpers");
|
|
11
|
+
const invalid_query_1 = require("../exceptions/invalid-query");
|
|
12
|
+
const get_column_1 = require("./get-column");
|
|
13
|
+
const get_column_path_1 = require("./get-column-path");
|
|
14
|
+
const get_relation_info_1 = require("./get-relation-info");
|
|
15
|
+
const utils_1 = require("@directus/shared/utils");
|
|
16
16
|
const generateAlias = (0, nanoid_1.customAlphabet)('abcdefghijklmnopqrstuvwxyz', 5);
|
|
17
17
|
/**
|
|
18
18
|
* Apply the Query to a given Knex query builder instance
|
|
@@ -155,9 +155,6 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
155
155
|
addJoins(rootQuery, rootFilter, collection);
|
|
156
156
|
addWhereClauses(knex, rootQuery, rootFilter, collection);
|
|
157
157
|
return rootQuery;
|
|
158
|
-
function isNegativeOperator(operator) {
|
|
159
|
-
return operator.indexOf('_n') === 0;
|
|
160
|
-
}
|
|
161
158
|
function addJoins(dbQuery, filter, collection) {
|
|
162
159
|
for (const [key, value] of Object.entries(filter)) {
|
|
163
160
|
if (key === '_or' || key === '_and') {
|
|
@@ -185,33 +182,8 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
185
182
|
}
|
|
186
183
|
}
|
|
187
184
|
}
|
|
188
|
-
function callbackSubqueryRelation(relation, value) {
|
|
189
|
-
return function (subQueryKnex) {
|
|
190
|
-
const field = relation.field;
|
|
191
|
-
const collection = relation.collection;
|
|
192
|
-
const column = `${collection}.${field}`;
|
|
193
|
-
subQueryKnex.from(collection).whereRaw(`${field} = ${column}`);
|
|
194
|
-
applyQuery(knex, relation.collection, subQueryKnex, {
|
|
195
|
-
filter: value,
|
|
196
|
-
}, schema, true);
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
function inverseFilters(value) {
|
|
200
|
-
for (const field in value) {
|
|
201
|
-
for (const operator in value[field]) {
|
|
202
|
-
let inverseOperator = operator;
|
|
203
|
-
if (isNegativeOperator(operator)) {
|
|
204
|
-
inverseOperator = '_' + operator.substring(2);
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
inverseOperator = '_n' + operator.substring(1);
|
|
208
|
-
}
|
|
209
|
-
value[field][inverseOperator] = value[field][operator];
|
|
210
|
-
delete value[field][operator];
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
185
|
function addWhereClauses(knex, dbQuery, filter, collection, logical = 'and') {
|
|
186
|
+
var _a, _b;
|
|
215
187
|
for (const [key, value] of Object.entries(filter)) {
|
|
216
188
|
if (key === '_or' || key === '_and') {
|
|
217
189
|
// If the _or array contains an empty object (full permissions), we should short-circuit and ignore all other
|
|
@@ -252,12 +224,24 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
252
224
|
if (relationType === 'o2a') {
|
|
253
225
|
pkField = knex.raw(`CAST(?? AS CHAR(255))`, [pkField]);
|
|
254
226
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
227
|
+
const subQueryBuilder = (filter) => (subQueryKnex) => {
|
|
228
|
+
const field = relation.field;
|
|
229
|
+
const collection = relation.collection;
|
|
230
|
+
const column = `${collection}.${field}`;
|
|
231
|
+
subQueryKnex
|
|
232
|
+
.select({ [field]: column })
|
|
233
|
+
.from(collection)
|
|
234
|
+
.whereNotNull(column);
|
|
235
|
+
applyQuery(knex, relation.collection, subQueryKnex, { filter }, schema, true);
|
|
236
|
+
};
|
|
237
|
+
if (((_a = Object.keys(value)) === null || _a === void 0 ? void 0 : _a[0]) === '_none') {
|
|
238
|
+
dbQuery[logical].whereNotIn(pkField, subQueryBuilder(Object.values(value)[0]));
|
|
239
|
+
}
|
|
240
|
+
else if (((_b = Object.keys(value)) === null || _b === void 0 ? void 0 : _b[0]) === '_some') {
|
|
241
|
+
dbQuery[logical].whereIn(pkField, subQueryBuilder(Object.values(value)[0]));
|
|
258
242
|
}
|
|
259
243
|
else {
|
|
260
|
-
dbQuery[logical].
|
|
244
|
+
dbQuery[logical].whereIn(pkField, subQueryBuilder(value));
|
|
261
245
|
}
|
|
262
246
|
}
|
|
263
247
|
}
|
|
@@ -267,6 +251,32 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
267
251
|
const selectionRaw = (0, get_column_1.getColumn)(knex, table, column, false, schema);
|
|
268
252
|
// Knex supports "raw" in the columnName parameter, but isn't typed as such. Too bad..
|
|
269
253
|
// See https://github.com/knex/knex/issues/4518 @TODO remove as any once knex is updated
|
|
254
|
+
// These operators don't rely on a value, and can thus be used without one (eg `?filter[field][_null]`)
|
|
255
|
+
if (operator === '_null' || (operator === '_nnull' && compareValue === false)) {
|
|
256
|
+
dbQuery[logical].whereNull(selectionRaw);
|
|
257
|
+
}
|
|
258
|
+
if (operator === '_nnull' || (operator === '_null' && compareValue === false)) {
|
|
259
|
+
dbQuery[logical].whereNotNull(selectionRaw);
|
|
260
|
+
}
|
|
261
|
+
if (operator === '_empty' || (operator === '_nempty' && compareValue === false)) {
|
|
262
|
+
dbQuery[logical].andWhere((query) => {
|
|
263
|
+
query.where(key, '=', '');
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
if (operator === '_nempty' || (operator === '_empty' && compareValue === false)) {
|
|
267
|
+
dbQuery[logical].andWhere((query) => {
|
|
268
|
+
query.where(key, '!=', '');
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
// Cast filter value (compareValue) based on function used
|
|
272
|
+
if (column.includes('(') && column.includes(')')) {
|
|
273
|
+
const functionName = column.split('(')[0];
|
|
274
|
+
const type = (0, utils_1.getOutputTypeForFunction)(functionName);
|
|
275
|
+
if (['bigInteger', 'integer', 'float', 'decimal'].includes(type)) {
|
|
276
|
+
compareValue = Number(compareValue);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Cast filter value (compareValue) based on type of field being filtered against
|
|
270
280
|
const [collection, field] = key.split('.');
|
|
271
281
|
if (collection in schema.collections && field in schema.collections[collection].fields) {
|
|
272
282
|
const type = schema.collections[collection].fields[field].type;
|
|
@@ -298,16 +308,105 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
298
308
|
// We need to remove any undefined values, as they are useless
|
|
299
309
|
compareValue = compareValue.filter((val) => val !== undefined);
|
|
300
310
|
}
|
|
301
|
-
if (operator
|
|
302
|
-
|
|
303
|
-
query: dbQuery[logical],
|
|
304
|
-
helpers,
|
|
305
|
-
selectionRaw,
|
|
306
|
-
compareValue,
|
|
307
|
-
});
|
|
311
|
+
if (operator === '_eq') {
|
|
312
|
+
dbQuery[logical].where(selectionRaw, '=', compareValue);
|
|
308
313
|
}
|
|
309
|
-
|
|
310
|
-
|
|
314
|
+
if (operator === '_neq') {
|
|
315
|
+
dbQuery[logical].whereNot(selectionRaw, compareValue);
|
|
316
|
+
}
|
|
317
|
+
if (operator === '_ieq') {
|
|
318
|
+
dbQuery[logical].whereRaw(`LOWER(??) = ?`, [selectionRaw, `${compareValue.toLowerCase()}`]);
|
|
319
|
+
}
|
|
320
|
+
if (operator === '_nieq') {
|
|
321
|
+
dbQuery[logical].whereRaw(`LOWER(??) <> ?`, [selectionRaw, `${compareValue.toLowerCase()}`]);
|
|
322
|
+
}
|
|
323
|
+
if (operator === '_contains') {
|
|
324
|
+
dbQuery[logical].where(selectionRaw, 'like', `%${compareValue}%`);
|
|
325
|
+
}
|
|
326
|
+
if (operator === '_ncontains') {
|
|
327
|
+
dbQuery[logical].whereNot(selectionRaw, 'like', `%${compareValue}%`);
|
|
328
|
+
}
|
|
329
|
+
if (operator === '_icontains') {
|
|
330
|
+
dbQuery[logical].whereRaw(`LOWER(??) LIKE ?`, [selectionRaw, `%${compareValue.toLowerCase()}%`]);
|
|
331
|
+
}
|
|
332
|
+
if (operator === '_nicontains') {
|
|
333
|
+
dbQuery[logical].whereRaw(`LOWER(??) NOT LIKE ?`, [selectionRaw, `%${compareValue.toLowerCase()}%`]);
|
|
334
|
+
}
|
|
335
|
+
if (operator === '_starts_with') {
|
|
336
|
+
dbQuery[logical].where(key, 'like', `${compareValue}%`);
|
|
337
|
+
}
|
|
338
|
+
if (operator === '_nstarts_with') {
|
|
339
|
+
dbQuery[logical].whereNot(key, 'like', `${compareValue}%`);
|
|
340
|
+
}
|
|
341
|
+
if (operator === '_istarts_with') {
|
|
342
|
+
dbQuery[logical].whereRaw(`LOWER(??) LIKE ?`, [selectionRaw, `${compareValue.toLowerCase()}%`]);
|
|
343
|
+
}
|
|
344
|
+
if (operator === '_nistarts_with') {
|
|
345
|
+
dbQuery[logical].whereRaw(`LOWER(??) NOT LIKE ?`, [selectionRaw, `${compareValue.toLowerCase()}%`]);
|
|
346
|
+
}
|
|
347
|
+
if (operator === '_ends_with') {
|
|
348
|
+
dbQuery[logical].where(key, 'like', `%${compareValue}`);
|
|
349
|
+
}
|
|
350
|
+
if (operator === '_nends_with') {
|
|
351
|
+
dbQuery[logical].whereNot(key, 'like', `%${compareValue}`);
|
|
352
|
+
}
|
|
353
|
+
if (operator === '_iends_with') {
|
|
354
|
+
dbQuery[logical].whereRaw(`LOWER(??) LIKE ?`, [selectionRaw, `%${compareValue.toLowerCase()}`]);
|
|
355
|
+
}
|
|
356
|
+
if (operator === '_niends_with') {
|
|
357
|
+
dbQuery[logical].whereRaw(`LOWER(??) NOT LIKE ?`, [selectionRaw, `%${compareValue.toLowerCase()}`]);
|
|
358
|
+
}
|
|
359
|
+
if (operator === '_gt') {
|
|
360
|
+
dbQuery[logical].where(selectionRaw, '>', compareValue);
|
|
361
|
+
}
|
|
362
|
+
if (operator === '_gte') {
|
|
363
|
+
dbQuery[logical].where(selectionRaw, '>=', compareValue);
|
|
364
|
+
}
|
|
365
|
+
if (operator === '_lt') {
|
|
366
|
+
dbQuery[logical].where(selectionRaw, '<', compareValue);
|
|
367
|
+
}
|
|
368
|
+
if (operator === '_lte') {
|
|
369
|
+
dbQuery[logical].where(selectionRaw, '<=', compareValue);
|
|
370
|
+
}
|
|
371
|
+
if (operator === '_in') {
|
|
372
|
+
let value = compareValue;
|
|
373
|
+
if (typeof value === 'string')
|
|
374
|
+
value = value.split(',');
|
|
375
|
+
dbQuery[logical].whereIn(selectionRaw, value);
|
|
376
|
+
}
|
|
377
|
+
if (operator === '_nin') {
|
|
378
|
+
let value = compareValue;
|
|
379
|
+
if (typeof value === 'string')
|
|
380
|
+
value = value.split(',');
|
|
381
|
+
dbQuery[logical].whereNotIn(selectionRaw, value);
|
|
382
|
+
}
|
|
383
|
+
if (operator === '_between') {
|
|
384
|
+
if (compareValue.length !== 2)
|
|
385
|
+
return;
|
|
386
|
+
let value = compareValue;
|
|
387
|
+
if (typeof value === 'string')
|
|
388
|
+
value = value.split(',');
|
|
389
|
+
dbQuery[logical].whereBetween(selectionRaw, value);
|
|
390
|
+
}
|
|
391
|
+
if (operator === '_nbetween') {
|
|
392
|
+
if (compareValue.length !== 2)
|
|
393
|
+
return;
|
|
394
|
+
let value = compareValue;
|
|
395
|
+
if (typeof value === 'string')
|
|
396
|
+
value = value.split(',');
|
|
397
|
+
dbQuery[logical].whereNotBetween(selectionRaw, value);
|
|
398
|
+
}
|
|
399
|
+
if (operator == '_intersects') {
|
|
400
|
+
dbQuery[logical].whereRaw(helpers.st.intersects(key, compareValue));
|
|
401
|
+
}
|
|
402
|
+
if (operator == '_nintersects') {
|
|
403
|
+
dbQuery[logical].whereRaw(helpers.st.nintersects(key, compareValue));
|
|
404
|
+
}
|
|
405
|
+
if (operator == '_intersects_bbox') {
|
|
406
|
+
dbQuery[logical].whereRaw(helpers.st.intersects_bbox(key, compareValue));
|
|
407
|
+
}
|
|
408
|
+
if (operator == '_nintersects_bbox') {
|
|
409
|
+
dbQuery[logical].whereRaw(helpers.st.nintersects_bbox(key, compareValue));
|
|
311
410
|
}
|
|
312
411
|
}
|
|
313
412
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { SchemaOverview } from '@directus/shared/types';
|
|
2
|
+
import { PrimaryKey } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Validate keys based on its type
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateKeys(schema: SchemaOverview, collection: string, keyField: string, keys: PrimaryKey | PrimaryKey[]): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateKeys = void 0;
|
|
7
|
+
const exceptions_1 = require("../exceptions");
|
|
8
|
+
const uuid_validate_1 = __importDefault(require("uuid-validate"));
|
|
9
|
+
/**
|
|
10
|
+
* Validate keys based on its type
|
|
11
|
+
*/
|
|
12
|
+
function validateKeys(schema, collection, keyField, keys) {
|
|
13
|
+
if (Array.isArray(keys)) {
|
|
14
|
+
for (const key of keys) {
|
|
15
|
+
validateKeys(schema, collection, keyField, key);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const primaryKeyFieldType = schema.collections[collection].fields[keyField].type;
|
|
20
|
+
if (primaryKeyFieldType === 'uuid' && !(0, uuid_validate_1.default)(String(keys))) {
|
|
21
|
+
throw new exceptions_1.ForbiddenException();
|
|
22
|
+
}
|
|
23
|
+
else if (primaryKeyFieldType === 'integer' && !Number.isInteger(Number(keys))) {
|
|
24
|
+
throw new exceptions_1.ForbiddenException();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.validateKeys = validateKeys;
|
|
@@ -104,7 +104,7 @@ function validateFilterPrimitive(value, key) {
|
|
|
104
104
|
false) {
|
|
105
105
|
throw new exceptions_1.InvalidQueryException(`The filter value for "${key}" has to be a string, number, or boolean`);
|
|
106
106
|
}
|
|
107
|
-
if (typeof value === 'number' &&
|
|
107
|
+
if (typeof value === 'number' && Number.isNaN(value) && value <= Number.MAX_SAFE_INTEGER) {
|
|
108
108
|
throw new exceptions_1.InvalidQueryException(`The filter value for "${key}" is not a valid number`);
|
|
109
109
|
}
|
|
110
110
|
if (typeof value === 'string' && value.length === 0) {
|