@directus/api 31.0.0 → 32.0.1

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 (135) hide show
  1. package/dist/app.js +2 -0
  2. package/dist/auth/auth.d.ts +2 -1
  3. package/dist/auth/auth.js +7 -2
  4. package/dist/auth/drivers/ldap.d.ts +0 -2
  5. package/dist/auth/drivers/ldap.js +9 -7
  6. package/dist/auth/drivers/oauth2.d.ts +0 -2
  7. package/dist/auth/drivers/oauth2.js +11 -8
  8. package/dist/auth/drivers/openid.d.ts +0 -2
  9. package/dist/auth/drivers/openid.js +11 -8
  10. package/dist/auth/drivers/saml.d.ts +0 -2
  11. package/dist/auth/drivers/saml.js +5 -5
  12. package/dist/auth.js +1 -2
  13. package/dist/cli/commands/bootstrap/index.js +12 -33
  14. package/dist/cli/commands/init/index.js +1 -1
  15. package/dist/cli/commands/schema/apply.d.ts +4 -0
  16. package/dist/cli/commands/schema/apply.js +26 -3
  17. package/dist/controllers/collections.js +7 -2
  18. package/dist/controllers/fields.js +31 -8
  19. package/dist/controllers/server.js +26 -1
  20. package/dist/controllers/settings.js +9 -2
  21. package/dist/controllers/users.js +2 -2
  22. package/dist/database/helpers/fn/types.js +3 -3
  23. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +2 -1
  24. package/dist/database/helpers/schema/dialects/cockroachdb.js +13 -0
  25. package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -1
  26. package/dist/database/helpers/schema/dialects/mssql.js +23 -0
  27. package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
  28. package/dist/database/helpers/schema/dialects/mysql.js +25 -0
  29. package/dist/database/helpers/schema/dialects/oracle.d.ts +2 -1
  30. package/dist/database/helpers/schema/dialects/oracle.js +13 -0
  31. package/dist/database/helpers/schema/dialects/postgres.d.ts +2 -1
  32. package/dist/database/helpers/schema/dialects/postgres.js +13 -0
  33. package/dist/database/helpers/schema/types.d.ts +5 -0
  34. package/dist/database/helpers/schema/types.js +6 -0
  35. package/dist/database/migrations/20251012A-add-field-searchable.d.ts +3 -0
  36. package/dist/database/migrations/20251012A-add-field-searchable.js +10 -0
  37. package/dist/database/migrations/20251014A-add-project-owner.d.ts +3 -0
  38. package/dist/database/migrations/20251014A-add-project-owner.js +37 -0
  39. package/dist/database/migrations/20251028A-add-retention-indexes.d.ts +3 -0
  40. package/dist/database/migrations/20251028A-add-retention-indexes.js +42 -0
  41. package/dist/database/run-ast/lib/apply-query/add-join.js +2 -2
  42. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
  43. package/dist/database/run-ast/lib/apply-query/index.d.ts +0 -1
  44. package/dist/database/run-ast/lib/apply-query/index.js +4 -6
  45. package/dist/database/run-ast/lib/apply-query/search.js +2 -0
  46. package/dist/database/run-ast/lib/get-db-query.js +7 -6
  47. package/dist/database/run-ast/utils/generate-alias.d.ts +6 -0
  48. package/dist/database/run-ast/utils/generate-alias.js +57 -0
  49. package/dist/flows.js +1 -0
  50. package/dist/mcp/schema.d.ts +14 -14
  51. package/dist/mcp/schema.js +6 -6
  52. package/dist/mcp/server.d.ts +9 -3
  53. package/dist/mcp/server.js +1 -1
  54. package/dist/mcp/tools/collections.d.ts +1 -1
  55. package/dist/mcp/tools/fields.d.ts +1 -1
  56. package/dist/mcp/tools/files.d.ts +25 -25
  57. package/dist/mcp/tools/flows.d.ts +36 -36
  58. package/dist/mcp/tools/folders.d.ts +18 -18
  59. package/dist/mcp/tools/items.d.ts +18 -18
  60. package/dist/mcp/tools/operations.d.ts +19 -19
  61. package/dist/mcp/tools/prompts/items.md +1 -1
  62. package/dist/metrics/lib/create-metrics.js +16 -25
  63. package/dist/middleware/collection-exists.js +2 -2
  64. package/dist/operations/mail/index.js +3 -1
  65. package/dist/operations/mail/rate-limiter.d.ts +1 -0
  66. package/dist/operations/mail/rate-limiter.js +29 -0
  67. package/dist/permissions/modules/process-payload/process-payload.js +3 -10
  68. package/dist/permissions/modules/validate-access/validate-access.js +2 -3
  69. package/dist/schedules/metrics.js +6 -2
  70. package/dist/schedules/project.d.ts +4 -0
  71. package/dist/schedules/project.js +27 -0
  72. package/dist/services/collections.d.ts +3 -3
  73. package/dist/services/collections.js +16 -1
  74. package/dist/services/fields.d.ts +21 -5
  75. package/dist/services/fields.js +105 -28
  76. package/dist/services/graphql/resolvers/query.js +1 -1
  77. package/dist/services/graphql/resolvers/system-admin.js +49 -5
  78. package/dist/services/graphql/schema/parse-query.js +8 -8
  79. package/dist/services/graphql/utils/aggregate-query.d.ts +1 -1
  80. package/dist/services/graphql/utils/aggregate-query.js +5 -1
  81. package/dist/services/graphql/utils/filter-replace-m2a.js +2 -1
  82. package/dist/services/import-export.d.ts +9 -1
  83. package/dist/services/import-export.js +318 -101
  84. package/dist/services/items.d.ts +1 -1
  85. package/dist/services/items.js +36 -20
  86. package/dist/services/mail/index.js +2 -0
  87. package/dist/services/mail/rate-limiter.d.ts +1 -0
  88. package/dist/services/mail/rate-limiter.js +29 -0
  89. package/dist/services/meta.js +28 -24
  90. package/dist/services/schema.js +4 -1
  91. package/dist/services/server.d.ts +1 -0
  92. package/dist/services/server.js +14 -18
  93. package/dist/services/settings.d.ts +2 -1
  94. package/dist/services/settings.js +15 -0
  95. package/dist/services/tus/server.js +14 -9
  96. package/dist/telemetry/lib/get-report.js +4 -4
  97. package/dist/telemetry/lib/send-report.d.ts +6 -1
  98. package/dist/telemetry/lib/send-report.js +3 -1
  99. package/dist/telemetry/types/report.d.ts +17 -1
  100. package/dist/telemetry/utils/get-settings.d.ts +9 -0
  101. package/dist/telemetry/utils/get-settings.js +14 -0
  102. package/dist/test-utils/README.md +760 -0
  103. package/dist/test-utils/cache.d.ts +51 -0
  104. package/dist/test-utils/cache.js +59 -0
  105. package/dist/test-utils/database.d.ts +48 -0
  106. package/dist/test-utils/database.js +52 -0
  107. package/dist/test-utils/emitter.d.ts +35 -0
  108. package/dist/test-utils/emitter.js +38 -0
  109. package/dist/test-utils/fields-service.d.ts +28 -0
  110. package/dist/test-utils/fields-service.js +36 -0
  111. package/dist/test-utils/items-service.d.ts +23 -0
  112. package/dist/test-utils/items-service.js +37 -0
  113. package/dist/test-utils/knex.d.ts +164 -0
  114. package/dist/test-utils/knex.js +268 -0
  115. package/dist/test-utils/schema.d.ts +26 -0
  116. package/dist/test-utils/schema.js +35 -0
  117. package/dist/types/auth.d.ts +0 -2
  118. package/dist/utils/apply-diff.js +15 -0
  119. package/dist/utils/create-admin.d.ts +11 -0
  120. package/dist/utils/create-admin.js +50 -0
  121. package/dist/utils/get-schema.js +5 -3
  122. package/dist/utils/get-snapshot-diff.js +49 -5
  123. package/dist/utils/get-snapshot.js +13 -7
  124. package/dist/utils/sanitize-schema.d.ts +11 -4
  125. package/dist/utils/sanitize-schema.js +9 -6
  126. package/dist/utils/schedule.js +15 -19
  127. package/dist/utils/validate-diff.js +31 -0
  128. package/dist/utils/validate-snapshot.js +7 -0
  129. package/dist/websocket/controllers/hooks.js +12 -20
  130. package/dist/websocket/messages.d.ts +3 -3
  131. package/package.json +63 -65
  132. package/dist/cli/utils/defaults.d.ts +0 -4
  133. package/dist/cli/utils/defaults.js +0 -17
  134. package/dist/telemetry/utils/get-project-id.d.ts +0 -2
  135. package/dist/telemetry/utils/get-project-id.js +0 -4
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Knex mocking utilities for service tests
3
+ * Provides mock knex instances, table builders, and tracker utilities
4
+ */
5
+ import { systemCollectionNames } from '@directus/system-data';
6
+ import knex from 'knex';
7
+ import { MockClient, createTracker } from 'knex-mock-client';
8
+ import { vi } from 'vitest';
9
+ /**
10
+ * Creates a mocked knex instance with tracker and schema builder support
11
+ *
12
+ * @returns Object containing the mocked db instance, tracker, and mockSchema
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const { db, tracker, mockSchema } = createMockKnex();
17
+ *
18
+ * // Use tracker to mock query responses
19
+ * tracker.on.select('users').response([{ id: 1, name: 'John' }]);
20
+ *
21
+ * // Verify schema operations
22
+ * expect(mockSchema.createTable).toHaveBeenCalled();
23
+ * ```
24
+ */
25
+ export function createMockKnex() {
26
+ const db = vi.mocked(knex.default({ client: MockClient }));
27
+ const tracker = createTracker(db);
28
+ // Mock schema builder methods with functional callbacks
29
+ const mockSchemaBuilder = {
30
+ createTable: vi.fn((_tableName, callback) => {
31
+ callback(createMockTableBuilder());
32
+ return Promise.resolve();
33
+ }),
34
+ dropTable: vi.fn().mockResolvedValue(undefined),
35
+ hasTable: vi.fn().mockResolvedValue(false),
36
+ table: vi.fn((_tableName, callback) => {
37
+ callback(createMockTableBuilder());
38
+ return Promise.resolve();
39
+ }),
40
+ alterTable: vi.fn((_tableName, callback) => {
41
+ callback(createMockTableBuilder());
42
+ return Promise.resolve();
43
+ }),
44
+ dropTableIfExists: vi.fn().mockResolvedValue(undefined),
45
+ renameTable: vi.fn().mockResolvedValue(undefined),
46
+ raw: vi.fn().mockResolvedValue(undefined),
47
+ };
48
+ Object.defineProperty(db, 'schema', {
49
+ get: () => mockSchemaBuilder,
50
+ configurable: true,
51
+ });
52
+ // Note: We do NOT override the query builder methods (select, where, from, etc.)
53
+ // because knex-mock-client already provides them and connects them to the tracker.
54
+ // Overriding them would break the tracker connection and cause queries to not return
55
+ // the mocked responses.
56
+ return { db, tracker, mockSchemaBuilder };
57
+ }
58
+ /**
59
+ * Creates a mock table builder for schema operations
60
+ * Used for testing column creation and alteration
61
+ *
62
+ * @returns Mock table builder with chainable methods
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const table = createMockTableBuilder();
67
+ * table.string('name', 255).notNullable().index();
68
+ * ```
69
+ */
70
+ export function createMockTableBuilder() {
71
+ return {
72
+ // Column types - actively used
73
+ string: vi.fn().mockReturnThis(),
74
+ text: vi.fn().mockReturnThis(),
75
+ integer: vi.fn().mockReturnThis(),
76
+ bigInteger: vi.fn().mockReturnThis(),
77
+ float: vi.fn().mockReturnThis(),
78
+ decimal: vi.fn().mockReturnThis(),
79
+ boolean: vi.fn().mockReturnThis(),
80
+ date: vi.fn().mockReturnThis(),
81
+ dateTime: vi.fn().mockReturnThis(),
82
+ timestamp: vi.fn().mockReturnThis(),
83
+ json: vi.fn().mockReturnThis(),
84
+ jsonb: vi.fn().mockReturnThis(),
85
+ uuid: vi.fn().mockReturnThis(),
86
+ // Column types - unused
87
+ // tinyint: vi.fn().mockReturnThis(),
88
+ // smallint: vi.fn().mockReturnThis(),
89
+ // mediumint: vi.fn().mockReturnThis(),
90
+ // bigint: vi.fn().mockReturnThis(),
91
+ // double: vi.fn().mockReturnThis(),
92
+ // datetime: vi.fn().mockReturnThis(),
93
+ // time: vi.fn().mockReturnThis(),
94
+ // timestamps: vi.fn().mockReturnThis(),
95
+ // binary: vi.fn().mockReturnThis(),
96
+ // enum: vi.fn().mockReturnThis(),
97
+ // enu: vi.fn().mockReturnThis(),
98
+ // geometry: vi.fn().mockReturnThis(),
99
+ // geography: vi.fn().mockReturnThis(),
100
+ // point: vi.fn().mockReturnThis(),
101
+ // linestring: vi.fn().mockReturnThis(),
102
+ // polygon: vi.fn().mockReturnThis(),
103
+ // multipoint: vi.fn().mockReturnThis(),
104
+ // multilinestring: vi.fn().mockReturnThis(),
105
+ // multipolygon: vi.fn().mockReturnThis(),
106
+ // geometrycollection: vi.fn().mockReturnThis(),
107
+ // specificType: vi.fn().mockReturnThis(),
108
+ // Auto-increment columns
109
+ increments: vi.fn().mockReturnThis(),
110
+ bigIncrements: vi.fn().mockReturnThis(),
111
+ // Column modifiers - actively used
112
+ defaultTo: vi.fn().mockReturnThis(),
113
+ notNullable: vi.fn().mockReturnThis(),
114
+ nullable: vi.fn().mockReturnThis(),
115
+ primary: vi.fn().mockReturnThis(),
116
+ unique: vi.fn().mockReturnThis(),
117
+ index: vi.fn().mockReturnThis(),
118
+ alter: vi.fn().mockReturnThis(),
119
+ // Column modifiers - unused
120
+ // unsigned: vi.fn().mockReturnThis(),
121
+ // comment: vi.fn().mockReturnThis(),
122
+ // collate: vi.fn().mockReturnThis(),
123
+ // charset: vi.fn().mockReturnThis(),
124
+ // first: vi.fn().mockReturnThis(),
125
+ // after: vi.fn().mockReturnThis(),
126
+ // references: vi.fn().mockReturnThis(),
127
+ // inTable: vi.fn().mockReturnThis(),
128
+ // onDelete: vi.fn().mockReturnThis(),
129
+ // onUpdate: vi.fn().mockReturnThis(),
130
+ // foreign: vi.fn().mockReturnThis(),
131
+ // Schema alterations - actively used
132
+ dropColumn: vi.fn().mockReturnThis(),
133
+ dropUnique: vi.fn().mockReturnThis(),
134
+ dropIndex: vi.fn().mockReturnThis(),
135
+ // Schema alterations - unused
136
+ // dropColumns: vi.fn().mockReturnThis(),
137
+ // renameColumn: vi.fn().mockReturnThis(),
138
+ // dropPrimary: vi.fn().mockReturnThis(),
139
+ // dropForeign: vi.fn().mockReturnThis(),
140
+ // dropTimestamps: vi.fn().mockReturnThis(),
141
+ // Table options - unused
142
+ // engine: vi.fn().mockReturnThis(),
143
+ // inherits: vi.fn().mockReturnThis(),
144
+ // queryContext: vi.fn().mockReturnThis(),
145
+ };
146
+ }
147
+ /**
148
+ * Sets up common database operation mock handlers for all system collections
149
+ * Automatically mocks CRUD operations (select, insert, update, delete) for all Directus system collections
150
+ *
151
+ * @param tracker The knex-mock-client tracker instance
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * const { db, tracker, mockSchema } = createMockKnex();
156
+ * setupSystemCollectionMocks(tracker);
157
+ * // Now all CRUD operations on system collections are mocked
158
+ * ```
159
+ */
160
+ export function setupSystemCollectionMocks(tracker) {
161
+ // Mock all CRUD operations for all system collections
162
+ for (const collection of systemCollectionNames) {
163
+ tracker.on.select(collection).response([]);
164
+ tracker.on.insert(collection).response([]);
165
+ tracker.on.update(collection).response([]);
166
+ tracker.on.delete(collection).response([]);
167
+ }
168
+ }
169
+ /**
170
+ * Resets all mock states
171
+ * Should be called in afterEach hooks to clean up between tests
172
+ *
173
+ * @param tracker The knex-mock-client tracker instance
174
+ * @param mockSchema The mock schema object from createMockKnex
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * const { db, tracker, mockSchema } = createMockKnex();
179
+ *
180
+ * afterEach(() => {
181
+ * resetMocks(tracker, mockSchema);
182
+ * });
183
+ * ```
184
+ */
185
+ export function resetKnexMocks(tracker, mockSchema) {
186
+ tracker.reset();
187
+ vi.clearAllMocks();
188
+ mockSchema.createTable.mockClear();
189
+ mockSchema.dropTable.mockClear();
190
+ mockSchema.hasTable.mockClear();
191
+ mockSchema.table.mockClear();
192
+ if (mockSchema.alterTable) {
193
+ mockSchema.alterTable.mockClear();
194
+ }
195
+ }
196
+ /**
197
+ * Creates a mock createTable function for testing table creation
198
+ * Returns a vi.fn() that calls the callback with a mock table builder
199
+ *
200
+ * @returns Mock function for db.schema.createTable
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * const { db } = createMockKnex();
205
+ * const createTableSpy = mockCreateTable();
206
+ * db.schema.createTable = createTableSpy as any;
207
+ *
208
+ * // Now when createTable is called, it will invoke the callback with a mock table builder
209
+ * await db.schema.createTable('users', (table) => {
210
+ * table.increments('id');
211
+ * table.string('name');
212
+ * });
213
+ * ```
214
+ */
215
+ export function mockCreateTable() {
216
+ return vi.fn((_tableName, callback) => {
217
+ callback(createMockTableBuilder());
218
+ return Promise.resolve();
219
+ });
220
+ }
221
+ /**
222
+ * Creates a mock alterTable function for testing schema alterations
223
+ * Returns a vi.fn() that calls the callback with a mock table builder
224
+ *
225
+ * @returns Mock function for db.schema.alterTable
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * const { db } = createMockKnex();
230
+ * const alterTableSpy = mockAlterTable();
231
+ * db.schema.alterTable = alterTableSpy as any;
232
+ *
233
+ * // Now when alterTable is called, it will invoke the callback with a mock table builder
234
+ * await db.schema.alterTable('users', (table) => {
235
+ * table.string('name');
236
+ * });
237
+ * ```
238
+ */
239
+ export function mockAlterTable() {
240
+ return vi.fn((_tableName, callback) => {
241
+ callback(createMockTableBuilder());
242
+ return Promise.resolve();
243
+ });
244
+ }
245
+ /**
246
+ * Creates a mock schema.table function for testing schema operations
247
+ * Returns a vi.fn() that calls the callback with a mock table builder
248
+ *
249
+ * @returns Mock function for db.schema.table
250
+ *
251
+ * @example
252
+ * ```typescript
253
+ * const { db } = createMockKnex();
254
+ * const schemaTableSpy = mockSchemaTable();
255
+ * db.schema.table = schemaTableSpy as any;
256
+ *
257
+ * // Now when schema.table is called, it will invoke the callback with a mock table builder
258
+ * await db.schema.table('users', (table) => {
259
+ * table.dropColumn('name');
260
+ * });
261
+ * ```
262
+ */
263
+ export function mockSchemaTable() {
264
+ return vi.fn((_tableName, callback) => {
265
+ callback(createMockTableBuilder());
266
+ return Promise.resolve();
267
+ });
268
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Schema mocking utilities for service tests
3
+ * Provides simplified mocks for @directus/schema module used in service testing
4
+ */
5
+ /**
6
+ * Creates a standard schema inspector mock for service tests
7
+ * This matches the pattern used across all service test files
8
+ *
9
+ * @returns Mock module object for vi.mock()
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * // Standard usage
14
+ * vi.mock('@directus/schema', () => mockSchema());
15
+ *
16
+ * // To dynamically change inspector behavior during tests, import and mock directly:
17
+ * import { createInspector } from '@directus/schema';
18
+ * vi.mocked(createInspector).mockReturnValue({
19
+ * tableInfo: vi.fn().mockResolvedValue([{ name: 'custom_table' }]),
20
+ * // ... other custom behaviors
21
+ * });
22
+ * ```
23
+ */
24
+ export declare function mockSchema(): {
25
+ createInspector: import("vitest").Mock<(...args: any[]) => any>;
26
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Schema mocking utilities for service tests
3
+ * Provides simplified mocks for @directus/schema module used in service testing
4
+ */
5
+ import { vi } from 'vitest';
6
+ /**
7
+ * Creates a standard schema inspector mock for service tests
8
+ * This matches the pattern used across all service test files
9
+ *
10
+ * @returns Mock module object for vi.mock()
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // Standard usage
15
+ * vi.mock('@directus/schema', () => mockSchema());
16
+ *
17
+ * // To dynamically change inspector behavior during tests, import and mock directly:
18
+ * import { createInspector } from '@directus/schema';
19
+ * vi.mocked(createInspector).mockReturnValue({
20
+ * tableInfo: vi.fn().mockResolvedValue([{ name: 'custom_table' }]),
21
+ * // ... other custom behaviors
22
+ * });
23
+ * ```
24
+ */
25
+ export function mockSchema() {
26
+ return {
27
+ createInspector: vi.fn().mockReturnValue({
28
+ tableInfo: vi.fn().mockResolvedValue([]),
29
+ columnInfo: vi.fn().mockResolvedValue([]),
30
+ primary: vi.fn().mockResolvedValue('id'),
31
+ foreignKeys: vi.fn().mockResolvedValue([]),
32
+ withSchema: vi.fn().mockReturnThis(),
33
+ }),
34
+ };
35
+ }
@@ -1,8 +1,6 @@
1
- import type { SchemaOverview } from '@directus/types';
2
1
  import type { Knex } from 'knex';
3
2
  export interface AuthDriverOptions {
4
3
  knex: Knex;
5
- schema: SchemaOverview;
6
4
  }
7
5
  export interface User {
8
6
  id: string;
@@ -205,6 +205,21 @@ export async function applyDiff(currentSnapshot, snapshotDiff, options) {
205
205
  !relation.diff.some((diff) => diff.kind === DiffKind.NEW)) === false);
206
206
  }
207
207
  }
