@autofleet/sadot 0.0.1-beta.9 → 0.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/dist/api/v1/definition/index.js +3 -3
- package/dist/api/v1/definition/index.js.map +1 -1
- package/dist/api/v1/definition/validations.js +5 -7
- package/dist/api/v1/definition/validations.js.map +1 -1
- package/dist/api/v1/index.js +1 -1
- package/dist/errors/index.js +46 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/hooks/create.js +5 -3
- package/dist/hooks/create.js.map +1 -1
- package/dist/hooks/enrich.js +110 -0
- package/dist/hooks/enrich.js.map +1 -0
- package/dist/hooks/find.js +3 -113
- package/dist/hooks/find.js.map +1 -1
- package/dist/hooks/index.js +2 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/update.js +0 -10
- package/dist/hooks/update.js.map +1 -1
- package/dist/index.js +39 -16
- package/dist/index.js.map +1 -1
- package/dist/models/CustomFieldDefinition.js +21 -16
- package/dist/models/CustomFieldDefinition.js.map +1 -1
- package/dist/models/CustomFieldValue.js +5 -7
- package/dist/models/CustomFieldValue.js.map +1 -1
- package/dist/models/index.js +18 -5
- package/dist/models/index.js.map +1 -1
- package/dist/repository/definition.js +6 -10
- package/dist/repository/definition.js.map +1 -1
- package/dist/repository/value.js +2 -1
- package/dist/repository/value.js.map +1 -1
- package/dist/tests/mocks/{index.js → definition.mock.js} +3 -5
- package/dist/tests/mocks/definition.mock.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/validations/custom.js +25 -41
- package/dist/utils/validations/custom.js.map +1 -1
- package/dist/utils/validations/type.js +27 -9
- package/dist/utils/validations/type.js.map +1 -1
- package/dist/utils/validations/validators.js +34 -0
- package/dist/utils/validations/validators.js.map +1 -0
- package/package.json +1 -1
- package/src/api/v1/definition/index.ts +5 -5
- package/src/api/v1/definition/validations.ts +5 -23
- package/src/api/v1/index.ts +1 -1
- package/src/errors/index.ts +42 -0
- package/src/hooks/create.ts +5 -3
- package/src/hooks/enrich.ts +102 -0
- package/src/hooks/find.ts +1 -102
- package/src/hooks/index.ts +2 -1
- package/src/hooks/update.ts +0 -15
- package/src/index.ts +37 -15
- package/src/models/CustomFieldDefinition.ts +23 -16
- package/src/models/CustomFieldValue.ts +7 -7
- package/src/models/index.ts +21 -7
- package/src/repository/definition.ts +6 -10
- package/src/repository/value.ts +2 -1
- package/src/tests/mocks/{index.ts → definition.mock.ts} +2 -4
- package/src/types/index.ts +1 -3
- package/src/utils/validations/custom.ts +26 -44
- package/src/utils/validations/type.ts +21 -5
- package/src/utils/validations/validators.ts +34 -0
- package/dist/tests/mocks/index.js.map +0 -1
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
import Joi from '@hapi/joi';
|
|
2
|
-
import { CustomFieldDefinitionType } from '../../../
|
|
2
|
+
import { CustomFieldDefinitionType } from '../../../utils/validations/type';
|
|
3
3
|
|
|
4
4
|
const CustomFieldDefinitionCreationSchema = Joi.object({
|
|
5
5
|
name: Joi.string().required(),
|
|
6
6
|
displayName: Joi.string().required(),
|
|
7
|
-
validation: Joi.
|
|
8
|
-
fieldType: Joi.string().valid(
|
|
9
|
-
CustomFieldDefinitionType.BOOLEAN,
|
|
10
|
-
CustomFieldDefinitionType.NUMBER,
|
|
11
|
-
CustomFieldDefinitionType.DATE,
|
|
12
|
-
CustomFieldDefinitionType.DATETIME,
|
|
13
|
-
CustomFieldDefinitionType.TEXT,
|
|
14
|
-
CustomFieldDefinitionType.IMAGE,
|
|
15
|
-
CustomFieldDefinitionType.ENUM,
|
|
16
|
-
).required(),
|
|
7
|
+
validation: Joi.any().required(),
|
|
8
|
+
fieldType: Joi.string().valid(...Object.values(CustomFieldDefinitionType)).required(),
|
|
17
9
|
entityId: Joi.string().guid().required(),
|
|
18
10
|
entityType: Joi.string().required(),
|
|
19
|
-
modelType: Joi.string().required(),
|
|
20
11
|
description: Joi.string(),
|
|
21
12
|
required: Joi.boolean(),
|
|
22
13
|
disabled: Joi.boolean(),
|
|
@@ -24,19 +15,10 @@ const CustomFieldDefinitionCreationSchema = Joi.object({
|
|
|
24
15
|
|
|
25
16
|
const CustomFieldDefinitionUpdateSchema = Joi.object({
|
|
26
17
|
displayName: Joi.string(),
|
|
27
|
-
validation: Joi.
|
|
28
|
-
fieldType: Joi.string().valid(
|
|
29
|
-
CustomFieldDefinitionType.BOOLEAN,
|
|
30
|
-
CustomFieldDefinitionType.NUMBER,
|
|
31
|
-
CustomFieldDefinitionType.DATE,
|
|
32
|
-
CustomFieldDefinitionType.DATETIME,
|
|
33
|
-
CustomFieldDefinitionType.TEXT,
|
|
34
|
-
CustomFieldDefinitionType.IMAGE,
|
|
35
|
-
CustomFieldDefinitionType.ENUM,
|
|
36
|
-
),
|
|
18
|
+
validation: Joi.any(),
|
|
19
|
+
fieldType: Joi.string().valid(...Object.values(CustomFieldDefinitionType)),
|
|
37
20
|
entityId: Joi.string().guid(),
|
|
38
21
|
entityType: Joi.string(),
|
|
39
|
-
modelType: Joi.string(),
|
|
40
22
|
description: Joi.string(),
|
|
41
23
|
required: Joi.boolean(),
|
|
42
24
|
disabled: Joi.boolean(),
|
package/src/api/v1/index.ts
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* eslint-disable max-classes-per-file */
|
|
2
|
+
import { BadRequest } from '@autofleet/errors';
|
|
3
|
+
|
|
4
|
+
export class MissingRequiredCustomFieldError extends BadRequest {
|
|
5
|
+
constructor(missingFields: string[]) {
|
|
6
|
+
const err = new Error(`The following custom fields are required: ${missingFields.join(',')}`);
|
|
7
|
+
super([err], null, missingFields);
|
|
8
|
+
this.message = 'MISSING_REQUIRED_CUSTOM_FIELDS';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class UnsupportedCustomFieldTypeError extends BadRequest {
|
|
13
|
+
constructor(fieldType: string) {
|
|
14
|
+
const err = new Error(`Type "${fieldType}" is not supported`);
|
|
15
|
+
super([err], null, null);
|
|
16
|
+
this.message = 'UNSUPPORTED_CUSTOM_FIELD_TYPE';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class UnsupportedCustomValidationError extends BadRequest {
|
|
21
|
+
constructor(fieldType: string) {
|
|
22
|
+
const err = new Error(`Validation for "${fieldType}" is not supported`);
|
|
23
|
+
super([err], null, null);
|
|
24
|
+
this.message = 'UNSUPPORTED_CUSTOM_VALIDATION_TYPE';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class InvalidValueError extends BadRequest {
|
|
29
|
+
constructor(value: any, fieldType: string) {
|
|
30
|
+
const err = new Error(`Invalid "${fieldType}" value ${JSON.stringify(value)}`);
|
|
31
|
+
super([err], null, null);
|
|
32
|
+
this.message = 'INVALID_VALUE';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class MissingDefinitionError extends BadRequest {
|
|
37
|
+
constructor(fieldNames: string[]) {
|
|
38
|
+
const err = new Error(`Missing custom field definition for field ${fieldNames.join(',')}`);
|
|
39
|
+
super([err], null, null);
|
|
40
|
+
this.message = 'MISSING_DEFINITION';
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/hooks/create.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as ValueRepo from '../repository/value';
|
|
2
2
|
import * as DefinitionRepo from '../repository/definition';
|
|
3
|
+
import { MissingRequiredCustomFieldError } from '../errors';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* A hook to create the custom fields when updating a model (more then one instance).
|
|
@@ -28,8 +29,9 @@ export const beforeCreate = (scopeAttributes: string[]) => async (
|
|
|
28
29
|
const { customFields } = instance;
|
|
29
30
|
if (customFieldsIdx > -1 && customFields) {
|
|
30
31
|
const fieldsNames = Object.keys(customFields);
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
33
|
+
if (missingFields?.length > 0) {
|
|
34
|
+
throw new MissingRequiredCustomFieldError(missingFields);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
await ValueRepo.updateValues(
|
|
@@ -42,6 +44,6 @@ export const beforeCreate = (scopeAttributes: string[]) => async (
|
|
|
42
44
|
// eslint-disable-next-line no-param-reassign
|
|
43
45
|
fields.splice(customFieldsIdx, 1);
|
|
44
46
|
} else if (requiredFieldsNames?.length > 0) {
|
|
45
|
-
throw new
|
|
47
|
+
throw new MissingRequiredCustomFieldError(requiredFieldsNames);
|
|
46
48
|
}
|
|
47
49
|
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
|
+
import * as ValueRepo from '../repository/value';
|
|
3
|
+
import * as DefinitionRepo from '../repository/definition';
|
|
4
|
+
import CustomFieldValue from '../models/CustomFieldValue';
|
|
5
|
+
import { SerializedCustomFields } from '../types/definition';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Serialize custom fields value into the format of {[name] -> [fieldData]}
|
|
9
|
+
*/
|
|
10
|
+
const serializeCustomFields = (customFieldValues: CustomFieldValue[]): SerializedCustomFields => {
|
|
11
|
+
const customFields = customFieldValues.reduce((acc, cfv) => ({
|
|
12
|
+
...acc,
|
|
13
|
+
[cfv.customFieldDefinition.name]: cfv.value,
|
|
14
|
+
}), {});
|
|
15
|
+
return customFields;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A hook to attach the custom fields when fetching a model instances.
|
|
19
|
+
*/
|
|
20
|
+
const enrichResults = (scopeAttributes: string[]) => async (
|
|
21
|
+
instancesOrInstance: any | any[],
|
|
22
|
+
options,
|
|
23
|
+
): Promise<void> => {
|
|
24
|
+
if (
|
|
25
|
+
options.originalAttributes?.length > 0
|
|
26
|
+
&& !options.originalAttributes?.includes?.('customFields')
|
|
27
|
+
) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const primaryKey = 'id';
|
|
32
|
+
let instances = Array.isArray(instancesOrInstance)
|
|
33
|
+
? instancesOrInstance
|
|
34
|
+
: [instancesOrInstance];
|
|
35
|
+
|
|
36
|
+
instances = instances.filter((instance) => !!instance);
|
|
37
|
+
|
|
38
|
+
const identifiers = instances.map((instance) =>
|
|
39
|
+
scopeAttributes
|
|
40
|
+
.map((attr) => instance[attr])).flat();
|
|
41
|
+
|
|
42
|
+
const uniqueIdentifiers = [...new Set(identifiers)].filter((identifier) => !!identifier);
|
|
43
|
+
const identifierCustomFieldDefinitionsMapping = {};
|
|
44
|
+
await Promise.all(uniqueIdentifiers.map(async (identifier) => {
|
|
45
|
+
const customFieldDefinitions = await DefinitionRepo.findByEntityId(
|
|
46
|
+
identifier,
|
|
47
|
+
{ transaction: options.transaction },
|
|
48
|
+
);
|
|
49
|
+
identifierCustomFieldDefinitionsMapping[identifier] = customFieldDefinitions;
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
// Get the values per instates ids:
|
|
53
|
+
const instancesIds = instances.map((i) => i[primaryKey]);
|
|
54
|
+
|
|
55
|
+
const customFieldValues = await ValueRepo.findValuesByModelIds(
|
|
56
|
+
instancesIds,
|
|
57
|
+
{ transaction: options.transaction },
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Group fields by modelId
|
|
61
|
+
const valuesGroupByInstance: {
|
|
62
|
+
[modelId: string]: CustomFieldValue[];
|
|
63
|
+
} = customFieldValues.reduce((acc, v) => {
|
|
64
|
+
const { modelId } = v;
|
|
65
|
+
if (!acc[modelId]) {
|
|
66
|
+
acc[modelId] = [];
|
|
67
|
+
}
|
|
68
|
+
acc[modelId].push(v);
|
|
69
|
+
return acc;
|
|
70
|
+
}, {});
|
|
71
|
+
|
|
72
|
+
// Attach custom fields to the instances
|
|
73
|
+
instances.forEach((instance) => {
|
|
74
|
+
const customFields = {};
|
|
75
|
+
const { id } = instance;
|
|
76
|
+
const instanceValues = valuesGroupByInstance[id];
|
|
77
|
+
if (instanceValues) {
|
|
78
|
+
const serializedCustomFields = serializeCustomFields(instanceValues);
|
|
79
|
+
Object.assign(customFields, serializedCustomFields);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
scopeAttributes.forEach((attribute) => {
|
|
83
|
+
const identifier = instance[attribute];
|
|
84
|
+
const customFieldDefinitions = identifierCustomFieldDefinitionsMapping[identifier];
|
|
85
|
+
if (customFieldDefinitions?.length > 0) {
|
|
86
|
+
customFieldDefinitions.forEach((customFieldDefinition) => {
|
|
87
|
+
if (customFields[customFieldDefinition.name] === undefined) {
|
|
88
|
+
customFields[customFieldDefinition.name] = null;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
instance.customFields = customFields;
|
|
94
|
+
options.attributesToRemove?.forEach?.((attribute) => {
|
|
95
|
+
delete instance.dataValues?.[attribute];
|
|
96
|
+
// if raw:
|
|
97
|
+
delete instance?.[attribute];
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export default enrichResults;
|
package/src/hooks/find.ts
CHANGED
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
/* eslint-disable no-param-reassign */
|
|
2
|
-
import * as ValueRepo from '../repository/value';
|
|
3
|
-
import * as DefinitionRepo from '../repository/definition';
|
|
4
|
-
import CustomFieldValue from '../models/CustomFieldValue';
|
|
5
|
-
import { SerializedCustomFields } from '../types/definition';
|
|
6
|
-
import logger from '../utils/logger';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Serialize custom fields value into the format of {[name] -> [fieldData]}
|
|
10
|
-
*/
|
|
11
|
-
const serializeCustomFields = (customFieldValues: CustomFieldValue[]): SerializedCustomFields => {
|
|
12
|
-
const customFields = customFieldValues.reduce((acc, cfv) => ({
|
|
13
|
-
...acc,
|
|
14
|
-
[cfv.customFieldDefinition.name]: cfv.value,
|
|
15
|
-
}), {});
|
|
16
|
-
return customFields;
|
|
17
|
-
};
|
|
18
2
|
|
|
19
3
|
const doScopeAttributesMissing = (
|
|
20
4
|
scopeAttributes: string[],
|
|
@@ -28,6 +12,7 @@ const doScopeAttributesMissing = (
|
|
|
28
12
|
return attributes;
|
|
29
13
|
};
|
|
30
14
|
|
|
15
|
+
// eslint-disable-next-line import/prefer-default-export
|
|
31
16
|
export const beforeFind = (scopeAttributes: string[]) => (options): void => {
|
|
32
17
|
const { attributes: queryAttributes } = options;
|
|
33
18
|
if (queryAttributes?.includes('customFields')) {
|
|
@@ -38,89 +23,3 @@ export const beforeFind = (scopeAttributes: string[]) => (options): void => {
|
|
|
38
23
|
}
|
|
39
24
|
}
|
|
40
25
|
};
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* A hook to attach the custom fields when fetching a model instances.
|
|
44
|
-
*/
|
|
45
|
-
export const enrichResults = (scopeAttributes: string[]) => async (
|
|
46
|
-
instancesOrInstance: any | any[],
|
|
47
|
-
options,
|
|
48
|
-
): Promise<void> => {
|
|
49
|
-
if (
|
|
50
|
-
options.originalAttributes?.length > 0
|
|
51
|
-
&& !options.originalAttributes?.includes?.('customFields')
|
|
52
|
-
) {
|
|
53
|
-
logger.info('No custom fields needed, returning', { options });
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const primaryKey = 'id';
|
|
58
|
-
let instances = Array.isArray(instancesOrInstance)
|
|
59
|
-
? instancesOrInstance
|
|
60
|
-
: [instancesOrInstance];
|
|
61
|
-
|
|
62
|
-
instances = instances.filter((instance) => !!instance);
|
|
63
|
-
|
|
64
|
-
const identifiers = instances.map((instance) =>
|
|
65
|
-
scopeAttributes
|
|
66
|
-
.map((attr) => instance[attr])).flat();
|
|
67
|
-
|
|
68
|
-
const uniqueIdentifiers = [...new Set(identifiers)].filter((identifier) => !!identifier);
|
|
69
|
-
const identifierCustomFieldDefinitionsMapping = {};
|
|
70
|
-
await Promise.all(uniqueIdentifiers.map(async (identifier) => {
|
|
71
|
-
const customFieldDefinitions = await DefinitionRepo.findByEntityId(
|
|
72
|
-
identifier,
|
|
73
|
-
options.transaction,
|
|
74
|
-
);
|
|
75
|
-
identifierCustomFieldDefinitionsMapping[identifier] = customFieldDefinitions;
|
|
76
|
-
}));
|
|
77
|
-
|
|
78
|
-
// Get the values per instates ids:
|
|
79
|
-
const instancesIds = instances.map((i) => i[primaryKey]);
|
|
80
|
-
|
|
81
|
-
const customFieldValues = await ValueRepo.findValuesByModelIds(
|
|
82
|
-
instancesIds,
|
|
83
|
-
{ transaction: options.transaction },
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
// Group fields by modelId
|
|
87
|
-
const valuesGroupByInstance: {
|
|
88
|
-
[modelId: string]: CustomFieldValue[];
|
|
89
|
-
} = customFieldValues.reduce((acc, v) => {
|
|
90
|
-
const { modelId } = v;
|
|
91
|
-
if (!acc[modelId]) {
|
|
92
|
-
acc[modelId] = [];
|
|
93
|
-
}
|
|
94
|
-
acc[modelId].push(v);
|
|
95
|
-
return acc;
|
|
96
|
-
}, {});
|
|
97
|
-
|
|
98
|
-
// Attach custom fields to the instances
|
|
99
|
-
instances.forEach((instance) => {
|
|
100
|
-
const customFields = {};
|
|
101
|
-
const { id } = instance;
|
|
102
|
-
const instanceValues = valuesGroupByInstance[id];
|
|
103
|
-
if (instanceValues) {
|
|
104
|
-
const serializedCustomFields = serializeCustomFields(instanceValues);
|
|
105
|
-
Object.assign(customFields, serializedCustomFields);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
scopeAttributes.forEach((attribute) => {
|
|
109
|
-
const identifier = instance[attribute];
|
|
110
|
-
const customFieldDefinitions = identifierCustomFieldDefinitionsMapping[identifier];
|
|
111
|
-
if (customFieldDefinitions?.length > 0) {
|
|
112
|
-
customFieldDefinitions.forEach((customFieldDefinition) => {
|
|
113
|
-
if (customFields[customFieldDefinition.name] === undefined) {
|
|
114
|
-
customFields[customFieldDefinition.name] = null;
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
instance.customFields = customFields;
|
|
120
|
-
options.attributesToRemove?.forEach?.((attribute) => {
|
|
121
|
-
delete instance.dataValues?.[attribute];
|
|
122
|
-
// if raw:
|
|
123
|
-
delete instance?.[attribute];
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
};
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import enrichResults from './enrich';
|
|
2
|
+
import { beforeFind } from './find';
|
|
2
3
|
import { beforeBulkUpdate, beforeUpdate } from './update';
|
|
3
4
|
import { beforeBulkCreate, beforeCreate } from './create';
|
|
4
5
|
import workaround from './workaround';
|
package/src/hooks/update.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as ValueRepo from '../repository/value';
|
|
2
|
-
import * as DefinitionRepo from '../repository/definition';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* A hook to update the custom fields when updating a model (more then one instance).
|
|
@@ -22,21 +21,9 @@ export const beforeUpdate = (scopeAttributes: string[]) => async (
|
|
|
22
21
|
const modelType = instance.constructor.name;
|
|
23
22
|
const identifiers = scopeAttributes.map((attribute) => instance[attribute]);
|
|
24
23
|
|
|
25
|
-
// get all model's required definitions
|
|
26
|
-
const requiredNullFieldsNames = await DefinitionRepo.getRequiredFields(
|
|
27
|
-
modelType,
|
|
28
|
-
instance.id,
|
|
29
|
-
identifiers,
|
|
30
|
-
true,
|
|
31
|
-
);
|
|
32
|
-
|
|
33
24
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
34
25
|
if (customFieldsIdx > -1) {
|
|
35
26
|
const { customFields } = instance;
|
|
36
|
-
const fieldsNames = Object.keys(customFields);
|
|
37
|
-
if (requiredNullFieldsNames.some((name) => !fieldsNames.includes(name))) {
|
|
38
|
-
throw new Error('some fields are required');
|
|
39
|
-
}
|
|
40
27
|
await ValueRepo.updateValues(
|
|
41
28
|
modelType,
|
|
42
29
|
instance.id,
|
|
@@ -46,7 +33,5 @@ export const beforeUpdate = (scopeAttributes: string[]) => async (
|
|
|
46
33
|
);
|
|
47
34
|
// eslint-disable-next-line no-param-reassign
|
|
48
35
|
fields.splice(customFieldsIdx, 1);
|
|
49
|
-
} else if (requiredNullFieldsNames?.length > 0) {
|
|
50
|
-
throw new Error('some fields are required');
|
|
51
36
|
}
|
|
52
37
|
};
|
package/src/index.ts
CHANGED
|
@@ -27,13 +27,13 @@ const addHooks = (models: ModelOptions[], getModel: ModelFetcher) => {
|
|
|
27
27
|
model.refreshAttributes();
|
|
28
28
|
// TODO: Uncomment after tests are passed
|
|
29
29
|
// model.addHook('afterFind', workaround);
|
|
30
|
-
model.addHook('beforeFind', beforeFind(scopeAttributes));
|
|
31
|
-
model.addHook('beforeBulkCreate', beforeBulkCreate);
|
|
32
|
-
model.addHook('beforeBulkUpdate', beforeBulkUpdate);
|
|
33
|
-
model.addHook('beforeCreate', beforeCreate(scopeAttributes));
|
|
34
|
-
model.addHook('beforeUpdate', beforeUpdate(scopeAttributes));
|
|
35
|
-
model.addHook('afterFind', enrichResults(scopeAttributes));
|
|
36
|
-
model.addHook('afterUpdate', enrichResults(scopeAttributes));
|
|
30
|
+
model.addHook('beforeFind', 'sadot-beforeFind', beforeFind(scopeAttributes));
|
|
31
|
+
model.addHook('beforeBulkCreate', 'sadot-beforeBulkCreate', beforeBulkCreate);
|
|
32
|
+
model.addHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate', beforeBulkUpdate);
|
|
33
|
+
model.addHook('beforeCreate', 'sadot-beforeCreate', beforeCreate(scopeAttributes));
|
|
34
|
+
model.addHook('beforeUpdate', 'sadot-beforeUpdate', beforeUpdate(scopeAttributes));
|
|
35
|
+
model.addHook('afterFind', 'sadot-afterFind', enrichResults(scopeAttributes));
|
|
36
|
+
model.addHook('afterUpdate', 'sadot-afterUpdate', enrichResults(scopeAttributes));
|
|
37
37
|
} catch (e) {
|
|
38
38
|
logger.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
39
39
|
}
|
|
@@ -49,18 +49,40 @@ const useCustomFields = async (
|
|
|
49
49
|
getModel: ModelFetcher,
|
|
50
50
|
options: CustomFieldOptions,
|
|
51
51
|
): Promise<void> => {
|
|
52
|
-
const {
|
|
53
|
-
// deepAfterFindWorkAround(sequelize);
|
|
52
|
+
const { models } = options;
|
|
54
53
|
if (app) {
|
|
55
54
|
app.use('/api', api);
|
|
56
55
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
await initTables(moreInnerSequelize);
|
|
60
|
-
} else {
|
|
61
|
-
await initTables(innerSequelize);
|
|
62
|
-
}
|
|
56
|
+
const sequelize = initDB(options.databaseConfig);
|
|
57
|
+
await initTables(sequelize, options.getUser);
|
|
63
58
|
addHooks(models, getModel);
|
|
64
59
|
};
|
|
65
60
|
|
|
66
61
|
export default useCustomFields;
|
|
62
|
+
|
|
63
|
+
const removeHooks = (models: ModelOptions[], getModel: ModelFetcher) => {
|
|
64
|
+
models.forEach(async ({ name }) => {
|
|
65
|
+
try {
|
|
66
|
+
const model = getModel(name);
|
|
67
|
+
if (!model) return;
|
|
68
|
+
if (model.rawAttributes.customFields) {
|
|
69
|
+
delete model.rawAttributes.customFields;
|
|
70
|
+
model.refreshAttributes();
|
|
71
|
+
}
|
|
72
|
+
// model.removeHook('afterFind', 'sadot-workaround');
|
|
73
|
+
model.removeHook('beforeFind', 'sadot-beforeFind');
|
|
74
|
+
model.removeHook('beforeBulkCreate', 'sadot-beforeBulkCreate');
|
|
75
|
+
model.removeHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate');
|
|
76
|
+
model.removeHook('beforeCreate', 'sadot-beforeCreate');
|
|
77
|
+
model.removeHook('beforeUpdate', 'sadot-beforeUpdate');
|
|
78
|
+
model.removeHook('afterFind', 'sadot-afterFind');
|
|
79
|
+
model.removeHook('afterUpdate', 'sadot-afterUpdate');
|
|
80
|
+
} catch (e) {
|
|
81
|
+
logger.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const disableCustomFields = (models, getModel) => {
|
|
87
|
+
removeHooks(models, getModel);
|
|
88
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
/* eslint-disable indent */
|
|
2
3
|
import {
|
|
3
4
|
Table,
|
|
4
5
|
Column,
|
|
@@ -9,31 +10,32 @@ import {
|
|
|
9
10
|
BeforeCreate,
|
|
10
11
|
DefaultScope,
|
|
11
12
|
AfterSave,
|
|
13
|
+
Is,
|
|
12
14
|
} from 'sequelize-typescript';
|
|
15
|
+
import { validateValidation } from '../utils/validations/custom';
|
|
16
|
+
import { CustomFieldDefinitionType } from '../utils/validations/type';
|
|
13
17
|
import { CustomFieldValue } from '.';
|
|
14
18
|
import { sendDimEvent } from '../events';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Supported custom field types
|
|
18
|
-
*/
|
|
19
|
-
// eslint-disable-next-line no-shadow
|
|
20
|
-
export enum CustomFieldDefinitionType {
|
|
21
|
-
NUMBER = 'number',
|
|
22
|
-
BOOLEAN = 'boolean',
|
|
23
|
-
DATE = 'date',
|
|
24
|
-
DATETIME = 'datetime',
|
|
25
|
-
TEXT = 'text',
|
|
26
|
-
IMAGE = 'image',
|
|
27
|
-
ENUM = 'enum',
|
|
28
|
-
}
|
|
19
|
+
import { UnsupportedCustomFieldTypeError, UnsupportedCustomValidationError } from '../errors';
|
|
29
20
|
|
|
30
21
|
@DefaultScope(() => ({ where: { disabled: false } }))
|
|
31
22
|
@Table({
|
|
32
23
|
indexes: [
|
|
33
|
-
|
|
24
|
+
{
|
|
25
|
+
name: 'unique_name_model_type',
|
|
26
|
+
fields: ['name', 'model_type', 'entity_id'],
|
|
27
|
+
unique: true,
|
|
28
|
+
},
|
|
34
29
|
],
|
|
35
30
|
timestamps: true,
|
|
36
|
-
|
|
31
|
+
validate: {
|
|
32
|
+
validationByType(this: CustomFieldDefinition) {
|
|
33
|
+
if (!validateValidation(this.fieldType, this.validation)) {
|
|
34
|
+
throw new UnsupportedCustomValidationError(`Validation provided for "${this.fieldType}" is not supported`);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
})
|
|
37
39
|
class CustomFieldDefinition extends Model {
|
|
38
40
|
@PrimaryKey
|
|
39
41
|
@Column({
|
|
@@ -54,6 +56,11 @@ class CustomFieldDefinition extends Model {
|
|
|
54
56
|
})
|
|
55
57
|
displayName?: string; // Defaulted to name with beforeCreate hook
|
|
56
58
|
|
|
59
|
+
@Is('SupportedType', (value) => {
|
|
60
|
+
if (!Object.values(CustomFieldDefinitionType).includes(value as CustomFieldDefinitionType)) {
|
|
61
|
+
throw new UnsupportedCustomFieldTypeError(`"${value}" is not a supported type.`);
|
|
62
|
+
}
|
|
63
|
+
})
|
|
57
64
|
@Column({
|
|
58
65
|
type: DataType.STRING,
|
|
59
66
|
allowNull: false,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
/* eslint-disable indent */
|
|
2
3
|
import {
|
|
3
4
|
Table,
|
|
4
5
|
Column,
|
|
@@ -13,16 +14,18 @@ import {
|
|
|
13
14
|
BeforeUpdate,
|
|
14
15
|
BeforeBulkCreate,
|
|
15
16
|
BeforeBulkUpdate,
|
|
17
|
+
Scopes,
|
|
16
18
|
} from 'sequelize-typescript';
|
|
17
19
|
import { sendDimEvent } from '../events';
|
|
18
20
|
import { CustomFieldDefinition } from '.';
|
|
19
21
|
import validateValue from '../utils/validations';
|
|
20
22
|
import * as CustomFieldDefinitionRepo from '../repository/definition';
|
|
21
23
|
import logger from '../utils/logger';
|
|
24
|
+
import { InvalidValueError } from '../errors';
|
|
22
25
|
|
|
23
26
|
@Table({
|
|
24
27
|
timestamps: true,
|
|
25
|
-
|
|
28
|
+
})
|
|
26
29
|
class CustomFieldValue extends Model {
|
|
27
30
|
@PrimaryKey
|
|
28
31
|
@Column({
|
|
@@ -41,7 +44,7 @@ class CustomFieldValue extends Model {
|
|
|
41
44
|
|
|
42
45
|
@Column({
|
|
43
46
|
type: DataType.JSONB,
|
|
44
|
-
allowNull:
|
|
47
|
+
allowNull: true,
|
|
45
48
|
})
|
|
46
49
|
value!: any;
|
|
47
50
|
|
|
@@ -60,7 +63,6 @@ class CustomFieldValue extends Model {
|
|
|
60
63
|
@BeforeBulkCreate
|
|
61
64
|
@BeforeBulkUpdate
|
|
62
65
|
static async validateValues(instances: CustomFieldValue[]): Promise<void> {
|
|
63
|
-
logger.info('CustomFieldValue BeforeBulkCreate/BeforeBulkUpdate', { instances });
|
|
64
66
|
const ids = instances.map((instance) => instance.customFieldDefinitionId);
|
|
65
67
|
const uniqueIds = [...new Set(ids)];
|
|
66
68
|
const definitions = await CustomFieldDefinitionRepo.findByIds(
|
|
@@ -80,7 +82,7 @@ class CustomFieldValue extends Model {
|
|
|
80
82
|
.find((definition) => definition.id === instance.customFieldDefinitionId);
|
|
81
83
|
const isValid = validateValue(instance.value, fieldType, validation);
|
|
82
84
|
if (!isValid) {
|
|
83
|
-
throw new
|
|
85
|
+
throw new InvalidValueError(instance.value, fieldType);
|
|
84
86
|
}
|
|
85
87
|
});
|
|
86
88
|
}
|
|
@@ -89,20 +91,18 @@ class CustomFieldValue extends Model {
|
|
|
89
91
|
@BeforeCreate
|
|
90
92
|
@BeforeUpsert
|
|
91
93
|
static async validateValue(instance: CustomFieldValue): Promise<void> {
|
|
92
|
-
logger.info('CustomFieldValue BeforeCreate/BeforeUpdate', { instance });
|
|
93
94
|
const { customFieldDefinitionId } = instance;
|
|
94
95
|
// eslint-disable-next-line max-len
|
|
95
96
|
const cfd = await CustomFieldDefinitionRepo.findById(customFieldDefinitionId, { withDisabled: true });
|
|
96
97
|
const { validation, fieldType } = cfd;
|
|
97
98
|
const isValid = validateValue(instance.value, fieldType, validation);
|
|
98
99
|
if (!isValid) {
|
|
99
|
-
throw new
|
|
100
|
+
throw new InvalidValueError(instance.value, fieldType);
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
@AfterUpsert
|
|
104
105
|
static afterSaveHandler(instance: CustomFieldValue, options): void {
|
|
105
|
-
logger.info('CustomFieldValue afterSave', { instance });
|
|
106
106
|
if (options.transaction) {
|
|
107
107
|
options.transaction.afterCommit(() => sendDimEvent(instance[0]));
|
|
108
108
|
} else {
|
package/src/models/index.ts
CHANGED
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
/* eslint-disable no-param-reassign */
|
|
2
2
|
import type { Sequelize } from 'sequelize-typescript';
|
|
3
3
|
import logger from '../utils/logger';
|
|
4
|
-
// import addPermanentHooks from '../hooks/permanent-hooks';
|
|
5
|
-
|
|
6
4
|
import CustomFieldDefinition from './CustomFieldDefinition';
|
|
7
5
|
import CustomFieldValue from './CustomFieldValue';
|
|
8
6
|
import TestModel from './tests/TestModel';
|
|
9
7
|
import AssociatedTestModel from './tests/AssociatedTestModel';
|
|
10
8
|
|
|
11
|
-
// export const CustomFieldDefinition = CustomFieldDefinitionModel;
|
|
12
|
-
// export { default as CustomFieldDefinition } from './custom-field-definition.model';
|
|
13
|
-
|
|
14
9
|
const productionModels = [CustomFieldDefinition, CustomFieldValue];
|
|
15
10
|
const testModels = [...productionModels, TestModel, AssociatedTestModel];
|
|
16
11
|
|
|
17
|
-
const initTables = async (sequelize: Sequelize): Promise<void> => {
|
|
12
|
+
const initTables = async (sequelize: Sequelize, getUser): Promise<void> => {
|
|
18
13
|
logger.info('custom-fields: initialize custom-fields tables');
|
|
19
14
|
// Detect models and import them to the orm
|
|
20
15
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
21
16
|
if (!sequelize.addModels) {
|
|
22
17
|
throw new Error('sequelize instance must have addModels function');
|
|
23
18
|
}
|
|
24
|
-
|
|
19
|
+
|
|
20
|
+
const models = process.env.NODE_ENV === 'test' ? testModels : productionModels;
|
|
21
|
+
|
|
22
|
+
sequelize.addModels(models);
|
|
25
23
|
await sequelize.dropSchema('custom-fields', { logging: false });
|
|
26
24
|
await sequelize.createSchema('custom-fields', { logging: false });
|
|
27
25
|
|
|
@@ -34,6 +32,22 @@ const initTables = async (sequelize: Sequelize): Promise<void> => {
|
|
|
34
32
|
await AssociatedTestModel.sync({ alter: true });
|
|
35
33
|
}
|
|
36
34
|
logger.info('custom-fields: models synced');
|
|
35
|
+
|
|
36
|
+
CustomFieldDefinition.addScope('userScope', () => {
|
|
37
|
+
const user = getUser();
|
|
38
|
+
if (user?.permissions) {
|
|
39
|
+
return {
|
|
40
|
+
where: {
|
|
41
|
+
entityId: [
|
|
42
|
+
...Object.keys(user.permissions.fleets),
|
|
43
|
+
...Object.keys(user.permissions.businessModels),
|
|
44
|
+
...Object.keys(user.permissions.demandSources),
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {};
|
|
50
|
+
});
|
|
37
51
|
};
|
|
38
52
|
|
|
39
53
|
export {
|