@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
@@ -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);
@@ -405,7 +405,7 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
405
405
  if (column.includes('(') && column.includes(')')) {
406
406
  const functionName = column.split('(')[0];
407
407
  const type = getOutputTypeForFunction(functionName);
408
- if (['bigInteger', 'integer', 'float', 'decimal'].includes(type)) {
408
+ if (['integer', 'float', 'decimal'].includes(type)) {
409
409
  compareValue = Number(compareValue);
410
410
  }
411
411
  }
@@ -422,7 +422,7 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
422
422
  compareValue = helpers.date.parse(compareValue);
423
423
  }
424
424
  }
425
- if (['bigInteger', 'integer', 'float', 'decimal'].includes(type)) {
425
+ if (['integer', 'float', 'decimal'].includes(type)) {
426
426
  if (Array.isArray(compareValue)) {
427
427
  compareValue = compareValue.map((val) => Number(val));
428
428
  }
@@ -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,7 @@
1
1
  import hash from 'object-hash';
2
2
  import url from 'url';
3
3
  import { getGraphqlQueryAndVariables } from './get-graphql-query-and-variables.js';
4
- import { version } from './package.js';
4
+ import { version } from 'directus/version';
5
5
  export function getCacheKey(req) {
6
6
  const path = url.parse(req.originalUrl).pathname;
7
7
  const isGraphQl = path?.startsWith('/graphql');
@@ -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,2 +1,2 @@
1
1
  import type { Request } from 'express';
2
- export declare function getIPFromReq(req: Request): string;
2
+ export declare function getIPFromReq(req: Request): string | null;
@@ -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']);
@@ -13,5 +15,5 @@ export function getIPFromReq(req) {
13
15
  }
14
16
  }
15
17
  // IP addresses starting with ::ffff: are IPv4 addresses in IPv6 format. We can strip the prefix to get back to IPv4
16
- return ip.startsWith('::ffff:') ? ip.substring(7) : ip;
18
+ return ip?.startsWith('::ffff:') ? ip.substring(7) : ip ?? null;
17
19
  }
@@ -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
@@ -1,10 +1,10 @@
1
+ import { version } from 'directus/version';
1
2
  import { fromPairs, isArray, isPlainObject, mapValues, omit, sortBy, toPairs } from 'lodash-es';
2
3
  import getDatabase, { getDatabaseClient } from '../database/index.js';
3
4
  import { CollectionsService } from '../services/collections.js';
4
5
  import { FieldsService } from '../services/fields.js';
5
6
  import { RelationsService } from '../services/relations.js';
6
7
  import { getSchema } from './get-schema.js';
7
- import { version } from './package.js';
8
8
  import { sanitizeCollection, sanitizeField, sanitizeRelation } from './sanitize-schema.js';
9
9
  export async function getSnapshot(options) {
10
10
  const database = options?.database ?? getDatabase();
@@ -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,
@@ -1,5 +1,5 @@
1
+ import { version } from 'directus/version';
1
2
  import hash from 'object-hash';
2
- import { version } from './package.js';
3
3
  export function getVersionedHash(item) {
4
4
  return hash({ item, version });
5
5
  }
@@ -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,4 +1,4 @@
1
1
  /**
2
2
  * Render and sanitize a markdown string
3
3
  */
4
- export declare function md(str: string): string;
4
+ export declare function md(value: string): string;
package/dist/utils/md.js CHANGED
@@ -3,6 +3,7 @@ import sanitizeHTML from 'sanitize-html';
3
3
  /**
4
4
  * Render and sanitize a markdown string
5
5
  */
6
- export function md(str) {
7
- return sanitizeHTML(marked(str, { headerIds: false, mangle: false }));
6
+ export function md(value) {
7
+ const markdown = marked.parse(value); /* Would only be a promise if used with async extensions */
8
+ return sanitizeHTML(markdown);
8
9
  }
@@ -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),
@@ -96,6 +100,7 @@ function validateFilter(filter) {
96
100
  validateFilterPrimitive(nested, '_eq');
97
101
  }
98
102
  else {
103
+ // @ts-ignore TODO Check which case this is supposed to cover
99
104
  validateFilter(nested);
100
105
  }
101
106
  }