@directus/api 32.1.0 → 32.2.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 (175) hide show
  1. package/dist/ai/chat/constants/system-prompt.d.ts +1 -0
  2. package/dist/ai/chat/constants/system-prompt.js +51 -0
  3. package/dist/ai/chat/controllers/chat.post.d.ts +2 -0
  4. package/dist/ai/chat/controllers/chat.post.js +47 -0
  5. package/dist/ai/chat/lib/create-ui-stream.d.ts +15 -0
  6. package/dist/ai/chat/lib/create-ui-stream.js +42 -0
  7. package/dist/ai/chat/middleware/load-settings.d.ts +2 -0
  8. package/dist/ai/chat/middleware/load-settings.js +18 -0
  9. package/dist/ai/chat/models/chat-request.d.ts +34 -0
  10. package/dist/ai/chat/models/chat-request.js +26 -0
  11. package/dist/ai/chat/models/providers.d.ts +9 -0
  12. package/dist/ai/chat/models/providers.js +9 -0
  13. package/dist/ai/chat/router.d.ts +1 -0
  14. package/dist/ai/chat/router.js +5 -0
  15. package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.d.ts +9 -0
  16. package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.js +38 -0
  17. package/dist/ai/chat/utils/fix-error-tool-calls.d.ts +12 -0
  18. package/dist/ai/chat/utils/fix-error-tool-calls.js +30 -0
  19. package/dist/ai/chat/utils/parse-json-schema-7.d.ts +13 -0
  20. package/dist/ai/chat/utils/parse-json-schema-7.js +75 -0
  21. package/dist/{mcp → ai/mcp}/server.d.ts +13 -16
  22. package/dist/{mcp → ai/mcp}/server.js +4 -13
  23. package/dist/ai/mcp/types.d.ts +15 -0
  24. package/dist/{mcp/tools/assets.js → ai/tools/assets/index.js} +8 -5
  25. package/dist/{mcp/tools/collections.js → ai/tools/collections/index.js} +7 -4
  26. package/dist/{mcp/tools/fields.js → ai/tools/fields/index.js} +12 -9
  27. package/dist/{mcp/tools/files.js → ai/tools/files/index.js} +11 -5
  28. package/dist/{mcp/tools/flows.js → ai/tools/flows/index.js} +11 -5
  29. package/dist/{mcp/tools/folders.js → ai/tools/folders/index.js} +12 -5
  30. package/dist/ai/tools/index.d.ts +15 -0
  31. package/dist/ai/tools/index.js +29 -0
  32. package/dist/{mcp/tools/items.js → ai/tools/items/index.js} +13 -6
  33. package/dist/{mcp/tools/prompts/items.md → ai/tools/items/prompt.md} +19 -15
  34. package/dist/{mcp/tools/operations.d.ts → ai/tools/operations/index.d.ts} +46 -0
  35. package/dist/{mcp/tools/operations.js → ai/tools/operations/index.js} +12 -5
  36. package/dist/{mcp/tools/relations.js → ai/tools/relations/index.js} +7 -4
  37. package/dist/{mcp/tools/schema.d.ts → ai/tools/schema/index.d.ts} +1 -1
  38. package/dist/{mcp/tools/schema.js → ai/tools/schema/index.js} +9 -6
  39. package/dist/{mcp/tools/system.js → ai/tools/system/index.js} +7 -4
  40. package/dist/{mcp/tools/trigger-flow.js → ai/tools/trigger-flow/index.js} +8 -5
  41. package/dist/{mcp → ai/tools}/types.d.ts +1 -17
  42. package/dist/ai/tools/utils.d.ts +9 -0
  43. package/dist/ai/tools/utils.js +17 -0
  44. package/dist/app.js +5 -0
  45. package/dist/auth/drivers/oauth2.d.ts +2 -1
  46. package/dist/auth/drivers/oauth2.js +17 -22
  47. package/dist/auth/drivers/openid.d.ts +2 -1
  48. package/dist/auth/drivers/openid.js +13 -18
  49. package/dist/auth/drivers/saml.js +6 -3
  50. package/dist/controllers/assets.js +39 -2
  51. package/dist/controllers/mcp.js +1 -1
  52. package/dist/database/migrations/20240806A-permissions-policies.js +2 -2
  53. package/dist/database/migrations/20251103A-add-ai-settings.d.ts +3 -0
  54. package/dist/database/migrations/20251103A-add-ai-settings.js +14 -0
  55. package/dist/database/run-ast/run-ast.js +1 -1
  56. package/dist/extensions/lib/installation/manager.js +5 -9
  57. package/dist/extensions/lib/sync/status.d.ts +11 -0
  58. package/dist/extensions/lib/sync/status.js +34 -0
  59. package/dist/extensions/lib/sync/sync.d.ts +6 -0
  60. package/dist/extensions/lib/sync/sync.js +90 -0
  61. package/dist/extensions/lib/sync/tracker.d.ts +18 -0
  62. package/dist/extensions/lib/sync/tracker.js +71 -0
  63. package/dist/extensions/lib/sync/utils.d.ts +24 -0
  64. package/dist/extensions/lib/sync/utils.js +62 -0
  65. package/dist/extensions/manager.d.ts +8 -4
  66. package/dist/extensions/manager.js +30 -13
  67. package/dist/middleware/respond.js +2 -2
  68. package/dist/permissions/lib/fetch-policies.d.ts +1 -1
  69. package/dist/permissions/lib/fetch-roles-tree.d.ts +6 -3
  70. package/dist/permissions/lib/fetch-roles-tree.js +5 -27
  71. package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +9 -7
  72. package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +17 -9
  73. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +1 -1
  74. package/dist/permissions/utils/fetch-raw-permissions.d.ts +1 -1
  75. package/dist/permissions/utils/fetch-share-info.d.ts +1 -1
  76. package/dist/permissions/utils/fetch-share-info.js +1 -1
  77. package/dist/permissions/utils/filter-policies-by-ip.js +1 -1
  78. package/dist/permissions/utils/get-permissions-for-share.js +8 -8
  79. package/dist/permissions/utils/with-cache.d.ts +8 -6
  80. package/dist/permissions/utils/with-cache.js +12 -10
  81. package/dist/request/is-denied-ip.js +2 -2
  82. package/dist/services/assets/name-deduper.d.ts +7 -0
  83. package/dist/services/assets/name-deduper.js +23 -0
  84. package/dist/services/assets.d.ts +15 -2
  85. package/dist/services/assets.js +98 -5
  86. package/dist/services/authentication.js +4 -4
  87. package/dist/services/comments.js +2 -2
  88. package/dist/services/extensions.js +4 -0
  89. package/dist/services/folders.d.ts +27 -2
  90. package/dist/services/folders.js +75 -0
  91. package/dist/services/graphql/resolvers/query.js +1 -1
  92. package/dist/services/import-export.d.ts +1 -1
  93. package/dist/services/import-export.js +4 -5
  94. package/dist/services/notifications.js +2 -2
  95. package/dist/services/payload.js +20 -0
  96. package/dist/services/roles.js +2 -2
  97. package/dist/services/tus/server.js +3 -3
  98. package/dist/telemetry/utils/get-settings.d.ts +15 -0
  99. package/dist/telemetry/utils/get-settings.js +25 -9
  100. package/dist/test-utils/README.md +95 -24
  101. package/dist/test-utils/cache.d.ts +2 -2
  102. package/dist/test-utils/cache.js +2 -2
  103. package/dist/test-utils/{fields-service.d.ts → services/fields-service.d.ts} +1 -1
  104. package/dist/test-utils/{fields-service.js → services/fields-service.js} +3 -2
  105. package/dist/test-utils/services/files-service.d.ts +28 -0
  106. package/dist/test-utils/services/files-service.js +34 -0
  107. package/dist/test-utils/services/folders-service.d.ts +28 -0
  108. package/dist/test-utils/services/folders-service.js +33 -0
  109. package/dist/utils/encrypt.d.ts +2 -0
  110. package/dist/utils/encrypt.js +64 -0
  111. package/dist/utils/get-accountability-for-role.js +2 -2
  112. package/dist/utils/get-accountability-for-token.js +4 -4
  113. package/dist/utils/get-cache-key.js +2 -2
  114. package/dist/utils/is-login-redirect-allowed.d.ts +4 -0
  115. package/dist/{auth/utils → utils}/is-login-redirect-allowed.js +8 -16
  116. package/dist/utils/require-text.d.ts +1 -0
  117. package/dist/utils/require-text.js +4 -0
  118. package/dist/utils/require-yaml.js +2 -2
  119. package/package.json +31 -25
  120. package/dist/auth/utils/generate-callback-url.d.ts +0 -8
  121. package/dist/auth/utils/generate-callback-url.js +0 -11
  122. package/dist/auth/utils/is-login-redirect-allowed.d.ts +0 -8
  123. package/dist/extensions/lib/sync-extensions.d.ts +0 -3
  124. package/dist/extensions/lib/sync-extensions.js +0 -70
  125. package/dist/extensions/lib/sync-status.d.ts +0 -10
  126. package/dist/extensions/lib/sync-status.js +0 -27
  127. package/dist/mcp/tools/index.d.ts +0 -15
  128. package/dist/mcp/tools/index.js +0 -29
  129. package/dist/mcp/tools/prompts/index.d.ts +0 -16
  130. package/dist/mcp/tools/prompts/index.js +0 -19
  131. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +0 -5
  132. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +0 -7
  133. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +0 -5
  134. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +0 -10
  135. package/dist/permissions/modules/fetch-global-access/types.d.ts +0 -4
  136. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +0 -4
  137. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +0 -27
  138. package/dist/utils/get-date-formatted.d.ts +0 -1
  139. package/dist/utils/get-date-formatted.js +0 -10
  140. package/dist/utils/ip-in-networks.d.ts +0 -6
  141. package/dist/utils/ip-in-networks.js +0 -13
  142. /package/dist/{mcp → ai/mcp}/index.d.ts +0 -0
  143. /package/dist/{mcp → ai/mcp}/index.js +0 -0
  144. /package/dist/{mcp → ai/mcp}/transport.d.ts +0 -0
  145. /package/dist/{mcp → ai/mcp}/transport.js +0 -0
  146. /package/dist/{mcp → ai/mcp}/types.js +0 -0
  147. /package/dist/{mcp/tools/assets.d.ts → ai/tools/assets/index.d.ts} +0 -0
  148. /package/dist/{mcp/tools/prompts/assets.md → ai/tools/assets/prompt.md} +0 -0
  149. /package/dist/{mcp/tools/collections.d.ts → ai/tools/collections/index.d.ts} +0 -0
  150. /package/dist/{mcp/tools/prompts/collections.md → ai/tools/collections/prompt.md} +0 -0
  151. /package/dist/{mcp/define.d.ts → ai/tools/define-tool.d.ts} +0 -0
  152. /package/dist/{mcp/define.js → ai/tools/define-tool.js} +0 -0
  153. /package/dist/{mcp/tools/fields.d.ts → ai/tools/fields/index.d.ts} +0 -0
  154. /package/dist/{mcp/tools/prompts/fields.md → ai/tools/fields/prompt.md} +0 -0
  155. /package/dist/{mcp/tools/files.d.ts → ai/tools/files/index.d.ts} +0 -0
  156. /package/dist/{mcp/tools/prompts/files.md → ai/tools/files/prompt.md} +0 -0
  157. /package/dist/{mcp/tools/flows.d.ts → ai/tools/flows/index.d.ts} +0 -0
  158. /package/dist/{mcp/tools/prompts/flows.md → ai/tools/flows/prompt.md} +0 -0
  159. /package/dist/{mcp/tools/folders.d.ts → ai/tools/folders/index.d.ts} +0 -0
  160. /package/dist/{mcp/tools/prompts/folders.md → ai/tools/folders/prompt.md} +0 -0
  161. /package/dist/{mcp/tools/items.d.ts → ai/tools/items/index.d.ts} +0 -0
  162. /package/dist/{mcp/tools/prompts/operations.md → ai/tools/operations/prompt.md} +0 -0
  163. /package/dist/{mcp/tools/relations.d.ts → ai/tools/relations/index.d.ts} +0 -0
  164. /package/dist/{mcp/tools/prompts/relations.md → ai/tools/relations/prompt.md} +0 -0
  165. /package/dist/{mcp/tools/prompts/schema.md → ai/tools/schema/prompt.md} +0 -0
  166. /package/dist/{mcp → ai/tools}/schema.d.ts +0 -0
  167. /package/dist/{mcp → ai/tools}/schema.js +0 -0
  168. /package/dist/{mcp/tools/system.d.ts → ai/tools/system/index.d.ts} +0 -0
  169. /package/dist/{mcp/tools/prompts/system-prompt-description.md → ai/tools/system/prompt-description.md} +0 -0
  170. /package/dist/{mcp/tools/prompts/system-prompt.md → ai/tools/system/prompt.md} +0 -0
  171. /package/dist/{mcp/tools/trigger-flow.d.ts → ai/tools/trigger-flow/index.d.ts} +0 -0
  172. /package/dist/{mcp/tools/prompts/trigger-flow.md → ai/tools/trigger-flow/prompt.md} +0 -0
  173. /package/dist/{permissions/modules/fetch-global-access → ai/tools}/types.js +0 -0
  174. /package/dist/test-utils/{items-service.d.ts → services/items-service.d.ts} +0 -0
  175. /package/dist/test-utils/{items-service.js → services/items-service.js} +0 -0
