@directus/api 14.1.2 → 16.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/dist/app.js +8 -6
  2. package/dist/auth/drivers/ldap.js +7 -4
  3. package/dist/auth/drivers/local.js +3 -2
  4. package/dist/auth/drivers/oauth2.js +11 -5
  5. package/dist/auth/drivers/openid.js +11 -5
  6. package/dist/auth/drivers/saml.js +6 -4
  7. package/dist/auth.js +7 -4
  8. package/dist/bus/index.d.ts +1 -0
  9. package/dist/bus/index.js +1 -0
  10. package/dist/bus/lib/use-bus.d.ts +9 -0
  11. package/dist/bus/lib/use-bus.js +21 -0
  12. package/dist/cache.js +9 -9
  13. package/dist/cli/commands/bootstrap/index.js +6 -2
  14. package/dist/cli/commands/count/index.js +2 -1
  15. package/dist/cli/commands/database/install.js +2 -1
  16. package/dist/cli/commands/database/migrate.js +2 -1
  17. package/dist/cli/commands/roles/create.js +2 -1
  18. package/dist/cli/commands/schema/apply.js +46 -34
  19. package/dist/cli/commands/schema/snapshot.js +6 -5
  20. package/dist/cli/commands/users/create.js +4 -3
  21. package/dist/cli/commands/users/passwd.js +5 -4
  22. package/dist/cli/index.js +2 -2
  23. package/dist/cli/load-extensions.js +4 -2
  24. package/dist/cli/utils/create-env/env-stub.liquid +1 -1
  25. package/dist/constants.d.ts +1 -1
  26. package/dist/constants.js +4 -1
  27. package/dist/controllers/assets.js +5 -3
  28. package/dist/controllers/auth.js +5 -4
  29. package/dist/controllers/extensions.js +18 -6
  30. package/dist/controllers/files.js +3 -3
  31. package/dist/controllers/schema.js +3 -2
  32. package/dist/controllers/shares.js +3 -3
  33. package/dist/database/helpers/index.d.ts +1 -1
  34. package/dist/database/index.d.ts +2 -1
  35. package/dist/database/index.js +11 -3
  36. package/dist/database/migrations/20210518A-add-foreign-key-constraints.js +3 -1
  37. package/dist/database/migrations/20210519A-add-system-fk-triggers.js +3 -1
  38. package/dist/database/migrations/20210802A-replace-groups.js +2 -1
  39. package/dist/database/migrations/20230721A-require-shares-fields.js +2 -1
  40. package/dist/database/migrations/20231215A-add-focalpoints.d.ts +3 -0
  41. package/dist/database/migrations/20231215A-add-focalpoints.js +12 -0
  42. package/dist/database/migrations/run.js +2 -1
  43. package/dist/database/run-ast.js +5 -2
  44. package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -7
  45. package/dist/database/system-data/fields/files.yaml +16 -0
  46. package/dist/database/system-data/relations/relations.yaml +4 -0
  47. package/dist/emitter.d.ts +1 -0
  48. package/dist/emitter.js +4 -1
  49. package/dist/extensions/lib/get-extensions-path.d.ts +1 -1
  50. package/dist/extensions/lib/get-extensions-path.js +2 -1
  51. package/dist/extensions/lib/get-extensions.d.ts +1 -1
  52. package/dist/extensions/lib/get-extensions.js +32 -8
  53. package/dist/extensions/lib/get-shared-deps-mapping.js +7 -5
  54. package/dist/extensions/lib/sandbox/register/call-reference.js +4 -2
  55. package/dist/extensions/lib/sandbox/register/route.d.ts +1 -0
  56. package/dist/extensions/lib/sandbox/sdk/generators/log.js +2 -1
  57. package/dist/extensions/lib/sync-extensions.js +6 -4
  58. package/dist/extensions/manager.d.ts +5 -0
  59. package/dist/extensions/manager.js +84 -34
  60. package/dist/flows.js +13 -7
  61. package/dist/logger.d.ts +7 -6
  62. package/dist/logger.js +116 -91
  63. package/dist/mailer.js +4 -2
  64. package/dist/middleware/cache.js +4 -2
  65. package/dist/middleware/check-ip.js +25 -6
  66. package/dist/middleware/cors.js +2 -1
  67. package/dist/middleware/error-handler.js +5 -5
  68. package/dist/middleware/rate-limiter-global.js +4 -2
  69. package/dist/middleware/rate-limiter-ip.js +16 -12
  70. package/dist/middleware/respond.js +4 -2
  71. package/dist/operations/log/index.js +2 -1
  72. package/dist/rate-limiter.d.ts +2 -1
  73. package/dist/rate-limiter.js +5 -2
  74. package/dist/redis/index.d.ts +3 -0
  75. package/dist/redis/index.js +3 -0
  76. package/dist/redis/lib/create-redis.d.ts +7 -0
  77. package/dist/redis/lib/create-redis.js +12 -0
  78. package/dist/redis/lib/use-redis.d.ts +16 -0
  79. package/dist/redis/lib/use-redis.js +22 -0
  80. package/dist/redis/utils/redis-config-available.d.ts +4 -0
  81. package/dist/redis/utils/redis-config-available.js +8 -0
  82. package/dist/request/request-interceptor.js +7 -5
  83. package/dist/request/response-interceptor.js +2 -2
  84. package/dist/request/validate-ip.d.ts +1 -1
  85. package/dist/request/validate-ip.js +23 -7
  86. package/dist/server.d.ts +2 -0
  87. package/dist/server.js +11 -7
  88. package/dist/services/activity.js +5 -4
  89. package/dist/services/assets.d.ts +2 -0
  90. package/dist/services/assets.js +9 -4
  91. package/dist/services/authentication.js +17 -9
  92. package/dist/services/collections.js +5 -4
  93. package/dist/services/extensions.d.ts +15 -9
  94. package/dist/services/extensions.js +75 -40
  95. package/dist/services/fields.js +9 -4
  96. package/dist/services/files.d.ts +2 -2
  97. package/dist/services/files.js +22 -14
  98. package/dist/services/graphql/index.js +96 -18
  99. package/dist/services/graphql/subscription.js +2 -2
  100. package/dist/services/graphql/types/bigint.js +16 -5
  101. package/dist/services/graphql/utils/process-error.d.ts +4 -1
  102. package/dist/services/graphql/utils/process-error.js +10 -8
  103. package/dist/services/import-export/index.js +5 -3
  104. package/dist/services/items.js +12 -8
  105. package/dist/services/mail/index.js +4 -2
  106. package/dist/services/notifications.js +7 -3
  107. package/dist/services/payload.js +3 -3
  108. package/dist/services/relations.js +19 -10
  109. package/dist/services/server.js +7 -7
  110. package/dist/services/shares.js +3 -2
  111. package/dist/services/specifications.js +5 -4
  112. package/dist/services/users.js +24 -13
  113. package/dist/services/versions.js +6 -5
  114. package/dist/services/webhooks.d.ts +2 -2
  115. package/dist/services/webhooks.js +2 -2
  116. package/dist/services/websocket.d.ts +1 -1
  117. package/dist/services/websocket.js +4 -3
  118. package/dist/storage/register-drivers.js +2 -1
  119. package/dist/storage/register-locations.js +2 -1
  120. package/dist/synchronization.js +3 -1
  121. package/dist/telemetry/index.d.ts +4 -0
  122. package/dist/telemetry/index.js +4 -0
  123. package/dist/telemetry/lib/get-report.d.ts +5 -0
  124. package/dist/telemetry/lib/get-report.js +42 -0
  125. package/dist/telemetry/lib/init-telemetry.d.ts +11 -0
  126. package/dist/telemetry/lib/init-telemetry.js +30 -0
  127. package/dist/telemetry/lib/send-report.d.ts +5 -0
  128. package/dist/telemetry/lib/send-report.js +23 -0
  129. package/dist/telemetry/lib/track.d.ts +10 -0
  130. package/dist/telemetry/lib/track.js +30 -0
  131. package/dist/telemetry/types/report.d.ts +58 -0
  132. package/dist/telemetry/types/report.js +1 -0
  133. package/dist/telemetry/utils/get-item-count.d.ts +26 -0
  134. package/dist/telemetry/utils/get-item-count.js +36 -0
  135. package/dist/telemetry/utils/get-random-wait-time.d.ts +5 -0
  136. package/dist/telemetry/utils/get-random-wait-time.js +5 -0
  137. package/dist/telemetry/utils/get-user-count.d.ts +7 -0
  138. package/dist/telemetry/utils/get-user-count.js +30 -0
  139. package/dist/telemetry/utils/get-user-item-count.d.ts +13 -0
  140. package/dist/telemetry/utils/get-user-item-count.js +18 -0
  141. package/dist/types/assets.d.ts +2 -0
  142. package/dist/utils/apply-diff.js +2 -1
  143. package/dist/utils/apply-query.js +2 -2
  144. package/dist/utils/delete-from-require-cache.js +2 -1
  145. package/dist/utils/get-accountability-for-token.js +3 -2
  146. package/dist/utils/get-auth-providers.js +2 -1
  147. package/dist/utils/get-cache-headers.js +5 -2
  148. package/dist/utils/get-cache-key.js +1 -1
  149. package/dist/utils/get-config-from-env.js +2 -1
  150. package/dist/utils/get-default-value.js +4 -3
  151. package/dist/utils/get-ip-from-req.d.ts +1 -1
  152. package/dist/utils/get-ip-from-req.js +5 -3
  153. package/dist/utils/get-permissions.js +5 -3
  154. package/dist/utils/get-schema.js +5 -2
  155. package/dist/utils/get-snapshot-diff.js +7 -9
  156. package/dist/utils/get-snapshot.js +5 -5
  157. package/dist/utils/get-versioned-hash.js +1 -1
  158. package/dist/utils/ip-in-networks.d.ts +6 -0
  159. package/dist/utils/ip-in-networks.js +13 -0
  160. package/dist/utils/is-url-allowed.js +2 -1
  161. package/dist/utils/job-queue.d.ts +1 -0
  162. package/dist/utils/job-queue.js +3 -0
  163. package/dist/utils/md.d.ts +1 -1
  164. package/dist/utils/md.js +3 -2
  165. package/dist/utils/sanitize-query.js +7 -2
  166. package/dist/utils/sanitize-schema.d.ts +1 -1
  167. package/dist/utils/should-clear-cache.js +2 -1
  168. package/dist/utils/should-skip-cache.js +2 -1
  169. package/dist/utils/transformations.js +95 -12
  170. package/dist/utils/validate-env.js +4 -2
  171. package/dist/utils/validate-query.js +8 -3
  172. package/dist/utils/validate-snapshot.js +3 -3
  173. package/dist/utils/validate-storage.js +4 -2
  174. package/dist/webhooks.js +4 -3
  175. package/dist/websocket/controllers/base.d.ts +2 -0
  176. package/dist/websocket/controllers/base.js +12 -6
  177. package/dist/websocket/controllers/graphql.d.ts +2 -0
  178. package/dist/websocket/controllers/graphql.js +5 -3
  179. package/dist/websocket/controllers/hooks.js +3 -2
  180. package/dist/websocket/controllers/index.d.ts +2 -0
  181. package/dist/websocket/controllers/index.js +4 -2
  182. package/dist/websocket/controllers/rest.d.ts +2 -0
  183. package/dist/websocket/controllers/rest.js +4 -2
  184. package/dist/websocket/errors.js +2 -1
  185. package/dist/websocket/handlers/heartbeat.js +4 -3
  186. package/dist/websocket/handlers/subscribe.d.ts +2 -2
  187. package/dist/websocket/handlers/subscribe.js +5 -4
  188. package/dist/websocket/types.d.ts +3 -1
  189. package/package.json +114 -115
  190. package/dist/__utils__/items-utils.d.ts +0 -2
  191. package/dist/__utils__/items-utils.js +0 -31
  192. package/dist/__utils__/mock-env.d.ts +0 -18
  193. package/dist/__utils__/mock-env.js +0 -41
  194. package/dist/__utils__/schemas.d.ts +0 -13
  195. package/dist/__utils__/schemas.js +0 -301
  196. package/dist/__utils__/snapshots.d.ts +0 -5
  197. package/dist/__utils__/snapshots.js +0 -903
  198. package/dist/env.d.ts +0 -13
  199. package/dist/env.js +0 -505
  200. package/dist/messenger.d.ts +0 -24
  201. package/dist/messenger.js +0 -64
  202. package/dist/utils/package.d.ts +0 -2
  203. package/dist/utils/package.js +0 -6
  204. package/dist/utils/telemetry.d.ts +0 -1
  205. package/dist/utils/telemetry.js +0 -23
  206. package/dist/utils/to-boolean.d.ts +0 -4
  207. package/dist/utils/to-boolean.js +0 -6
