@directus/api 15.0.0 → 17.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 (185) hide show
  1. package/dist/app.js +6 -4
  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 +9 -2
  5. package/dist/auth/drivers/openid.js +9 -2
  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 +2 -1
  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/load-extensions.js +4 -2
  23. package/dist/cli/utils/create-env/env-stub.liquid +1 -1
  24. package/dist/constants.d.ts +1 -1
  25. package/dist/constants.js +4 -1
  26. package/dist/controllers/assets.js +5 -3
  27. package/dist/controllers/auth.js +5 -4
  28. package/dist/controllers/extensions.js +18 -6
  29. package/dist/controllers/files.js +3 -3
  30. package/dist/controllers/permissions.js +11 -2
  31. package/dist/controllers/schema.js +3 -2
  32. package/dist/controllers/shares.js +3 -3
  33. package/dist/controllers/utils.js +13 -32
  34. package/dist/database/helpers/index.d.ts +1 -1
  35. package/dist/database/index.js +9 -2
  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/emitter.js +3 -1
  47. package/dist/extensions/lib/get-extensions-path.d.ts +1 -1
  48. package/dist/extensions/lib/get-extensions-path.js +2 -1
  49. package/dist/extensions/lib/get-extensions.d.ts +1 -1
  50. package/dist/extensions/lib/get-extensions.js +32 -8
  51. package/dist/extensions/lib/get-shared-deps-mapping.js +6 -4
  52. package/dist/extensions/lib/sandbox/register/call-reference.js +4 -2
  53. package/dist/extensions/lib/sandbox/sdk/generators/log.js +2 -1
  54. package/dist/extensions/lib/sync-extensions.js +6 -4
  55. package/dist/extensions/manager.js +43 -19
  56. package/dist/flows.js +13 -7
  57. package/dist/logger.d.ts +7 -7
  58. package/dist/logger.js +116 -92
  59. package/dist/mailer.js +4 -2
  60. package/dist/middleware/cache.js +4 -2
  61. package/dist/middleware/check-ip.js +25 -6
  62. package/dist/middleware/cors.js +2 -1
  63. package/dist/middleware/error-handler.js +5 -5
  64. package/dist/middleware/rate-limiter-global.js +4 -2
  65. package/dist/middleware/rate-limiter-ip.js +2 -1
  66. package/dist/middleware/respond.js +5 -3
  67. package/dist/operations/log/index.js +2 -1
  68. package/dist/rate-limiter.d.ts +2 -1
  69. package/dist/rate-limiter.js +5 -2
  70. package/dist/redis/index.d.ts +3 -2
  71. package/dist/redis/index.js +3 -2
  72. package/dist/redis/{create-redis.js → lib/create-redis.js} +2 -2
  73. package/dist/redis/utils/redis-config-available.d.ts +4 -0
  74. package/dist/redis/utils/redis-config-available.js +8 -0
  75. package/dist/request/request-interceptor.js +7 -5
  76. package/dist/request/response-interceptor.js +2 -2
  77. package/dist/request/validate-ip.d.ts +1 -1
  78. package/dist/request/validate-ip.js +23 -7
  79. package/dist/server.js +11 -7
  80. package/dist/services/activity.js +5 -4
  81. package/dist/services/assets.d.ts +2 -0
  82. package/dist/services/assets.js +9 -6
  83. package/dist/services/authentication.js +17 -9
  84. package/dist/services/authorization.d.ts +1 -1
  85. package/dist/services/authorization.js +15 -3
  86. package/dist/services/collections.js +5 -4
  87. package/dist/services/extensions.d.ts +15 -9
  88. package/dist/services/extensions.js +74 -39
  89. package/dist/services/fields.js +9 -4
  90. package/dist/services/files.d.ts +2 -2
  91. package/dist/services/files.js +22 -14
  92. package/dist/services/graphql/index.js +46 -3
  93. package/dist/services/graphql/subscription.js +2 -2
  94. package/dist/services/graphql/types/bigint.js +16 -5
  95. package/dist/services/graphql/utils/process-error.d.ts +4 -1
  96. package/dist/services/graphql/utils/process-error.js +10 -8
  97. package/dist/services/{import-export/index.d.ts → import-export.d.ts} +1 -1
  98. package/dist/services/{import-export/index.js → import-export.js} +14 -12
  99. package/dist/services/index.d.ts +1 -1
  100. package/dist/services/index.js +1 -1
  101. package/dist/services/items.js +12 -8
  102. package/dist/services/mail/index.js +4 -2
  103. package/dist/services/notifications.js +7 -3
  104. package/dist/services/permissions.d.ts +3 -2
  105. package/dist/services/permissions.js +76 -1
  106. package/dist/services/relations.js +19 -10
  107. package/dist/services/roles.js +83 -15
  108. package/dist/services/server.js +7 -5
  109. package/dist/services/shares.js +3 -2
  110. package/dist/services/specifications.js +2 -1
  111. package/dist/services/users.js +20 -9
  112. package/dist/services/versions.js +6 -5
  113. package/dist/services/webhooks.d.ts +2 -2
  114. package/dist/services/webhooks.js +2 -2
  115. package/dist/services/websocket.d.ts +1 -1
  116. package/dist/services/websocket.js +4 -3
  117. package/dist/storage/register-drivers.js +2 -1
  118. package/dist/storage/register-locations.js +2 -1
  119. package/dist/synchronization.js +3 -1
  120. package/dist/telemetry/lib/get-report.js +1 -1
  121. package/dist/telemetry/lib/init-telemetry.js +2 -2
  122. package/dist/telemetry/lib/send-report.js +1 -1
  123. package/dist/telemetry/lib/track.js +2 -3
  124. package/dist/telemetry/utils/get-user-count.js +1 -1
  125. package/dist/types/assets.d.ts +2 -0
  126. package/dist/types/items.d.ts +4 -12
  127. package/dist/types/items.js +0 -4
  128. package/dist/utils/apply-diff.js +2 -1
  129. package/dist/utils/apply-query.js +0 -11
  130. package/dist/utils/delete-from-require-cache.js +2 -1
  131. package/dist/utils/get-accountability-for-token.js +3 -2
  132. package/dist/utils/get-auth-providers.js +2 -1
  133. package/dist/utils/get-cache-headers.js +5 -2
  134. package/dist/utils/get-config-from-env.js +2 -1
  135. package/dist/utils/get-default-value.js +4 -3
  136. package/dist/utils/get-ip-from-req.js +4 -2
  137. package/dist/utils/get-permissions.js +5 -3
  138. package/dist/utils/get-schema.js +5 -2
  139. package/dist/utils/get-snapshot-diff.js +7 -9
  140. package/dist/utils/get-snapshot.js +4 -4
  141. package/dist/utils/ip-in-networks.d.ts +6 -0
  142. package/dist/utils/ip-in-networks.js +13 -0
  143. package/dist/utils/is-url-allowed.js +2 -1
  144. package/dist/utils/job-queue.d.ts +1 -0
  145. package/dist/utils/job-queue.js +3 -0
  146. package/dist/utils/sanitize-query.js +7 -2
  147. package/dist/utils/sanitize-schema.d.ts +1 -1
  148. package/dist/utils/should-clear-cache.js +2 -1
  149. package/dist/utils/should-skip-cache.js +2 -1
  150. package/dist/utils/transformations.js +95 -12
  151. package/dist/utils/validate-env.js +4 -2
  152. package/dist/utils/validate-query.js +7 -3
  153. package/dist/utils/validate-storage.js +4 -2
  154. package/dist/webhooks.js +4 -3
  155. package/dist/websocket/controllers/base.js +12 -6
  156. package/dist/websocket/controllers/graphql.js +4 -2
  157. package/dist/websocket/controllers/hooks.js +3 -2
  158. package/dist/websocket/controllers/index.js +4 -2
  159. package/dist/websocket/controllers/rest.js +4 -2
  160. package/dist/websocket/errors.js +2 -1
  161. package/dist/websocket/handlers/heartbeat.js +4 -3
  162. package/dist/websocket/handlers/subscribe.d.ts +2 -2
  163. package/dist/websocket/handlers/subscribe.js +5 -4
  164. package/package.json +57 -57
  165. package/dist/__utils__/items-utils.d.ts +0 -2
  166. package/dist/__utils__/items-utils.js +0 -31
  167. package/dist/__utils__/mock-env.d.ts +0 -18
  168. package/dist/__utils__/mock-env.js +0 -41
  169. package/dist/__utils__/schemas.d.ts +0 -13
  170. package/dist/__utils__/schemas.js +0 -301
  171. package/dist/__utils__/snapshots.d.ts +0 -5
  172. package/dist/__utils__/snapshots.js +0 -903
  173. package/dist/env.d.ts +0 -14
  174. package/dist/env.js +0 -511
  175. package/dist/messenger.d.ts +0 -24
  176. package/dist/messenger.js +0 -64
  177. package/dist/services/import-export/import-worker.d.ts +0 -9
  178. package/dist/services/import-export/import-worker.js +0 -9
  179. package/dist/utils/to-boolean.d.ts +0 -4
  180. package/dist/utils/to-boolean.js +0 -6
  181. package/dist/worker-pool.d.ts +0 -2
  182. package/dist/worker-pool.js +0 -19
  183. /package/dist/redis/{create-redis.d.ts → lib/create-redis.d.ts} +0 -0
  184. /package/dist/redis/{use-redis.d.ts → lib/use-redis.d.ts} +0 -0
  185. /package/dist/redis/{use-redis.js → lib/use-redis.js} +0 -0
