directus 9.14.3 → 9.15.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 (55) hide show
  1. package/dist/app.js +1 -1
  2. package/dist/auth/drivers/ldap.js +18 -8
  3. package/dist/auth/drivers/oauth2.js +19 -9
  4. package/dist/auth/drivers/openid.js +19 -9
  5. package/dist/cache.d.ts +3 -0
  6. package/dist/cache.js +21 -2
  7. package/dist/constants.d.ts +1 -0
  8. package/dist/constants.js +2 -1
  9. package/dist/controllers/assets.js +23 -1
  10. package/dist/controllers/extensions.js +3 -2
  11. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +3 -14
  12. package/dist/database/helpers/schema/dialects/cockroachdb.js +2 -8
  13. package/dist/database/helpers/schema/dialects/oracle.d.ts +3 -10
  14. package/dist/database/helpers/schema/dialects/oracle.js +2 -5
  15. package/dist/database/helpers/schema/types.d.ts +8 -18
  16. package/dist/database/helpers/schema/types.js +7 -36
  17. package/dist/database/migrations/20201105B-change-webhook-url-type.js +3 -2
  18. package/dist/database/migrations/20210312A-webhooks-collections-text.js +3 -2
  19. package/dist/database/migrations/20210415A-make-filesize-nullable.js +2 -2
  20. package/dist/database/migrations/20210510A-restructure-relations.js +3 -3
  21. package/dist/database/migrations/20210903A-add-auth-provider.js +2 -2
  22. package/dist/database/migrations/20210907A-webhooks-collections-not-null.js +4 -2
  23. package/dist/database/migrations/20210920A-webhooks-url-not-null.js +2 -2
  24. package/dist/database/migrations/20220303A-remove-default-project-color.js +2 -2
  25. package/dist/database/migrations/20220325B-add-default-language.js +2 -2
  26. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.js +2 -2
  27. package/dist/database/migrations/20220801A-update-notifications-timestamp-column.d.ts +3 -0
  28. package/dist/database/migrations/20220801A-update-notifications-timestamp-column.js +19 -0
  29. package/dist/database/migrations/20220802A-add-custom-aspect-ratios.d.ts +3 -0
  30. package/dist/database/migrations/20220802A-add-custom-aspect-ratios.js +15 -0
  31. package/dist/database/system-data/fields/settings.yaml +33 -0
  32. package/dist/extensions.d.ts +2 -2
  33. package/dist/extensions.js +10 -9
  34. package/dist/logger.js +0 -1
  35. package/dist/middleware/cache.js +3 -3
  36. package/dist/middleware/respond.js +2 -2
  37. package/dist/operations/item-create/index.js +1 -1
  38. package/dist/operations/item-delete/index.js +2 -2
  39. package/dist/operations/item-read/index.js +2 -2
  40. package/dist/operations/item-update/index.js +3 -3
  41. package/dist/services/assets.d.ts +1 -1
  42. package/dist/services/assets.js +7 -2
  43. package/dist/services/authorization.js +2 -1
  44. package/dist/services/graphql/index.js +37 -11
  45. package/dist/services/graphql/types/hash.d.ts +2 -0
  46. package/dist/services/graphql/types/hash.js +9 -0
  47. package/dist/services/specifications.js +1 -6
  48. package/dist/utils/apply-query.js +11 -0
  49. package/dist/utils/compress.d.ts +3 -0
  50. package/dist/utils/compress.js +17 -0
  51. package/dist/utils/get-ast-from-query.js +1 -1
  52. package/dist/utils/get-graphql-type.js +3 -0
  53. package/dist/utils/get-permissions.js +2 -2
  54. package/dist/utils/get-schema.js +1 -2
  55. package/package.json +10 -9
@@ -4,7 +4,7 @@ exports.down = exports.up = void 0;
4
4
  const helpers_1 = require("../helpers");
