directus 9.7.0 → 9.9.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 (112) 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 +10 -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 +14 -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/index.js +0 -3
  42. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.d.ts +3 -0
  43. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.js +17 -0
  44. package/dist/database/migrations/20220314A-add-translation-strings.d.ts +3 -0
  45. package/dist/database/migrations/20220314A-add-translation-strings.js +15 -0
  46. package/dist/database/migrations/20220322A-rename-field-typecast-flags.d.ts +3 -0
  47. package/dist/database/migrations/20220322A-rename-field-typecast-flags.js +77 -0
  48. package/dist/database/migrations/20220323A-add-field-validation.d.ts +3 -0
  49. package/dist/database/migrations/20220323A-add-field-validation.js +17 -0
  50. package/dist/database/migrations/20220325A-fix-typecast-flags.d.ts +3 -0
  51. package/dist/database/migrations/20220325A-fix-typecast-flags.js +49 -0
  52. package/dist/database/migrations/20220325B-add-default-language.d.ts +3 -0
  53. package/dist/database/migrations/20220325B-add-default-language.js +28 -0
  54. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.d.ts +3 -0
  55. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.js +22 -0
  56. package/dist/database/migrations/run.js +1 -1
  57. package/dist/database/run-ast.js +4 -2
  58. package/dist/database/system-data/fields/activity.yaml +4 -4
  59. package/dist/database/system-data/fields/collections.yaml +4 -4
  60. package/dist/database/system-data/fields/fields.yaml +17 -8
  61. package/dist/database/system-data/fields/files.yaml +2 -2
  62. package/dist/database/system-data/fields/panels.yaml +2 -2
  63. package/dist/database/system-data/fields/permissions.yaml +4 -4
  64. package/dist/database/system-data/fields/presets.yaml +17 -3
  65. package/dist/database/system-data/fields/relations.yaml +1 -1
  66. package/dist/database/system-data/fields/revisions.yaml +2 -2
  67. package/dist/database/system-data/fields/roles.yaml +4 -4
  68. package/dist/database/system-data/fields/settings.yaml +18 -4
  69. package/dist/database/system-data/fields/users.yaml +5 -2
  70. package/dist/database/system-data/fields/webhooks.yaml +4 -4
  71. package/dist/env.js +4 -1
  72. package/dist/exceptions/index.d.ts +1 -0
  73. package/dist/exceptions/index.js +1 -0
  74. package/dist/exceptions/token-expired.d.ts +4 -0
  75. package/dist/exceptions/token-expired.js +10 -0
  76. package/dist/logger.js +2 -1
  77. package/dist/middleware/cache.js +10 -0
  78. package/dist/services/activity.js +4 -1
  79. package/dist/services/authorization.d.ts +1 -1
  80. package/dist/services/authorization.js +198 -14
  81. package/dist/services/collections.d.ts +2 -0
  82. package/dist/services/collections.js +230 -198
  83. package/dist/services/fields.js +208 -174
  84. package/dist/services/files.d.ts +5 -1
  85. package/dist/services/files.js +59 -40
  86. package/dist/services/graphql.d.ts +2 -3
  87. package/dist/services/graphql.js +39 -9
  88. package/dist/services/items.js +1 -0
  89. package/dist/services/payload.d.ts +1 -0
  90. package/dist/services/payload.js +29 -24
  91. package/dist/services/relations.js +93 -81
  92. package/dist/services/server.js +1 -0
  93. package/dist/services/shares.js +2 -1
  94. package/dist/services/users.js +3 -1
  95. package/dist/types/files.d.ts +8 -0
  96. package/dist/utils/apply-query.js +38 -10
  97. package/dist/utils/apply-snapshot.d.ts +3 -1
  98. package/dist/utils/apply-snapshot.js +34 -5
  99. package/dist/utils/get-column.d.ts +6 -5
  100. package/dist/utils/get-column.js +16 -8
  101. package/dist/utils/get-graphql-type.js +1 -0
  102. package/dist/utils/get-local-type.js +7 -2
  103. package/dist/utils/get-schema.d.ts +1 -1
  104. package/dist/utils/get-schema.js +15 -10
  105. package/dist/utils/jwt.js +1 -1
  106. package/dist/utils/track.js +3 -2
  107. package/dist/utils/url.d.ts +1 -1
  108. package/dist/utils/url.js +1 -1
  109. package/dist/utils/validate-query.js +19 -15
  110. package/dist/utils/validate-storage.js +3 -1
  111. package/example.env +4 -0
  112. package/package.json +14 -13
