directus 9.7.1 → 9.9.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 (103) hide show
  1. package/dist/__mocks__/cache.d.ts +5 -0
  2. package/dist/__mocks__/cache.js +7 -0
  3. package/dist/auth/drivers/ldap.js +10 -11
  4. package/dist/auth/drivers/oauth2.js +9 -4
  5. package/dist/auth/drivers/openid.js +7 -4
  6. package/dist/cache.js +2 -2
  7. package/dist/cli/commands/schema/apply.js +9 -3
  8. package/dist/controllers/assets.js +5 -0
  9. package/dist/controllers/files.d.ts +2 -0
  10. package/dist/controllers/files.js +13 -5
  11. package/dist/database/helpers/date/dialects/default.d.ts +3 -0
  12. package/dist/database/helpers/date/dialects/default.js +7 -0
  13. package/dist/database/helpers/date/dialects/mssql.d.ts +1 -9
  14. package/dist/database/helpers/date/dialects/mssql.js +4 -23
  15. package/dist/database/helpers/date/dialects/mysql.d.ts +2 -9
  16. package/dist/database/helpers/date/dialects/mysql.js +7 -22
  17. package/dist/database/helpers/date/dialects/oracle.d.ts +1 -9
  18. package/dist/database/helpers/date/dialects/oracle.js +7 -23
  19. package/dist/database/helpers/date/dialects/sqlite.d.ts +1 -9
  20. package/dist/database/helpers/date/dialects/sqlite.js +8 -24
  21. package/dist/database/helpers/date/index.d.ts +4 -4
  22. package/dist/database/helpers/date/index.js +9 -9
  23. package/dist/database/helpers/date/types.d.ts +3 -9
  24. package/dist/database/helpers/date/types.js +10 -0
  25. package/dist/database/helpers/fn/dialects/mssql.d.ts +13 -0
  26. package/dist/database/helpers/fn/dialects/mssql.js +42 -0
  27. package/dist/database/helpers/{date/dialects/postgres.d.ts → fn/dialects/mysql.d.ts} +3 -2
  28. package/dist/database/helpers/fn/dialects/mysql.js +42 -0
  29. package/dist/database/helpers/fn/dialects/oracle.d.ts +13 -0
  30. package/dist/database/helpers/fn/dialects/oracle.js +42 -0
  31. package/dist/database/helpers/fn/dialects/postgres.d.ts +13 -0
  32. package/dist/database/helpers/{date → fn}/dialects/postgres.js +18 -3
  33. package/dist/database/helpers/fn/dialects/sqlite.d.ts +13 -0
  34. package/dist/database/helpers/fn/dialects/sqlite.js +42 -0
  35. package/dist/database/helpers/fn/index.d.ts +7 -0
  36. package/dist/database/helpers/fn/index.js +17 -0
  37. package/dist/database/helpers/fn/types.d.ts +18 -0
  38. package/dist/database/helpers/fn/types.js +27 -0
  39. package/dist/database/helpers/index.d.ts +4 -1
  40. package/dist/database/helpers/index.js +7 -1
  41. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.d.ts +3 -0
  42. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.js +17 -0
  43. package/dist/database/migrations/20220322A-rename-field-typecast-flags.js +6 -2
  44. package/dist/database/migrations/20220323A-add-field-validation.d.ts +3 -0
  45. package/dist/database/migrations/20220323A-add-field-validation.js +17 -0
  46. package/dist/database/migrations/20220325A-fix-typecast-flags.d.ts +3 -0
  47. package/dist/database/migrations/20220325A-fix-typecast-flags.js +49 -0
  48. package/dist/database/migrations/20220325B-add-default-language.d.ts +3 -0
  49. package/dist/database/migrations/20220325B-add-default-language.js +28 -0
  50. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.d.ts +3 -0
  51. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.js +22 -0
  52. package/dist/database/run-ast.js +7 -5
  53. package/dist/database/system-data/fields/activity.yaml +4 -4
  54. package/dist/database/system-data/fields/collections.yaml +1 -1
  55. package/dist/database/system-data/fields/fields.yaml +9 -0
  56. package/dist/database/system-data/fields/presets.yaml +14 -0
  57. package/dist/database/system-data/fields/settings.yaml +12 -1
  58. package/dist/database/system-data/fields/users.yaml +3 -0
  59. package/dist/env.js +5 -3
  60. package/dist/exceptions/index.d.ts +1 -0
  61. package/dist/exceptions/index.js +1 -0
  62. package/dist/exceptions/token-expired.d.ts +4 -0
  63. package/dist/exceptions/token-expired.js +10 -0
  64. package/dist/logger.js +2 -1
  65. package/dist/middleware/cache.js +10 -0
  66. package/dist/services/activity.js +4 -1
  67. package/dist/services/authorization.d.ts +1 -1
  68. package/dist/services/authorization.js +174 -48
  69. package/dist/services/collections.d.ts +2 -0
  70. package/dist/services/collections.js +232 -198
  71. package/dist/services/fields.js +210 -174
  72. package/dist/services/files.d.ts +5 -1
  73. package/dist/services/files.js +59 -40
  74. package/dist/services/graphql.d.ts +2 -3
  75. package/dist/services/graphql.js +53 -10
  76. package/dist/services/items.js +5 -3
  77. package/dist/services/payload.d.ts +2 -1
  78. package/dist/services/payload.js +28 -21
  79. package/dist/services/relations.js +93 -81
  80. package/dist/services/server.js +1 -0
  81. package/dist/services/shares.js +2 -1
  82. package/dist/services/specifications.js +1 -3
  83. package/dist/services/users.js +7 -2
  84. package/dist/types/files.d.ts +8 -0
  85. package/dist/utils/apply-query.js +38 -10
  86. package/dist/utils/apply-snapshot.d.ts +3 -1
  87. package/dist/utils/apply-snapshot.js +34 -5
  88. package/dist/utils/get-ast-from-query.js +15 -3
  89. package/dist/utils/get-column.d.ts +6 -5
  90. package/dist/utils/get-column.js +16 -8
  91. package/dist/utils/get-graphql-type.js +1 -0
  92. package/dist/utils/get-local-type.js +5 -0
  93. package/dist/utils/get-schema.d.ts +1 -1
  94. package/dist/utils/get-schema.js +18 -10
  95. package/dist/utils/jwt.js +1 -1
  96. package/dist/utils/reduce-schema.js +20 -12
  97. package/dist/utils/track.js +3 -2
  98. package/dist/utils/url.d.ts +1 -1
  99. package/dist/utils/url.js +1 -1
  100. package/dist/utils/validate-query.js +19 -15
  101. package/dist/utils/validate-storage.js +3 -1
  102. package/example.env +4 -0
  103. package/package.json +14 -13