@@ -1,20 +1,12 @@
1
- /**
2
- * I know this looks a little silly, but it allows us to explicitly differentiate between when we're
3
- * expecting an item vs any other generic object.
4
- */
5
1
  import type { DirectusError } from '@directus/errors';
6
2
  import type { EventContext } from '@directus/types';
7
3
  import type { MutationTracker } from '../services/items.js';
8
4
  export type Item = Record<string, any>;
9
5
  export type PrimaryKey = string | number;
10
- export type Alterations = {
11
- create: {
12
- [key: string]: any;
13
- }[];
14
- update: {
15
- [key: string]: any;
16
- }[];
17
- delete: (number | string)[];
6
+ export type Alterations<T extends Item = Item, K extends keyof T | undefined = undefined> = {
7
+ create: Partial<T>[];
8
+ update: (K extends keyof T ? Partial<T> & Pick<T, K> : Partial<T>)[];
9
+ delete: (K extends keyof T ? T[K] : PrimaryKey)[];
18
10
  };
19
11
  export type MutationOptions = {
20
12
  /**
@@ -1,5 +1 @@
1
- /**
2
- * I know this looks a little silly, but it allows us to explicitly differentiate between when we're
3
- * expecting an item vs any other generic object.
4
- */
5
1
  export {};
@@ -4,12 +4,13 @@ import { flushCaches } from '../cache.js';
4
4
  import { getHelpers } from '../database/helpers/index.js';
5
5
  import getDatabase from '../database/index.js';
6
6
  import emitter from '../emitter.js';
7
- import logger from '../logger.js';
7
+ import { useLogger } from '../logger.js';
8
8
  import { CollectionsService } from '../services/collections.js';
9
9
  import { FieldsService } from '../services/fields.js';
10
10
  import { RelationsService } from '../services/relations.js';
11
11
  import { DiffKind } from '../types/index.js';
12
12
  import { getSchema } from './get-schema.js';
13
+ const logger = useLogger();
13
14
  export async function applyDiff(currentSnapshot, snapshotDiff, options) {
14
15
  const database = options?.database ?? getDatabase();
15
16
  const helpers = getHelpers(database);
@@ -408,9 +408,6 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
408
408
  if (['integer', 'float', 'decimal'].includes(type)) {
409
409
  compareValue = Number(compareValue);
410
410
  }
411
- else if (type === 'bigInteger') {
412
- compareValue = BigInt(compareValue);
413
- }
414
411
  }
415
412
  // Cast filter value (compareValue) based on type of field being filtered against
416
413
  const [collection, field] = key.split('.');
@@ -433,14 +430,6 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
433
430
  compareValue = Number(compareValue);
434
431
  }
435
432
  }