@@ -40,6 +40,7 @@ const utils_1 = require("@directus/shared/utils");
40
40
  const lodash_1 = require("lodash");
41
41
  const relations_1 = require("./relations");
42
42
  const helpers_1 = require("../database/helpers");
43
+ const constants_2 = require("@directus/shared/constants");
43
44
  class FieldsService {
44
45
  constructor(options) {
45
46
  this.knex = options.knex || (0, database_1.default)();
@@ -60,6 +61,7 @@ class FieldsService {
60
61
  }));
61
62
  }
62
63
  async readAll(collection) {
64
+ var _a, _b, _c, _d;
63
65
  let fields;
64
66
  if (this.accountability && this.accountability.admin !== true && this.hasReadAccess === false) {
65
67
  throw new exceptions_1.ForbiddenException();
@@ -151,6 +153,15 @@ class FieldsService {
151
153
  return allowedFields.includes(field.field);
152
154
  });
153
155
  }
156
+ // Update specific database type overrides
157
+ for (const field of result) {
158
+ if ((_b = (_a = field.meta) === null || _a === void 0 ? void 0 : _a.special) === null || _b === void 0 ? void 0 : _b.includes('cast-timestamp')) {
159
+ field.type = 'timestamp';
160
+ }
161
+ else if ((_d = (_c = field.meta) === null || _c === void 0 ? void 0 : _c.special) === null || _d === void 0 ? void 0 : _d.includes('cast-datetime')) {
162
+ field.type = 'dateTime';
163
+ }
164
+ }
154
165
  return result;
155
166
  }