@@ -1,6 +1,7 @@
1
1
  import { Action, FUNCTIONS } from '@directus/constants';
2
+ import { useEnv } from '@directus/env';
2
3
  import { ErrorCode, ForbiddenError, InvalidPayloadError, isDirectusError } from '@directus/errors';
3
- import { parseFilterFunctionPath } from '@directus/utils';
4
+ import { parseFilterFunctionPath, toBoolean } from '@directus/utils';
4
5
  import argon2 from 'argon2';
5
6
  import { GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLID, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType, NoSchemaIntrospectionCustomRule, execute, specifiedRules, validate, } from 'graphql';
6
7
  import { GraphQLJSON, InputTypeComposer, ObjectTypeComposer, SchemaComposer, toInputObjectType } from 'graphql-compose';
@@ -8,14 +9,12 @@ import { assign, flatten, get, mapKeys, merge, omit, pick, set, transform, uniq
8
9
  import { clearSystemCache, getCache } from '../../cache.js';
9
10
  import { DEFAULT_AUTH_PROVIDER, GENERATE_SPECIAL } from '../../constants.js';
10
11
  import getDatabase from '../../database/index.js';
11
- import env from '../../env.js';
12
12
  import { generateHash } from '../../utils/generate-hash.js';
13
13
  import { getGraphQLType } from '../../utils/get-graphql-type.js';
14
14
  import { getMilliseconds } from '../../utils/get-milliseconds.js';
15
15
  import { getService } from '../../utils/get-service.js';
16
16
  import { reduceSchema } from '../../utils/reduce-schema.js';
17
17
  import { sanitizeQuery } from '../../utils/sanitize-query.js';
18
- import { toBoolean } from '../../utils/to-boolean.js';
19
18
  import { validateQuery } from '../../utils/validate-query.js';
20
19
  import { ActivityService } from '../activity.js';
21
20
  import { AuthenticationService } from '../authentication.js';
@@ -41,6 +40,7 @@ import { GraphQLStringOrFloat } from './types/string-or-float.js';
41
40
  import { GraphQLVoid } from './types/void.js';
42
41
  import { addPathToValidationError } from './utils/add-path-to-validation-error.js';
43
42
  import processError from './utils/process-error.js';
43
+ const env = useEnv();
44
44
  const validationRules = Array.from(specifiedRules);
45
45
  if (env['GRAPHQL_INTROSPECTION'] === false) {
46
46
  validationRules.push(NoSchemaIntrospectionCustomRule);
@@ -126,7 +126,7 @@ export class GraphQLService {
126
126
  delete: { value: 'delete' },
127
127
  },
128
128
  });