5
5
  async function up(knex) {
6
6
  const helper = (0, helpers_1.getHelpers)(knex).schema;
7
- await helper.changeToString('directus_panels', 'icon', {
7
+ await helper.changeToType('directus_panels', 'icon', 'string', {
8
8
  nullable: true,
9
9
  default: null,
10
10
  length: 30,
@@ -13,7 +13,7 @@ async function up(knex) {
13
13
  exports.up = up;
14
14
  async function down(knex) {
15
15
  const helper = (0, helpers_1.getHelpers)(knex).schema;
16
- await helper.changeToString('directus_panels', 'icon', {
16
+ await helper.changeToType('directus_panels', 'icon', 'string', {
17
17
  nullable: true,
18
18
  default: 'insert_chart',
19
19
  length: 30,
@@ -0,0 +1,3 @@
1
+ import { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.down = exports.up = void 0;
4
+ const helpers_1 = require("../helpers");
5
+ async function up(knex) {
6
+ const helper = (0, helpers_1.getHelpers)(knex).schema;
7
+ await helper.changeToType('directus_notifications', 'timestamp', 'timestamp', {
8
+ nullable: true,
9
+ default: knex.fn.now(),
10
+ });
11
+ }
12
+ exports.up = up;
13
+ async function down(knex) {
14
+ const helper = (0, helpers_1.getHelpers)(knex).schema;
15
+ await helper.changeToType('directus_notifications', 'timestamp', 'timestamp', {
16
+ nullable: false,
17
+ });
18
+ }
19
+ exports.down = down;
@@ -0,0 +1,3 @@
1
+ import { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.down = exports.up = void 0;
4
+ async function up(knex) {
5
+ await knex.schema.alterTable('directus_settings', (table) => {
6
+ table.json('custom_aspect_ratios');
7
+ });
8
+ }
9
+ exports.up = up;
10
+ async function down(knex) {
11
+ await knex.schema.alterTable('directus_settings', (table) => {
12
+ table.dropColumn('custom_aspect_ratios');
13
+ });
14
+ }
15
+ exports.down = down;
@@ -392,3 +392,36 @@ fields:
392
392
  - field: translation_strings
393
393
  special: cast-json
394
394
  hidden: true
395
+
396
+ - field: image_editor
397
+ interface: presentation-divider
398
+ options:
399
+ icon: image
400
+ title: $t:fields.directus_settings.image_editor
401
+ special:
402
+ - alias
403
+ - no-data
404
+ width: full
405
+
406
+ - field: custom_aspect_ratios
407
+ interface: list
408
+ special: cast-json
409
+ options:
410
+ template: '{{text}}'
411
+ fields:
412
+ - field: text
413
+ name: $t:text
414
+ type: string
415
+ meta:
416
+ interface: text-input
417
+ width: half
418
+ options:
419
+ placeholder: $t:text
420
+ - field: value
421
+ name: $t:value
422
+ type: float
423
+ meta:
424
+ interface: input
425
+ width: half
426
+ options:
427
+ placeholder: $t:value
@@ -1,5 +1,5 @@
1
1
  import { Router } from 'express';
2
- import { AppExtensionType, ExtensionType } from '@directus/shared/types';
2
+ import { AppExtensionType, ExtensionType, HybridExtensionType } from '@directus/shared/types';
3
3
  export declare function getExtensionManager(): ExtensionManager;
4
4
  declare type Options = {
5
5
  schedule: boolean;
@@ -19,7 +19,7 @@ declare class ExtensionManager {
19
19
  initialize(options?: Partial<Options>): Promise<void>;
20
20
  reload(): void;
21
21
  getExtensionsList(type?: ExtensionType): string[];
22
- getAppExtensions(type: AppExtensionType): string | undefined;
22
+ getAppExtensions(type: AppExtensionType | HybridExtensionType): string | undefined;
23
23
  getEndpointRouter(): Router;
24
24
  private load;
25
25
  private unload;
@@ -132,7 +132,7 @@ class ExtensionManager {
132
132
  }
133
133
  async load() {
134
134
  try {
135
- await (0, node_1.ensureExtensionDirs)(env_1.default.EXTENSIONS_PATH, env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_EXTENSION_TYPES);
135
+ await (0, node_1.ensureExtensionDirs)(env_1.default.EXTENSIONS_PATH, env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_OR_HYBRID_EXTENSION_TYPES);
136
136
  this.extensions = await this.getExtensions();
137
137
  }
138
138
  catch (err) {
@@ -160,9 +160,9 @@ class ExtensionManager {
160
160
  initializeWatcher() {
161
161
  if (this.options.watch && !this.watcher) {
162
162
  logger_1.default.info('Watching extensions for changes...');
163
- const localExtensionPaths = (env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_EXTENSION_TYPES).flatMap((type) => {
163
+ const localExtensionPaths = (env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_OR_HYBRID_EXTENSION_TYPES).flatMap((type) => {
164
164
  const typeDir = path_1.default.posix.join(path_1.default.relative('.', env_1.default.EXTENSIONS_PATH).split(path_1.default.sep).join(path_1.default.posix.sep), (0, utils_1.pluralize)(type));
165
- return (0, utils_1.isHybridExtension)(type)
165
+ return (0, utils_1.isIn)(type, constants_1.HYBRID_EXTENSION_TYPES)
166
166
  ? [path_1.default.posix.join(typeDir, '*', 'app.js'), path_1.default.posix.join(typeDir, '*', 'api.js')]
167
167
  : path_1.default.posix.join(typeDir, '*', 'index.js');
168
168
  });
@@ -179,9 +179,9 @@ class ExtensionManager {
179
179
  if (this.watcher) {
180
180
  const toPackageExtensionPaths = (extensions) => extensions
181
181
  .filter((extension) => !extension.local)
182
- .flatMap((extension) => extension.type === constants_1.PACK_EXTENSION_TYPE
182
+ .flatMap((extension) => (0, utils_1.isTypeIn)(extension, constants_1.PACKAGE_EXTENSION_TYPES)
183
183
  ? path_1.default.resolve(extension.path, 'package.json')
184
- : (0, utils_1.isExtensionObject)(extension, constants_1.HYBRID_EXTENSION_TYPES)
184
+ : (0, utils_1.isTypeIn)(extension, constants_1.HYBRID_EXTENSION_TYPES)
185
185
  ? [
186
186
  path_1.default.resolve(extension.path, extension.entrypoint.app),
187
187
  path_1.default.resolve(extension.path, extension.entrypoint.api),
@@ -194,8 +194,8 @@ class ExtensionManager {
194
194
  }
195
195
  }
196
196
  async getExtensions() {
197
- const packageExtensions = await (0, node_1.getPackageExtensions)('.', env_1.default.SERVE_APP ? constants_1.EXTENSION_PACKAGE_TYPES : constants_1.API_EXTENSION_PACKAGE_TYPES);
198
- const localExtensions = await (0, node_1.getLocalExtensions)(env_1.default.EXTENSIONS_PATH, env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_EXTENSION_TYPES);
197
+ const packageExtensions = await (0, node_1.getPackageExtensions)('.', env_1.default.SERVE_APP ? constants_1.EXTENSION_PACKAGE_TYPES : constants_1.API_OR_HYBRID_EXTENSION_PACKAGE_TYPES);
198
+ const localExtensions = await (0, node_1.getLocalExtensions)(env_1.default.EXTENSIONS_PATH, env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_OR_HYBRID_EXTENSION_TYPES);
199
199
  return [...packageExtensions, ...localExtensions];
200
200
  }
201
201
  async generateExtensionBundles() {
@@ -205,7 +205,7 @@ class ExtensionManager {
205
205
  replacement: path,
206
206
  }));
207
207
  const bundles = {};
208
- for (const extensionType of constants_1.APP_EXTENSION_TYPES) {
208
+ for (const extensionType of constants_1.APP_OR_HYBRID_EXTENSION_TYPES) {
209
209
  const entry = (0, node_1.generateExtensionsEntry)(extensionType, this.extensions);
210
210
  try {
211
211
  const bundle = await (0, rollup_1.rollup)({
@@ -226,7 +226,8 @@ class ExtensionManager {
226
226
  return bundles;
227
227
  }
228
228
  async getSharedDepsMapping(deps) {
229
- const appDir = await fs_extra_1.default.readdir(path_1.default.join((0, node_1.resolvePackage)('@directus/app'), 'dist', 'assets'));
229
+ var _a;
230
+ const appDir = await fs_extra_1.default.readdir(path_1.default.join((0, node_1.resolvePackage)('@directus/app', (_a = require.main) === null || _a === void 0 ? void 0 : _a.filename), 'dist', 'assets'));
230
231
  const depsMapping = {};
231
232
  for (const dep of deps) {
232
233
  const depRegex = new RegExp(`${(0, lodash_1.escapeRegExp)(dep.replace(/\//g, '_'))}\\.[0-9a-f]{8}\\.entry\\.js`);
package/dist/logger.js CHANGED
@@ -67,7 +67,6 @@ const httpLoggerEnvConfig = (0, get_config_from_env_1.getConfigFromEnv)('LOGGER_
67
67
  exports.expressLogger = (0, pino_http_1.default)({
68
68
  logger,
69
69
  ...httpLoggerEnvConfig,
70
- }, {
71
70
  serializers: {
72
71
  req(request) {
73
72
  const output = pino_http_1.stdSerializers.req(request);
@@ -10,7 +10,7 @@ const get_cache_headers_1 = require("../utils/get-cache-headers");
10
10
  const get_cache_key_1 = require("../utils/get-cache-key");
11
11
  const logger_1 = __importDefault(require("../logger"));
12
12
  const checkCacheMiddleware = (0, async_handler_1.default)(async (req, res, next) => {
13
- var _a, _b, _c;
13
+ var _a, _b, _c, _d;
14
14
  const { cache } = (0, cache_1.getCache)();
15
15
  if (req.method.toLowerCase() !== 'get' && ((_a = req.path) === null || _a === void 0 ? void 0 : _a.startsWith('/graphql')) === false)
16
16
  return next();
@@ -26,7 +26,7 @@ const checkCacheMiddleware = (0, async_handler_1.default)(async (req, res, next)
26
26
  const key = (0, get_cache_key_1.getCacheKey)(req);
27
27
  let cachedData;
28
28
  try {
29
- cachedData = await cache.get(key);
29
+ cachedData = await (0, cache_1.getCacheValue)(cache, key);
30
30
  }
31
31
  catch (err) {
32
32
  logger_1.default.warn(err, `[cache] Couldn't read key ${key}. ${err.message}`);
@@ -37,7 +37,7 @@ const checkCacheMiddleware = (0, async_handler_1.default)(async (req, res, next)
37
37
  if (cachedData) {
38
38
  let cacheExpiryDate;
39
39
  try {
40
- cacheExpiryDate = (await cache.get(`${key}__expires_at`));
40
+ cacheExpiryDate = (_d = (await (0, cache_1.getCacheValue)(cache, `${key}__expires_at`))) === null || _d === void 0 ? void 0 : _d.exp;
41
41
  }
42
42
  catch (err) {
43
43
  logger_1.default.warn(err, `[cache] Couldn't read key ${`${key}__expires_at`}. ${err.message}`);
@@ -32,8 +32,8 @@ exports.respond = (0, async_handler_1.default)(async (req, res) => {
32
32
  exceedsMaxSize === false) {
33
33
  const key = (0, get_cache_key_1.getCacheKey)(req);
34
34
  try {
35
- await cache.set(key, res.locals.payload, (0, ms_1.default)(env_1.default.CACHE_TTL));
36
- await cache.set(`${key}__expires_at`, Date.now() + (0, ms_1.default)(env_1.default.CACHE_TTL));
35
+ await (0, cache_1.setCacheValue)(cache, key, res.locals.payload, (0, ms_1.default)(env_1.default.CACHE_TTL));
36
+ await (0, cache_1.setCacheValue)(cache, `${key}__expires_at`, { exp: Date.now() + (0, ms_1.default)(env_1.default.CACHE_TTL) });
37
37
  }
38
38
  catch (err) {
39
39
  logger_1.default.warn(err, `[cache] Couldn't set key ${key}. ${err}`);
@@ -32,7 +32,7 @@ exports.default = (0, utils_1.defineOperationApi)({
32
32
  result = null;
33
33
  }
34
34
  else {
35
- result = await itemsService.createMany((0, utils_1.toArray)(payloadObject), { emitEvents });
35
+ result = await itemsService.createMany((0, utils_1.toArray)(payloadObject), { emitEvents: !!emitEvents });
36
36
  }
37
37
  return result;
38
38
  },
@@ -35,10 +35,10 @@ exports.default = (0, utils_1.defineOperationApi)({
35
35
  else {
36
36
  const keys = (0, utils_1.toArray)(key);
37
37
  if (keys.length === 1) {
38
- result = await itemsService.deleteOne(keys[0], { emitEvents });
38
+ result = await itemsService.deleteOne(keys[0], { emitEvents: !!emitEvents });
39
39
  }
40
40
  else {
41
- result = await itemsService.deleteMany(keys, { emitEvents });
41
+ result = await itemsService.deleteMany(keys, { emitEvents: !!emitEvents });
42
42
  }
43
43
  }
44
44
  return result;
@@ -35,10 +35,10 @@ exports.default = (0, utils_1.defineOperationApi)({
35
35
  else {
36
36
  const keys = (0, utils_1.toArray)(key);
37
37
  if (keys.length === 1) {
38
- result = await itemsService.readOne(keys[0], sanitizedQueryObject, { emitEvents });
38
+ result = await itemsService.readOne(keys[0], sanitizedQueryObject, { emitEvents: !!emitEvents });
39
39
  }
40
40
  else {
41
- result = await itemsService.readMany(keys, sanitizedQueryObject, { emitEvents });
41
+ result = await itemsService.readMany(keys, sanitizedQueryObject, { emitEvents: !!emitEvents });
42
42
  }
43
43
  }
44
44
  return result;
@@ -35,15 +35,15 @@ exports.default = (0, utils_1.defineOperationApi)({
35
35
  }
36
36
  let result;
37
37
  if (!key || (Array.isArray(key) && key.length === 0)) {
38
- result = await itemsService.updateByQuery(sanitizedQueryObject, payloadObject, { emitEvents });
38
+ result = await itemsService.updateByQuery(sanitizedQueryObject, payloadObject, { emitEvents: !!emitEvents });
39
39
  }
40
40
  else {
41
41
  const keys = (0, utils_1.toArray)(key);
42
42
  if (keys.length === 1) {
43
- result = await itemsService.updateOne(keys[0], payloadObject, { emitEvents });
43
+ result = await itemsService.updateOne(keys[0], payloadObject, { emitEvents: !!emitEvents });
44
44
  }
45
45
  else {
46
- result = await itemsService.updateMany(keys, payloadObject, { emitEvents });
46
+ result = await itemsService.updateMany(keys, payloadObject, { emitEvents: !!emitEvents });
47
47
  }
48
48
  }
49
49
  return result;
@@ -1,8 +1,8 @@
1
1
  /// <reference types="node" />
2
2
  import { Range, StatResponse } from '@directus/drive';
3
+ import { Accountability } from '@directus/shared/types';
3
4
  import { Knex } from 'knex';
4
5
  import { AbstractServiceOptions, TransformationParams, TransformationPreset } from '../types';
5
- import { Accountability } from '@directus/shared/types';
6
6
  import { AuthorizationService } from './authorization';
7
7
  export declare class AssetsService {
8
8
  knex: Knex;
@@ -32,13 +32,14 @@ const mime_types_1 = require("mime-types");
32
32
  const object_hash_1 = __importDefault(require("object-hash"));
33
33
  const path_1 = __importDefault(require("path"));
34
34
  const sharp_1 = __importDefault(require("sharp"));
35
+ const uuid_validate_1 = __importDefault(require("uuid-validate"));
35
36
  const database_1 = __importDefault(require("../database"));
36
37
  const env_1 = __importDefault(require("../env"));
37
38
  const exceptions_1 = require("../exceptions");
39
+ const logger_1 = __importDefault(require("../logger"));
38
40
  const storage_1 = __importDefault(require("../storage"));
39
- const authorization_1 = require("./authorization");
40
41
  const TransformationUtils = __importStar(require("../utils/transformations"));
41
- const uuid_validate_1 = __importDefault(require("uuid-validate"));
42
+ const authorization_1 = require("./authorization");
42
43
  sharp_1.default.concurrency(1);
43
44
  // Note: don't put this in the service. The service can be initialized in multiple places, but they
44
45
  // should all share the same semaphore instance.
@@ -140,6 +141,10 @@ class AssetsService {
140
141
  sequentialRead: true,
141
142
  }).rotate();
142
143
  transforms.forEach(([method, ...args]) => transformer[method].apply(transformer, args));
144
+ readStream.on('error', (e) => {
145
+ logger_1.default.error(e, `Couldn't transform file ${file.id}`);
146
+ readStream.unpipe(transformer);
147
+ });
143
148
  await storage_1.default.disk(file.storage).put(assetFilename, readStream.pipe(transformer), type);
144
149
  return {
145
150
  stream: storage_1.default.disk(file.storage).getStream(assetFilename, range),
@@ -13,6 +13,7 @@ const strip_function_1 = require("../utils/strip-function");
13
13
  const items_1 = require("./items");
14
14
  const payload_1 = require("./payload");
15
15
  const get_relation_info_1 = require("../utils/get-relation-info");
16
+ const constants_1 = require("../constants");
16
17
  class AuthorizationService {
17
18
  constructor(options) {
18
19
  this.knex = options.knex || (0, database_1.default)();
@@ -379,7 +380,7 @@ class AuthorizationService {
379
380
  const requiredColumns = [];
380
381
  for (const field of Object.values(this.schema.collections[collection].fields)) {
381
382
  const specials = (_g = field === null || field === void 0 ? void 0 : field.special) !== null && _g !== void 0 ? _g : [];
382
- const hasGenerateSpecial = ['uuid', 'date-created', 'role-created', 'user-created'].some((name) => specials.includes(name));
383
+ const hasGenerateSpecial = constants_1.GENERATE_SPECIAL.some((name) => specials.includes(name));
383
384
  const nullable = field.nullable || hasGenerateSpecial || field.generated;
384
385
  if (!nullable) {
385
386
  requiredColumns.push(field);
@@ -50,6 +50,7 @@ const geojson_1 = require("./types/geojson");
50
50
  const string_or_float_1 = require("./types/string-or-float");
51
51
  const void_1 = require("./types/void");
52
52
  const add_path_to_validation_error_1 = require("./utils/add-path-to-validation-error");
53
+ const hash_1 = require("./types/hash");
53
54
  const validationRules = Array.from(graphql_1.specifiedRules);
54
55
  if (env_1.default.GRAPHQL_INTROSPECTION === false) {
55
56
  validationRules.push(graphql_1.NoSchemaIntrospectionCustomRule);
@@ -277,7 +278,9 @@ class GraphQLService {
277
278
  // GraphQL doesn't differentiate between not-null and has-to-be-submitted. We
278
279
  // can't non-null in update, as that would require every not-nullable field to be
279
280
  // submitted on updates
280
- if (field.nullable === false && action !== 'update') {
281
+ if (field.nullable === false &&
282
+ !constants_1.GENERATE_SPECIAL.some((flag) => field.special.includes(flag)) &&
283
+ action !== 'update') {
281
284
  type = (0, graphql_1.GraphQLNonNull)(type);
282
285
  }
283
286
  if (collection.primary === field.field) {
@@ -562,6 +565,23 @@ class GraphQLService {
562
565
  },
563
566
  },
564
567
  });
568
+ const HashFilterOperators = schemaComposer.createInputTC({
569
+ name: 'hash_filter_operators',
570
+ fields: {
571
+ _null: {
572
+ type: graphql_1.GraphQLBoolean,
573
+ },
574
+ _nnull: {
575
+ type: graphql_1.GraphQLBoolean,
576
+ },
577
+ _empty: {
578
+ type: graphql_1.GraphQLBoolean,
579
+ },
580
+ _nempty: {
581
+ type: graphql_1.GraphQLBoolean,
582
+ },
583
+ },
584
+ });
565
585
  const CountFunctionFilterOperators = schemaComposer.createInputTC({
566
586
  name: 'count_function_filter_operators',
567
587
  fields: {
@@ -635,6 +655,9 @@ class GraphQLService {
635
655
  case geojson_1.GraphQLGeoJSON:
636
656
  filterOperatorType = GeometryFilterOperators;
637
657
  break;
658
+ case hash_1.GraphQLHash:
659
+ filterOperatorType = HashFilterOperators;
660
+ break;
638
661
  default:
639
662
  filterOperatorType = StringFilterOperators;
640
663
  }
@@ -1023,7 +1046,7 @@ class GraphQLService {
1023
1046
  * Directus' query structure which is then executed by the services.
1024
1047
  */
1025
1048
  async resolveQuery(info) {
1026
- var _a, _b, _c;
1049
+ var _a, _b, _c, _d;
1027
1050
  let collection = info.fieldName;
1028
1051
  if (this.scope === 'system')
1029
1052
  collection = `directus_${collection}`;
@@ -1057,13 +1080,15 @@ class GraphQLService {
1057
1080
  query.limit = 1;
1058
1081
  }
1059
1082
  // Transform count(a.b.c) into a.b.count(c)
1060
- for (let fieldIndex = 0; fieldIndex < query.fields.length; fieldIndex++) {
1061
- if (query.fields[fieldIndex].includes('(') && query.fields[fieldIndex].includes(')')) {
1062
- const functionName = query.fields[fieldIndex].split('(')[0];
1063
- const columnNames = query.fields[fieldIndex].match(constants_2.REGEX_BETWEEN_PARENS)[1].split('.');
1064
- if (columnNames.length > 1) {
1065
- const column = columnNames.pop();
1066
- query.fields[fieldIndex] = columnNames.join('.') + '.' + functionName + '(' + column + ')';
1083
+ if ((_c = query.fields) === null || _c === void 0 ? void 0 : _c.length) {
1084
+ for (let fieldIndex = 0; fieldIndex < query.fields.length; fieldIndex++) {
1085
+ if (query.fields[fieldIndex].includes('(') && query.fields[fieldIndex].includes(')')) {
1086
+ const functionName = query.fields[fieldIndex].split('(')[0];
1087
+ const columnNames = query.fields[fieldIndex].match(constants_2.REGEX_BETWEEN_PARENS)[1].split('.');
1088
+ if (columnNames.length > 1) {
1089
+ const column = columnNames.pop();
1090
+ query.fields[fieldIndex] = columnNames.join('.') + '.' + functionName + '(' + column + ')';
1091
+ }
1067
1092
  }
1068
1093
  }
1069
1094
  }
@@ -1073,7 +1098,7 @@ class GraphQLService {
1073
1098
  }
1074
1099
  if (query.group) {
1075
1100
  // for every entry in result add a group field based on query.group;
1076
- const aggregateKeys = Object.keys((_c = query.aggregate) !== null && _c !== void 0 ? _c : {});
1101
+ const aggregateKeys = Object.keys((_d = query.aggregate) !== null && _d !== void 0 ? _d : {});
1077
1102
  result.map((field) => {
1078
1103
  field.group = (0, lodash_1.omit)(field, aggregateKeys);
1079
1104
  });
@@ -1313,7 +1338,8 @@ class GraphQLService {
1313
1338
  result[currentKey] = Object.values(value)[0];
1314
1339
  }
1315
1340
  else {
1316
- result[currentKey] = (value === null || value === void 0 ? void 0 : value.constructor) === Object ? replaceFuncDeep(value) : value;
1341
+ result[currentKey] =
1342
+ (value === null || value === void 0 ? void 0 : value.constructor) === Object || (value === null || value === void 0 ? void 0 : value.constructor) === Array ? replaceFuncDeep(value) : value;
1317
1343
  }
1318
1344
  });
1319
1345
  }
@@ -0,0 +1,2 @@
1
+ import { GraphQLScalarType } from 'graphql';
2
+ export declare const GraphQLHash: GraphQLScalarType;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GraphQLHash = void 0;
4
+ const graphql_1 = require("graphql");
5
+ exports.GraphQLHash = new graphql_1.GraphQLScalarType({
6
+ ...graphql_1.GraphQLString,
7
+ name: 'Hash',
8
+ description: 'Hashed string values',
9
+ });
@@ -77,12 +77,7 @@ class OASSpecsService {
77
77
  integer: {
78
78
  type: 'integer',
79
79
  },
80
- json: {
81
- type: 'array',
82
- items: {
83
- type: 'string',
84
- },
85
- },
80
+ json: {},
86
81
  string: {
87
82
  type: 'string',
88
83
  },
@@ -13,6 +13,7 @@ const get_column_1 = require("./get-column");
13
13
  const get_column_path_1 = require("./get-column-path");
14
14
  const get_relation_info_1 = require("./get-relation-info");
15
15
  const utils_1 = require("@directus/shared/utils");
16
+ const strip_function_1 = require("./strip-function");
16
17
  const generateAlias = (0, nanoid_1.customAlphabet)('abcdefghijklmnopqrstuvwxyz', 5);
17
18
  /**
18
19
  * Apply the Query to a given Knex query builder instance
@@ -211,9 +212,11 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
211
212
  const { columnPath, targetCollection } = (0, get_column_path_1.getColumnPath)({ path: filterPath, collection, relations, aliasMap });
212
213
  if (!columnPath)
213
214
  continue;
215
+ validateFilterOperator(schema.collections[targetCollection].fields[(0, strip_function_1.stripFunction)(filterPath[filterPath.length - 1])].type, filterOperator);
214
216
  applyFilterToQuery(columnPath, filterOperator, filterValue, logical, targetCollection);
215
217
  }
216
218
  else {
219
+ validateFilterOperator(schema.collections[collection].fields[(0, strip_function_1.stripFunction)(filterPath[0])].type, filterOperator);
217
220
  applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue, logical);
218
221
  }
219
222
  }
@@ -245,6 +248,14 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
245
248
  }
246
249
  }
247
250
  }
251
+ function validateFilterOperator(type, filterOperator) {
252
+ if (filterOperator.startsWith('_')) {
253
+ filterOperator = filterOperator.slice(1);
254
+ }
255
+ if (!(0, utils_1.getFilterOperatorsForType)(type).includes(filterOperator)) {
256
+ throw new invalid_query_1.InvalidQueryException(`"${type}" field type does not contain the "_${filterOperator}" filter operator`);
257
+ }
258
+ }
248
259
  function applyFilterToQuery(key, operator, compareValue, logical = 'and', originalCollectionName) {
249
260
  const [table, column] = key.split('.');
250
261
  // Is processed through Knex.Raw, so should be safe to string-inject into these where queries
@@ -0,0 +1,3 @@
1
+ /// <reference types="node" />
2
+ export declare function compress(raw: Record<string, any> | Record<string, any>[]): Promise<Buffer>;
3
+ export declare function decompress(compressed: Buffer): Promise<any>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decompress = exports.compress = void 0;
4
+ const snappy_1 = require("snappy");
5
+ const utils_1 = require("@directus/shared/utils");
6
+ async function compress(raw) {
7
+ if (!raw)
8
+ return raw;
9
+ return await (0, snappy_1.compress)((0, utils_1.compress)(raw));
10
+ }
11
+ exports.compress = compress;
12
+ async function decompress(compressed) {
13
+ if (!compressed)
14
+ return compressed;
15
+ return (0, utils_1.decompress)((await (0, snappy_1.uncompress)(compressed, { asBuffer: false })));
16
+ }
17
+ exports.decompress = decompress;
@@ -70,7 +70,7 @@ async function getASTFromQuery(collection, query, schema, options) {
70
70
  if (!fields)
71
71
  return [];
72
72
  fields = await convertWildcards(parentCollection, fields);
73
- if (!fields)
73
+ if (!fields || !Array.isArray(fields))
74
74
  return [];
75
75
  const children = [];
76
76
  const relationalStructure = {};
@@ -5,6 +5,7 @@ const graphql_1 = require("graphql");
5
5
  const graphql_compose_1 = require("graphql-compose");
6
6
  const date_1 = require("../services/graphql/types/date");
7
7
  const geojson_1 = require("../services/graphql/types/geojson");
8
+ const hash_1 = require("../services/graphql/types/hash");
8
9
  function getGraphQLType(localType) {
9
10
  switch (localType) {
10
11
  case 'boolean':
@@ -26,6 +27,8 @@ function getGraphQLType(localType) {
26
27
  case 'dateTime':
27
28
  case 'date':
28
29
  return date_1.GraphQLDate;
30
+ case 'hash':
31
+ return hash_1.GraphQLHash;
29
32
  default:
30
33
  return graphql_1.GraphQLString;
31
34
  }
@@ -17,12 +17,12 @@ const merge_permissions_1 = require("../utils/merge-permissions");
17
17
  const merge_permissions_for_share_1 = require("./merge-permissions-for-share");
18
18
  async function getPermissions(accountability, schema) {
19
19
  const database = (0, database_1.default)();
20
- const { systemCache, cache } = (0, cache_1.getCache)();
20
+ const { cache } = (0, cache_1.getCache)();
21
21
  let permissions = [];
22
22
  const { user, role, app, admin, share_scope } = accountability;
23
23
  const cacheKey = `permissions-${(0, object_hash_1.default)({ user, role, app, admin, share_scope })}`;
24
24
  if (env_1.default.CACHE_PERMISSIONS !== false) {
25
- const cachedPermissions = await systemCache.get(cacheKey);
25
+ const cachedPermissions = await (0, cache_1.getSystemCache)(cacheKey);
26
26
  if (cachedPermissions) {
27
27
  if (!cachedPermissions.containDynamicData) {
28
28
  return processPermissions(accountability, cachedPermissions.permissions, {});
@@ -20,12 +20,11 @@ const get_local_type_1 = __importDefault(require("./get-local-type"));
20
20
  async function getSchema(options) {
21
21
  const database = (options === null || options === void 0 ? void 0 : options.database) || (0, database_1.default)();
22
22
  const schemaInspector = (0, schema_1.default)(database);
23
- const { systemCache } = (0, cache_1.getCache)();
24
23
  let result;
25
24
  if (env_1.default.CACHE_SCHEMA !== false) {
26
25
  let cachedSchema;
27
26
  try {
28
- cachedSchema = (await systemCache.get('schema'));
27
+ cachedSchema = (await (0, cache_1.getSystemCache)('schema'));
29
28
  }
30
29
  catch (err) {
31
30
  logger_1.default.warn(err, `[schema-cache] Couldn't retrieve cache. ${err}`);