156
167
  async readOne(collection, field) {
@@ -199,211 +210,227 @@ class FieldsService {
199
210
  if (this.accountability && this.accountability.admin !== true) {
200
211
  throw new exceptions_1.ForbiddenException();
201
212
  }
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,
213
+ try {
214
+ const exists = field.field in this.schema.collections[collection].fields ||
215
+ (0, lodash_1.isNil)(await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()) === false;
216
+ // Check if field already exists, either as a column, or as a row in directus_fields
217
+ if (exists) {
218
+ throw new exceptions_1.InvalidPayloadException(`Field "${field.field}" already exists in collection "${collection}"`);
219
+ }
220
+ // Add flag for specific database type overrides
221
+ const flagToAdd = this.helpers.date.fieldFlagForField(field.type);
222
+ if (flagToAdd) {
223
+ (0, utils_1.addFieldFlag)(field, flagToAdd);
224
+ }
225
+ await this.knex.transaction(async (trx) => {
226
+ const itemsService = new items_1.ItemsService('directus_fields', {
227
+ knex: trx,
228
+ accountability: this.accountability,
229
+ schema: this.schema,
230
+ });
231
+ const hookAdjustedField = await emitter_1.default.emitFilter(`fields.create`, field, {
232
+ collection: collection,
233
+ }, {
234
+ database: trx,
235
+ schema: this.schema,
236
+ accountability: this.accountability,
237
+ });
238
+ if (hookAdjustedField.type && constants_1.ALIAS_TYPES.includes(hookAdjustedField.type) === false) {
239
+ if (table) {
240
+ this.addColumnToTable(table, hookAdjustedField);
241
+ }
242
+ else {
243
+ await trx.schema.alterTable(collection, (table) => {
244
+ this.addColumnToTable(table, hookAdjustedField);
245
+ });
246
+ }
247
+ }
248
+ if (hookAdjustedField.meta) {
249
+ await itemsService.createOne({
250
+ ...hookAdjustedField.meta,
251
+ collection: collection,
252
+ field: hookAdjustedField.field,
253
+ }, { emitEvents: false });
254
+ }
255
+ emitter_1.default.emitAction(`fields.create`, {
256
+ payload: hookAdjustedField,
257
+ key: hookAdjustedField.field,
258
+ collection: collection,
259
+ }, {
260
+ database: (0, database_1.default)(),
261
+ schema: this.schema,
262
+ accountability: this.accountability,
263
+ });
214
264
  });
215
- const hookAdjustedField = await emitter_1.default.emitFilter(`fields.create`, field, {
265
+ }
266
+ finally {
267
+ if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
268
+ await this.cache.clear();
269
+ }
270
+ await (0, cache_1.clearSystemCache)();
271
+ }
272
+ }
273
+ async updateField(collection, field) {
274
+ if (this.accountability && this.accountability.admin !== true) {
275
+ throw new exceptions_1.ForbiddenException();
276
+ }
277
+ try {
278
+ const hookAdjustedField = await emitter_1.default.emitFilter(`fields.update`, field, {
279
+ keys: [field.field],
216
280
  collection: collection,
217
281
  }, {
218
- database: trx,
282
+ database: this.knex,
219
283
  schema: this.schema,
220
284
  accountability: this.accountability,
221
285
  });
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
- });
286
+ const record = field.meta
287
+ ? await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()
288
+ : null;
289
+ if (hookAdjustedField.schema) {
290
+ const existingColumn = await this.schemaInspector.columnInfo(collection, hookAdjustedField.field);
291
+ if (!(0, lodash_1.isEqual)(existingColumn, hookAdjustedField.schema)) {
292
+ try {
293
+ await this.knex.schema.alterTable(collection, (table) => {
294
+ if (!hookAdjustedField.schema)
295
+ return;
296
+ this.addColumnToTable(table, field, existingColumn);
297
+ });
298
+ }
299
+ catch (err) {
300
+ throw await (0, translate_1.translateDatabaseError)(err);
301
+ }
230
302
  }
231
303
  }
232
304
  if (hookAdjustedField.meta) {
233
- await itemsService.createOne({
234
- ...hookAdjustedField.meta,
235
- collection: collection,
236
- field: hookAdjustedField.field,
237
- }, { emitEvents: false });
305
+ if (record) {
306
+ await this.itemsService.updateOne(record.id, {
307
+ ...hookAdjustedField.meta,
308
+ collection: collection,
309
+ field: hookAdjustedField.field,
310
+ }, { emitEvents: false });
311
+ }
312
+ else {
313
+ await this.itemsService.createOne({
314
+ ...hookAdjustedField.meta,
315
+ collection: collection,
316
+ field: hookAdjustedField.field,
317
+ }, { emitEvents: false });
318
+ }
238
319
  }
239
- emitter_1.default.emitAction(`fields.create`, {
320
+ emitter_1.default.emitAction(`fields.update`, {
240
321
  payload: hookAdjustedField,
241
- key: hookAdjustedField.field,
322
+ keys: [hookAdjustedField.field],
242
323
  collection: collection,
243
324
  }, {
244
325
  database: (0, database_1.default)(),
245
326
  schema: this.schema,
246
327
  accountability: this.accountability,
247
328
  });
248
- });
249
- if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
250
- await this.cache.clear();
251
- }
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
- }
282
- }
329
+ return field.field;
283
330
  }
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 });
331
+ finally {
332
+ if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
333
+ await this.cache.clear();
298
334
  }
335
+ await (0, cache_1.clearSystemCache)();
299
336
  }
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
337
  }
