@autofleet/sadot 0.6.4-beta.1 → 0.6.5

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 (62) hide show
  1. package/dist/api/v1/definition/validations.js +21 -4
  2. package/dist/hooks/create.d.ts +1 -1
  3. package/dist/hooks/enrich.d.ts +1 -1
  4. package/dist/hooks/update.d.ts +1 -1
  5. package/dist/index.d.ts +2 -3
  6. package/dist/index.js +2 -4
  7. package/dist/models/CustomFieldDefinition.d.ts +1 -1
  8. package/dist/models/CustomFieldDefinition.js +11 -6
  9. package/dist/models/CustomFieldValue.js +3 -8
  10. package/dist/repository/definition.d.ts +2 -2
  11. package/dist/repository/value.d.ts +2 -2
  12. package/dist/scopes/filter.d.ts +8 -3
  13. package/dist/scopes/filter.js +36 -27
  14. package/dist/tests/mocks/definition.mock.d.ts +3 -3
  15. package/dist/tests/mocks/definition.mock.js +8 -10
  16. package/dist/types/index.d.ts +2 -2
  17. package/dist/utils/constants/index.d.ts +13 -0
  18. package/dist/utils/constants/index.js +15 -2
  19. package/dist/utils/helpers/index.d.ts +3 -3
  20. package/dist/utils/helpers/index.js +3 -3
  21. package/dist/utils/validations/index.d.ts +2 -2
  22. package/dist/utils/validations/index.js +15 -15
  23. package/dist/utils/validations/{custom-fields.js → schema/custom-fields.js} +0 -1
  24. package/dist/utils/validations/type.d.ts +10 -14
  25. package/dist/utils/validations/type.js +0 -48
  26. package/dist/utils/validations/validators/index.d.ts +14 -0
  27. package/dist/utils/validations/validators/index.js +33 -0
  28. package/dist/utils/validations/validators/select.validator.d.ts +5 -0
  29. package/dist/utils/validations/validators/select.validator.js +9 -0
  30. package/dist/utils/validations/validators/status.validator.d.ts +12 -0
  31. package/dist/utils/validations/validators/status.validator.js +9 -0
  32. package/package.json +9 -8
  33. package/src/api/v1/definition/index.ts +1 -1
  34. package/src/api/v1/definition/validations.ts +20 -1
  35. package/src/hooks/create.ts +1 -1
  36. package/src/hooks/enrich.ts +4 -4
  37. package/src/hooks/update.ts +1 -1
  38. package/src/index.ts +2 -3
  39. package/src/models/CustomFieldDefinition.ts +7 -5
  40. package/src/models/CustomFieldValue.ts +1 -5
  41. package/src/repository/definition.ts +2 -5
  42. package/src/repository/value.ts +2 -2
  43. package/src/scopes/filter.ts +43 -30
  44. package/src/tests/functional/searching/index.ts +1 -1
  45. package/src/tests/mocks/definition.mock.ts +6 -8
  46. package/src/types/index.ts +2 -2
  47. package/src/utils/constants/index.ts +14 -1
  48. package/src/utils/helpers/index.ts +5 -5
  49. package/src/utils/init.ts +1 -1
  50. package/src/utils/validations/index.ts +18 -15
  51. package/src/utils/validations/{custom-fields.ts → schema/custom-fields.ts} +0 -1
  52. package/src/utils/validations/type.ts +14 -40
  53. package/src/utils/validations/validators/index.ts +31 -0
  54. package/src/utils/validations/validators/select.validator.ts +11 -0
  55. package/src/utils/validations/validators/status.validator.ts +18 -0
  56. package/dist/utils/validations/custom.d.ts +0 -15
  57. package/dist/utils/validations/custom.js +0 -42
  58. package/dist/utils/validations/validators.d.ts +0 -12
  59. package/dist/utils/validations/validators.js +0 -33
  60. package/src/utils/validations/custom.ts +0 -39
  61. package/src/utils/validations/validators.ts +0 -34
  62. /package/dist/utils/validations/{custom-fields.d.ts → schema/custom-fields.d.ts} +0 -0
