directus 9.23.4 → 9.24.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 (54) hide show
  1. package/dist/app.js +3 -3
  2. package/dist/controllers/activity.js +1 -1
  3. package/dist/controllers/assets.js +1 -1
  4. package/dist/controllers/auth.js +3 -3
  5. package/dist/controllers/collections.js +1 -1
  6. package/dist/controllers/dashboards.js +2 -2
  7. package/dist/controllers/fields.js +1 -1
  8. package/dist/controllers/files.js +2 -2
  9. package/dist/controllers/flows.js +2 -2
  10. package/dist/controllers/folders.js +2 -2
  11. package/dist/controllers/items.js +2 -2
  12. package/dist/controllers/notifications.js +2 -2
  13. package/dist/controllers/operations.js +2 -2
  14. package/dist/controllers/panels.js +2 -2
  15. package/dist/controllers/permissions.js +2 -2
  16. package/dist/controllers/presets.js +2 -2
  17. package/dist/controllers/relations.js +1 -1
  18. package/dist/controllers/roles.js +2 -2
  19. package/dist/controllers/shares.js +1 -1
  20. package/dist/controllers/utils.js +2 -2
  21. package/dist/controllers/webhooks.js +2 -2
  22. package/dist/database/run-ast.js +2 -2
  23. package/dist/exceptions/database/dialects/mssql.js +2 -2
  24. package/dist/exceptions/database/dialects/mysql.js +6 -6
  25. package/dist/exceptions/database/record-not-unique.d.ts +1 -1
  26. package/dist/middleware/authenticate.d.ts +1 -1
  27. package/dist/middleware/authenticate.js +1 -1
  28. package/dist/middleware/collection-exists.js +1 -1
  29. package/dist/middleware/cors.js +1 -1
  30. package/dist/middleware/extract-token.js +1 -1
  31. package/dist/middleware/get-permissions.js +1 -1
  32. package/dist/middleware/schema.js +1 -1
  33. package/dist/middleware/use-collection.js +1 -1
  34. package/dist/middleware/validate-batch.js +1 -1
  35. package/dist/services/files.js +6 -6
  36. package/dist/services/graphql/index.js +70 -18
  37. package/dist/services/relations.js +4 -4
  38. package/dist/services/users.js +1 -1
  39. package/dist/utils/apply-diff.js +12 -12
  40. package/dist/utils/apply-query.js +1 -1
  41. package/dist/utils/get-ast-from-query.js +1 -1
  42. package/dist/utils/get-column-path.js +2 -1
  43. package/dist/utils/get-schema.js +3 -3
  44. package/dist/utils/get-snapshot-diff.js +1 -1
  45. package/dist/utils/parse-image-metadata.js +3 -3
  46. package/dist/utils/reduce-schema.js +5 -5
  47. package/dist/utils/should-skip-cache.js +12 -3
  48. package/dist/utils/strip-function.js +1 -1
  49. package/dist/utils/telemetry.d.ts +1 -0
  50. package/dist/utils/telemetry.js +30 -0
  51. package/dist/utils/validate-keys.js +1 -1
  52. package/package.json +13 -13
  53. package/dist/utils/track.d.ts +0 -1
  54. package/dist/utils/track.js +0 -81
package/dist/app.js CHANGED
@@ -81,7 +81,7 @@ const lodash_1 = require("lodash");
81
81
  const auth_2 = require("./auth");
82
82
  const cache_2 = require("./cache");
83
83
  const get_config_from_env_1 = require("./utils/get-config-from-env");
84
- const track_1 = require("./utils/track");
84
+ const telemetry_1 = require("./utils/telemetry");
85
85
  const url_1 = require("./utils/url");
86
86
  const validate_env_1 = require("./utils/validate-env");
87
87
  const validate_storage_1 = require("./utils/validate-storage");
@@ -198,7 +198,7 @@ async function createApp() {
198
198
  if (env_1.default['RATE_LIMITER_ENABLED'] === true) {
199
199
  app.use(rate_limiter_ip_1.default);
200
200
  }
201
- app.get('/server/ping', (req, res) => res.send('pong'));
201
+ app.get('/server/ping', (_req, res) => res.send('pong'));
202
202
  app.use(authenticate_1.default);
203
203
  app.use(check_ip_1.checkIP);
204
204
  app.use(sanitize_query_1.default);
@@ -243,7 +243,7 @@ async function createApp() {
243
243
  await emitter_1.default.emitInit('routes.after', { app });
244
244
  // Register all webhooks
245
245
  await (0, webhooks_2.init)();
246
- (0, track_1.track)('serverStarted');
246
+ (0, telemetry_1.collectTelemetry)();
247
247
  await emitter_1.default.emitInit('app.after', { app });
248
248
  return app;
249
249
  }
@@ -117,7 +117,7 @@ router.patch('/comment/:pk', (0, async_handler_1.default)(async (req, res, next)
117
117
  }
118
118
  return next();
119
119
  }), respond_1.respond);
