@ditojs/server 2.0.4 → 2.1.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.
- package/package.json +11 -13
- package/src/app/Application.js +228 -212
- package/src/app/Validator.js +53 -43
- package/src/cli/console.js +6 -4
- package/src/cli/db/createMigration.js +59 -30
- package/src/cli/db/migrate.js +6 -4
- package/src/cli/db/reset.js +8 -5
- package/src/cli/db/rollback.js +6 -4
- package/src/cli/db/seed.js +2 -1
- package/src/cli/index.js +1 -1
- package/src/controllers/AdminController.js +100 -84
- package/src/controllers/CollectionController.js +37 -30
- package/src/controllers/Controller.js +83 -43
- package/src/controllers/ControllerAction.js +27 -15
- package/src/controllers/ModelController.js +4 -1
- package/src/controllers/RelationController.js +19 -21
- package/src/controllers/UsersController.js +3 -4
- package/src/decorators/parameters.js +3 -1
- package/src/decorators/scope.js +1 -1
- package/src/errors/ControllerError.js +2 -1
- package/src/errors/DatabaseError.js +20 -11
- package/src/graph/DitoGraphProcessor.js +48 -40
- package/src/graph/expression.js +6 -8
- package/src/graph/graph.js +20 -11
- package/src/lib/EventEmitter.js +12 -12
- package/src/middleware/handleConnectMiddleware.js +16 -10
- package/src/middleware/handleError.js +6 -5
- package/src/middleware/handleSession.js +78 -0
- package/src/middleware/handleUser.js +2 -2
- package/src/middleware/index.js +2 -0
- package/src/middleware/logRequests.js +3 -3
- package/src/middleware/setupRequestStorage.js +14 -0
- package/src/mixins/AssetMixin.js +62 -58
- package/src/mixins/SessionMixin.js +13 -10
- package/src/mixins/TimeStampedMixin.js +33 -29
- package/src/mixins/UserMixin.js +130 -116
- package/src/models/Model.js +245 -194
- package/src/models/definitions/filters.js +14 -13
- package/src/query/QueryBuilder.js +252 -195
- package/src/query/QueryFilters.js +3 -3
- package/src/query/QueryParameters.js +2 -2
- package/src/query/Registry.js +8 -10
- package/src/schema/keywords/_validate.js +10 -8
- package/src/schema/properties.test.js +247 -206
- package/src/schema/relations.js +42 -20
- package/src/schema/relations.test.js +36 -19
- package/src/services/Service.js +8 -14
- package/src/storage/S3Storage.js +5 -3
- package/src/storage/Storage.js +16 -14
- package/src/utils/function.js +7 -4
- package/src/utils/function.test.js +30 -6
- package/src/utils/object.test.js +5 -1
- package/types/index.d.ts +244 -257
- package/src/app/SessionStore.js +0 -31
package/src/models/Model.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import objection from 'objection'
|
|
2
2
|
import {
|
|
3
|
-
isString,
|
|
4
|
-
|
|
3
|
+
isString,
|
|
4
|
+
isObject,
|
|
5
|
+
isArray,
|
|
6
|
+
isFunction,
|
|
7
|
+
isPromise,
|
|
8
|
+
asArray,
|
|
9
|
+
merge,
|
|
10
|
+
flatten,
|
|
11
|
+
parseDataPath,
|
|
12
|
+
normalizeDataPath,
|
|
13
|
+
getValueAtDataPath,
|
|
5
14
|
mapConcurrently
|
|
6
15
|
} from '@ditojs/utils'
|
|
7
16
|
import { QueryBuilder } from '../query/index.js'
|
|
@@ -15,7 +24,8 @@ import { populateGraph, filterGraph } from '../graph/index.js'
|
|
|
15
24
|
import { formatJson } from '../utils/json.js'
|
|
16
25
|
import {
|
|
17
26
|
ResponseError,
|
|
18
|
-
GraphError,
|
|
27
|
+
GraphError,
|
|
28
|
+
ModelError,
|
|
19
29
|
NotFoundError,
|
|
20
30
|
RelationError
|
|
21
31
|
} from '../errors/index.js'
|
|
@@ -65,7 +75,8 @@ export class Model extends objection.Model {
|
|
|
65
75
|
if (accessor in this.prototype) {
|
|
66
76
|
throw new RelationError(
|
|
67
77
|
`Model '${this.name}' already defines a property with name ` +
|
|
68
|
-
`'${accessor}' that clashes with the relation accessor.`
|
|
78
|
+
`'${accessor}' that clashes with the relation accessor.`
|
|
79
|
+
)
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
// Define an accessor on the class as well as on the prototype that when
|
|
@@ -97,16 +108,13 @@ export class Model extends objection.Model {
|
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
// @overridable
|
|
100
|
-
static setup() {
|
|
101
|
-
}
|
|
111
|
+
static setup() {}
|
|
102
112
|
|
|
103
113
|
// @overridable
|
|
104
|
-
static initialize() {
|
|
105
|
-
}
|
|
114
|
+
static initialize() {}
|
|
106
115
|
|
|
107
116
|
// @overridable
|
|
108
|
-
$initialize() {
|
|
109
|
-
}
|
|
117
|
+
$initialize() {}
|
|
110
118
|
|
|
111
119
|
get $app() {
|
|
112
120
|
return this.constructor.app
|
|
@@ -222,8 +230,8 @@ export class Model extends objection.Model {
|
|
|
222
230
|
if (options.async && !options.skipValidation) {
|
|
223
231
|
// Handle async validation, as supported by Dito:
|
|
224
232
|
const model = new this()
|
|
225
|
-
return model.$validate(json, options).then(
|
|
226
|
-
|
|
233
|
+
return model.$validate(json, options).then(json =>
|
|
234
|
+
model.$setJson(json, {
|
|
227
235
|
...options,
|
|
228
236
|
skipValidation: true
|
|
229
237
|
})
|
|
@@ -237,15 +245,20 @@ export class Model extends objection.Model {
|
|
|
237
245
|
static query(trx) {
|
|
238
246
|
return super.query(trx).onError(err => {
|
|
239
247
|
// TODO: Shouldn't this wrapping happen on the Controller level?
|
|
240
|
-
err =
|
|
241
|
-
|
|
242
|
-
|
|
248
|
+
err =
|
|
249
|
+
err instanceof ResponseError
|
|
250
|
+
? err
|
|
251
|
+
: err instanceof objection.DBError
|
|
252
|
+
? this.app.createDatabaseError(err)
|
|
253
|
+
: new ResponseError(err)
|
|
243
254
|
return Promise.reject(err)
|
|
244
255
|
})
|
|
245
256
|
}
|
|
246
257
|
|
|
247
258
|
static async count(...args) {
|
|
248
|
-
const { count } = await this.query()
|
|
259
|
+
const { count } = (await this.query()
|
|
260
|
+
.count(...args)
|
|
261
|
+
.first()) || {}
|
|
249
262
|
return +count || 0
|
|
250
263
|
}
|
|
251
264
|
|
|
@@ -361,25 +374,31 @@ export class Model extends objection.Model {
|
|
|
361
374
|
}
|
|
362
375
|
|
|
363
376
|
static get relationMappings() {
|
|
364
|
-
return this._getCached(
|
|
365
|
-
|
|
366
|
-
|
|
377
|
+
return this._getCached(
|
|
378
|
+
'relationMappings',
|
|
379
|
+
() => convertRelations(this, this.definition.relations, this.app.models),
|
|
380
|
+
{}
|
|
381
|
+
)
|
|
367
382
|
}
|
|
368
383
|
|
|
369
384
|
static get jsonSchema() {
|
|
370
|
-
return this._getCached(
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
385
|
+
return this._getCached(
|
|
386
|
+
'jsonSchema',
|
|
387
|
+
() => {
|
|
388
|
+
const schema = convertSchema({
|
|
389
|
+
type: 'object',
|
|
390
|
+
properties: this.definition.properties
|
|
391
|
+
})
|
|
392
|
+
addRelationSchemas(this, schema.properties)
|
|
393
|
+
// Merge in root-level schema additions
|
|
394
|
+
merge(schema, this.definition.schema)
|
|
395
|
+
return {
|
|
396
|
+
$id: this.name,
|
|
397
|
+
...schema
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
{}
|
|
401
|
+
)
|
|
383
402
|
}
|
|
384
403
|
|
|
385
404
|
static get virtualAttributes() {
|
|
@@ -389,36 +408,58 @@ export class Model extends objection.Model {
|
|
|
389
408
|
}
|
|
390
409
|
|
|
391
410
|
static get jsonAttributes() {
|
|
392
|
-
return this._getCached(
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
411
|
+
return this._getCached(
|
|
412
|
+
'jsonSchema:jsonAttributes',
|
|
413
|
+
() =>
|
|
414
|
+
this.getAttributes(
|
|
415
|
+
({ type, specificType, computed }) => (
|
|
416
|
+
!computed &&
|
|
417
|
+
!specificType &&
|
|
418
|
+
(type === 'object' || type === 'array')
|
|
419
|
+
)
|
|
420
|
+
),
|
|
421
|
+
[]
|
|
422
|
+
)
|
|
396
423
|
}
|
|
397
424
|
|
|
398
425
|
static get booleanAttributes() {
|
|
399
|
-
return this._getCached(
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
426
|
+
return this._getCached(
|
|
427
|
+
'jsonSchema:booleanAttributes',
|
|
428
|
+
() =>
|
|
429
|
+
this.getAttributes(
|
|
430
|
+
({ type, computed }) => !computed && type === 'boolean'
|
|
431
|
+
),
|
|
432
|
+
[]
|
|
433
|
+
)
|
|
403
434
|
}
|
|
404
435
|
|
|
405
436
|
static get dateAttributes() {
|
|
406
|
-
return this._getCached(
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
437
|
+
return this._getCached(
|
|
438
|
+
'jsonSchema:dateAttributes',
|
|
439
|
+
() =>
|
|
440
|
+
this.getAttributes(
|
|
441
|
+
({ type, computed }) => (
|
|
442
|
+
!computed && ['date', 'datetime', 'timestamp'].includes(type)
|
|
443
|
+
)
|
|
444
|
+
),
|
|
445
|
+
[]
|
|
446
|
+
)
|
|
410
447
|
}
|
|
411
448
|
|
|
412
449
|
static get computedAttributes() {
|
|
413
|
-
return this._getCached(
|
|
414
|
-
|
|
415
|
-
|
|
450
|
+
return this._getCached(
|
|
451
|
+
'jsonSchema:computedAttributes',
|
|
452
|
+
() => this.getAttributes(({ computed }) => computed),
|
|
453
|
+
[]
|
|
454
|
+
)
|
|
416
455
|
}
|
|
417
456
|
|
|
418
457
|
static get hiddenAttributes() {
|
|
419
|
-
return this._getCached(
|
|
420
|
-
|
|
421
|
-
|
|
458
|
+
return this._getCached(
|
|
459
|
+
'jsonSchema:hiddenAttributes',
|
|
460
|
+
() => this.getAttributes(({ hidden }) => hidden),
|
|
461
|
+
[]
|
|
462
|
+
)
|
|
422
463
|
}
|
|
423
464
|
|
|
424
465
|
static getAttributes(filter) {
|
|
@@ -440,10 +481,13 @@ export class Model extends objection.Model {
|
|
|
440
481
|
// 'jsonSchema' changes, all cached child values are invalidated.
|
|
441
482
|
let entry
|
|
442
483
|
for (const part of identifier.split(':')) {
|
|
443
|
-
entry = cache[part] =
|
|
444
|
-
cache
|
|
445
|
-
|
|
446
|
-
|
|
484
|
+
entry = cache[part] = (
|
|
485
|
+
cache[part] ||
|
|
486
|
+
{
|
|
487
|
+
cache: {},
|
|
488
|
+
value: undefined
|
|
489
|
+
}
|
|
490
|
+
)
|
|
447
491
|
cache = entry.cache
|
|
448
492
|
}
|
|
449
493
|
if (entry?.value === undefined) {
|
|
@@ -707,19 +751,19 @@ export class Model extends objection.Model {
|
|
|
707
751
|
}
|
|
708
752
|
// Now check possible scope prefixes and handle them:
|
|
709
753
|
switch (modifier[0]) {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
754
|
+
case '^': // Eager-applied scope:
|
|
755
|
+
// Always apply eager-scopes, even if the model itself doesn't know
|
|
756
|
+
// it. The scope may still be known in eager-loaded relations.
|
|
757
|
+
// Note: `applyScope()` will handle the '^' sign.
|
|
758
|
+
return query.applyScope(modifier)
|
|
759
|
+
case '-': // Ignore scope:
|
|
760
|
+
return query.ignoreScope(modifier.slice(1))
|
|
761
|
+
case '#': // Select column:
|
|
762
|
+
return query.select(modifier.slice(1))
|
|
763
|
+
case '*': // Select all columns:
|
|
764
|
+
if (modifier.length === 1) {
|
|
765
|
+
return query.select('*')
|
|
766
|
+
}
|
|
723
767
|
}
|
|
724
768
|
}
|
|
725
769
|
super.modifierNotFound(query, modifier)
|
|
@@ -747,22 +791,24 @@ export class Model extends objection.Model {
|
|
|
747
791
|
// @override
|
|
748
792
|
static createValidationError({ type, message, errors, options, json }) {
|
|
749
793
|
switch (type) {
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
794
|
+
case 'ModelValidation':
|
|
795
|
+
return this.app.createValidationError({
|
|
796
|
+
type,
|
|
797
|
+
message: (
|
|
798
|
+
message ||
|
|
799
|
+
`The provided data for the ${this.name} model is not valid`
|
|
800
|
+
),
|
|
801
|
+
errors,
|
|
802
|
+
options,
|
|
803
|
+
json
|
|
804
|
+
})
|
|
805
|
+
case 'RelationExpression':
|
|
806
|
+
case 'UnallowedRelation':
|
|
807
|
+
return new RelationError({ type, message, errors })
|
|
808
|
+
case 'InvalidGraph':
|
|
809
|
+
return new GraphError({ type, message, errors })
|
|
810
|
+
default:
|
|
811
|
+
return new ResponseError({ type, message, errors })
|
|
766
812
|
}
|
|
767
813
|
}
|
|
768
814
|
|
|
@@ -926,122 +972,127 @@ export class Model extends objection.Model {
|
|
|
926
972
|
static _configureAssetsHooks(assets) {
|
|
927
973
|
const assetDataPaths = Object.keys(assets)
|
|
928
974
|
|
|
929
|
-
this.on(
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
975
|
+
this.on(
|
|
976
|
+
[
|
|
977
|
+
'before:insert',
|
|
978
|
+
'before:update',
|
|
979
|
+
'before:delete'
|
|
980
|
+
],
|
|
981
|
+
async ({ type, transaction, inputItems, asFindQuery }) => {
|
|
982
|
+
const isInsert = type === 'before:insert'
|
|
983
|
+
const isDelete = type === 'before:delete'
|
|
984
|
+
// Figure out which asset data paths where actually present in the
|
|
985
|
+
// submitted data, and only compare these. But when deleting, use all.
|
|
986
|
+
const dataPaths = isDelete
|
|
987
|
+
? assetDataPaths
|
|
988
|
+
: assetDataPaths.filter(
|
|
989
|
+
dataPath => (
|
|
990
|
+
// Skip check for wildcard data-paths.
|
|
991
|
+
/^\*\*?/.test(dataPath) ||
|
|
992
|
+
// Only keep normal data paths that match present properties.
|
|
993
|
+
(parseDataPath(dataPath)[0] in inputItems[0])
|
|
994
|
+
)
|
|
995
|
+
)
|
|
996
|
+
// `dataPaths` is empty in the case of an update/insert that does not
|
|
997
|
+
// affect the assets.
|
|
998
|
+
if (dataPaths.length === 0) return
|
|
999
|
+
|
|
1000
|
+
const afterItems = isDelete
|
|
1001
|
+
? []
|
|
1002
|
+
: inputItems
|
|
1003
|
+
// Load the model's asset files in their current state before the query
|
|
1004
|
+
// is executed. For deletes, load the data for all asset data-paths.
|
|
1005
|
+
// Otherwise, only load the columns present in the input data.
|
|
1006
|
+
const beforeItems = isInsert
|
|
1007
|
+
? []
|
|
1008
|
+
: isDelete
|
|
1009
|
+
? // When deleting, it's ok to load all columns when data-paths
|
|
1010
|
+
// contain wildcards unfiltered, since `afterItems` will be empty
|
|
1011
|
+
// anyway.
|
|
1012
|
+
await loadAssetDataPaths(asFindQuery(), dataPaths)
|
|
1013
|
+
: await asFindQuery().select(
|
|
1014
|
+
// Select only the properties that are present in the data,
|
|
1015
|
+
// and which aren't the result of computed properties.
|
|
1016
|
+
Object.keys(inputItems[0]).filter(key => {
|
|
1017
|
+
const property = this.definition.properties[key]
|
|
1018
|
+
return property && !property.computed
|
|
1019
|
+
})
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
const afterFilesPerDataPath = getFilesPerAssetDataPath(
|
|
1023
|
+
afterItems,
|
|
1024
|
+
dataPaths
|
|
1025
|
+
)
|
|
1026
|
+
const beforeFilesPerDataPath = getFilesPerAssetDataPath(
|
|
1027
|
+
beforeItems,
|
|
1028
|
+
dataPaths
|
|
947
1029
|
)
|
|
948
|
-
// `dataPaths` is empty in the case of an update/insert that does not
|
|
949
|
-
// affect the assets.
|
|
950
|
-
if (dataPaths.length === 0) return
|
|
951
|
-
|
|
952
|
-
const afterItems = isDelete
|
|
953
|
-
? []
|
|
954
|
-
: inputItems
|
|
955
|
-
// Load the model's asset files in their current state before the query is
|
|
956
|
-
// executed. For deletes, load the data for all asset data-paths.
|
|
957
|
-
// Otherwise, only load the columns present in the input data.
|
|
958
|
-
const beforeItems = isInsert
|
|
959
|
-
? []
|
|
960
|
-
: isDelete
|
|
961
|
-
// When deleting it's ok to load all columns when data-paths contain
|
|
962
|
-
// wildcards unfiltered, since `afterItems` will be empty anyway.
|
|
963
|
-
? await loadAssetDataPaths(asFindQuery(), dataPaths)
|
|
964
|
-
: await asFindQuery().select(
|
|
965
|
-
// Select only the properties that are present in the data,
|
|
966
|
-
// and which aren't the result of computed properties.
|
|
967
|
-
Object.keys(inputItems[0]).filter(key => {
|
|
968
|
-
const property = this.definition.properties[key]
|
|
969
|
-
return property && !property.computed
|
|
970
|
-
})
|
|
971
|
-
)
|
|
972
|
-
|
|
973
|
-
const afterFilesPerDataPath = getFilesPerAssetDataPath(
|
|
974
|
-
afterItems,
|
|
975
|
-
dataPaths
|
|
976
|
-
)
|
|
977
|
-
const beforeFilesPerDataPath = getFilesPerAssetDataPath(
|
|
978
|
-
beforeItems,
|
|
979
|
-
dataPaths
|
|
980
|
-
)
|
|
981
1030
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1031
|
+
const importedFiles = []
|
|
1032
|
+
const modifiedFiles = []
|
|
1033
|
+
|
|
1034
|
+
if (transaction.rollback) {
|
|
1035
|
+
// Prevent wrong memory leak error messages when installing more than
|
|
1036
|
+
// 10 'rollback' handlers, which can happen with more complex queries.
|
|
1037
|
+
transaction.setMaxListeners(0)
|
|
1038
|
+
transaction.on('rollback', async error => {
|
|
1039
|
+
if (importedFiles.length > 0) {
|
|
1040
|
+
console.info(
|
|
1041
|
+
`Received '${error}', removing imported files again: ${
|
|
1042
|
+
importedFiles.map(file => `'${file.name}'`)
|
|
1043
|
+
}`
|
|
1044
|
+
)
|
|
1045
|
+
await mapConcurrently(
|
|
1046
|
+
importedFiles,
|
|
1047
|
+
file => file.storage.removeFile(file)
|
|
1048
|
+
)
|
|
1049
|
+
}
|
|
1050
|
+
if (modifiedFiles.length > 0) {
|
|
1051
|
+
// TODO: `modifiedFiles` should be restored as well, but that's
|
|
1052
|
+
// far from trivial since no backup is kept in
|
|
1053
|
+
// `handleModifiedAssets()`
|
|
1054
|
+
console.warn(
|
|
1055
|
+
`Unable to restore these already modified files: ${
|
|
1056
|
+
modifiedFiles.map(file => `'${file.name}'`)
|
|
1057
|
+
}`
|
|
1058
|
+
)
|
|
1059
|
+
}
|
|
1060
|
+
})
|
|
1061
|
+
}
|
|
1012
1062
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
)
|
|
1028
|
-
importedFiles.push(
|
|
1029
|
-
...await this.app.handleAddedAndRemovedAssets(
|
|
1030
|
-
storage,
|
|
1031
|
-
addedFiles,
|
|
1032
|
-
removedFiles,
|
|
1033
|
-
transaction
|
|
1063
|
+
for (const dataPath of dataPaths) {
|
|
1064
|
+
const storage = this.app.getStorage(assets[dataPath].storage)
|
|
1065
|
+
const beforeFiles = beforeFilesPerDataPath[dataPath] || []
|
|
1066
|
+
const afterFiles = afterFilesPerDataPath[dataPath] || []
|
|
1067
|
+
const beforeByKey = mapFilesByKey(beforeFiles)
|
|
1068
|
+
const afterByKey = mapFilesByKey(afterFiles)
|
|
1069
|
+
const removedFiles = beforeFiles.filter(file => !afterByKey[file.key])
|
|
1070
|
+
const addedFiles = afterFiles.filter(file => !beforeByKey[file.key])
|
|
1071
|
+
// Also handle modified files, which are files where the data property
|
|
1072
|
+
// is changed before update / patch, meaning the file is changed.
|
|
1073
|
+
// NOTE: This will change the content for all the references to it,
|
|
1074
|
+
// and so should only really be used when there's only one reference.
|
|
1075
|
+
const modifiedFiles = afterFiles.filter(
|
|
1076
|
+
file => file.data && beforeByKey[file.key]
|
|
1034
1077
|
)
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1078
|
+
importedFiles.push(
|
|
1079
|
+
...(await this.app.handleAddedAndRemovedAssets(
|
|
1080
|
+
storage,
|
|
1081
|
+
addedFiles,
|
|
1082
|
+
removedFiles,
|
|
1083
|
+
transaction
|
|
1084
|
+
))
|
|
1041
1085
|
)
|
|
1042
|
-
|
|
1086
|
+
modifiedFiles.push(
|
|
1087
|
+
...(await this.app.handleModifiedAssets(
|
|
1088
|
+
storage,
|
|
1089
|
+
modifiedFiles,
|
|
1090
|
+
transaction
|
|
1091
|
+
))
|
|
1092
|
+
)
|
|
1093
|
+
}
|
|
1043
1094
|
}
|
|
1044
|
-
|
|
1095
|
+
)
|
|
1045
1096
|
}
|
|
1046
1097
|
}
|
|
1047
1098
|
|
|
@@ -1055,7 +1106,7 @@ const metaMap = new WeakMap()
|
|
|
1055
1106
|
function getMeta(modelClass, key, value) {
|
|
1056
1107
|
let meta = metaMap.get(modelClass)
|
|
1057
1108
|
if (!meta) {
|
|
1058
|
-
metaMap.set(modelClass, meta = {})
|
|
1109
|
+
metaMap.set(modelClass, (meta = {}))
|
|
1059
1110
|
}
|
|
1060
1111
|
if (!(key in meta)) {
|
|
1061
1112
|
meta[key] = isFunction(value) ? value() : value
|
|
@@ -47,15 +47,15 @@ function convertFilterObject(name, object) {
|
|
|
47
47
|
: queryFilter
|
|
48
48
|
const func = properties
|
|
49
49
|
? (query, ...args) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
// When the filter provides multiple properties, match them
|
|
51
|
+
// all, but combine the expressions with OR.
|
|
52
|
+
for (const property of properties) {
|
|
53
|
+
query.orWhere(query => queryHandler(query, property, ...args))
|
|
54
|
+
}
|
|
54
55
|
}
|
|
55
|
-
}
|
|
56
56
|
: (query, ...args) => {
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
queryHandler(query, name, ...args)
|
|
58
|
+
}
|
|
59
59
|
return addHandlerSettings(func, queryFilter)
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -75,12 +75,12 @@ function wrapWithValidation(filter, name, app) {
|
|
|
75
75
|
})
|
|
76
76
|
if (validator?.validate) {
|
|
77
77
|
return (query, ...args) => {
|
|
78
|
-
|
|
78
|
+
// Convert args to object for validation:
|
|
79
79
|
const object = {}
|
|
80
80
|
let index = 0
|
|
81
81
|
for (const { name } of validator.list) {
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
// Use dataName if no name is given, see:
|
|
83
|
+
// Application.compileParametersValidator()
|
|
84
84
|
object[name || dataName] = args[index++]
|
|
85
85
|
}
|
|
86
86
|
try {
|
|
@@ -88,11 +88,12 @@ function wrapWithValidation(filter, name, app) {
|
|
|
88
88
|
} catch (error) {
|
|
89
89
|
throw app.createValidationError({
|
|
90
90
|
type: 'FilterValidation',
|
|
91
|
-
message:
|
|
92
|
-
|
|
91
|
+
message: `The provided data for query filter '${
|
|
92
|
+
name
|
|
93
|
+
}' is not valid`,
|
|
93
94
|
errors: app.validator.prefixInstancePaths(
|
|
94
95
|
error.errors,
|
|
95
|
-
|
|
96
|
+
`.${name}`
|
|
96
97
|
)
|
|
97
98
|
})
|
|
98
99
|
}
|