315
338
  async deleteField(collection, field) {
316
339
  if (this.accountability && this.accountability.admin !== true) {
317
340
  throw new exceptions_1.ForbiddenException();
318
341
  }
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,
342
+ try {
343
+ await emitter_1.default.emitFilter('fields.delete', [field], {
344
+ collection: collection,
345
+ }, {
346
+ database: this.knex,
343
347
  schema: this.schema,
344
- });
345
- const fieldsService = new FieldsService({
346
- knex: trx,
347
348
  accountability: this.accountability,
348
- schema: this.schema,
349
349
  });
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);
350
+ await this.knex.transaction(async (trx) => {
351
+ var _a, _b;
352
+ if (this.schema.collections[collection] &&
353
+ field in this.schema.collections[collection].fields &&
354
+ this.schema.collections[collection].fields[field].alias === false) {
355
+ await trx.schema.table(collection, (table) => {
356
+ table.dropColumn(field);
357
+ });
358
+ }
359
+ const relations = this.schema.relations.filter((relation) => {
360
+ var _a;
361
+ return ((relation.collection === collection && relation.field === field) ||
362
+ (relation.related_collection === collection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === field));
363
+ });
364
+ const relationsService = new relations_1.RelationsService({
365
+ knex: trx,
366
+ accountability: this.accountability,
367
+ schema: this.schema,
368
+ });
369
+ const fieldsService = new FieldsService({
370
+ knex: trx,
371
+ accountability: this.accountability,
372
+ schema: this.schema,
373
+ });
374
+ for (const relation of relations) {
375
+ const isM2O = relation.collection === collection && relation.field === field;
376
+ // If the current field is a m2o, delete the related o2m if it exists and remove the relationship
377
+ if (isM2O) {
378
+ await relationsService.deleteOne(collection, field);
379
+ if (relation.related_collection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field)) {
380
+ await fieldsService.deleteField(relation.related_collection, relation.meta.one_field);
381
+ }
382
+ }
383
+ // If the current field is a o2m, just delete the one field config from the relation
384
+ if (!isM2O && ((_b = relation.meta) === null || _b === void 0 ? void 0 : _b.one_field)) {
385
+ await trx('directus_relations')
386
+ .update({ one_field: null })
387
+ .where({ many_collection: relation.collection, many_field: relation.field });
357
388
  }
358
389
  }
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 });
390
+ const collectionMeta = await trx
391
+ .select('archive_field', 'sort_field')
392
+ .from('directus_collections')
393
+ .where({ collection })
394
+ .first();
395
+ const collectionMetaUpdates = {};
396
+ if ((collectionMeta === null || collectionMeta === void 0 ? void 0 : collectionMeta.archive_field) === field) {
397
+ collectionMetaUpdates.archive_field = null;
364
398
  }
399
+ if ((collectionMeta === null || collectionMeta === void 0 ? void 0 : collectionMeta.sort_field) === field) {
400
+ collectionMetaUpdates.sort_field = null;
401
+ }
402
+ if (Object.keys(collectionMetaUpdates).length > 0) {
403
+ await trx('directus_collections').update(collectionMetaUpdates).where({ collection });
404
+ }
405
+ // Cleanup directus_fields
406
+ const metaRow = await trx
407
+ .select('collection', 'field')
408
+ .from('directus_fields')
409
+ .where({ collection, field })
410
+ .first();
411
+ if (metaRow) {
412
+ // Handle recursive FK constraints
413
+ await trx('directus_fields')
414
+ .update({ group: null })
415
+ .where({ group: metaRow.field, collection: metaRow.collection });
416
+ }
417
+ await trx('directus_fields').delete().where({ collection, field });
418
+ });
419
+ emitter_1.default.emitAction('fields.delete', {
420
+ payload: [field],
421
+ collection: collection,
422
+ }, {
423
+ database: this.knex,
424
+ schema: this.schema,
425
+ accountability: this.accountability,
426
+ });
427
+ }
428
+ finally {
429
+ if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
430
+ await this.cache.clear();
365
431
  }
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
- });
432
+ await (0, cache_1.clearSystemCache)();
433
+ }
407
434
  }
