directus 9.9.1 → 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.
Files changed (67) hide show
  1. package/README.md +1 -1
  2. package/dist/app.js +3 -0
  3. package/dist/auth/drivers/oauth2.d.ts +1 -1
  4. package/dist/auth/drivers/oauth2.js +14 -11
  5. package/dist/auth/drivers/openid.d.ts +1 -1
  6. package/dist/auth/drivers/openid.js +14 -11
  7. package/dist/cli/commands/schema/apply.js +4 -3
  8. package/dist/controllers/assets.js +8 -9
  9. package/dist/database/helpers/date/dialects/sqlite.js +6 -2
  10. package/dist/database/index.js +5 -0
  11. package/dist/database/migrations/20210225A-add-relations-sort-field.js +2 -1
  12. package/dist/database/migrations/20210506A-rename-interfaces.js +2 -1
  13. package/dist/database/migrations/20210802A-replace-groups.js +2 -1
  14. package/dist/database/migrations/20210805A-update-groups.js +2 -1
  15. package/dist/database/migrations/20210805B-change-image-metadata-structure.js +3 -2
  16. package/dist/database/migrations/20211007A-update-presets.js +5 -4
  17. package/dist/database/run-ast.js +10 -14
  18. package/dist/database/system-data/fields/activity.yaml +3 -0
  19. package/dist/database/system-data/fields/dashboards.yaml +3 -1
  20. package/dist/database/system-data/fields/notifications.yaml +3 -1
  21. package/dist/database/system-data/fields/panels.yaml +3 -1
  22. package/dist/database/system-data/fields/shares.yaml +3 -1
  23. package/dist/env.js +193 -10
  24. package/dist/exceptions/index.d.ts +1 -0
  25. package/dist/exceptions/index.js +1 -0
  26. package/dist/exceptions/invalid-provider.d.ts +4 -0
  27. package/dist/exceptions/invalid-provider.js +10 -0
  28. package/dist/exceptions/range-not-satisfiable.d.ts +2 -2
  29. package/dist/exceptions/range-not-satisfiable.js +5 -1
  30. package/dist/middleware/graphql.js +2 -1
  31. package/dist/services/assets.js +27 -1
  32. package/dist/services/authentication.js +4 -1
  33. package/dist/services/fields.js +15 -8
  34. package/dist/services/graphql.js +61 -33
  35. package/dist/services/import-export.d.ts +1 -1
  36. package/dist/services/import-export.js +14 -11
  37. package/dist/services/items.d.ts +3 -3
  38. package/dist/services/items.js +31 -3
  39. package/dist/services/payload.d.ts +2 -2
  40. package/dist/services/payload.js +9 -8
  41. package/dist/services/users.d.ts +4 -0
  42. package/dist/services/users.js +20 -0
  43. package/dist/utils/apply-query.d.ts +2 -1
  44. package/dist/utils/apply-query.js +144 -149
  45. package/dist/utils/apply-snapshot.d.ts +3 -3
  46. package/dist/utils/apply-snapshot.js +64 -49
  47. package/dist/utils/get-ast-from-query.js +1 -7
  48. package/dist/utils/get-column-path.d.ts +16 -0
  49. package/dist/utils/get-column-path.js +46 -0
  50. package/dist/utils/get-default-value.js +4 -3
  51. package/dist/utils/get-permissions.d.ts +1 -1
  52. package/dist/utils/get-permissions.js +9 -8
  53. package/dist/utils/get-relation-info.d.ts +7 -0
  54. package/dist/utils/get-relation-info.js +45 -0
  55. package/dist/utils/get-relation-type.d.ts +1 -1
  56. package/dist/utils/get-schema.js +2 -1
  57. package/dist/utils/get-snapshot.js +22 -4
  58. package/dist/utils/merge-permissions-for-share.js +1 -1
  59. package/dist/utils/parse-json.d.ts +5 -0
  60. package/dist/utils/parse-json.js +19 -0
  61. package/dist/utils/reduce-schema.js +4 -5
  62. package/dist/utils/sanitize-query.d.ts +1 -2
  63. package/dist/utils/sanitize-query.js +6 -5
  64. package/dist/utils/validate-keys.d.ts +6 -0
  65. package/dist/utils/validate-keys.js +28 -0
  66. package/dist/utils/validate-query.js +1 -1
  67. package/package.json +16 -18
