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
@@ -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) {
@@ -184,6 +195,8 @@ class FieldsService {
184
195
  catch {
185
196
  // Do nothing
186
197
  }
198
+ if (!column && !fieldInfo)
199
+ throw new exceptions_1.ForbiddenException();
187
200
  const type = (0, get_local_type_1.default)(column, fieldInfo);
188
201
  const data = {
189
202
  collection,
@@ -199,211 +212,227 @@ class FieldsService {
199
212
  if (this.accountability && this.accountability.admin !== true) {
200
213
  throw new exceptions_1.ForbiddenException();
201
214
  }
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,
215
+ try {
216
+ const exists = field.field in this.schema.collections[collection].fields ||
217
+ (0, lodash_1.isNil)(await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()) === false;
218
+ // Check if field already exists, either as a column, or as a row in directus_fields
219
+ if (exists) {
220
+ throw new exceptions_1.InvalidPayloadException(`Field "${field.field}" already exists in collection "${collection}"`);
221
+ }
222
+ // Add flag for specific database type overrides
223
+ const flagToAdd = this.helpers.date.fieldFlagForField(field.type);
224
+ if (flagToAdd) {
225
+ (0, utils_1.addFieldFlag)(field, flagToAdd);
226
+ }
227
+ await this.knex.transaction(async (trx) => {
228
+ const itemsService = new items_1.ItemsService('directus_fields', {
229
+ knex: trx,
230
+ accountability: this.accountability,
231
+ schema: this.schema,
232
+ });
233
+ const hookAdjustedField = await emitter_1.default.emitFilter(`fields.create`, field, {
234
+ collection: collection,
235
+ }, {
236
+ database: trx,
237
+ schema: this.schema,
238
+ accountability: this.accountability,
239
+ });
240
+ if (hookAdjustedField.type && constants_1.ALIAS_TYPES.includes(hookAdjustedField.type) === false) {
241
+ if (table) {
242
+ this.addColumnToTable(table, hookAdjustedField);
243
+ }
244
+ else {
245
+ await trx.schema.alterTable(collection, (table) => {
246
+ this.addColumnToTable(table, hookAdjustedField);
247
+ });
248
+ }
249
+ }
250
+ if (hookAdjustedField.meta) {
251
+ await itemsService.createOne({
252
+ ...hookAdjustedField.meta,
253
+ collection: collection,
254
+ field: hookAdjustedField.field,
255
+ }, { emitEvents: false });
256
+ }
257
+ emitter_1.default.emitAction(`fields.create`, {
258
+ payload: hookAdjustedField,
259
+ key: hookAdjustedField.field,
260
+ collection: collection,
261
+ }, {
262
+ database: (0, database_1.default)(),
263
+ schema: this.schema,
264
+ accountability: this.accountability,
265
+ });
214
266
  });
215
- const hookAdjustedField = await emitter_1.default.emitFilter(`fields.create`, field, {
267
+ }
268
+ finally {
269
+ if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
270
+ await this.cache.clear();
271
+ }
272
+ await (0, cache_1.clearSystemCache)();
273
+ }
274
+ }
275
+ async updateField(collection, field) {
276
+ if (this.accountability && this.accountability.admin !== true) {
277
+ throw new exceptions_1.ForbiddenException();
278
+ }
279
+ try {
280
+ const hookAdjustedField = await emitter_1.default.emitFilter(`fields.update`, field, {
281
+ keys: [field.field],
216
282
  collection: collection,
217
283
  }, {
218
- database: trx,
284
+ database: this.knex,
219
285
  schema: this.schema,
220
286
  accountability: this.accountability,
221
287
  });
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
- });
288
+ const record = field.meta
289
+ ? await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()
290
+ : null;
291
+ if (hookAdjustedField.schema) {
292
+ const existingColumn = await this.schemaInspector.columnInfo(collection, hookAdjustedField.field);
293
+ if (!(0, lodash_1.isEqual)(existingColumn, hookAdjustedField.schema)) {
294
+ try {
295
+ await this.knex.schema.alterTable(collection, (table) => {
296
+ if (!hookAdjustedField.schema)
297
+ return;
298
+ this.addColumnToTable(table, field, existingColumn);
299
+ });
300
+ }
301
+ catch (err) {
302
+ throw await (0, translate_1.translateDatabaseError)(err);
303
+ }
230
304
  }
231
305
  }
232
306
  if (hookAdjustedField.meta) {
233
- await itemsService.createOne({
234
- ...hookAdjustedField.meta,
235
- collection: collection,
236
- field: hookAdjustedField.field,
237
- }, { emitEvents: false });
307
+ if (record) {
308
+ await this.itemsService.updateOne(record.id, {
309
+ ...hookAdjustedField.meta,
310
+ collection: collection,
311
+ field: hookAdjustedField.field,
312
+ }, { emitEvents: false });
313
+ }
314
+ else {
315
+ await this.itemsService.createOne({
316
+ ...hookAdjustedField.meta,
317
+ collection: collection,
318
+ field: hookAdjustedField.field,
319
+ }, { emitEvents: false });
320
+ }
238
321
  }
239
- emitter_1.default.emitAction(`fields.create`, {
322
+ emitter_1.default.emitAction(`fields.update`, {
240
323
  payload: hookAdjustedField,
241
- key: hookAdjustedField.field,
324
+ keys: [hookAdjustedField.field],
242
325
  collection: collection,
243
326
  }, {
244
327
  database: (0, database_1.default)(),
245
328
  schema: this.schema,
246
329
  accountability: this.accountability,
247
330
  });
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();
331
+ return field.field;
257
332
  }
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
- }
283
- }
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 });
333
+ finally {
334
+ if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
335
+ await this.cache.clear();
298
336
  }
337
+ await (0, cache_1.clearSystemCache)();
299
338
  }
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
339
  }