436
- if (type === 'bigInteger') {
437
- if (Array.isArray(compareValue)) {
438
- compareValue = compareValue.map((val) => BigInt(val));
439
- }
440
- else {
441
- compareValue = BigInt(compareValue);
442
- }
443
- }
444
433
  }
445
434
  if (operator === '_eq') {
446
435
  dbQuery[logical].where(selectionRaw, '=', compareValue);
@@ -1,7 +1,8 @@
1
1
  import { createRequire } from 'node:module';
2
- import logger from '../logger.js';
2
+ import { useLogger } from '../logger.js';
3
3
  const require = createRequire(import.meta.url);
4
4
  export function deleteFromRequireCache(modulePath) {
5
+ const logger = useLogger();
5
6
  try {
6
7
  const moduleCachePath = require.resolve(modulePath);
7
8
  delete require.cache[moduleCachePath];
@@ -1,9 +1,10 @@
1
- import getDatabase from '../database/index.js';
2
- import env from '../env.js';
1
+ import { useEnv } from '@directus/env';
3
2
  import { InvalidCredentialsError } from '@directus/errors';
3
+ import getDatabase from '../database/index.js';
4
4
  import isDirectusJWT from './is-directus-jwt.js';
5
5
  import { verifyAccessJWT } from './jwt.js';
6
6
  export async function getAccountabilityForToken(token, accountability) {
7
+ const env = useEnv();
7
8
  if (!accountability) {
8
9
  accountability = {
9
10
  user: null,
@@ -1,6 +1,7 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { toArray } from '@directus/utils';
2
- import env from '../env.js';
3
3
  export function getAuthProviders() {
4
+ const env = useEnv();
4
5
  return toArray(env['AUTH_PROVIDERS'])
5
6
  .filter((provider) => provider && env[`AUTH_${provider.toUpperCase()}_DRIVER`])
6
7
  .map((provider) => ({
@@ -1,4 +1,4 @@
1
- import env from '../env.js';
1
+ import { useEnv } from '@directus/env';
2
2
  import { shouldSkipCache } from './should-skip-cache.js';
3
3
  /**
4
4
  * Returns the Cache-Control header for the current request
@@ -9,6 +9,7 @@ import { shouldSkipCache } from './should-skip-cache.js';
9
9
  * @param personalized Whether requests depend on the authentication status of users
10
10
  */
11
11
  export function getCacheControlHeader(req, ttl, globalCacheSettings, personalized) {
12
+ const env = useEnv();
12
13
  // When the user explicitly asked to skip the cache
13
14
  if (shouldSkipCache(req))
14
15
  return 'no-store';
@@ -29,7 +30,9 @@ export function getCacheControlHeader(req, ttl, globalCacheSettings, personalize
29
30
  const ttlSeconds = Math.round(ttl / 1000);
30
31
  headerValues.push(`max-age=${ttlSeconds}`);
31
32
  // When the s-maxage flag should be included
32
- if (globalCacheSettings && Number.isInteger(env['CACHE_CONTROL_S_MAXAGE']) && env['CACHE_CONTROL_S_MAXAGE'] >= 0) {
33
+ if (globalCacheSettings &&
34
+ Number.isInteger(env['CACHE_CONTROL_S_MAXAGE']) &&
35
+ env['CACHE_CONTROL_S_MAXAGE'] >= 0) {
33
36
  headerValues.push(`s-maxage=${env['CACHE_CONTROL_S_MAXAGE']}`);
34
37
  }
35
38
  return headerValues.join(', ');
@@ -1,7 +1,8 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import camelcase from 'camelcase';
2
3
  import { set } from 'lodash-es';
3
- import env from '../env.js';
4
4
  export function getConfigFromEnv(prefix, omitPrefix, type = 'camelcase') {
5
+ const env = useEnv();
5
6
  const config = {};
6
7
  for (const [key, value] of Object.entries(env)) {
7
8
  if (key.toLowerCase().startsWith(prefix.toLowerCase()) === false)
@@ -1,6 +1,6 @@
1
1
  import { parseJSON } from '@directus/utils';
2
- import env from '../env.js';
3
- import logger from '../logger.js';
2
+ import { getNodeEnv } from '@directus/utils/node';
3
+ import { useLogger } from '../logger.js';
4
4
  import getLocalType from './get-local-type.js';
5
5
  export default function getDefaultValue(column, field) {
6
6
  const type = getLocalType(column, field);
@@ -37,6 +37,7 @@ function castToBoolean(value) {
37
37
  return Boolean(value);
38
38
  }
39
39
  function castToObject(value) {
40
+ const logger = useLogger();
40
41
  if (!value)
41
42
  return value;
42
43
  if (typeof value === 'object')
@@ -46,7 +47,7 @@ function castToObject(value) {
46
47
  return parseJSON(value);
47
48
  }
48
49
  catch (err) {
49
- if (env['NODE_ENV'] === 'development') {
50
+ if (getNodeEnv() === 'development') {
50
51
  logger.error(err);
51
52
  }
52
53
  return value;
@@ -1,7 +1,9 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { isIP } from 'net';
2
- import env from '../env.js';
3
- import logger from '../logger.js';
3
+ import { useLogger } from '../logger.js';
4
4
  export function getIPFromReq(req) {
5
+ const env = useEnv();
6
+ const logger = useLogger();
5
7
  let ip = req.ip;
6
8
  if (env['IP_CUSTOM_HEADER']) {
7
9
  const customIPHeaderValue = req.get(env['IP_CUSTOM_HEADER']);
@@ -1,18 +1,20 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { deepMap, parseFilter, parseJSON, parsePreset } from '@directus/utils';
2
3
  import { cloneDeep } from 'lodash-es';
3
4
  import hash from 'object-hash';
4
5
  import { getCache, getCacheValue, getSystemCache, setCacheValue, setSystemCache } from '../cache.js';
5
6
  import getDatabase from '../database/index.js';
6
7
  import { appAccessMinimalPermissions } from '../database/system-data/app-access-permissions/index.js';
7
- import env from '../env.js';
8
- import logger from '../logger.js';
8
+ import { useLogger } from '../logger.js';
9
9
  import { RolesService } from '../services/roles.js';
10
10
  import { UsersService } from '../services/users.js';
11
- import { mergePermissions } from './merge-permissions.js';
12
11
  import { mergePermissionsForShare } from './merge-permissions-for-share.js';
12
+ import { mergePermissions } from './merge-permissions.js';
13
13
  export async function getPermissions(accountability, schema) {
14
14
  const database = getDatabase();
15
15
  const { cache } = getCache();
16
+ const env = useEnv();
17
+ const logger = useLogger();
16
18
  let permissions = [];
17
19
  const { user, role, app, admin, share_scope } = accountability;
18
20
  const cacheKey = `permissions-${hash({ user, role, app, admin, share_scope })}`;
@@ -1,3 +1,4 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { createInspector } from '@directus/schema';
2
3
  import { parseJSON, toArray } from '@directus/utils';
3
4
  import { mapValues } from 'lodash-es';
@@ -6,12 +7,13 @@ import { ALIAS_TYPES } from '../constants.js';
6
7
  import getDatabase from '../database/index.js';
7
8
  import { systemCollectionRows } from '../database/system-data/collections/index.js';
8
9
  import { systemFieldRows } from '../database/system-data/fields/index.js';
9
- import env from '../env.js';
10
- import logger from '../logger.js';
10
+ import { useLogger } from '../logger.js';
11
11
  import { RelationsService } from '../services/relations.js';
12
12
  import getDefaultValue from './get-default-value.js';
13
13
  import getLocalType from './get-local-type.js';
14
+ const logger = useLogger();
14
15
  export async function getSchema(options) {
16
+ const env = useEnv();
15
17
  const database = options?.database || getDatabase();
16
18
  const schemaInspector = createInspector(database);
17
19
  let result;
@@ -42,6 +44,7 @@ export async function getSchema(options) {
42
44
  return result;
43
45
  }
44
46
  async function getDatabaseSchema(database, schemaInspector) {
47
+ const env = useEnv();
45
48
  const result = {
46
49
  collections: {},
47
50
  relations: [],
@@ -1,10 +1,9 @@
1
1
  import deepDiff from 'deep-diff';
2
- import { orderBy } from 'lodash-es';
3
2
  import { DiffKind } from '../types/index.js';
4
3
  import { sanitizeCollection, sanitizeField, sanitizeRelation } from './sanitize-schema.js';
5
4
  export function getSnapshotDiff(current, after) {
6
5
  const diffedSnapshot = {
7
- collections: orderBy([
6
+ collections: [
8
7
  ...current.collections.map((currentCollection) => {
9
8
  const afterCollection = after.collections.find((afterCollection) => afterCollection.collection === currentCollection.collection);
10
9
  return {
@@ -21,8 +20,8 @@ export function getSnapshotDiff(current, after) {
21
20
  collection: afterCollection.collection,
22
21
  diff: deepDiff.diff(undefined, sanitizeCollection(afterCollection)),
23
22
  })),
24
- ].filter((obj) => Array.isArray(obj.diff)), 'collection'),
25
- fields: orderBy([
23
+ ].filter((obj) => Array.isArray(obj.diff)),
24
+ fields: [
26
25
  ...current.fields.map((currentField) => {
27
26
  const afterField = after.fields.find((afterField) => afterField.collection === currentField.collection && afterField.field === currentField.field);
28
27
  const isAutoIncrementPrimaryKey = !!currentField.schema?.is_primary_key && !!currentField.schema?.has_auto_increment;
@@ -42,8 +41,8 @@ export function getSnapshotDiff(current, after) {
42
41
  field: afterField.field,
43
42
  diff: deepDiff.diff(undefined, sanitizeField(afterField)),
44
43
  })),
45
- ].filter((obj) => Array.isArray(obj.diff)), ['collection']),
46
- relations: orderBy([
44
+ ].filter((obj) => Array.isArray(obj.diff)),
45
+ relations: [
47
46
  ...current.relations.map((currentRelation) => {
48
47
  const afterRelation = after.relations.find((afterRelation) => afterRelation.collection === currentRelation.collection && afterRelation.field === currentRelation.field);
49
48
  return {
@@ -55,8 +54,7 @@ export function getSnapshotDiff(current, after) {
55
54
  }),
56
55
  ...after.relations
57
56
  .filter((afterRelation) => {
58
- const currentRelation = current.relations.find((currentRelation) => currentRelation.collection === afterRelation.collection &&
59
- afterRelation.field === currentRelation.field);
57
+ const currentRelation = current.relations.find((currentRelation) => currentRelation.collection === afterRelation.collection && afterRelation.field === currentRelation.field);
60
58
  return !!currentRelation === false;
61
59
  })
62
60
  .map((afterRelation) => ({
@@ -65,7 +63,7 @@ export function getSnapshotDiff(current, after) {
65
63
  related_collection: afterRelation.related_collection,
66
64
  diff: deepDiff.diff(undefined, sanitizeRelation(afterRelation)),
67
65
  })),
68
- ].filter((obj) => Array.isArray(obj.diff)), ['collection']),
66
+ ].filter((obj) => Array.isArray(obj.diff)),
69
67
  };
70
68
  /**
71
69
  * When you delete a collection, we don't have to individually drop all the fields/relations as well
@@ -19,11 +19,11 @@ export async function getSnapshot(options) {
19
19
  relationsService.readAll(),
20
20
  ]);
21
21
  const collectionsFiltered = collectionsRaw.filter((item) => excludeSystem(item));
22
- const fieldsFiltered = fieldsRaw.filter((item) => excludeSystem(item)).map(omitID);
23
- const relationsFiltered = relationsRaw.filter((item) => excludeSystem(item)).map(omitID);
22
+ const fieldsFiltered = fieldsRaw.filter((item) => excludeSystem(item));
23
+ const relationsFiltered = relationsRaw.filter((item) => excludeSystem(item));
24
24
  const collectionsSorted = sortBy(mapValues(collectionsFiltered, sortDeep), ['collection']);
25
- const fieldsSorted = sortBy(mapValues(fieldsFiltered, sortDeep), ['collection', 'field']);
26
- const relationsSorted = sortBy(mapValues(relationsFiltered, sortDeep), ['collection', 'field']);
25
+ const fieldsSorted = sortBy(mapValues(fieldsFiltered, sortDeep), ['collection', 'meta.id']).map(omitID);
26
+ const relationsSorted = sortBy(mapValues(relationsFiltered, sortDeep), ['collection', 'meta.id']).map(omitID);
27
27
  return {
28
28
  version: 1,
29
29
  directus: version,
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Checks if an IP address is contained in a list of networks
3
+ * @param networks List of IP addresses (192.168.0.1), CIDR notations (192.168.0.0/24) or IP ranges (192-168.0.0-192.168.2.0)
4
+ * @throws Will throw if list contains invalid network definitions
5
+ */
6
+ export declare function ipInNetworks(ip: string, networks: string[]): boolean;
@@ -0,0 +1,13 @@
1
+ import { matches } from 'ip-matching';
2
+ /**
3
+ * Checks if an IP address is contained in a list of networks
4
+ * @param networks List of IP addresses (192.168.0.1), CIDR notations (192.168.0.0/24) or IP ranges (192-168.0.0-192.168.2.0)
5
+ * @throws Will throw if list contains invalid network definitions
6
+ */
7
+ export function ipInNetworks(ip, networks) {
8
+ for (const allowedIp of networks) {
9
+ if (matches(ip, allowedIp))
10
+ return true;
11
+ }
12
+ return false;
13
+ }
@@ -1,10 +1,11 @@
1
1
  import { toArray } from '@directus/utils';
2
- import logger from '../logger.js';
3
2
  import { URL } from 'url';
3
+ import { useLogger } from '../logger.js';
4
4
  /**
5
5
  * Check if url matches allow list either exactly or by domain+path
6
6
  */
7
7
  export default function isUrlAllowed(url, allowList) {
8
+ const logger = useLogger();
8
9
  const urlAllowList = toArray(allowList);
9
10
  if (urlAllowList.includes(url))
10
11
  return true;
@@ -5,4 +5,5 @@ export declare class JobQueue {
5
5
  constructor();
6
6
  enqueue(job: PromiseCallback): void;
7
7
  private run;
8
+ get size(): number;
8
9
  }
@@ -19,4 +19,7 @@ export class JobQueue {
19
19
  }
20
20
  this.running = false;
21
21
  }
22
+ get size() {
23
+ return this.jobs.length;
24
+ }
22
25
  }
@@ -1,9 +1,10 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { parseFilter, parseJSON } from '@directus/utils';
2
3
  import { flatten, get, isPlainObject, merge, set } from 'lodash-es';
3
- import env from '../env.js';
4
- import logger from '../logger.js';
4
+ import { useLogger } from '../logger.js';
5
5
  import { Meta } from '../types/index.js';
6
6
  export function sanitizeQuery(rawQuery, accountability) {
7
+ const env = useEnv();
7
8
  const query = {};
8
9
  const hasMaxLimit = 'QUERY_LIMIT_MAX' in env &&
9
10
  Number(env['QUERY_LIMIT_MAX']) >= 0 &&
@@ -84,6 +85,7 @@ function sanitizeSort(rawSort) {
84
85
  return fields;
85
86
  }
86
87
  function sanitizeAggregate(rawAggregate) {
88
+ const logger = useLogger();
87
89
  let aggregate = rawAggregate;
88
90
  if (typeof rawAggregate === 'string') {
89
91
  try {
@@ -102,6 +104,7 @@ function sanitizeAggregate(rawAggregate) {
102
104
  return aggregate;
103
105
  }
104
106
  function sanitizeFilter(rawFilter, accountability) {
107
+ const logger = useLogger();
105
108
  let filters = rawFilter;
106
109
  if (typeof rawFilter === 'string') {
107
110
  try {
@@ -137,6 +140,7 @@ function sanitizeMeta(rawMeta) {
137
140
  return [rawMeta];
138
141
  }
139
142
  function sanitizeDeep(deep, accountability) {
143
+ const logger = useLogger();
140
144
  const result = {};
141
145
  if (typeof deep === 'string') {
142
146
  try {
@@ -175,6 +179,7 @@ function sanitizeDeep(deep, accountability) {
175
179
  }
176
180
  }
177
181
  function sanitizeAlias(rawAlias) {
182
+ const logger = useLogger();
178
183
  let alias = rawAlias;
179
184
  if (typeof rawAlias === 'string') {
180
185
  try {
@@ -16,7 +16,7 @@ export declare function sanitizeCollection(collection: Collection | undefined):
16
16
  * @returns sanitized field
17
17
  */
18
18
  export declare function sanitizeField(field: Field | undefined, sanitizeAllSchema?: boolean): Partial<Field> | undefined;
19
- export declare function sanitizeColumn(column: Column): Pick<Column, "table" | "name" | "numeric_precision" | "numeric_scale" | "data_type" | "default_value" | "max_length" | "is_nullable" | "is_unique" | "is_primary_key" | "is_generated" | "generation_expression" | "has_auto_increment" | "foreign_key_table" | "foreign_key_column">;
19
+ export declare function sanitizeColumn(column: Column): Pick<Column, "name" | "table" | "numeric_precision" | "numeric_scale" | "data_type" | "default_value" | "max_length" | "is_nullable" | "is_unique" | "is_primary_key" | "is_generated" | "generation_expression" | "has_auto_increment" | "foreign_key_table" | "foreign_key_column">;
20
20
  /**
21
21
  * Pick certain database vendor specific relation properties that should be compared when performing diff
22
22
  *
@@ -1,4 +1,4 @@
1
- import env from '../env.js';
1
+ import { useEnv } from '@directus/env';
2
2
  /**
3
3
  * Check whether cache should be cleared
4
4
  *
@@ -7,6 +7,7 @@ import env from '../env.js';
7
7
  * @param collection Collection name to check if cache purging should be ignored
8
8
  */
9
9
  export function shouldClearCache(cache, opts, collection) {
10
+ const env = useEnv();
10
11
  if (env['CACHE_AUTO_PURGE']) {
11
12
  if (collection && env['CACHE_AUTO_PURGE_IGNORE_LIST'].includes(collection)) {
12
13
  return false;
@@ -1,6 +1,6 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { getEndpoint } from '@directus/utils';
2
3
  import url from 'url';
3
- import env from '../env.js';
4
4
  import { Url } from './url.js';
5
5
  /**
6
6
  * Whether to skip caching for the current request
@@ -8,6 +8,7 @@ import { Url } from './url.js';
8
8
  * @param req Express request object
9
9
  */
10
10
  export function shouldSkipCache(req) {
11
+ const env = useEnv();
11
12
  // Always skip cache for requests coming from the data studio based on Referer header
12
13
  const referer = req.get('Referer');
13
14
  if (referer) {
@@ -1,3 +1,4 @@
1
+ import { clamp } from 'lodash-es';
1
2
  export function resolvePreset({ transformationParams, acceptFormat }, file) {
2
3
  const transforms = transformationParams.transforms ? [...transformationParams.transforms] : [];
3
4
  if (transformationParams.format || transformationParams.quality) {
@@ -9,18 +10,54 @@ export function resolvePreset({ transformationParams, acceptFormat }, file) {
9
10
  },
10
11
  ]);
11
12
  }
12
- if (transformationParams.width || transformationParams.height) {
13
- transforms.push([
14
- 'resize',
15
- {
16
- width: transformationParams.width ? Number(transformationParams.width) : undefined,
17
- height: transformationParams.height ? Number(transformationParams.height) : undefined,
18
- fit: transformationParams.fit,
19
- withoutEnlargement: transformationParams.withoutEnlargement
20
- ? Boolean(transformationParams.withoutEnlargement)
21
- : undefined,
22
- },
23
- ]);
13
+ if ((transformationParams.width || transformationParams.height) && file.width && file.height) {
14
+ const toWidth = transformationParams.width ? Number(transformationParams.width) : undefined;
15
+ const toHeight = transformationParams.height ? Number(transformationParams.height) : undefined;
16
+ const toFocalPointX = transformationParams.focal_point_x
17
+ ? Number(transformationParams.focal_point_x)
18
+ : file.focal_point_x;
19
+ const toFocalPointY = transformationParams.focal_point_y
20
+ ? Number(transformationParams.focal_point_y)
21
+ : file.focal_point_y;
22
+ /*
23
+ * Focal point cropping only works with a fixed size (width x height) when `cover`ing,
24
+ * since the other modes show the whole image. Sharp by default also simply scales up/down
25
+ * when only supplied with one dimension, so we **must** check, else we break existing behaviour.
26
+ * See: https://sharp.pixelplumbing.com/api-resize#resize
27
+ * Also only crop to focal point when explicitly defined so that users can still `cover` with
28
+ * other parameters like `position` and `gravity` - Else fall back to regular behaviour
29
+ */
30
+ if ((transformationParams.fit === undefined || transformationParams.fit === 'cover') &&
31
+ toWidth &&
32
+ toHeight &&
33
+ toFocalPointX !== null &&
34
+ toFocalPointY !== null) {
35
+ const transformArgs = getResizeArguments({ w: file.width, h: file.height }, { w: toWidth, h: toHeight }, { x: toFocalPointX, y: toFocalPointY });
36
+ transforms.push([
37
+ 'resize',
38
+ {
39
+ width: transformArgs.width,
40
+ height: transformArgs.height,
41
+ fit: transformationParams.fit,
42
+ withoutEnlargement: transformationParams.withoutEnlargement
43
+ ? Boolean(transformationParams.withoutEnlargement)
44
+ : undefined,
45
+ },
46
+ ], ['extract', transformArgs.region]);
47
+ }
48
+ else {
49
+ transforms.push([
50
+ 'resize',
51
+ {
52
+ width: toWidth,
53
+ height: toHeight,
54
+ fit: transformationParams.fit,
55
+ withoutEnlargement: transformationParams.withoutEnlargement
56
+ ? Boolean(transformationParams.withoutEnlargement)
57
+ : undefined,
58
+ },
59
+ ]);
60
+ }
24
61
  }
25
62
  return transforms;
26
63
  }
@@ -47,3 +84,49 @@ export function maybeExtractFormat(transforms) {
47
84
  const lastToFormat = toFormats[toFormats.length - 1];
48
85
  return lastToFormat ? lastToFormat[1]?.toString() : undefined;
49
86
  }
87
+ /**
88
+ * Resize an image but keep it centered on the focal point.
89
+ * Based on the method outlined in https://github.com/lovell/sharp/issues/1198#issuecomment-384591756
90
+ */
91
+ function getResizeArguments(original, target, focalPoint) {
92
+ const { width, height, factor } = getIntermediateDimensions(original, target);
93
+ const region = getExtractionRegion(factor, focalPoint ?? { x: original.w / 2, y: original.h / 2 }, target, {
94
+ w: width,
95
+ h: height,
96
+ });
97
+ return { width, height, region };
98
+ }
99
+ /**
100
+ * Calculates the dimensions of the intermediate (resized) image.
101
+ */
102
+ function getIntermediateDimensions(original, target) {
103
+ const hRatio = original.h / target.h;
104
+ const wRatio = original.w / target.w;
105
+ let factor;
106
+ let width;
107
+ let height;
108
+ if (hRatio < wRatio) {
109
+ factor = hRatio;
110
+ height = Math.round(target.h);
111
+ width = Math.round(original.w / factor);
112
+ }
113
+ else {
114
+ factor = wRatio;
115
+ width = Math.round(target.w);
116
+ height = Math.round(original.h / factor);
117
+ }
118
+ return { width, height, factor };
119
+ }
120
+ /**
121
+ * Calculates the Region to extract from the intermediate image.
122
+ */
123
+ function getExtractionRegion(factor, focalPoint, target, intermediate) {
124
+ const newXCenter = focalPoint.x / factor;
125
+ const newYCenter = focalPoint.y / factor;
126
+ return {
127
+ left: clamp(Math.round(newXCenter - target.w / 2), 0, intermediate.w - target.w),
128
+ top: clamp(Math.round(newYCenter - target.h / 2), 0, intermediate.h - target.h),
129
+ width: target.w,
130
+ height: target.h,
131
+ };
132
+ }
@@ -1,6 +1,8 @@
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
  export function validateEnv(requiredKeys) {
4
+ const env = useEnv();
5
+ const logger = useLogger();
4
6
  for (const requiredKey of requiredKeys) {
5
7
  if (requiredKey in env === false) {
6
8
  logger.error(`"${requiredKey}" Environment Variable is missing.`);
@@ -1,16 +1,20 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { InvalidQueryError } from '@directus/errors';
1
3
  import Joi from 'joi';
2
4
  import { isPlainObject, uniq } from 'lodash-es';
3
5
  import { stringify } from 'wellknown';
4
- import env from '../env.js';
5
- import { InvalidQueryError } from '@directus/errors';
6
6
  import { calculateFieldDepth } from './calculate-field-depth.js';
7
+ const env = useEnv();
7
8
  const querySchema = Joi.object({
8
9
  fields: Joi.array().items(Joi.string()),
9
10
  group: Joi.array().items(Joi.string()),
10
11
  sort: Joi.array().items(Joi.string()),
11
12
  filter: Joi.object({}).unknown(),
12
13
  limit: 'QUERY_LIMIT_MAX' in env && env['QUERY_LIMIT_MAX'] !== -1
13
- ? Joi.number().integer().min(-1).max(env['QUERY_LIMIT_MAX']) // min should be 0
14
+ ? Joi.number()
15
+ .integer()
16
+ .min(-1)
17
+ .max(env['QUERY_LIMIT_MAX']) // min should be 0
14
18
  : Joi.number().integer().min(-1),
15
19
  offset: Joi.number().integer().min(0),
16
20
  page: Joi.number().integer().min(0),
@@ -1,11 +1,13 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { toArray } from '@directus/utils';
2
3
  import { constants } from 'fs';
3
4
  import { access } from 'node:fs/promises';
4
5
  import path from 'path';
5
- import env from '../env.js';
6
6
  import { getExtensionsPath } from '../extensions/lib/get-extensions-path.js';
7
- import logger from '../logger.js';
7
+ import { useLogger } from '../logger.js';
8
8
  export async function validateStorage() {
9
+ const env = useEnv();
10
+ const logger = useLogger();
9
11
  if (env['DB_CLIENT'] === 'sqlite3') {
10
12
  try {
11
13
  await access(path.dirname(env['DB_FILENAME']), constants.R_OK | constants.W_OK);