@autofleet/sadot 0.0.1-beta → 0.0.1-beta.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/{src/api → api}/index.js +1 -0
- package/dist/api/index.js.map +1 -0
- package/dist/{src/api → api}/v1/definition/index.js +28 -25
- package/dist/api/v1/definition/index.js.map +1 -0
- package/dist/api/v1/definition/validations.js +36 -0
- package/dist/api/v1/definition/validations.js.map +1 -0
- package/dist/{src/api → api}/v1/errors.js +1 -0
- package/dist/api/v1/errors.js.map +1 -0
- package/dist/{src/api → api}/v1/index.js +2 -1
- package/dist/api/v1/index.js.map +1 -0
- package/dist/{src/events → events}/index.js +2 -21
- package/dist/events/index.js.map +1 -0
- package/dist/{src/hooks → hooks}/create.js +18 -15
- package/dist/hooks/create.js.map +1 -0
- package/dist/{src/hooks/enrich.js → hooks/find.js} +29 -52
- package/dist/hooks/find.js.map +1 -0
- package/dist/{src/hooks → hooks}/index.js +4 -5
- package/dist/hooks/index.js.map +1 -0
- package/dist/{src/hooks → hooks}/update.js +23 -8
- package/dist/hooks/update.js.map +1 -0
- package/dist/{src/hooks → hooks}/workaround.js +15 -5
- package/dist/hooks/workaround.js.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/{src/models → models}/CustomFieldDefinition.js +17 -22
- package/dist/models/CustomFieldDefinition.js.map +1 -0
- package/dist/{src/models → models}/CustomFieldValue.js +39 -24
- package/dist/models/CustomFieldValue.js.map +1 -0
- package/dist/models/index.js +50 -0
- package/dist/models/index.js.map +1 -0
- package/dist/{src/models → models}/tests/AssociatedTestModel.js +1 -0
- package/dist/models/tests/AssociatedTestModel.js.map +1 -0
- package/dist/{src/models → models}/tests/TestModel.js +1 -0
- package/dist/models/tests/TestModel.js.map +1 -0
- package/dist/repository/definition.js +107 -0
- package/dist/repository/definition.js.map +1 -0
- package/dist/{src/repository → repository}/value.js +36 -27
- package/dist/repository/value.js.map +1 -0
- package/dist/{src/tests → tests}/api/test-api.js +22 -12
- package/dist/tests/api/test-api.js.map +1 -0
- package/dist/tests/helpers/index.js +28 -0
- package/dist/tests/helpers/index.js.map +1 -0
- package/dist/tests/mocks/index.js +60 -0
- package/dist/tests/mocks/index.js.map +1 -0
- package/dist/{src/tests → tests}/mocks/testModel.js +18 -10
- package/dist/tests/mocks/testModel.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/{src/types → types}/definition/index.js +1 -0
- package/dist/types/definition/index.js.map +1 -0
- package/dist/{src/types → types}/index.js +1 -0
- package/dist/types/index.js.map +1 -0
- package/dist/{src/types → types}/value/index.js +1 -0
- package/dist/types/value/index.js.map +1 -0
- package/dist/{src/utils → utils}/db/index.js +1 -8
- package/dist/utils/db/index.js.map +1 -0
- package/dist/{src/utils → utils}/logger/index.js +2 -2
- package/dist/utils/logger/index.js.map +1 -0
- package/dist/{src/utils → utils}/validations/custom-fields.js +1 -0
- package/dist/utils/validations/custom-fields.js.map +1 -0
- package/dist/utils/validations/custom.js +58 -0
- package/dist/utils/validations/custom.js.map +1 -0
- package/dist/{src/utils → utils}/validations/index.js +1 -0
- package/dist/utils/validations/index.js.map +1 -0
- package/dist/utils/validations/type.js +32 -0
- package/dist/utils/validations/type.js.map +1 -0
- package/package.json +5 -12
- package/src/api/v1/definition/index.ts +13 -20
- package/src/api/v1/definition/validations.ts +27 -13
- package/src/api/v1/index.ts +1 -1
- package/src/events/index.ts +1 -23
- package/src/hooks/create.ts +6 -12
- package/src/hooks/find.ts +82 -23
- package/src/hooks/index.ts +2 -4
- package/src/hooks/update.ts +11 -5
- package/src/hooks/workaround.ts +2 -2
- package/src/index.ts +17 -60
- package/src/models/CustomFieldDefinition.ts +15 -23
- package/src/models/CustomFieldValue.ts +10 -10
- package/src/models/index.ts +17 -79
- package/src/models/tests/AssociatedTestModel.ts +1 -0
- package/src/models/tests/TestModel.ts +1 -0
- package/src/repository/definition.ts +41 -27
- package/src/repository/value.ts +11 -12
- package/src/tests/mocks/events.mock.ts +1 -1
- package/src/tests/mocks/{definition.mock.ts → index.ts} +6 -5
- package/src/tests/mocks/testModel.ts +7 -12
- package/src/types/definition/index.ts +1 -0
- package/src/types/index.ts +6 -4
- package/src/types/value/index.ts +2 -1
- package/src/utils/db/index.ts +0 -7
- package/src/utils/logger/index.ts +1 -3
- package/src/utils/validations/custom.ts +45 -28
- package/src/utils/validations/type.ts +6 -23
- package/tsconfig.json +26 -9
- package/dist/jest.config.d.ts +0 -12
- package/dist/src/api/index.d.ts +0 -2
- package/dist/src/api/v1/definition/index.d.ts +0 -2
- package/dist/src/api/v1/definition/validations.d.ts +0 -2
- package/dist/src/api/v1/definition/validations.js +0 -36
- package/dist/src/api/v1/errors.d.ts +0 -2
- package/dist/src/api/v1/index.d.ts +0 -2
- package/dist/src/errors/index.d.ts +0 -16
- package/dist/src/errors/index.js +0 -45
- package/dist/src/events/index.d.ts +0 -4
- package/dist/src/hooks/create.d.ts +0 -9
- package/dist/src/hooks/enrich.d.ts +0 -5
- package/dist/src/hooks/find.d.ts +0 -1
- package/dist/src/hooks/find.js +0 -29
- package/dist/src/hooks/index.d.ts +0 -6
- package/dist/src/hooks/update.d.ts +0 -9
- package/dist/src/hooks/workaround.d.ts +0 -10
- package/dist/src/index.d.ts +0 -11
- package/dist/src/index.js +0 -105
- package/dist/src/models/CustomFieldDefinition.d.ts +0 -23
- package/dist/src/models/CustomFieldValue.d.ts +0 -15
- package/dist/src/models/index.d.ts +0 -8
- package/dist/src/models/index.js +0 -87
- package/dist/src/models/tests/AssociatedTestModel.d.ts +0 -12
- package/dist/src/models/tests/TestModel.d.ts +0 -11
- package/dist/src/repository/definition.d.ts +0 -17
- package/dist/src/repository/definition.js +0 -80
- package/dist/src/repository/value.d.ts +0 -24
- package/dist/src/tests/api/test-api.d.ts +0 -2
- package/dist/src/tests/helpers/database-config.d.ts +0 -15
- package/dist/src/tests/helpers/database-config.js +0 -16
- package/dist/src/tests/helpers/index.d.ts +0 -2
- package/dist/src/tests/helpers/index.js +0 -18
- package/dist/src/tests/mocks/definition.mock.d.ts +0 -37
- package/dist/src/tests/mocks/definition.mock.js +0 -64
- package/dist/src/tests/mocks/events.mock.d.ts +0 -3
- package/dist/src/tests/mocks/events.mock.js +0 -19
- package/dist/src/tests/mocks/testModel.d.ts +0 -12
- package/dist/src/types/definition/index.d.ts +0 -23
- package/dist/src/types/index.d.ts +0 -13
- package/dist/src/types/value/index.d.ts +0 -15
- package/dist/src/utils/constants/index.d.ts +0 -1
- package/dist/src/utils/constants/index.js +0 -5
- package/dist/src/utils/db/index.d.ts +0 -4
- package/dist/src/utils/logger/index.d.ts +0 -2
- package/dist/src/utils/validations/custom-fields.d.ts +0 -2
- package/dist/src/utils/validations/custom.d.ts +0 -15
- package/dist/src/utils/validations/custom.js +0 -42
- package/dist/src/utils/validations/index.d.ts +0 -2
- package/dist/src/utils/validations/type.d.ts +0 -18
- package/dist/src/utils/validations/type.js +0 -50
- package/dist/src/utils/validations/validators.d.ts +0 -12
- package/dist/src/utils/validations/validators.js +0 -33
- package/src/errors/index.ts +0 -42
- package/src/hooks/enrich.ts +0 -125
- package/src/tests/helpers/database-config.ts +0 -14
- package/src/utils/constants/index.ts +0 -2
- package/src/utils/validations/validators.ts +0 -34
package/package.json
CHANGED
|
@@ -1,39 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sadot",
|
|
3
|
-
"version": "0.0.1-beta",
|
|
3
|
+
"version": "0.0.1-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "ts-node src/index.ts",
|
|
8
8
|
"build": "rm -rf dist && tsc",
|
|
9
9
|
"linter": "./node_modules/.bin/eslint .",
|
|
10
|
-
"test": "jest --forceExit
|
|
11
|
-
"coverage": "jest --coverage --forceExit
|
|
10
|
+
"test": "jest --forceExit",
|
|
11
|
+
"coverage": "jest --coverage --forceExit && rm -rf ./coverage",
|
|
12
12
|
"dev": "nodemon"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@autofleet/errors": "^1.0.10",
|
|
16
16
|
"@autofleet/events": "^2.0.0",
|
|
17
|
-
"@autofleet/logger": "^2.0.5",
|
|
18
17
|
"@hapi/joi": "^17.1.1",
|
|
19
18
|
"express": "^4.18.2",
|
|
20
|
-
"
|
|
21
|
-
"reflect-metadata": "^0.1.13",
|
|
22
|
-
"sequelize": "^6.31.1",
|
|
23
|
-
"sequelize-typescript": "^2.1.5",
|
|
24
|
-
"uuid": "^9.0.0"
|
|
19
|
+
"sequelize": "^6.31.1"
|
|
25
20
|
},
|
|
26
21
|
"devDependencies": {
|
|
27
22
|
"@types/express": "^4.17.17",
|
|
28
23
|
"@types/jest": "^27.0.9",
|
|
29
|
-
"@types/node": "^20.5.6",
|
|
30
|
-
"@types/uuid": "^9.0.2",
|
|
31
24
|
"@typescript-eslint/eslint-plugin": "^4.8.1",
|
|
32
|
-
"@typescript-eslint/parser": "^4.33.0",
|
|
33
25
|
"eslint": "^7.13.0",
|
|
34
26
|
"eslint-config-airbnb-typescript": "^12.0.0",
|
|
35
27
|
"eslint-plugin-import": "^2.22.1",
|
|
36
28
|
"jest": "^27.0.5",
|
|
29
|
+
"sequelize-typescript": "^2.1.5",
|
|
37
30
|
"ts-jest": "^27.0.3",
|
|
38
31
|
"ts-node": "^8.6.2",
|
|
39
32
|
"typescript": "^4.9.5",
|
|
@@ -10,7 +10,7 @@ import logger from '../../../utils/logger';
|
|
|
10
10
|
const router = Router({ mergeParams: true });
|
|
11
11
|
const ENTITY = 'CustomFieldDefinition';
|
|
12
12
|
|
|
13
|
-
const toPascalCase = (str: string): string => str.replace(/(^\w|-\w)/g,
|
|
13
|
+
const toPascalCase = (str: string): string => str.replace(/(^\w|-\w)/g, subStr => subStr.replace(/-/, '').toUpperCase());
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Create
|
|
@@ -19,13 +19,13 @@ router.post('/', async (req: Request, res: Response) => {
|
|
|
19
19
|
const { modelName } = req.params as any;
|
|
20
20
|
const modelType = toPascalCase(modelName);
|
|
21
21
|
try {
|
|
22
|
-
const validatedPayload: CreateCustomFieldDefinition =
|
|
23
|
-
|
|
22
|
+
const validatedPayload: CreateCustomFieldDefinition =
|
|
23
|
+
await validateCustomFieldDefinitionCreation({
|
|
24
|
+
...req.body,
|
|
25
|
+
modelType,
|
|
26
|
+
});
|
|
24
27
|
|
|
25
|
-
const customFieldDefinition = await DefinitionRepo.create(
|
|
26
|
-
...validatedPayload,
|
|
27
|
-
modelType,
|
|
28
|
-
});
|
|
28
|
+
const customFieldDefinition = await DefinitionRepo.create(validatedPayload);
|
|
29
29
|
return res.status(201).json(customFieldDefinition);
|
|
30
30
|
} catch (err) {
|
|
31
31
|
logger.error('Failed to create custom field definition', err);
|
|
@@ -57,18 +57,11 @@ router.get('/:customFieldDefinitionId', async (req, res) => {
|
|
|
57
57
|
*/
|
|
58
58
|
router.get('/', async (req, res) => {
|
|
59
59
|
const { modelName } = req.params as any;
|
|
60
|
-
const { entityIds } = req.query as any;
|
|
61
|
-
|
|
62
60
|
const modelType = toPascalCase(modelName);
|
|
63
61
|
try {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
const customFieldDefinitions = await DefinitionRepo.findAll(
|
|
69
|
-
{ ...where },
|
|
70
|
-
{ withDisabled: true },
|
|
71
|
-
);
|
|
62
|
+
const customFieldDefinitions = await DefinitionRepo.findAll({
|
|
63
|
+
modelType,
|
|
64
|
+
});
|
|
72
65
|
return res.json(customFieldDefinitions);
|
|
73
66
|
} catch (err) {
|
|
74
67
|
logger.error('Failed to fetch custom field definitions', err);
|
|
@@ -83,8 +76,8 @@ router.patch('/:customFieldDefinitionId', async (req, res) => {
|
|
|
83
76
|
const { customFieldDefinitionId, modelName } = req.params as any;
|
|
84
77
|
const modelType = toPascalCase(modelName);
|
|
85
78
|
try {
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
const validatedPayload: UpdateCustomFieldDefinition =
|
|
80
|
+
await validateCustomFieldDefinitionUpdate(req.body);
|
|
88
81
|
|
|
89
82
|
const customFieldDefinition = await DefinitionRepo.findByWhere({
|
|
90
83
|
id: customFieldDefinitionId,
|
|
@@ -97,7 +90,7 @@ router.patch('/:customFieldDefinitionId', async (req, res) => {
|
|
|
97
90
|
|
|
98
91
|
const updatedCustomFieldDefinition = await DefinitionRepo.update(
|
|
99
92
|
customFieldDefinitionId,
|
|
100
|
-
|
|
93
|
+
validatedPayload,
|
|
101
94
|
);
|
|
102
95
|
|
|
103
96
|
return res.status(200).json(updatedCustomFieldDefinition);
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import Joi from '@hapi/joi';
|
|
2
|
-
import { CustomFieldDefinitionType } from '../../../
|
|
3
|
-
|
|
4
|
-
const ValidationSchema = Joi.when('fieldType', {
|
|
5
|
-
is: CustomFieldDefinitionType.SELECT,
|
|
6
|
-
then: Joi.array().items(Joi.string()).min(1).unique(),
|
|
7
|
-
otherwise: Joi.any(),
|
|
8
|
-
});
|
|
2
|
+
import { CustomFieldDefinitionType } from '../../../models/CustomFieldDefinition';
|
|
9
3
|
|
|
10
4
|
const CustomFieldDefinitionCreationSchema = Joi.object({
|
|
11
5
|
name: Joi.string().required(),
|
|
12
6
|
displayName: Joi.string().required(),
|
|
13
|
-
validation:
|
|
14
|
-
fieldType: Joi.string().valid(
|
|
7
|
+
validation: Joi.object().required(),
|
|
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(),
|
|
15
17
|
entityId: Joi.string().guid().required(),
|
|
16
18
|
entityType: Joi.string().required(),
|
|
19
|
+
modelType: Joi.string().required(),
|
|
17
20
|
description: Joi.string(),
|
|
18
21
|
required: Joi.boolean(),
|
|
19
22
|
disabled: Joi.boolean(),
|
|
@@ -21,15 +24,26 @@ const CustomFieldDefinitionCreationSchema = Joi.object({
|
|
|
21
24
|
|
|
22
25
|
const CustomFieldDefinitionUpdateSchema = Joi.object({
|
|
23
26
|
displayName: Joi.string(),
|
|
24
|
-
validation:
|
|
25
|
-
fieldType: Joi.string().valid(
|
|
27
|
+
validation: Joi.object(),
|
|
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
|
+
),
|
|
37
|
+
entityId: Joi.string().guid(),
|
|
38
|
+
entityType: Joi.string(),
|
|
39
|
+
modelType: Joi.string(),
|
|
26
40
|
description: Joi.string(),
|
|
27
41
|
required: Joi.boolean(),
|
|
28
42
|
disabled: Joi.boolean(),
|
|
29
43
|
});
|
|
30
44
|
|
|
31
|
-
export const validateCustomFieldDefinitionCreation =
|
|
45
|
+
export const validateCustomFieldDefinitionCreation = payload =>
|
|
32
46
|
CustomFieldDefinitionCreationSchema.validateAsync(payload, { abortEarly: false });
|
|
33
47
|
|
|
34
|
-
export const validateCustomFieldDefinitionUpdate =
|
|
48
|
+
export const validateCustomFieldDefinitionUpdate = payload =>
|
|
35
49
|
CustomFieldDefinitionUpdateSchema.validateAsync(payload, { abortEarly: false });
|
package/src/api/v1/index.ts
CHANGED
package/src/events/index.ts
CHANGED
|
@@ -3,21 +3,6 @@ import logger from '../utils/logger';
|
|
|
3
3
|
|
|
4
4
|
const events = new Events({ logger });
|
|
5
5
|
|
|
6
|
-
const KEYS_TO_CONVERT = ['value'];
|
|
7
|
-
|
|
8
|
-
const stringifyBools = (savedObject: any, keysToConvert: Array<string>) => {
|
|
9
|
-
if (Object.keys(savedObject).some((key) => keysToConvert.includes(key))) {
|
|
10
|
-
const objectToReturn = { ...savedObject };
|
|
11
|
-
keysToConvert.forEach((key) => {
|
|
12
|
-
if (typeof savedObject[key] === 'boolean') {
|
|
13
|
-
objectToReturn[key] = savedObject[key].toString();
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
return objectToReturn;
|
|
17
|
-
}
|
|
18
|
-
return savedObject;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
6
|
const modelTableMapping = {
|
|
22
7
|
CustomFieldDefinition: {
|
|
23
8
|
tableName: 'dim_custom_field_definition',
|
|
@@ -32,17 +17,10 @@ const modelTableMapping = {
|
|
|
32
17
|
export const sendDimEvent = (instance): void => {
|
|
33
18
|
const mapping = modelTableMapping[instance.constructor.name];
|
|
34
19
|
if (mapping) {
|
|
35
|
-
let objectToSend = instance.get();
|
|
36
|
-
try {
|
|
37
|
-
objectToSend = stringifyBools(instance.get(), KEYS_TO_CONVERT);
|
|
38
|
-
} catch (err) {
|
|
39
|
-
logger.error('Failed to convert booleans in dim event payload', err);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
20
|
events.sendObject(
|
|
43
21
|
mapping.tableName,
|
|
44
22
|
mapping.eventVersion,
|
|
45
|
-
|
|
23
|
+
instance.get(),
|
|
46
24
|
);
|
|
47
25
|
}
|
|
48
26
|
};
|
package/src/hooks/create.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import logger from '../utils/logger';
|
|
2
1
|
import * as ValueRepo from '../repository/value';
|
|
3
2
|
import * as DefinitionRepo from '../repository/definition';
|
|
4
|
-
import { MissingRequiredCustomFieldError } from '../errors';
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* A hook to create the custom fields when updating a model (more then one instance).
|
|
@@ -19,33 +17,29 @@ export const beforeCreate = (scopeAttributes: string[]) => async (
|
|
|
19
17
|
instance,
|
|
20
18
|
options,
|
|
21
19
|
): Promise<void> => {
|
|
22
|
-
logger.debug('sadot - before create hook');
|
|
23
20
|
const { fields } = options;
|
|
24
21
|
const modelType = instance.constructor.name;
|
|
25
|
-
const identifiers = scopeAttributes.map(
|
|
22
|
+
const identifiers = scopeAttributes.map(attribute => instance[attribute]);
|
|
26
23
|
// get all model's required definitions
|
|
27
|
-
const requiredFieldsNames = await DefinitionRepo.getRequiredFields(modelType,
|
|
28
|
-
instance.id,
|
|
29
|
-
identifiers);
|
|
24
|
+
const requiredFieldsNames = await DefinitionRepo.getRequiredFields(modelType, instance.id, identifiers);
|
|
30
25
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
31
26
|
const { customFields } = instance;
|
|
32
27
|
if (customFieldsIdx > -1 && customFields) {
|
|
33
28
|
const fieldsNames = Object.keys(customFields);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
throw new MissingRequiredCustomFieldError(missingFields);
|
|
29
|
+
if (requiredFieldsNames.some(name => !fieldsNames.includes(name))) {
|
|
30
|
+
throw new Error('some fields are required');
|
|
37
31
|
}
|
|
38
32
|
|
|
39
33
|
await ValueRepo.updateValues(
|
|
40
34
|
modelType,
|
|
41
35
|
instance.id,
|
|
42
|
-
identifiers,
|
|
43
36
|
customFields,
|
|
44
37
|
{ transaction: options.transaction },
|
|
45
38
|
);
|
|
46
39
|
// eslint-disable-next-line no-param-reassign
|
|
47
40
|
fields.splice(customFieldsIdx, 1);
|
|
48
41
|
} else if (requiredFieldsNames?.length > 0) {
|
|
49
|
-
throw new
|
|
42
|
+
throw new Error('some fields are required');
|
|
50
43
|
}
|
|
51
44
|
};
|
|
45
|
+
|
package/src/hooks/find.ts
CHANGED
|
@@ -1,27 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
|
|
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;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
/**
|
|
19
|
+
* A hook to attach the custom fields when fetching a model instances.
|
|
20
|
+
*/
|
|
21
|
+
const afterFind = (scopeAttributes: string[]) => async (
|
|
22
|
+
instancesOrInstance: any | any[],
|
|
23
|
+
options,
|
|
24
|
+
): Promise<void> => {
|
|
25
|
+
const primaryKey = 'id';
|
|
26
|
+
const instances = Array.isArray(instancesOrInstance)
|
|
27
|
+
? instancesOrInstance
|
|
28
|
+
: [instancesOrInstance];
|
|
29
|
+
|
|
30
|
+
const identifiers = instances.map(instance => scopeAttributes.map(attr => instance[attr])).flat();
|
|
31
|
+
const uniqueIdentifiers = [...new Set(identifiers)];
|
|
32
|
+
const identifierCustomFieldDefinitionsMapping = {};
|
|
33
|
+
await Promise.all(uniqueIdentifiers.map(async (identifier) => {
|
|
34
|
+
const customFieldDefinitions = await DefinitionRepo.findByEntityId(
|
|
35
|
+
identifier,
|
|
36
|
+
options.transaction,
|
|
37
|
+
);
|
|
38
|
+
identifierCustomFieldDefinitionsMapping[identifier] = customFieldDefinitions;
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
// Get the values per instates ids:
|
|
42
|
+
const instancesIds = instances.map(i => i[primaryKey]);
|
|
43
|
+
|
|
44
|
+
const customFieldValues = await ValueRepo.findValuesByModelIds(
|
|
45
|
+
instancesIds,
|
|
46
|
+
{ transaction: options.transaction },
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Group fields by modelId
|
|
50
|
+
const valuesGroupByInstance: {
|
|
51
|
+
[modelId: string]: CustomFieldValue[];
|
|
52
|
+
} = customFieldValues.reduce((acc, v) => {
|
|
53
|
+
const { modelId } = v;
|
|
54
|
+
if (!acc[modelId]) {
|
|
55
|
+
acc[modelId] = [];
|
|
56
|
+
}
|
|
57
|
+
acc[modelId].push(v);
|
|
58
|
+
return acc;
|
|
59
|
+
}, {});
|
|
60
|
+
|
|
61
|
+
// Attach custom fields to the instances
|
|
62
|
+
instances.forEach((instance) => {
|
|
63
|
+
const customFields = {};
|
|
64
|
+
const { id } = instance;
|
|
65
|
+
const instanceValues = valuesGroupByInstance[id];
|
|
66
|
+
if (instanceValues) {
|
|
67
|
+
const serializedCustomFields = serializeCustomFields(instanceValues);
|
|
68
|
+
Object.assign(customFields, serializedCustomFields);
|
|
25
69
|
}
|
|
26
|
-
|
|
70
|
+
|
|
71
|
+
scopeAttributes.forEach((attribute) => {
|
|
72
|
+
const identifier = instance[attribute];
|
|
73
|
+
const customFieldDefinitions = identifierCustomFieldDefinitionsMapping[identifier];
|
|
74
|
+
if (customFieldDefinitions?.length > 0) {
|
|
75
|
+
customFieldDefinitions.forEach((customFieldDefinition) => {
|
|
76
|
+
if (customFields[customFieldDefinition.name] === undefined) {
|
|
77
|
+
customFields[customFieldDefinition.name] = null;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
instance.setDataValue('customFields', customFields);
|
|
83
|
+
});
|
|
27
84
|
};
|
|
85
|
+
|
|
86
|
+
export default afterFind;
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { beforeFind } from './find';
|
|
1
|
+
import afterFind from './find';
|
|
3
2
|
import { beforeBulkUpdate, beforeUpdate } from './update';
|
|
4
3
|
import { beforeBulkCreate, beforeCreate } from './create';
|
|
5
4
|
import workaround from './workaround';
|
|
6
5
|
|
|
7
6
|
export {
|
|
8
|
-
|
|
9
|
-
beforeFind,
|
|
7
|
+
afterFind,
|
|
10
8
|
beforeBulkUpdate,
|
|
11
9
|
beforeUpdate,
|
|
12
10
|
beforeBulkCreate,
|
package/src/hooks/update.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import logger from '../utils/logger';
|
|
2
1
|
import * as ValueRepo from '../repository/value';
|
|
2
|
+
import * as DefinitionRepo from '../repository/definition';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A hook to update the custom fields when updating a model (more then one instance).
|
|
@@ -9,7 +9,6 @@ export const beforeBulkUpdate = (options): void => {
|
|
|
9
9
|
// eslint-disable-next-line no-param-reassign
|
|
10
10
|
options.individualHooks = true;
|
|
11
11
|
};
|
|
12
|
-
|
|
13
12
|
/**
|
|
14
13
|
* A hook to update the custom fields when updating a model instance.
|
|
15
14
|
* TODO - cleanup if update fail
|
|
@@ -18,22 +17,29 @@ export const beforeUpdate = (scopeAttributes: string[]) => async (
|
|
|
18
17
|
instance,
|
|
19
18
|
options,
|
|
20
19
|
): Promise<void> => {
|
|
21
|
-
logger.debug('sadot - before update hook');
|
|
22
20
|
const { fields } = options;
|
|
23
21
|
const modelType = instance.constructor.name;
|
|
24
|
-
const identifiers = scopeAttributes.map(
|
|
22
|
+
const identifiers = scopeAttributes.map(attribute => instance[attribute]);
|
|
23
|
+
// get all model's required definitions
|
|
24
|
+
const requiredNullFieldsNames = await DefinitionRepo.getRequiredFields(modelType, instance.id, identifiers, true);
|
|
25
25
|
|
|
26
26
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
27
27
|
if (customFieldsIdx > -1) {
|
|
28
28
|
const { customFields } = instance;
|
|
29
|
+
const fieldsNames = Object.keys(customFields);
|
|
30
|
+
if (requiredNullFieldsNames.some(name => !fieldsNames.includes(name))) {
|
|
31
|
+
throw new Error('some fields are required');
|
|
32
|
+
}
|
|
29
33
|
await ValueRepo.updateValues(
|
|
30
34
|
modelType,
|
|
31
35
|
instance.id,
|
|
32
|
-
identifiers,
|
|
33
36
|
customFields,
|
|
34
37
|
{ transaction: options.transaction },
|
|
35
38
|
);
|
|
36
39
|
// eslint-disable-next-line no-param-reassign
|
|
37
40
|
fields.splice(customFieldsIdx, 1);
|
|
41
|
+
} else if (requiredNullFieldsNames?.length > 0) {
|
|
42
|
+
throw new Error('some fields are required');
|
|
38
43
|
}
|
|
39
44
|
};
|
|
45
|
+
|
package/src/hooks/workaround.ts
CHANGED
|
@@ -33,11 +33,11 @@ const handleChildrenAfterFindHook = async (instances, options, level = 0) => {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
const { associations } = constructor;
|
|
36
|
-
const associatedNames = Object.keys(instance).filter(
|
|
36
|
+
const associatedNames = Object.keys(instance).filter(attribute =>
|
|
37
37
|
Object.keys(associations).includes(attribute));
|
|
38
38
|
|
|
39
39
|
if (associatedNames.length) {
|
|
40
|
-
const childInstances = associatedNames.map(
|
|
40
|
+
const childInstances = associatedNames.map(name => instance[name]);
|
|
41
41
|
return handleChildrenAfterFindHook(childInstances, options, level + 1);
|
|
42
42
|
}
|
|
43
43
|
|
package/src/index.ts
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import type { Application } from 'express';
|
|
2
2
|
import { DataTypes } from 'sequelize';
|
|
3
|
-
import {
|
|
4
|
-
import { initTables, initTestModels } from './models';
|
|
3
|
+
import { initTables } from './models';
|
|
5
4
|
import api from './api';
|
|
6
|
-
import {
|
|
7
|
-
beforeFind,
|
|
8
|
-
enrichResults,
|
|
9
|
-
beforeBulkUpdate,
|
|
10
|
-
beforeUpdate,
|
|
11
|
-
beforeCreate,
|
|
12
|
-
beforeBulkCreate,
|
|
13
|
-
} from './hooks';
|
|
5
|
+
import { afterFind, beforeBulkUpdate, beforeUpdate, beforeCreate, beforeBulkCreate } from './hooks';
|
|
14
6
|
import initDB from './utils/db';
|
|
15
7
|
import logger from './utils/logger';
|
|
16
8
|
import { CustomFieldOptions, ModelFetcher, ModelOptions } from './types';
|
|
@@ -21,27 +13,19 @@ const addHooks = (models: ModelOptions[], getModel: ModelFetcher) => {
|
|
|
21
13
|
models.forEach(async ({ name, scopeAttributes }) => {
|
|
22
14
|
try {
|
|
23
15
|
const model = getModel(name);
|
|
24
|
-
if (!model)
|
|
25
|
-
logger.warn('sadot - tried to addHooks to a model that does not exist yet', {
|
|
26
|
-
name,
|
|
27
|
-
scopeAttributes,
|
|
28
|
-
});
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
16
|
+
if (!model) return;
|
|
31
17
|
model.rawAttributes.customFields = {
|
|
32
18
|
type: DataTypes.VIRTUAL,
|
|
33
19
|
};
|
|
34
20
|
model.refreshAttributes();
|
|
35
21
|
// TODO: Uncomment after tests are passed
|
|
36
22
|
// model.addHook('afterFind', workaround);
|
|
37
|
-
model.addHook('
|
|
38
|
-
model.addHook('beforeBulkCreate',
|
|
39
|
-
model.addHook('
|
|
40
|
-
model.addHook('
|
|
41
|
-
model.addHook('
|
|
42
|
-
model.addHook('
|
|
43
|
-
model.addHook('afterUpdate', 'sadot-afterUpdate', enrichResults(name, scopeAttributes));
|
|
44
|
-
model.addHook('afterCreate', 'sadot-afterCreate', enrichResults(name, scopeAttributes));
|
|
23
|
+
model.addHook('afterFind', afterFind);
|
|
24
|
+
model.addHook('beforeBulkCreate', beforeBulkCreate);
|
|
25
|
+
model.addHook('afterFind', afterFind(scopeAttributes));
|
|
26
|
+
model.addHook('beforeBulkUpdate', beforeBulkUpdate);
|
|
27
|
+
model.addHook('beforeCreate', beforeCreate(scopeAttributes));
|
|
28
|
+
model.addHook('beforeUpdate', beforeUpdate(scopeAttributes));
|
|
45
29
|
} catch (e) {
|
|
46
30
|
logger.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
47
31
|
}
|
|
@@ -56,46 +40,19 @@ const useCustomFields = async (
|
|
|
56
40
|
app: Application | null,
|
|
57
41
|
getModel: ModelFetcher,
|
|
58
42
|
options: CustomFieldOptions,
|
|
59
|
-
): Promise<
|
|
60
|
-
const { models } = options;
|
|
43
|
+
): Promise<void> => {
|
|
44
|
+
const { innerSequelize, models } = options;
|
|
45
|
+
// deepAfterFindWorkAround(sequelize);
|
|
61
46
|
if (app) {
|
|
62
47
|
app.use('/api', api);
|
|
63
48
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
await
|
|
49
|
+
if (!innerSequelize) {
|
|
50
|
+
const moreInnerSequelize = initDB(options.databaseConfig);
|
|
51
|
+
await initTables(moreInnerSequelize);
|
|
52
|
+
} else {
|
|
53
|
+
await initTables(innerSequelize);
|
|
67
54
|
}
|
|
68
55
|
addHooks(models, getModel);
|
|
69
|
-
await initTables(sequelize, options.getUser);
|
|
70
|
-
logger.debug('sadot - custom fields finished initializing with models', models);
|
|
71
|
-
return sequelize;
|
|
72
56
|
};
|
|
73
57
|
|
|
74
58
|
export default useCustomFields;
|
|
75
|
-
|
|
76
|
-
const removeHooks = (models: ModelOptions[], getModel: ModelFetcher) => {
|
|
77
|
-
models.forEach(async ({ name }) => {
|
|
78
|
-
try {
|
|
79
|
-
const model = getModel(name);
|
|
80
|
-
if (!model) return;
|
|
81
|
-
if (model.rawAttributes.customFields) {
|
|
82
|
-
delete model.rawAttributes.customFields;
|
|
83
|
-
model.refreshAttributes();
|
|
84
|
-
}
|
|
85
|
-
// model.removeHook('afterFind', 'sadot-workaround');
|
|
86
|
-
model.removeHook('beforeFind', 'sadot-beforeFind');
|
|
87
|
-
model.removeHook('beforeBulkCreate', 'sadot-beforeBulkCreate');
|
|
88
|
-
model.removeHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate');
|
|
89
|
-
model.removeHook('beforeCreate', 'sadot-beforeCreate');
|
|
90
|
-
model.removeHook('beforeUpdate', 'sadot-beforeUpdate');
|
|
91
|
-
model.removeHook('afterFind', 'sadot-afterFind');
|
|
92
|
-
model.removeHook('afterUpdate', 'sadot-afterUpdate');
|
|
93
|
-
} catch (e) {
|
|
94
|
-
logger.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
export const disableCustomFields = (models, getModel) => {
|
|
100
|
-
removeHooks(models, getModel);
|
|
101
|
-
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
/* eslint-disable indent */
|
|
3
2
|
import {
|
|
4
3
|
Table,
|
|
5
4
|
Column,
|
|
@@ -10,32 +9,30 @@ import {
|
|
|
10
9
|
BeforeCreate,
|
|
11
10
|
DefaultScope,
|
|
12
11
|
AfterSave,
|
|
13
|
-
Is,
|
|
14
12
|
} from 'sequelize-typescript';
|
|
15
|
-
import { validateValidation } from '../utils/validations/custom';
|
|
16
|
-
import { CustomFieldDefinitionType } from '../utils/validations/type';
|
|
17
13
|
import { CustomFieldValue } from '.';
|
|
18
14
|
import { sendDimEvent } from '../events';
|
|
19
|
-
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Supported custom field types
|
|
18
|
+
*/
|
|
19
|
+
export enum CustomFieldDefinitionType {
|
|
20
|
+
NUMBER = 'number',
|
|
21
|
+
BOOLEAN = 'boolean',
|
|
22
|
+
DATE = 'date',
|
|
23
|
+
DATETIME = 'datetime',
|
|
24
|
+
TEXT = 'text',
|
|
25
|
+
IMAGE = 'image',
|
|
26
|
+
ENUM = 'enum',
|
|
27
|
+
}
|
|
20
28
|
|
|
21
29
|
@DefaultScope(() => ({ where: { disabled: false } }))
|
|
22
30
|
@Table({
|
|
23
31
|
indexes: [
|
|
24
|
-
|
|
25
|
-
name: 'unique_name_model_type',
|
|
26
|
-
fields: ['model_type', 'entity_id', 'name'],
|
|
27
|
-
unique: true,
|
|
28
|
-
},
|
|
32
|
+
{ name: "unique_name_model_type", fields: ["name", "model_type", "entity_id"], unique: true },
|
|
29
33
|
],
|
|
30
34
|
timestamps: true,
|
|
31
|
-
|
|
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
|
-
})
|
|
35
|
+
})
|
|
39
36
|
class CustomFieldDefinition extends Model {
|
|
40
37
|
@PrimaryKey
|
|
41
38
|
@Column({
|
|
@@ -56,11 +53,6 @@ class CustomFieldDefinition extends Model {
|
|
|
56
53
|
})
|
|
57
54
|
displayName?: string; // Defaulted to name with beforeCreate hook
|
|
58
55
|
|
|
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
|
-
})
|
|
64
56
|
@Column({
|
|
65
57
|
type: DataType.STRING,
|
|
66
58
|
allowNull: false,
|