@autofleet/sadot 0.8.2-beta-71b4ad14.0 → 0.8.3
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/validations.js +3 -3
- package/dist/errors/index.d.ts +5 -1
- package/dist/errors/index.js +17 -4
- package/dist/events/index.js +7 -9
- package/dist/models/CustomFieldDefinition.js +2 -2
- package/dist/models/CustomFieldValue.d.ts +3 -2
- package/dist/models/CustomFieldValue.js +22 -14
- package/dist/tests/mocks/definition.mock.d.ts +3 -2
- package/dist/tests/mocks/definition.mock.js +9 -1
- package/dist/utils/validations/index.d.ts +2 -1
- package/dist/utils/validations/index.js +4 -7
- package/dist/utils/validations/type.d.ts +2 -1
- package/dist/utils/validations/validators/index.js +9 -9
- package/dist/utils/validations/validators/select.validator.js +5 -2
- package/dist/utils/validations/validators/status.validator.js +8 -2
- package/package.json +2 -4
- package/src/api/v1/definition/validations.ts +3 -3
- package/src/errors/index.ts +20 -3
- package/src/events/index.ts +8 -10
- package/src/models/CustomFieldDefinition.ts +2 -2
- package/src/models/CustomFieldValue.ts +25 -17
- package/src/tests/mocks/definition.mock.ts +8 -0
- package/src/utils/validations/index.ts +3 -6
- package/src/utils/validations/type.ts +2 -1
- package/src/utils/validations/validators/index.ts +9 -9
- package/src/utils/validations/validators/select.validator.ts +3 -2
- package/src/utils/validations/validators/status.validator.ts +6 -2
- package/tsconfig.build.json +1 -1
|
@@ -40,11 +40,11 @@ const DefaultValueSchema = joi_1.default.when('fieldType', {
|
|
|
40
40
|
{ is: constants_1.CustomFieldDefinitionType.BOOLEAN, then: joi_1.default.boolean().allow(null) },
|
|
41
41
|
{ is: constants_1.CustomFieldDefinitionType.DATE, then: joi_1.default.date().allow(null) },
|
|
42
42
|
{ is: constants_1.CustomFieldDefinitionType.DATETIME, then: joi_1.default.date().allow(null) },
|
|
43
|
-
{ is: constants_1.CustomFieldDefinitionType.FILE, then: FileValidationSchema },
|
|
44
|
-
{ is: constants_1.CustomFieldDefinitionType.IMAGE, then: joi_1.default.string().uri().allow(null) },
|
|
43
|
+
{ is: constants_1.CustomFieldDefinitionType.FILE, then: joi_1.default.array().items(FileValidationSchema).allow(null) },
|
|
44
|
+
{ is: constants_1.CustomFieldDefinitionType.IMAGE, then: joi_1.default.array().items(joi_1.default.string().uri()).allow(null) },
|
|
45
45
|
{ is: constants_1.CustomFieldDefinitionType.NUMBER, then: joi_1.default.number().allow(null) },
|
|
46
46
|
{ is: constants_1.CustomFieldDefinitionType.SELECT, then: joi_1.default.string().allow(null) },
|
|
47
|
-
{ is: constants_1.CustomFieldDefinitionType.STATUS, then:
|
|
47
|
+
{ is: constants_1.CustomFieldDefinitionType.STATUS, then: joi_1.default.string().allow(null) },
|
|
48
48
|
{ is: constants_1.CustomFieldDefinitionType.TEXT, then: joi_1.default.string().allow(null) },
|
|
49
49
|
],
|
|
50
50
|
});
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BadRequest } from '@autofleet/errors';
|
|
2
|
+
import type { ValidationError } from 'joi';
|
|
2
3
|
export declare class MissingRequiredCustomFieldError extends BadRequest {
|
|
3
4
|
constructor(missingFields: string[]);
|
|
4
5
|
}
|
|
@@ -8,8 +9,11 @@ export declare class UnsupportedCustomFieldTypeError extends BadRequest {
|
|
|
8
9
|
export declare class UnsupportedCustomValidationError extends BadRequest {
|
|
9
10
|
constructor(fieldType: string);
|
|
10
11
|
}
|
|
12
|
+
export declare class InvalidFieldTypeError extends BadRequest {
|
|
13
|
+
constructor(fieldType: string);
|
|
14
|
+
}
|
|
11
15
|
export declare class InvalidValueError extends BadRequest {
|
|
12
|
-
constructor(value: any,
|
|
16
|
+
constructor(value: any, fieldDefinitionName: string, joiValidationError: ValidationError);
|
|
13
17
|
}
|
|
14
18
|
export declare class MissingDefinitionError extends BadRequest {
|
|
15
19
|
constructor(fieldNames: string[]);
|
package/dist/errors/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MissingDefinitionError = exports.InvalidValueError = exports.UnsupportedCustomValidationError = exports.UnsupportedCustomFieldTypeError = exports.MissingRequiredCustomFieldError = void 0;
|
|
3
|
+
exports.MissingDefinitionError = exports.InvalidValueError = exports.InvalidFieldTypeError = exports.UnsupportedCustomValidationError = exports.UnsupportedCustomFieldTypeError = exports.MissingRequiredCustomFieldError = void 0;
|
|
4
4
|
/* eslint-disable max-classes-per-file */
|
|
5
5
|
const errors_1 = require("@autofleet/errors");
|
|
6
6
|
class MissingRequiredCustomFieldError extends errors_1.BadRequest {
|
|
@@ -27,11 +27,24 @@ class UnsupportedCustomValidationError extends errors_1.BadRequest {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
exports.UnsupportedCustomValidationError = UnsupportedCustomValidationError;
|
|
30
|
+
class InvalidFieldTypeError extends errors_1.BadRequest {
|
|
31
|
+
constructor(fieldType) {
|
|
32
|
+
const err = new Error(`Invalid field type ${fieldType}`);
|
|
33
|
+
super([err], null, null);
|
|
34
|
+
this.message = 'INVALID_FIELD_TYPE';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.InvalidFieldTypeError = InvalidFieldTypeError;
|
|
30
38
|
class InvalidValueError extends errors_1.BadRequest {
|
|
31
|
-
constructor(value,
|
|
32
|
-
const
|
|
39
|
+
constructor(value, fieldDefinitionName, joiValidationError) {
|
|
40
|
+
const formattedErrorMessage = joiValidationError.message
|
|
41
|
+
.replace(/"/g, '')
|
|
42
|
+
.replace('value', `'${fieldDefinitionName}'`);
|
|
43
|
+
const formattedValue = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
44
|
+
const invalidValueMessage = `Invalid Value on field '${fieldDefinitionName}'. ${formattedErrorMessage}. received: '${formattedValue}'`;
|
|
45
|
+
const err = new Error(invalidValueMessage);
|
|
33
46
|
super([err], null, null);
|
|
34
|
-
this.message =
|
|
47
|
+
this.message = invalidValueMessage;
|
|
35
48
|
}
|
|
36
49
|
}
|
|
37
50
|
exports.InvalidValueError = InvalidValueError;
|
package/dist/events/index.js
CHANGED
|
@@ -7,18 +7,16 @@ exports.sendDimEvent = void 0;
|
|
|
7
7
|
const events_1 = __importDefault(require("@autofleet/events"));
|
|
8
8
|
const logger_1 = __importDefault(require("../utils/logger"));
|
|
9
9
|
const events = new events_1.default({ logger: logger_1.default });
|
|
10
|
-
const KEYS_TO_CONVERT = ['value', 'defaultValue'];
|
|
10
|
+
const KEYS_TO_CONVERT = ['value', 'defaultValue', 'blockEditingFromUI'];
|
|
11
11
|
const stringifyBooleans = (savedObject, keysToConvert) => {
|
|
12
|
-
|
|
12
|
+
const savedObjectKeySet = new Set(Object.keys(savedObject));
|
|
13
|
+
if (keysToConvert.every((key) => !savedObjectKeySet.has(key))) {
|
|
13
14
|
return savedObject;
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
return objectToReturn;
|
|
16
|
+
return {
|
|
17
|
+
...savedObject,
|
|
18
|
+
...Object.fromEntries(keysToConvert.map((key) => [key, typeof savedObject[key] === 'boolean' ? savedObject[key].toString() : savedObject[key]])),
|
|
19
|
+
};
|
|
22
20
|
};
|
|
23
21
|
const modelTableMapping = {
|
|
24
22
|
CustomFieldDefinition: {
|
|
@@ -28,8 +28,8 @@ let CustomFieldDefinition = class CustomFieldDefinition extends sequelize_typesc
|
|
|
28
28
|
}
|
|
29
29
|
if (![null, undefined].includes(instance.defaultValue)) {
|
|
30
30
|
const isValid = (0, validations_1.validateValue)(instance.defaultValue, instance.fieldType, instance.validation);
|
|
31
|
-
if (
|
|
32
|
-
throw new errors_1.InvalidValueError(instance.defaultValue, instance.
|
|
31
|
+
if (isValid.error) {
|
|
32
|
+
throw new errors_1.InvalidValueError(instance.defaultValue, instance.name, isValid.error);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -8,8 +8,9 @@ declare class CustomFieldValue extends Model {
|
|
|
8
8
|
updatedAt?: Date;
|
|
9
9
|
deletedAt?: Date;
|
|
10
10
|
customFieldDefinition: CustomFieldDefinition;
|
|
11
|
-
static
|
|
12
|
-
static
|
|
11
|
+
private static validateValueAgainstDefinition;
|
|
12
|
+
static validateCustomFieldValues(instances: CustomFieldValue[]): Promise<void>;
|
|
13
|
+
static validateCustomFieldValue(instance: CustomFieldValue): Promise<void>;
|
|
13
14
|
static afterSaveHandler(instance: CustomFieldValue, options: any): void;
|
|
14
15
|
}
|
|
15
16
|
export default CustomFieldValue;
|
|
@@ -39,7 +39,21 @@ const validations_1 = require("../utils/validations");
|
|
|
39
39
|
const CustomFieldDefinitionRepo = __importStar(require("../repository/definition"));
|
|
40
40
|
const errors_1 = require("../errors");
|
|
41
41
|
let CustomFieldValue = class CustomFieldValue extends sequelize_typescript_1.Model {
|
|
42
|
-
static
|
|
42
|
+
static validateValueAgainstDefinition(instance, definition) {
|
|
43
|
+
const { validation, fieldType, name } = definition;
|
|
44
|
+
const isValidFieldType = (0, validations_1.validateFieldType)(fieldType);
|
|
45
|
+
if (!isValidFieldType) {
|
|
46
|
+
throw new errors_1.InvalidFieldTypeError(fieldType);
|
|
47
|
+
}
|
|
48
|
+
// Always allow null values
|
|
49
|
+
if (instance.value === null)
|
|
50
|
+
return;
|
|
51
|
+
const validateValueResponse = (0, validations_1.validateValue)(instance.value, fieldType, validation);
|
|
52
|
+
if (validateValueResponse.error) {
|
|
53
|
+
throw new errors_1.InvalidValueError(instance.value, name, validateValueResponse.error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
static async validateCustomFieldValues(instances) {
|
|
43
57
|
const ids = instances.map((instance) => instance.customFieldDefinitionId);
|
|
44
58
|
const uniqueIds = [...new Set(ids)];
|
|
45
59
|
const definitions = await CustomFieldDefinitionRepo.findByIds(uniqueIds, { withDisabled: true });
|
|
@@ -47,23 +61,17 @@ let CustomFieldValue = class CustomFieldValue extends sequelize_typescript_1.Mod
|
|
|
47
61
|
throw new Error('Definitions not found');
|
|
48
62
|
}
|
|
49
63
|
instances.forEach((instance) => {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!isValid) {
|
|
54
|
-
throw new errors_1.InvalidValueError(instance.value, fieldType);
|
|
64
|
+
const definition = definitions.find((d) => d.id === instance.customFieldDefinitionId);
|
|
65
|
+
if (definition) {
|
|
66
|
+
this.validateValueAgainstDefinition(instance, definition);
|
|
55
67
|
}
|
|
56
68
|
});
|
|
57
69
|
}
|
|
58
|
-
static async
|
|
70
|
+
static async validateCustomFieldValue(instance) {
|
|
59
71
|
const { customFieldDefinitionId } = instance;
|
|
60
72
|
// eslint-disable-next-line max-len
|
|
61
73
|
const cfd = await CustomFieldDefinitionRepo.findById(customFieldDefinitionId, { withDisabled: true });
|
|
62
|
-
|
|
63
|
-
const isValid = (0, validations_1.validateValue)(instance.value, fieldType, validation);
|
|
64
|
-
if (!isValid) {
|
|
65
|
-
throw new errors_1.InvalidValueError(instance.value, fieldType);
|
|
66
|
-
}
|
|
74
|
+
this.validateValueAgainstDefinition(instance, cfd);
|
|
67
75
|
}
|
|
68
76
|
static afterSaveHandler(instance, options) {
|
|
69
77
|
if (options.transaction) {
|
|
@@ -120,7 +128,7 @@ __decorate([
|
|
|
120
128
|
__metadata("design:type", Function),
|
|
121
129
|
__metadata("design:paramtypes", [Array]),
|
|
122
130
|
__metadata("design:returntype", Promise)
|
|
123
|
-
], CustomFieldValue, "
|
|
131
|
+
], CustomFieldValue, "validateCustomFieldValues", null);
|
|
124
132
|
__decorate([
|
|
125
133
|
sequelize_typescript_1.BeforeUpdate,
|
|
126
134
|
sequelize_typescript_1.BeforeCreate,
|
|
@@ -128,7 +136,7 @@ __decorate([
|
|
|
128
136
|
__metadata("design:type", Function),
|
|
129
137
|
__metadata("design:paramtypes", [CustomFieldValue]),
|
|
130
138
|
__metadata("design:returntype", Promise)
|
|
131
|
-
], CustomFieldValue, "
|
|
139
|
+
], CustomFieldValue, "validateCustomFieldValue", null);
|
|
132
140
|
__decorate([
|
|
133
141
|
sequelize_typescript_1.AfterUpsert,
|
|
134
142
|
__metadata("design:type", Function),
|
|
@@ -15,13 +15,13 @@ export declare const coolFieldDefinition2: {
|
|
|
15
15
|
updatedAt?: Date;
|
|
16
16
|
deletedAt?: Date;
|
|
17
17
|
defaultValue?: any;
|
|
18
|
+
blockEditingFromUI?: boolean;
|
|
18
19
|
displayName?: string;
|
|
19
20
|
validation?: any;
|
|
20
21
|
fieldType: string;
|
|
21
22
|
entityId: string;
|
|
22
23
|
entityType: string;
|
|
23
24
|
modelType: string;
|
|
24
|
-
blockEditingFromUI?: boolean;
|
|
25
25
|
};
|
|
26
26
|
export declare const coolFieldDefinition3: {
|
|
27
27
|
name: string;
|
|
@@ -32,16 +32,17 @@ export declare const coolFieldDefinition3: {
|
|
|
32
32
|
updatedAt?: Date;
|
|
33
33
|
deletedAt?: Date;
|
|
34
34
|
defaultValue?: any;
|
|
35
|
+
blockEditingFromUI?: boolean;
|
|
35
36
|
displayName?: string;
|
|
36
37
|
validation?: any;
|
|
37
38
|
fieldType: string;
|
|
38
39
|
entityId: string;
|
|
39
40
|
entityType: string;
|
|
40
41
|
modelType: string;
|
|
41
|
-
blockEditingFromUI?: boolean;
|
|
42
42
|
};
|
|
43
43
|
export declare const booleanField: (modelType: string) => CreateCustomFieldDefinition;
|
|
44
44
|
export declare const selectField: (modelType: string, options: any) => CreateCustomFieldDefinition;
|
|
45
45
|
export declare const statusField: (modelType: string, options: any) => CreateCustomFieldDefinition;
|
|
46
|
+
export declare const fileField: (modelType: string) => CreateCustomFieldDefinition;
|
|
46
47
|
export declare const createDefinition: (defaults: Partial<CustomFieldDefinitionDTO>) => CreateCustomFieldDefinition;
|
|
47
48
|
export declare const createDefinitions: (defaults: Partial<CustomFieldDefinitionDTO>, length?: number) => CreateCustomFieldDefinition[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createDefinitions = exports.createDefinition = exports.statusField = exports.selectField = exports.booleanField = exports.coolFieldDefinition3 = exports.coolFieldDefinition2 = exports.coolFieldDefinition = exports.contextAwareFieldDefinition = void 0;
|
|
3
|
+
exports.createDefinitions = exports.createDefinition = exports.fileField = exports.statusField = exports.selectField = exports.booleanField = exports.coolFieldDefinition3 = exports.coolFieldDefinition2 = exports.coolFieldDefinition = exports.contextAwareFieldDefinition = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
exports.contextAwareFieldDefinition = {
|
|
6
6
|
name: 'cool field',
|
|
@@ -49,6 +49,14 @@ const statusField = (modelType, options) => ({
|
|
|
49
49
|
entityType: 'fleetId',
|
|
50
50
|
});
|
|
51
51
|
exports.statusField = statusField;
|
|
52
|
+
const fileField = (modelType) => ({
|
|
53
|
+
name: 'file',
|
|
54
|
+
modelType,
|
|
55
|
+
fieldType: 'file',
|
|
56
|
+
entityId: (0, node_crypto_1.randomUUID)(),
|
|
57
|
+
entityType: 'fleetId',
|
|
58
|
+
});
|
|
59
|
+
exports.fileField = fileField;
|
|
52
60
|
// eslint-disable-next-line max-len
|
|
53
61
|
const createDefinition = (defaults) => ({
|
|
54
62
|
name: defaults?.name || `def_${(0, node_crypto_1.randomUUID)()}`,
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { CustomFieldDefinitionType } from '../constants';
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const validateFieldType: (type: CustomFieldDefinitionType) => boolean;
|
|
3
|
+
export declare const validateValue: (value: unknown, valueType: CustomFieldDefinitionType, validation?: unknown) => import("joi").ValidationResult;
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateValue = void 0;
|
|
3
|
+
exports.validateValue = exports.validateFieldType = void 0;
|
|
4
4
|
const validators_1 = require("./validators");
|
|
5
|
+
const validateFieldType = (type) => Object.keys(validators_1.validators).includes(type);
|
|
6
|
+
exports.validateFieldType = validateFieldType;
|
|
5
7
|
const validateValue = (value, valueType, validation) => {
|
|
6
8
|
const validator = validators_1.validators[valueType];
|
|
7
|
-
|
|
8
|
-
// Unsupported field type
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
// Always allow null values
|
|
12
|
-
return value === null || validator(value, validation);
|
|
9
|
+
return validator(value, validation);
|
|
13
10
|
/** TODO: Add validation for required fields
|
|
14
11
|
* @example
|
|
15
12
|
* if (validations.required && !value) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import type { ValidationResult } from 'joi';
|
|
1
2
|
import type { CustomFieldDefinitionType } from '../constants';
|
|
2
3
|
/**
|
|
3
4
|
* Validator is a function that validates a custom-field `value`,
|
|
4
5
|
* against a custom-field definition `validation object`.
|
|
5
6
|
* @returns `true` if the value is valid, `false` otherwise.
|
|
6
7
|
*/
|
|
7
|
-
export type Validator<Value, DefinitionValidationObject> = (value: Value, validation?: DefinitionValidationObject) =>
|
|
8
|
+
export type Validator<Value, DefinitionValidationObject> = (value: Value, validation?: DefinitionValidationObject) => ValidationResult;
|
|
8
9
|
/**
|
|
9
10
|
* Validators is a map of custom-field types to their respective validators.
|
|
10
11
|
* The key is the custom-field type, and the value is the validator function.
|
|
@@ -22,19 +22,19 @@ exports.CustomValidationTypes = {
|
|
|
22
22
|
exports.validators = {
|
|
23
23
|
[constants_1.CustomFieldDefinitionType.SELECT]: select_validator_1.validateSelect,
|
|
24
24
|
[constants_1.CustomFieldDefinitionType.STATUS]: status_validator_1.validateStatus,
|
|
25
|
-
[constants_1.CustomFieldDefinitionType.TEXT]: (value) => (
|
|
26
|
-
[constants_1.CustomFieldDefinitionType.NUMBER]: (value) => (
|
|
27
|
-
[constants_1.CustomFieldDefinitionType.BOOLEAN]: (value) => (
|
|
28
|
-
[constants_1.CustomFieldDefinitionType.DATE]: (value) =>
|
|
29
|
-
[constants_1.CustomFieldDefinitionType.DATETIME]: (value) =>
|
|
30
|
-
[constants_1.CustomFieldDefinitionType.IMAGE]: (value) =>
|
|
25
|
+
[constants_1.CustomFieldDefinitionType.TEXT]: (value) => joi_1.default.string().min(0).validate(value),
|
|
26
|
+
[constants_1.CustomFieldDefinitionType.NUMBER]: (value) => joi_1.default.number().strict(true).validate(value),
|
|
27
|
+
[constants_1.CustomFieldDefinitionType.BOOLEAN]: (value) => joi_1.default.boolean().strict().validate(value),
|
|
28
|
+
[constants_1.CustomFieldDefinitionType.DATE]: (value) => joi_1.default.date().validate(value),
|
|
29
|
+
[constants_1.CustomFieldDefinitionType.DATETIME]: (value) => joi_1.default.date().validate(value),
|
|
30
|
+
[constants_1.CustomFieldDefinitionType.IMAGE]: (value) => joi_1.default.array().min(1).unique()
|
|
31
31
|
.items(joi_1.default.string().uri())
|
|
32
|
-
.validate(value)
|
|
33
|
-
[constants_1.CustomFieldDefinitionType.FILE]: (value) =>
|
|
32
|
+
.validate(value),
|
|
33
|
+
[constants_1.CustomFieldDefinitionType.FILE]: (value) => joi_1.default.array().min(1).unique().items(joi_1.default.object({
|
|
34
34
|
name: joi_1.default.string().required(),
|
|
35
35
|
type: joi_1.default.string(),
|
|
36
36
|
size: joi_1.default.string(),
|
|
37
37
|
addedBy: joi_1.default.string().uuid(),
|
|
38
38
|
}))
|
|
39
|
-
.validate(value)
|
|
39
|
+
.validate(value),
|
|
40
40
|
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.validateSelect = void 0;
|
|
7
|
+
const joi_1 = __importDefault(require("joi"));
|
|
4
8
|
/**
|
|
5
9
|
* Validate that the value is one of the select values
|
|
6
10
|
*/
|
|
7
|
-
const validateSelect = (value, selectValues) => (
|
|
8
|
-
&& selectValues.includes(value));
|
|
11
|
+
const validateSelect = (value, selectValues) => (joi_1.default.string().allow(null).valid(...selectValues).validate(value));
|
|
9
12
|
exports.validateSelect = validateSelect;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.validateStatus = void 0;
|
|
7
|
+
const joi_1 = __importDefault(require("joi"));
|
|
4
8
|
/**
|
|
5
9
|
* Validate that the value is one of the status values
|
|
6
10
|
*/
|
|
7
|
-
const validateStatus = (value, statusValues) => (
|
|
8
|
-
|
|
11
|
+
const validateStatus = (value, statusValues) => (joi_1.default.string()
|
|
12
|
+
.allow(null)
|
|
13
|
+
.valid(...statusValues.map((statusValue) => statusValue.value))
|
|
14
|
+
.validate(value));
|
|
9
15
|
exports.validateStatus = validateStatus;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sadot",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "ts-node src/index.ts",
|
|
8
|
-
"build": "rm -rf dist && tsc
|
|
8
|
+
"build": "rm -rf dist && tsc",
|
|
9
9
|
"linter": "eslint .",
|
|
10
10
|
"test": "jest --forceExit --runInBand",
|
|
11
11
|
"coverage": "jest --coverage --forceExit --runInBand && rm -rf ./coverage",
|
|
@@ -41,7 +41,6 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/express": "^4.17.17",
|
|
43
43
|
"@types/jest": "^29.5.13",
|
|
44
|
-
"@types/supertest": "^6.0.2",
|
|
45
44
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
46
45
|
"@typescript-eslint/parser": "^7.18.0",
|
|
47
46
|
"eslint": "^8.57.0",
|
|
@@ -49,7 +48,6 @@
|
|
|
49
48
|
"eslint-plugin-import": "^2.22.1",
|
|
50
49
|
"jest": "^29.7.0",
|
|
51
50
|
"npm-watch": "^0.11.0",
|
|
52
|
-
"supertest": "^7.0.0",
|
|
53
51
|
"ts-jest": "^29.1.2",
|
|
54
52
|
"ts-node": "^8.6.2",
|
|
55
53
|
"typescript": "^5.3.3"
|
|
@@ -36,11 +36,11 @@ const DefaultValueSchema = Joi.when('fieldType', {
|
|
|
36
36
|
{ is: CustomFieldDefinitionType.BOOLEAN, then: Joi.boolean().allow(null) },
|
|
37
37
|
{ is: CustomFieldDefinitionType.DATE, then: Joi.date().allow(null) },
|
|
38
38
|
{ is: CustomFieldDefinitionType.DATETIME, then: Joi.date().allow(null) },
|
|
39
|
-
{ is: CustomFieldDefinitionType.FILE, then: FileValidationSchema },
|
|
40
|
-
{ is: CustomFieldDefinitionType.IMAGE, then: Joi.string().uri().allow(null) },
|
|
39
|
+
{ is: CustomFieldDefinitionType.FILE, then: Joi.array().items(FileValidationSchema).allow(null) },
|
|
40
|
+
{ is: CustomFieldDefinitionType.IMAGE, then: Joi.array().items(Joi.string().uri()).allow(null) },
|
|
41
41
|
{ is: CustomFieldDefinitionType.NUMBER, then: Joi.number().allow(null) },
|
|
42
42
|
{ is: CustomFieldDefinitionType.SELECT, then: Joi.string().allow(null) },
|
|
43
|
-
{ is: CustomFieldDefinitionType.STATUS, then:
|
|
43
|
+
{ is: CustomFieldDefinitionType.STATUS, then: Joi.string().allow(null) },
|
|
44
44
|
{ is: CustomFieldDefinitionType.TEXT, then: Joi.string().allow(null) },
|
|
45
45
|
],
|
|
46
46
|
});
|
package/src/errors/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable max-classes-per-file */
|
|
2
2
|
import { BadRequest } from '@autofleet/errors';
|
|
3
|
+
import type { ValidationError } from 'joi';
|
|
3
4
|
|
|
4
5
|
export class MissingRequiredCustomFieldError extends BadRequest {
|
|
5
6
|
constructor(missingFields: string[]) {
|
|
@@ -25,11 +26,27 @@ export class UnsupportedCustomValidationError extends BadRequest {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
export class InvalidFieldTypeError extends BadRequest {
|
|
30
|
+
constructor(fieldType: string) {
|
|
31
|
+
const err = new Error(`Invalid field type ${fieldType}`);
|
|
32
|
+
super([err], null, null);
|
|
33
|
+
this.message = 'INVALID_FIELD_TYPE';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
export class InvalidValueError extends BadRequest {
|
|
29
|
-
constructor(value: any,
|
|
30
|
-
const
|
|
38
|
+
constructor(value: any, fieldDefinitionName: string, joiValidationError: ValidationError) {
|
|
39
|
+
const formattedErrorMessage = joiValidationError.message
|
|
40
|
+
.replace(/"/g, '')
|
|
41
|
+
.replace('value', `'${fieldDefinitionName}'`);
|
|
42
|
+
|
|
43
|
+
const formattedValue = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
44
|
+
|
|
45
|
+
const invalidValueMessage = `Invalid Value on field '${fieldDefinitionName}'. ${formattedErrorMessage}. received: '${formattedValue}'`;
|
|
46
|
+
|
|
47
|
+
const err = new Error(invalidValueMessage);
|
|
31
48
|
super([err], null, null);
|
|
32
|
-
this.message =
|
|
49
|
+
this.message = invalidValueMessage;
|
|
33
50
|
}
|
|
34
51
|
}
|
|
35
52
|
|
package/src/events/index.ts
CHANGED
|
@@ -3,19 +3,17 @@ import logger from '../utils/logger';
|
|
|
3
3
|
|
|
4
4
|
const events = new Events({ logger });
|
|
5
5
|
|
|
6
|
-
const KEYS_TO_CONVERT = ['value', 'defaultValue'];
|
|
6
|
+
const KEYS_TO_CONVERT = ['value', 'defaultValue', 'blockEditingFromUI'];
|
|
7
7
|
|
|
8
|
-
const stringifyBooleans = (savedObject: any, keysToConvert:
|
|
9
|
-
|
|
8
|
+
const stringifyBooleans = (savedObject: any, keysToConvert: string[]) => {
|
|
9
|
+
const savedObjectKeySet = new Set(Object.keys(savedObject));
|
|
10
|
+
if (keysToConvert.every((key) => !savedObjectKeySet.has(key))) {
|
|
10
11
|
return savedObject;
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
return objectToReturn;
|
|
13
|
+
return {
|
|
14
|
+
...savedObject,
|
|
15
|
+
...Object.fromEntries(keysToConvert.map((key) => [key, typeof savedObject[key] === 'boolean' ? savedObject[key].toString() : savedObject[key]])),
|
|
16
|
+
};
|
|
19
17
|
};
|
|
20
18
|
|
|
21
19
|
const modelTableMapping = {
|
|
@@ -143,8 +143,8 @@ class CustomFieldDefinition extends Model {
|
|
|
143
143
|
}
|
|
144
144
|
if (![null, undefined].includes(instance.defaultValue)) {
|
|
145
145
|
const isValid = validateValue(instance.defaultValue, instance.fieldType, instance.validation);
|
|
146
|
-
if (
|
|
147
|
-
throw new InvalidValueError(instance.defaultValue, instance.
|
|
146
|
+
if (isValid.error) {
|
|
147
|
+
throw new InvalidValueError(instance.defaultValue, instance.name, isValid.error);
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
}
|
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
} from 'sequelize-typescript';
|
|
16
16
|
import { sendDimEvent } from '../events';
|
|
17
17
|
import { CustomFieldDefinition } from '.';
|
|
18
|
-
import { validateValue } from '../utils/validations';
|
|
18
|
+
import { validateFieldType, validateValue } from '../utils/validations';
|
|
19
19
|
import * as CustomFieldDefinitionRepo from '../repository/definition';
|
|
20
|
-
import { InvalidValueError } from '../errors';
|
|
20
|
+
import { InvalidFieldTypeError, InvalidValueError } from '../errors';
|
|
21
21
|
|
|
22
22
|
@Table({
|
|
23
23
|
timestamps: true,
|
|
@@ -56,9 +56,26 @@ class CustomFieldValue extends Model {
|
|
|
56
56
|
@BelongsTo(() => CustomFieldDefinition, { scope: { disabled: false } })
|
|
57
57
|
customFieldDefinition: CustomFieldDefinition;
|
|
58
58
|
|
|
59
|
+
private static validateValueAgainstDefinition(
|
|
60
|
+
instance: CustomFieldValue,
|
|
61
|
+
definition: CustomFieldDefinition,
|
|
62
|
+
): void {
|
|
63
|
+
const { validation, fieldType, name } = definition;
|
|
64
|
+
const isValidFieldType = validateFieldType(fieldType);
|
|
65
|
+
if (!isValidFieldType) {
|
|
66
|
+
throw new InvalidFieldTypeError(fieldType);
|
|
67
|
+
}
|
|
68
|
+
// Always allow null values
|
|
69
|
+
if (instance.value === null) return;
|
|
70
|
+
const validateValueResponse = validateValue(instance.value, fieldType, validation);
|
|
71
|
+
if (validateValueResponse.error) {
|
|
72
|
+
throw new InvalidValueError(instance.value, name, validateValueResponse.error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
59
76
|
@BeforeBulkCreate
|
|
60
77
|
@BeforeBulkUpdate
|
|
61
|
-
static async
|
|
78
|
+
static async validateCustomFieldValues(instances: CustomFieldValue[]): Promise<void> {
|
|
62
79
|
const ids = instances.map((instance) => instance.customFieldDefinitionId);
|
|
63
80
|
const uniqueIds = [...new Set(ids)];
|
|
64
81
|
const definitions = await CustomFieldDefinitionRepo.findByIds(
|
|
@@ -71,14 +88,9 @@ class CustomFieldValue extends Model {
|
|
|
71
88
|
}
|
|
72
89
|
|
|
73
90
|
instances.forEach((instance) => {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
} = definitions
|
|
78
|
-
.find((definition) => definition.id === instance.customFieldDefinitionId);
|
|
79
|
-
const isValid = validateValue(instance.value, fieldType, validation);
|
|
80
|
-
if (!isValid) {
|
|
81
|
-
throw new InvalidValueError(instance.value, fieldType);
|
|
91
|
+
const definition = definitions.find((d) => d.id === instance.customFieldDefinitionId);
|
|
92
|
+
if (definition) {
|
|
93
|
+
this.validateValueAgainstDefinition(instance, definition);
|
|
82
94
|
}
|
|
83
95
|
});
|
|
84
96
|
}
|
|
@@ -86,15 +98,11 @@ class CustomFieldValue extends Model {
|
|
|
86
98
|
@BeforeUpdate
|
|
87
99
|
@BeforeCreate
|
|
88
100
|
@BeforeUpsert
|
|
89
|
-
static async
|
|
101
|
+
static async validateCustomFieldValue(instance: CustomFieldValue): Promise<void> {
|
|
90
102
|
const { customFieldDefinitionId } = instance;
|
|
91
103
|
// eslint-disable-next-line max-len
|
|
92
104
|
const cfd = await CustomFieldDefinitionRepo.findById(customFieldDefinitionId, { withDisabled: true });
|
|
93
|
-
|
|
94
|
-
const isValid = validateValue(instance.value, fieldType, validation);
|
|
95
|
-
if (!isValid) {
|
|
96
|
-
throw new InvalidValueError(instance.value, fieldType);
|
|
97
|
-
}
|
|
105
|
+
this.validateValueAgainstDefinition(instance, cfd);
|
|
98
106
|
}
|
|
99
107
|
|
|
100
108
|
@AfterUpsert
|
|
@@ -52,6 +52,14 @@ export const statusField = (modelType: string, options): CreateCustomFieldDefini
|
|
|
52
52
|
entityType: 'fleetId',
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
+
export const fileField = (modelType: string): CreateCustomFieldDefinition => ({
|
|
56
|
+
name: 'file',
|
|
57
|
+
modelType,
|
|
58
|
+
fieldType: 'file',
|
|
59
|
+
entityId: uuidv4(),
|
|
60
|
+
entityType: 'fleetId',
|
|
61
|
+
});
|
|
62
|
+
|
|
55
63
|
// eslint-disable-next-line max-len
|
|
56
64
|
export const createDefinition = (defaults: Partial<CustomFieldDefinitionDTO>): CreateCustomFieldDefinition => ({
|
|
57
65
|
name: defaults?.name || `def_${uuidv4()}`,
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import type { CustomFieldDefinitionType } from '../constants';
|
|
2
2
|
import { validators } from './validators';
|
|
3
3
|
|
|
4
|
+
export const validateFieldType = (type: CustomFieldDefinitionType): boolean => Object.keys(validators).includes(type);
|
|
5
|
+
|
|
4
6
|
export const validateValue = (
|
|
5
7
|
value: unknown,
|
|
6
8
|
valueType: CustomFieldDefinitionType,
|
|
7
9
|
validation?: unknown,
|
|
8
10
|
) => {
|
|
9
11
|
const validator = validators[valueType];
|
|
10
|
-
|
|
11
|
-
// Unsupported field type
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
// Always allow null values
|
|
15
|
-
return value === null || validator(value, validation);
|
|
12
|
+
return validator(value, validation);
|
|
16
13
|
/** TODO: Add validation for required fields
|
|
17
14
|
* @example
|
|
18
15
|
* if (validations.required && !value) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ValidationResult } from 'joi';
|
|
1
2
|
import type { CustomFieldDefinitionType } from '../constants';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -8,7 +9,7 @@ import type { CustomFieldDefinitionType } from '../constants';
|
|
|
8
9
|
export type Validator<Value, DefinitionValidationObject> = (
|
|
9
10
|
value: Value,
|
|
10
11
|
validation?: DefinitionValidationObject
|
|
11
|
-
) =>
|
|
12
|
+
) => ValidationResult;
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Validators is a map of custom-field types to their respective validators.
|
|
@@ -20,19 +20,19 @@ export const CustomValidationTypes = {
|
|
|
20
20
|
export const validators: Validators = {
|
|
21
21
|
[CustomFieldDefinitionType.SELECT]: validateSelect,
|
|
22
22
|
[CustomFieldDefinitionType.STATUS]: validateStatus,
|
|
23
|
-
[CustomFieldDefinitionType.TEXT]: (value) => (
|
|
24
|
-
[CustomFieldDefinitionType.NUMBER]: (value) => (
|
|
25
|
-
[CustomFieldDefinitionType.BOOLEAN]: (value) => (
|
|
26
|
-
[CustomFieldDefinitionType.DATE]: (value) =>
|
|
27
|
-
[CustomFieldDefinitionType.DATETIME]: (value) =>
|
|
28
|
-
[CustomFieldDefinitionType.IMAGE]: (value) =>
|
|
23
|
+
[CustomFieldDefinitionType.TEXT]: (value) => Joi.string().min(0).validate(value),
|
|
24
|
+
[CustomFieldDefinitionType.NUMBER]: (value) => Joi.number().strict(true).validate(value),
|
|
25
|
+
[CustomFieldDefinitionType.BOOLEAN]: (value) => Joi.boolean().strict().validate(value),
|
|
26
|
+
[CustomFieldDefinitionType.DATE]: (value) => Joi.date().validate(value),
|
|
27
|
+
[CustomFieldDefinitionType.DATETIME]: (value) => Joi.date().validate(value),
|
|
28
|
+
[CustomFieldDefinitionType.IMAGE]: (value) => Joi.array().min(1).unique()
|
|
29
29
|
.items(Joi.string().uri())
|
|
30
|
-
.validate(value)
|
|
31
|
-
[CustomFieldDefinitionType.FILE]: (value) =>
|
|
30
|
+
.validate(value),
|
|
31
|
+
[CustomFieldDefinitionType.FILE]: (value) => Joi.array().min(1).unique().items(Joi.object({
|
|
32
32
|
name: Joi.string().required(),
|
|
33
33
|
type: Joi.string(),
|
|
34
34
|
size: Joi.string(),
|
|
35
35
|
addedBy: Joi.string().uuid(),
|
|
36
36
|
}))
|
|
37
|
-
.validate(value)
|
|
37
|
+
.validate(value),
|
|
38
38
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
1
2
|
import type { Validator } from '../type';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -6,6 +7,6 @@ import type { Validator } from '../type';
|
|
|
6
7
|
export const validateSelect: Validator<string, string[]> = (
|
|
7
8
|
value,
|
|
8
9
|
selectValues,
|
|
9
|
-
) => (
|
|
10
|
-
|
|
10
|
+
) => (
|
|
11
|
+
Joi.string().allow(null).valid(...selectValues).validate(value)
|
|
11
12
|
);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
1
2
|
import type { Validator } from '../type';
|
|
2
3
|
|
|
3
4
|
type StatusColor = string | null; // TODO: Takes from @autofleet/colors ?
|
|
@@ -13,6 +14,9 @@ type StatusOption = {
|
|
|
13
14
|
export const validateStatus: Validator<StatusValue, StatusOption[]> = (
|
|
14
15
|
value,
|
|
15
16
|
statusValues,
|
|
16
|
-
) => (
|
|
17
|
-
|
|
17
|
+
) => (
|
|
18
|
+
Joi.string()
|
|
19
|
+
.allow(null)
|
|
20
|
+
.valid(...statusValues.map((statusValue) => statusValue.value))
|
|
21
|
+
.validate(value)
|
|
18
22
|
);
|
package/tsconfig.build.json
CHANGED