directus 9.7.1 → 9.8.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 (59) hide show
  1. package/dist/cache.js +2 -2
  2. package/dist/database/helpers/date/dialects/default.d.ts +3 -0
  3. package/dist/database/helpers/date/dialects/default.js +7 -0
  4. package/dist/database/helpers/date/dialects/sqlite.d.ts +0 -9
  5. package/dist/database/helpers/date/dialects/sqlite.js +0 -24
  6. package/dist/database/helpers/date/index.d.ts +6 -6
  7. package/dist/database/helpers/date/index.js +13 -13
  8. package/dist/database/helpers/date/types.d.ts +0 -9
  9. package/dist/database/helpers/{date → fn}/dialects/mssql.d.ts +3 -2
  10. package/dist/database/helpers/{date → fn}/dialects/mssql.js +14 -3
  11. package/dist/database/helpers/{date → fn}/dialects/mysql.d.ts +3 -2
  12. package/dist/database/helpers/{date → fn}/dialects/mysql.js +14 -3
  13. package/dist/database/helpers/{date → fn}/dialects/oracle.d.ts +3 -2
  14. package/dist/database/helpers/{date → fn}/dialects/oracle.js +14 -3
  15. package/dist/database/helpers/{date → fn}/dialects/postgres.d.ts +3 -2
  16. package/dist/database/helpers/{date → fn}/dialects/postgres.js +14 -3
  17. package/dist/database/helpers/fn/dialects/sqlite.d.ts +13 -0
  18. package/dist/database/helpers/fn/dialects/sqlite.js +42 -0
  19. package/dist/database/helpers/fn/index.d.ts +7 -0
  20. package/dist/database/helpers/fn/index.js +17 -0
  21. package/dist/database/helpers/fn/types.d.ts +18 -0
  22. package/dist/database/helpers/fn/types.js +27 -0
  23. package/dist/database/helpers/index.d.ts +4 -1
  24. package/dist/database/helpers/index.js +7 -1
  25. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.d.ts +3 -0
  26. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.js +17 -0
  27. package/dist/database/migrations/20220322A-rename-field-typecast-flags.js +6 -2
  28. package/dist/database/migrations/20220323A-add-field-validation.d.ts +3 -0
  29. package/dist/database/migrations/20220323A-add-field-validation.js +17 -0
  30. package/dist/database/migrations/20220325A-fix-typecast-flags.d.ts +3 -0
  31. package/dist/database/migrations/20220325A-fix-typecast-flags.js +49 -0
  32. package/dist/database/migrations/20220325B-add-default-language.d.ts +3 -0
  33. package/dist/database/migrations/20220325B-add-default-language.js +28 -0
  34. package/dist/database/run-ast.js +4 -2
  35. package/dist/database/system-data/fields/activity.yaml +4 -4
  36. package/dist/database/system-data/fields/fields.yaml +9 -0
  37. package/dist/database/system-data/fields/presets.yaml +14 -0
  38. package/dist/database/system-data/fields/settings.yaml +13 -1
  39. package/dist/logger.js +2 -1
  40. package/dist/services/activity.js +4 -1
  41. package/dist/services/authorization.d.ts +1 -1
  42. package/dist/services/authorization.js +128 -44
  43. package/dist/services/collections.js +222 -198
  44. package/dist/services/fields.js +184 -173
  45. package/dist/services/payload.js +8 -6
  46. package/dist/services/relations.js +93 -81
  47. package/dist/services/server.js +1 -0
  48. package/dist/services/shares.js +2 -1
  49. package/dist/services/users.js +3 -1
  50. package/dist/utils/apply-query.js +21 -3
  51. package/dist/utils/get-column.d.ts +6 -5
  52. package/dist/utils/get-column.js +16 -8
  53. package/dist/utils/get-schema.d.ts +1 -1
  54. package/dist/utils/get-schema.js +15 -10
  55. package/dist/utils/track.js +3 -2
  56. package/dist/utils/url.d.ts +1 -1
  57. package/dist/utils/url.js +1 -1
  58. package/dist/utils/validate-storage.js +3 -1
  59. package/package.json +12 -12