@@ -5,20 +5,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.GraphQLService = exports.GraphQLDate = exports.GraphQLGeoJSON = void 0;
7
7
  const argon2_1 = __importDefault(require("argon2"));
8
- const validate_query_1 = require("../utils/validate-query");
9
8
  const graphql_1 = require("graphql");
10
9
  const graphql_compose_1 = require("graphql-compose");
11
10
  const lodash_1 = require("lodash");
12
11
  const ms_1 = __importDefault(require("ms"));
13
12
  const cache_1 = require("../cache");
13
+ const constants_1 = require("../constants");
14
14
  const database_1 = __importDefault(require("../database"));
15
15
  const env_1 = __importDefault(require("../env"));
16
16
  const exceptions_1 = require("../exceptions");
17
17
  const extensions_1 = require("../extensions");
18
18
  const types_1 = require("../types");
19
+ const generate_hash_1 = require("../utils/generate-hash");
19
20
  const get_graphql_type_1 = require("../utils/get-graphql-type");
20
21
  const reduce_schema_1 = require("../utils/reduce-schema");
21
22
  const sanitize_query_1 = require("../utils/sanitize-query");
23
+ const validate_query_1 = require("../utils/validate-query");
22
24
  const activity_1 = require("./activity");
23
25
  const authentication_1 = require("./authentication");
24
26
  const collections_1 = require("./collections");
@@ -26,9 +28,9 @@ const fields_1 = require("./fields");
26
28
  const files_1 = require("./files");
27
29
  const folders_1 = require("./folders");
28
30
  const items_1 = require("./items");
31
+ const notifications_1 = require("./notifications");
29
32
  const permissions_1 = require("./permissions");
30
33
  const presets_1 = require("./presets");
31
- const notifications_1 = require("./notifications");
32
34
  const relations_1 = require("./relations");
33
35
  const revisions_1 = require("./revisions");
34
36
  const roles_1 = require("./roles");
@@ -40,8 +42,6 @@ const tfa_1 = require("./tfa");
40
42
  const users_1 = require("./users");
41
43
  const utils_1 = require("./utils");
42
44
  const webhooks_1 = require("./webhooks");