208
+ for (const { collection, field, diff } of snapshotDiff.systemFields) {
209
+ if (diff?.[0]?.kind === DiffKind.EDIT) {
210
+ try {
211
+ const newValues = diff.reduce((acc, currentDiff) => {
212
+ deepDiff.applyChange(acc, undefined, currentDiff);
213
+ return acc;
214
+ }, { collection, field });
215
+ await fieldsService.updateField(collection, newValues, mutationOptions);
216
+ }
217
+ catch (err) {
218
+ logger.error(`Failed to update field "${collection}.${field}"`);
219
+ throw err;
220
+ }
221
+ }
222
+ }
208
223
  const relationsService = new RelationsService({
209
224
  knex: trx,
210
225
  schema: await getSchema({ database: trx, bypassCache: true }),
@@ -0,0 +1,11 @@
1
+ import type { SchemaOverview } from '@directus/types';
2
+ import type { Policy, Role, User } from '@directus/types';
3
+ export declare const defaultAdminRole: Partial<Role>;
4
+ export declare const defaultAdminUser: Partial<User>;
5
+ export declare const defaultAdminPolicy: Partial<Policy>;
6
+ export declare function createAdmin(schema: SchemaOverview, admin?: {
7
+ email?: string;
8
+ password?: string;
9
+ first_name?: string;
10
+ last_name?: string;
11
+ }): Promise<void>;
@@ -0,0 +1,50 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { useLogger } from '../logger/index.js';
3
+ import { AccessService } from '../services/access.js';
4
+ import { UsersService } from '../services/index.js';
5
+ import { PoliciesService } from '../services/policies.js';
6
+ import { RolesService } from '../services/roles.js';
7
+ export const defaultAdminRole = {
8
+ name: 'Administrator',
9
+ icon: 'verified',
10
+ description: '$t:admin_description',
11
+ };
12
+ export const defaultAdminUser = {
13
+ status: 'active',
14
+ first_name: 'Admin',
15
+ last_name: 'User',
16
+ };
17
+ export const defaultAdminPolicy = {
18
+ name: 'Administrator',
19
+ icon: 'verified',
20
+ admin_access: true,
21
+ app_access: true,
22
+ description: '$t:admin_description',
23
+ };
24
+ export async function createAdmin(schema, admin) {
25
+ const logger = useLogger();
26
+ const env = useEnv();
27
+ logger.info('Setting up first admin role...');
28
+ const accessService = new AccessService({ schema });
29
+ const policiesService = new PoliciesService({ schema });
30
+ const rolesService = new RolesService({ schema });
31
+ const role = await rolesService.createOne(defaultAdminRole);
32
+ const policy = await policiesService.createOne(defaultAdminPolicy);
33
+ await accessService.createOne({ policy, role });
34
+ const usersService = new UsersService({ schema });
35
+ const adminEmail = admin?.email ?? env['ADMIN_EMAIL'];
36
+ const adminPassword = admin?.password ?? env['ADMIN_PASSWORD'];
37
+ if (!adminEmail || !adminPassword)
38
+ return;
39
+ const token = env['ADMIN_TOKEN'] ?? null;
40
+ logger.info('Adding first admin user...');
41
+ await usersService.createOne({
42
+ ...defaultAdminUser,
43
+ first_name: admin?.first_name ?? defaultAdminUser.first_name,
44
+ last_name: admin?.last_name ?? defaultAdminUser.last_name,
45
+ email: adminEmail,
46
+ password: adminPassword,
47
+ token,
48
+ role,
49
+ });
50
+ }
@@ -1,7 +1,7 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { createInspector } from '@directus/schema';
3
3
  import { systemCollectionRows } from '@directus/system-data';
4
- import { parseJSON, toArray } from '@directus/utils';
4
+ import { parseJSON, toArray, toBoolean } from '@directus/utils';
5
5
  import { mapValues } from 'lodash-es';
6
6
  import { useBus } from '../bus/index.js';
7
7
  import { getMemorySchemaCache, setMemorySchemaCache } from '../cache.js';
@@ -106,7 +106,7 @@ async function getDatabaseSchema(database, schemaInspector) {
106
106
  result.collections[collection] = {
107
107
  collection,
108
108
  primary: info.primary,
109
- singleton: collectionMeta?.singleton === true || collectionMeta?.singleton === 'true' || collectionMeta?.singleton === 1,
109
+ singleton: toBoolean(collectionMeta?.singleton),
110
110
  note: collectionMeta?.note || null,
111
111
  sortField: collectionMeta?.sort_field || null,
112
112
  accountability: collectionMeta ? collectionMeta.accountability : 'all',
@@ -124,13 +124,14 @@ async function getDatabaseSchema(database, schemaInspector) {
124
124
  note: null,
125
125
  validation: null,
126
126
  alias: false,
127
+ searchable: true,
127
128
  };
128
129
  }),
129
130
  };
130
131
  }
