@autofleet/sadot 0.6.2 → 0.6.3-beta.1
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.d.ts +2 -2
- package/dist/api/v1/definition/validations.js +2 -2
- package/dist/api/v1/errors.js +1 -1
- package/dist/hooks/create.d.ts +2 -1
- package/dist/hooks/create.js +8 -4
- package/dist/hooks/enrich.d.ts +3 -1
- package/dist/hooks/enrich.js +22 -4
- package/dist/hooks/update.d.ts +2 -1
- package/dist/hooks/update.js +4 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -1
- package/dist/models/index.d.ts +3 -1
- package/dist/models/index.js +8 -2
- package/dist/models/tests/contextAwareModels/ContextAwareTestModel.d.ts +10 -0
- package/dist/models/tests/contextAwareModels/ContextAwareTestModel.js +55 -0
- package/dist/models/tests/contextAwareModels/ContextTestModel.d.ts +13 -0
- package/dist/models/tests/contextAwareModels/ContextTestModel.js +47 -0
- package/dist/repository/definition.d.ts +7 -4
- package/dist/repository/definition.js +27 -15
- package/dist/repository/value.d.ts +5 -1
- package/dist/repository/value.js +13 -3
- package/dist/scopes/filter.d.ts +9 -0
- package/dist/scopes/filter.js +37 -5
- package/dist/tests/functional/searching/index.d.ts +8 -0
- package/dist/tests/functional/searching/index.js +44 -0
- package/dist/tests/helpers/database-config.d.ts +1 -0
- package/dist/tests/helpers/database-config.js +1 -0
- package/dist/tests/helpers/index.js +2 -0
- package/dist/tests/mocks/definition.mock.d.ts +6 -0
- package/dist/tests/mocks/definition.mock.js +7 -1
- package/dist/tests/mocks/testModel.d.ts +1 -1
- package/dist/types/index.d.ts +20 -2
- package/dist/utils/constants/index.d.ts +4 -1
- package/dist/utils/constants/index.js +3 -1
- package/dist/utils/helpers/index.d.ts +25 -0
- package/dist/utils/helpers/index.js +34 -0
- package/dist/utils/init.d.ts +5 -4
- package/dist/utils/init.js +19 -9
- package/dist/utils/scopeAttributes.d.ts +2 -0
- package/dist/utils/scopeAttributes.js +11 -0
- package/dist/utils/validations/custom-fields.d.ts +2 -1
- package/dist/utils/validations/custom-fields.js +1 -1
- package/dist/utils/validations/type.js +1 -1
- package/package.json +8 -8
- package/src/api/v1/definition/index.ts +1 -1
- package/src/api/v1/definition/validations.ts +0 -13
- package/src/hooks/create.ts +1 -1
- package/src/hooks/enrich.ts +4 -4
- package/src/hooks/update.ts +1 -1
- package/src/index.ts +2 -2
- package/src/models/CustomFieldDefinition.ts +5 -9
- package/src/models/CustomFieldValue.ts +5 -1
- package/src/repository/definition.ts +5 -2
- package/src/repository/value.ts +2 -2
- package/src/scopes/filter.ts +26 -1
- package/src/tests/functional/searching/index.ts +1 -1
- package/src/tests/mocks/definition.mock.ts +8 -6
- package/src/types/index.ts +2 -2
- package/src/utils/helpers/index.ts +4 -4
- package/src/utils/init.ts +4 -1
- package/src/utils/validations/{schema/custom-fields.ts → custom-fields.ts} +1 -0
- package/src/utils/validations/custom.ts +39 -0
- package/src/utils/validations/index.ts +15 -18
- package/src/utils/validations/type.ts +32 -5
- package/src/utils/validations/validators.ts +34 -0
- package/.env +0 -3
- package/src/utils/validations/validators/index.ts +0 -29
- package/src/utils/validations/validators/select.validator.ts +0 -13
- package/src/utils/validations/validators/status.validator.ts +0 -20
package/src/scopes/filter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable import/prefer-default-export */
|
|
2
|
-
import { Op,
|
|
2
|
+
import { Op, WhereOptions } from 'sequelize';
|
|
3
3
|
import { Sequelize } from 'sequelize-typescript';
|
|
4
4
|
import { customFields } from '@autofleet/common-types';
|
|
5
5
|
|
|
@@ -75,3 +75,28 @@ export const customFieldsFilterScope = (
|
|
|
75
75
|
};
|
|
76
76
|
|
|
77
77
|
export const scopeName = CUSTOM_FIELDS_FILTER_SCOPE;
|
|
78
|
+
|
|
79
|
+
export const customFieldsSortScope = (
|
|
80
|
+
name: string,
|
|
81
|
+
) => (sort: CustomFieldSort[]) => {
|
|
82
|
+
if (!sort || sort.length === 0) {
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const incluedes = Object.entries(sort).map(([key]) =>
|
|
87
|
+
Sequelize.literal(`(select CustomFieldAggregation.custom_fields->>'${key}'
|
|
88
|
+
from (SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields
|
|
89
|
+
FROM custom_field_values AS cv
|
|
90
|
+
INNER JOIN custom_field_definitions AS cd
|
|
91
|
+
ON cv.custom_field_definition_id = cd.id AND cd.model_type = 'Task'
|
|
92
|
+
where cv.model_id = "${name}"."id"
|
|
93
|
+
GROUP BY cv.model_id) AS CustomFieldAggregation) as "customFields.${key}"`));
|
|
94
|
+
const orders = Object.entries(sort).map(([key, value]) => Sequelize.literal(`"customFields.${key}" ${value}`));
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
attributes: {
|
|
98
|
+
include: incluedes,
|
|
99
|
+
},
|
|
100
|
+
order: orders,
|
|
101
|
+
};
|
|
102
|
+
};
|
|
@@ -16,7 +16,7 @@ const customFieldsSearchTestFlow = async ({
|
|
|
16
16
|
fieldValue,
|
|
17
17
|
searchTerm,
|
|
18
18
|
expectedNumberOfQueryResults,
|
|
19
|
-
}: CustomFieldsSearchTestFlowInput): Promise<void> => {
|
|
19
|
+
}: CustomFieldsSearchTestFlowInput) : Promise<void> => {
|
|
20
20
|
const definition = createDefinition({ fieldType, name: 'coolDefinition' });
|
|
21
21
|
await DefinitionRepo.create({ ...definition });
|
|
22
22
|
const [testModel1] = await createTestModels(definition.entityId, 2);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
-
import
|
|
2
|
+
import { CreateCustomFieldDefinition, CustomFieldDefinitionDTO } from '../../types/definition';
|
|
3
3
|
|
|
4
4
|
export const contextAwareFieldDefinition = {
|
|
5
5
|
name: 'cool field',
|
|
@@ -34,7 +34,7 @@ export const booleanField = (modelType: string): CreateCustomFieldDefinition =>
|
|
|
34
34
|
entityType: 'fleetId',
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
export const
|
|
37
|
+
export const enumField = (modelType: string, options): CreateCustomFieldDefinition => ({
|
|
38
38
|
name: 'choices',
|
|
39
39
|
modelType,
|
|
40
40
|
fieldType: 'select',
|
|
@@ -43,11 +43,13 @@ export const selectField = (modelType: string, options): CreateCustomFieldDefini
|
|
|
43
43
|
entityType: 'fleetId',
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
export const
|
|
47
|
-
name: '
|
|
46
|
+
export const rangeField = (modelType: string): CreateCustomFieldDefinition => ({
|
|
47
|
+
name: 'ranges',
|
|
48
48
|
modelType,
|
|
49
|
-
fieldType: '
|
|
50
|
-
validation:
|
|
49
|
+
fieldType: 'number',
|
|
50
|
+
validation: {
|
|
51
|
+
between: [10, 12],
|
|
52
|
+
},
|
|
51
53
|
entityId: uuidv4(),
|
|
52
54
|
entityType: 'fleetId',
|
|
53
55
|
});
|
package/src/types/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable import/prefer-default-export */
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { WhereOptions, Op, BindOrReplacements } from 'sequelize';
|
|
3
|
+
import { ModelStatic, Sequelize } from 'sequelize-typescript';
|
|
4
4
|
import { CustomFieldDefinitionType } from '../validations/type';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -28,8 +28,8 @@ interface CustomFieldsSearchPayload {
|
|
|
28
28
|
export const generateCustomFieldSearchQueryPayload = (
|
|
29
29
|
searchTerm: string,
|
|
30
30
|
model: ModelStatic,
|
|
31
|
-
entityId: string,
|
|
32
|
-
customFieldsTypesToExclude: CustomFieldDefinitionType[] = [
|
|
31
|
+
entityId : string,
|
|
32
|
+
customFieldsTypesToExclude : CustomFieldDefinitionType[] = [
|
|
33
33
|
CustomFieldDefinitionType.DATETIME,
|
|
34
34
|
CustomFieldDefinitionType.DATE,
|
|
35
35
|
],
|
package/src/utils/init.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DataTypes } from 'sequelize';
|
|
2
|
-
import
|
|
2
|
+
import { ModelCtor } from 'sequelize-typescript';
|
|
3
3
|
import { customFields } from '@autofleet/common-types';
|
|
4
|
+
import { CUSTOM_FIELDS_SORT_SCOPE } from '@autofleet/common-types/lib/custom-fields';
|
|
4
5
|
import {
|
|
5
6
|
CustomFieldDefinition,
|
|
6
7
|
CustomFieldValue,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
import { customFieldsFilterScope } from '../scopes';
|
|
17
18
|
import logger from './logger';
|
|
18
19
|
import type { ModelFetcher, Models } from '../types';
|
|
20
|
+
import { customFieldsSortScope } from '../scopes/filter';
|
|
19
21
|
|
|
20
22
|
const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
|
|
21
23
|
|
|
@@ -99,6 +101,7 @@ export const addScopes = (models: Models[], getModel: ModelFetcher): void => {
|
|
|
99
101
|
addAssociations(model, name);
|
|
100
102
|
// Add filter scope
|
|
101
103
|
model.addScope(CUSTOM_FIELDS_FILTER_SCOPE, customFieldsFilterScope(name));
|
|
104
|
+
model.addScope(CUSTOM_FIELDS_SORT_SCOPE, customFieldsSortScope(name));
|
|
102
105
|
} catch (e) {
|
|
103
106
|
logger.error(`Could not add custom fields scopes to model ${name}. `, e);
|
|
104
107
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* eslint-disable no-shadow */
|
|
2
|
+
import logger from '../logger';
|
|
3
|
+
import { CustomFieldDefinitionType } from './type';
|
|
4
|
+
import validators from './validators';
|
|
5
|
+
|
|
6
|
+
export const mustHaveCustomValidation = {
|
|
7
|
+
[CustomFieldDefinitionType.SELECT]: true,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validates the given validations object against the supported field types and their validators.
|
|
12
|
+
* @return true if the validation is valid, false otherwise.
|
|
13
|
+
*/
|
|
14
|
+
export const validateValidation = (valueType, validation) => {
|
|
15
|
+
if (!validation) {
|
|
16
|
+
if (mustHaveCustomValidation[valueType]) {
|
|
17
|
+
logger.error(`No custom validation for custom field type ${valueType} found`);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validates the given value against the custom validation rules for the specified field type.
|
|
27
|
+
* If no custom validation rules are provided, it falls back to the default validation.
|
|
28
|
+
* @returns true if the value is valid according to the validation rules, false otherwise.
|
|
29
|
+
*/
|
|
30
|
+
const customValidation = (value, valueType, validation) => {
|
|
31
|
+
const validator = validators?.[valueType];
|
|
32
|
+
if (!validation || !validator) {
|
|
33
|
+
return validateValidation(valueType, validation);
|
|
34
|
+
}
|
|
35
|
+
// Always allow null values
|
|
36
|
+
return value === null || validator(value, validation);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default customValidation;
|
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import customValidation from './custom';
|
|
2
|
+
import validateValueType from './type';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
value
|
|
6
|
-
valueType: CustomFieldDefinitionType,
|
|
7
|
-
validation?: unknown,
|
|
8
|
-
) => {
|
|
9
|
-
const validator = validators[valueType];
|
|
10
|
-
if (!validator) {
|
|
11
|
-
// Unsupported field type
|
|
4
|
+
const validateValue = (value, valueType, customValidations) => {
|
|
5
|
+
if (!validateValueType(value, valueType)) {
|
|
12
6
|
return false;
|
|
13
7
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
if (customValidations) {
|
|
9
|
+
return customValidation(value, valueType, customValidations);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// if (validations.required && !value) {
|
|
13
|
+
// return false;
|
|
14
|
+
// }
|
|
15
|
+
|
|
16
|
+
return true;
|
|
22
17
|
};
|
|
18
|
+
|
|
19
|
+
export default validateValue;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
1
2
|
/**
|
|
2
|
-
* Supported custom field types
|
|
3
|
+
* Supported custom field types
|
|
3
4
|
*/
|
|
5
|
+
// eslint-disable-next-line no-shadow
|
|
4
6
|
export enum CustomFieldDefinitionType {
|
|
5
7
|
NUMBER = 'number',
|
|
6
8
|
BOOLEAN = 'boolean',
|
|
@@ -9,10 +11,35 @@ export enum CustomFieldDefinitionType {
|
|
|
9
11
|
TEXT = 'text',
|
|
10
12
|
IMAGE = 'image',
|
|
11
13
|
SELECT = 'select',
|
|
12
|
-
STATUS = 'status',
|
|
13
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Validate that the given value is really of type "valueType"
|
|
17
|
+
* TODO: verify that required field not set to null
|
|
18
|
+
*/
|
|
19
|
+
const validateValueType = (value: unknown, valueType: CustomFieldDefinitionType): boolean => {
|
|
20
|
+
if (value === null) {
|
|
21
|
+
// Null is always allowed
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
switch (valueType) {
|
|
26
|
+
case CustomFieldDefinitionType.TEXT:
|
|
27
|
+
return typeof value === 'string';
|
|
28
|
+
case CustomFieldDefinitionType.NUMBER:
|
|
29
|
+
return typeof value === 'number';
|
|
30
|
+
case CustomFieldDefinitionType.BOOLEAN:
|
|
31
|
+
return typeof value === 'boolean';
|
|
32
|
+
case CustomFieldDefinitionType.DATE:
|
|
33
|
+
case CustomFieldDefinitionType.DATETIME:
|
|
34
|
+
return !Joi.date().validate(value).error;
|
|
35
|
+
case CustomFieldDefinitionType.SELECT:
|
|
36
|
+
return true; // custom validation
|
|
37
|
+
case CustomFieldDefinitionType.IMAGE:
|
|
38
|
+
return !Joi.array().min(1).unique().items(Joi.string().uri())
|
|
39
|
+
.validate(value).error;
|
|
40
|
+
default:
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
18
43
|
};
|
|
44
|
+
|
|
45
|
+
export default validateValueType;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// eslint-disable-next-line no-shadow
|
|
2
|
+
export enum CustomValidations {
|
|
3
|
+
SELECT = 'select',
|
|
4
|
+
RANGE = 'between'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
type Validator = (value, validation) => boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Validate {@link CustomValidations.ENUM Enum}
|
|
10
|
+
*/
|
|
11
|
+
const validateEnum: Validator = (value, enumValues) => (Array.isArray(enumValues)
|
|
12
|
+
&& enumValues.length > 0
|
|
13
|
+
&& enumValues.includes(value)
|
|
14
|
+
);
|
|
15
|
+
/**
|
|
16
|
+
* Validate {@link CustomValidations.RANGE Range}
|
|
17
|
+
*/
|
|
18
|
+
const validateRange: Validator = (value, range) => {
|
|
19
|
+
const [min, max] = range;
|
|
20
|
+
if (min === undefined || max === undefined) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return value >= range.min && value <= range.max;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validators for custom fields
|
|
28
|
+
*/
|
|
29
|
+
const validators: { [key: string]: Validator } = {
|
|
30
|
+
[CustomValidations.SELECT]: validateEnum,
|
|
31
|
+
[CustomValidations.RANGE]: validateRange,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default validators;
|
package/.env
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import Joi from 'joi';
|
|
2
|
-
import { CustomFieldDefinitionType, type Validators } from '../type';
|
|
3
|
-
import { validateSelect } from './select.validator';
|
|
4
|
-
import { validateStatus } from './status.validator';
|
|
5
|
-
|
|
6
|
-
type CustomValidationTypes = Extract<CustomFieldDefinitionType, 'select' | 'status'>;
|
|
7
|
-
/**
|
|
8
|
-
* Custom field types that must have custom validation.
|
|
9
|
-
*/
|
|
10
|
-
export const CustomValidationTypes = {
|
|
11
|
-
[CustomFieldDefinitionType.SELECT]: CustomFieldDefinitionType.SELECT,
|
|
12
|
-
[CustomFieldDefinitionType.STATUS]: CustomFieldDefinitionType.STATUS,
|
|
13
|
-
} as const;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Validators for custom fields
|
|
17
|
-
*/
|
|
18
|
-
export const validators: Validators = {
|
|
19
|
-
[CustomFieldDefinitionType.SELECT]: validateSelect,
|
|
20
|
-
[CustomFieldDefinitionType.STATUS]: validateStatus,
|
|
21
|
-
[CustomFieldDefinitionType.TEXT]: (value) => (typeof value === 'string'),
|
|
22
|
-
[CustomFieldDefinitionType.NUMBER]: (value) => (typeof value === 'number'),
|
|
23
|
-
[CustomFieldDefinitionType.BOOLEAN]: (value) => (typeof value === 'boolean'),
|
|
24
|
-
[CustomFieldDefinitionType.DATE]: (value) => (!Joi.date().validate(value).error),
|
|
25
|
-
[CustomFieldDefinitionType.DATETIME]: (value) => (!Joi.date().validate(value).error),
|
|
26
|
-
[CustomFieldDefinitionType.IMAGE]: (value) => (!Joi.array().min(1).unique()
|
|
27
|
-
.items(Joi.string().uri())
|
|
28
|
-
.validate(value).error),
|
|
29
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Validator } from '../type';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Validate that the value is one of the select values
|
|
5
|
-
*/
|
|
6
|
-
export const validateSelect: Validator<string, string[]> = (
|
|
7
|
-
value,
|
|
8
|
-
selectValues,
|
|
9
|
-
) => (selectValues
|
|
10
|
-
&& Array.isArray(selectValues)
|
|
11
|
-
&& selectValues.length > 0
|
|
12
|
-
&& selectValues.includes(value)
|
|
13
|
-
);
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { Validator } from '../type';
|
|
2
|
-
|
|
3
|
-
type StatusColor = string | null; // TODO: Takes from @autofleet/colors ?
|
|
4
|
-
type StatusValue = string;
|
|
5
|
-
type StatusOption = {
|
|
6
|
-
value: StatusValue;
|
|
7
|
-
color: StatusColor;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Validate that the value is one of the status values
|
|
12
|
-
*/
|
|
13
|
-
export const validateStatus: Validator<StatusValue, StatusOption[]> = (
|
|
14
|
-
value,
|
|
15
|
-
statusValues,
|
|
16
|
-
) => (statusValues
|
|
17
|
-
&& Array.isArray(statusValues)
|
|
18
|
-
&& statusValues.length > 0
|
|
19
|
-
&& statusValues.map((status) => status.value).includes(value)
|
|
20
|
-
);
|