@@ -1,14 +1,30 @@
1
- import { toBoolean } from '@directus/utils';
1
+ import { SettingsService } from '../../services/settings.js';
2
+ import { getSchema } from '../../utils/get-schema.js';
2
3
  export const getSettings = async (db) => {
3
- const settings = await db
4
- .select('project_id', 'mcp_enabled', 'mcp_allow_deletes', 'mcp_system_prompt_enabled', 'visual_editor_urls')
5
- .from('directus_settings')
6
- .first();
4
+ const settingsService = new SettingsService({
5
+ knex: db,
6
+ schema: await getSchema({ database: db }),
7
+ });
8
+ const settings = (await settingsService.readSingleton({
9
+ fields: [
10
+ 'project_id',
11
+ 'mcp_enabled',
12
+ 'mcp_allow_deletes',
13
+ 'mcp_system_prompt_enabled',
14
+ 'visual_editor_urls',
15
+ 'ai_openai_api_key',
16
+ 'ai_anthropic_api_key',
17
+ 'ai_system_prompt',
18
+ ],
19
+ }));
7
20
  return {
8
21
  project_id: settings.project_id,
9
- mcp_enabled: toBoolean(settings?.mcp_enabled),
10
- mcp_allow_deletes: toBoolean(settings?.mcp_allow_deletes),
11
- mcp_system_prompt_enabled: toBoolean(settings?.mcp_system_prompt_enabled),
12
- visual_editor_urls: settings.visual_editor_urls ? JSON.parse(settings.visual_editor_urls).length : 0,
22
+ mcp_enabled: settings?.mcp_enabled || false,
23
+ mcp_allow_deletes: settings?.mcp_allow_deletes || false,
24
+ mcp_system_prompt_enabled: settings?.mcp_system_prompt_enabled || false,
25
+ visual_editor_urls: settings.visual_editor_urls?.length || 0,
26
+ ai_openai_api_key: Boolean(settings?.ai_openai_api_key),
27
+ ai_anthropic_api_key: Boolean(settings?.ai_anthropic_api_key),
28
+ ai_system_prompt: Boolean(settings?.ai_system_prompt),
13
29
  };
14
30
  };