package/dist/env.js CHANGED
@@ -14,6 +14,180 @@ const lodash_1 = require("lodash");
14
14
  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
+ 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}$`));
17
191
  const acceptedEnvTypes = ['string', 'number', 'regex', 'array', 'json'];
18
192
  const defaults = {
19
193
  CONFIG_PATH: path_1.default.resolve(process.cwd(), '.env'),
@@ -70,6 +244,7 @@ const defaults = {
70
244
  RELATIONAL_BATCH_SIZE: 25000,
71
245
  EXPORT_BATCH_SIZE: 5000,
72
246
  FILE_METADATA_ALLOW_LIST: 'ifd0.Make,ifd0.Model,exif.FNumber,exif.ExposureTime,exif.FocalLength,exif.ISO',
247
+ GRAPHQL_INTROSPECTION: true,
73
248
  };
74
249
  // Allows us to force certain environment variable into a type, instead of relying
75
250
  // on the auto-parsed type in processValues. ref #3705
@@ -84,6 +259,7 @@ const typeMap = {
84
259
  DB_EXCLUDE_TABLES: 'array',
85
260
  IMPORT_IP_DENY_LIST: 'array',
86
261
  FILE_METADATA_ALLOW_LIST: 'array',
262
+ GRAPHQL_INTROSPECTION: 'boolean',
87
263
  };
88
264
  let env = {
89
265
  ...defaults,
@@ -177,15 +353,17 @@ function processValues(env) {
177
353
  let newKey;
178
354
  if (key.length > 5 && key.endsWith('_FILE')) {
179
355
  newKey = key.slice(0, -5);
180
- if (newKey in env) {
181
- throw new Error(`Duplicate environment variable encountered: you can't use "${newKey}" and "${key}" simultaneously.`);
182
- }
183
- try {
184
- value = fs_1.default.readFileSync(value, { encoding: 'utf8' });
185
- key = newKey;
186
- }
187
- catch {
188
- throw new Error(`Failed to read value from file "${value}", defined in environment variable "${key}".`);
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
+ }
189
367
  }
190
368
  }
191
369
  // Convert values with a type prefix
@@ -209,6 +387,8 @@ function processValues(env) {
209
387
  case 'json':
210
388
  env[key] = tryJSON(value);
211
389
  break;
390
+ case 'boolean':
391
+ env[key] = toBoolean(value);
212
392
  }
213
393
  continue;
214
394
  }
@@ -251,9 +431,12 @@ function processValues(env) {
251
431
  }
252
432
  function tryJSON(value) {
253
433
  try {
254
- return JSON.parse(value);
434
+ return (0, parse_json_1.parseJSON)(value);
255
435
  }
256
436
  catch {
257
437
  return value;
258
438
  }
259
439
  }
440
+ function toBoolean(value) {
441
+ return value === 'true' || value === true || value === '1' || value === 1;
442
+ }
@@ -7,6 +7,7 @@ export * from './invalid-credentials';
7
7
  export * from './invalid-ip';
8
8
  export * from './invalid-otp';
9
9
  export * from './invalid-payload';
10
+ export * from './invalid-provider';
10
11
  export * from './invalid-query';
11
12
  export * from './invalid-token';
12
13
  export * from './method-not-allowed';
@@ -19,6 +19,7 @@ __exportStar(require("./invalid-credentials"), exports);
19
19
  __exportStar(require("./invalid-ip"), exports);
20
20
  __exportStar(require("./invalid-otp"), exports);
21
21
  __exportStar(require("./invalid-payload"), exports);
22
+ __exportStar(require("./invalid-provider"), exports);
22
23
  __exportStar(require("./invalid-query"), exports);