408
435
  addColumnToTable(table, field, alter = null) {
409
436
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
@@ -441,9 +468,16 @@ class FieldsService {
441
468
  column = table[field.type](field.field);
442
469
  }
443
470
  if (((_f = field.schema) === null || _f === void 0 ? void 0 : _f.default_value) !== undefined) {
444
- if (typeof field.schema.default_value === 'string' && field.schema.default_value.toLowerCase() === 'now()') {
471
+ if (typeof field.schema.default_value === 'string' &&
472
+ (field.schema.default_value.toLowerCase() === 'now()' || field.schema.default_value === 'CURRENT_TIMESTAMP')) {
445
473
  column.defaultTo(this.knex.fn.now());
446
474
  }
475
+ else if (typeof field.schema.default_value === 'string' &&
476
+ field.schema.default_value.includes('CURRENT_TIMESTAMP(') &&
477
+ field.schema.default_value.includes(')')) {
478
+ const precision = field.schema.default_value.match(constants_2.REGEX_BETWEEN_PARENS)[1];
479
+ column.defaultTo(this.knex.fn.now(Number(precision)));
480
+ }
447
481
  else if (typeof field.schema.default_value === 'string' &&
448
482
  ['"null"', 'null'].includes(field.schema.default_value.toLowerCase())) {
449
483
  column.defaultTo(null);
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { AbstractServiceOptions, File, PrimaryKey, MutationOptions } from '../types';
2
+ import { AbstractServiceOptions, File, PrimaryKey, MutationOptions, Metadata } from '../types';
3
3
  import { ItemsService } from './items';
4
4
  export declare class FilesService extends ItemsService {
5
5
  constructor(options: AbstractServiceOptions);
@@ -10,6 +10,10 @@ export declare class FilesService extends ItemsService {
10
10
  filename_download: string;
11
11
  storage: string;
12
12
  }, primaryKey?: PrimaryKey, opts?: MutationOptions): Promise<PrimaryKey>;
13
+ /**
14
+ * Extract metadata from a buffer's content
15
+ */
16
+ getMetadata(bufferContent: any, allowList?: any): Promise<Metadata>;
13
17
  /**
14
18
  * Import a single file from an external URL
15
19
  */
@@ -88,46 +88,13 @@ class FilesService extends items_1.ItemsService {
88
88
  payload.filesize = size;
89
89
  if (['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/tiff'].includes(payload.type)) {
90
90
  const buffer = await storage_1.default.disk(data.storage).getBuffer(payload.filename_disk);
91
- try {
92
- const meta = await (0, sharp_1.default)(buffer.content, {}).metadata();
93
- if (meta.orientation && meta.orientation >= 5) {
94
- payload.height = meta.width;
95
- payload.width = meta.height;
96
- }
97
- else {
98
- payload.width = meta.width;
99
- payload.height = meta.height;
100
- }
101
- }
102
- catch (err) {
103
- logger_1.default.warn(`Couldn't extract sharp metadata from file`);
104
- logger_1.default.warn(err);
105
- }
106
- payload.metadata = {};
107
- try {
108
- payload.metadata = await exifr_1.default.parse(buffer.content, {
109
- icc: false,
110
- iptc: true,
111
- ifd1: true,
112
- interop: true,
113
- translateValues: true,
114
- reviveValues: true,
115
- mergeOutput: false,
116
- });
117
- if ((_b = (_a = payload.metadata) === null || _a === void 0 ? void 0 : _a.iptc) === null || _b === void 0 ? void 0 : _b.Headline) {
118
- payload.title = payload.metadata.iptc.Headline;
119
- }
120
- if (!payload.description && ((_d = (_c = payload.metadata) === null || _c === void 0 ? void 0 : _c.iptc) === null || _d === void 0 ? void 0 : _d.Caption)) {
121
- payload.description = payload.metadata.iptc.Caption;
122
- }
123
- if ((_f = (_e = payload.metadata) === null || _e === void 0 ? void 0 : _e.iptc) === null || _f === void 0 ? void 0 : _f.Keywords) {
124
- payload.tags = payload.metadata.iptc.Keywords;
125
- }
126
- }
127
- catch (err) {
128
- logger_1.default.warn(`Couldn't extract EXIF metadata from file`);
129
- logger_1.default.warn(err);
130
- }
91
+ const { height, width, description, title, tags, metadata } = await this.getMetadata(buffer.content);
92
+ (_a = payload.height) !== null && _a !== void 0 ? _a : (payload.height = height);
93
+ (_b = payload.width) !== null && _b !== void 0 ? _b : (payload.width = width);
94
+ (_c = payload.description) !== null && _c !== void 0 ? _c : (payload.description = description);
95
+ (_d = payload.title) !== null && _d !== void 0 ? _d : (payload.title = title);
96
+ (_e = payload.tags) !== null && _e !== void 0 ? _e : (payload.tags = tags);
97
+ (_f = payload.metadata) !== null && _f !== void 0 ? _f : (payload.metadata = metadata);
131
98
  }
132
99
  // We do this in a service without accountability. Even if you don't have update permissions to the file,
133
100
  // we still want to be able to set the extracted values from the file on create
@@ -152,6 +119,58 @@ class FilesService extends items_1.ItemsService {
152
119
  }
153
120
  return primaryKey;
154
121
  }
122
+ /**
123
+ * Extract metadata from a buffer's content
124
+ */
125
+ async getMetadata(bufferContent, allowList = env_1.default.FILE_METADATA_ALLOW_LIST) {
126
+ const metadata = {};
127
+ try {
128
+ const sharpMetadata = await (0, sharp_1.default)(bufferContent, {}).metadata();
129
+ if (sharpMetadata.orientation && sharpMetadata.orientation >= 5) {
130
+ metadata.height = sharpMetadata.width;
131
+ metadata.width = sharpMetadata.height;
132
+ }
133
+ else {
134
+ metadata.width = sharpMetadata.width;
135
+ metadata.height = sharpMetadata.height;
136
+ }
137
+ }
138
+ catch (err) {
139
+ logger_1.default.warn(`Couldn't extract sharp metadata from file`);
140
+ logger_1.default.warn(err);
141
+ }
142
+ try {
143
+ const exifrMetadata = await exifr_1.default.parse(bufferContent, {
144
+ icc: false,
145
+ iptc: true,
146
+ ifd1: true,
147
+ interop: true,
148
+ translateValues: true,
149
+ reviveValues: true,
150
+ mergeOutput: false,
151
+ });
152
+ if (allowList === '*' || (allowList === null || allowList === void 0 ? void 0 : allowList[0]) === '*') {
153
+ metadata.metadata = exifrMetadata;
154
+ }
155
+ else {
156
+ metadata.metadata = (0, lodash_1.pick)(exifrMetadata, allowList);
157
+ }
158
+ if (!metadata.description && (exifrMetadata === null || exifrMetadata === void 0 ? void 0 : exifrMetadata.Caption)) {
159
+ metadata.description = exifrMetadata.Caption;
160
+ }
161
+ if (exifrMetadata === null || exifrMetadata === void 0 ? void 0 : exifrMetadata.Headline) {
162
+ metadata.title = exifrMetadata.Headline;
163
+ }
164
+ if (exifrMetadata === null || exifrMetadata === void 0 ? void 0 : exifrMetadata.Keywords) {
165
+ metadata.tags = exifrMetadata.Keywords;
166
+ }
167
+ }
168
+ catch (err) {
169
+ logger_1.default.warn(`Couldn't extract EXIF metadata from file`);
170
+ logger_1.default.warn(err);
171
+ }
172
+ return metadata;
173
+ }
155
174
  /**
156
175
  * Import a single file from an external URL
157
176
  */
@@ -1,9 +1,8 @@
1
+ import { BaseException } from '@directus/shared/exceptions';
2
+ import { Accountability, Query, SchemaOverview } from '@directus/shared/types';
1
3
  import { ArgumentNode, FormattedExecutionResult, FragmentDefinitionNode, GraphQLError, GraphQLResolveInfo, GraphQLScalarType, GraphQLSchema, ObjectFieldNode, SelectionNode } from 'graphql';
2
- import { SchemaOverview } from '@directus/shared/types';
3
4
  import { ObjectTypeComposer, SchemaComposer } from 'graphql-compose';
4
5
  import { Knex } from 'knex';
5
- import { BaseException } from '@directus/shared/exceptions';
6
- import { Accountability, Query } from '@directus/shared/types';
7
6
  import { AbstractServiceOptions, GraphQLParams, Item } from '../types';
8
7
  import { ItemsService } from './items';
9
8
  export declare const GraphQLGeoJSON: GraphQLScalarType;