@@ -14,21 +14,23 @@ This directory contains mock implementations for commonly used modules in servic
14
14
  - **[emitter.ts](#emitterts)** - Event emitter mocks
15
15
  - **[items-service.ts](#items-servicets)** - ItemsService mocks
16
16
  - **[fields-service.ts](#fields-servicets)** - FieldsService mocks
17
+ - **[files-service.ts](#files-servicets)** - FilesService mocks
18
+ - **[folders-service.ts](#folders-servicets)** - FoldersService mocks
17
19
  - **[test-helpers.ts](#test-helpersts)** - Test data factory functions
18
20
 
19
21
  ## Quick Start
20
22
 
21
23
  ```typescript
22
- import { createMockKnex, resetKnexMocks } from '../__mocks__/knex.js';
24
+ import { createMockKnex, resetKnexMocks } from '../test-utils/knex.js';
23
25
 
24
26
  // Set up mocks
25
27
  vi.mock('../../src/database/index', async () => {
26
- const { mockDatabase } = await import('../__mocks__/database.js');
28
+ const { mockDatabase } = await import('../test-utils/database.js');
27
29
  return mockDatabase();
28
30
  });
29
31
 
30
32
  vi.mock('../cache.js', async () => {
31
- const { mockCache } = await import('../__mocks__/cache.js');
33
+ const { mockCache } = await import('../test-utils/cache.js');
32
34
  return mockCache();
33
35
  });
34
36
 
@@ -203,13 +205,13 @@ Creates a standard database module mock for service tests.
203
205
  ```typescript
204
206
  // Standard PostgreSQL mock
205
207
  vi.mock('../../src/database/index', async () => {
206
- const { mockDatabase } = await import('../__mocks__/database.js');
208
+ const { mockDatabase } = await import('../test-utils/database.js');
207
209
  return mockDatabase();
208
210
  });
209
211
 
210
212
  // MySQL-specific mock
211
213
  vi.mock('../../src/database/index', async () => {
212
- const { mockDatabase } = await import('../__mocks__/database.js');
214
+ const { mockDatabase } = await import('../test-utils/database.js');
213
215
  return mockDatabase('mysql');
214
216
  });
215
217
 
@@ -229,7 +231,7 @@ transaction wrapper).
229
231
 
230
232
  ```typescript
231
233
  vi.mock('../utils/transaction.js', async () => {
232
- const { mockTransaction } = await import('../__mocks__/database.js');
234
+ const { mockTransaction } = await import('../test-utils/database.js');
233
235
  return mockTransaction();
234
236
  });
235
237
 
@@ -269,13 +271,13 @@ mocks for vi.mock() declarations and spies for testing cache behavior.
269
271
  ```typescript
270
272
  // Standard usage for vi.mock()
271
273
  vi.mock('../cache.js', async () => {
272
- const { mockCache } = await import('../__mocks__/cache.js');
274
+ const { mockCache } = await import('../test-utils/cache.js');
273
275
  return mockCache();
274
276
  });
275
277
 
276
278
  // Testing cache clearing with spies
277
279
  import { getCache } from '../cache.js';
278
- import { mockCache } from '../__mocks__/cache.js';
280
+ import { mockCache } from '../test-utils/cache.js';
279
281
 
280
282
  test('should clear cache after update', async () => {
281
283
  const { spies } = mockCache();
@@ -305,7 +307,7 @@ Creates a standard schema inspector mock with tableInfo, columnInfo, primary, fo
305
307
  ```typescript
306
308
  // Standard usage
307
309
  vi.mock('@directus/schema', async () => {
308
- const { mockSchema } = await import('../__mocks__/schema.js');
310
+ const { mockSchema } = await import('../test-utils/schema.js');
309
311
  return mockSchema();
310
312
  });
311
313
 
@@ -340,7 +342,7 @@ Creates a standard emitter mock with emitAction, emitFilter, emitInit, and event
340
342
  ```typescript
341
343
  // Standard usage
342
344
  vi.mock('../emitter.js', async () => {
343
- const { mockEmitter } = await import('../__mocks__/emitter.js');
345
+ const { mockEmitter } = await import('../test-utils/emitter.js');
344
346
  return mockEmitter();
345
347
  });
346
348
 
@@ -390,7 +392,7 @@ Creates a standard ItemsService mock with all CRUD methods pre-configured with s
390
392
  ```typescript
391
393
  // Standard usage
392
394
  vi.mock('./items.js', async () => {
393
- const { mockItemsService } = await import('../__mocks__/items-service.js');
395
+ const { mockItemsService } = await import('../test-utils/services/items-service.js');
394
396
  return mockItemsService();
395
397
  });
396
398
 
@@ -423,6 +425,8 @@ Creates a standard FieldsService mock with common methods pre-configured.
423
425
 
424
426
  **Mocked methods:**
425
427
 
428
+ In addition to the base `ItemsService` method the following `FieldsService` specific methods are available:
429
+
426
430
  - `addColumnToTable` → no-op function
427
431
  - `addColumnIndex` → resolves to undefined
428
432
  - `deleteField` → resolves to undefined
@@ -434,7 +438,7 @@ Creates a standard FieldsService mock with common methods pre-configured.
434
438
  ```typescript
435
439
  // Standard usage in CollectionsService tests
436
440
  vi.mock('./fields.js', async () => {
437
- const { mockFieldsService } = await import('../__mocks__/fields-service.js');
441
+ const { mockFieldsService } = await import('../test-utils/services/fields-service.js');
438
442
  return mockFieldsService();
439
443
  });
440
444
 
@@ -450,43 +454,110 @@ expect(addColumnIndexSpy).toHaveBeenCalled();
450
454
 
451
455
  ---
452
456
 
457
+ ### files-service.ts
458
+
459
+ Provides FilesService mocking utilities for testing services that depend on FilesService.
460
+
461
+ #### `mockFilesService()`
462
+
463
+ Creates a standard FilesService mock with common methods pre-configured.
464
+
465
+ **Returns:** Mock module object with `FilesService` class
466
+
467
+ **Mocked methods:**
468
+
469
+ In addition to the base `ItemsService` method the following `FilesService` specific methods are available:
470
+
471
+ - `uploadOne` → `1`
472
+ - `importOne` → `1`
473
+
474
+ **Example:**
475
+
476
+ ```typescript
477
+ // Standard usage in service tests
478
+ vi.mock('./files.js', async () => {
479
+ const { mockFilesService } = await import('../test-utils/services/files-service.js');
480
+ return mockFilesService();
481
+ });
482
+
483
+ // Override specific methods during tests
484
+ import { FilesService } from './files.js';
485
+
486
+ const uploadOneSpy = vi.spyOn(FilesService.prototype, 'uploadOne').mockResolvedValue(`1`);
487
+ ```
488
+
489
+ ---
490
+
491
+ ### folders-service.ts
492
+
493
+ Provides FoldersService mocking utilities for testing services that depend on FoldersService.
494
+
495
+ #### `mockFilesService()`
496
+
497
+ Creates a standard FoldersService mock with common methods pre-configured.
498
+
499
+ **Returns:** Mock module object with `FoldersService` class
500
+
501
+ **Mocked methods:**
502
+
503
+ In addition to the base `ItemsService` method the following `FoldersService` specific methods are available:
504
+
505
+ - `buildTree` → return `1` => `root` map
506
+
507
+ **Example:**
508
+
509
+ ```typescript
510
+ // Standard usage in service tests
511
+ vi.mock('./folders.js', async () => {
512
+ const { mockFoldersService } = await import('../test-utils/services/folders-service.js');
513
+ return mockFilesService();
514
+ });
515
+
516
+ // Override specific methods during tests
517
+ import { FoldersService } from './folders.js';
518
+
519
+ const buildTreeSpy = vi.spyOn(FoldersService.prototype, 'buildTree').mockResolvedValue(new Map('1', 'root-alt'));
520
+ ```
521
+
522
+ ---
523
+
453
524
  ## Common Patterns
454
525
 
455
526
  ### Full Service Test Setup
456
527
 
457
528
  ```typescript
458
- import { createMockKnex, resetKnexMocks } from '../__mocks__/knex.js';
529
+ import { createMockKnex, resetKnexMocks } from '../test-utils/knex.js';
459
530
  import { SchemaBuilder } from '@directus/schema-builder';
460
531
  import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
461
532
 
462
533
  // Mock all dependencies (before imports)
463
534
  vi.mock('../../src/database/index', async () => {
464
- const { mockDatabase } = await import('../__mocks__/database.js');
535
+ const { mockDatabase } = await import('../test-utils/database.js');
465
536
  return mockDatabase();
466
537
  });
467
538
 
468
539
  vi.mock('@directus/schema', async () => {
469
- const { mockSchema } = await import('../__mocks__/schema.js');
540
+ const { mockSchema } = await import('../test-utils/schema.js');
470
541
  return mockSchema();
471
542
  });
472
543
 
473
544
  vi.mock('../cache.js', async () => {
474
- const { mockCache } = await import('../__mocks__/cache.js');
545
+ const { mockCache } = await import('../test-utils/cache.js');
475
546
  return mockCache();
476
547
  });
477
548
 
478
549
  vi.mock('../emitter.js', async () => {
479
- const { mockEmitter } = await import('../__mocks__/emitter.js');
550
+ const { mockEmitter } = await import('../test-utils/emitter.js');
480
551
  return mockEmitter();
481
552
  });
482
553
 
483
554
  vi.mock('./items.js', async () => {
484
- const { mockItemsService } = await import('../__mocks__/items-service.js');
555
+ const { mockItemsService } = await import('../test-utils/services/items-service.js');
485
556
  return mockItemsService();
486
557
  });
487
558
 
488
559
  vi.mock('../utils/transaction.js', async () => {
489
- const { mockTransaction } = await import('../__mocks__/database.js');
560
+ const { mockTransaction } = await import('../test-utils/database.js');
490
561
  return mockTransaction();
491
562
  });
492
563
 
@@ -525,7 +596,7 @@ describe('Integration Tests', () => {
525
596
  ### Testing Schema Operations
526
597
 
527
598
  ```typescript
528
- import { mockCreateTable, mockAlterTable, createMockTableBuilder } from '../__mocks__/knex.js';
599
+ import { mockCreateTable, mockAlterTable, createMockTableBuilder } from '../test-utils/knex.js';
529
600
 
530
601
  test('should create table with correct schema', async () => {
531
602
  const { db, mockSchemaBuilder } = createMockKnex();
@@ -552,7 +623,7 @@ test('should alter table to add column', async () => {
552
623
 
553
624
  ```typescript
554
625
  import { getCache } from '../cache.js';
555
- import { mockCache } from '../__mocks__/cache.js';
626
+ import { mockCache } from '../test-utils/cache.js';
556
627
 
557
628
  test('should clear cache after update', async () => {
558
629
  const { spies } = mockCache();
@@ -604,7 +675,7 @@ test('should read column info', async () => {
604
675
  ### Testing with System Collection Mocks
605
676
 
606
677
  ```typescript
607
- import { setupSystemCollectionMocks } from '../__mocks__/knex.js';
678
+ import { setupSystemCollectionMocks } from '../test-utils/knex.js';
608
679
 
609
680
  describe('Service Tests', () => {
610
681
  const { db, tracker, mockSchemaBuilder } = createMockKnex();
@@ -678,7 +749,7 @@ Always declare `vi.mock()` calls **before** importing the modules they mock:
678
749
  ```typescript
679
750
  // ✅ Correct - mocks first
680
751
  vi.mock('../cache.js', async () => {
681
- const { mockCache } = await import('../__mocks__/cache.js');
752
+ const { mockCache } = await import('../test-utils/cache.js');
682
753
  return mockCache();
683
754
  });
684
755
 
@@ -688,7 +759,7 @@ import { YourService } from './your-service.js';
688
759
  import { YourService } from './your-service.js';
689
760
 
690
761
  vi.mock('../cache.js', async () => {
691
- const { mockCache } = await import('../__mocks__/cache.js');
762
+ const { mockCache } = await import('../test-utils/cache.js');
692
763
  return mockCache();
693
764
  });
694
765
  ```
@@ -12,13 +12,13 @@
12
12
  * ```typescript
13
13
  * // Standard usage for vi.mock()
14
14
  * vi.mock('../cache.js', async () => {
15
- * const { mockCache } = await import('../__mocks__/cache.js');
15
+ * const { mockCache } = await import('../test-utils/cache.js');
16
16
  * return mockCache();
17
17
  * });
18
18
  *
19
19
  * // Testing cache clearing with spies
20
20
  * import { getCache } from '../cache.js';
21
- * import { mockCache } from '../__mocks__/cache.js';
21
+ * import { mockCache } from '../test-utils/cache.js';
22
22
  *
23
23
  * test('should clear cache after update', async () => {
24
24
  * const { spies } = mockCache();
@@ -13,13 +13,13 @@ import { vi } from 'vitest';
13
13
  * ```typescript
14
14
  * // Standard usage for vi.mock()
15
15
  * vi.mock('../cache.js', async () => {
16
- * const { mockCache } = await import('../__mocks__/cache.js');
16
+ * const { mockCache } = await import('../test-utils/cache.js');
17
17
  * return mockCache();
18
18
  * });
19
19
  *
20
20
  * // Testing cache clearing with spies
21
21
  * import { getCache } from '../cache.js';
22
- * import { mockCache } from '../__mocks__/cache.js';
22
+ * import { mockCache } from '../test-utils/cache.js';
23
23
  *
24
24
  * test('should clear cache after update', async () => {
25
25
  * const { spies } = mockCache();
@@ -12,7 +12,7 @@
12
12
  * ```typescript
13
13
  * // Standard usage
14
14
  * vi.mock('./fields.js', async () => {
15
- * const { mockFieldsService } = await import('../__mocks__/fields-service.js');
15
+ * const { mockFieldsService } = await import('../test-utils/services/fields-service.js');
16
16
  * return mockFieldsService();
17
17
  * });
18
18
  *
@@ -3,6 +3,7 @@
3
3
  * Provides simplified mocks for src/services/fields module used in service testing
4
4
  */
5
5
  import { vi } from 'vitest';
6
+ import { mockItemsService } from './items-service.js';
6
7
  /**
7
8
  * Creates a standard FieldsService mock for service tests
8
9
  * This matches the pattern used in CollectionsService tests
@@ -13,7 +14,7 @@ import { vi } from 'vitest';
13
14
  * ```typescript
14
15
  * // Standard usage
15
16
  * vi.mock('./fields.js', async () => {
16
- * const { mockFieldsService } = await import('../__mocks__/fields-service.js');
17
+ * const { mockFieldsService } = await import('../test-utils/services/fields-service.js');
17
18
  * return mockFieldsService();
18
19
  * });
19
20
  *
@@ -25,7 +26,7 @@ import { vi } from 'vitest';
25
26
  * ```
26
27
  */
27
28
  export function mockFieldsService() {
28
- const FieldsService = vi.fn();
29
+ const { ItemsService: FieldsService } = mockItemsService();
29
30
  // Mock common methods used by other services (like CollectionsService)
30
31
  FieldsService.prototype.addColumnToTable = vi.fn().mockImplementation(() => { });
31
32
  FieldsService.prototype.addColumnIndex = vi.fn().mockResolvedValue(undefined);
@@ -0,0 +1,28 @@
1
+ /**
2
+ * FilesService mocking utilities for service tests
3
+ * Provides simplified mocks for src/services/files module used in service testing
4
+ */
5
+ /**
6
+ * Creates a standard FilesService mock for service tests
7
+ * This matches the pattern used in CollectionsService tests
8
+ *
9
+ * @returns Mock module object for vi.mock()
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * // Standard usage
14
+ * vi.mock('./files.js', async () => {
15
+ * const { mockFilesService } = await import('../test-utils/services/files-service.js');
16
+ * return mockFilesService();
17
+ * });
18
+ *
19
+ * // To dynamically change FilesService behavior during tests:
20
+ * import { FilesService } from './files.js';
21
+ * vi.spyOn(FilesService.prototype, 'addColumnToTable').mockImplementation((table, collection, field) => {
22
+ * // custom implementation
23
+ * });
24
+ * ```
25
+ */
26
+ export declare function mockFilesService(): {
27
+ FilesService: import("vitest").Mock<(...args: any[]) => any>;
28
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * FilesService mocking utilities for service tests
3
+ * Provides simplified mocks for src/services/files module used in service testing
4
+ */
5
+ import { vi } from 'vitest';
6
+ import { mockItemsService } from './items-service.js';
7
+ /**
8
+ * Creates a standard FilesService mock for service tests
9
+ * This matches the pattern used in CollectionsService tests
10
+ *
11
+ * @returns Mock module object for vi.mock()
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * // Standard usage
16
+ * vi.mock('./files.js', async () => {
17
+ * const { mockFilesService } = await import('../test-utils/services/files-service.js');
18
+ * return mockFilesService();
19
+ * });
20
+ *
21
+ * // To dynamically change FilesService behavior during tests:
22
+ * import { FilesService } from './files.js';
23
+ * vi.spyOn(FilesService.prototype, 'addColumnToTable').mockImplementation((table, collection, field) => {
24
+ * // custom implementation
25
+ * });
26
+ * ```
27
+ */
28
+ export function mockFilesService() {
29
+ const { ItemsService: FilesService } = mockItemsService();
30
+ // non-crud methods
31
+ FilesService.prototype.uploadOne = vi.fn().mockResolvedValue(1);
32
+ FilesService.prototype.importOne = vi.fn().mockResolvedValue(1);
33
+ return { FilesService };
34
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * FoldersService mocking utilities for service tests
3
+ * Provides simplified mocks for src/services/folders module used in service testing
4
+ */
5
+ /**
6
+ * Creates a standard FoldersService mock for service tests
7
+ * This matches the pattern used in CollectionsService tests
8
+ *
9
+ * @returns Mock module object for vi.mock()
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * // Standard usage
14
+ * vi.mock('./folders.js', async () => {
15
+ * const { mockFoldersService } = await import('../test-utils/services/folders-service.js');
16
+ * return mockFoldersService();
17
+ * });
18
+ *
19
+ * // To dynamically change FoldersService behavior during tests:
20
+ * import { FoldersService } from './folders.js';
21
+ * vi.spyOn(FoldersService.prototype, 'addColumnToTable').mockImplementation((table, collection, field) => {
22
+ * // custom implementation
23
+ * });
24
+ * ```
25
+ */
26
+ export declare function mockFoldersService(): {
27
+ FoldersService: import("vitest").Mock<(...args: any[]) => any>;
28
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * FoldersService mocking utilities for service tests
3
+ * Provides simplified mocks for src/services/folders module used in service testing
4
+ */
5
+ import { vi } from 'vitest';
6
+ import { mockItemsService } from './items-service.js';
7
+ /**
8
+ * Creates a standard FoldersService mock for service tests
9
+ * This matches the pattern used in CollectionsService tests
10
+ *
11
+ * @returns Mock module object for vi.mock()
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * // Standard usage
16
+ * vi.mock('./folders.js', async () => {
17
+ * const { mockFoldersService } = await import('../test-utils/services/folders-service.js');
18
+ * return mockFoldersService();
19
+ * });
20
+ *
21
+ * // To dynamically change FoldersService behavior during tests:
22
+ * import { FoldersService } from './folders.js';
23
+ * vi.spyOn(FoldersService.prototype, 'addColumnToTable').mockImplementation((table, collection, field) => {
24
+ * // custom implementation
25
+ * });
26
+ * ```
27
+ */
28
+ export function mockFoldersService() {
29
+ const { ItemsService: FoldersService } = mockItemsService();
30
+ // non-crud methods
31
+ FoldersService.prototype.buildTree = vi.fn().mockResolvedValue(new Map([['1', 'root']]));
32
+ return { FoldersService };
33
+ }
@@ -0,0 +1,2 @@
1
+ export declare const encrypt: (plainText: string, password: string) => Promise<string>;
2
+ export declare const decrypt: (encryptedText: string, password: string) => Promise<string>;
@@ -0,0 +1,64 @@
1
+ import crypto from 'node:crypto';
2
+ import { promisify } from 'node:util';
3
+ const VERSION = '1';
4
+ const KDF = 'scrypt';
5
+ const SCRYPT_DEFAULTS = { N: 2 ** 14, r: 8, p: 1 }; // ~16MB mem, good server default
6
+ const scryptAsync = promisify(crypto.scrypt);
7
+ const deriveKey = async (password, salt, opts = SCRYPT_DEFAULTS) => {
8
+ return await scryptAsync(password, salt, 32, opts);
9
+ };
10
+ export const encrypt = async (plainText, password) => {
11
+ const salt = crypto.randomBytes(16);
12
+ const keyBuf = await deriveKey(password, salt, SCRYPT_DEFAULTS);
13
+ // Generate a 12-byte IV for GCM and keep base64 string for storage
14
+ const ivBuf = crypto.randomBytes(12);
15
+ const iv = ivBuf.toString('base64');
16
+ const cipher = crypto.createCipheriv('aes-256-gcm', keyBuf, ivBuf);
17
+ let cipherText = cipher.update(plainText, 'utf8', 'base64');
18
+ cipherText += cipher.final('base64');
19
+ const tag = cipher.getAuthTag().toString('base64');
20
+ // 1||scrypt||N||r||p||salt||iv||cipherText||tag
21
+ return [
22
+ VERSION,
23
+ KDF,
24
+ SCRYPT_DEFAULTS.N,
25
+ SCRYPT_DEFAULTS.r,
26
+ SCRYPT_DEFAULTS.p,
27
+ salt.toString('base64'),
28
+ iv,
29
+ cipherText,
30
+ tag,
31
+ ].join('||');
32
+ };
33
+ export const decrypt = async (encryptedText, password) => {
34
+ const parts = encryptedText.split('||');
35
+ if (parts.length < 9)
36
+ throw new Error('Invalid encrypted payload');
37
+ const [version, kdf, nStr, rStr, pStr, saltB64, ivB64, cipherText, tagB64] = parts;
38
+ if (version !== VERSION)
39
+ throw new Error(`Unsupported version: ${version}`);
40
+ if (kdf !== KDF)
41
+ throw new Error(`Unsupported kdf: ${kdf}`);
42
+ if (!saltB64)
43
+ throw new Error('No salt in encrypted string');
44
+ if (!ivB64)
45
+ throw new Error('No iv in encrypted string');
46
+ if (cipherText === undefined)
47
+ throw new Error('No cipherText in encrypted string');
48
+ if (!tagB64)
49
+ throw new Error('No tag in encrypted string');
50
+ const opts = {
51
+ N: Number(nStr) || SCRYPT_DEFAULTS.N,
52
+ r: Number(rStr) || SCRYPT_DEFAULTS.r,
53
+ p: Number(pStr) || SCRYPT_DEFAULTS.p,
54
+ };
55
+ const salt = Buffer.from(saltB64, 'base64');
56
+ const keyBuf = await deriveKey(password, salt, opts);
57
+ const iv = Buffer.from(ivB64, 'base64');
58
+ const tag = Buffer.from(tagB64, 'base64');
59
+ const decipher = crypto.createDecipheriv('aes-256-gcm', keyBuf, iv);
60
+ decipher.setAuthTag(tag);
61
+ let plaintext = decipher.update(cipherText, 'base64', 'utf8');
62
+ plaintext += decipher.final('utf8');
63
+ return plaintext;
64
+ };
@@ -13,13 +13,13 @@ export async function getAccountabilityForRole(role, context) {
13
13
  });
14
14
  }
15
15
  else {
16
- const roles = await fetchRolesTree(role, context.database);
16
+ const roles = await fetchRolesTree(role, { knex: context.database });
17
17
  // The roles tree should always include the passed role. If it doesn't, it's because it
18
18
  // couldn't be read from the database and therefore doesn't exist
19
19
  if (roles.length === 0) {
20
20
  throw new Error(`Configured role "${role}" isn't a valid role ID or doesn't exist.`);
21
21
  }
22
- const globalAccess = await fetchGlobalAccess({ user: null, roles, ip: context.accountability?.ip ?? null }, context.database);
22
+ const globalAccess = await fetchGlobalAccess({ user: null, roles, ip: context.accountability?.ip ?? null }, { knex: context.database });
23
23
  generatedAccountability = createDefaultAccountability({
24
24
  role,
25
25
  roles,
@@ -25,8 +25,8 @@ export async function getAccountabilityForToken(token, accountability) {
25
25
  if (payload.id)
26
26
  accountability.user = payload.id;
27
27
  accountability.role = payload.role;
28
- accountability.roles = await fetchRolesTree(payload.role, database);
29
- const { admin, app } = await fetchGlobalAccess(accountability, database);
28
+ accountability.roles = await fetchRolesTree(payload.role, { knex: database });
29
+ const { admin, app } = await fetchGlobalAccess(accountability, { knex: database });
30
30
  accountability.admin = admin;
31
31
  accountability.app = app;
32
32
  }
@@ -44,8 +44,8 @@ export async function getAccountabilityForToken(token, accountability) {
44
44
  }
45
45
  accountability.user = user.id;
46
46
  accountability.role = user.role;
47
- accountability.roles = await fetchRolesTree(user.role, database);
48
- const { admin, app } = await fetchGlobalAccess(accountability, database);
47
+ accountability.roles = await fetchRolesTree(user.role, { knex: database });
48
+ const { admin, app } = await fetchGlobalAccess(accountability, { knex: database });
49
49
  accountability.admin = admin;
50
50
  accountability.app = app;
51
51
  }
@@ -1,10 +1,10 @@
1
+ import { ipInNetworks } from '@directus/utils/node';
2
+ import { version } from 'directus/version';
1
3
  import hash from 'object-hash';
2
4
  import url from 'url';
3
5
  import getDatabase from '../database/index.js';
4
6
  import { fetchPoliciesIpAccess } from '../permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js';
5
7
  import { getGraphqlQueryAndVariables } from './get-graphql-query-and-variables.js';
6
- import { version } from 'directus/version';
7
- import { ipInNetworks } from './ip-in-networks.js';
8
8
  export async function getCacheKey(req) {
9
9
  const path = url.parse(req.originalUrl).pathname;
10
10
  const isGraphQl = path?.startsWith('/graphql');
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Checks if the defined redirect after successful SSO login is in the allow list
3
+ */
4
+ export declare function isLoginRedirectAllowed(redirect: unknown, provider: string): boolean;