120
- router.delete('/comment/:pk', (0, async_handler_1.default)(async (req, res, next) => {
120
+ router.delete('/comment/:pk', (0, async_handler_1.default)(async (req, _res, next) => {
121
121
  const service = new services_1.ActivityService({
122
122
  accountability: req.accountability,
123
123
  schema: req.schema,
@@ -103,7 +103,7 @@ router.get('/:pk/:filename?',
103
103
  }),
104
104
  // Return file
105
105
  (0, async_handler_1.default)(async (req, res) => {
106
- const id = req.params['pk']?.substring(0, 36);
106
+ const id = req.params['pk'].substring(0, 36);
107
107
  const service = new services_1.AssetsService({
108
108
  accountability: req.accountability,
109
109
  schema: req.schema,
@@ -107,7 +107,7 @@ router.post('/logout', (0, async_handler_1.default)(async (req, res, next) => {
107
107
  }
108
108
  return next();
109
109
  }), respond_1.respond);
110
- router.post('/password/request', (0, async_handler_1.default)(async (req, res, next) => {
110
+ router.post('/password/request', (0, async_handler_1.default)(async (req, _res, next) => {
111
111
  if (typeof req.body.email !== 'string') {
112
112
  throw new exceptions_1.InvalidPayloadException(`"email" field is required.`);
113
113
  }
@@ -136,7 +136,7 @@ router.post('/password/request', (0, async_handler_1.default)(async (req, res, n
136
136
  }
137
137
  }
138
138
  }), respond_1.respond);
139
- router.post('/password/reset', (0, async_handler_1.default)(async (req, res, next) => {
139
+ router.post('/password/reset', (0, async_handler_1.default)(async (req, _res, next) => {
140
140
  if (typeof req.body.token !== 'string') {
141
141
  throw new exceptions_1.InvalidPayloadException(`"token" field is required.`);
142
142
  }
@@ -157,7 +157,7 @@ router.post('/password/reset', (0, async_handler_1.default)(async (req, res, nex
157
157
  await service.resetPassword(req.body.token, req.body.password);
158
158
  return next();
159
159
  }), respond_1.respond);
160
- router.get('/', (0, async_handler_1.default)(async (req, res, next) => {
160
+ router.get('/', (0, async_handler_1.default)(async (_req, res, next) => {
161
161
  res.locals['payload'] = {
162
162
  data: (0, get_auth_providers_1.getAuthProviders)(),
163
163
  disableDefault: env_1.default['AUTH_DISABLE_DEFAULT'],
@@ -94,7 +94,7 @@ router.patch('/:collection', (0, async_handler_1.default)(async (req, res, next)
94
94
  }
95
95
  return next();
96
96
  }), respond_1.respond);
97
- router.delete('/:collection', (0, async_handler_1.default)(async (req, res, next) => {
97
+ router.delete('/:collection', (0, async_handler_1.default)(async (req, _res, next) => {
98
98
  const collectionsService = new services_1.CollectionsService({
99
99
  accountability: req.accountability,
100
100
  schema: req.schema,
@@ -116,7 +116,7 @@ router.patch('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
116
116
  }
117
117
  return next();
118
118
  }), respond_1.respond);
119
- router.delete('/', (0, async_handler_1.default)(async (req, res, next) => {
119
+ router.delete('/', (0, async_handler_1.default)(async (req, _res, next) => {
120
120
  const service = new services_1.DashboardsService({
121
121
  accountability: req.accountability,
122
122
  schema: req.schema,
@@ -133,7 +133,7 @@ router.delete('/', (0, async_handler_1.default)(async (req, res, next) => {
133
133
  }
134
134
  return next();
135
135
  }), respond_1.respond);
136
- router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
136
+ router.delete('/:pk', (0, async_handler_1.default)(async (req, _res, next) => {
137
137
  const service = new services_1.DashboardsService({
138
138
  accountability: req.accountability,
139
139
  schema: req.schema,
@@ -149,7 +149,7 @@ router.patch('/:collection/:field', collection_exists_1.default, (0, async_handl
149
149
  }
150
150
  return next();
151
151
  }), respond_1.respond);
152
- router.delete('/:collection/:field', collection_exists_1.default, (0, async_handler_1.default)(async (req, res, next) => {
152
+ router.delete('/:collection/:field', collection_exists_1.default, (0, async_handler_1.default)(async (req, _res, next) => {
153
153
  const service = new fields_1.FieldsService({
154
154
  accountability: req.accountability,
155
155
  schema: req.schema,
@@ -246,7 +246,7 @@ router.patch('/:pk', (0, async_handler_1.default)(exports.multipartHandler), (0,
246
246
  }
247
247
  return next();
248
248
  }), respond_1.respond);
249
- router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, res, next) => {
249
+ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, _res, next) => {
250
250
  const service = new services_1.FilesService({
251
251
  accountability: req.accountability,
252
252
  schema: req.schema,
@@ -263,7 +263,7 @@ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_hand
263
263
  }
264
264
  return next();
265
265
  }), respond_1.respond);
266
- router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
266
+ router.delete('/:pk', (0, async_handler_1.default)(async (req, _res, next) => {
267
267
  const service = new services_1.FilesService({
268
268
  accountability: req.accountability,
269
269
  schema: req.schema,
@@ -135,7 +135,7 @@ router.patch('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
135
135
  }
136
136
  return next();
137
137
  }), respond_1.respond);
138
- router.delete('/', (0, async_handler_1.default)(async (req, res, next) => {
138
+ router.delete('/', (0, async_handler_1.default)(async (req, _res, next) => {
139
139
  const service = new services_1.FlowsService({
140
140
  accountability: req.accountability,
141
141
  schema: req.schema,
@@ -152,7 +152,7 @@ router.delete('/', (0, async_handler_1.default)(async (req, res, next) => {
152
152
  }
153
153
  return next();
154
154
  }), respond_1.respond);
155
- router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
155
+ router.delete('/:pk', (0, async_handler_1.default)(async (req, _res, next) => {
156
156
  const service = new services_1.FlowsService({
157
157
  accountability: req.accountability,
158
158
  schema: req.schema,
@@ -125,7 +125,7 @@ router.patch('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
125
125
  }
126
126
  return next();
127
127
  }), respond_1.respond);
128
- router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, res, next) => {
128
+ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, _res, next) => {
129
129
  const service = new services_1.FoldersService({
130
130
  accountability: req.accountability,
131
131
  schema: req.schema,
@@ -142,7 +142,7 @@ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_hand
142
142
  }
143
143
  return next();
144
144
  }), respond_1.respond);
145
- router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
145
+ router.delete('/:pk', (0, async_handler_1.default)(async (req, _res, next) => {
146
146
  const service = new services_1.FoldersService({
147
147
  accountability: req.accountability,
148
148
  schema: req.schema,
@@ -151,7 +151,7 @@ router.patch('/:collection/:pk', collection_exists_1.default, (0, async_handler_
151
151
  }
152
152
  return next();
153
153
  }), respond_1.respond);
154
- router.delete('/:collection', collection_exists_1.default, (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, res, next) => {
154
+ router.delete('/:collection', collection_exists_1.default, (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, _res, next) => {
155
155
  if (req.params['collection'].startsWith('directus_'))
156
156
  throw new exceptions_1.ForbiddenException();
157
157
  const service = new services_1.ItemsService(req.collection, {
@@ -170,7 +170,7 @@ router.delete('/:collection', collection_exists_1.default, (0, validate_batch_1.
170
170
  }
171
171
  return next();
172
172
  }), respond_1.respond);
173
- router.delete('/:collection/:pk', collection_exists_1.default, (0, async_handler_1.default)(async (req, res, next) => {
173
+ router.delete('/:collection/:pk', collection_exists_1.default, (0, async_handler_1.default)(async (req, _res, next) => {
174
174
  if (req.params['collection'].startsWith('directus_'))
175
175
  throw new exceptions_1.ForbiddenException();
176
176
  const service = new services_1.ItemsService(req.collection, {
@@ -125,7 +125,7 @@ router.patch('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
125
125
  }
126
126
  return next();
127
127
  }), respond_1.respond);
128
- router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, res, next) => {
128
+ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, _res, next) => {
129
129
  const service = new services_1.NotificationsService({
130
130
  accountability: req.accountability,
131
131
  schema: req.schema,
@@ -142,7 +142,7 @@ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_hand
142
142
  }
143
143
  return next();
144
144
  }), respond_1.respond);
145
- router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
145
+ router.delete('/:pk', (0, async_handler_1.default)(async (req, _res, next) => {
146
146
  const service = new services_1.NotificationsService({
147
147
  accountability: req.accountability,
148
148
  schema: req.schema,
@@ -116,7 +116,7 @@ router.patch('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
116
116
  }
117
117
  return next();
118
118
  }), respond_1.respond);
119
- router.delete('/', (0, async_handler_1.default)(async (req, res, next) => {
119
+ router.delete('/', (0, async_handler_1.default)(async (req, _res, next) => {
120
120
  const service = new services_1.OperationsService({
121
121
  accountability: req.accountability,
122
122
  schema: req.schema,
@@ -133,7 +133,7 @@ router.delete('/', (0, async_handler_1.default)(async (req, res, next) => {
133
133
  }
134
134
  return next();
135
135
  }), respond_1.respond);
136
- router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
136
+ router.delete('/:pk', (0, async_handler_1.default)(async (req, _res, next) => {
137
137
  const service = new services_1.OperationsService({
138
138
  accountability: req.accountability,
139
139
  schema: req.schema,
@@ -116,7 +116,7 @@ router.patch('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
116
116
  }
117
117
  return next();
118
118
  }), respond_1.respond);
119
- router.delete('/', (0, async_handler_1.default)(async (req, res, next) => {
119
+ router.delete('/', (0, async_handler_1.default)(async (req, _res, next) => {
120
120
  const service = new services_1.PanelsService({
121
121
  accountability: req.accountability,
122
122
  schema: req.schema,
@@ -133,7 +133,7 @@ router.delete('/', (0, async_handler_1.default)(async (req, res, next) => {
133
133
  }
134
134
  return next();
135
135
  }), respond_1.respond);
136
- router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
136
+ router.delete('/:pk', (0, async_handler_1.default)(async (req, _res, next) => {
137
137
  const service = new services_1.PanelsService({
138
138
  accountability: req.accountability,
139
139
  schema: req.schema,
@@ -127,7 +127,7 @@ router.patch('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
127
127
  }
128
128
  return next();
129
129
  }), respond_1.respond);
130
- router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, res, next) => {
130
+ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, _res, next) => {
131
131
  const service = new services_1.PermissionsService({
132
132
  accountability: req.accountability,
133
133
  schema: req.schema,
@@ -144,7 +144,7 @@ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_hand
144
144
  }
145
145
  return next();
146
146
  }), respond_1.respond);
147
- router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
147
+ router.delete('/:pk', (0, async_handler_1.default)(async (req, _res, next) => {
148
148
  const service = new services_1.PermissionsService({
149
149
  accountability: req.accountability,
150
150
  schema: req.schema,
@@ -125,7 +125,7 @@ router.patch('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
125
125
  }
126
126
  return next();
127
127
  }), respond_1.respond);
128
- router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, res, next) => {
128
+ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, _res, next) => {
129
129
  const service = new services_1.PresetsService({
130
130
  accountability: req.accountability,
131
131
  schema: req.schema,
@@ -142,7 +142,7 @@ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_hand
142
142
  }
143
143
  return next();
144
144
  }), respond_1.respond);
145
- router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
145
+ router.delete('/:pk', (0, async_handler_1.default)(async (req, _res, next) => {
146
146
  const service = new services_1.PresetsService({
147
147
  accountability: req.accountability,
148
148
  schema: req.schema,
@@ -106,7 +106,7 @@ router.patch('/:collection/:field', collection_exists_1.default, (0, async_handl
106
106
  }
107
107
  return next();
108
108
  }), respond_1.respond);
109
- router.delete('/:collection/:field', collection_exists_1.default, (0, async_handler_1.default)(async (req, res, next) => {
109
+ router.delete('/:collection/:field', collection_exists_1.default, (0, async_handler_1.default)(async (req, _res, next) => {
110
110
  const service = new services_1.RelationsService({
111
111
  accountability: req.accountability,
112
112
  schema: req.schema,
@@ -116,7 +116,7 @@ router.patch('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
116
116
  }
117
117
  return next();
118
118
  }), respond_1.respond);
119
- router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, res, next) => {
119
+ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, _res, next) => {
120
120
  const service = new services_1.RolesService({
121
121
  accountability: req.accountability,
122
122
  schema: req.schema,
@@ -133,7 +133,7 @@ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_hand
133
133
  }
134
134
  return next();
135
135
  }), respond_1.respond);
136
- router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
136
+ router.delete('/:pk', (0, async_handler_1.default)(async (req, _res, next) => {
137
137
  const service = new services_1.RolesService({
138
138
  accountability: req.accountability,
139
139
  schema: req.schema,
@@ -38,7 +38,7 @@ const sharedInviteSchema = joi_1.default.object({
38
38
  share: joi_1.default.string().required(),
39
39
  emails: joi_1.default.array().items(joi_1.default.string()),
40
40
  }).unknown();
41
- router.post('/invite', (0, async_handler_1.default)(async (req, res, next) => {
41
+ router.post('/invite', (0, async_handler_1.default)(async (req, _res, next) => {
42
42
  const service = new services_1.SharesService({
43
43
  schema: req.schema,
44
44
  accountability: req.accountability,
@@ -55,7 +55,7 @@ router.post('/sort/:collection', collection_exists_1.default, (0, async_handler_
55
55
  await service.sort(req.collection, req.body);
56
56
  return res.status(200).end();
57
57
  }));
58
- router.post('/revert/:revision', (0, async_handler_1.default)(async (req, res, next) => {
58
+ router.post('/revert/:revision', (0, async_handler_1.default)(async (req, _res, next) => {
59
59
  const service = new services_1.RevisionsService({
60
60
  accountability: req.accountability,
61
61
  schema: req.schema,
@@ -93,7 +93,7 @@ router.post('/import/:collection', collection_exists_1.default, (0, async_handle
93
93
  busboy.on('error', (err) => next(err));
94
94
  req.pipe(busboy);
95
95
  }));
96
- router.post('/export/:collection', collection_exists_1.default, (0, async_handler_1.default)(async (req, res, next) => {
96
+ router.post('/export/:collection', collection_exists_1.default, (0, async_handler_1.default)(async (req, _res, next) => {
97
97
  if (!req.body.query) {
98
98
  throw new exceptions_1.InvalidPayloadException(`"query" is required.`);
99
99
  }
@@ -113,7 +113,7 @@ router.patch('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
113
113
  }
114
114
  return next();
115
115
  }), respond_1.respond);
116
- router.delete('/', (0, async_handler_1.default)(async (req, res, next) => {
116
+ router.delete('/', (0, async_handler_1.default)(async (req, _res, next) => {
117
117
  const service = new services_1.WebhooksService({
118
118
  accountability: req.accountability,
119
119
  schema: req.schema,
@@ -130,7 +130,7 @@ router.delete('/', (0, async_handler_1.default)(async (req, res, next) => {
130
130
  }
131
131
  return next();
132
132
  }), respond_1.respond);
133
- router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
133
+ router.delete('/:pk', (0, async_handler_1.default)(async (req, _res, next) => {
134
134
  const service = new services_1.WebhooksService({
135
135
  accountability: req.accountability,
136
136
  schema: req.schema,
@@ -280,7 +280,7 @@ function applyParentFilters(schema, nestedCollectionNodes, parentItem) {
280
280
  continue;
281
281
  if (nestedNode.type === 'm2o') {
282
282
  const foreignField = schema.collections[nestedNode.relation.related_collection].primary;
283
- const foreignIds = (0, lodash_1.uniq)(parentItems.map((res) => res[nestedNode.relation.field])).filter((id) => id);
283
+ const foreignIds = (0, lodash_1.uniq)(parentItems.map((res) => res[nestedNode.relation.field])).filter((id) => !(0, lodash_1.isNil)(id));
284
284
  (0, lodash_1.merge)(nestedNode, { query: { filter: { [foreignField]: { _in: foreignIds } } } });
285
285
  }
286
286
  else if (nestedNode.type === 'o2m') {
@@ -302,7 +302,7 @@ function applyParentFilters(schema, nestedCollectionNodes, parentItem) {
302
302
  });
303
303
  }
304
304
  const foreignField = nestedNode.relation.field;
305
- const foreignIds = (0, lodash_1.uniq)(parentItems.map((res) => res[nestedNode.parentKey])).filter((id) => id);
305
+ const foreignIds = (0, lodash_1.uniq)(parentItems.map((res) => res[nestedNode.parentKey])).filter((id) => !(0, lodash_1.isNil)(id));
306
306
  (0, lodash_1.merge)(nestedNode, { query: { filter: { [foreignField]: { _in: foreignIds } } } });
307
307
  }
308
308
  else if (nestedNode.type === 'a2o') {
@@ -54,8 +54,8 @@ async function uniqueViolation(error) {
54
54
  const parenMatches = error.message.match(betweenParens);
55
55
  if (!quoteMatches || !parenMatches)
56
56
  return error;
57
- const keyName = quoteMatches[1]?.slice(1, -1);
58
- let collection = quoteMatches[0]?.slice(1, -1);
57
+ const keyName = quoteMatches[1].slice(1, -1);
58
+ let collection = quoteMatches[0].slice(1, -1);
59
59
  let field = null;
60
60
  if (keyName) {
61
61
  const database = (0, database_1.default)();
@@ -49,7 +49,7 @@ function uniqueViolation(error) {
49
49
  */
50
50
  /** MySQL 8+ style error message */
51
51
  if (matches[1].includes('.')) {
52
- const collection = matches[1]?.slice(1, -1).split('.')[0];
52
+ const collection = matches[1].slice(1, -1).split('.')[0];
53
53
  let field = null;
54
54
  const indexName = matches[1]?.slice(1, -1).split('.')[1];
55
55
  if (indexName?.startsWith(`${collection}_`) && indexName.endsWith('_unique')) {
@@ -64,7 +64,7 @@ function uniqueViolation(error) {
64
64
  }
65
65
  else {
66
66
  /** MySQL 5.7 style error message */
67
- const indexName = matches[1]?.slice(1, -1);
67
+ const indexName = matches[1].slice(1, -1);
68
68
  const collection = indexName.split('_')[0];
69
69
  let field = null;
70
70
  if (indexName?.startsWith(`${collection}_`) && indexName.endsWith('_unique')) {
@@ -127,9 +127,9 @@ function foreignKeyViolation(error) {
127
127
  const parenMatches = error.sql.match(betweenParens);
128
128
  if (!tickMatches || !parenMatches)
129
129
  return error;
130
- const collection = tickMatches[1]?.slice(1, -1);
131
- const field = tickMatches[3]?.slice(1, -1);
132
- const invalid = parenMatches[1]?.slice(1, -1);
130
+ const collection = tickMatches[1].slice(1, -1);
131
+ const field = tickMatches[3].slice(1, -1);
132
+ const invalid = parenMatches[1].slice(1, -1);
133
133
  return new invalid_foreign_key_1.InvalidForeignKeyException(field, {
134
134
  collection,
135
135
  field,
@@ -143,6 +143,6 @@ function containsNullValues(error) {
143
143
  const tickMatches = error.sql.match(betweenTicks);
144
144
  if (!tickMatches)
145
145
  return error;
146
- const field = tickMatches[1]?.slice(1, -1);
146
+ const field = tickMatches[1].slice(1, -1);
147
147
  return new contains_null_values_1.ContainsNullValuesException(field);
148
148
  }
@@ -2,7 +2,7 @@ import { BaseException } from '@directus/shared/exceptions';
2
2
  type Extensions = {
3
3
  collection: string;
4
4
  field: string | null;
5
- invalid?: string;
5
+ invalid?: string | undefined;
6
6
  };
7
7
  export declare class RecordNotUniqueException extends BaseException {
8
8
  constructor(field: string | null, extensions?: Extensions);
@@ -3,6 +3,6 @@ import type { NextFunction, Request, Response } from 'express';
3
3
  /**
4
4
  * Verify the passed JWT and assign the user ID and role to `req`
5
5
  */
6
- export declare const handler: (req: Request, res: Response, next: NextFunction) => Promise<void>;
6
+ export declare const handler: (req: Request, _res: Response, next: NextFunction) => Promise<void>;
7
7
  declare const _default: (req: Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: NextFunction) => Promise<void>;
8
8
  export default _default;
@@ -16,7 +16,7 @@ const jwt_1 = require("../utils/jwt");
16
16
  /**
17
17
  * Verify the passed JWT and assign the user ID and role to `req`
18
18
  */
19
- const handler = async (req, res, next) => {
19
+ const handler = async (req, _res, next) => {
20
20
  const defaultAccountability = {
21
21
  user: null,
22
22
  role: null,
@@ -9,7 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  const collections_1 = require("../database/system-data/collections");
10
10
  const exceptions_1 = require("../exceptions");
11
11
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
12
- const collectionExists = (0, async_handler_1.default)(async (req, res, next) => {
12
+ const collectionExists = (0, async_handler_1.default)(async (req, _res, next) => {
13
13
  if (!req.params['collection'])
14
14
  return next();
15
15
  if (req.params['collection'] in req.schema.collections === false) {
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const cors_1 = __importDefault(require("cors"));
7
7
  const env_1 = __importDefault(require("../env"));
8
- let corsMiddleware = (req, res, next) => next();
8
+ let corsMiddleware = (_req, _res, next) => next();
9
9
  if (env_1.default['CORS_ENABLED'] === true) {
10
10
  corsMiddleware = (0, cors_1.default)({
11
11
  origin: env_1.default['CORS_ORIGIN'] || true,
@@ -8,7 +8,7 @@
8
8
  * and store in req.token
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- const extractToken = (req, res, next) => {
11
+ const extractToken = (req, _res, next) => {
12
12
  let token = null;
13
13
  if (req.query && req.query['access_token']) {
14
14
  token = req.query['access_token'];
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
7
7
  const get_permissions_1 = require("../utils/get-permissions");
8
- const getPermissions = (0, async_handler_1.default)(async (req, res, next) => {
8
+ const getPermissions = (0, async_handler_1.default)(async (req, _res, next) => {
9
9
  if (!req.accountability) {
10
10
  throw new Error('getPermissions middleware needs to be called after authenticate');
11
11
  }
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
7
7
  const get_schema_1 = require("../utils/get-schema");
8
- const schema = (0, async_handler_1.default)(async (req, res, next) => {
8
+ const schema = (0, async_handler_1.default)(async (req, _res, next) => {
9
9
  req.schema = await (0, get_schema_1.getSchema)();
10
10
  return next();
11
11
  });
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
7
- const useCollection = (collection) => (0, async_handler_1.default)(async (req, res, next) => {
7
+ const useCollection = (collection) => (0, async_handler_1.default)(async (req, _res, next) => {
8
8
  req.collection = collection;
9
9
  next();
10
10
  });
@@ -9,7 +9,7 @@ const exceptions_1 = require("../exceptions");
9
9
  const exceptions_2 = require("@directus/shared/exceptions");
10
10
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
11
11
  const sanitize_query_1 = require("../utils/sanitize-query");
12
- const validateBatch = (scope) => (0, async_handler_1.default)(async (req, res, next) => {
12
+ const validateBatch = (scope) => (0, async_handler_1.default)(async (req, _res, next) => {
13
13
  if (req.method.toLowerCase() === 'get') {
14
14
  req.body = {};
15
15
  return next();
@@ -79,12 +79,12 @@ class FilesService extends items_1.ItemsService {
79
79
  if (['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/tiff'].includes(payload.type)) {
80
80
  const stream = await storage.location(data.storage).read(payload.filename_disk);
81
81
  const { height, width, description, title, tags, metadata } = await this.getMetadata(stream);
82
- payload.height = height ?? null;
83
- payload.width = width ?? null;
84
- payload.description = description ?? null;
85
- payload.title = title ?? null;
86
- payload.tags = tags ?? null;
87
- payload.metadata = metadata ?? null;
82
+ payload.height ??= height ?? null;
83
+ payload.width ??= width ?? null;
84
+ payload.description ??= description ?? null;
85
+ payload.title ??= title ?? null;
86
+ payload.tags ??= tags ?? null;
87
+ payload.metadata ??= metadata ?? null;
88
88
  }
89
89
  // We do this in a service without accountability. Even if you don't have update permissions to the file,
90
90
  // we still want to be able to set the extracted values from the file on create
@@ -378,7 +378,7 @@ class GraphQLService {
378
378
  type: new graphql_1.GraphQLUnionType({
379
379
  name: `${relation.collection}_${relation.field}_union`,
380
380
  types: relation.meta.one_allowed_collections.map((collection) => CollectionTypes[collection].getType()),
381
- resolveType(value, context, info) {
381
+ resolveType(_value, context, info) {
382
382
  let path = [];
383
383
  let currentPath = info.path;
384
384
  while (currentPath.prev) {
@@ -959,9 +959,7 @@ class GraphQLService {
959
959
  resolve: async ({ args, info }) => await self.resolveMutation(args, info),
960
960
  };
961
961
  if (collectionIsReadable) {
962
- resolverDefinition.args = ReadCollectionTypes[collection.collection]
963
- .getResolver(collection.collection)
964
- .getArgs();
962
+ resolverDefinition.args = ReadCollectionTypes[collection.collection].getResolver(collection.collection).getArgs();
965
963
  }
966
964
  CreateCollectionTypes[collection.collection].addResolver(resolverDefinition);
967
965
  CreateCollectionTypes[collection.collection].addResolver({
@@ -970,17 +968,13 @@ class GraphQLService {
970
968
  resolve: async ({ args, info }) => await self.resolveMutation(args, info),
971
969
  });
972
970
  CreateCollectionTypes[collection.collection].getResolver(`create_${collection.collection}_items`).addArgs({
973
- ...CreateCollectionTypes[collection.collection]
974
- .getResolver(`create_${collection.collection}_items`)
975
- .getArgs(),
971
+ ...CreateCollectionTypes[collection.collection].getResolver(`create_${collection.collection}_items`).getArgs(),
976
972
  data: [
977
973
  (0, graphql_compose_1.toInputObjectType)(CreateCollectionTypes[collection.collection]).setTypeName(`create_${collection.collection}_input`).NonNull,
978
974
  ],
979
975
  });
980
976
  CreateCollectionTypes[collection.collection].getResolver(`create_${collection.collection}_item`).addArgs({
981
- ...CreateCollectionTypes[collection.collection]
982
- .getResolver(`create_${collection.collection}_item`)
983
- .getArgs(),
977
+ ...CreateCollectionTypes[collection.collection].getResolver(`create_${collection.collection}_item`).getArgs(),
984
978
  data: (0, graphql_compose_1.toInputObjectType)(CreateCollectionTypes[collection.collection]).setTypeName(`create_${collection.collection}_input`).NonNull,
985
979
  });
986
980
  }
@@ -1343,7 +1337,7 @@ class GraphQLService {
1343
1337
  if (!query.deep)
1344
1338
  query.deep = {};
1345
1339
  const args = this.parseArgs(selection.arguments, variableValues);
1346
- (0, lodash_1.set)(query.deep, currentAlias ?? current, (0, lodash_1.merge)({}, (0, lodash_1.get)(query.deep, currentAlias ?? current), (0, lodash_1.mapKeys)((0, sanitize_query_1.sanitizeQuery)(args, this.accountability), (value, key) => `_${key}`)));
1340
+ (0, lodash_1.set)(query.deep, currentAlias ?? current, (0, lodash_1.merge)({}, (0, lodash_1.get)(query.deep, currentAlias ?? current), (0, lodash_1.mapKeys)((0, sanitize_query_1.sanitizeQuery)(args, this.accountability), (_value, key) => `_${key}`)));
1347
1341
  }
1348
1342
  }
1349
1343
  }
@@ -1498,15 +1492,60 @@ class GraphQLService {
1498
1492
  const ServerInfo = schemaComposer.createObjectTC({
1499
1493
  name: 'server_info',
1500
1494
  fields: {
1501
- project_name: { type: graphql_1.GraphQLString },
1502
- project_logo: { type: graphql_1.GraphQLString },
1503
- project_color: { type: graphql_1.GraphQLString },
1504
- project_foreground: { type: graphql_1.GraphQLString },
1505
- project_background: { type: graphql_1.GraphQLString },
1506
- project_note: { type: graphql_1.GraphQLString },
1507
- custom_css: { type: graphql_1.GraphQLString },
1495
+ project: {
1496
+ type: new graphql_1.GraphQLObjectType({
1497
+ name: 'server_info_project',
1498
+ fields: {
1499
+ project_name: { type: graphql_1.GraphQLString },
1500
+ project_descriptor: { type: graphql_1.GraphQLString },
1501
+ project_logo: { type: graphql_1.GraphQLString },
1502
+ project_color: { type: graphql_1.GraphQLString },
1503
+ default_language: { type: graphql_1.GraphQLString },
1504
+ public_foreground: { type: graphql_1.GraphQLString },
1505
+ public_background: { type: graphql_1.GraphQLString },
1506
+ public_note: { type: graphql_1.GraphQLString },
1507
+ custom_css: { type: graphql_1.GraphQLString },
1508
+ },
1509
+ }),
1510
+ },
1508
1511
  },
1509
1512
  });
1513
+ if (this.accountability?.user) {
1514
+ ServerInfo.addFields({
1515
+ rateLimit: env_1.default['RATE_LIMITER_ENABLED']
1516
+ ? {
1517
+ type: new graphql_1.GraphQLObjectType({
1518
+ name: 'server_info_rate_limit',
1519
+ fields: {
1520
+ points: { type: graphql_1.GraphQLInt },
1521
+ duration: { type: graphql_1.GraphQLInt },
1522
+ },
1523
+ }),
1524
+ }
1525
+ : graphql_1.GraphQLBoolean,
1526
+ rateLimitGlobal: env_1.default['RATE_LIMITER_GLOBAL_ENABLED']
1527
+ ? {
1528
+ type: new graphql_1.GraphQLObjectType({
1529
+ name: 'server_info_rate_limit_global',
1530
+ fields: {
1531
+ points: { type: graphql_1.GraphQLInt },
1532
+ duration: { type: graphql_1.GraphQLInt },
1533
+ },
1534
+ }),
1535
+ }
1536
+ : graphql_1.GraphQLBoolean,
1537
+ flows: {
1538
+ type: new graphql_1.GraphQLObjectType({
1539
+ name: 'server_info_flows',
1540
+ fields: {
1541
+ execAllowedModules: {
1542
+ type: new graphql_1.GraphQLList(graphql_1.GraphQLString),
1543
+ },
1544
+ },
1545
+ }),
1546
+ },
1547
+ });
1548
+ }
1510
1549
  if (this.accountability?.admin === true) {
1511
1550
  ServerInfo.addFields({
1512
1551
  directus: {
@@ -1860,6 +1899,19 @@ class GraphQLService {
1860
1899
  return true;
1861
1900
  },
1862
1901
  },
1902
+ utils_random_string: {
1903
+ type: graphql_1.GraphQLString,
1904
+ args: {
1905
+ length: graphql_1.GraphQLInt,
1906
+ },
1907
+ resolve: async (_, args) => {
1908
+ const { nanoid } = await import('nanoid');
1909
+ if (args['length'] && Number(args['length']) > 500) {
1910
+ throw new exceptions_1.InvalidPayloadException(`"length" can't be more than 500 characters`);
1911
+ }
1912
+ return nanoid(args['length'] ? Number(args['length']) : 32);
1913
+ },
1914
+ },
1863
1915
  utils_hash_generate: {
1864
1916
  type: graphql_1.GraphQLString,
1865
1917
  args: {
@@ -442,15 +442,15 @@ class RelationsService {
442
442
  collectionsAllowed = false;
443
443
  }
444
444
  if (!allowedFields[relation.collection] ||
445
- (allowedFields[relation.collection].includes('*') === false &&
446
- allowedFields[relation.collection].includes(relation.field) === false)) {
445
+ (allowedFields[relation.collection]?.includes('*') === false &&
446
+ allowedFields[relation.collection]?.includes(relation.field) === false)) {
447
447
  fieldsAllowed = false;
448
448
  }
449
449
  if (relation.related_collection &&
450
450
  relation.meta?.one_field &&
451
451
  (!allowedFields[relation.related_collection] ||
452
- (allowedFields[relation.related_collection].includes('*') === false &&
453
- allowedFields[relation.related_collection].includes(relation.meta.one_field) === false))) {
452
+ (allowedFields[relation.related_collection]?.includes('*') === false &&
453
+ allowedFields[relation.related_collection]?.includes(relation.meta.one_field) === false))) {
454
454
  fieldsAllowed = false;
455
455
  }
456
456
  return collectionsAllowed && fieldsAllowed;
@@ -335,7 +335,7 @@ class UsersService extends items_1.ItemsService {
335
335
  const token = jsonwebtoken_1.default.sign(payload, env_1.default['SECRET'], { expiresIn: '1d', issuer: 'directus' });
336
336
  const acceptURL = url
337
337
  ? new url_1.Url(url).setQuery('token', token).toString()
338
- : new url_1.Url(env_1.default['PUBLIC_URL']).addPath('admin', 'reset-password').setQuery('token', token);
338
+ : new url_1.Url(env_1.default['PUBLIC_URL']).addPath('admin', 'reset-password').setQuery('token', token).toString();
339
339
  const subjectLine = subject ? subject : 'Password Reset Request';
340
340
  await mailService.send({
341
341
  to: email,
@@ -27,7 +27,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
27
27
  const getNestedCollectionsToDelete = (currentLevelCollection) => snapshotDiff.collections.filter(({ diff }) => diff[0].lhs?.meta?.group === currentLevelCollection);
28
28
  const createCollections = async (collections) => {
29
29
  for (const { collection, diff } of collections) {
30
- if (diff?.[0].kind === types_1.DiffKind.NEW && diff[0].rhs) {
30
+ if (diff?.[0]?.kind === types_1.DiffKind.NEW && diff[0].rhs) {
31
31
  // We'll nest the to-be-created fields in the same collection creation, to prevent
32
32
  // creating a collection without a primary key
33
33
  const fields = snapshotDiff.fields
@@ -64,7 +64,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
64
64
  };
65
65
  const deleteCollections = async (collections) => {
66
66
  for (const { collection, diff } of collections) {
67
- if (diff?.[0].kind === types_1.DiffKind.DELETE) {
67
+ if (diff?.[0]?.kind === types_1.DiffKind.DELETE) {
68
68
  const relations = schema.relations.filter((r) => r.related_collection === collection || r.collection === collection);
69
69
  if (relations.length > 0) {
70
70
  const relationsService = new services_1.RelationsService({ knex: trx, schema });
@@ -94,7 +94,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
94
94
  // Finds all collections that need to be created
95
95
  const filterCollectionsForCreation = ({ diff }) => {
96
96
  // Check new collections only
97
- const isNewCollection = diff[0].kind === types_1.DiffKind.NEW;
97
+ const isNewCollection = diff[0]?.kind === types_1.DiffKind.NEW;
98
98
  if (!isNewCollection)
99
99
  return false;
100
100
  // Create now if no group
@@ -111,7 +111,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
111
111
  // TopLevelCollection - I exist in current schema
112
112
  // NestedCollection - I exist in snapshotDiff as a new collection
113
113
  // TheCurrentCollectionInIteration - I exist in snapshotDiff as a new collection but will be created as part of NestedCollection
114
- const parentWillBeCreatedInThisApply = snapshotDiff.collections.filter(({ collection, diff }) => diff[0].kind === types_1.DiffKind.NEW && collection === groupName).length > 0;
114
+ const parentWillBeCreatedInThisApply = snapshotDiff.collections.filter(({ collection, diff }) => diff[0]?.kind === types_1.DiffKind.NEW && collection === groupName).length > 0;
115
115
  // Has group, but parent is not new, parent is also not being created in this snapshot apply
116
116
  if (parentExists && !parentWillBeCreatedInThisApply)
117
117
  return true;
@@ -121,9 +121,9 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
121
121
  // then continue with nested collections recursively
122
122
  await createCollections(snapshotDiff.collections.filter(filterCollectionsForCreation));
123
123
  // delete top level collections (no group) first, then continue with nested collections recursively
124
- await deleteCollections(snapshotDiff.collections.filter(({ diff }) => diff[0].kind === types_1.DiffKind.DELETE && diff[0].lhs.meta?.group === null));
124
+ await deleteCollections(snapshotDiff.collections.filter(({ diff }) => diff[0]?.kind === types_1.DiffKind.DELETE && diff[0].lhs.meta?.group === null));
125
125
  for (const { collection, diff } of snapshotDiff.collections) {
126
- if (diff?.[0].kind === types_1.DiffKind.EDIT || diff?.[0].kind === types_1.DiffKind.ARRAY) {
126
+ if (diff?.[0]?.kind === types_1.DiffKind.EDIT || diff?.[0]?.kind === types_1.DiffKind.ARRAY) {
127
127
  const currentCollection = currentSnapshot.collections.find((field) => {
128
128
  return field.collection === collection;
129
129
  });
@@ -147,7 +147,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
147
147
  schema: await (0, get_schema_1.getSchema)({ database: trx, bypassCache: true }),
148
148
  });
149
149
  for (const { collection, field, diff } of snapshotDiff.fields) {
150
- if (diff?.[0].kind === types_1.DiffKind.NEW && !isNestedMetaUpdate(diff?.[0])) {
150
+ if (diff?.[0]?.kind === types_1.DiffKind.NEW && !isNestedMetaUpdate(diff?.[0])) {
151
151
  try {
152
152
  await fieldsService.createField(collection, diff[0].rhs, undefined, mutationOptions);
153
153
  }
@@ -156,7 +156,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
156
156
  throw err;
157
157
  }
158
158
  }
159
- if (diff?.[0].kind === types_1.DiffKind.EDIT || diff?.[0].kind === types_1.DiffKind.ARRAY || isNestedMetaUpdate(diff?.[0])) {
159
+ if (diff?.[0]?.kind === types_1.DiffKind.EDIT || diff?.[0]?.kind === types_1.DiffKind.ARRAY || isNestedMetaUpdate(diff[0])) {
160
160
  const currentField = currentSnapshot.fields.find((snapshotField) => {
161
161
  return snapshotField.collection === collection && snapshotField.field === field;
162
162
  });
@@ -174,7 +174,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
174
174
  }
175
175
  }
176
176
  }
177
- if (diff?.[0].kind === types_1.DiffKind.DELETE && !isNestedMetaUpdate(diff?.[0])) {
177
+ if (diff?.[0]?.kind === types_1.DiffKind.DELETE && !isNestedMetaUpdate(diff?.[0])) {
178
178
  try {
179
179
  await fieldsService.deleteField(collection, field, mutationOptions);
180
180
  }
@@ -196,7 +196,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
196
196
  for (const diffEdit of diff) {
197
197
  (0, lodash_1.set)(structure, diffEdit.path, undefined);
198
198
  }
199
- if (diff?.[0].kind === types_1.DiffKind.NEW) {
199
+ if (diff?.[0]?.kind === types_1.DiffKind.NEW) {
200
200
  try {
201
201
  await relationsService.createOne(diff[0].rhs, mutationOptions);
202
202
  }
@@ -205,7 +205,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
205
205
  throw err;
206
206
  }
207
207
  }
208
- if (diff?.[0].kind === types_1.DiffKind.EDIT || diff?.[0].kind === types_1.DiffKind.ARRAY) {
208
+ if (diff?.[0]?.kind === types_1.DiffKind.EDIT || diff?.[0]?.kind === types_1.DiffKind.ARRAY) {
209
209
  const currentRelation = currentSnapshot.relations.find((relation) => {
210
210
  return relation.collection === collection && relation.field === field;
211
211
  });
@@ -223,7 +223,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
223
223
  }
224
224
  }
225
225
  }
226
- if (diff?.[0].kind === types_1.DiffKind.DELETE) {
226
+ if (diff?.[0]?.kind === types_1.DiffKind.DELETE) {
227
227
  try {
228
228
  await relationsService.deleteOne(collection, field, mutationOptions);
229
229
  }
@@ -362,7 +362,7 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, aliasMap)
362
362
  }
363
363
  // Cast filter value (compareValue) based on type of field being filtered against
364
364
  const [collection, field] = key.split('.');
365
- const mappedCollection = originalCollectionName || collection;
365
+ const mappedCollection = (originalCollectionName || collection);
366
366
  if (mappedCollection in schema.collections && field in schema.collections[mappedCollection].fields) {
367
367
  const type = schema.collections[mappedCollection].fields[field].type;
368
368
  if (['date', 'dateTime', 'time', 'timestamp'].includes(type)) {
@@ -296,5 +296,5 @@ async function getASTFromQuery(collection, query, schema, options) {
296
296
  }
297
297
  exports.default = getASTFromQuery;
298
298
  function getDeepQuery(query) {
299
- return (0, lodash_1.mapKeys)((0, lodash_1.omitBy)(query, (value, key) => key.startsWith('_') === false), (value, key) => key.substring(1));
299
+ return (0, lodash_1.mapKeys)((0, lodash_1.omitBy)(query, (_value, key) => key.startsWith('_') === false), (_value, key) => key.substring(1));
300
300
  }
@@ -43,7 +43,8 @@ function getColumnPath({ path, collection, aliasMap, relations, schema }) {
43
43
  addNestedPkField = schema.collections[parent].primary;
44
44
  }
45
45
  // Nested level alias field
46
- else if (remainingParts.length === 1 && schema.collections[parent].fields[remainingParts[0]].type === 'alias') {
46
+ else if (remainingParts.length === 1 &&
47
+ schema.collections[parent].fields[remainingParts[0]].type === 'alias') {
47
48
  remainingParts.push(schema.collections[relation.related_collection].primary);
48
49
  addNestedPkField = schema.collections[relation.related_collection].primary;
49
50
  }
@@ -81,7 +81,7 @@ async function getDatabaseSchema(database, schemaInspector) {
81
81
  note: collectionMeta?.note || null,
82
82
  sortField: collectionMeta?.sort_field || null,
83
83
  accountability: collectionMeta ? collectionMeta.accountability : 'all',
84
- fields: (0, lodash_1.mapValues)(schemaOverview[collection].columns, (column) => {
84
+ fields: (0, lodash_1.mapValues)(schemaOverview[collection]?.columns, (column) => {
85
85
  return {
86
86
  field: column.column_name,
87
87
  defaultValue: (0, get_default_value_1.default)(column) ?? null,
@@ -108,8 +108,8 @@ async function getDatabaseSchema(database, schemaInspector) {
108
108
  for (const field of fields) {
109
109
  if (!result.collections[field.collection])
110
110
  continue;
111
- const existing = result.collections[field.collection].fields[field.field];
112
- const column = schemaOverview[field.collection].columns[field.field];
111
+ const existing = result.collections[field.collection]?.fields[field.field];
112
+ const column = schemaOverview[field.collection]?.columns[field.field];
113
113
  const special = field.special ? (0, utils_1.toArray)(field.special) : [];
114
114
  if (constants_1.ALIAS_TYPES.some((type) => special.includes(type)) === false && !existing)
115
115
  continue;
@@ -73,7 +73,7 @@ function getSnapshotDiff(current, after) {
73
73
  * When you delete a collection, we don't have to individually drop all the fields/relations as well
74
74
  */
75
75
  const deletedCollections = diffedSnapshot.collections
76
- .filter((collection) => collection.diff?.[0].kind === types_1.DiffKind.DELETE)
76
+ .filter((collection) => collection.diff?.[0]?.kind === types_1.DiffKind.DELETE)
77
77
  .map(({ collection }) => collection);
78
78
  diffedSnapshot.fields = diffedSnapshot.fields.filter((field) => deletedCollections.includes(field.collection) === false);
79
79
  diffedSnapshot.relations = diffedSnapshot.relations.filter((relation) => deletedCollections.includes(relation.collection) === false);
@@ -53,8 +53,8 @@ function parseXmp(buffer) {
53
53
  if (!tagMatches || tagMatches.length === 0) {
54
54
  return;
55
55
  }
56
- const value = tagMatches[1].trim();
57
- if (value.toLowerCase().indexOf('<rdf:bag>') === 0) {
56
+ const value = tagMatches[1]?.trim();
57
+ if (value?.toLowerCase().indexOf('<rdf:bag>') === 0) {
58
58
  const r = new RegExp('<rdf:li>(.*?)</rdf:li>', 'smig');
59
59
  let match = r.exec(value);
60
60
  const result = [];
@@ -65,7 +65,7 @@ function parseXmp(buffer) {
65
65
  xmp[x] = result;
66
66
  }
67
67
  else {
68
- xmp[x] = value.replace(/<[^>]*>?/gm, '').trim();
68
+ xmp[x] = value?.replace(/<[^>]*>?/gm, '').trim();
69
69
  }
70
70
  });
71
71
  return xmp;
@@ -56,7 +56,7 @@ function reduceSchema(schema, permissions, actions = ['create', 'read', 'update'
56
56
  if (relation.related_collection &&
57
57
  (Object.keys(allowedFieldsInCollection).includes(relation.related_collection) === false ||
58
58
  // Ignore legacy permissions with an empty fields array
59
- allowedFieldsInCollection[relation.related_collection].length === 0)) {
59
+ allowedFieldsInCollection[relation.related_collection]?.length === 0)) {
60
60
  collectionsAllowed = false;
61
61
  }
62
62
  if (relation.meta?.one_allowed_collections &&
@@ -64,15 +64,15 @@ function reduceSchema(schema, permissions, actions = ['create', 'read', 'update'
64
64
  collectionsAllowed = false;
65
65
  }
66
66
  if (!allowedFieldsInCollection[relation.collection] ||
67
- (allowedFieldsInCollection[relation.collection].includes('*') === false &&
68
- allowedFieldsInCollection[relation.collection].includes(relation.field) === false)) {
67
+ (allowedFieldsInCollection[relation.collection]?.includes('*') === false &&
68
+ allowedFieldsInCollection[relation.collection]?.includes(relation.field) === false)) {
69
69
  fieldsAllowed = false;
70
70
  }
71
71
  if (relation.related_collection &&
72
72
  relation.meta?.one_field &&
73
73
  (!allowedFieldsInCollection[relation.related_collection] ||
74
- (allowedFieldsInCollection[relation.related_collection].includes('*') === false &&
75
- allowedFieldsInCollection[relation.related_collection].includes(relation.meta?.one_field) === false))) {
74
+ (allowedFieldsInCollection[relation.related_collection]?.includes('*') === false &&
75
+ allowedFieldsInCollection[relation.related_collection]?.includes(relation.meta?.one_field) === false))) {
76
76
  fieldsAllowed = false;
77
77
  }
78
78
  return collectionsAllowed && fieldsAllowed;
@@ -11,9 +11,18 @@ const url_1 = require("./url");
11
11
  function shouldSkipCache(req) {
12
12
  const env = (0, env_1.getEnv)();
13
13
  // Always skip cache for requests coming from the data studio based on Referer header
14
- const adminUrl = new url_1.Url(env['PUBLIC_URL']).addPath('admin').toString();
15
- if (req.get('Referer')?.startsWith(adminUrl))
16
- return true;
14
+ const referer = req.get('Referer');
15
+ if (referer) {
16
+ const adminUrl = new url_1.Url(env['PUBLIC_URL']).addPath('admin');
17
+ if (adminUrl.isRootRelative()) {
18
+ const refererUrl = new url_1.Url(referer);
19
+ if (refererUrl.path.join('/').startsWith(adminUrl.path.join('/')))
20
+ return true;
21
+ }
22
+ else if (referer.startsWith(adminUrl.toString())) {
23
+ return true;
24
+ }
25
+ }
17
26
  if (env['CACHE_SKIP_ALLOWED'] && req.get('cache-control')?.includes('no-store'))
18
27
  return true;
19
28
  return false;
@@ -7,7 +7,7 @@ const constants_1 = require("@directus/shared/constants");
7
7
  */
8
8
  function stripFunction(field) {
9
9
  if (field.includes('(') && field.includes(')')) {
10
- return field.match(constants_1.REGEX_BETWEEN_PARENS)[1].trim();
10
+ return field.match(constants_1.REGEX_BETWEEN_PARENS)?.[1]?.trim() ?? field;
11
11
  }
12
12
  else {
13
13
  return field;
@@ -0,0 +1 @@
1
+ export declare function collectTelemetry(): Promise<void>;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.collectTelemetry = void 0;
7
+ const node_machine_id_1 = require("node-machine-id");
8
+ const package_json_1 = require("../../package.json");
9
+ const env_1 = __importDefault(require("../env"));
10
+ const logger_1 = __importDefault(require("../logger"));
11
+ async function collectTelemetry() {
12
+ const axios = (await import('axios')).default;
13
+ if (env_1.default['TELEMETRY'] !== false) {
14
+ try {
15
+ await axios.post('https://telemetry.directus.io/', {
16
+ version: package_json_1.version,
17
+ public_url: env_1.default['PUBLIC_URL'],
18
+ project_id: env_1.default['KEY'],
19
+ machine_id: await (0, node_machine_id_1.machineId)(),
20
+ db_client: env_1.default['DB_CLIENT'],
21
+ });
22
+ }
23
+ catch (err) {
24
+ if (env_1.default['NODE_ENV'] === 'development') {
25
+ logger_1.default.error(err);
26
+ }
27
+ }
28
+ }
29
+ }
30
+ exports.collectTelemetry = collectTelemetry;
@@ -16,7 +16,7 @@ function validateKeys(schema, collection, keyField, keys) {
16
16
  }
17
17
  }
18
18
  else {
19
- const primaryKeyFieldType = schema.collections[collection].fields[keyField].type;
19
+ const primaryKeyFieldType = schema.collections[collection]?.fields[keyField]?.type;
20
20
  if (primaryKeyFieldType === 'uuid' && !(0, uuid_validate_1.default)(String(keys))) {
21
21
  throw new exceptions_1.ForbiddenException();
22
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directus",
3
- "version": "9.23.4",
3
+ "version": "9.24.0",
4
4
  "description": "Directus is a real-time API and App dashboard for managing SQL database content",
5
5
  "keywords": [
6
6
  "directus",
@@ -144,18 +144,18 @@
144
144
  "uuid-validate": "0.0.3",
145
145
  "vm2": "3.9.14",
146
146
  "wellknown": "0.5.0",
147
- "@directus/app": "9.23.4",
148
- "@directus/schema": "9.23.4",
149
- "@directus/extensions-sdk": "9.23.4",
150
- "@directus/shared": "9.23.4",
151
- "@directus/storage": "9.23.4",
152
- "@directus/specs": "9.23.4",
153
- "@directus/storage-driver-azure": "9.23.4",
154
- "@directus/storage-driver-cloudinary": "9.23.4",
155
- "@directus/storage-driver-gcs": "9.23.4",
156
- "@directus/storage-driver-local": "9.23.4",
157
- "@directus/storage-driver-s3": "9.23.4",
158
- "@directus/utils": "9.23.4"
147
+ "@directus/app": "9.24.0",
148
+ "@directus/extensions-sdk": "9.24.0",
149
+ "@directus/schema": "9.24.0",
150
+ "@directus/shared": "9.24.0",
151
+ "@directus/specs": "9.24.0",
152
+ "@directus/storage": "9.24.0",
153
+ "@directus/storage-driver-azure": "9.24.0",
154
+ "@directus/storage-driver-cloudinary": "9.24.0",
155
+ "@directus/storage-driver-gcs": "9.24.0",
156
+ "@directus/storage-driver-local": "9.24.0",
157
+ "@directus/storage-driver-s3": "9.24.0",
158
+ "@directus/utils": "9.24.0"
159
159
  },
160
160
  "devDependencies": {
161
161
  "@ngneat/falso": "6.4.0",
@@ -1 +0,0 @@
1
- export declare function track(event: string): Promise<void>;
@@ -1,81 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.track = void 0;
7
- const node_machine_id_1 = require("node-machine-id");
8
- const os_1 = __importDefault(require("os"));
9
- // @ts-ignore
10
- const utils_1 = require("@directus/shared/utils");
11
- const package_json_1 = require("../../package.json");
12
- const env_1 = __importDefault(require("../env"));
13
- const logger_1 = __importDefault(require("../logger"));
14
- const get_milliseconds_1 = require("./get-milliseconds");
15
- async function track(event) {
16
- const axios = (await import('axios')).default;
17
- if (env_1.default['TELEMETRY'] !== false) {
18
- const info = await getEnvInfo(event);
19
- try {
20
- await axios.post('https://telemetry.directus.io/', info);
21
- }
22
- catch (err) {
23
- if (env_1.default['NODE_ENV'] === 'development') {
24
- logger_1.default.error(err);
25
- }
26
- }
27
- }
28
- }
29
- exports.track = track;
30
- async function getEnvInfo(event) {
31
- return {
32
- version: package_json_1.version,
33
- event: event,
34
- project_id: env_1.default['KEY'],
35
- machine_id: await (0, node_machine_id_1.machineId)(),
36
- environment: env_1.default['NODE_ENV'],
37
- stack: 'node',
38
- os: {
39
- arch: os_1.default.arch(),
40
- platform: os_1.default.platform(),
41
- release: os_1.default.release(),
42
- },
43
- rate_limiter: {
44
- enabled: env_1.default['RATE_LIMITER_ENABLED'],
45
- points: +env_1.default['RATE_LIMITER_POINTS'],
46
- duration: +env_1.default['RATE_LIMITER_DURATION'],
47
- store: env_1.default['RATE_LIMITER_STORE'],
48
- },
49
- cache: {
50
- enabled: env_1.default['CACHE_ENABLED'],
51
- ttl: (0, get_milliseconds_1.getMilliseconds)(env_1.default['CACHE_TTL']),
52
- store: env_1.default['CACHE_STORE'],
53
- },
54
- storage: {
55
- drivers: getStorageDrivers(),
56
- },
57
- cors: {
58
- enabled: env_1.default['CORS_ENABLED'],
59
- },
60
- email: {
61
- transport: env_1.default['EMAIL_TRANSPORT'],
62
- },
63
- auth: {
64
- providers: (0, utils_1.toArray)(env_1.default['AUTH_PROVIDERS'])
65
- .map((v) => v.trim())
66
- .filter((v) => v),
67
- },
68
- db_client: env_1.default['DB_CLIENT'],
69
- };
70
- }
71
- function getStorageDrivers() {
72
- const drivers = [];
73
- const locations = (0, utils_1.toArray)(env_1.default['STORAGE_LOCATIONS'])
74
- .map((v) => v.trim())
75
- .filter((v) => v);
76
- for (const location of locations) {
77
- const driver = env_1.default[`STORAGE_${location.toUpperCase()}_DRIVER`];
78
- drivers.push(driver);
79
- }
80
- return drivers;
81
- }