43
- const generate_hash_1 = require("../utils/generate-hash");
44
- const constants_1 = require("../constants");
45
45
  const GraphQLVoid = new graphql_1.GraphQLScalarType({
46
46
  name: 'Void',
47
47
  description: 'Represents NULL values',
@@ -235,6 +235,14 @@ class GraphQLService {
235
235
  function getTypes(action) {
236
236
  var _a, _b, _c, _d, _e;
237
237
  const CollectionTypes = {};
238
+ const CountFunctions = schemaComposer.createObjectTC({
239
+ name: 'count_functions',
240
+ fields: {
241
+ count: {
242
+ type: graphql_1.GraphQLInt,
243
+ },
244
+ },
245
+ });
238
246
  const DateFunctions = schemaComposer.createObjectTC({
239
247
  name: 'date_functions',
240
248
  fields: {
@@ -328,6 +336,15 @@ class GraphQLService {
328
336
  },
329
337
  };
330
338
  }
339
+ if (field.type === 'json' || field.type === 'alias') {
340
+ acc[`${field.field}_func`] = {
341
+ type: CountFunctions,
342
+ resolve: (obj) => {
343
+ const funcFields = Object.keys(CountFunctions.getFields()).map((key) => `${field.field}_${key}`);
344
+ return (0, lodash_1.mapKeys)((0, lodash_1.pick)(obj, funcFields), (_value, key) => key.substring(field.field.length + 1));
345
+ },
346
+ };
347
+ }
331
348
  return acc;
332
349
  }, {}),
333
350
  });
@@ -550,6 +567,14 @@ class GraphQLService {
550
567
  },
551
568
  },
552
569
  });
570
+ const CountFunctionFilterOperators = schemaComposer.createInputTC({
571
+ name: 'count_function_filter_operators',
572
+ fields: {
573
+ count: {
574
+ type: NumberFilterOperators,
575
+ },
576
+ },
577
+ });
553
578
  const DateFunctionFilterOperators = schemaComposer.createInputTC({
554
579
  name: 'date_function_filter_operators',
555
580
  fields: {
@@ -634,6 +659,11 @@ class GraphQLService {
634
659
  type: DateTimeFunctionFilterOperators,
635
660
  };
636
661
  }
662
+ if (field.type === 'json' || field.type === 'alias') {
663
+ acc[`${field.field}_func`] = {
664
+ type: CountFunctionFilterOperators,
665
+ };
666
+ }
637
667
  return acc;
638
668
  }, {}),
639
669
  });