131
132
  const fields = [
132
133
  ...(await database
133
- .select('id', 'collection', 'field', 'special', 'note', 'validation')
134
+ .select('id', 'collection', 'field', 'special', 'note', 'validation', 'searchable')
134
135
  .from('directus_fields')),
135
136
  ...systemFieldRows,
136
137
  ].filter((field) => (field.special ? toArray(field.special) : []).includes('no-data') === false);
@@ -159,6 +160,7 @@ async function getDatabaseSchema(database, schemaInspector) {
159
160
  note: field.note,
160
161
  alias: existing?.alias ?? true,
161
162
  validation: validation ?? null,
163
+ searchable: toBoolean(field.searchable) ?? true,
162
164
  };
163
165
  }
164
166
  const relationsService = new RelationsService({ knex: database, schema: result });
@@ -1,14 +1,15 @@
1
1
  import deepDiff from 'deep-diff';
2
2
  import { DiffKind } from '@directus/types';
3
- import { sanitizeCollection, sanitizeField, sanitizeRelation } from './sanitize-schema.js';
3
+ import { sanitizeCollection, sanitizeField, sanitizeRelation, sanitizeSystemField } from './sanitize-schema.js';
4
4
  export function getSnapshotDiff(current, after) {
5
5
  const diffedSnapshot = {
6
6
  collections: [
7
7
  ...current.collections.map((currentCollection) => {
8
8
  const afterCollection = after.collections.find((afterCollection) => afterCollection.collection === currentCollection.collection);
9
+ const afterCollectionSanitized = afterCollection ? sanitizeCollection(afterCollection) : undefined;
9
10
  return {
10
11
  collection: currentCollection.collection,
11
- diff: deepDiff.diff(sanitizeCollection(currentCollection), sanitizeCollection(afterCollection)),
12
+ diff: deepDiff.diff(sanitizeCollection(currentCollection), afterCollectionSanitized),
12
13
  };
13
14
  }),
14
15
  ...after.collections
@@ -32,13 +33,14 @@ export function getSnapshotDiff(current, after) {
32
33
  return {
33
34
  collection: currentField.collection,
34
35
  field: currentField.field,
35
- diff: deepDiff.diff(sanitizeField(currentField, isAutoIncrementPrimaryKey), sanitizeField(undefined, isAutoIncrementPrimaryKey)),
36
+ diff: deepDiff.diff(sanitizeField(currentField, isAutoIncrementPrimaryKey), undefined),
36
37
  };
37
38
  }
39
+ const afterFieldSanitized = afterField ? sanitizeField(afterField, isAutoIncrementPrimaryKey) : undefined;
38
40
  return {
39
41
  collection: currentField.collection,
40
42
  field: currentField.field,
41
- diff: deepDiff.diff(sanitizeField(currentField, isAutoIncrementPrimaryKey), sanitizeField(afterField, isAutoIncrementPrimaryKey)),
43
+ diff: deepDiff.diff(sanitizeField(currentField, isAutoIncrementPrimaryKey), afterFieldSanitized),
42
44
  };
43
45
  }),
44
46
  ...after.fields
@@ -58,14 +60,49 @@ export function getSnapshotDiff(current, after) {
58
60
  diff: deepDiff.diff(undefined, sanitizeField(afterField)),
59
61
  })),