@@ -199,211 +199,222 @@ class FieldsService {
199
199
  if (this.accountability && this.accountability.admin !== true) {
200
200
  throw new exceptions_1.ForbiddenException();
201
201
  }
202
- const exists = field.field in this.schema.collections[collection].fields ||
203
- (0, lodash_1.isNil)(await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()) ===
204
- false;
205
- // Check if field already exists, either as a column, or as a row in directus_fields
206
- if (exists) {
207
- throw new exceptions_1.InvalidPayloadException(`Field "${field.field}" already exists in collection "${collection}"`);
208
- }
209
- await this.knex.transaction(async (trx) => {
210
- const itemsService = new items_1.ItemsService('directus_fields', {
211
- knex: trx,
212
- accountability: this.accountability,
213
- schema: this.schema,
202
+ try {
203
+ const exists = field.field in this.schema.collections[collection].fields ||
204
+ (0, lodash_1.isNil)(await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()) === false;
205
+ // Check if field already exists, either as a column, or as a row in directus_fields
206
+ if (exists) {
207
+ throw new exceptions_1.InvalidPayloadException(`Field "${field.field}" already exists in collection "${collection}"`);
208
+ }
209
+ await this.knex.transaction(async (trx) => {
210
+ const itemsService = new items_1.ItemsService('directus_fields', {
211
+ knex: trx,
212
+ accountability: this.accountability,
213
+ schema: this.schema,
214
+ });
215
+ const hookAdjustedField = await emitter_1.default.emitFilter(`fields.create`, field, {
216
+ collection: collection,
217
+ }, {
218
+ database: trx,
219
+ schema: this.schema,
220
+ accountability: this.accountability,
221
+ });
222
+ if (hookAdjustedField.type && constants_1.ALIAS_TYPES.includes(hookAdjustedField.type) === false) {
223
+ if (table) {
224
+ this.addColumnToTable(table, hookAdjustedField);
225
+ }
226
+ else {
227
+ await trx.schema.alterTable(collection, (table) => {
228
+ this.addColumnToTable(table, hookAdjustedField);
229
+ });
230
+ }
231
+ }
232
+ if (hookAdjustedField.meta) {
233
+ await itemsService.createOne({
234
+ ...hookAdjustedField.meta,
235
+ collection: collection,
236
+ field: hookAdjustedField.field,
237
+ }, { emitEvents: false });
238
+ }
239
+ emitter_1.default.emitAction(`fields.create`, {
240
+ payload: hookAdjustedField,
241
+ key: hookAdjustedField.field,
242
+ collection: collection,
243
+ }, {
244
+ database: (0, database_1.default)(),
245
+ schema: this.schema,
246
+ accountability: this.accountability,
247
+ });
214
248
  });
215
- const hookAdjustedField = await emitter_1.default.emitFilter(`fields.create`, field, {
249
+ }
250
+ finally {
251
+ if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
252
+ await this.cache.clear();
253
+ }
254
+ await (0, cache_1.clearSystemCache)();
255
+ }
256
+ }
257
+ async updateField(collection, field) {
258
+ if (this.accountability && this.accountability.admin !== true) {
259
+ throw new exceptions_1.ForbiddenException();
260
+ }
261
+ try {
262
+ const hookAdjustedField = await emitter_1.default.emitFilter(`fields.update`, field, {
263
+ keys: [field.field],
216
264
  collection: collection,
217
265
  }, {
218
- database: trx,
266
+ database: this.knex,
219
267
  schema: this.schema,
220
268
  accountability: this.accountability,
221
269
  });
222
- if (hookAdjustedField.type && constants_1.ALIAS_TYPES.includes(hookAdjustedField.type) === false) {
223
- if (table) {
224
- this.addColumnToTable(table, hookAdjustedField);
225
- }
226
- else {
227
- await trx.schema.alterTable(collection, (table) => {
228
- this.addColumnToTable(table, hookAdjustedField);
229
- });
270
+ const record = field.meta
271
+ ? await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()
272
+ : null;
273
+ if (hookAdjustedField.schema) {
274
+ const existingColumn = await this.schemaInspector.columnInfo(collection, hookAdjustedField.field);
275
+ if (!(0, lodash_1.isEqual)(existingColumn, hookAdjustedField.schema)) {
276
+ try {
277
+ await this.knex.schema.alterTable(collection, (table) => {
278
+ if (!hookAdjustedField.schema)
279
+ return;
280
+ this.addColumnToTable(table, field, existingColumn);
281
+ });
282
+ }
283
+ catch (err) {
284
+ throw await (0, translate_1.translateDatabaseError)(err);
285
+ }
230
286
  }
231
287
  }
232
288
  if (hookAdjustedField.meta) {
233
- await itemsService.createOne({
234
- ...hookAdjustedField.meta,
235
- collection: collection,
236
- field: hookAdjustedField.field,
237
- }, { emitEvents: false });
289
+ if (record) {
290
+ await this.itemsService.updateOne(record.id, {
291
+ ...hookAdjustedField.meta,
292
+ collection: collection,
293
+ field: hookAdjustedField.field,
294
+ }, { emitEvents: false });
295
+ }
296
+ else {
297
+ await this.itemsService.createOne({
298
+ ...hookAdjustedField.meta,
299
+ collection: collection,
300
+ field: hookAdjustedField.field,
301
+ }, { emitEvents: false });
302
+ }
238
303
  }
239
- emitter_1.default.emitAction(`fields.create`, {
304
+ emitter_1.default.emitAction(`fields.update`, {
240
305
  payload: hookAdjustedField,
241
- key: hookAdjustedField.field,
306
+ keys: [hookAdjustedField.field],
242
307
  collection: collection,
243
308
  }, {
244
309
  database: (0, database_1.default)(),
245
310
  schema: this.schema,
246
311
  accountability: this.accountability,
247
312
  });
248
- });
249
- if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
250
- await this.cache.clear();
313
+ return field.field;
251
314
  }
252
- await (0, cache_1.clearSystemCache)();
253
- }
254
- async updateField(collection, field) {
255
- if (this.accountability && this.accountability.admin !== true) {
256
- throw new exceptions_1.ForbiddenException();
257
- }
258
- const hookAdjustedField = await emitter_1.default.emitFilter(`fields.update`, field, {
259
- keys: [field.field],
260
- collection: collection,
261
- }, {
262
- database: this.knex,
263
- schema: this.schema,
264
- accountability: this.accountability,
265
- });
266
- const record = field.meta
267
- ? await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()
268
- : null;
269
- if (hookAdjustedField.schema) {
270
- const existingColumn = await this.schemaInspector.columnInfo(collection, hookAdjustedField.field);
271
- if (!(0, lodash_1.isEqual)(existingColumn, hookAdjustedField.schema)) {
272
- try {
273
- await this.knex.schema.alterTable(collection, (table) => {
274
- if (!hookAdjustedField.schema)
275
- return;
276
- this.addColumnToTable(table, field, existingColumn);
277
- });
278
- }
279
- catch (err) {
280
- throw await (0, translate_1.translateDatabaseError)(err);
281
- }
315
+ finally {
316
+ if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
317
+ await this.cache.clear();
282
318
  }
319
+ await (0, cache_1.clearSystemCache)();
283
320
  }
284
- if (hookAdjustedField.meta) {
285
- if (record) {
286
- await this.itemsService.updateOne(record.id, {
287
- ...hookAdjustedField.meta,
288
- collection: collection,
289
- field: hookAdjustedField.field,
290
- }, { emitEvents: false });
291
- }
292
- else {
293
- await this.itemsService.createOne({
294
- ...hookAdjustedField.meta,
295
- collection: collection,
296
- field: hookAdjustedField.field,
297
- }, { emitEvents: false });
298
- }
299
- }
300
- if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
301
- await this.cache.clear();
302
- }
303
- await (0, cache_1.clearSystemCache)();
304
- emitter_1.default.emitAction(`fields.update`, {
305
- payload: hookAdjustedField,
306
- keys: [hookAdjustedField.field],
307
- collection: collection,
308
- }, {
309
- database: (0, database_1.default)(),
310
- schema: this.schema,
311
- accountability: this.accountability,
312
- });
313
- return field.field;
314
321
  }
315
322
  async deleteField(collection, field) {
316
323
  if (this.accountability && this.accountability.admin !== true) {
317
324
  throw new exceptions_1.ForbiddenException();
318
325
  }
319
- await emitter_1.default.emitFilter('fields.delete', [field], {
320
- collection: collection,
321
- }, {
322
- database: this.knex,
323
- schema: this.schema,
324
- accountability: this.accountability,
325
- });
326
- await this.knex.transaction(async (trx) => {
327
- var _a, _b;
328
- if (this.schema.collections[collection] &&
329
- field in this.schema.collections[collection].fields &&
330
- this.schema.collections[collection].fields[field].alias === false) {
331
- await trx.schema.table(collection, (table) => {
332
- table.dropColumn(field);
333
- });
334
- }
335
- const relations = this.schema.relations.filter((relation) => {
336
- var _a;
337
- return ((relation.collection === collection && relation.field === field) ||
338
- (relation.related_collection === collection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === field));
339
- });
340
- const relationsService = new relations_1.RelationsService({
341
- knex: trx,
342
- accountability: this.accountability,
326
+ try {
327
+ await emitter_1.default.emitFilter('fields.delete', [field], {
328
+ collection: collection,
329
+ }, {
330
+ database: this.knex,
343
331
  schema: this.schema,
344
- });
345
- const fieldsService = new FieldsService({
346
- knex: trx,
347
332
  accountability: this.accountability,
348
- schema: this.schema,
349
333
  });
350
- for (const relation of relations) {
351
- const isM2O = relation.collection === collection && relation.field === field;
352
- // If the current field is a m2o, delete the related o2m if it exists and remove the relationship
353
- if (isM2O) {
354
- await relationsService.deleteOne(collection, field);
355
- if (relation.related_collection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field)) {
356
- await fieldsService.deleteField(relation.related_collection, relation.meta.one_field);
334
+ await this.knex.transaction(async (trx) => {
335
+ var _a, _b;
336
+ if (this.schema.collections[collection] &&
337
+ field in this.schema.collections[collection].fields &&
338
+ this.schema.collections[collection].fields[field].alias === false) {
339
+ await trx.schema.table(collection, (table) => {
340
+ table.dropColumn(field);
341
+ });
342
+ }
343
+ const relations = this.schema.relations.filter((relation) => {
344
+ var _a;
345
+ return ((relation.collection === collection && relation.field === field) ||
346
+ (relation.related_collection === collection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === field));
347
+ });
348
+ const relationsService = new relations_1.RelationsService({
349
+ knex: trx,
350
+ accountability: this.accountability,
351
+ schema: this.schema,
352
+ });
353
+ const fieldsService = new FieldsService({
354
+ knex: trx,
355
+ accountability: this.accountability,
356
+ schema: this.schema,
357
+ });
358
+ for (const relation of relations) {
359
+ const isM2O = relation.collection === collection && relation.field === field;
360
+ // If the current field is a m2o, delete the related o2m if it exists and remove the relationship
361
+ if (isM2O) {
362
+ await relationsService.deleteOne(collection, field);
363
+ if (relation.related_collection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field)) {
364
+ await fieldsService.deleteField(relation.related_collection, relation.meta.one_field);
365
+ }
366
+ }
367
+ // If the current field is a o2m, just delete the one field config from the relation
368
+ if (!isM2O && ((_b = relation.meta) === null || _b === void 0 ? void 0 : _b.one_field)) {
369
+ await trx('directus_relations')
370
+ .update({ one_field: null })
371
+ .where({ many_collection: relation.collection, many_field: relation.field });
357
372
  }
358
373
  }
359
- // If the current field is a o2m, just delete the one field config from the relation
360
- if (!isM2O && ((_b = relation.meta) === null || _b === void 0 ? void 0 : _b.one_field)) {
361
- await trx('directus_relations')
362
- .update({ one_field: null })
363
- .where({ many_collection: relation.collection, many_field: relation.field });
374
+ const collectionMeta = await trx
375
+ .select('archive_field', 'sort_field')
376
+ .from('directus_collections')
377
+ .where({ collection })
378
+ .first();
379
+ const collectionMetaUpdates = {};
380
+ if ((collectionMeta === null || collectionMeta === void 0 ? void 0 : collectionMeta.archive_field) === field) {
381
+ collectionMetaUpdates.archive_field = null;
364
382
  }
383
+ if ((collectionMeta === null || collectionMeta === void 0 ? void 0 : collectionMeta.sort_field) === field) {
384
+ collectionMetaUpdates.sort_field = null;
385
+ }
386
+ if (Object.keys(collectionMetaUpdates).length > 0) {
387
+ await trx('directus_collections').update(collectionMetaUpdates).where({ collection });
388
+ }
389
+ // Cleanup directus_fields
390
+ const metaRow = await trx
391
+ .select('collection', 'field')
392
+ .from('directus_fields')
393
+ .where({ collection, field })
394
+ .first();
395
+ if (metaRow) {
396
+ // Handle recursive FK constraints
397
+ await trx('directus_fields')
398
+ .update({ group: null })
399
+ .where({ group: metaRow.field, collection: metaRow.collection });
400
+ }
401
+ await trx('directus_fields').delete().where({ collection, field });
402
+ });
403
+ emitter_1.default.emitAction('fields.delete', {
404
+ payload: [field],
405
+ collection: collection,
406
+ }, {
407
+ database: this.knex,
408
+ schema: this.schema,
409
+ accountability: this.accountability,
410
+ });
411
+ }
412
+ finally {
413
+ if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
414
+ await this.cache.clear();
365
415
  }
366
- const collectionMeta = await trx
367
- .select('archive_field', 'sort_field')
368
- .from('directus_collections')
369
- .where({ collection })
370
- .first();
371
- const collectionMetaUpdates = {};
372
- if ((collectionMeta === null || collectionMeta === void 0 ? void 0 : collectionMeta.archive_field) === field) {
373
- collectionMetaUpdates.archive_field = null;
374
- }
375
- if ((collectionMeta === null || collectionMeta === void 0 ? void 0 : collectionMeta.sort_field) === field) {
376
- collectionMetaUpdates.sort_field = null;
377
- }
378
- if (Object.keys(collectionMetaUpdates).length > 0) {
379
- await trx('directus_collections').update(collectionMetaUpdates).where({ collection });
380
- }
381
- // Cleanup directus_fields
382
- const metaRow = await trx
383
- .select('collection', 'field')
384
- .from('directus_fields')
385
- .where({ collection, field })
386
- .first();
387
- if (metaRow) {
388
- // Handle recursive FK constraints
389
- await trx('directus_fields')
390
- .update({ group: null })
391
- .where({ group: metaRow.field, collection: metaRow.collection });
392
- }
393
- await trx('directus_fields').delete().where({ collection, field });
394
- });
395
- if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
396
- await this.cache.clear();
397
- }
398
- await (0, cache_1.clearSystemCache)();
399
- emitter_1.default.emitAction('fields.delete', {
400
- payload: [field],
401
- collection: collection,
402
- }, {
403
- database: this.knex,
404
- schema: this.schema,
405
- accountability: this.accountability,
406
- });
416
+ await (0, cache_1.clearSystemCache)();
417
+ }
407
418
  }
408
419
  addColumnToTable(table, field, alter = null) {
409
420
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
@@ -247,7 +247,7 @@ class PayloadService {
247
247
  payload[name] = newValue;
248
248
  }
249
249
  if (dateColumn.type === 'date') {
250
- const [year, month, day] = value.toISOString().substr(0, 10).split('-');
250
+ const [year, month, day] = value.toISOString().slice(0, 10).split('-');
251
251
  // Strip off the time / timezone information from a date-only value
252
252
  const newValue = `${year}-${month}-${day}`;
253
253
  payload[name] = newValue;
@@ -429,7 +429,7 @@ class PayloadService {
429
429
  delete: joi_1.default.array().items(joi_1.default.string(), joi_1.default.number()),
430
430
  });
431
431
  for (const relation of relationsToProcess) {
432
- if (!relation.meta || !payload[relation.meta.one_field])
432
+ if (!relation.meta)
433
433
  continue;
434
434
  const currentPrimaryKeyField = this.schema.collections[relation.related_collection].primary;
435
435
  const relatedPrimaryKeyField = this.schema.collections[relation.collection].primary;
@@ -441,9 +441,11 @@ class PayloadService {
441
441
  const recordsToUpsert = [];
442
442
  const savedPrimaryKeys = [];
443
443
  // 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];
444
+ const field = payload[relation.meta.one_field];
445
+ if (!field || Array.isArray(field)) {
446
+ const updates = field || []; // treat falsey values as removing all children
447
+ for (let i = 0; i < updates.length; i++) {
448
+ const relatedRecord = updates[i];
447
449
  let record = (0, lodash_1.cloneDeep)(relatedRecord);
448
450
  if (typeof relatedRecord === 'string' || typeof relatedRecord === 'number') {
449
451
  const existingRecord = await this.knex
@@ -507,7 +509,7 @@ class PayloadService {
507
509
  }
508
510
  // "Updates" object w/ create/update/delete
509
511
  else {
510
- const alterations = payload[relation.meta.one_field];
512
+ const alterations = field;
511
513
  const { error } = nestedUpdateSchema.validate(alterations);
512
514
  if (error)
513
515
  throw new exceptions_1.InvalidPayloadException(`Invalid one-to-many update structure: ${error.message}`);
@@ -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',