129
- const { ReadCollectionTypes } = getReadableTypes();
129
+ const { ReadCollectionTypes, VersionCollectionTypes } = getReadableTypes();
130
130
  const { CreateCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes } = getWritableTypes();
131
131
  const scopeFilter = (collection) => {
132
132
  if (this.scope === 'items' && collection.collection.startsWith('directus_') === true)
@@ -158,6 +158,9 @@ export class GraphQLService {
158
158
  acc[`${collectionName}_by_id`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_by_id`);
159
159
  acc[`${collectionName}_aggregated`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_aggregated`);
160
160
  }
161
+ if (this.scope === 'items') {
162
+ acc[`${collectionName}_by_version`] = VersionCollectionTypes[collection.collection].getResolver(`${collection.collection}_by_version`);
163
+ }
161
164
  return acc;
162
165
  }, {}));
163
166
  }
@@ -221,6 +224,7 @@ export class GraphQLService {
221
224
  */
222
225
  function getTypes(action) {
223
226
  const CollectionTypes = {};
227
+ const VersionTypes = {};
224
228
  const CountFunctions = schemaComposer.createObjectTC({
225
229
  name: 'count_functions',
226
230
  fields: {
@@ -289,13 +293,19 @@ export class GraphQLService {
289
293
  type = new GraphQLNonNull(type);
290
294
  }
291
295
  if (collection.primary === field.field) {
292
- if (!field.defaultValue && !field.special.includes('uuid') && action === 'create') {
296
+ // permissions IDs need to be nullable https://github.com/directus/directus/issues/20509
297
+ if (collection.collection === 'directus_permissions') {
298
+ type = GraphQLID;
299
+ }
300
+ else if (!field.defaultValue && !field.special.includes('uuid') && action === 'create') {
293
301
  type = new GraphQLNonNull(GraphQLID);
294
302
  }
295
- else if (['create', 'update'].includes(action))
303
+ else if (['create', 'update'].includes(action)) {
296
304
  type = GraphQLID;
297
- else
305
+ }
306
+ else {
298
307
  type = new GraphQLNonNull(GraphQLID);
308
+ }
299
309
  }
300
310
  acc[field.field] = {
301
311
  type,
@@ -345,6 +355,9 @@ export class GraphQLService {
345
355
  return acc;
346
356
  }, {}),
347
357
  });
358
+ if (self.scope === 'items') {
359
+ VersionTypes[collection.collection] = CollectionTypes[collection.collection].clone(`version_${collection.collection}`);
360
+ }
348
361
  }
349
362
  for (const relation of schema[action].relations) {
350
363
  if (relation.related_collection) {
@@ -367,6 +380,16 @@ export class GraphQLService {
367
380
  },
368
381
  },
369
382
  });
383
+ if (self.scope === 'items') {
384
+ VersionTypes[relation.related_collection]?.addFields({
385
+ [relation.meta.one_field]: {
386
+ type: GraphQLJSON,
387
+ resolve: (obj, _, __, info) => {
388
+ return obj[info?.path?.key ?? relation.meta.one_field];
389
+ },
390
+ },
391
+ });
392
+ }
370
393
  }
371
394
  }