60
62
  ].filter((obj) => Array.isArray(obj.diff)),
63
+ systemFields: [
64
+ ...(current.systemFields ?? []).map((currentSystemField) => {
65
+ const afterSystemField = (after.systemFields ?? []).find((afterSystemField) => afterSystemField.collection === currentSystemField.collection &&
66
+ afterSystemField.field === currentSystemField.field);
67
+ const afterSystemFieldSanitized = afterSystemField
68
+ ? sanitizeSystemField(afterSystemField)
69
+ : invertIndexed(currentSystemField);
70
+ return {
71
+ collection: currentSystemField.collection,
72
+ field: currentSystemField.field,
73
+ diff: deepDiff.diff(sanitizeSystemField(currentSystemField), afterSystemFieldSanitized),
74
+ };
75
+ }),
76
+ ...(after.systemFields ?? [])
77
+ .filter((afterSystemField) => {
78
+ if (!afterSystemField.schema.is_indexed)
79
+ return false;
80
+ const currentSystemField = (current.systemFields ?? []).find((currentSystemField) => currentSystemField.collection === afterSystemField.collection &&
81
+ afterSystemField.field === currentSystemField.field);
82
+ return Boolean(currentSystemField) === false;
83
+ })
84
+ .map((afterSystemField) => {
85
+ const currentSystemField = (current.systemFields ?? []).find((currentSystemField) => currentSystemField.collection === afterSystemField.collection &&
86
+ currentSystemField.field === afterSystemField.field);
87
+ const currentSystemFieldSanitized = currentSystemField
88
+ ? sanitizeSystemField(currentSystemField)
89
+ : invertIndexed(afterSystemField);
90
+ return {
91
+ collection: afterSystemField.collection,
92
+ field: afterSystemField.field,
93
+ diff: deepDiff.diff(currentSystemFieldSanitized, sanitizeSystemField(afterSystemField)),
94
+ };
95
+ }),
96
+ ].filter((obj) => Array.isArray(obj.diff)),
61
97
  relations: [
62
98
  ...current.relations.map((currentRelation) => {
63
99
  const afterRelation = after.relations.find((afterRelation) => afterRelation.collection === currentRelation.collection && afterRelation.field === currentRelation.field);
100
+ const afterRelationSanitized = afterRelation ? sanitizeRelation(afterRelation) : undefined;
64
101
  return {
65
102
  collection: currentRelation.collection,
66
103
  field: currentRelation.field,
67
104
  related_collection: currentRelation.related_collection,
68
- diff: deepDiff.diff(sanitizeRelation(currentRelation), sanitizeRelation(afterRelation)),
105
+ diff: deepDiff.diff(sanitizeRelation(currentRelation), afterRelationSanitized),
69
106
  };
70
107
  }),
71
108
  ...after.relations
@@ -91,3 +128,10 @@ export function getSnapshotDiff(current, after) {
91
128
  diffedSnapshot.relations = diffedSnapshot.relations.filter((relation) => deletedCollections.includes(relation.collection) === false);
92
129
  return diffedSnapshot;
93
130
  }
131
+ function invertIndexed(field) {
132
+ const newSchema = { ...field.schema };
133
+ if ('is_indexed' in field.schema) {
134
+ newSchema.is_indexed = !field.schema.is_indexed;
135
+ }
136
+ return { ...field, schema: newSchema };
137
+ }
@@ -5,7 +5,7 @@ import { CollectionsService } from '../services/collections.js';
5
5
  import { FieldsService } from '../services/fields.js';
6
6
  import { RelationsService } from '../services/relations.js';
7
7
  import { getSchema } from './get-schema.js';
8
- import { sanitizeCollection, sanitizeField, sanitizeRelation } from './sanitize-schema.js';
8
+ import { sanitizeCollection, sanitizeField, sanitizeRelation, sanitizeSystemField } from './sanitize-schema.js';
9
9
  export async function getSnapshot(options) {
10
10
  const database = options?.database ?? getDatabase();
11
11
  const vendor = getDatabaseClient(database);
@@ -21,16 +21,19 @@ export async function getSnapshot(options) {
21
21
  const collectionsFiltered = collectionsRaw.filter((item) => excludeSystem(item) && excludeUntracked(item));
22
22
  const fieldsFiltered = fieldsRaw.filter((item) => excludeSystem(item) && excludeUntracked(item));
23
23
  const relationsFiltered = relationsRaw.filter((item) => excludeSystem(item) && excludeUntracked(item));
24
- const collectionsSorted = sortBy(mapValues(collectionsFiltered, sortDeep), ['collection']);
25
- const fieldsSorted = sortBy(mapValues(fieldsFiltered, sortDeep), ['collection', 'meta.id']).map(omitID);
26
- const relationsSorted = sortBy(mapValues(relationsFiltered, sortDeep), ['collection', 'meta.id']).map(omitID);
24
+ const systemFieldsFiltered = fieldsRaw.filter((item) => systemFieldWithIndex(item));
25
+ const collectionsSorted = sortBy(mapValues(collectionsFiltered, sortDeep), ['collection']).map((collection) => sanitizeCollection(collection));
26
+ const fieldsSorted = sortBy(mapValues(fieldsFiltered, sortDeep), ['collection', 'meta.id']).map((field) => sanitizeField(omitID(field)));
27
+ const systemFieldsSorted = sortBy(systemFieldsFiltered, ['collection', 'field']).map((field) => sanitizeSystemField(field));
28
+ const relationsSorted = sortBy(mapValues(relationsFiltered, sortDeep), ['collection', 'meta.id']).map((relation) => sanitizeRelation(omitID(relation)));
27
29
  return {
28
30
  version: 1,
29
31
  directus: version,
30
32
  vendor,
31
- collections: collectionsSorted.map((collection) => sanitizeCollection(collection)),
32
- fields: fieldsSorted.map((field) => sanitizeField(field)),
33
- relations: relationsSorted.map((relation) => sanitizeRelation(relation)),
33
+ collections: collectionsSorted,
34
+ fields: fieldsSorted,
35
+ systemFields: systemFieldsSorted,
36
+ relations: relationsSorted,
34
37
  };
35
38
  }
36
39
  function excludeSystem(item) {
@@ -38,6 +41,9 @@ function excludeSystem(item) {
38
41
  return false;
39
42
  return true;
40
43
  }
44
+ function systemFieldWithIndex(item) {
45
+ return item.meta?.system === true && item.schema?.is_indexed;
46
+ }
41
47
  function excludeUntracked(item) {
42
48
  if (item?.meta === null)
43
49
  return false;
@@ -1,5 +1,5 @@
1
1
  import type { Column } from '@directus/schema';
2
- import type { Field, Relation } from '@directus/types';
2
+ import type { Field, Relation, SnapshotCollection, SnapshotField, SnapshotRelation, SnapshotSystemField } from '@directus/types';
3
3
  import type { Collection } from '../types/index.js';
4
4
  /**
5
5
  * Pick certain database vendor specific collection properties that should be compared when performing diff
@@ -7,7 +7,7 @@ import type { Collection } from '../types/index.js';
7
7
  * @param collection collection to sanitize
8
8
  * @returns sanitized collection
9
9
  */
10
- export declare function sanitizeCollection(collection: Collection | undefined): Partial<Collection> | undefined;
10
+ export declare function sanitizeCollection(collection: Collection): SnapshotCollection;
11
11
  /**
12
12
  * Pick certain database vendor specific field properties that should be compared when performing diff
13
13
  *
@@ -15,7 +15,7 @@ export declare function sanitizeCollection(collection: Collection | undefined):
15
15
  * @param sanitizeAllSchema Whether or not the whole field schema should be sanitized. Mainly used to prevent modifying autoincrement fields
16
16
  * @returns sanitized field
17
17
  */
18
- export declare function sanitizeField(field: Field | undefined, sanitizeAllSchema?: boolean): Partial<Field> | undefined;
18
+ export declare function sanitizeField(field: Field, sanitizeAllSchema?: boolean): SnapshotField;
19
19
  export declare function sanitizeColumn(column: Column): Pick<Column, "table" | "foreign_key_table" | "foreign_key_column" | "name" | "data_type" | "default_value" | "max_length" | "numeric_precision" | "numeric_scale" | "is_nullable" | "is_unique" | "is_indexed" | "is_primary_key" | "is_generated" | "generation_expression" | "has_auto_increment">;
20
20
  /**
21
21
  * Pick certain database vendor specific relation properties that should be compared when performing diff
@@ -23,4 +23,11 @@ export declare function sanitizeColumn(column: Column): Pick<Column, "table" | "
23
23
  * @param relation relation to sanitize
24
24
  * @returns sanitized relation
25
25
  */
26
- export declare function sanitizeRelation(relation: Relation | undefined): Partial<Relation> | undefined;
26
+ export declare function sanitizeRelation(relation: Relation): SnapshotRelation;
27
+ /**
28
+ * Pick certain specific system field properties that should be compared when performing diff
29
+ *
30
+ * @param field field to sanitize
31
+ * @returns sanitized system field
32
+ */
33
+ export declare function sanitizeSystemField(field: Field | SnapshotSystemField): SnapshotSystemField;