@@ -0,0 +1,33 @@
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.validators = exports.CustomValidationTypes = void 0;
7
+ const joi_1 = __importDefault(require("joi"));
8
+ const constants_1 = require("../../constants");
9
+ const select_validator_1 = require("./select.validator");
10
+ const status_validator_1 = require("./status.validator");
11
+ /**
12
+ * Custom field types that must have custom validation.
13
+ * TODO: remove this after moving to schema-based validation
14
+ */
15
+ exports.CustomValidationTypes = {
16
+ [constants_1.CustomFieldDefinitionType.SELECT]: constants_1.CustomFieldDefinitionType.SELECT,
17
+ [constants_1.CustomFieldDefinitionType.STATUS]: constants_1.CustomFieldDefinitionType.STATUS,
18
+ };
19
+ /**
20
+ * Validators for custom fields
21
+ */
22
+ exports.validators = {
23
+ [constants_1.CustomFieldDefinitionType.SELECT]: select_validator_1.validateSelect,
24
+ [constants_1.CustomFieldDefinitionType.STATUS]: status_validator_1.validateStatus,
25
+ [constants_1.CustomFieldDefinitionType.TEXT]: (value) => (typeof value === 'string'),
26
+ [constants_1.CustomFieldDefinitionType.NUMBER]: (value) => (typeof value === 'number'),
27
+ [constants_1.CustomFieldDefinitionType.BOOLEAN]: (value) => (typeof value === 'boolean'),
28
+ [constants_1.CustomFieldDefinitionType.DATE]: (value) => (!joi_1.default.date().validate(value).error),
29
+ [constants_1.CustomFieldDefinitionType.DATETIME]: (value) => (!joi_1.default.date().validate(value).error),
30
+ [constants_1.CustomFieldDefinitionType.IMAGE]: (value) => (!joi_1.default.array().min(1).unique()
31
+ .items(joi_1.default.string().uri())
32
+ .validate(value).error),
33
+ };
@@ -0,0 +1,5 @@
1
+ import type { Validator } from '../type';
2
+ /**
3
+ * Validate that the value is one of the select values
4
+ */
5
+ export declare const validateSelect: Validator<string, string[]>;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateSelect = void 0;
4
+ /**
5
+ * Validate that the value is one of the select values
6
+ */
7
+ const validateSelect = (value, selectValues) => (Array.isArray(selectValues)
8
+ && selectValues.includes(value));
9
+ exports.validateSelect = validateSelect;
@@ -0,0 +1,12 @@
1
+ import type { Validator } from '../type';
2
+ type StatusColor = string | null;
3
+ type StatusValue = string;
4
+ type StatusOption = {
5
+ value: StatusValue;
6
+ color: StatusColor;
7
+ };
8
+ /**
9
+ * Validate that the value is one of the status values
10
+ */
11
+ export declare const validateStatus: Validator<StatusValue, StatusOption[]>;
12
+ export {};
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateStatus = void 0;
4
+ /**
5
+ * Validate that the value is one of the status values
6
+ */
7
+ const validateStatus = (value, statusValues) => (Array.isArray(statusValues)
8
+ && statusValues.some((status) => status.value === value));
9
+ exports.validateStatus = validateStatus;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/sadot",
3
- "version": "0.6.4-beta.1",
3
+ "version": "0.6.5",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -11,7 +11,8 @@
11
11
  "coverage": "jest --coverage --forceExit --runInBand && rm -rf ./coverage",
12
12
  "build-to-local-repo": "npm run build && cp -r dist/* ../$REPO/node_modules/$npm_package_name/dist",
13
13
  "dev": "nodemon",
14
- "watch": "npm-watch build-to-local-repo"
14
+ "watch": "npm-watch build-to-local-repo",
15
+ "publish-dev": "npm run build && npm publish --tag dev"
15
16
  },
