@autofleet/sadot 0.0.1-beta.9 → 0.0.2-beta
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/jest.config.d.ts +12 -0
- package/dist/src/api/index.d.ts +2 -0
- package/dist/{api → src/api}/index.js +0 -1
- package/dist/src/api/v1/definition/index.d.ts +2 -0
- package/dist/{api → src/api}/v1/definition/index.js +23 -27
- package/dist/src/api/v1/definition/validations.d.ts +2 -0
- package/dist/{api → src/api}/v1/definition/validations.js +10 -10
- package/dist/src/api/v1/errors.d.ts +2 -0
- package/dist/{api → src/api}/v1/errors.js +0 -1
- package/dist/src/api/v1/index.d.ts +2 -0
- package/dist/{api → src/api}/v1/index.js +1 -2
- package/dist/src/errors/index.d.ts +16 -0
- package/dist/src/errors/index.js +45 -0
- package/dist/src/events/index.d.ts +4 -0
- package/dist/{events → src/events}/index.js +21 -2
- package/dist/src/hooks/create.d.ts +9 -0
- package/dist/{hooks → src/hooks}/create.js +14 -17
- package/dist/src/hooks/enrich.d.ts +5 -0
- package/dist/src/hooks/enrich.js +118 -0
- package/dist/src/hooks/find.d.ts +1 -0
- package/dist/src/hooks/find.js +29 -0
- package/dist/src/hooks/index.d.ts +6 -0
- package/dist/{hooks → src/hooks}/index.js +2 -2
- package/dist/src/hooks/update.d.ts +9 -0
- package/dist/{hooks → src/hooks}/update.js +7 -22
- package/dist/src/hooks/workaround.d.ts +10 -0
- package/dist/{hooks → src/hooks}/workaround.js +3 -13
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +105 -0
- package/dist/src/models/CustomFieldDefinition.d.ts +23 -0
- package/dist/{models → src/models}/CustomFieldDefinition.js +22 -18
- package/dist/src/models/CustomFieldValue.d.ts +15 -0
- package/dist/{models → src/models}/CustomFieldValue.js +24 -40
- package/dist/src/models/index.d.ts +8 -0
- package/dist/src/models/index.js +87 -0
- package/dist/src/models/tests/AssociatedTestModel.d.ts +12 -0
- package/dist/{models → src/models}/tests/AssociatedTestModel.js +0 -1
- package/dist/src/models/tests/TestModel.d.ts +11 -0
- package/dist/{models → src/models}/tests/TestModel.js +0 -1
- package/dist/src/repository/definition.d.ts +17 -0
- package/dist/src/repository/definition.js +80 -0
- package/dist/src/repository/value.d.ts +24 -0
- package/dist/{repository → src/repository}/value.js +22 -30
- package/dist/src/tests/api/test-api.d.ts +2 -0
- package/dist/{tests → src/tests}/api/test-api.js +12 -22
- package/dist/src/tests/helpers/database-config.d.ts +15 -0
- package/dist/{tests → src/tests}/helpers/database-config.js +0 -1
- package/dist/src/tests/helpers/index.d.ts +2 -0
- package/dist/src/tests/helpers/index.js +18 -0
- package/dist/src/tests/mocks/definition.mock.d.ts +37 -0
- package/dist/{tests/mocks/index.js → src/tests/mocks/definition.mock.js} +20 -17
- package/dist/src/tests/mocks/events.mock.d.ts +3 -0
- package/dist/{tests → src/tests}/mocks/events.mock.js +0 -1
- package/dist/src/tests/mocks/testModel.d.ts +12 -0
- package/dist/{tests → src/tests}/mocks/testModel.js +4 -14
- package/dist/src/types/definition/index.d.ts +23 -0
- package/dist/{types → src/types}/definition/index.js +0 -1
- package/dist/src/types/index.d.ts +13 -0
- package/dist/{types → src/types}/index.js +0 -1
- package/dist/src/types/value/index.d.ts +15 -0
- package/dist/{types → src/types}/value/index.js +0 -1
- package/dist/src/utils/constants/index.d.ts +1 -0
- package/dist/src/utils/constants/index.js +5 -0
- package/dist/src/utils/db/index.d.ts +4 -0
- package/dist/{utils → src/utils}/db/index.js +8 -1
- package/dist/src/utils/logger/index.d.ts +2 -0
- package/dist/{utils → src/utils}/logger/index.js +2 -2
- package/dist/src/utils/validations/custom-fields.d.ts +2 -0
- package/dist/{utils → src/utils}/validations/custom-fields.js +0 -1
- package/dist/src/utils/validations/custom.d.ts +15 -0
- package/dist/src/utils/validations/custom.js +42 -0
- package/dist/src/utils/validations/index.d.ts +2 -0
- package/dist/{utils → src/utils}/validations/index.js +0 -1
- package/dist/src/utils/validations/type.d.ts +18 -0
- package/dist/src/utils/validations/type.js +50 -0
- package/dist/src/utils/validations/validators.d.ts +12 -0
- package/dist/src/utils/validations/validators.js +33 -0
- package/package.json +3 -1
- package/src/api/v1/definition/index.ts +15 -8
- package/src/api/v1/definition/validations.ts +11 -25
- package/src/api/v1/index.ts +1 -1
- package/src/errors/index.ts +42 -0
- package/src/events/index.ts +23 -1
- package/src/hooks/create.ts +7 -3
- package/src/hooks/enrich.ts +125 -0
- package/src/hooks/find.ts +2 -101
- package/src/hooks/index.ts +2 -1
- package/src/hooks/update.ts +2 -15
- package/src/index.ts +52 -17
- package/src/models/CustomFieldDefinition.ts +23 -16
- package/src/models/CustomFieldValue.ts +7 -7
- package/src/models/index.ts +72 -16
- package/src/repository/definition.ts +26 -33
- package/src/repository/value.ts +4 -2
- package/src/tests/mocks/{index.ts → definition.mock.ts} +2 -4
- package/src/types/index.ts +4 -6
- package/src/utils/constants/index.ts +2 -0
- package/src/utils/db/index.ts +7 -0
- package/src/utils/logger/index.ts +3 -1
- package/src/utils/validations/custom.ts +26 -44
- package/src/utils/validations/type.ts +23 -6
- package/src/utils/validations/validators.ts +34 -0
- package/tsconfig.json +9 -25
- package/dist/api/index.js.map +0 -1
- package/dist/api/v1/definition/index.js.map +0 -1
- package/dist/api/v1/definition/validations.js.map +0 -1
- package/dist/api/v1/errors.js.map +0 -1
- package/dist/api/v1/index.js.map +0 -1
- package/dist/events/index.js.map +0 -1
- package/dist/hooks/create.js.map +0 -1
- package/dist/hooks/find.js +0 -136
- package/dist/hooks/find.js.map +0 -1
- package/dist/hooks/index.js.map +0 -1
- package/dist/hooks/update.js.map +0 -1
- package/dist/hooks/workaround.js.map +0 -1
- package/dist/index.js +0 -81
- package/dist/index.js.map +0 -1
- package/dist/models/CustomFieldDefinition.js.map +0 -1
- package/dist/models/CustomFieldValue.js.map +0 -1
- package/dist/models/index.js +0 -50
- package/dist/models/index.js.map +0 -1
- package/dist/models/tests/AssociatedTestModel.js.map +0 -1
- package/dist/models/tests/TestModel.js.map +0 -1
- package/dist/repository/definition.js +0 -101
- package/dist/repository/definition.js.map +0 -1
- package/dist/repository/value.js.map +0 -1
- package/dist/tests/api/test-api.js.map +0 -1
- package/dist/tests/helpers/database-config.js.map +0 -1
- package/dist/tests/helpers/index.js +0 -28
- package/dist/tests/helpers/index.js.map +0 -1
- package/dist/tests/mocks/events.mock.js.map +0 -1
- package/dist/tests/mocks/index.js.map +0 -1
- package/dist/tests/mocks/testModel.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/dist/types/definition/index.js.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/types/value/index.js.map +0 -1
- package/dist/utils/db/index.js.map +0 -1
- package/dist/utils/logger/index.js.map +0 -1
- package/dist/utils/validations/custom-fields.js.map +0 -1
- package/dist/utils/validations/custom.js +0 -59
- package/dist/utils/validations/custom.js.map +0 -1
- package/dist/utils/validations/index.js.map +0 -1
- package/dist/utils/validations/type.js +0 -32
- package/dist/utils/validations/type.js.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CustomFieldDefinitionType = void 0;
|
|
7
|
+
const joi_1 = __importDefault(require("@hapi/joi"));
|
|
8
|
+
/**
|
|
9
|
+
* Supported custom field types
|
|
10
|
+
*/
|
|
11
|
+
// eslint-disable-next-line no-shadow
|
|
12
|
+
var CustomFieldDefinitionType;
|
|
13
|
+
(function (CustomFieldDefinitionType) {
|
|
14
|
+
CustomFieldDefinitionType["NUMBER"] = "number";
|
|
15
|
+
CustomFieldDefinitionType["BOOLEAN"] = "boolean";
|
|
16
|
+
CustomFieldDefinitionType["DATE"] = "date";
|
|
17
|
+
CustomFieldDefinitionType["DATETIME"] = "datetime";
|
|
18
|
+
CustomFieldDefinitionType["TEXT"] = "text";
|
|
19
|
+
CustomFieldDefinitionType["IMAGE"] = "image";
|
|
20
|
+
CustomFieldDefinitionType["SELECT"] = "select";
|
|
21
|
+
})(CustomFieldDefinitionType = exports.CustomFieldDefinitionType || (exports.CustomFieldDefinitionType = {}));
|
|
22
|
+
/**
|
|
23
|
+
* Validate that the given value is really of type "valueType"
|
|
24
|
+
* TODO: verify that required field not set to null
|
|
25
|
+
*/
|
|
26
|
+
const validateValueType = (value, valueType) => {
|
|
27
|
+
if (value === null) {
|
|
28
|
+
// Null is always allowed
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
switch (valueType) {
|
|
32
|
+
case CustomFieldDefinitionType.TEXT:
|
|
33
|
+
return typeof value === 'string';
|
|
34
|
+
case CustomFieldDefinitionType.NUMBER:
|
|
35
|
+
return typeof value === 'number';
|
|
36
|
+
case CustomFieldDefinitionType.BOOLEAN:
|
|
37
|
+
return typeof value === 'boolean';
|
|
38
|
+
case CustomFieldDefinitionType.DATE:
|
|
39
|
+
case CustomFieldDefinitionType.DATETIME:
|
|
40
|
+
return !joi_1.default.date().validate(value).error;
|
|
41
|
+
case CustomFieldDefinitionType.SELECT:
|
|
42
|
+
return true; // custom validation
|
|
43
|
+
case CustomFieldDefinitionType.IMAGE:
|
|
44
|
+
return !joi_1.default.array().min(1).unique().items(joi_1.default.string().uri())
|
|
45
|
+
.validate(value).error;
|
|
46
|
+
default:
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.default = validateValueType;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare enum CustomValidations {
|
|
2
|
+
SELECT = "select",
|
|
3
|
+
RANGE = "between"
|
|
4
|
+
}
|
|
5
|
+
type Validator = (value: any, validation: any) => boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Validators for custom fields
|
|
8
|
+
*/
|
|
9
|
+
declare const validators: {
|
|
10
|
+
[key: string]: Validator;
|
|
11
|
+
};
|
|
12
|
+
export default validators;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CustomValidations = void 0;
|
|
4
|
+
// eslint-disable-next-line no-shadow
|
|
5
|
+
var CustomValidations;
|
|
6
|
+
(function (CustomValidations) {
|
|
7
|
+
CustomValidations["SELECT"] = "select";
|
|
8
|
+
CustomValidations["RANGE"] = "between";
|
|
9
|
+
})(CustomValidations = exports.CustomValidations || (exports.CustomValidations = {}));
|
|
10
|
+
/**
|
|
11
|
+
* Validate {@link CustomValidations.ENUM Enum}
|
|
12
|
+
*/
|
|
13
|
+
const validateEnum = (value, enumValues) => (Array.isArray(enumValues)
|
|
14
|
+
&& enumValues.length > 0
|
|
15
|
+
&& enumValues.includes(value));
|
|
16
|
+
/**
|
|
17
|
+
* Validate {@link CustomValidations.RANGE Range}
|
|
18
|
+
*/
|
|
19
|
+
const validateRange = (value, range) => {
|
|
20
|
+
const [min, max] = range;
|
|
21
|
+
if (min === undefined || max === undefined) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return value >= range.min && value <= range.max;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Validators for custom fields
|
|
28
|
+
*/
|
|
29
|
+
const validators = {
|
|
30
|
+
[CustomValidations.SELECT]: validateEnum,
|
|
31
|
+
[CustomValidations.RANGE]: validateRange,
|
|
32
|
+
};
|
|
33
|
+
exports.default = validators;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sadot",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2-beta",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/express": "^4.17.17",
|
|
28
28
|
"@types/jest": "^27.0.9",
|
|
29
|
+
"@types/node": "^20.5.6",
|
|
30
|
+
"@types/uuid": "^9.0.2",
|
|
29
31
|
"@typescript-eslint/eslint-plugin": "^4.8.1",
|
|
30
32
|
"@typescript-eslint/parser": "^4.33.0",
|
|
31
33
|
"eslint": "^7.13.0",
|
|
@@ -20,12 +20,12 @@ router.post('/', async (req: Request, res: Response) => {
|
|
|
20
20
|
const modelType = toPascalCase(modelName);
|
|
21
21
|
try {
|
|
22
22
|
const validatedPayload: CreateCustomFieldDefinition = await
|
|
23
|
-
validateCustomFieldDefinitionCreation(
|
|
24
|
-
|
|
23
|
+
validateCustomFieldDefinitionCreation(req.body);
|
|
24
|
+
|
|
25
|
+
const customFieldDefinition = await DefinitionRepo.create({
|
|
26
|
+
...validatedPayload,
|
|
25
27
|
modelType,
|
|
26
28
|
});
|
|
27
|
-
|
|
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,11 +57,18 @@ 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
|
+
|
|
60
62
|
const modelType = toPascalCase(modelName);
|
|
61
63
|
try {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
const where: any = { modelType };
|
|
65
|
+
if (entityIds?.length > 0) {
|
|
66
|
+
where.entityId = entityIds;
|
|
67
|
+
}
|
|
68
|
+
const customFieldDefinitions = await DefinitionRepo.findAll(
|
|
69
|
+
{ ...where },
|
|
70
|
+
{ withDisabled: true },
|
|
71
|
+
);
|
|
65
72
|
return res.json(customFieldDefinitions);
|
|
66
73
|
} catch (err) {
|
|
67
74
|
logger.error('Failed to fetch custom field definitions', err);
|
|
@@ -90,7 +97,7 @@ router.patch('/:customFieldDefinitionId', async (req, res) => {
|
|
|
90
97
|
|
|
91
98
|
const updatedCustomFieldDefinition = await DefinitionRepo.update(
|
|
92
99
|
customFieldDefinitionId,
|
|
93
|
-
validatedPayload,
|
|
100
|
+
{ ...validatedPayload, modelType },
|
|
94
101
|
);
|
|
95
102
|
|
|
96
103
|
return res.status(200).json(updatedCustomFieldDefinition);
|
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
import Joi from '@hapi/joi';
|
|
2
|
-
import { CustomFieldDefinitionType } from '../../../
|
|
2
|
+
import { CustomFieldDefinitionType } from '../../../utils/validations/type';
|
|
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
|
+
});
|
|
3
9
|
|
|
4
10
|
const CustomFieldDefinitionCreationSchema = Joi.object({
|
|
5
11
|
name: Joi.string().required(),
|
|
6
12
|
displayName: Joi.string().required(),
|
|
7
|
-
validation:
|
|
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(),
|
|
13
|
+
validation: ValidationSchema,
|
|
14
|
+
fieldType: Joi.string().valid(...Object.values(CustomFieldDefinitionType)).required(),
|
|
17
15
|
entityId: Joi.string().guid().required(),
|
|
18
16
|
entityType: Joi.string().required(),
|
|
19
|
-
modelType: Joi.string().required(),
|
|
20
17
|
description: Joi.string(),
|
|
21
18
|
required: Joi.boolean(),
|
|
22
19
|
disabled: Joi.boolean(),
|
|
@@ -24,19 +21,8 @@ const CustomFieldDefinitionCreationSchema = Joi.object({
|
|
|
24
21
|
|
|
25
22
|
const CustomFieldDefinitionUpdateSchema = Joi.object({
|
|
26
23
|
displayName: Joi.string(),
|
|
27
|
-
validation:
|
|
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(),
|
|
24
|
+
validation: ValidationSchema,
|
|
25
|
+
fieldType: Joi.string().valid(...Object.values(CustomFieldDefinitionType)),
|
|
40
26
|
description: Joi.string(),
|
|
41
27
|
required: Joi.boolean(),
|
|
42
28
|
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/events/index.ts
CHANGED
|
@@ -3,6 +3,21 @@ 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
|
+
|
|
6
21
|
const modelTableMapping = {
|
|
7
22
|
CustomFieldDefinition: {
|
|
8
23
|
tableName: 'dim_custom_field_definition',
|
|
@@ -17,10 +32,17 @@ const modelTableMapping = {
|
|
|
17
32
|
export const sendDimEvent = (instance): void => {
|
|
18
33
|
const mapping = modelTableMapping[instance.constructor.name];
|
|
19
34
|
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
|
+
|
|
20
42
|
events.sendObject(
|
|
21
43
|
mapping.tableName,
|
|
22
44
|
mapping.eventVersion,
|
|
23
|
-
|
|
45
|
+
objectToSend,
|
|
24
46
|
);
|
|
25
47
|
}
|
|
26
48
|
};
|
package/src/hooks/create.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import logger from '../utils/logger';
|
|
1
2
|
import * as ValueRepo from '../repository/value';
|
|
2
3
|
import * as DefinitionRepo from '../repository/definition';
|
|
4
|
+
import { MissingRequiredCustomFieldError } from '../errors';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* A hook to create the custom fields when updating a model (more then one instance).
|
|
@@ -17,6 +19,7 @@ export const beforeCreate = (scopeAttributes: string[]) => async (
|
|
|
17
19
|
instance,
|
|
18
20
|
options,
|
|
19
21
|
): Promise<void> => {
|
|
22
|
+
logger.debug('sadot - before create hook');
|
|
20
23
|
const { fields } = options;
|
|
21
24
|
const modelType = instance.constructor.name;
|
|
22
25
|
const identifiers = scopeAttributes.map((attribute) => instance[attribute]);
|
|
@@ -28,8 +31,9 @@ export const beforeCreate = (scopeAttributes: string[]) => async (
|
|
|
28
31
|
const { customFields } = instance;
|
|
29
32
|
if (customFieldsIdx > -1 && customFields) {
|
|
30
33
|
const fieldsNames = Object.keys(customFields);
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
35
|
+
if (missingFields?.length > 0) {
|
|
36
|
+
throw new MissingRequiredCustomFieldError(missingFields);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
await ValueRepo.updateValues(
|
|
@@ -42,6 +46,6 @@ export const beforeCreate = (scopeAttributes: string[]) => async (
|
|
|
42
46
|
// eslint-disable-next-line no-param-reassign
|
|
43
47
|
fields.splice(customFieldsIdx, 1);
|
|
44
48
|
} else if (requiredFieldsNames?.length > 0) {
|
|
45
|
-
throw new
|
|
49
|
+
throw new MissingRequiredCustomFieldError(requiredFieldsNames);
|
|
46
50
|
}
|
|
47
51
|
};
|
|
@@ -0,0 +1,125 @@
|
|
|
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 CustomFieldDefinition from '../models/CustomFieldDefinition';
|
|
6
|
+
import { SerializedCustomFields } from '../types/definition';
|
|
7
|
+
import logger from '../utils/logger';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Serialize custom fields value into the format of {[name] -> [fieldData]}
|
|
11
|
+
*/
|
|
12
|
+
const serializeCustomFields = (
|
|
13
|
+
customFieldValues: CustomFieldValue[],
|
|
14
|
+
customFieldDefinitionsHash: Record<string, CustomFieldDefinition>,
|
|
15
|
+
): SerializedCustomFields => {
|
|
16
|
+
const customFields = customFieldValues.reduce((acc, cfv) => ({
|
|
17
|
+
...acc,
|
|
18
|
+
...(
|
|
19
|
+
customFieldDefinitionsHash[cfv.customFieldDefinitionId]
|
|
20
|
+
&& { [customFieldDefinitionsHash[cfv.customFieldDefinitionId].name]: cfv.value }
|
|
21
|
+
),
|
|
22
|
+
}), {});
|
|
23
|
+
return customFields;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* A hook to attach the custom fields when fetching a model instances.
|
|
27
|
+
*/
|
|
28
|
+
const enrichResults = (modelType: string, scopeAttributes: string[]) => async (
|
|
29
|
+
instancesOrInstance: any | any[],
|
|
30
|
+
options,
|
|
31
|
+
): Promise<void> => {
|
|
32
|
+
if (
|
|
33
|
+
options.originalAttributes?.length > 0
|
|
34
|
+
&& !options.originalAttributes?.includes?.('customFields')
|
|
35
|
+
) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const primaryKey = 'id';
|
|
40
|
+
let instances = Array.isArray(instancesOrInstance)
|
|
41
|
+
? instancesOrInstance
|
|
42
|
+
: [instancesOrInstance];
|
|
43
|
+
|
|
44
|
+
instances = instances.filter(Boolean);
|
|
45
|
+
|
|
46
|
+
const identifiers = instances.map((instance) =>
|
|
47
|
+
scopeAttributes
|
|
48
|
+
.map((attr) => instance[attr])).flat();
|
|
49
|
+
|
|
50
|
+
const uniqueIdentifiers = [...new Set(identifiers)].filter(Boolean);
|
|
51
|
+
const identifierCustomFieldDefinitionsMapping = uniqueIdentifiers.reduce((map, identifier) => ({
|
|
52
|
+
...map,
|
|
53
|
+
[identifier]: [],
|
|
54
|
+
}), {});
|
|
55
|
+
const customFieldDefinitions = await DefinitionRepo.findByEntityIds(
|
|
56
|
+
modelType,
|
|
57
|
+
uniqueIdentifiers,
|
|
58
|
+
{ transaction: options.transaction },
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const definitionsMap = customFieldDefinitions.reduce((map, definition) => ({
|
|
62
|
+
...map,
|
|
63
|
+
[definition.id]: definition,
|
|
64
|
+
}), {});
|
|
65
|
+
|
|
66
|
+
customFieldDefinitions.forEach((cfd) => {
|
|
67
|
+
identifierCustomFieldDefinitionsMapping[cfd.entityId].push(cfd);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Get the values per instates ids:
|
|
71
|
+
const instancesIds = instances.map((i) => i[primaryKey]);
|
|
72
|
+
|
|
73
|
+
const customFieldValues = await ValueRepo.findValuesByModelIds(
|
|
74
|
+
instancesIds,
|
|
75
|
+
{ transaction: options.transaction },
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Group fields by modelId
|
|
79
|
+
const valuesGroupByInstance: {
|
|
80
|
+
[modelId: string]: CustomFieldValue[];
|
|
81
|
+
} = customFieldValues.reduce((acc, v) => {
|
|
82
|
+
const { modelId } = v;
|
|
83
|
+
if (!acc[modelId]) {
|
|
84
|
+
acc[modelId] = [];
|
|
85
|
+
}
|
|
86
|
+
acc[modelId].push(v);
|
|
87
|
+
return acc;
|
|
88
|
+
}, {});
|
|
89
|
+
|
|
90
|
+
// Attach custom fields to the instances
|
|
91
|
+
instances.forEach((instance) => {
|
|
92
|
+
const customFields = {};
|
|
93
|
+
const { id } = instance;
|
|
94
|
+
const instanceValues = valuesGroupByInstance[id];
|
|
95
|
+
if (instanceValues) {
|
|
96
|
+
const serializedCustomFields = serializeCustomFields(instanceValues, definitionsMap);
|
|
97
|
+
Object.assign(customFields, serializedCustomFields);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
scopeAttributes.forEach((attribute) => {
|
|
101
|
+
const identifier = instance[attribute];
|
|
102
|
+
const entityCustomFieldDefinitions = identifierCustomFieldDefinitionsMapping[identifier];
|
|
103
|
+
if (entityCustomFieldDefinitions?.length > 0) {
|
|
104
|
+
entityCustomFieldDefinitions.forEach((customFieldDefinition) => {
|
|
105
|
+
if (customFields[customFieldDefinition.name] === undefined) {
|
|
106
|
+
customFields[customFieldDefinition.name] = null;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
if (customFields && Object.keys(customFields).length > 0) {
|
|
112
|
+
logger.info('sadot - enrichResults - customFields', customFields);
|
|
113
|
+
instance.customFields = customFields;
|
|
114
|
+
} else {
|
|
115
|
+
logger.info('sadot - enrichResults - no customFields');
|
|
116
|
+
}
|
|
117
|
+
options.attributesToRemove?.forEach?.((attribute) => {
|
|
118
|
+
delete instance.dataValues?.[attribute];
|
|
119
|
+
// if raw:
|
|
120
|
+
delete instance?.[attribute];
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export default enrichResults;
|
package/src/hooks/find.ts
CHANGED
|
@@ -1,21 +1,6 @@
|
|
|
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
2
|
import logger from '../utils/logger';
|
|
7
3
|
|
|
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
|
-
|
|
19
4
|
const doScopeAttributesMissing = (
|
|
20
5
|
scopeAttributes: string[],
|
|
21
6
|
queryAttributes: (string | string[])[],
|
|
@@ -28,99 +13,15 @@ const doScopeAttributesMissing = (
|
|
|
28
13
|
return attributes;
|
|
29
14
|
};
|
|
30
15
|
|
|
16
|
+
// eslint-disable-next-line import/prefer-default-export
|
|
31
17
|
export const beforeFind = (scopeAttributes: string[]) => (options): void => {
|
|
32
18
|
const { attributes: queryAttributes } = options;
|
|
33
19
|
if (queryAttributes?.includes('customFields')) {
|
|
34
20
|
const missingScopeAttributes = doScopeAttributesMissing(scopeAttributes, queryAttributes);
|
|
21
|
+
logger.debug('sadot - before find hook');
|
|
35
22
|
if (missingScopeAttributes?.length > 0) {
|
|
36
23
|
queryAttributes.push(...missingScopeAttributes);
|
|
37
24
|
options.attributesToRemove = missingScopeAttributes;
|
|
38
25
|
}
|
|
39
26
|
}
|
|
40
27
|
};
|
|
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,5 @@
|
|
|
1
|
+
import logger from '../utils/logger';
|
|
1
2
|
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).
|
|
@@ -18,25 +18,14 @@ export const beforeUpdate = (scopeAttributes: string[]) => async (
|
|
|
18
18
|
instance,
|
|
19
19
|
options,
|
|
20
20
|
): Promise<void> => {
|
|
21
|
+
logger.debug('sadot - before update hook');
|
|
21
22
|
const { fields } = options;
|
|
22
23
|
const modelType = instance.constructor.name;
|
|
23
24
|
const identifiers = scopeAttributes.map((attribute) => instance[attribute]);
|
|
24
25
|
|
|
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
26
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
34
27
|
if (customFieldsIdx > -1) {
|
|
35
28
|
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
29
|
await ValueRepo.updateValues(
|
|
41
30
|
modelType,
|
|
42
31
|
instance.id,
|
|
@@ -46,7 +35,5 @@ export const beforeUpdate = (scopeAttributes: string[]) => async (
|
|
|
46
35
|
);
|
|
47
36
|
// eslint-disable-next-line no-param-reassign
|
|
48
37
|
fields.splice(customFieldsIdx, 1);
|
|
49
|
-
} else if (requiredNullFieldsNames?.length > 0) {
|
|
50
|
-
throw new Error('some fields are required');
|
|
51
38
|
}
|
|
52
39
|
};
|