372
395
  else if (relation.meta?.one_allowed_collections && action === 'read') {
@@ -399,13 +422,13 @@ export class GraphQLService {
399
422
  });
400
423
  }
401
424
  }
402
- return { CollectionTypes };
425
+ return { CollectionTypes, VersionTypes };
403
426
  }
404
427
  /**
405
428
  * Create readable types and attach resolvers for each. Also prepares full filter argument structures
406
429
  */
407
430
  function getReadableTypes() {
408
- const { CollectionTypes: ReadCollectionTypes } = getTypes('read');
431
+ const { CollectionTypes: ReadCollectionTypes, VersionTypes: VersionCollectionTypes } = getTypes('read');
409
432
  const ReadableCollectionFilterTypes = {};
410
433
  const AggregatedFunctions = {};
411
434
  const AggregatedFields = {};
@@ -572,6 +595,47 @@ export class GraphQLService {
572
595
  },
573
596
  },
574
597
  });
598
+ const BigIntFilterOperators = schemaComposer.createInputTC({
599
+ name: 'big_int_filter_operators',
600
+ fields: {
601
+ _eq: {
602
+ type: GraphQLBigInt,
603
+ },
604
+ _neq: {
605
+ type: GraphQLBigInt,
606
+ },
607
+ _in: {
608
+ type: new GraphQLList(GraphQLBigInt),
609
+ },
610
+ _nin: {
611
+ type: new GraphQLList(GraphQLBigInt),
612
+ },
613
+ _gt: {
614
+ type: GraphQLBigInt,
615
+ },
616
+ _gte: {
617
+ type: GraphQLBigInt,
618
+ },
619
+ _lt: {
620
+ type: GraphQLBigInt,
621
+ },
622
+ _lte: {
623
+ type: GraphQLBigInt,
624
+ },
625
+ _null: {
626
+ type: GraphQLBoolean,
627
+ },
628
+ _nnull: {
629
+ type: GraphQLBoolean,
630
+ },
631
+ _between: {
632
+ type: new GraphQLList(GraphQLBigInt),
633
+ },
634
+ _nbetween: {
635
+ type: new GraphQLList(GraphQLBigInt),
636
+ },
637
+ },
638
+ });
575
639
  const GeometryFilterOperators = schemaComposer.createInputTC({
576
640
  name: 'geometry_filter_operators',
577
641
  fields: {
@@ -682,6 +746,8 @@ export class GraphQLService {
682
746
  filterOperatorType = BooleanFilterOperators;
683
747
  break;
684
748
  case GraphQLBigInt:
749
+ filterOperatorType = BigIntFilterOperators;
750
+ break;
685
751
  case GraphQLInt:
686
752
  case GraphQLFloat:
687
753
  filterOperatorType = NumberFilterOperators;
@@ -843,13 +909,6 @@ export class GraphQLService {
843
909
  },
844
910
  };
845
911
  }
846
- else {
847
- resolver.args = {
848
- version: {
849
- type: GraphQLString,
850
- },
851
- };
852
- }
853
912
  ReadCollectionTypes[collection.collection].addResolver(resolver);
854
913
  ReadCollectionTypes[collection.collection].addResolver({
855
914
  name: `${collection.collection}_aggregated`,
@@ -885,7 +944,6 @@ export class GraphQLService {
885
944
  type: ReadCollectionTypes[collection.collection],
886
945
  args: {
887
946
  id: new GraphQLNonNull(GraphQLID),
888
- version: GraphQLString,
889
947
  },
890
948
  resolve: async ({ info, context }) => {
891
949
  const result = await self.resolveQuery(info);
@@ -894,6 +952,23 @@ export class GraphQLService {
894
952
  },
895
953
  });
896
954
  }
955
+ if (self.scope === 'items') {
956
+ VersionCollectionTypes[collection.collection].addResolver({
957
+ name: `${collection.collection}_by_version`,
958
+ type: VersionCollectionTypes[collection.collection],
959
+ args: collection.singleton
960
+ ? { version: new GraphQLNonNull(GraphQLString) }
961
+ : {
962
+ version: new GraphQLNonNull(GraphQLString),
963
+ id: new GraphQLNonNull(GraphQLID),
964
+ },
965
+ resolve: async ({ info, context }) => {
966
+ const result = await self.resolveQuery(info);
967
+ context['data'] = result;
968
+ return result;
969
+ },
970
+ });
971
+ }
897
972
  const eventName = `${collection.collection}_mutated`;
898
973
  if (collection.collection in ReadCollectionTypes) {
899
974
  const subscriptionType = schemaComposer.createObjectTC({
@@ -973,7 +1048,7 @@ export class GraphQLService {
973
1048
  }
974
1049
  }
975
1050
  }
976
- return { ReadCollectionTypes, ReadableCollectionFilterTypes };
1051
+ return { ReadCollectionTypes, VersionCollectionTypes, ReadableCollectionFilterTypes };
977
1052
  }
978
1053
  function getWritableTypes() {
979
1054
  const { CollectionTypes: CreateCollectionTypes } = getTypes('create');
@@ -1135,6 +1210,9 @@ export class GraphQLService {
1135
1210
  if (collection.endsWith('_by_id') && collection in this.schema.collections === false) {
1136
1211
  collection = collection.slice(0, -6);
1137
1212
  }
1213
+ if (collection.endsWith('_by_version') && collection in this.schema.collections === false) {
1214
+ collection = collection.slice(0, -11);
1215
+ }
1138
1216
  }
1139
1217
  if (args['id']) {
1140
1218
  query.filter = {
@@ -1,11 +1,11 @@
1
1
  import { EventEmitter, on } from 'events';
2
- import { getMessenger } from '../../messenger.js';
2
+ import { useBus } from '../../bus/index.js';
3
3
  import { getSchema } from '../../utils/get-schema.js';
4
4
  import { refreshAccountability } from '../../websocket/authenticate.js';
5
5
  import { getPayload } from '../../websocket/utils/items.js';
6
6
  const messages = createPubSub(new EventEmitter());
7
7
  export function bindPubSub() {
8
- const messenger = getMessenger();
8
+ const messenger = useBus();
9
9
  messenger.subscribe('websocket.event', (message) => {
10
10
  messages.publish(`${message['collection']}_mutated`, message);
11
11
  });
@@ -1,4 +1,7 @@
1
1
  import { GraphQLScalarType, Kind } from 'graphql';
2
+ // minimum and maximum int64 values database vendors use for big integer
3
+ const MIN_BIG_INT = -9223372036854775808n;
4
+ const MAX_BIG_INT = 9223372036854775807n;
2
5
  export const GraphQLBigInt = new GraphQLScalarType({
3
6
  name: 'GraphQLBigInt',
4
7
  description: 'BigInt value',
@@ -26,11 +29,19 @@ export const GraphQLBigInt = new GraphQLScalarType({
26
29
  },
27
30
  });
28
31
  function parseNumberValue(input) {
29
- if (!/[+-]?([0-9]+[.])?[0-9]+/.test(input))
30
- return input;
31
- const value = parseInt(input);
32
- if (isNaN(value) || value < Number.MIN_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER) {
32
+ // Attempt to parse the input as a regular integer
33
+ const intValue = Number(input);
34
+ if (isNaN(intValue)) {
33
35
  throw new Error('Invalid GraphQLBigInt');
34
36
  }
35
- return value;
37
+ if (!Number.isSafeInteger(intValue)) {
38
+ // If the input is not a safe integer, its a big int, so return it as string,
39
+ // because currently string is the best way to handle big int due to knex limitations and JSON.stringify not able to serialise bigInt
40
+ const bigIntInput = BigInt(input);
41
+ if (bigIntInput < MIN_BIG_INT || bigIntInput > MAX_BIG_INT) {
42
+ throw new Error('Invalid GraphQLBigInt');
43
+ }
44
+ return input;
45
+ }
46
+ return intValue;
36
47
  }
@@ -1,4 +1,7 @@
1
+ import { type DirectusError } from '@directus/errors';
1
2
  import type { Accountability } from '@directus/types';
2
3
  import type { GraphQLError, GraphQLFormattedError } from 'graphql';
3
- declare const processError: (accountability: Accountability | null, error: Readonly<GraphQLError>) => GraphQLFormattedError;
4
+ declare const processError: (accountability: Accountability | null, error: Readonly<GraphQLError & {
5
+ originalError: GraphQLError | DirectusError | Error | undefined;
6
+ }>) => GraphQLFormattedError;
4
7
  export default processError;
@@ -1,8 +1,12 @@
1
1
  import { isDirectusError } from '@directus/errors';
2
- import logger from '../../../logger.js';
2
+ import { useLogger } from '../../../logger.js';
3
3
  const processError = (accountability, error) => {
4
+ const logger = useLogger();
4
5
  logger.error(error);
5
- const { originalError } = error;
6
+ let originalError = error.originalError;
7
+ if (originalError && 'originalError' in originalError) {
8
+ originalError = originalError.originalError;
9
+ }
6
10
  if (isDirectusError(originalError)) {
7
11
  return {
8
12
  message: originalError.message,
@@ -10,6 +14,8 @@ const processError = (accountability, error) => {
10
14
  code: originalError.code,
11
15
  ...(originalError.extensions ?? {}),
12
16
  },
17
+ ...(error.locations && { locations: error.locations }),
18
+ ...(error.path && { path: error.path }),
13
19
  };
14
20
  }
15
21
  else {
@@ -19,13 +25,9 @@ const processError = (accountability, error) => {
19
25
  extensions: {
20
26
  code: 'INTERNAL_SERVER_ERROR',
21
27
  },
28
+ ...(error.locations && { locations: error.locations }),
29
+ ...(error.path && { path: error.path }),
22
30
  };
23
- if (error.locations) {
24
- graphqlFormattedError.locations = error.locations;
25
- }
26
- if (error.path) {
27
- graphqlFormattedError.path = error.path;
28
- }
29
31
  return graphqlFormattedError;
30
32
  }
31
33
  else {
@@ -1,3 +1,5 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { ForbiddenError, InvalidPayloadError, ServiceUnavailableError, UnsupportedMediaTypeError, } from '@directus/errors';
1
3
  import { parseJSON, toArray } from '@directus/utils';
2
4
  import { queue } from 'async';
3
5
  import destroyStream from 'destroy';
@@ -10,9 +12,7 @@ import Papa from 'papaparse';
10
12
  import StreamArray from 'stream-json/streamers/StreamArray.js';
11
13
  import getDatabase from '../../database/index.js';
12
14
  import emitter from '../../emitter.js';
13
- import env from '../../env.js';
14
- import { ForbiddenError, InvalidPayloadError, ServiceUnavailableError, UnsupportedMediaTypeError, } from '@directus/errors';
15
- import logger from '../../logger.js';
15
+ import { useLogger } from '../../logger.js';
16
16
  import { getDateFormatted } from '../../utils/get-date-formatted.js';
17
17
  import { Url } from '../../utils/url.js';
18
18
  import { userName } from '../../utils/user-name.js';
@@ -20,6 +20,8 @@ import { FilesService } from '../files.js';
20
20
  import { ItemsService } from '../items.js';
21
21
  import { NotificationsService } from '../notifications.js';
22
22
  import { UsersService } from '../users.js';
23
+ const env = useEnv();
24
+ const logger = useLogger();
23
25
  export class ImportService {
24
26
  knex;
25
27
  accountability;
@@ -1,19 +1,19 @@
1
1
  import { Action } from '@directus/constants';
2
+ import { useEnv } from '@directus/env';
3
+ import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
2
4
  import { assign, clone, cloneDeep, omit, pick, without } from 'lodash-es';
3
5
  import { getCache } from '../cache.js';
6
+ import { translateDatabaseError } from '../database/errors/translate.js';
4
7
  import { getHelpers } from '../database/helpers/index.js';
5
8
  import getDatabase from '../database/index.js';
6
9
  import runAST from '../database/run-ast.js';
7
10
  import emitter from '../emitter.js';
8
- import env from '../env.js';
9
- import { ForbiddenError } from '@directus/errors';
10
- import { translateDatabaseError } from '../database/errors/translate.js';
11
- import { InvalidPayloadError } from '@directus/errors';
12
11
  import getASTFromQuery from '../utils/get-ast-from-query.js';
13
12
  import { shouldClearCache } from '../utils/should-clear-cache.js';
14
13
  import { validateKeys } from '../utils/validate-keys.js';
15
14
  import { AuthorizationService } from './authorization.js';
16
15
  import { PayloadService } from './payload.js';
16
+ const env = useEnv();
17
17
  export class ItemsService {
18
18
  collection;
19
19
  knex;
@@ -118,15 +118,19 @@ export class ItemsService {
118
118
  const payloadWithTypeCasting = await payloadService.processValues('create', payloadWithoutAliases);
119
119
  // The primary key can already exist in the payload.
120
120
  // In case of manual string / UUID primary keys it's always provided at this point.
121
- // In case of an integer primary key, it might be provided as the user can specify the value manually.
121
+ // In case of an (big) integer primary key, it might be provided as the user can specify the value manually.
122
122
  let primaryKey = payloadWithTypeCasting[primaryKeyField];
123
+ if (primaryKey) {
124
+ validateKeys(this.schema, this.collection, primaryKeyField, primaryKey);
125
+ }
123
126
  // If a PK of type number was provided, although the PK is set the auto_increment,
124
127
  // depending on the database, the sequence might need to be reset to protect future PK collisions.
125
128
  let autoIncrementSequenceNeedsToBeReset = false;
126
129
  const pkField = this.schema.collections[this.collection].fields[primaryKeyField];
127
130
  if (primaryKey &&
131
+ pkField &&
128
132
  !opts.bypassAutoIncrementSequenceReset &&
129
- pkField.type === 'integer' &&
133
+ ['integer', 'bigInteger'].includes(pkField.type) &&
130
134
  pkField.defaultValue === 'AUTO_INCREMENT') {
131
135
  autoIncrementSequenceNeedsToBeReset = true;
132
136
  }
@@ -505,7 +509,7 @@ export class ItemsService {
505
509
  nestedActionEvents.push(...nestedActionEventsM2O);
506
510
  nestedActionEvents.push(...nestedActionEventsA2O);
507
511
  for (const key of keys) {
508
- const { revisions, nestedActionEvents: nestedActionEventsO2M } = await payloadService.processO2M(payload, key, opts);
512
+ const { revisions, nestedActionEvents: nestedActionEventsO2M } = await payloadService.processO2M(payloadWithA2O, key, opts);
509
513
  childrenRevisions.push(...revisions);
510
514
  nestedActionEvents.push(...nestedActionEventsO2M);
511
515
  }
@@ -569,7 +573,7 @@ export class ItemsService {
569
573
  ? ['items.update', `${this.collection}.items.update`]
570
574
  : `${this.eventScope}.update`,
571
575
  meta: {
572
- payload,
576
+ payload: payloadWithPresets,
573
577
  keys,
574
578
  collection: this.collection,
575
579
  },
@@ -1,14 +1,16 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { InvalidPayloadError } from '@directus/errors';
2
3
  import fse from 'fs-extra';
3
4
  import { Liquid } from 'liquidjs';
4
5
  import path from 'path';
5
6
  import { fileURLToPath } from 'url';
6
7
  import getDatabase from '../../database/index.js';
7
- import env from '../../env.js';
8
8
  import { getExtensionsPath } from '../../extensions/lib/get-extensions-path.js';
9
- import logger from '../../logger.js';
9
+ import { useLogger } from '../../logger.js';
10
10
  import getMailer from '../../mailer.js';
11
11
  import { Url } from '../../utils/url.js';
12
+ const env = useEnv();
13
+ const logger = useLogger();
12
14
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
15
  const liquidEngine = new Liquid({
14
16
  root: [path.resolve(getExtensionsPath(), 'templates'), path.resolve(__dirname, 'templates')],
@@ -1,10 +1,12 @@
1
- import env from '../env.js';
2
- import logger from '../logger.js';
1
+ import { useEnv } from '@directus/env';
2
+ import { useLogger } from '../logger.js';
3
3
  import { md } from '../utils/md.js';
4
4
  import { Url } from '../utils/url.js';
5
5
  import { ItemsService } from './items.js';
6
6
  import { MailService } from './mail/index.js';
7
7
  import { UsersService } from './users.js';
8
+ const env = useEnv();
9
+ const logger = useLogger();
8
10
  export class NotificationsService extends ItemsService {
9
11
  usersService;
10
12
  mailService;
@@ -30,7 +32,9 @@ export class NotificationsService extends ItemsService {
30
32
  const user = await this.usersService.readOne(data.recipient, {
31
33
  fields: ['id', 'email', 'email_notifications', 'role.app_access'],
32
34
  });
33
- const manageUserAccountUrl = new Url(env['PUBLIC_URL']).addPath('admin', 'users', user['id']).toString();
35
+ const manageUserAccountUrl = new Url(env['PUBLIC_URL'])
36
+ .addPath('admin', 'users', user['id'])
37
+ .toString();
34
38
  const html = data.message ? md(data.message) : '';
35
39
  if (user['email'] && user['email_notifications'] === true) {
36
40
  try {
@@ -1,13 +1,13 @@
1
+ import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
1
2
  import { parseJSON, toArray } from '@directus/utils';
2
3
  import { format, isValid, parseISO } from 'date-fns';
3
- import flat from 'flat';
4
+ import { unflatten } from 'flat';
4
5
  import Joi from 'joi';
5
6
  import { clone, cloneDeep, isNil, isObject, isPlainObject, omit, pick } from 'lodash-es';
6
7
  import { v4 as uuid } from 'uuid';
7
8
  import { parse as wktToGeoJSON } from 'wellknown';
8
9
  import { getHelpers } from '../database/helpers/index.js';
9
10
  import getDatabase from '../database/index.js';
10
- import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
11
11
  import { generateHash } from '../utils/generate-hash.js';
12
12
  import { ItemsService } from './items.js';
13
13
  /**
@@ -164,7 +164,7 @@ export class PayloadService {
164
164
  const aggregateKeys = Object.keys(payload[0]).filter((key) => key.includes('->'));
165
165
  if (aggregateKeys.length) {
166
166
  for (const item of payload) {
167
- Object.assign(item, flat.unflatten(pick(item, aggregateKeys), { delimiter: '->' }));
167
+ Object.assign(item, unflatten(pick(item, aggregateKeys), { delimiter: '->' }));
168
168
  aggregateKeys.forEach((key) => delete item[key]);
169
169
  }
170
170
  }
@@ -1,3 +1,4 @@
1
+ import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
1
2
  import { createInspector } from '@directus/schema';
2
3
  import { toArray } from '@directus/utils';
3
4
  import { clearSystemCache, getCache } from '../cache.js';
@@ -5,7 +6,6 @@ import { getHelpers } from '../database/helpers/index.js';
5
6
  import getDatabase, { getSchemaInspector } from '../database/index.js';
6
7
  import { systemRelationRows } from '../database/system-data/relations/index.js';
7
8
  import emitter from '../emitter.js';
8
- import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
9
9
  import { getDefaultIndexName } from '../utils/get-default-index-name.js';
10
10
  import { getSchema } from '../utils/get-schema.js';
11
11
  import { ItemsService } from './items.js';
@@ -115,16 +115,18 @@ export class RelationsService {
115
115
  if (!relation.field) {
116
116
  throw new InvalidPayloadError({ reason: '"field" is required' });
117
117
  }
118
- if (relation.collection in this.schema.collections === false) {
118
+ const collectionSchema = this.schema.collections[relation.collection];
119
+ if (!collectionSchema) {
119
120
  throw new InvalidPayloadError({ reason: `Collection "${relation.collection}" doesn't exist` });
120
121
  }
121
- if (relation.field in this.schema.collections[relation.collection].fields === false) {
122
+ const fieldSchema = collectionSchema.fields[relation.field];
123
+ if (!fieldSchema) {
122
124
  throw new InvalidPayloadError({
123
125
  reason: `Field "${relation.field}" doesn't exist in collection "${relation.collection}"`,
124
126
  });
125
127
  }
126
128
  // A primary key should not be a foreign key
127
- if (this.schema.collections[relation.collection].primary === relation.field) {
129
+ if (collectionSchema.primary === relation.field) {
128
130
  throw new InvalidPayloadError({
129
131
  reason: `Field "${relation.field}" in collection "${relation.collection}" is a primary key`,
130
132
  });
@@ -151,7 +153,7 @@ export class RelationsService {
151
153
  await this.knex.transaction(async (trx) => {
152
154
  if (relation.related_collection) {
153
155
  await trx.schema.alterTable(relation.collection, async (table) => {
154
- this.alterType(table, relation);
156
+ this.alterType(table, relation, fieldSchema.nullable);
155
157
  const constraintName = getDefaultIndexName('foreign', relation.collection, relation.field);
156
158
  const builder = table
157
159
  .foreign(relation.field, constraintName)
@@ -198,10 +200,12 @@ export class RelationsService {
198
200
  if (this.accountability && this.accountability.admin !== true) {
199
201
  throw new ForbiddenError();
200
202
  }
201
- if (collection in this.schema.collections === false) {
203
+ const collectionSchema = this.schema.collections[collection];
204
+ if (!collectionSchema) {
202
205
  throw new InvalidPayloadError({ reason: `Collection "${collection}" doesn't exist` });
203
206
  }
204
- if (field in this.schema.collections[collection].fields === false) {
207
+ const fieldSchema = collectionSchema.fields[field];
208
+ if (!fieldSchema) {
205
209
  throw new InvalidPayloadError({ reason: `Field "${field}" doesn't exist in collection "${collection}"` });
206
210
  }
207
211
  const existingRelation = this.schema.relations.find((existingRelation) => existingRelation.collection === collection && existingRelation.field === field);
@@ -225,7 +229,7 @@ export class RelationsService {
225
229
  constraintName = this.helpers.schema.constraintName(constraintName);
226
230
  existingRelation.schema.constraint_name = constraintName;
227
231
  }
228
- this.alterType(table, relation);
232
+ this.alterType(table, relation, fieldSchema.nullable);
229
233
  const builder = table
230
234
  .foreign(field, constraintName || undefined)
231
235
  .references(`${existingRelation.related_collection}.${this.schema.collections[existingRelation.related_collection].primary}`);
@@ -447,11 +451,16 @@ export class RelationsService {
447
451
  *
448
452
  * @TODO This is a bit of a hack, and might be better of abstracted elsewhere
449
453
  */
450
- alterType(table, relation) {
454
+ alterType(table, relation, nullable) {
451
455
  const m2oFieldDBType = this.schema.collections[relation.collection].fields[relation.field].dbType;
452
456
  const relatedFieldDBType = this.schema.collections[relation.related_collection].fields[this.schema.collections[relation.related_collection].primary].dbType;
453
457
  if (m2oFieldDBType !== relatedFieldDBType && m2oFieldDBType === 'int' && relatedFieldDBType === 'int unsigned') {
454
- table.specificType(relation.field, 'int unsigned').alter();
458
+ const alterField = table.specificType(relation.field, 'int unsigned');
459
+ // Maintains the non-nullable state
460
+ if (!nullable) {
461
+ alterField.notNullable();
462
+ }
463
+ alterField.alter();
455
464
  }
456
465
  }
457
466
  }
@@ -1,19 +1,20 @@
1
- import { toArray } from '@directus/utils';
1
+ import { useEnv } from '@directus/env';
2
+ import { toArray, toBoolean } from '@directus/utils';
3
+ import { version } from 'directus/version';
2
4
  import { merge } from 'lodash-es';
3
5
  import { Readable } from 'node:stream';
4
6
  import { performance } from 'perf_hooks';
5
7
  import { getCache } from '../cache.js';
6
8
  import getDatabase, { hasDatabaseConnection } from '../database/index.js';
7
- import env from '../env.js';
8
- import logger from '../logger.js';
9
+ import { useLogger } from '../logger.js';
9
10
  import getMailer from '../mailer.js';
10
11
  import { rateLimiterGlobal } from '../middleware/rate-limiter-global.js';
11
12
  import { rateLimiter } from '../middleware/rate-limiter-ip.js';
12
13
  import { SERVER_ONLINE } from '../server.js';
13
14
  import { getStorage } from '../storage/index.js';
14
- import { version } from '../utils/package.js';
15
- import { toBoolean } from '../utils/to-boolean.js';
16
15
  import { SettingsService } from './settings.js';
16
+ const env = useEnv();
17
+ const logger = useLogger();
17
18
  export class ServerService {
18
19
  knex;
19
20
  accountability;
@@ -70,8 +71,6 @@ export class ServerService {
70
71
  default: env['QUERY_LIMIT_DEFAULT'],
71
72
  max: Number.isFinite(env['QUERY_LIMIT_MAX']) ? env['QUERY_LIMIT_MAX'] : -1,
72
73
  };
73
- }
74
- if (this.accountability?.user) {
75
74
  if (toBoolean(env['WEBSOCKETS_ENABLED'])) {
76
75
  info['websocket'] = {};
77
76
  info['websocket'].rest = toBoolean(env['WEBSOCKETS_REST_ENABLED'])
@@ -93,6 +92,7 @@ export class ServerService {
93
92
  else {
94
93
  info['websocket'] = false;
95
94
  }
95
+ info['version'] = version;
96
96
  }
97
97
  return info;
98
98
  }
@@ -1,7 +1,7 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { ForbiddenError, InvalidCredentialsError } from '@directus/errors';
1
3
  import argon2 from 'argon2';
2
4
  import jwt from 'jsonwebtoken';
3
- import env from '../env.js';
4
- import { ForbiddenError, InvalidCredentialsError } from '@directus/errors';
5
5
  import { getMilliseconds } from '../utils/get-milliseconds.js';
6
6
  import { md } from '../utils/md.js';
7
7
  import { Url } from '../utils/url.js';
@@ -10,6 +10,7 @@ import { AuthorizationService } from './authorization.js';
10
10
  import { ItemsService } from './items.js';
11
11
  import { MailService } from './mail/index.js';
12
12
  import { UsersService } from './users.js';
13
+ const env = useEnv();
13
14
  export class SharesService extends ItemsService {
14
15
  authorizationService;
15
16
  constructor(options) {