16
17
  "watch": {
17
18
  "build-to-local-repo": {
@@ -45,16 +46,16 @@
45
46
  "@types/express": "^4.17.17",
46
47
  "@types/jest": "^27.0.9",
47
48
  "@types/uuid": "^9.0.2",
48
- "@typescript-eslint/eslint-plugin": "^4.8.1",
49
- "@typescript-eslint/parser": "^4.33.0",
50
- "eslint": "^7.13.0",
49
+ "@typescript-eslint/eslint-plugin": "^6.4.0",
50
+ "@typescript-eslint/parser": "^6.4.0",
51
+ "eslint": "^7.32.0",
51
52
  "eslint-config-airbnb-typescript": "^12.0.0",
52
53
  "eslint-plugin-import": "^2.22.1",
53
- "jest": "^27.0.5",
54
+ "jest": "^29.7.0",
55
+ "ts-jest": "^29.1.2",
54
56
  "npm-watch": "^0.11.0",
55
- "ts-jest": "^27.0.3",
56
57
  "ts-node": "^8.6.2",
57
- "typescript": "^4.9.5",
58
+ "typescript": "^5.3.3",
58
59
  "typescript-eslint": "^0.0.1-alpha.0"
59
60
  },
60
61
  "author": "Autofleet",
@@ -3,7 +3,7 @@ import { Router } from 'express';
3
3
  import type { Request, Response } from 'express';
4
4
  import handleError from '../errors';
5
5
  import * as DefinitionRepo from '../../../repository/definition';
6
- import { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../../../types/definition';
6
+ import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../../../types/definition';
7
7
  import { validateCustomFieldDefinitionCreation, validateCustomFieldDefinitionUpdate } from './validations';
8
8
  import logger from '../../../utils/logger';
9
9
 
@@ -1,10 +1,29 @@
1
1
  import Joi from 'joi';
2
- import { CustomFieldDefinitionType } from '../../../utils/validations/type';
2
+ import { CustomFieldDefinitionType } from '../../../utils/constants';
3
3
 
4
+ const statusValidationObjectSchema = Joi.array().items(
5
+ Joi.object({
6
+ value: Joi.string().required(),
7
+ color: Joi.string().required(),
8
+ }),
9
+ ).min(1).unique('value');
10
+ /**
11
+ * Schema for the validation of custom field definition
12
+ * The only custom validation is for
13
+ * {@link CustomFieldDefinitionType.SELECT SELECT}
14
+ * and
15
+ * {@link CustomFieldDefinitionType.STATUS STATUS}
16
+ * field types.
17
+ * The rest of the field types are validated by Joi
18
+ */
4
19
  const ValidationSchema = Joi.when('fieldType', {
5
20
  is: CustomFieldDefinitionType.SELECT,
6
21
  then: Joi.array().items(Joi.string()).min(1).unique(),
7
22
  otherwise: Joi.any(),
23
+ }).when('fieldType', {
24
+ is: CustomFieldDefinitionType.STATUS,
25
+ then: statusValidationObjectSchema,
26
+ otherwise: Joi.any(),
8
27
  });
9
28
 
10
29
  const CustomFieldDefinitionCreationSchema = Joi.object({
@@ -2,7 +2,7 @@ import logger from '../utils/logger';
2
2
  import * as ValueRepo from '../repository/value';
3
3
  import * as DefinitionRepo from '../repository/definition';
4
4
  import { MissingRequiredCustomFieldError } from '../errors';
5
- import { ModelOptions } from '../types';
5
+ import type { ModelOptions } from '../types';
6
6
  import applyScopeToInstance from '../utils/scopeAttributes';
7
7
 
8
8
  /**
@@ -1,10 +1,10 @@
1
1
  /* eslint-disable no-param-reassign */
2
2
  import * as ValueRepo from '../repository/value';
3
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 { ModelOptions } from '../types';
4
+ import type CustomFieldValue from '../models/CustomFieldValue';
5
+ import type CustomFieldDefinition from '../models/CustomFieldDefinition';
6
+ import type { SerializedCustomFields } from '../types/definition';
7
+ import type { ModelOptions } from '../types';
8
8
  import applyScopeToInstance from '../utils/scopeAttributes';
9
9
 
10
10
  type SupportedHookTypes = 'afterFind' | 'afterCreate' | 'afterUpdate';
@@ -1,6 +1,6 @@
1
1
  import logger from '../utils/logger';
2
2
  import * as ValueRepo from '../repository/value';
3
- import { ModelOptions } from '../types';
3
+ import type { ModelOptions } from '../types';
4
4
  import applyScopeToInstance from '../utils/scopeAttributes';
5
5
 
6
6
  /**
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Application } from 'express';
2
- import { Sequelize } from 'sequelize-typescript';
2
+ import type { Sequelize } from 'sequelize-typescript';
3
3
  import {
4
4
  initTables, initTestModels,
5
5
  } from './models';
@@ -11,13 +11,12 @@ import {
11
11
  addHooks, addScopes, applyCustomAssociation, removeHooks,
12
12
  } from './utils/init';
13
13
 
14
- export * from './utils/validations/custom-fields';
14
+ export * from './utils/validations/schema/custom-fields';
15
15
 
16
16
  export * from './utils/constants';
17
17
 
18
18
  export * from './utils/helpers';
19
19
 
20
- export { CustomFieldDefinitionType } from './utils/validations/type';
21
20
  /**
22
21
  * Adding custom fields enrichment to the models inside the MODELS_FILE_NAME json file
23
22
  * @see {@link 'custom-fields/config'} for configurations
@@ -1,5 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- /* eslint-disable indent */
3
1
  import {
4
2
  Table,
5
3
  Column,
@@ -12,11 +10,12 @@ import {
12
10
  AfterSave,
13
11
  Is,
14
12
  } from 'sequelize-typescript';
15
- import { validateValidation } from '../utils/validations/custom';
16
- import { CustomFieldDefinitionType } from '../utils/validations/type';
13
+ import { CustomFieldDefinitionType } from '../utils/constants';
14
+ import { CustomValidationTypes } from '../utils/validations/validators';
17
15
  import { CustomFieldValue } from '.';
18
16
  import { sendDimEvent } from '../events';
19
17
  import { UnsupportedCustomFieldTypeError, UnsupportedCustomValidationError } from '../errors';
18
+ import logger from '../utils/logger';
20
19
 
21
20
  @DefaultScope(() => ({ where: { disabled: false } }))
22
21
  @Table({
@@ -30,7 +29,10 @@ import { UnsupportedCustomFieldTypeError, UnsupportedCustomValidationError } fro
30
29
  timestamps: true,
31
30
  validate: {
32
31
  validationByType(this: CustomFieldDefinition) {
33
- if (!validateValidation(this.fieldType, this.validation)) {
32
+ // Verify the validation is provided if needed
33
+ if (!this.validation && CustomValidationTypes[this.fieldType]) {
34
+ // TODO: enforce the validation-object schema based on the fieldType
35
+ logger.error(`No custom validation for custom field type ${this.fieldType} found`);
34
36
  throw new UnsupportedCustomValidationError(`Validation provided for "${this.fieldType}" is not supported`);
35
37
  }
36
38
  },
@@ -1,5 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- /* eslint-disable indent */
3
1
  import {
4
2
  Table,
5
3
  Column,
@@ -14,13 +12,11 @@ import {
14
12
  BeforeUpdate,
15
13
  BeforeBulkCreate,
16
14
  BeforeBulkUpdate,
17
- Scopes,
18
15
  } from 'sequelize-typescript';
19
16
  import { sendDimEvent } from '../events';
20
17
  import { CustomFieldDefinition } from '.';
21
- import validateValue from '../utils/validations';
18
+ import { validateValue } from '../utils/validations';
22
19
  import * as CustomFieldDefinitionRepo from '../repository/definition';
23
- import logger from '../utils/logger';
24
20
  import { InvalidValueError } from '../errors';
25
21
 
26
22
  @Table({
@@ -1,10 +1,7 @@
1
- import {
2
- FindOptions,
3
- Op, WhereOptions,
4
- } from 'sequelize';
1
+ import { Op, type FindOptions, type WhereOptions } from 'sequelize';
5
2
  import { CustomFieldDefinition } from '../models';
6
3
  import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../types/definition';
7
- import { ModelOptions } from '../types';
4
+ import type { ModelOptions } from '../types';
8
5
 
9
6
  export const create = (data: CreateCustomFieldDefinition): Promise<CustomFieldDefinition> =>
10
7
  CustomFieldDefinition.create(data);
@@ -2,10 +2,10 @@
2
2
  import type { FindOptions, WhereOptions } from 'sequelize';
3
3
  import { CustomFieldValue, CustomFieldDefinition } from '../models';
4
4
  import * as DefinitionRepo from './definition';
5
- import { CreateCustomFieldValue, ValuesToUpdate } from '../types/value';
5
+ import type { CreateCustomFieldValue, ValuesToUpdate } from '../types/value';
6
6
  import logger from '../utils/logger';
7
7
  import { MissingDefinitionError } from '../errors';
8
- import { ModelOptions } from '../types';
8
+ import type { ModelOptions } from '../types';
9
9
 
10
10
  export const findByModelIdAndDefinition = async (modelId: string, customFieldDefinitionId: string) =>
11
11
  CustomFieldValue.findAll({ where: { modelId, customFieldDefinitionId }, include: [CustomFieldDefinition] });
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
- import { Op, WhereOptions } from 'sequelize';
2
+ import { Op, type WhereOptions } from 'sequelize';
3
3
  import { Sequelize } from 'sequelize-typescript';
4
4
  import { customFields } from '@autofleet/common-types';
5
5
 
@@ -10,7 +10,11 @@ const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
10
10
  * Currently supporting strings and arrays of strings.
11
11
  * More types to be added (TBA).
12
12
  */
13
- export type ConditionValue = string | string[];
13
+ type ConditionWithOperator = {
14
+ operator: string;
15
+ value: string;
16
+ };
17
+ export type ConditionValue = ConditionWithOperator | ConditionWithOperator[] | string | string[];
14
18
 
15
19
  export type CustomFieldSort = {
16
20
  field: string;
@@ -21,6 +25,13 @@ export type CustomFieldFilterOptions = {
21
25
  where?: WhereOptions;
22
26
  }
23
27
 
28
+ const castIfNeeded = (conditionValue: string): string => {
29
+ if (!Number.isNaN(Date.parse(conditionValue))) {
30
+ return '::timestamp';
31
+ }
32
+ return '';
33
+ };
34
+ const AND_DELIMETER = ' AND ';
24
35
  /**
25
36
  * A Sequelize scope for filtering models by custom fields.
26
37
  * This scope builds a WHERE clause to be applied on the main query.
@@ -37,24 +48,33 @@ export const customFieldsFilterScope = (
37
48
  // Build the WHERE clause for custom field filtering
38
49
  const conditionsStrings = Object.entries(conditions)
39
50
  .map(
40
- ([key, value]) => {
41
- if (Array.isArray(value)) {
42
- if (value.length === 0) {
51
+ ([key, condition]) => {
52
+ if (Array.isArray(condition)) {
53
+ if (condition.length === 0) {
43
54
  // if empty array, the condition is ignored
44
55
  return false;
45
56
  }
46
- const values = value.map((v) => `'${v}'`).join(',');
47
- return `custom_fields->>'${key}' IN (${values})`;
57
+ if (typeof condition[0] === 'string') {
58
+ const values = condition.map((v) => `'${v}'`).join(',');
59
+ return `(custom_fields->>'${key}') IN (${values})`;
60
+ }
61
+ return condition
62
+ .map((c) => `(custom_fields->>'${key}')${castIfNeeded(c.value)} ${c.operator} '${c.value}'`).join(AND_DELIMETER);
48
63
  }
49
- return `custom_fields->>'${key}' = '${value}'`;
64
+ if (typeof condition === 'string') {
65
+ return `(custom_fields->>'${key}')${castIfNeeded(condition)} = '${condition}'`;
66
+ }
67
+ if (condition?.operator) {
68
+ return `(custom_fields->>'${key}')${castIfNeeded(condition.value)} ${condition.operator} '${condition.value}'`;
69
+ }
70
+ return false;
50
71
  },
51
72
  )
52
73
  .filter(Boolean);
53
-
54
74
  if (conditionsStrings.length === 0) {
55
75
  return {};
56
76
  }
57
- const customFieldConditions = conditionsStrings.join(' AND ');
77
+ const customFieldConditions = conditionsStrings.join(AND_DELIMETER);
58
78
 
59
79
  const subQuery = `${'SELECT model_id FROM ('
60
80
  + 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
@@ -62,8 +82,7 @@ export const customFieldsFilterScope = (
62
82
  + 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
63
83
  + `AND cd.model_type = '${name}' `
64
84
  + 'GROUP BY cv.model_id'
65
- + ') AS CustomFieldAggregation '
66
- + 'WHERE '}${customFieldConditions}`;
85
+ + ') AS CustomFieldAggregation WHERE '}${customFieldConditions}`;
67
86
 
68
87
  return {
69
88
  where: {
@@ -82,26 +101,20 @@ export const customFieldsSortScope = (
82
101
  if (!sort || sort.length === 0) {
83
102
  return {};
84
103
  }
104
+
105
+ console.log('sort', sort);
106
+ console.log('name', name);
107
+ console.log('Object.entries(sort)', Object.entries(sort));
85
108
  const includes = Object.entries(sort).map(([key]) =>
86
- ([
87
- Sequelize.literal(`(
88
- SELECT custom_fields->>'${key}'
89
- FROM (
90
- SELECT
91
- cv.model_id,
92
- jsonb_object_agg(cd.name, cv.value) AS custom_fields
93
- FROM custom_field_values AS cv
94
- INNER JOIN custom_field_definitions AS cd
95
- ON cv.custom_field_definition_id = cd.id
96
- AND cd.model_type = '${name}'
97
- WHERE cv.model_id = "${name}"."id"
98
- GROUP BY cv.model_id
99
- ) AS CustomFieldAggregation
100
- )`),
101
- `customFields_${key}`,
102
- ]));
109
+ Sequelize.literal(`(select CustomFieldAggregation.custom_fields->>'${key}'
110
+ from (SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields
111
+ FROM custom_field_values AS cv
112
+ INNER JOIN custom_field_definitions AS cd
113
+ ON cv.custom_field_definition_id = cd.id AND cd.model_type = '${name}'
114
+ where cv.model_id = "${name}"."id"
115
+ GROUP BY cv.model_id) AS CustomFieldAggregation) as "customFields.${key}"`));
116
+ const orders = Object.entries(sort).map(([key, value]) => Sequelize.literal(`"customFields.${key}" ${value}`));
103
117
 
104
- const orders = Object.entries(sort).map(([key, value]) => Sequelize.literal(`"customFields_${key}" ${value}`));
105
118
  return {
106
119
  attributes: {
107
120
  include: includes,
@@ -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 { CreateCustomFieldDefinition, CustomFieldDefinitionDTO } from '../../types/definition';
2
+ import type { 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 enumField = (modelType: string, options): CreateCustomFieldDefinition => ({
37
+ export const selectField = (modelType: string, options): CreateCustomFieldDefinition => ({
38
38
  name: 'choices',
39
39
  modelType,
40
40
  fieldType: 'select',
@@ -43,13 +43,11 @@ export const enumField = (modelType: string, options): CreateCustomFieldDefiniti
43
43
  entityType: 'fleetId',
44
44
  });
45
45
 
46
- export const rangeField = (modelType: string): CreateCustomFieldDefinition => ({
47
- name: 'ranges',
46
+ export const statusField = (modelType: string, options): CreateCustomFieldDefinition => ({
47
+ name: 'lifecycle',
48
48
  modelType,
49
- fieldType: 'number',
50
- validation: {
51
- between: [10, 12],
52
- },
49
+ fieldType: 'status',
50
+ validation: options,
53
51
  entityId: uuidv4(),
54
52
  entityType: 'fleetId',
55
53
  });
@@ -1,5 +1,5 @@
1
- import { IncludeOptions } from 'sequelize';
2
- import { ModelCtor } from 'sequelize-typescript';
1
+ import type { IncludeOptions } from 'sequelize';
2
+ import type { ModelCtor } from 'sequelize-typescript';
3
3
 
4
4
  export type ModelFetcher = (name: string) => any;
5
5
 
@@ -2,9 +2,22 @@ import { customFields } from '@autofleet/common-types';
2
2
 
3
3
  const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
4
4
 
5
- // eslint-disable-next-line import/prefer-default-export
6
5
  export const supportedEntities = ['businessModelId', 'fleetId', 'demandSourceId'];
7
6
 
7
+ /**
8
+ * Supported custom field types
9
+ */
10
+ export enum CustomFieldDefinitionType {
11
+ NUMBER = 'number',
12
+ BOOLEAN = 'boolean',
13
+ DATE = 'date',
14
+ DATETIME = 'datetime',
15
+ TEXT = 'text',
16
+ IMAGE = 'image',
17
+ SELECT = 'select',
18
+ STATUS = 'status',
19
+ }
20
+
8
21
  export {
9
22
  /** @deprecated Use the value from `@autofleet/common-types` instead */
10
23
  CUSTOM_FIELDS_FILTER_SCOPE,
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
- import { WhereOptions, Op, BindOrReplacements } from 'sequelize';
3
- import { ModelStatic, Sequelize } from 'sequelize-typescript';
4
- import { CustomFieldDefinitionType } from '../validations/type';
2
+ import { type WhereOptions, Op, type BindOrReplacements } from 'sequelize';
3
+ import { type ModelStatic, Sequelize } from 'sequelize-typescript';
4
+ import { CustomFieldDefinitionType } from '../constants';
5
5
 
6
6
  /**
7
7
  * Builds a WHERE clause and replacements for free-text search by custom fields.
@@ -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,5 +1,5 @@
1
1
  import { DataTypes } from 'sequelize';
2
- import { ModelCtor } from 'sequelize-typescript';
2
+ import type { ModelCtor } from 'sequelize-typescript';
3
3
  import { customFields } from '@autofleet/common-types';
4
4
  import { CUSTOM_FIELDS_SORT_SCOPE } from '@autofleet/common-types/lib/custom-fields';
5
5
  import {
@@ -1,19 +1,22 @@
1
- import customValidation from './custom';
2
- import validateValueType from './type';
1
+ import type { CustomFieldDefinitionType } from '../constants';
2
+ import { validators } from './validators';
3
3
 
4
- const validateValue = (value, valueType, customValidations) => {
5
- if (!validateValueType(value, valueType)) {
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
6
12
  return false;
7
13
  }
8
- if (customValidations) {
9
- return customValidation(value, valueType, customValidations);
10
- }
11
-
12
- // if (validations.required && !value) {
13
- // return false;
14
- // }
15
-
16
- return true;
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
+ */
17
22
  };
18
-
19
- export default validateValue;
@@ -1,4 +1,3 @@
1
- /* eslint-disable import/prefer-default-export */
2
1
  import Joi from 'joi';
3
2
 
4
3
  const CustomFieldsSchema = Joi.object().pattern(
@@ -1,45 +1,19 @@
1
- import Joi from 'joi';
1
+ import type { CustomFieldDefinitionType } from '../constants';
2
+
2
3
  /**
3
- * Supported custom field types
4
+ * Validator is a function that validates a custom-field `value`,
5
+ * against a custom-field definition `validation object`.
6
+ * @returns `true` if the value is valid, `false` otherwise.
4
7
  */
5
- // eslint-disable-next-line no-shadow
6
- export enum CustomFieldDefinitionType {
7
- NUMBER = 'number',
8
- BOOLEAN = 'boolean',
9
- DATE = 'date',
10
- DATETIME = 'datetime',
11
- TEXT = 'text',
12
- IMAGE = 'image',
13
- SELECT = 'select',
14
- }
8
+ export type Validator<Value, DefinitionValidationObject> = (
9
+ value: Value,
10
+ validation?: DefinitionValidationObject
11
+ ) => boolean;
12
+
15
13
  /**
16
- * Validate that the given value is really of type "valueType"
17
- * TODO: verify that required field not set to null
14
+ * Validators is a map of custom-field types to their respective validators.
15
+ * The key is the custom-field type, and the value is the validator function.
18
16
  */
19
- const validateValueType = (value: unknown, valueType: CustomFieldDefinitionType): boolean => {
20
- if (value === null) {
21
- // Null is always allowed
22
- return true;
23
- }
24
-
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
- }
17
+ export type Validators = {
18
+ [K in CustomFieldDefinitionType]: Validator<unknown, unknown>;
43
19
  };
44
-
45
- export default validateValueType;