@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,10 +1,9 @@
1
- import { isDirectusError } from '@directus/errors';
1
+ import { useEnv } from '@directus/env';
2
+ import { ErrorCode, InvalidPayloadError, isDirectusError } from '@directus/errors';
2
3
  import { Router } from 'express';
3
4
  import { createLDAPAuthRouter, createLocalAuthRouter, createOAuth2AuthRouter, createOpenIDAuthRouter, createSAMLAuthRouter, } from '../auth/drivers/index.js';
4
5
  import { COOKIE_OPTIONS, DEFAULT_AUTH_PROVIDER } from '../constants.js';
5
- import env from '../env.js';
6
- import { ErrorCode, InvalidPayloadError } from '@directus/errors';
7
- import logger from '../logger.js';
6
+ import { useLogger } from '../logger.js';
8
7
  import { respond } from '../middleware/respond.js';
9
8
  import { AuthenticationService } from '../services/authentication.js';
10
9
  import { UsersService } from '../services/users.js';
@@ -12,6 +11,8 @@ import asyncHandler from '../utils/async-handler.js';
12
11
  import { getAuthProviders } from '../utils/get-auth-providers.js';
13
12
  import { getIPFromReq } from '../utils/get-ip-from-req.js';
14
13
  const router = Router();
14
+ const env = useEnv();
15
+ const logger = useLogger();
15
16
  const authProviders = getAuthProviders();