@@ -1145,6 +1175,7 @@ class GraphQLService {
1145
1175
  continue;
1146
1176
  selection = selection;
1147
1177
  let current;
1178
+ let currentAlias = null;
1148
1179
  // Union type (Many-to-Any)
1149
1180
  if (selection.kind === 'InlineFragment') {
1150
1181
  if (selection.typeCondition.name.value.startsWith('__'))
@@ -1157,8 +1188,20 @@ class GraphQLService {
1157
1188
  if (selection.name.value.startsWith('__'))
1158
1189
  continue;
1159
1190
  current = selection.name.value;
1191
+ if (selection.alias) {
1192
+ currentAlias = selection.alias.value;
1193
+ }
1160
1194
  if (parent) {
1161
1195
  current = `${parent}.${current}`;
1196
+ if (currentAlias) {
1197
+ currentAlias = `${parent}.${currentAlias}`;
1198
+ // add nested aliases into deep query
1199
+ if (selection.selectionSet) {
1200
+ if (!query.deep)
1201
+ query.deep = {};
1202
+ (0, lodash_1.set)(query.deep, parent, (0, lodash_1.merge)((0, lodash_1.get)(query.deep, parent), { _alias: { [selection.alias.value]: selection.name.value } }));
1203
+ }
1204
+ }
1162
1205
  }
1163
1206
  }
1164
1207
  if (selection.selectionSet) {
@@ -1173,7 +1216,7 @@ class GraphQLService {
1173
1216
  }
1174
1217
  }
1175
1218
  else {
1176
- children = parseFields(selection.selectionSet.selections, current);
1219
+ children = parseFields(selection.selectionSet.selections, currentAlias !== null && currentAlias !== void 0 ? currentAlias : current);
1177
1220
  }
1178
1221
  fields.push(...children);
1179
1222
  }
@@ -1308,7 +1351,7 @@ class GraphQLService {
1308
1351
  return result;
1309
1352
  }
1310
1353
  injectSystemResolvers(schemaComposer, { CreateCollectionTypes, ReadCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes, }, schema) {
1311
- var _a, _b, _c, _d, _e;
1354
+ var _a, _b, _c, _d, _e, _f;
1312
1355
  const AuthTokens = schemaComposer.createObjectTC({
1313
1356
  name: 'auth_tokens',
1314
1357
  fields: {
@@ -2131,7 +2174,7 @@ class GraphQLService {
2131
2174
  },
2132
2175
  });
2133
2176
  }
2134
- if ('directus_users' in schema.update.collections) {
2177
+ if ('directus_users' in schema.update.collections && ((_c = this.accountability) === null || _c === void 0 ? void 0 : _c.user)) {
2135
2178
  schemaComposer.Mutation.addFields({
2136
2179
  update_users_me: {
2137
2180
  type: ReadCollectionTypes['directus_users'],
@@ -2160,7 +2203,7 @@ class GraphQLService {
2160
2203
  if ('directus_activity' in schema.create.collections) {
2161
2204
  schemaComposer.Mutation.addFields({
2162
2205
  create_comment: {
2163
- type: (_c = ReadCollectionTypes['directus_activity']) !== null && _c !== void 0 ? _c : graphql_1.GraphQLBoolean,
2206
+ type: (_d = ReadCollectionTypes['directus_activity']) !== null && _d !== void 0 ? _d : graphql_1.GraphQLBoolean,
2164
2207
  args: {
2165
2208
  collection: (0, graphql_1.GraphQLNonNull)(graphql_1.GraphQLString),
2166
2209
  item: (0, graphql_1.GraphQLNonNull)(graphql_1.GraphQLID),
@@ -2192,7 +2235,7 @@ class GraphQLService {
2192
2235
  if ('directus_activity' in schema.update.collections) {
2193
2236
  schemaComposer.Mutation.addFields({
2194
2237
  update_comment: {
2195
- type: (_d = ReadCollectionTypes['directus_activity']) !== null && _d !== void 0 ? _d : graphql_1.GraphQLBoolean,
2238
+ type: (_e = ReadCollectionTypes['directus_activity']) !== null && _e !== void 0 ? _e : graphql_1.GraphQLBoolean,
2196
2239
  args: {
2197
2240
  id: (0, graphql_1.GraphQLNonNull)(graphql_1.GraphQLID),
2198
2241
  comment: (0, graphql_1.GraphQLNonNull)(graphql_1.GraphQLString),
@@ -2235,7 +2278,7 @@ class GraphQLService {
2235
2278
  if ('directus_files' in schema.create.collections) {
2236
2279
  schemaComposer.Mutation.addFields({
2237
2280
  import_file: {
2238
- type: (_e = ReadCollectionTypes['directus_files']) !== null && _e !== void 0 ? _e : graphql_1.GraphQLBoolean,
2281
+ type: (_f = ReadCollectionTypes['directus_files']) !== null && _f !== void 0 ? _f : graphql_1.GraphQLBoolean,
2239
2282
  args: {
2240
2283
  url: (0, graphql_1.GraphQLNonNull)(graphql_1.GraphQLString),
2241
2284
  data: (0, graphql_compose_1.toInputObjectType)(CreateCollectionTypes['directus_files']).setTypeName('create_directus_files_input'),
@@ -354,13 +354,14 @@ class ItemsService {
354
354
  knex: trx,
355
355
  schema: this.schema,
356
356
  });
357
- const revisionIDs = await revisionsService.createMany(await Promise.all(activity.map(async (activity, index) => ({
357
+ const revisions = (await Promise.all(activity.map(async (activity, index) => ({
358
358
  activity: activity,
359
359
  collection: this.collection,
360
360
  item: keys[index],
361
361
  data: snapshots && Array.isArray(snapshots) ? JSON.stringify(snapshots[index]) : JSON.stringify(snapshots),
362
362
  delta: await payloadService.prepareDelta(payloadWithTypeCasting),
363
- }))));
363
+ })))).filter((revision) => revision.delta);
364
+ const revisionIDs = await revisionsService.createMany(revisions);
364
365
  for (let i = 0; i < revisionIDs.length; i++) {
365
366
  const revisionID = revisionIDs[i];
366
367
  if (opts === null || opts === void 0 ? void 0 : opts.onRevisionCreate) {
@@ -529,7 +530,8 @@ class ItemsService {
529
530
  defaults[name] = null;
530
531
  continue;
531
532
  }
532
- defaults[name] = field.defaultValue;
533
+ if (field.defaultValue)
534
+ defaults[name] = field.defaultValue;
533
535
  }
534
536
  return defaults;
535
537
  }
@@ -10,6 +10,7 @@ declare type Transformers = {
10
10
  payload: Partial<Item>;
11
11
  accountability: Accountability | null;
12
12
  specials: string[];
13
+ helpers: Helpers;
13
14
  }) => Promise<any>;
14
15
  };
15
16
  /**
@@ -64,6 +65,6 @@ export declare class PayloadService {
64
65
  * Transforms the input partial payload to match the output structure, to have consistency
65
66
  * between delta and data
66
67
  */
67
- prepareDelta(data: Partial<Item>): Promise<string>;
68
+ prepareDelta(data: Partial<Item>): Promise<string | null>;
68
69
  }
69
70
  export {};
@@ -89,14 +89,14 @@ class PayloadService {
89
89
  return (accountability === null || accountability === void 0 ? void 0 : accountability.role) || null;
90
90
  return value;
91
91
  },
92
- async 'date-created'({ action, value }) {
92
+ async 'date-created'({ action, value, helpers }) {
93
93
  if (action === 'create')
94
- return new Date();
94
+ return new Date(helpers.date.writeTimestamp(new Date().toISOString()));
95
95
  return value;
96
96
  },
97
- async 'date-updated'({ action, value }) {
97
+ async 'date-updated'({ action, value, helpers }) {
98
98
  if (action === 'update')
99
- return new Date();
99
+ return new Date(helpers.date.writeTimestamp(new Date().toISOString()));
100
100
  return value;
101
101
  },
102
102
  async 'cast-csv'({ action, value }) {
@@ -181,6 +181,7 @@ class PayloadService {
181
181
  payload,
182
182
  accountability,
183
183
  specials: fieldSpecials,
184
+ helpers: this.helpers,
184
185
  });
185
186
  }
186
187
  }
@@ -233,21 +234,23 @@ class PayloadService {
233
234
  value = new Date(value);
234
235
  }
235
236
  if (dateColumn.type === 'timestamp') {
236
- const newValue = value.toISOString();
237
+ const newValue = this.helpers.date.readTimestampString(value.toISOString());
237
238
  payload[name] = newValue;
238
239
  }
239
240
  if (dateColumn.type === 'dateTime') {
240
- const year = String(value.getUTCFullYear());
241
- const month = String(value.getUTCMonth() + 1).padStart(2, '0');
242
- const date = String(value.getUTCDate()).padStart(2, '0');
243
- const hours = String(value.getUTCHours()).padStart(2, '0');
244
- const minutes = String(value.getUTCMinutes()).padStart(2, '0');
245
- const seconds = String(value.getUTCSeconds()).padStart(2, '0');
246
- const newValue = `${year}-${month}-${date}T${hours}:${minutes}:${seconds}`;
241
+ const year = String(value.getFullYear());
242
+ const month = String(value.getMonth() + 1).padStart(2, '0');
243
+ const day = String(value.getDate()).padStart(2, '0');
244
+ const hours = String(value.getHours()).padStart(2, '0');
245
+ const minutes = String(value.getMinutes()).padStart(2, '0');
246
+ const seconds = String(value.getSeconds()).padStart(2, '0');
247
+ const newValue = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
247
248
  payload[name] = newValue;
248
249
  }
249
250
  if (dateColumn.type === 'date') {
250
- const [year, month, day] = value.toISOString().substr(0, 10).split('-');
251
+ const year = String(value.getFullYear());
252
+ const month = String(value.getMonth() + 1).padStart(2, '0');
253
+ const day = String(value.getDate()).padStart(2, '0');
251
254
  // Strip off the time / timezone information from a date-only value
252
255
  const newValue = `${year}-${month}-${day}`;
253
256
  payload[name] = newValue;
@@ -258,16 +261,16 @@ class PayloadService {
258
261
  if (dateColumn.type === 'date') {
259
262
  const [date] = value.split('T');
260
263
  const [year, month, day] = date.split('-');
261
- payload[name] = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day)));
264
+ payload[name] = new Date(Number(year), Number(month) - 1, Number(day));
262
265
  }
263
266
  if (dateColumn.type === 'dateTime') {
264
267
  const [date, time] = value.split('T');
265
268
  const [year, month, day] = date.split('-');
266
269
  const [hours, minutes, seconds] = time.substring(0, 8).split(':');
267
- payload[name] = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hours), Number(minutes), Number(seconds)));
270
+ payload[name] = new Date(Number(year), Number(month) - 1, Number(day), Number(hours), Number(minutes), Number(seconds));
268
271
  }
269
272
  if (dateColumn.type === 'timestamp') {
270
- const newValue = (0, date_fns_1.parseISO)(value);
273
+ const newValue = this.helpers.date.writeTimestamp(value);
271
274
  payload[name] = newValue;
272
275
  }
273
276
  }
@@ -429,7 +432,7 @@ class PayloadService {
429
432
  delete: joi_1.default.array().items(joi_1.default.string(), joi_1.default.number()),
430
433
  });
431
434
  for (const relation of relationsToProcess) {
432
- if (!relation.meta || !payload[relation.meta.one_field])
435
+ if (!relation.meta)
433
436
  continue;
434
437
  const currentPrimaryKeyField = this.schema.collections[relation.related_collection].primary;
435
438
  const relatedPrimaryKeyField = this.schema.collections[relation.collection].primary;
@@ -441,9 +444,11 @@ class PayloadService {
441
444
  const recordsToUpsert = [];
442
445
  const savedPrimaryKeys = [];
443
446
  // Nested array of individual items
444
- if (Array.isArray(payload[relation.meta.one_field])) {
445
- for (let i = 0; i < (payload[relation.meta.one_field] || []).length; i++) {
446
- const relatedRecord = (payload[relation.meta.one_field] || [])[i];
447
+ const field = payload[relation.meta.one_field];
448
+ if (!field || Array.isArray(field)) {
449
+ const updates = field || []; // treat falsey values as removing all children
450
+ for (let i = 0; i < updates.length; i++) {
451
+ const relatedRecord = updates[i];
447
452
  let record = (0, lodash_1.cloneDeep)(relatedRecord);
448
453
  if (typeof relatedRecord === 'string' || typeof relatedRecord === 'number') {
449
454
  const existingRecord = await this.knex
@@ -507,7 +512,7 @@ class PayloadService {
507
512
  }
508
513
  // "Updates" object w/ create/update/delete
509
514
  else {
510
- const alterations = payload[relation.meta.one_field];
515
+ const alterations = field;
511
516
  const { error } = nestedUpdateSchema.validate(alterations);
512
517
  if (error)
513
518
  throw new exceptions_1.InvalidPayloadException(`Invalid one-to-many update structure: ${error.message}`);
@@ -573,6 +578,8 @@ class PayloadService {
573
578
  }
574
579
  }
575
580
  payload = await this.processValues('read', payload);
581
+ if (Object.keys(payload).length === 0)
582
+ return null;
576
583
  return JSON.stringify(payload);
577
584
  }
578
585
  }
@@ -146,36 +146,40 @@ class RelationsService {
146
146
  if (existingRelation) {
147
147
  throw new exceptions_1.InvalidPayloadException(`Field "${relation.field}" in collection "${relation.collection}" already has an associated relationship`);
148
148
  }
149
- const metaRow = {
150
- ...(relation.meta || {}),
151
- many_collection: relation.collection,
152
- many_field: relation.field,
153
- one_collection: relation.related_collection || null,
154
- };
155
- await this.knex.transaction(async (trx) => {
156
- if (relation.related_collection) {
157
- await trx.schema.alterTable(relation.collection, async (table) => {
158
- var _a;
159
- this.alterType(table, relation);
160
- const constraintName = (0, get_default_index_name_1.getDefaultIndexName)('foreign', relation.collection, relation.field);
161
- const builder = table
162
- .foreign(relation.field, constraintName)
163
- .references(`${relation.related_collection}.${this.schema.collections[relation.related_collection].primary}`);
164
- if ((_a = relation.schema) === null || _a === void 0 ? void 0 : _a.on_delete) {
165
- builder.onDelete(relation.schema.on_delete);
166
- }
149
+ try {
150
+ const metaRow = {
151
+ ...(relation.meta || {}),
152
+ many_collection: relation.collection,
153
+ many_field: relation.field,
154
+ one_collection: relation.related_collection || null,
155
+ };
156
+ await this.knex.transaction(async (trx) => {
157
+ if (relation.related_collection) {
158
+ await trx.schema.alterTable(relation.collection, async (table) => {
159
+ var _a;
160
+ this.alterType(table, relation);
161
+ const constraintName = (0, get_default_index_name_1.getDefaultIndexName)('foreign', relation.collection, relation.field);
162
+ const builder = table
163
+ .foreign(relation.field, constraintName)
164
+ .references(`${relation.related_collection}.${this.schema.collections[relation.related_collection].primary}`);
165
+ if ((_a = relation.schema) === null || _a === void 0 ? void 0 : _a.on_delete) {
166
+ builder.onDelete(relation.schema.on_delete);
167
+ }
168
+ });
169
+ }
170
+ const relationsItemService = new items_1.ItemsService('directus_relations', {
171
+ knex: trx,
172
+ schema: this.schema,
173
+ // We don't set accountability here. If you have read access to certain fields, you are
174
+ // allowed to extract the relations regardless of permissions to directus_relations. This
175
+ // happens in `filterForbidden` down below
167
176
  });
168
- }
169
- const relationsItemService = new items_1.ItemsService('directus_relations', {
170
- knex: trx,
171
- schema: this.schema,
172
- // We don't set accountability here. If you have read access to certain fields, you are
173
- // allowed to extract the relations regardless of permissions to directus_relations. This
174
- // happens in `filterForbidden` down below
177
+ await relationsItemService.createOne(metaRow);
175
178
  });
176
- await relationsItemService.createOne(metaRow);
177
- });
178
- await (0, cache_1.clearSystemCache)();
179
+ }
180
+ finally {
181
+ await (0, cache_1.clearSystemCache)();
182
+ }
179
183
  }
180
184
  /**
181
185
  * Update an existing foreign key constraint
@@ -196,47 +200,51 @@ class RelationsService {
196
200
  if (!existingRelation) {
197
201
  throw new exceptions_1.InvalidPayloadException(`Field "${field}" in collection "${collection}" doesn't have a relationship.`);
198
202
  }
199
- await this.knex.transaction(async (trx) => {
200
- if (existingRelation.related_collection) {
201
- await trx.schema.alterTable(collection, async (table) => {
202
- var _a;
203
- let constraintName = (0, get_default_index_name_1.getDefaultIndexName)('foreign', collection, field);
204
- // If the FK already exists in the DB, drop it first
205
- if (existingRelation === null || existingRelation === void 0 ? void 0 : existingRelation.schema) {
206
- constraintName = existingRelation.schema.constraint_name || constraintName;
207
- table.dropForeign(field, constraintName);
203
+ try {
204
+ await this.knex.transaction(async (trx) => {
205
+ if (existingRelation.related_collection) {
206
+ await trx.schema.alterTable(collection, async (table) => {
207
+ var _a;
208
+ let constraintName = (0, get_default_index_name_1.getDefaultIndexName)('foreign', collection, field);
209
+ // If the FK already exists in the DB, drop it first
210
+ if (existingRelation === null || existingRelation === void 0 ? void 0 : existingRelation.schema) {
211
+ constraintName = existingRelation.schema.constraint_name || constraintName;
212
+ table.dropForeign(field, constraintName);
213
+ }
214
+ this.alterType(table, relation);
215
+ const builder = table
216
+ .foreign(field, constraintName || undefined)
217
+ .references(`${existingRelation.related_collection}.${this.schema.collections[existingRelation.related_collection].primary}`);
218
+ if ((_a = relation.schema) === null || _a === void 0 ? void 0 : _a.on_delete) {
219
+ builder.onDelete(relation.schema.on_delete);
220
+ }
221
+ });
222
+ }
223
+ const relationsItemService = new items_1.ItemsService('directus_relations', {
224
+ knex: trx,
225
+ schema: this.schema,
226
+ // We don't set accountability here. If you have read access to certain fields, you are
227
+ // allowed to extract the relations regardless of permissions to directus_relations. This
228
+ // happens in `filterForbidden` down below
229
+ });
230
+ if (relation.meta) {
231
+ if (existingRelation === null || existingRelation === void 0 ? void 0 : existingRelation.meta) {
232
+ await relationsItemService.updateOne(existingRelation.meta.id, relation.meta);
208
233
  }
209
- this.alterType(table, relation);
210
- const builder = table
211
- .foreign(field, constraintName || undefined)
212
- .references(`${existingRelation.related_collection}.${this.schema.collections[existingRelation.related_collection].primary}`);
213
- if ((_a = relation.schema) === null || _a === void 0 ? void 0 : _a.on_delete) {
214
- builder.onDelete(relation.schema.on_delete);
234
+ else {
235
+ await relationsItemService.createOne({
236
+ ...(relation.meta || {}),
237
+ many_collection: relation.collection,
238
+ many_field: relation.field,
239
+ one_collection: existingRelation.related_collection || null,
240
+ });
215
241
  }
216
- });
217
- }
218
- const relationsItemService = new items_1.ItemsService('directus_relations', {
219
- knex: trx,
220
- schema: this.schema,
221
- // We don't set accountability here. If you have read access to certain fields, you are
222
- // allowed to extract the relations regardless of permissions to directus_relations. This
223
- // happens in `filterForbidden` down below
224
- });
225
- if (relation.meta) {
226
- if (existingRelation === null || existingRelation === void 0 ? void 0 : existingRelation.meta) {
227
- await relationsItemService.updateOne(existingRelation.meta.id, relation.meta);
228
- }
229
- else {
230
- await relationsItemService.createOne({
231
- ...(relation.meta || {}),
232
- many_collection: relation.collection,
233
- many_field: relation.field,
234
- one_collection: existingRelation.related_collection || null,
235
- });
236
242
  }
237
- }
238
- });
239
- await (0, cache_1.clearSystemCache)();
243
+ });
244
+ }
245
+ finally {
246
+ await (0, cache_1.clearSystemCache)();
247
+ }
240
248
  }
241
249
  /**
242
250
  * Delete an existing relationship
@@ -255,21 +263,25 @@ class RelationsService {
255
263
  if (!existingRelation) {
256
264
  throw new exceptions_1.InvalidPayloadException(`Field "${field}" in collection "${collection}" doesn't have a relationship.`);
257
265
  }
258
- await this.knex.transaction(async (trx) => {
259
- var _a;
260
- const existingConstraints = await this.schemaInspector.foreignKeys();
261
- const constraintNames = existingConstraints.map((key) => key.constraint_name);
262
- if (((_a = existingRelation.schema) === null || _a === void 0 ? void 0 : _a.constraint_name) &&
263
- constraintNames.includes(existingRelation.schema.constraint_name)) {
264
- await trx.schema.alterTable(existingRelation.collection, (table) => {
265
- table.dropForeign(existingRelation.field, existingRelation.schema.constraint_name);
266
- });
267
- }
268
- if (existingRelation.meta) {
269
- await trx('directus_relations').delete().where({ many_collection: collection, many_field: field });
270
- }
271
- });
272
- await (0, cache_1.clearSystemCache)();
266
+ try {
267
+ await this.knex.transaction(async (trx) => {
268
+ var _a;
269
+ const existingConstraints = await this.schemaInspector.foreignKeys();
270
+ const constraintNames = existingConstraints.map((key) => key.constraint_name);
271
+ if (((_a = existingRelation.schema) === null || _a === void 0 ? void 0 : _a.constraint_name) &&
272
+ constraintNames.includes(existingRelation.schema.constraint_name)) {
273
+ await trx.schema.alterTable(existingRelation.collection, (table) => {
274
+ table.dropForeign(existingRelation.field, existingRelation.schema.constraint_name);
275
+ });
276
+ }
277
+ if (existingRelation.meta) {
278
+ await trx('directus_relations').delete().where({ many_collection: collection, many_field: field });
279
+ }
280
+ });
281
+ }
282
+ finally {
283
+ await (0, cache_1.clearSystemCache)();
284
+ }
273
285
  }
274
286
  /**
275
287
  * Whether or not the current user has read access to relations
@@ -55,6 +55,7 @@ class ServerService {
55
55
  'project_descriptor',
56
56
  'project_logo',
57
57
  'project_color',
58
+ 'default_language',
58
59
  'public_foreground',
59
60
  'public_background',
60
61
  'public_note',
@@ -16,6 +16,7 @@ const users_1 = require("./users");
16
16
  const mail_1 = require("./mail");
17
17
  const user_name_1 = require("../utils/user-name");
18
18
  const md_1 = require("../utils/md");
19
+ const url_1 = require("../utils/url");
19
20
  class SharesService extends items_1.ItemsService {
20
21
  constructor(options) {
21
22
  super('directus_shares', options);
@@ -116,7 +117,7 @@ Hello!
116
117
 
117
118
  ${(0, user_name_1.userName)(userInfo)} has invited you to view an item in ${share.collection}.
118
119
 
119
- [Open](${env_1.default.PUBLIC_URL}/admin/shared/${payload.share})
120
+ [Open](${new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin', 'shared', payload.share).toString()})
120
121
  `;
121
122
  for (const email of payload.emails) {
122
123
  await mailService.send({
@@ -315,9 +315,7 @@ class OASSpecsService {
315
315
  schema: {
316
316
  properties: {
317
317
  data: {
318
- items: {
319
- $ref: `#/components/schemas/${tag.name}`,
320
- },
318
+ $ref: `#/components/schemas/${tag.name}`,
321
319
  },
322
320
  },
323
321
  },
@@ -140,8 +140,11 @@ class UsersService extends items_1.ItemsService {
140
140
  * Update many users by primary key
141
141
  */
142
142
  async updateMany(keys, data, opts) {
143
+ var _a, _b;
143
144
  if (data.role) {
144
- const newRole = await this.knex.select('admin_access').from('directus_roles').where('id', data.role).first();
145
+ // data.role will be an object with id with GraphQL mutations
146
+ const roleId = (_b = (_a = data.role) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : data.role;
147
+ const newRole = await this.knex.select('admin_access').from('directus_roles').where('id', roleId).first();
145
148
  if (!(newRole === null || newRole === void 0 ? void 0 : newRole.admin_access)) {
146
149
  await this.checkRemainingAdminExistence(keys);
147
150
  }
@@ -264,7 +267,9 @@ class UsersService extends items_1.ItemsService {
264
267
  });
265
268
  const payload = { email, scope: 'password-reset', hash: (0, utils_2.getSimpleHash)('' + user.password) };
266
269
  const token = jsonwebtoken_1.default.sign(payload, env_1.default.SECRET, { expiresIn: '1d', issuer: 'directus' });
267
- const acceptURL = url ? `${url}?token=${token}` : `${env_1.default.PUBLIC_URL}/admin/reset-password?token=${token}`;
270
+ const acceptURL = url
271
+ ? new url_1.Url(url).setQuery('token', token).toString()
272
+ : new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin', 'reset-password').setQuery('token', token);
268
273
  const subjectLine = subject ? subject : 'Password Reset Request';
269
274
  await mailService.send({
270
275
  to: email,
@@ -19,3 +19,11 @@ export declare type File = {
19
19
  tags: string | null;
20
20
  metadata: Record<string, any> | null;
21
21
  };
22
+ export declare type Metadata = {
23
+ height?: number | undefined;
24
+ width?: number | undefined;
25
+ description?: string | undefined;
26
+ title?: string | undefined;
27
+ tags?: any | undefined;
28
+ metadata?: any | undefined;
29
+ };