23
24
  __exportStar(require("./invalid-token"), exports);
24
25
  __exportStar(require("./method-not-allowed"), exports);
@@ -0,0 +1,4 @@
1
+ import { BaseException } from '@directus/shared/exceptions';
2
+ export declare class InvalidProviderException extends BaseException {
3
+ constructor(message?: string);
4
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InvalidProviderException = void 0;
4
+ const exceptions_1 = require("@directus/shared/exceptions");
5
+ class InvalidProviderException extends exceptions_1.BaseException {
6
+ constructor(message = 'Invalid provider.') {
7
+ super(message, 403, 'INVALID_PROVIDER');
8
+ }
9
+ }
10
+ exports.InvalidProviderException = InvalidProviderException;
@@ -1,5 +1,5 @@
1
- import { Range } from '@directus/drive';
2
1
  import { BaseException } from '@directus/shared/exceptions';
2
+ import { Range } from '@directus/drive';
3
3
  export declare class RangeNotSatisfiableException extends BaseException {
4
- constructor(range: Range);
4
+ constructor(range?: Range);
5
5
  }
@@ -4,7 +4,11 @@ exports.RangeNotSatisfiableException = void 0;
4
4
  const exceptions_1 = require("@directus/shared/exceptions");
5
5
  class RangeNotSatisfiableException extends exceptions_1.BaseException {
6
6
  constructor(range) {
7
- super(`Range "${range.start}-${range.end}" is invalid or the file's size doesn't match the requested range.`, 416, 'RANGE_NOT_SATISFIABLE');
7
+ var _a, _b;
8
+ const rangeString = range && ((range === null || range === void 0 ? void 0 : range.start) !== undefined || (range === null || range === void 0 ? void 0 : range.end) !== undefined)
9
+ ? `"${(_a = range.start) !== null && _a !== void 0 ? _a : ''}-${(_b = range.end) !== null && _b !== void 0 ? _b : ''}" `
10
+ : '';
11
+ super(`Range ${rangeString}is invalid or the file's size doesn't match the requested range.`, 416, 'RANGE_NOT_SATISFIABLE');
8
12
  }
9
13
  }
10
14
  exports.RangeNotSatisfiableException = RangeNotSatisfiableException;
@@ -7,6 +7,7 @@ exports.parseGraphQL = void 0;
7
7
  const graphql_1 = require("graphql");
8
8
  const exceptions_1 = require("../exceptions");
9
9
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
10
+ const parse_json_1 = require("../utils/parse-json");
10
11
  exports.parseGraphQL = (0, async_handler_1.default)(async (req, res, next) => {
11
12
  if (req.method !== 'GET' && req.method !== 'POST') {
12
13
  throw new exceptions_1.MethodNotAllowedException('GraphQL only supports GET and POST requests.', { allow: ['GET', 'POST'] });
@@ -19,7 +20,7 @@ exports.parseGraphQL = (0, async_handler_1.default)(async (req, res, next) => {
19
20
  query = req.query.query || null;
20
21
  if (req.query.variables) {
21
22
  try {
22
- variables = JSON.parse(req.query.variables);
23
+ variables = (0, parse_json_1.parseJSON)(req.query.variables);
23
24
  }
24
25
  catch {
25
26
  throw new exceptions_1.InvalidQueryException(`Variables are invalid JSON.`);
@@ -70,9 +70,35 @@ class AssetsService {
70
70
  if (!exists)
71
71
  throw new exceptions_1.ForbiddenException();
72
72
  if (range) {
73
- if (range.start >= file.filesize || (range.end && range.end >= file.filesize)) {
73
+ const missingRangeLimits = range.start === undefined && range.end === undefined;
74
+ const endBeforeStart = range.start !== undefined && range.end !== undefined && range.end <= range.start;
75
+ const startOverflow = range.start !== undefined && range.start >= file.filesize;
76
+ const endUnderflow = range.end !== undefined && range.end <= 0;
77
+ if (missingRangeLimits || endBeforeStart || startOverflow || endUnderflow) {
74
78
  throw new exceptions_1.RangeNotSatisfiableException(range);
75
79
  }
80
+ const lastByte = file.filesize - 1;
81
+ if (range.end) {
82
+ if (range.start === undefined) {
83
+ // fetch chunk from tail
84
+ range.start = file.filesize - range.end;
85
+ range.end = lastByte;
86
+ }
87
+ if (range.end >= file.filesize) {
88
+ // fetch entire file
89
+ range.end = lastByte;
90
+ }
91
+ }
92
+ if (range.start) {
93
+ if (range.end === undefined) {
94
+ // fetch entire file
95
+ range.end = lastByte;
96
+ }
97
+ if (range.start < 0) {
98
+ // fetch file from head
99
+ range.start = 0;
100
+ }
101
+ }
76
102
  }
77
103
  const type = file.type;
78
104
  const transforms = TransformationUtils.resolvePreset(transformation, file);
@@ -45,7 +45,6 @@ class AuthenticationService {
45
45
  .from('directus_users as u')
46
46
  .leftJoin('directus_roles as r', 'u.role', 'r.id')
47
47
  .where('u.id', await provider.getUserID((0, lodash_1.cloneDeep)(payload)))
48
- .andWhere('u.provider', providerName)
49
48
  .first();
50
49
  const updatedPayload = await emitter_1.default.emitFilter('auth.login', payload, {
51
50
  status: 'pending',
@@ -79,6 +78,10 @@ class AuthenticationService {
79
78
  throw new exceptions_1.InvalidCredentialsException();
80
79
  }
81
80
  }
81
+ else if (user.provider !== providerName) {
82
+ await (0, stall_1.stall)(STALL_TIME, timeStart);
83
+ throw new exceptions_1.InvalidProviderException();
84
+ }
82
85
  const settingsService = new settings_1.SettingsService({
83
86
  knex: this.knex,
84
87
  schema: this.schema,
@@ -351,13 +351,6 @@ class FieldsService {
351
351
  });
352
352
  await this.knex.transaction(async (trx) => {
353
353
  var _a, _b;
354
- if (this.schema.collections[collection] &&
355
- field in this.schema.collections[collection].fields &&
356
- this.schema.collections[collection].fields[field].alias === false) {
357
- await trx.schema.table(collection, (table) => {
358
- table.dropColumn(field);
359
- });
360
- }
361
354
  const relations = this.schema.relations.filter((relation) => {
362
355
  var _a;
363
356
  return ((relation.collection === collection && relation.field === field) ||
@@ -389,6 +382,14 @@ class FieldsService {
389
382
  .where({ many_collection: relation.collection, many_field: relation.field });
390
383
  }
391
384
  }
385
+ // Delete field only after foreign key constraints are removed
386
+ if (this.schema.collections[collection] &&
387
+ field in this.schema.collections[collection].fields &&
388
+ this.schema.collections[collection].fields[field].alias === false) {
389
+ await trx.schema.table(collection, (table) => {
390
+ table.dropColumn(field);
391
+ });
392
+ }
392
393
  const collectionMeta = await trx
393
394
  .select('archive_field', 'sort_field')
394
395
  .from('directus_collections')
@@ -441,7 +442,13 @@ class FieldsService {
441
442
  if (field.type === 'alias' || field.type === 'unknown')
442
443
  return;
443
444
  if ((_a = field.schema) === null || _a === void 0 ? void 0 : _a.has_auto_increment) {
444
- column = table.increments(field.field);
445
+ if (field.type === 'bigInteger') {
446
+ // Create an auto-incremented big integer (MySQL, PostgreSQL) or an auto-incremented integer (other DBs)
447
+ column = table.bigIncrements(field.field);
448
+ }
449
+ else {
450
+ column = table.increments(field.field);
451
+ }
445
452
  }
446
453
  else if (field.type === 'string') {
447
454
  column = table.string(field.field, (_c = (_b = field.schema) === null || _b === void 0 ? void 0 : _b.max_length) !== null && _c !== void 0 ? _c : undefined);
@@ -89,7 +89,16 @@ class GraphQLService {
89
89
  async execute({ document, variables, operationName, contextValue, }) {
90
90
  var _a;
91
91
  const schema = this.getSchema();
92
- const validationErrors = (0, graphql_1.validate)(schema, document, graphql_1.specifiedRules);
92
+ const validationErrors = (0, graphql_1.validate)(schema, document, [
93
+ ...graphql_1.specifiedRules,
94
+ (context) => ({
95
+ Field(node) {
96
+ if (env_1.default.GRAPHQL_INTROSPECTION === false && (node.name.value === '__schema' || node.name.value === '__type')) {
97
+ context.reportError(new graphql_1.GraphQLError('GraphQL introspection is not allowed. The query contained __schema or __type.', [node]));
98
+ }
99
+ },
100
+ }),
101
+ ]);
93
102
  if (validationErrors.length > 0) {
94
103
  throw new exceptions_1.GraphQLValidationException({ graphqlErrors: validationErrors });
95
104
  }
@@ -161,16 +170,7 @@ class GraphQLService {
161
170
  acc[collectionName] = ReadCollectionTypes[collection.collection].getResolver(collection.collection);
162
171
  if (this.schema.collections[collection.collection].singleton === false) {
163
172
  acc[`${collectionName}_by_id`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_by_id`);
164
- const hasAggregate = Object.values(collection.fields).some((field) => {
165
- const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type);
166
- if (graphqlType === graphql_1.GraphQLInt || graphqlType === graphql_1.GraphQLFloat) {
167
- return true;
168
- }
169
- return false;
170
- });
171
- if (hasAggregate) {
172
- acc[`${collectionName}_aggregated`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_aggregated`);
173
- }
173
+ acc[`${collectionName}_aggregated`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_aggregated`);
174
174
  }
175
175
  return acc;
176
176
  }, {}));
@@ -415,7 +415,8 @@ class GraphQLService {
415
415
  const { CollectionTypes: ReadCollectionTypes } = getTypes('read');
416
416
  const ReadableCollectionFilterTypes = {};
417
417
  const AggregatedFunctions = {};
418
- const AggregatedFilters = {};
418
+ const AggregatedFields = {};
419
+ const AggregateMethods = {};
419
420
  const StringFilterOperators = schemaComposer.createInputTC({
420
421
  name: 'string_filter_operators',
421
422
  fields: {
@@ -671,7 +672,7 @@ class GraphQLService {
671
672
  _and: [ReadableCollectionFilterTypes[collection.collection]],
672
673
  _or: [ReadableCollectionFilterTypes[collection.collection]],
673
674
  });
674
- AggregatedFilters[collection.collection] = schemaComposer.createObjectTC({
675
+ AggregatedFields[collection.collection] = schemaComposer.createObjectTC({
675
676
  name: `${collection.collection}_aggregated_fields`,
676
677
  fields: Object.values(collection.fields).reduce((acc, field) => {
677
678
  const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type);
@@ -689,46 +690,71 @@ class GraphQLService {
689
690
  return acc;
690
691
  }, {}),
691
692
  });
692
- AggregatedFunctions[collection.collection] = schemaComposer.createObjectTC({
693
- name: `${collection.collection}_aggregated`,
694
- fields: {
695
- group: {
696
- name: 'group',
697
- type: graphql_compose_1.GraphQLJSON,
698
- },
693
+ AggregateMethods[collection.collection] = {
694
+ group: {
695
+ name: 'group',
696
+ type: graphql_compose_1.GraphQLJSON,
697
+ },
698
+ countAll: {
699
+ name: 'countAll',
700
+ type: graphql_1.GraphQLInt,
701
+ },
702
+ count: {
703
+ name: 'count',
704
+ type: schemaComposer.createObjectTC({
705
+ name: `${collection.collection}_aggregated_count`,
706
+ fields: Object.values(collection.fields).reduce((acc, field) => {
707
+ acc[field.field] = {
708
+ type: graphql_1.GraphQLInt,
709
+ description: field.note,
710
+ };
711
+ return acc;
712
+ }, {}),
713
+ }),
714
+ },
715
+ };
716
+ const hasNumericAggregates = Object.values(collection.fields).some((field) => {
717
+ const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type);
718
+ if (graphqlType === graphql_1.GraphQLInt || graphqlType === graphql_1.GraphQLFloat) {
719
+ return true;
720
+ }
721
+ return false;
722
+ });
723
+ if (hasNumericAggregates) {
724
+ Object.assign(AggregateMethods[collection.collection], {
699
725
  avg: {
700
726
  name: 'avg',
701
- type: AggregatedFilters[collection.collection],
727
+ type: AggregatedFields[collection.collection],
702
728
  },
703
729
  sum: {
704
730
  name: 'sum',
705
- type: AggregatedFilters[collection.collection],
706
- },
707
- count: {
708
- name: 'count',
709
- type: AggregatedFilters[collection.collection],
731
+ type: AggregatedFields[collection.collection],
710
732
  },
711
733
  countDistinct: {
712
734
  name: 'countDistinct',
713
- type: AggregatedFilters[collection.collection],
735
+ type: AggregatedFields[collection.collection],
714
736
  },
715
737
  avgDistinct: {
716
738
  name: 'avgDistinct',
717
- type: AggregatedFilters[collection.collection],
739
+ type: AggregatedFields[collection.collection],
718
740
  },
719
741
  sumDistinct: {
720
742
  name: 'sumDistinct',
721
- type: AggregatedFilters[collection.collection],
743
+ type: AggregatedFields[collection.collection],
722
744
  },
723
745
  min: {
724
746
  name: 'min',
725
- type: AggregatedFilters[collection.collection],
747
+ type: AggregatedFields[collection.collection],
726
748
  },
727
749
  max: {
728
750
  name: 'max',
729
- type: AggregatedFilters[collection.collection],
751
+ type: AggregatedFields[collection.collection],
730
752
  },
731
- },
753
+ });
754
+ }
755
+ AggregatedFunctions[collection.collection] = schemaComposer.createObjectTC({
756
+ name: `${collection.collection}_aggregated`,
757
+ fields: AggregateMethods[collection.collection],
732
758
  });
733
759
  ReadCollectionTypes[collection.collection].addResolver({
734
760
  name: collection.collection,
@@ -1228,7 +1254,7 @@ class GraphQLService {
1228
1254
  if (!query.deep)
1229
1255
  query.deep = {};
1230
1256
  const args = this.parseArgs(selection.arguments, variableValues);
1231
- (0, lodash_1.set)(query.deep, current, (0, lodash_1.merge)((0, lodash_1.get)(query.deep, current), (0, lodash_1.mapKeys)((0, sanitize_query_1.sanitizeQuery)(args, this.accountability), (value, key) => `_${key}`)));
1257
+ (0, lodash_1.set)(query.deep, currentAlias !== null && currentAlias !== void 0 ? currentAlias : current, (0, lodash_1.merge)((0, lodash_1.get)(query.deep, currentAlias !== null && currentAlias !== void 0 ? currentAlias : current), (0, lodash_1.mapKeys)((0, sanitize_query_1.sanitizeQuery)(args, this.accountability), (value, key) => `_${key}`)));
1232
1258
  }
1233
1259
  }
1234
1260
  }
@@ -1287,8 +1313,10 @@ class GraphQLService {
1287
1313
  */
1288
1314
  formatError(error) {
1289
1315
  if (Array.isArray(error)) {
1316
+ error[0].extensions.code = error[0].code;
1290
1317
  return new graphql_1.GraphQLError(error[0].message, undefined, undefined, undefined, undefined, error[0]);
1291
1318
  }
1319
+ error.extensions.code = error.code;
1292
1320
  return new graphql_1.GraphQLError(error.message, undefined, undefined, undefined, undefined, error);
1293
1321
  }
1294
1322
  /**
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
+ import { Accountability, Query, SchemaOverview } from '@directus/shared/types';
2
3
  import { Knex } from 'knex';
3
4
  import { AbstractServiceOptions, File } from '../types';
4
- import { Accountability, Query, SchemaOverview } from '@directus/shared/types';
5
5
  export declare class ImportService {
6
6
  knex: Knex;
7
7
  accountability: Accountability | null;
@@ -4,24 +4,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ExportService = exports.ImportService = void 0;
7
- const database_1 = __importDefault(require("../database"));
8
- const exceptions_1 = require("../exceptions");
9
- const StreamArray_1 = __importDefault(require("stream-json/streamers/StreamArray"));
10
- const items_1 = require("./items");
7
+ const utils_1 = require("@directus/shared/utils");
11
8
  const async_1 = require("async");
12
- const destroy_1 = __importDefault(require("destroy"));
13
9
  const csv_parser_1 = __importDefault(require("csv-parser"));
14
- const lodash_1 = require("lodash");
10
+ const destroy_1 = __importDefault(require("destroy"));
11
+ const fs_extra_1 = require("fs-extra");
15
12
  const js2xmlparser_1 = require("js2xmlparser");
16
13
  const json2csv_1 = require("json2csv");
17
- const fs_extra_1 = require("fs-extra");
14
+ const lodash_1 = require("lodash");
15
+ const StreamArray_1 = __importDefault(require("stream-json/streamers/StreamArray"));
16
+ const strip_bom_stream_1 = __importDefault(require("strip-bom-stream"));
18
17
  const tmp_promise_1 = require("tmp-promise");
18
+ const database_1 = __importDefault(require("../database"));
19
19
  const env_1 = __importDefault(require("../env"));
20
- const files_1 = require("./files");
20
+ const exceptions_1 = require("../exceptions");
21
+ const logger_1 = __importDefault(require("../logger"));
21
22
  const get_date_formatted_1 = require("../utils/get-date-formatted");
22
- const utils_1 = require("@directus/shared/utils");
23
+ const parse_json_1 = require("../utils/parse-json");
24
+ const files_1 = require("./files");
25
+ const items_1 = require("./items");
23
26
  const notifications_1 = require("./notifications");
24
- const logger_1 = __importDefault(require("../logger"));
25
27
  class ImportService {
26
28
  constructor(options) {
27
29
  this.knex = options.knex || (0, database_1.default)();
@@ -91,6 +93,7 @@ class ImportService {
91
93
  });
92
94
  return new Promise((resolve, reject) => {
93
95
  stream
96
+ .pipe((0, strip_bom_stream_1.default)())
94
97
  .pipe((0, csv_parser_1.default)())
95
98
  .on('data', (value) => {
96
99
  const obj = (0, lodash_1.transform)(value, (result, value, key) => {
@@ -99,7 +102,7 @@ class ImportService {
99
102
  }
100
103
  else {
101
104
  try {
102
- const parsedJson = JSON.parse(value);
105
+ const parsedJson = (0, parse_json_1.parseJSON)(value);
103
106
  (0, lodash_1.set)(result, key, parsedJson);
104
107
  }
105
108
  catch {
@@ -1,7 +1,7 @@
1
- import { Knex } from 'knex';
1
+ import { Accountability, PermissionsAction, Query, SchemaOverview } from '@directus/shared/types';
2
2
  import Keyv from 'keyv';
3
- import { Accountability, Query, PermissionsAction, SchemaOverview } from '@directus/shared/types';
4
- import { AbstractService, AbstractServiceOptions, Item as AnyItem, PrimaryKey, MutationOptions } from '../types';
3
+ import { Knex } from 'knex';
4
+ import { AbstractService, AbstractServiceOptions, Item as AnyItem, MutationOptions, PrimaryKey } from '../types';
5
5
  export declare type QueryOptions = {
6
6
  stripNonRequested?: boolean;
7
7
  permissionsAction?: PermissionsAction;