315
340
  async deleteField(collection, field) {
316
341
  if (this.accountability && this.accountability.admin !== true) {
317
342
  throw new exceptions_1.ForbiddenException();
318
343
  }
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,
344
+ try {
345
+ await emitter_1.default.emitFilter('fields.delete', [field], {
346
+ collection: collection,
347
+ }, {
348
+ database: this.knex,
343
349
  schema: this.schema,
344
- });
345
- const fieldsService = new FieldsService({
346
- knex: trx,
347
350
  accountability: this.accountability,
348
- schema: this.schema,
349
351
  });
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);
352
+ await this.knex.transaction(async (trx) => {
353
+ var _a, _b;
354
+ if (this.schema.collections[collection] &&
355
+ field in this.schema.collections[collection].fields &&
356
+ this.schema.collections[collection].fields[field].alias === false) {
357
+ await trx.schema.table(collection, (table) => {
358
+ table.dropColumn(field);
359
+ });
360
+ }
361
+ const relations = this.schema.relations.filter((relation) => {
362
+ var _a;
363
+ return ((relation.collection === collection && relation.field === field) ||
364
+ (relation.related_collection === collection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === field));
365
+ });
366
+ const relationsService = new relations_1.RelationsService({
367
+ knex: trx,
368
+ accountability: this.accountability,
369
+ schema: this.schema,
370
+ });
371
+ const fieldsService = new FieldsService({
372
+ knex: trx,
373
+ accountability: this.accountability,
374
+ schema: this.schema,
375
+ });
376
+ for (const relation of relations) {
377
+ const isM2O = relation.collection === collection && relation.field === field;
378
+ // If the current field is a m2o, delete the related o2m if it exists and remove the relationship
379
+ if (isM2O) {
380
+ await relationsService.deleteOne(collection, field);
381
+ if (relation.related_collection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field)) {
382
+ await fieldsService.deleteField(relation.related_collection, relation.meta.one_field);
383
+ }
384
+ }
385
+ // If the current field is a o2m, just delete the one field config from the relation
386
+ if (!isM2O && ((_b = relation.meta) === null || _b === void 0 ? void 0 : _b.one_field)) {
387
+ await trx('directus_relations')
388
+ .update({ one_field: null })
389
+ .where({ many_collection: relation.collection, many_field: relation.field });
357
390
  }
358
391
  }
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 });
392
+ const collectionMeta = await trx
393
+ .select('archive_field', 'sort_field')
394
+ .from('directus_collections')
395
+ .where({ collection })
396
+ .first();
397
+ const collectionMetaUpdates = {};
398
+ if ((collectionMeta === null || collectionMeta === void 0 ? void 0 : collectionMeta.archive_field) === field) {
399
+ collectionMetaUpdates.archive_field = null;
364
400
  }
401
+ if ((collectionMeta === null || collectionMeta === void 0 ? void 0 : collectionMeta.sort_field) === field) {
402
+ collectionMetaUpdates.sort_field = null;
403
+ }
404
+ if (Object.keys(collectionMetaUpdates).length > 0) {
405
+ await trx('directus_collections').update(collectionMetaUpdates).where({ collection });
406
+ }
407
+ // Cleanup directus_fields
408
+ const metaRow = await trx
409
+ .select('collection', 'field')
410
+ .from('directus_fields')
411
+ .where({ collection, field })
412
+ .first();
413
+ if (metaRow) {
414
+ // Handle recursive FK constraints
415
+ await trx('directus_fields')
416
+ .update({ group: null })
417
+ .where({ group: metaRow.field, collection: metaRow.collection });
418
+ }
419
+ await trx('directus_fields').delete().where({ collection, field });
420
+ });
421
+ emitter_1.default.emitAction('fields.delete', {
422
+ payload: [field],
423
+ collection: collection,
424
+ }, {
425
+ database: this.knex,
426
+ schema: this.schema,
427
+ accountability: this.accountability,
428
+ });
429
+ }
430
+ finally {
431
+ if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
432
+ await this.cache.clear();
365
433
  }
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
- });
434
+ await (0, cache_1.clearSystemCache)();
435
+ }
407
436
  }
408
437
  addColumnToTable(table, field, alter = null) {
409
438
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
@@ -441,9 +470,16 @@ class FieldsService {
441
470
  column = table[field.type](field.field);
442
471
  }
443
472
  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()') {
473
+ if (typeof field.schema.default_value === 'string' &&
474
+ (field.schema.default_value.toLowerCase() === 'now()' || field.schema.default_value === 'CURRENT_TIMESTAMP')) {
445
475
  column.defaultTo(this.knex.fn.now());
446
476
  }
477
+ else if (typeof field.schema.default_value === 'string' &&
478
+ field.schema.default_value.includes('CURRENT_TIMESTAMP(') &&
479
+ field.schema.default_value.includes(')')) {
480
+ const precision = field.schema.default_value.match(constants_2.REGEX_BETWEEN_PARENS)[1];
481
+ column.defaultTo(this.knex.fn.now(Number(precision)));
482
+ }
447
483
  else if (typeof field.schema.default_value === 'string' &&
448
484
  ['"null"', 'null'].includes(field.schema.default_value.toLowerCase())) {
449
485
  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;