16
17
  for (const authProvider of authProviders) {
17
18
  let authRouter;
@@ -1,14 +1,15 @@
1
- import { ForbiddenError, RouteNotFoundError } from '@directus/errors';
1
+ import { useEnv } from '@directus/env';
2
+ import { ErrorCode, ForbiddenError, RouteNotFoundError, isDirectusError } from '@directus/errors';
2
3
  import express from 'express';
3
- import env from '../env.js';
4
4
  import { getExtensionManager } from '../extensions/index.js';
5
5
  import { respond } from '../middleware/respond.js';
6
6
  import useCollection from '../middleware/use-collection.js';
7
- import { ExtensionsService } from '../services/extensions.js';
7
+ import { ExtensionReadError, ExtensionsService } from '../services/extensions.js';
8
8
  import asyncHandler from '../utils/async-handler.js';
9
9
  import { getCacheControlHeader } from '../utils/get-cache-headers.js';
10
10
  import { getMilliseconds } from '../utils/get-milliseconds.js';
11
11
  const router = express.Router();
12
+ const env = useEnv();
12
13
  router.use(useCollection('directus_extensions'));
13
14
  router.get('/', asyncHandler(async (req, res, next) => {
14
15
  const service = new ExtensionsService({
@@ -29,9 +30,20 @@ router.patch('/:bundleOrName/:name?', asyncHandler(async (req, res, next) => {
29
30
  if (bundle === undefined || !name) {
30
31
  throw new ForbiddenError();
31
32
  }
32
- await service.updateOne(bundle, name, req.body);
33
- const updated = await service.readOne(bundle, name);
34
- res.locals['payload'] = { data: updated || null };
33
+ try {
34
+ const result = await service.updateOne(bundle, name, req.body);
35
+ res.locals['payload'] = { data: result || null };
36
+ }
37
+ catch (error) {
38
+ let finalError = error;
39
+ if (error instanceof ExtensionReadError) {
40
+ finalError = error.originalError;
41
+ if (isDirectusError(finalError, ErrorCode.Forbidden)) {
42
+ return next();
43
+ }
44
+ }
45
+ throw finalError;
46
+ }
35
47
  return next();
36
48
  }), respond);
37
49
  router.get('/sources/:chunk', asyncHandler(async (req, res) => {
@@ -1,4 +1,5 @@
1
- import { isDirectusError } from '@directus/errors';
1
+ import { useEnv } from '@directus/env';
2
+ import { ErrorCode, InvalidPayloadError, isDirectusError } from '@directus/errors';
2
3
  import formatTitle from '@directus/format-title';
3
4
  import { toArray } from '@directus/utils';
4
5
  import Busboy from 'busboy';
@@ -7,8 +8,6 @@ import express from 'express';
7
8
  import Joi from 'joi';
8
9
  import { minimatch } from 'minimatch';
9
10
  import path from 'path';
10
- import env from '../env.js';
11
- import { ErrorCode, InvalidPayloadError } from '@directus/errors';
12
11
  import { respond } from '../middleware/respond.js';
13
12
  import useCollection from '../middleware/use-collection.js';
14
13
  import { validateBatch } from '../middleware/validate-batch.js';
@@ -17,6 +16,7 @@ import { MetaService } from '../services/meta.js';
17
16
  import asyncHandler from '../utils/async-handler.js';
18
17
  import { sanitizeQuery } from '../utils/sanitize-query.js';
19
18
  const router = express.Router();
19
+ const env = useEnv();
20
20
  router.use(useCollection('directus_files'));
21
21
  export const multipartHandler = (req, res, next) => {
22
22
  if (req.is('multipart/form-data') === false)
@@ -1,6 +1,5 @@
1
- import { isDirectusError } from '@directus/errors';
1
+ import { ErrorCode, isDirectusError } from '@directus/errors';
2
2
  import express from 'express';
3
- import { ErrorCode } from '@directus/errors';
4
3
  import { respond } from '../middleware/respond.js';
5
4
  import useCollection from '../middleware/use-collection.js';
6
5
  import { validateBatch } from '../middleware/validate-batch.js';
@@ -152,4 +151,14 @@ router.delete('/:pk', asyncHandler(async (req, _res, next) => {
152
151
  await service.deleteOne(req.params['pk']);
153
152
  return next();
154
153
  }), respond);
154
+ router.get('/me/:collection/:pk?', asyncHandler(async (req, res, next) => {
155
+ const { collection, pk } = req.params;
156
+ const service = new PermissionsService({
157
+ accountability: req.accountability,
158
+ schema: req.schema,
159
+ });
160
+ const itemPermissions = await service.getItemPermissions(collection, pk);
161
+ res.locals['payload'] = { data: itemPermissions };
162
+ return next();
163
+ }), respond);
155
164
  export default router;
@@ -1,9 +1,9 @@
1
+ import { InvalidPayloadError, UnsupportedMediaTypeError } from '@directus/errors';
1
2
  import { parseJSON } from '@directus/utils';
2
3
  import Busboy from 'busboy';
3
4
  import express from 'express';
4
5
  import { load as loadYaml } from 'js-yaml';
5
- import { InvalidPayloadError, UnsupportedMediaTypeError } from '@directus/errors';
6
- import logger from '../logger.js';
6
+ import { useLogger } from '../logger.js';
7
7
  import { respond } from '../middleware/respond.js';
8
8
  import { SchemaService } from '../services/schema.js';
9
9
  import asyncHandler from '../utils/async-handler.js';
@@ -36,6 +36,7 @@ const schemaMultipartHandler = (req, res, next) => {
36
36
  let isFileIncluded = false;
37
37
  let upload = null;
38
38
  busboy.on('file', async (_, fileStream, { mimeType }) => {
39
+ const logger = useLogger();
39
40
  if (isFileIncluded)
40
41
  return next(new InvalidPayloadError({ reason: `More than one file was included in the body` }));
41
42
  isFileIncluded = true;
@@ -1,9 +1,8 @@
1
- import { isDirectusError } from '@directus/errors';
1
+ import { useEnv } from '@directus/env';
2
+ import { ErrorCode, InvalidPayloadError, isDirectusError } from '@directus/errors';
2
3
  import express from 'express';
3
4
  import Joi from 'joi';
4
5
  import { COOKIE_OPTIONS, UUID_REGEX } from '../constants.js';
5
- import env from '../env.js';
6
- import { ErrorCode, InvalidPayloadError } from '@directus/errors';
7
6
  import { respond } from '../middleware/respond.js';
8
7
  import useCollection from '../middleware/use-collection.js';
9
8
  import { validateBatch } from '../middleware/validate-batch.js';
@@ -11,6 +10,7 @@ import { SharesService } from '../services/shares.js';
11
10
  import asyncHandler from '../utils/async-handler.js';
12
11
  import { sanitizeQuery } from '../utils/sanitize-query.js';
13
12
  const router = express.Router();
13
+ const env = useEnv();
14
14
  router.use(useCollection('directus_shares'));
15
15
  const sharedLoginSchema = Joi.object({
16
16
  share: Joi.string().required(),
@@ -1,13 +1,11 @@
1
+ import { InvalidPayloadError, InvalidQueryError, UnsupportedMediaTypeError } from '@directus/errors';
1
2
  import argon2 from 'argon2';
2
3
  import Busboy from 'busboy';
3
4
  import { Router } from 'express';
4
5
  import Joi from 'joi';
5
- import fs from 'node:fs';
6
- import { createRequire } from 'node:module';
7
- import { InvalidPayloadError, InvalidQueryError, UnsupportedMediaTypeError } from '@directus/errors';
8
6
  import collectionExists from '../middleware/collection-exists.js';
9
7
  import { respond } from '../middleware/respond.js';
10
- import { ExportService } from '../services/import-export/index.js';
8
+ import { ExportService, ImportService } from '../services/import-export.js';
11
9
  import { RevisionsService } from '../services/revisions.js';
12
10
  import { UtilsService } from '../services/utils.js';
13
11
  import asyncHandler from '../utils/async-handler.js';
@@ -66,6 +64,10 @@ router.post('/import/:collection', collectionExists, asyncHandler(async (req, re
66
64
  if (req.is('multipart/form-data') === false) {
67
65
  throw new UnsupportedMediaTypeError({ mediaType: req.headers['content-type'], where: 'Content-Type header' });
68
66
  }
67
+ const service = new ImportService({
68
+ accountability: req.accountability,
69
+ schema: req.schema,
70
+ });
69
71
  let headers;
70
72
  if (req.headers['content-type']) {
71
73
  headers = req.headers;
@@ -78,34 +80,13 @@ router.post('/import/:collection', collectionExists, asyncHandler(async (req, re
78
80
  }
79
81
  const busboy = Busboy({ headers });
80
82
  busboy.on('file', async (_fieldname, fileStream, { mimeType }) => {
81
- const { createTmpFile } = await import('@directus/utils/node');
82
- const { getWorkerPool } = await import('../worker-pool.js');
83
- const tmpFile = await createTmpFile().catch(() => null);
84
- if (!tmpFile)
85
- throw new Error('Failed to create temporary file for import');
86
- fileStream.pipe(fs.createWriteStream(tmpFile.path));
87
- fileStream.on('end', async () => {
88
- const workerPool = getWorkerPool();
89
- const require = createRequire(import.meta.url);
90
- const filename = require.resolve('../services/import-export/import-worker');
91
- const workerData = {
92
- collection: req.params['collection'],
93
- mimeType,
94
- filePath: tmpFile.path,
95
- accountability: req.accountability,
96
- schema: req.schema,
97
- };
98
- try {
99
- await workerPool.run(workerData, { filename });
100
- res.status(200).end();
101
- }
102
- catch (error) {
103
- next(error);
104
- }
105
- finally {
106
- await tmpFile.cleanup();
107
- }
108
- });
83
+ try {
84
+ await service.import(req.params['collection'], mimeType, fileStream);
85
+ }
86
+ catch (err) {
87
+ return next(err);
88
+ }
89
+ return res.status(200).end();
109
90
  });
110
91
  busboy.on('error', (err) => next(err));
111
92
  req.pipe(busboy);
@@ -6,7 +6,7 @@ import * as geometryHelpers from './geometry/index.js';
6
6
  import * as schemaHelpers from './schema/index.js';
7
7
  import * as sequenceHelpers from './sequence/index.js';
8
8
  export declare function getHelpers(database: Knex): {
9
- date: dateHelpers.mysql | dateHelpers.postgres | dateHelpers.mssql | dateHelpers.sqlite | dateHelpers.oracle;
9
+ date: dateHelpers.postgres | dateHelpers.oracle | dateHelpers.mysql | dateHelpers.mssql | dateHelpers.sqlite;
10
10
  st: geometryHelpers.mysql | geometryHelpers.postgres | geometryHelpers.mssql | geometryHelpers.sqlite | geometryHelpers.oracle | geometryHelpers.redshift;
11
11
  schema: schemaHelpers.mysql | schemaHelpers.cockroachdb | schemaHelpers.mssql | schemaHelpers.postgres | schemaHelpers.sqlite | schemaHelpers.oracle;
12
12
  sequence: sequenceHelpers.mysql | sequenceHelpers.postgres;
@@ -1,3 +1,4 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { createInspector } from '@directus/schema';
2
3
  import fse from 'fs-extra';
3
4
  import knex from 'knex';
@@ -7,9 +8,8 @@ import { fileURLToPath } from 'node:url';
7
8
  import path from 'path';
8
9
  import { performance } from 'perf_hooks';
9
10
  import { promisify } from 'util';
10
- import env from '../env.js';
11
11
  import { getExtensionsPath } from '../extensions/lib/get-extensions-path.js';
12
- import logger from '../logger.js';
12
+ import { useLogger } from '../logger.js';
13
13
  import { getConfigFromEnv } from '../utils/get-config-from-env.js';
14
14
  import { validateEnv } from '../utils/validate-env.js';
15
15
  import { getHelpers } from './helpers/index.js';
@@ -22,6 +22,8 @@ export function getDatabase() {
22
22
  if (database) {
23
23
  return database;
24
24
  }
25
+ const env = useEnv();
26
+ const logger = useLogger();
25
27
  const { client, version, searchPath, connectionString, pool: poolConfig = {}, ...connectionConfig } = getConfigFromEnv('DB_', ['DB_EXCLUDE_TABLES']);
26
28
  const requiredEnvVars = ['DB_CLIENT'];
27
29
  switch (client) {
@@ -165,6 +167,7 @@ export async function hasDatabaseConnection(database) {
165
167
  }
166
168
  export async function validateDatabaseConnection(database) {
167
169
  database = database ?? getDatabase();
170
+ const logger = useLogger();
168
171
  try {
169
172
  if (getDatabaseClient(database) === 'oracle') {
170
173
  await database.raw('select 1 from DUAL');
@@ -209,6 +212,7 @@ export async function isInstalled() {
209
212
  }
210
213
  export async function validateMigrations() {
211
214
  const database = getDatabase();
215
+ const logger = useLogger();
212
216
  try {
213
217
  let migrationFiles = await fse.readdir(path.join(__dirname, 'migrations'));
214
218
  const customMigrationsPath = path.resolve(getExtensionsPath(), 'migrations');
@@ -234,6 +238,7 @@ export async function validateDatabaseExtensions() {
234
238
  const client = getDatabaseClient(database);
235
239
  const helpers = getHelpers(database);
236
240
  const geometrySupport = await helpers.st.supported();
241
+ const logger = useLogger();
237
242
  if (!geometrySupport) {
238
243
  switch (client) {
239
244
  case 'postgres':
@@ -248,7 +253,9 @@ export async function validateDatabaseExtensions() {
248
253
  }
249
254
  }
250
255
  async function validateDatabaseCharset(database) {
256
+ const env = useEnv();
251
257
  database = database ?? getDatabase();
258
+ const logger = useLogger();
252
259
  if (getDatabaseClient(database) === 'mysql') {
253
260
  const { collation } = await database.select(database.raw(`@@collation_database as collation`)).first();
254
261
  const tables = await database('information_schema.tables')
@@ -1,7 +1,8 @@
1
1
  import { createInspector } from '@directus/schema';
2
- import logger from '../../logger.js';
2
+ import { useLogger } from '../../logger.js';
3
3
  import { getDefaultIndexName } from '../../utils/get-default-index-name.js';
4
4
  export async function up(knex) {
5
+ const logger = useLogger();
5
6
  const inspector = createInspector(knex);
6
7
  const foreignKeys = await inspector.foreignKeys();
7
8
  const relations = await knex
@@ -87,6 +88,7 @@ export async function up(knex) {
87
88
  }
88
89
  }
89
90
  export async function down(knex) {
91
+ const logger = useLogger();
90
92
  const relations = await knex
91
93
  .select('many_collection', 'many_field', 'one_collection')
92
94
  .from('directus_relations');
@@ -1,5 +1,5 @@
1
1
  import { createInspector } from '@directus/schema';
2
- import logger from '../../logger.js';
2
+ import { useLogger } from '../../logger.js';
3
3
  /**
4
4
  * Things to keep in mind:
5
5
  *
@@ -77,6 +77,7 @@ const updates = [
77
77
  },
78
78
  ];
79
79
  export async function up(knex) {
80
+ const logger = useLogger();
80
81
  const inspector = createInspector(knex);
81
82
  const foreignKeys = await inspector.foreignKeys();
82
83
  for (const update of updates) {
@@ -123,6 +124,7 @@ export async function up(knex) {
123
124
  }
124
125
  }
125
126
  export async function down(knex) {
127
+ const logger = useLogger();
126
128
  for (const update of updates) {
127
129
  for (const constraint of update.constraints) {
128
130
  try {
@@ -1,6 +1,7 @@
1
1
  import { parseJSON } from '@directus/utils';
2
- import logger from '../../logger.js';
2
+ import { useLogger } from '../../logger.js';
3
3
  export async function up(knex) {
4
+ const logger = useLogger();
4
5
  const dividerGroups = await knex.select('*').from('directus_fields').where('interface', '=', 'group-divider');
5
6
  for (const dividerGroup of dividerGroups) {
6
7
  const newOptions = { showHeader: true };
@@ -1,5 +1,5 @@
1
1
  import { createInspector } from '@directus/schema';
2
- import logger from '../../logger.js';
2
+ import { useLogger } from '../../logger.js';
3
3
  import { getHelpers } from '../helpers/index.js';
4
4
  export async function up(knex) {
5
5
  const helper = getHelpers(knex).schema;
@@ -33,6 +33,7 @@ export async function down(knex) {
33
33
  * Temporarily drop foreign key constraint for MySQL instances, see https://github.com/directus/directus/issues/19399
34
34
  */
35
35
  async function dropConstraint(knex) {
36
+ const logger = useLogger();
36
37
  const inspector = createInspector(knex);
37
38
  const foreignKeys = await inspector.foreignKeys('directus_shares');
38
39
  const collectionForeignKeys = foreignKeys.filter((fk) => fk.column === 'collection');
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,12 @@
1
+ export async function up(knex) {
2
+ await knex.schema.alterTable('directus_files', (table) => {
3
+ table.integer('focal_point_x').nullable();
4
+ table.integer('focal_point_y').nullable();
5
+ });
6
+ }
7
+ export async function down(knex) {
8
+ await knex.schema.alterTable('directus_files', (table) => {
9
+ table.dropColumn('focal_point_x');
10
+ table.dropColumn('focal_point_y');
11
+ });
12
+ }
@@ -6,10 +6,11 @@ import { fileURLToPath } from 'node:url';
6
6
  import path from 'path';
7
7
  import { flushCaches } from '../../cache.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 getModuleDefault from '../../utils/get-module-default.js';
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
12
  export default async function run(database, direction, log = true) {
13
+ const logger = useLogger();
13
14
  let migrationFiles = await fse.readdir(__dirname);
14
15
  const customMigrationsPath = path.resolve(getExtensionsPath(), 'migrations');
15
16
  let customMigrationFiles = ((await fse.pathExists(customMigrationsPath)) && (await fse.readdir(customMigrationsPath))) || [];
@@ -1,13 +1,13 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { toArray } from '@directus/utils';
2
3
  import { clone, cloneDeep, isNil, merge, pick, uniq } from 'lodash-es';
3
- import { getHelpers } from './helpers/index.js';
4
- import env from '../env.js';
5
4
  import { PayloadService } from '../services/payload.js';
6
5
  import { applyFunctionToColumnName } from '../utils/apply-function-to-column-name.js';
7
6
  import applyQuery, { applyLimit, applySort, generateAlias } from '../utils/apply-query.js';
8
7
  import { getCollectionFromAlias } from '../utils/get-collection-from-alias.js';
9
8
  import { getColumn } from '../utils/get-column.js';
10
9
  import { stripFunction } from '../utils/strip-function.js';
10
+ import { getHelpers } from './helpers/index.js';
11
11
  import getDatabase from './index.js';
12
12
  /**
13
13
  * Execute a given AST using Knex. Returns array of items based on requested AST.
@@ -26,6 +26,7 @@ export default async function runAST(originalAST, schema, options) {
26
26
  return await run(ast.name, ast.children, options?.query || ast.query);
27
27
  }
28
28
  async function run(collection, children, query) {
29
+ const env = useEnv();
29
30
  // Retrieve the database columns to select in the current AST
30
31
  const { fieldNodes, primaryKeyField, nestedCollectionNodes } = await parseCurrentLevel(schema, collection, children, query);
31
32
  // The actual knex query builder instance. This is a promise that resolves with the raw items from the db
@@ -148,6 +149,7 @@ function getColumnPreprocessor(knex, schema, table) {
148
149
  };
149
150
  }
150
151
  async function getDBQuery(schema, knex, table, fieldNodes, query) {
152
+ const env = useEnv();
151
153
  const preProcess = getColumnPreprocessor(knex, schema, table);
152
154
  const queryCopy = clone(query);
153
155
  const helpers = getHelpers(knex);
@@ -296,6 +298,7 @@ function applyParentFilters(schema, nestedCollectionNodes, parentItem) {
296
298
  return nestedCollectionNodes;
297
299
  }
298
300
  function mergeWithParentItems(schema, nestedItem, parentItem, nestedNode) {
301
+ const env = useEnv();
299
302
  const nestedItems = toArray(nestedItem);
300
303
  const parentItems = clone(toArray(parentItem));
301
304
  if (nestedNode.type === 'm2o') {
@@ -102,13 +102,6 @@
102
102
  - appearance
103
103
  - theme_light
104
104
  - theme_dark
105
- - theme_light_overrides
106
- - theme_dark_overrides
107
105
  - tfa_secret
108
106
  - status
109
107
  - role
110
-
111
- # This is a temporary allowed field to help people migrate from
112
- # 10.6 to 10.7 and should be removed in 10.8
113
- # @TODO remove
114
- - theme
@@ -46,6 +46,22 @@ fields:
46
46
  width: half
47
47
  readonly: true
48
48
 
49
+ - field: focal_point_divider
50
+ interface: presentation-divider
51
+ options:
52
+ icon: image_search
53
+ title: $t:field_options.directus_files.focal_point_divider
54
+ special:
55
+ - alias
56
+ - no-data
57
+ width: full
58
+
59
+ - field: focal_point_x
60
+ width: half
61
+
62
+ - field: focal_point_y
63
+ width: half
64
+
49
65
  - field: storage_divider
50
66
  interface: presentation-divider
51
67
  options:
package/dist/emitter.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import ee2 from 'eventemitter2';
2
2
  import getDatabase from './database/index.js';
3
- import logger from './logger.js';
3
+ import { useLogger } from './logger.js';
4
4
  export class Emitter {
5
5
  filterEmitter;
6
6
  actionEmitter;
@@ -42,6 +42,7 @@ export class Emitter {
42
42
  return updatedPayload;
43
43
  }
44
44
  emitAction(event, meta, context = null) {
45
+ const logger = useLogger();
45
46
  const events = Array.isArray(event) ? event : [event];
46
47
  for (const event of events) {
47
48
  this.actionEmitter.emitAsync(event, { event, ...meta }, context ?? this.getDefaultContext()).catch((err) => {
@@ -51,6 +52,7 @@ export class Emitter {
51
52
  }
52
53
  }
53
54
  async emitInit(event, meta) {
55
+ const logger = useLogger();
54
56
  try {
55
57
  await this.initEmitter.emitAsync(event, { event, ...meta });
56
58
  }
@@ -1 +1 @@
1
- export declare const getExtensionsPath: () => any;
1
+ export declare const getExtensionsPath: () => string;
@@ -1,6 +1,7 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { join } from 'path';
2
- import env from '../../env.js';
3
3
  export const getExtensionsPath = () => {
4
+ const env = useEnv();
4
5
  if (env['EXTENSIONS_LOCATION']) {
5
6
  return join(env['TEMP_PATH'], 'extensions');
6
7
  }
@@ -1 +1 @@
1
- export declare const getExtensions: () => Promise<import("@directus/extensions/node").Extension[]>;
1
+ export declare const getExtensions: () => Promise<any[]>;
@@ -1,12 +1,36 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { getLocalExtensions, getPackageExtensions, resolvePackageExtensions } from '@directus/extensions/node';
2
- import env from '../../env.js';
3
+ import { useLogger } from '../../logger.js';
3
4
  import { getExtensionsPath } from './get-extensions-path.js';
4
5
  export const getExtensions = async () => {
5
- const localExtensions = await getLocalExtensions(getExtensionsPath());
6
- const loadedNames = localExtensions.map(({ name }) => name);
7
- const filterDuplicates = ({ name }) => loadedNames.includes(name) === false;
8
- const localPackageExtensions = (await resolvePackageExtensions(getExtensionsPath())).filter((extension) => filterDuplicates(extension));
9
- loadedNames.push(...localPackageExtensions.map(({ name }) => name));
10
- const packageExtensions = (await getPackageExtensions(env['PACKAGE_FILE_LOCATION'])).filter((extension) => filterDuplicates(extension));
11
- return [...packageExtensions, ...localPackageExtensions, ...localExtensions];
6
+ const env = useEnv();
7
+ const logger = useLogger();
8
+ const loadedExtensions = new Map();
9
+ const duplicateExtensions = [];
10
+ const filterDuplicates = (extension) => {
11
+ const isExistingExtension = loadedExtensions.has(extension.name);
12
+ if (isExistingExtension) {
13
+ duplicateExtensions.push(extension.name);
14
+ return;
15
+ }
16
+ if (extension.type === 'bundle') {
17
+ const bundleEntryNames = new Set();
18
+ for (const entry of extension.entries) {
19
+ if (bundleEntryNames.has(entry.name)) {
20
+ // Do not load entire bundle if it has duplicated entries
21
+ duplicateExtensions.push(extension.name);
22
+ return;
23
+ }
24
+ bundleEntryNames.add(entry.name);
25
+ }
26
+ }
27
+ loadedExtensions.set(extension.name, extension);
28
+ };
29
+ (await getLocalExtensions(getExtensionsPath())).forEach(filterDuplicates);
30
+ (await resolvePackageExtensions(getExtensionsPath())).forEach(filterDuplicates);
31
+ (await getPackageExtensions(env['PACKAGE_FILE_LOCATION'])).forEach(filterDuplicates);
32
+ if (duplicateExtensions.length > 0) {
33
+ logger.warn(`Failed to load the following extensions because they have/contain duplicate names: ${duplicateExtensions.join(', ')}`);
34
+ }
35
+ return Array.from(loadedExtensions.values());
12
36
  };
@@ -1,14 +1,16 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { resolvePackage } from '@directus/utils/node';
2
3
  import { escapeRegExp } from 'lodash-es';
3
4
  import { readdir } from 'node:fs/promises';
4
- import path from 'path';
5
- import env from '../../env.js';
6
- import logger from '../../logger.js';
7
- import { Url } from '../../utils/url.js';
8
5
  import { dirname } from 'node:path';
9
6
  import { fileURLToPath } from 'node:url';
7
+ import path from 'path';
8
+ import { useLogger } from '../../logger.js';
9
+ import { Url } from '../../utils/url.js';
10
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
11
  export const getSharedDepsMapping = async (deps) => {
12
+ const env = useEnv();
13
+ const logger = useLogger();
12
14
  const appDir = await readdir(path.join(resolvePackage('@directus/app', __dirname), 'dist', 'assets'));
13
15
  const depsMapping = {};
14
16
  for (const dep of deps) {
@@ -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 async function callReference(fn, args) {
4
+ const env = useEnv();
5
+ const logger = useLogger();
4
6
  const sandboxTimeout = Number(env['EXTENSIONS_SANDBOX_TIMEOUT']);
5
7
  try {
6
8
  return await fn.apply(undefined, args, {
@@ -1,5 +1,6 @@
1
- import logger from '../../../../../logger.js';
1
+ import { useLogger } from '../../../../../logger.js';
2
2
  export function logGenerator(requestedScopes) {
3
+ const logger = useLogger();
3
4
  return (message) => {
4
5
  if (requestedScopes.log === undefined)
5
6
  throw new Error('No permission to access "log"');
@@ -1,3 +1,4 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { NESTED_EXTENSION_TYPES } from '@directus/extensions';
2
3
  import { ensureExtensionDirs } from '@directus/extensions/node';
3
4
  import mid from 'node-machine-id';
@@ -6,19 +7,20 @@ import { mkdir } from 'node:fs/promises';
6
7
  import { dirname, join, relative, resolve, sep } from 'node:path';
7
8
  import { pipeline } from 'node:stream/promises';
8
9
  import Queue from 'p-queue';
9
- import env from '../../env.js';
10
- import logger from '../../logger.js';
11
- import { getMessenger } from '../../messenger.js';
10
+ import { useBus } from '../../bus/index.js';
11
+ import { useLogger } from '../../logger.js';
12
12
  import { getStorage } from '../../storage/index.js';
13
13
  import { getExtensionsPath } from './get-extensions-path.js';
14
14
  import { SyncStatus, getSyncStatus, setSyncStatus } from './sync-status.js';
15
15
  export const syncExtensions = async () => {
16
+ const env = useEnv();
17
+ const logger = useLogger();
16
18
  const extensionsPath = getExtensionsPath();
17
19
  if (!env['EXTENSIONS_LOCATION']) {
18
20
  // Safe to run with multiple instances since dirs are created with `recursive: true`
19
21
  return ensureExtensionDirs(extensionsPath, NESTED_EXTENSION_TYPES);
20
22
  }
21
- const messenger = getMessenger();
23
+ const messenger = useBus();
22
24
  const isPrimaryProcess = String(process.env['NODE_APP_INSTANCE']) === '0' || process.env['NODE_APP_INSTANCE'] === undefined;
23
25
  const id = await mid.machineId();
24
26
  const message = `extensions-sync/${id}`;