@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.
Files changed (69) hide show
  1. package/dist/api/v1/definition/validations.d.ts +2 -2
  2. package/dist/api/v1/definition/validations.js +2 -2
  3. package/dist/api/v1/errors.js +1 -1
  4. package/dist/hooks/create.d.ts +2 -1
  5. package/dist/hooks/create.js +8 -4
  6. package/dist/hooks/enrich.d.ts +3 -1
  7. package/dist/hooks/enrich.js +22 -4
  8. package/dist/hooks/update.d.ts +2 -1
  9. package/dist/hooks/update.js +4 -3
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +5 -1
  12. package/dist/models/index.d.ts +3 -1
  13. package/dist/models/index.js +8 -2
  14. package/dist/models/tests/contextAwareModels/ContextAwareTestModel.d.ts +10 -0
  15. package/dist/models/tests/contextAwareModels/ContextAwareTestModel.js +55 -0
  16. package/dist/models/tests/contextAwareModels/ContextTestModel.d.ts +13 -0
  17. package/dist/models/tests/contextAwareModels/ContextTestModel.js +47 -0
  18. package/dist/repository/definition.d.ts +7 -4
  19. package/dist/repository/definition.js +27 -15
  20. package/dist/repository/value.d.ts +5 -1
  21. package/dist/repository/value.js +13 -3
  22. package/dist/scopes/filter.d.ts +9 -0
  23. package/dist/scopes/filter.js +37 -5
  24. package/dist/tests/functional/searching/index.d.ts +8 -0
  25. package/dist/tests/functional/searching/index.js +44 -0
  26. package/dist/tests/helpers/database-config.d.ts +1 -0
  27. package/dist/tests/helpers/database-config.js +1 -0
  28. package/dist/tests/helpers/index.js +2 -0
  29. package/dist/tests/mocks/definition.mock.d.ts +6 -0
  30. package/dist/tests/mocks/definition.mock.js +7 -1
  31. package/dist/tests/mocks/testModel.d.ts +1 -1
  32. package/dist/types/index.d.ts +20 -2
  33. package/dist/utils/constants/index.d.ts +4 -1
  34. package/dist/utils/constants/index.js +3 -1
  35. package/dist/utils/helpers/index.d.ts +25 -0
  36. package/dist/utils/helpers/index.js +34 -0
  37. package/dist/utils/init.d.ts +5 -4
  38. package/dist/utils/init.js +19 -9
  39. package/dist/utils/scopeAttributes.d.ts +2 -0
  40. package/dist/utils/scopeAttributes.js +11 -0
  41. package/dist/utils/validations/custom-fields.d.ts +2 -1
  42. package/dist/utils/validations/custom-fields.js +1 -1
  43. package/dist/utils/validations/type.js +1 -1
  44. package/package.json +8 -8
  45. package/src/api/v1/definition/index.ts +1 -1
  46. package/src/api/v1/definition/validations.ts +0 -13
  47. package/src/hooks/create.ts +1 -1
  48. package/src/hooks/enrich.ts +4 -4
  49. package/src/hooks/update.ts +1 -1
  50. package/src/index.ts +2 -2
  51. package/src/models/CustomFieldDefinition.ts +5 -9
  52. package/src/models/CustomFieldValue.ts +5 -1
  53. package/src/repository/definition.ts +5 -2
  54. package/src/repository/value.ts +2 -2
  55. package/src/scopes/filter.ts +26 -1
  56. package/src/tests/functional/searching/index.ts +1 -1
  57. package/src/tests/mocks/definition.mock.ts +8 -6
  58. package/src/types/index.ts +2 -2
  59. package/src/utils/helpers/index.ts +4 -4
  60. package/src/utils/init.ts +4 -1
  61. package/src/utils/validations/{schema/custom-fields.ts → custom-fields.ts} +1 -0
  62. package/src/utils/validations/custom.ts +39 -0
  63. package/src/utils/validations/index.ts +15 -18
  64. package/src/utils/validations/type.ts +32 -5
  65. package/src/utils/validations/validators.ts +34 -0
  66. package/.env +0 -3
  67. package/src/utils/validations/validators/index.ts +0 -29
  68. package/src/utils/validations/validators/select.validator.ts +0 -13
  69. package/src/utils/validations/validators/status.validator.ts +0 -20
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
- import { Op, type WhereOptions } from 'sequelize';
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 type { CreateCustomFieldDefinition, CustomFieldDefinitionDTO } from '../../types/definition';
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 selectField = (modelType: string, options): CreateCustomFieldDefinition => ({
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 statusField = (modelType: string, options): CreateCustomFieldDefinition => ({
47
- name: 'lifecycle',
46
+ export const rangeField = (modelType: string): CreateCustomFieldDefinition => ({
47
+ name: 'ranges',
48
48
  modelType,
49
- fieldType: 'status',
50
- validation: options,
49
+ fieldType: 'number',
50
+ validation: {
51
+ between: [10, 12],
52
+ },
51
53
  entityId: uuidv4(),
52
54
  entityType: 'fleetId',
53
55
  });
@@ -1,5 +1,5 @@
1
- import type { IncludeOptions } from 'sequelize';
2
- import type { ModelCtor } from 'sequelize-typescript';
1
+ import { IncludeOptions } from 'sequelize';
2
+ import { ModelCtor } from 'sequelize-typescript';
3
3
 
4
4
  export type ModelFetcher = (name: string) => any;
5
5
 
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
- import { type WhereOptions, Op, type BindOrReplacements } from 'sequelize';
3
- import { type ModelStatic, Sequelize } from 'sequelize-typescript';
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 type { ModelCtor } from 'sequelize-typescript';
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
  }
@@ -1,3 +1,4 @@
1
+ /* eslint-disable import/prefer-default-export */
1
2
  import Joi from 'joi';
2
3
 
3
4
  const CustomFieldsSchema = Joi.object().pattern(
@@ -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 type { CustomFieldDefinitionType } from './type';
2
- import { validators } from './validators';
1
+ import customValidation from './custom';
2
+ import validateValueType from './type';
3
3
 
4
- export const validateValue = (
5
- value: unknown,
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
- // Always allow null values
15
- return value === null || validator(value, validation);
16
- /** TODO: Add validation for required fields
17
- * @example
18
- * if (validations.required && !value) {
19
- * return false;
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 enum
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
- export type Validator<Value, Validation> = (value: Value, validation?: Validation) => boolean;
16
- export type Validators = {
17
- [K in CustomFieldDefinitionType]: Validator<unknown, unknown>;
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,3 +0,0 @@
1
- NODE_ENV=test
2
- # LOG_LEVEL=debug
3
- # SADOT_DEBUG=true
@@ -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
- );