@autofleet/sadot 0.6.7-beta.1 → 0.6.8-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 (44) 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 +10 -9
  13. package/dist/scopes/filter.js +67 -24
  14. package/dist/tests/helpers/database-config.d.ts +4 -1
  15. package/dist/tests/helpers/database-config.js +1 -1
  16. package/dist/tests/mocks/definition.mock.d.ts +3 -3
  17. package/dist/tests/mocks/definition.mock.js +8 -10
  18. package/dist/types/index.d.ts +2 -2
  19. package/dist/utils/constants/index.d.ts +14 -0
  20. package/dist/utils/constants/index.js +16 -2
  21. package/dist/utils/helpers/index.d.ts +4 -3
  22. package/dist/utils/helpers/index.js +9 -4
  23. package/dist/utils/validations/index.d.ts +2 -2
  24. package/dist/utils/validations/index.js +15 -15
  25. package/dist/utils/validations/{custom-fields.js → schema/custom-fields.js} +0 -1
  26. package/dist/utils/validations/type.d.ts +10 -14
  27. package/dist/utils/validations/type.js +0 -48
  28. package/dist/utils/validations/validators/index.d.ts +14 -0
  29. package/dist/utils/validations/validators/index.js +40 -0
  30. package/dist/utils/validations/validators/select.validator.d.ts +5 -0
  31. package/dist/utils/validations/validators/select.validator.js +9 -0
  32. package/dist/utils/validations/validators/status.validator.d.ts +12 -0
  33. package/dist/utils/validations/validators/status.validator.js +9 -0
  34. package/package.json +4 -3
  35. package/src/scopes/filter.ts +50 -26
  36. package/src/tests/helpers/database-config.ts +1 -1
  37. package/src/utils/constants/index.ts +1 -0
  38. package/src/utils/helpers/index.ts +5 -0
  39. package/src/utils/validations/validators/index.ts +7 -0
  40. package/dist/utils/validations/custom.d.ts +0 -15
  41. package/dist/utils/validations/custom.js +0 -42
  42. package/dist/utils/validations/validators.d.ts +0 -12
  43. package/dist/utils/validations/validators.js +0 -33
  44. /package/dist/utils/validations/{custom-fields.d.ts → schema/custom-fields.d.ts} +0 -0
@@ -5,17 +5,34 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.validateCustomFieldDefinitionUpdate = exports.validateCustomFieldDefinitionCreation = void 0;
7
7
  const joi_1 = __importDefault(require("joi"));
8
- const type_1 = require("../../../utils/validations/type");
8
+ const constants_1 = require("../../../utils/constants");
9
+ const statusValidationObjectSchema = joi_1.default.array().items(joi_1.default.object({
10
+ value: joi_1.default.string().required(),
11
+ color: joi_1.default.string().required(),
12
+ })).min(1).unique('value');
13
+ /**
14
+ * Schema for the validation of custom field definition
15
+ * The only custom validation is for
16
+ * {@link CustomFieldDefinitionType.SELECT SELECT}
17
+ * and
18
+ * {@link CustomFieldDefinitionType.STATUS STATUS}
19
+ * field types.
20
+ * The rest of the field types are validated by Joi
21
+ */
9
22
  const ValidationSchema = joi_1.default.when('fieldType', {
10
- is: type_1.CustomFieldDefinitionType.SELECT,
23
+ is: constants_1.CustomFieldDefinitionType.SELECT,
11
24
  then: joi_1.default.array().items(joi_1.default.string()).min(1).unique(),
12
25
  otherwise: joi_1.default.any(),
26
+ }).when('fieldType', {
27
+ is: constants_1.CustomFieldDefinitionType.STATUS,
28
+ then: statusValidationObjectSchema,
29
+ otherwise: joi_1.default.any(),
13
30
  });
14
31
  const CustomFieldDefinitionCreationSchema = joi_1.default.object({
15
32
  name: joi_1.default.string().required(),
16
33
  displayName: joi_1.default.string().required(),
17
34
  validation: ValidationSchema,
18
- fieldType: joi_1.default.string().valid(...Object.values(type_1.CustomFieldDefinitionType)).required(),
35
+ fieldType: joi_1.default.string().valid(...Object.values(constants_1.CustomFieldDefinitionType)).required(),
19
36
  entityId: joi_1.default.string().guid().required(),
20
37
  entityType: joi_1.default.string().required(),
21
38
  description: joi_1.default.string(),
@@ -25,7 +42,7 @@ const CustomFieldDefinitionCreationSchema = joi_1.default.object({
25
42
  const CustomFieldDefinitionUpdateSchema = joi_1.default.object({
26
43
  displayName: joi_1.default.string(),
27
44
  validation: ValidationSchema,
28
- fieldType: joi_1.default.string().valid(...Object.values(type_1.CustomFieldDefinitionType)),
45
+ fieldType: joi_1.default.string().valid(...Object.values(constants_1.CustomFieldDefinitionType)),
29
46
  description: joi_1.default.string().allow(null),
30
47
  required: joi_1.default.boolean(),
31
48
  disabled: joi_1.default.boolean(),
@@ -1,4 +1,4 @@
1
- import { ModelOptions } from '../types';
1
+ import type { ModelOptions } from '../types';
2
2
  /**
3
3
  * A hook to create the custom fields when updating a model (more then one instance).
4
4
  */
@@ -1,4 +1,4 @@
1
- import { ModelOptions } from '../types';
1
+ import type { ModelOptions } from '../types';
2
2
  type SupportedHookTypes = 'afterFind' | 'afterCreate' | 'afterUpdate';
3
3
  /**
4
4
  * A hook to attach the custom fields when fetching a model instances.
@@ -1,4 +1,4 @@
1
- import { ModelOptions } from '../types';
1
+ import type { ModelOptions } from '../types';
2
2
  /**
3
3
  * A hook to update the custom fields when updating a model (more then one instance).
4
4
  */
package/dist/index.d.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  import type { Application } from 'express';
2
- import { Sequelize } from 'sequelize-typescript';
2
+ import type { Sequelize } from 'sequelize-typescript';
3
3
  import type { CustomFieldOptions, ModelFetcher } from './types';
4
- export * from './utils/validations/custom-fields';
4
+ export * from './utils/validations/schema/custom-fields';
5
5
  export * from './utils/constants';
6
6
  export * from './utils/helpers';
7
- export { CustomFieldDefinitionType } from './utils/validations/type';
8
7
  /**
9
8
  * Adding custom fields enrichment to the models inside the MODELS_FILE_NAME json file
10
9
  * @see {@link 'custom-fields/config'} for configurations
package/dist/index.js CHANGED
@@ -17,17 +17,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.disableCustomFields = exports.CustomFieldDefinitionType = void 0;
20
+ exports.disableCustomFields = void 0;
21
21
  const models_1 = require("./models");
22
22
  const api_1 = __importDefault(require("./api"));
23
23
  const db_1 = __importDefault(require("./utils/db"));
24
24
  const logger_1 = __importDefault(require("./utils/logger"));
25
25
  const init_1 = require("./utils/init");
26
- __exportStar(require("./utils/validations/custom-fields"), exports);
26
+ __exportStar(require("./utils/validations/schema/custom-fields"), exports);
27
27
  __exportStar(require("./utils/constants"), exports);
28
28
  __exportStar(require("./utils/helpers"), exports);
29
- var type_1 = require("./utils/validations/type");
30
- Object.defineProperty(exports, "CustomFieldDefinitionType", { enumerable: true, get: function () { return type_1.CustomFieldDefinitionType; } });
31
29
  /**
32
30
  * Adding custom fields enrichment to the models inside the MODELS_FILE_NAME json file
33
31
  * @see {@link 'custom-fields/config'} for configurations
@@ -1,5 +1,5 @@
1
1
  import { Model } from 'sequelize-typescript';
2
- import { CustomFieldDefinitionType } from '../utils/validations/type';
2
+ import { CustomFieldDefinitionType } from '../utils/constants';
3
3
  import { CustomFieldValue } from '.';
4
4
  declare class CustomFieldDefinition extends Model {
5
5
  id: string;
@@ -8,15 +8,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
- /* eslint-disable @typescript-eslint/no-unused-vars */
13
- /* eslint-disable indent */
14
15
  const sequelize_typescript_1 = require("sequelize-typescript");
15
- const custom_1 = require("../utils/validations/custom");
16
- const type_1 = require("../utils/validations/type");
16
+ const constants_1 = require("../utils/constants");
17
+ const validators_1 = require("../utils/validations/validators");
17
18
  const _1 = require(".");
18
19
  const events_1 = require("../events");
19
20
  const errors_1 = require("../errors");
21
+ const logger_1 = __importDefault(require("../utils/logger"));
20
22
  let CustomFieldDefinition = class CustomFieldDefinition extends sequelize_typescript_1.Model {
21
23
  static displayNameDefaultValue(instance) {
22
24
  if (!instance?.displayName) {
@@ -57,7 +59,7 @@ __decorate([
57
59
  ], CustomFieldDefinition.prototype, "displayName", void 0);
58
60
  __decorate([
59
61
  (0, sequelize_typescript_1.Is)('SupportedType', (value) => {
60
- if (!Object.values(type_1.CustomFieldDefinitionType).includes(value)) {
62
+ if (!Object.values(constants_1.CustomFieldDefinitionType).includes(value)) {
61
63
  throw new errors_1.UnsupportedCustomFieldTypeError(`"${value}" is not a supported type.`);
62
64
  }
63
65
  }),
@@ -155,7 +157,10 @@ CustomFieldDefinition = __decorate([
155
157
  timestamps: true,
156
158
  validate: {
157
159
  validationByType() {
158
- if (!(0, custom_1.validateValidation)(this.fieldType, this.validation)) {
160
+ // Verify the validation is provided if needed
161
+ if (!this.validation && validators_1.CustomValidationTypes[this.fieldType]) {
162
+ // TODO: enforce the validation-object schema based on the fieldType
163
+ logger_1.default.error(`No custom validation for custom field type ${this.fieldType} found`);
159
164
  throw new errors_1.UnsupportedCustomValidationError(`Validation provided for "${this.fieldType}" is not supported`);
160
165
  }
161
166
  },
@@ -31,16 +31,11 @@ var __importStar = (this && this.__importStar) || function (mod) {
31
31
  var __metadata = (this && this.__metadata) || function (k, v) {
32
32
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
33
33
  };
34
- var __importDefault = (this && this.__importDefault) || function (mod) {
35
- return (mod && mod.__esModule) ? mod : { "default": mod };
36
- };
37
34
  Object.defineProperty(exports, "__esModule", { value: true });
38
- /* eslint-disable @typescript-eslint/no-unused-vars */
39
- /* eslint-disable indent */
40
35
  const sequelize_typescript_1 = require("sequelize-typescript");
41
36
  const events_1 = require("../events");
42
37
  const _1 = require(".");
43
- const validations_1 = __importDefault(require("../utils/validations"));
38
+ const validations_1 = require("../utils/validations");
44
39
  const CustomFieldDefinitionRepo = __importStar(require("../repository/definition"));
45
40
  const errors_1 = require("../errors");
46
41
  let CustomFieldValue = class CustomFieldValue extends sequelize_typescript_1.Model {
@@ -54,7 +49,7 @@ let CustomFieldValue = class CustomFieldValue extends sequelize_typescript_1.Mod
54
49
  instances.forEach((instance) => {
55
50
  const { validation, fieldType, } = definitions
56
51
  .find((definition) => definition.id === instance.customFieldDefinitionId);
57
- const isValid = (0, validations_1.default)(instance.value, fieldType, validation);
52
+ const isValid = (0, validations_1.validateValue)(instance.value, fieldType, validation);
58
53
  if (!isValid) {
59
54
  throw new errors_1.InvalidValueError(instance.value, fieldType);
60
55
  }
@@ -65,7 +60,7 @@ let CustomFieldValue = class CustomFieldValue extends sequelize_typescript_1.Mod
65
60
  // eslint-disable-next-line max-len
66
61
  const cfd = await CustomFieldDefinitionRepo.findById(customFieldDefinitionId, { withDisabled: true });
67
62
  const { validation, fieldType } = cfd;
68
- const isValid = (0, validations_1.default)(instance.value, fieldType, validation);
63
+ const isValid = (0, validations_1.validateValue)(instance.value, fieldType, validation);
69
64
  if (!isValid) {
70
65
  throw new errors_1.InvalidValueError(instance.value, fieldType);
71
66
  }
@@ -1,7 +1,7 @@
1
- import { FindOptions, WhereOptions } from 'sequelize';
1
+ import { type FindOptions, type WhereOptions } from 'sequelize';
2
2
  import { CustomFieldDefinition } from '../models';
3
3
  import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../types/definition';
4
- import { ModelOptions } from '../types';
4
+ import type { ModelOptions } from '../types';
5
5
  export declare const create: (data: CreateCustomFieldDefinition) => Promise<CustomFieldDefinition>;
6
6
  export declare const findAll: (where: WhereOptions, options?: any) => Promise<CustomFieldDefinition[]>;
7
7
  export declare const findByIds: (ids: string[], options?: any) => Promise<CustomFieldDefinition[]>;
@@ -1,7 +1,7 @@
1
1
  import type { FindOptions } from 'sequelize';
2
2
  import { CustomFieldValue } from '../models';
3
- import { CreateCustomFieldValue, ValuesToUpdate } from '../types/value';
4
- import { ModelOptions } from '../types';
3
+ import type { CreateCustomFieldValue, ValuesToUpdate } from '../types/value';
4
+ import type { ModelOptions } from '../types';
5
5
  export declare const findByModelIdAndDefinition: (modelId: string, customFieldDefinitionId: string) => Promise<CustomFieldValue[]>;
6
6
  export declare const create: (data: CreateCustomFieldValue, withAssociations?: boolean) => Promise<CustomFieldValue>;
7
7
  export declare const findAllValues: () => Promise<CustomFieldValue[]>;
@@ -1,32 +1,33 @@
1
- import { WhereOptions } from 'sequelize';
1
+ import { type WhereOptions } from 'sequelize';
2
2
  /**
3
3
  * Type representing possible condition values.
4
4
  * Currently supporting strings and arrays of strings.
5
5
  * More types to be added (TBA).
6
6
  */
7
- export type ConditionValue = string | string[];
7
+ type ConditionWithOperator = {
8
+ operator: string;
9
+ value: string;
10
+ };
11
+ export type ConditionValue = ConditionWithOperator | ConditionWithOperator[] | string | string[];
8
12
  export type CustomFieldSort = {
9
13
  field: string;
10
14
  direction: 'ASC' | 'DESC';
11
15
  };
12
16
  export type CustomFieldFilterOptions = {
13
17
  where?: WhereOptions;
18
+ replacements?: Record<string, string>;
14
19
  };
15
- /**
16
- * A Sequelize scope for filtering models by custom fields.
17
- * This scope builds a WHERE clause to be applied on the main query.
18
- *
19
- * @param {string} name - The model type name used to join custom_field_definitions.
20
- * @returns {Function} - A function that takes conditions and returns the Sequelize options object.
21
- */
22
20
  export declare const customFieldsFilterScope: (name: string) => (conditions: Record<string, ConditionValue>) => CustomFieldFilterOptions;
23
21
  export declare const scopeName = "filterByCustomFields";
24
22
  export declare const customFieldsSortScope: (name: string) => (sort: CustomFieldSort[]) => {
25
23
  attributes?: undefined;
26
24
  order?: undefined;
25
+ replacements?: undefined;
27
26
  } | {
28
27
  attributes: {
29
28
  include: (string | import("sequelize/types/utils").Literal)[][];
30
29
  };
31
30
  order: import("sequelize/types/utils").Literal[];
31
+ replacements: Record<string, string>;
32
32
  };
33
+ export {};
@@ -1,54 +1,90 @@
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.customFieldsSortScope = exports.scopeName = exports.customFieldsFilterScope = void 0;
4
7
  /* eslint-disable import/prefer-default-export */
5
8
  const sequelize_1 = require("sequelize");
6
9
  const sequelize_typescript_1 = require("sequelize-typescript");
7
10
  const common_types_1 = require("@autofleet/common-types");
11
+ const moment_1 = __importDefault(require("moment"));
12
+ const helpers_1 = require("../utils/helpers");
8
13
  const { CUSTOM_FIELDS_FILTER_SCOPE } = common_types_1.customFields;
9
- /**
10
- * A Sequelize scope for filtering models by custom fields.
11
- * This scope builds a WHERE clause to be applied on the main query.
12
- *
13
- * @param {string} name - The model type name used to join custom_field_definitions.
14
- * @returns {Function} - A function that takes conditions and returns the Sequelize options object.
15
- */
14
+ const castIfNeeded = (conditionValue) => {
15
+ if (moment_1.default.isDate(conditionValue)) {
16
+ return '::timestamp';
17
+ }
18
+ if (!Number.isNaN(Number(conditionValue))) {
19
+ return '::numeric';
20
+ }
21
+ return '';
22
+ };
23
+ const AND_DELIMETER = ' AND ';
16
24
  const customFieldsFilterScope = (name) => (conditions) => {
17
25
  if (!conditions || Object.keys(conditions).length === 0) {
18
26
  return {};
19
27
  }
28
+ const randomStr = (0, helpers_1.generateRandomString)();
29
+ const replacementMapPrefix = `customFieldsFilterScopeConditionMap_${randomStr}`;
30
+ const replacements = {};
31
+ replacements[`customFieldsFilterScopeConditionName_${randomStr}`] = `${name}`;
20
32
  // Build the WHERE clause for custom field filtering
21
33
  const conditionsStrings = Object.entries(conditions)
22
- .map(([key, value]) => {
23
- if (Array.isArray(value)) {
24
- if (value.length === 0) {
34
+ .map(([key, condition], index) => {
35
+ const replacemetKey = `${replacementMapPrefix}Key${index}`;
36
+ replacements[replacemetKey] = `${key}`;
37
+ if (Array.isArray(condition)) {
38
+ if (condition.length === 0) {
25
39
  // if empty array, the condition is ignored
26
40
  return false;
27
41
  }
28
- const values = value.map((v) => `'${v}'`).join(',');
29
- return `custom_fields->>'${key}' IN (${values})`;
42
+ if (typeof condition[0] === 'string') {
43
+ const values = condition.map((v) => {
44
+ const valRandom = (0, helpers_1.generateRandomString)();
45
+ replacements[`${valRandom}`] = `${v}`;
46
+ return ` :${valRandom} `;
47
+ }).join(',');
48
+ return `(custom_fields->> :${replacemetKey} ) IN ( ${values} )`;
49
+ }
50
+ return condition
51
+ .map((c, cIndex) => {
52
+ const valRep = `${replacementMapPrefix}Values${index}${cIndex}`;
53
+ replacements[valRep] = `${c.value}`;
54
+ return `(custom_fields->> :${replacemetKey} )${castIfNeeded(c.value)} ${c.operator} :${valRep}`;
55
+ }).join(AND_DELIMETER);
56
+ }
57
+ if (typeof condition === 'string') {
58
+ const conditionRep = `${replacementMapPrefix}Condition${index}`;
59
+ replacements[conditionRep] = `${condition}`;
60
+ return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition)} = :${conditionRep}`;
61
+ }
62
+ if (condition?.operator) {
63
+ const valueRep = `${replacementMapPrefix}Values${index}`;
64
+ replacements[valueRep] = `${condition.value}`;
65
+ return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition.value)} ${condition.operator} :${valueRep}`;
30
66
  }
31
- return `custom_fields->>'${key}' = '${value}'`;
67
+ return false;
32
68
  })
33
69
  .filter(Boolean);
34
70
  if (conditionsStrings.length === 0) {
35
71
  return {};
36
72
  }
37
- const customFieldConditions = conditionsStrings.join(' AND ');
73
+ const customFieldConditions = conditionsStrings.join(AND_DELIMETER);
38
74
  const subQuery = `${'SELECT model_id FROM ('
39
75
  + 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
40
76
  + 'FROM custom_field_values AS cv '
41
77
  + 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
42
- + `AND cd.model_type = '${name}' `
78
+ + `AND cd.model_type = :${`customFieldsFilterScopeConditionName_${randomStr}`} `
43
79
  + 'GROUP BY cv.model_id'
44
- + ') AS CustomFieldAggregation '
45
- + 'WHERE '}${customFieldConditions}`;
80
+ + ') AS CustomFieldAggregation WHERE '} ${customFieldConditions}`;
46
81
  return {
47
82
  where: {
48
83
  id: {
49
84
  [sequelize_1.Op.in]: sequelize_typescript_1.Sequelize.literal(`(${subQuery})`),
50
85
  },
51
86
  },
87
+ replacements,
52
88
  };
53
89
  };
54
90
  exports.customFieldsFilterScope = customFieldsFilterScope;
@@ -57,26 +93,33 @@ const customFieldsSortScope = (name) => (sort) => {
57
93
  if (!sort || sort.length === 0) {
58
94
  return {};
59
95
  }
60
- const includes = Object.entries(sort).map(([key]) => ([
61
- sequelize_typescript_1.Sequelize.literal(`(
96
+ const randomStr = (0, helpers_1.generateRandomString)();
97
+ const replacements = {};
98
+ const includes = Object.entries(sort).map(([key], index) => {
99
+ const keyReplacement = `key_${randomStr}_${index}`;
100
+ replacements[`keyCustomFields_${randomStr}_${index}`] = `customFields_${key}`;
101
+ replacements[keyReplacement] = `${key}`;
102
+ return ([
103
+ sequelize_typescript_1.Sequelize.literal(`(
62
104
  SELECT value
63
105
  FROM (SELECT cv.model_id, cv.value
64
106
  FROM custom_field_values AS cv INNER JOIN custom_field_definitions AS cd
65
107
  ON cv.custom_field_definition_id = cd.id
66
108
  AND cd.model_type = '${name}'
67
109
  WHERE cv.model_id = "${name}"."id"
68
- AND cd.name = '${key}'
110
+ AND cd.name = :${keyReplacement}
69
111
  ) AS CustomFieldAggregation
70
112
  )
71
- `),
72
- `customFields_${key}`,
73
- ]));
74
- const orders = Object.entries(sort).map(([key, value]) => sequelize_typescript_1.Sequelize.literal(`"customFields_${key}" ${value}`));
113
+ `), randomStr,
114
+ ]);
115
+ });
116
+ const orders = Object.entries(sort).map(([, value]) => sequelize_typescript_1.Sequelize.literal(`"${randomStr}" ${value}`));
75
117
  return {
76
118
  attributes: {
77
119
  include: includes,
78
120
  },
79
121
  order: orders,
122
+ replacements,
80
123
  };
81
124
  };
82
125
  exports.customFieldsSortScope = customFieldsSortScope;
@@ -10,7 +10,10 @@ declare const _default: {
10
10
  underscored: boolean;
11
11
  underscoredAll: boolean;
12
12
  };
13
- logging: boolean;
13
+ logging: {
14
+ (...data: any[]): void;
15
+ (message?: any, ...optionalParams: any[]): void;
16
+ };
14
17
  };
15
18
  };
16
19
  export default _default;
@@ -12,6 +12,6 @@ exports.default = {
12
12
  underscored: true,
13
13
  underscoredAll: true,
14
14
  },
15
- logging: false,
15
+ logging: console.log,
16
16
  },
17
17
  };
@@ -1,4 +1,4 @@
1
- import { CreateCustomFieldDefinition, CustomFieldDefinitionDTO } from '../../types/definition';
1
+ import type { CreateCustomFieldDefinition, CustomFieldDefinitionDTO } from '../../types/definition';
2
2
  export declare const contextAwareFieldDefinition: {
3
3
  name: string;
4
4
  modelType: string;
@@ -37,7 +37,7 @@ export declare const coolFieldDefinition3: {
37
37
  modelType: string;
38
38
  };
39
39
  export declare const booleanField: (modelType: string) => CreateCustomFieldDefinition;
40
- export declare const enumField: (modelType: string, options: any) => CreateCustomFieldDefinition;
41
- export declare const rangeField: (modelType: string) => CreateCustomFieldDefinition;
40
+ export declare const selectField: (modelType: string, options: any) => CreateCustomFieldDefinition;
41
+ export declare const statusField: (modelType: string, options: any) => CreateCustomFieldDefinition;
42
42
  export declare const createDefinition: (defaults: Partial<CustomFieldDefinitionDTO>) => CreateCustomFieldDefinition;
43
43
  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.rangeField = exports.enumField = exports.booleanField = exports.coolFieldDefinition3 = exports.coolFieldDefinition2 = exports.coolFieldDefinition = exports.contextAwareFieldDefinition = void 0;
3
+ exports.createDefinitions = exports.createDefinition = exports.statusField = exports.selectField = exports.booleanField = exports.coolFieldDefinition3 = exports.coolFieldDefinition2 = exports.coolFieldDefinition = exports.contextAwareFieldDefinition = void 0;
4
4
  const uuid_1 = require("uuid");
5
5
  exports.contextAwareFieldDefinition = {
6
6
  name: 'cool field',
@@ -31,7 +31,7 @@ const booleanField = (modelType) => ({
31
31
  entityType: 'fleetId',
32
32
  });
33
33
  exports.booleanField = booleanField;
34
- const enumField = (modelType, options) => ({
34
+ const selectField = (modelType, options) => ({
35
35
  name: 'choices',
36
36
  modelType,
37
37
  fieldType: 'select',
@@ -39,18 +39,16 @@ const enumField = (modelType, options) => ({
39
39
  entityId: (0, uuid_1.v4)(),
40
40
  entityType: 'fleetId',
41
41
  });
42
- exports.enumField = enumField;
43
- const rangeField = (modelType) => ({
44
- name: 'ranges',
42
+ exports.selectField = selectField;
43
+ const statusField = (modelType, options) => ({
44
+ name: 'lifecycle',
45
45
  modelType,
46
- fieldType: 'number',
47
- validation: {
48
- between: [10, 12],
49
- },
46
+ fieldType: 'status',
47
+ validation: options,
50
48
  entityId: (0, uuid_1.v4)(),
51
49
  entityType: 'fleetId',
52
50
  });
53
- exports.rangeField = rangeField;
51
+ exports.statusField = statusField;
54
52
  // eslint-disable-next-line max-len
55
53
  const createDefinition = (defaults) => ({
56
54
  name: defaults?.name || `def_${(0, uuid_1.v4)()}`,
@@ -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
  export type ModelFetcher = (name: string) => any;
4
4
  export type ModelOptions = {
5
5
  /**
@@ -1,5 +1,19 @@
1
1
  declare const CUSTOM_FIELDS_FILTER_SCOPE: string;
2
2
  export declare const supportedEntities: string[];
3
+ /**
4
+ * Supported custom field types
5
+ */
6
+ export declare enum CustomFieldDefinitionType {
7
+ NUMBER = "number",
8
+ BOOLEAN = "boolean",
9
+ DATE = "date",
10
+ DATETIME = "datetime",
11
+ TEXT = "text",
12
+ IMAGE = "image",
13
+ SELECT = "select",
14
+ STATUS = "status",
15
+ FILE = "file"
16
+ }
3
17
  export {
4
18
  /** @deprecated Use the value from `@autofleet/common-types` instead */
5
19
  CUSTOM_FIELDS_FILTER_SCOPE, };
@@ -1,8 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CUSTOM_FIELDS_FILTER_SCOPE = exports.supportedEntities = void 0;
3
+ exports.CUSTOM_FIELDS_FILTER_SCOPE = exports.CustomFieldDefinitionType = exports.supportedEntities = void 0;
4
4
  const common_types_1 = require("@autofleet/common-types");
5
5
  const { CUSTOM_FIELDS_FILTER_SCOPE } = common_types_1.customFields;
6
6
  exports.CUSTOM_FIELDS_FILTER_SCOPE = CUSTOM_FIELDS_FILTER_SCOPE;
7
- // eslint-disable-next-line import/prefer-default-export
8
7
  exports.supportedEntities = ['businessModelId', 'fleetId', 'demandSourceId'];
8
+ /**
9
+ * Supported custom field types
10
+ */
11
+ var CustomFieldDefinitionType;
12
+ (function (CustomFieldDefinitionType) {
13
+ CustomFieldDefinitionType["NUMBER"] = "number";
14
+ CustomFieldDefinitionType["BOOLEAN"] = "boolean";
15
+ CustomFieldDefinitionType["DATE"] = "date";
16
+ CustomFieldDefinitionType["DATETIME"] = "datetime";
17
+ CustomFieldDefinitionType["TEXT"] = "text";
18
+ CustomFieldDefinitionType["IMAGE"] = "image";
19
+ CustomFieldDefinitionType["SELECT"] = "select";
20
+ CustomFieldDefinitionType["STATUS"] = "status";
21
+ CustomFieldDefinitionType["FILE"] = "file";
22
+ })(CustomFieldDefinitionType || (exports.CustomFieldDefinitionType = CustomFieldDefinitionType = {}));
@@ -1,6 +1,6 @@
1
- import { WhereOptions, BindOrReplacements } from 'sequelize';
2
- import { ModelStatic } from 'sequelize-typescript';
3
- import { CustomFieldDefinitionType } from '../validations/type';
1
+ import { type WhereOptions, type BindOrReplacements } from 'sequelize';
2
+ import { type ModelStatic } from 'sequelize-typescript';
3
+ import { CustomFieldDefinitionType } from '../constants';
4
4
  /**
5
5
  * Builds a WHERE clause and replacements for free-text search by custom fields.
6
6
  *
@@ -21,5 +21,6 @@ interface CustomFieldsSearchPayload {
21
21
  where: WhereOptions;
22
22
  replacements: BindOrReplacements;
23
23
  }
24
+ export declare const generateRandomString: (length?: number) => string;
24
25
  export declare const generateCustomFieldSearchQueryPayload: (searchTerm: string, model: ModelStatic, entityId: string, customFieldsTypesToExclude?: CustomFieldDefinitionType[]) => CustomFieldsSearchPayload;
25
26
  export {};
@@ -1,13 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateCustomFieldSearchQueryPayload = void 0;
3
+ exports.generateCustomFieldSearchQueryPayload = exports.generateRandomString = void 0;
4
4
  /* eslint-disable import/prefer-default-export */
5
5
  const sequelize_1 = require("sequelize");
6
6
  const sequelize_typescript_1 = require("sequelize-typescript");
7
- const type_1 = require("../validations/type");
7
+ const constants_1 = require("../constants");
8
+ const generateRandomString = (length = 5) => {
9
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
10
+ return Array.from({ length }, () => characters.charAt(Math.floor(Math.random() * characters.length))).join('');
11
+ };
12
+ exports.generateRandomString = generateRandomString;
8
13
  const generateCustomFieldSearchQueryPayload = (searchTerm, model, entityId, customFieldsTypesToExclude = [
9
- type_1.CustomFieldDefinitionType.DATETIME,
10
- type_1.CustomFieldDefinitionType.DATE,
14
+ constants_1.CustomFieldDefinitionType.DATETIME,
15
+ constants_1.CustomFieldDefinitionType.DATE,
11
16
  ]) => {
12
17
  const excludedTypesString = customFieldsTypesToExclude.map((type) => `'${type}'`).join(',');
13
18
  const subQuery = 'EXISTS ('
@@ -1,2 +1,2 @@
1
- declare const validateValue: (value: any, valueType: any, customValidations: any) => boolean;
2
- export default validateValue;
1
+ import type { CustomFieldDefinitionType } from '../constants';
2
+ export declare const validateValue: (value: unknown, valueType: CustomFieldDefinitionType, validation?: unknown) => boolean;
@@ -1,20 +1,20 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const custom_1 = __importDefault(require("./custom"));
7
- const type_1 = __importDefault(require("./type"));
8
- const validateValue = (value, valueType, customValidations) => {
9
- if (!(0, type_1.default)(value, valueType)) {
3
+ exports.validateValue = void 0;
4
+ const validators_1 = require("./validators");
5
+ const validateValue = (value, valueType, validation) => {
6
+ const validator = validators_1.validators[valueType];
7
+ if (!validator) {
8
+ // Unsupported field type
10
9
  return false;
11
10
  }
12
- if (customValidations) {
13
- return (0, custom_1.default)(value, valueType, customValidations);
14
- }
15
- // if (validations.required && !value) {
16
- // return false;
17
- // }
18
- return true;
11
+ // Always allow null values
12
+ return value === null || validator(value, validation);
13
+ /** TODO: Add validation for required fields
14
+ * @example
15
+ * if (validations.required && !value) {
16
+ * return false;
17
+ * }
18
+ */
19
19
  };
20
- exports.default = validateValue;
20
+ exports.validateValue = validateValue;
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.CustomFieldsSchema = void 0;
7
- /* eslint-disable import/prefer-default-export */
8
7
  const joi_1 = __importDefault(require("joi"));
9
8
  const CustomFieldsSchema = joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.any());
10
9
  exports.CustomFieldsSchema = CustomFieldsSchema;
@@ -1,18 +1,14 @@
1
+ import type { CustomFieldDefinitionType } from '../constants';
1
2
  /**
2
- * Supported custom field types
3
+ * Validator is a function that validates a custom-field `value`,
4
+ * against a custom-field definition `validation object`.
5
+ * @returns `true` if the value is valid, `false` otherwise.
3
6
  */
4
- export declare enum CustomFieldDefinitionType {
5
- NUMBER = "number",
6
- BOOLEAN = "boolean",
7
- DATE = "date",
8
- DATETIME = "datetime",
9
- TEXT = "text",
10
- IMAGE = "image",
11
- SELECT = "select"
12
- }
7
+ export type Validator<Value, DefinitionValidationObject> = (value: Value, validation?: DefinitionValidationObject) => boolean;
13
8
  /**
14
- * Validate that the given value is really of type "valueType"
15
- * TODO: verify that required field not set to null
9
+ * Validators is a map of custom-field types to their respective validators.
10
+ * The key is the custom-field type, and the value is the validator function.
16
11
  */
17
- declare const validateValueType: (value: unknown, valueType: CustomFieldDefinitionType) => boolean;
18
- export default validateValueType;
12
+ export type Validators = {
13
+ [K in CustomFieldDefinitionType]: Validator<unknown, unknown>;
14
+ };
@@ -1,50 +1,2 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.CustomFieldDefinitionType = void 0;
7
- const joi_1 = __importDefault(require("joi"));
8
- /**
9
- * Supported custom field types
10
- */
11
- // eslint-disable-next-line no-shadow
12
- var CustomFieldDefinitionType;
13
- (function (CustomFieldDefinitionType) {
14
- CustomFieldDefinitionType["NUMBER"] = "number";
15
- CustomFieldDefinitionType["BOOLEAN"] = "boolean";
16
- CustomFieldDefinitionType["DATE"] = "date";
17
- CustomFieldDefinitionType["DATETIME"] = "datetime";
18
- CustomFieldDefinitionType["TEXT"] = "text";
19
- CustomFieldDefinitionType["IMAGE"] = "image";
20
- CustomFieldDefinitionType["SELECT"] = "select";
21
- })(CustomFieldDefinitionType || (exports.CustomFieldDefinitionType = CustomFieldDefinitionType = {}));
22
- /**
23
- * Validate that the given value is really of type "valueType"
24
- * TODO: verify that required field not set to null
25
- */
26
- const validateValueType = (value, valueType) => {
27
- if (value === null) {
28
- // Null is always allowed
29
- return true;
30
- }
31
- switch (valueType) {
32
- case CustomFieldDefinitionType.TEXT:
33
- return typeof value === 'string';
34
- case CustomFieldDefinitionType.NUMBER:
35
- return typeof value === 'number';
36
- case CustomFieldDefinitionType.BOOLEAN:
37
- return typeof value === 'boolean';
38
- case CustomFieldDefinitionType.DATE:
39
- case CustomFieldDefinitionType.DATETIME:
40
- return !joi_1.default.date().validate(value).error;
41
- case CustomFieldDefinitionType.SELECT:
42
- return true; // custom validation
43
- case CustomFieldDefinitionType.IMAGE:
44
- return !joi_1.default.array().min(1).unique().items(joi_1.default.string().uri())
45
- .validate(value).error;
46
- default:
47
- return false;
48
- }
49
- };
50
- exports.default = validateValueType;
@@ -0,0 +1,14 @@
1
+ import { CustomFieldDefinitionType } from '../../constants';
2
+ import type { Validators } from '../type';
3
+ /**
4
+ * Custom field types that must have custom validation.
5
+ * TODO: remove this after moving to schema-based validation
6
+ */
7
+ export declare const CustomValidationTypes: {
8
+ readonly select: CustomFieldDefinitionType.SELECT;
9
+ readonly status: CustomFieldDefinitionType.STATUS;
10
+ };
11
+ /**
12
+ * Validators for custom fields
13
+ */
14
+ export declare const validators: Validators;
@@ -0,0 +1,40 @@
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
+ [constants_1.CustomFieldDefinitionType.FILE]: (value) => (!joi_1.default.array().min(1).unique().items(joi_1.default.object({
34
+ name: joi_1.default.string().required(),
35
+ type: joi_1.default.string(),
36
+ size: joi_1.default.string(),
37
+ addedBy: joi_1.default.string().uuid(),
38
+ }))
39
+ .validate(value).error),
40
+ };
@@ -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.7-beta.1",
3
+ "version": "0.6.8-beta.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -9,7 +9,7 @@
9
9
  "linter": "./node_modules/.bin/eslint .",
10
10
  "test": "jest --forceExit --runInBand",
11
11
  "coverage": "jest --coverage --forceExit --runInBand && rm -rf ./coverage",
12
- "build-to-local-repo": "npm run build && cp -r dist/* ../task-ms/node_modules/@autofleet/sadot/dist",
12
+ "build-to-local-repo": "npm run build && cp -r dist/* ../$REPO/node_modules/$npm_package_name/dist",
13
13
  "dev": "nodemon",
14
14
  "watch": "npm-watch build-to-local-repo",
15
15
  "publish-dev": "npm run build && npm publish --tag dev"
@@ -36,6 +36,7 @@
36
36
  "@autofleet/logger": "^2.0.5",
37
37
  "express": "^4.18.2",
38
38
  "joi": "^17.7.0",
39
+ "moment": "^2.30.1",
39
40
  "pg": "^8.10.0",
40
41
  "reflect-metadata": "^0.1.13",
41
42
  "sequelize": "^6.31.1",
@@ -52,8 +53,8 @@
52
53
  "eslint-config-airbnb-typescript": "^12.0.0",
53
54
  "eslint-plugin-import": "^2.22.1",
54
55
  "jest": "^29.7.0",
55
- "ts-jest": "^29.1.2",
56
56
  "npm-watch": "^0.11.0",
57
+ "ts-jest": "^29.1.2",
57
58
  "ts-node": "^8.6.2",
58
59
  "typescript": "^5.3.3",
59
60
  "typescript-eslint": "^0.0.1-alpha.0"
@@ -2,6 +2,8 @@
2
2
  import { Op, type WhereOptions } from 'sequelize';
3
3
  import { Sequelize } from 'sequelize-typescript';
4
4
  import { customFields } from '@autofleet/common-types';
5
+ import moment from 'moment';
6
+ import { generateRandomString } from '../utils/helpers';
5
7
 
6
8
  const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
7
9
 
@@ -22,51 +24,68 @@ export type CustomFieldSort = {
22
24
  }
23
25
 
24
26
  export type CustomFieldFilterOptions = {
25
- where?: WhereOptions;
27
+ where?: WhereOptions;
28
+ replacements?: Record<string, string>;
26
29
  }
27
30
 
28
31
  const castIfNeeded = (conditionValue: string): string => {
29
- if (!Number.isNaN(Date.parse(conditionValue))) {
32
+ if (moment.isDate(conditionValue)) {
30
33
  return '::timestamp';
34
+ } if (!Number.isNaN(Number(conditionValue))) {
35
+ return '::numeric';
31
36
  }
32
37
  return '';
33
38
  };
34
39
  const AND_DELIMETER = ' AND ';
35
- /**
36
- * A Sequelize scope for filtering models by custom fields.
37
- * This scope builds a WHERE clause to be applied on the main query.
38
- *
39
- * @param {string} name - The model type name used to join custom_field_definitions.
40
- * @returns {Function} - A function that takes conditions and returns the Sequelize options object.
41
- */
40
+
42
41
  export const customFieldsFilterScope = (
43
42
  name: string,
44
43
  ) => (conditions: Record<string, ConditionValue>): CustomFieldFilterOptions => {
45
44
  if (!conditions || Object.keys(conditions).length === 0) {
46
45
  return {};
47
46
  }
47
+ const randomStr = generateRandomString();
48
+ const replacementMapPrefix = `customFieldsFilterScopeConditionMap_${randomStr}`;
49
+ const replacements: Record<string, string> = {};
50
+ replacements[`customFieldsFilterScopeConditionName_${randomStr}`] = `${name}`;
51
+
48
52
  // Build the WHERE clause for custom field filtering
49
53
  const conditionsStrings = Object.entries(conditions)
50
54
  .map(
51
- ([key, condition]) => {
55
+ ([key, condition], index) => {
56
+ const replacemetKey = `${replacementMapPrefix}Key${index}`;
57
+ replacements[replacemetKey] = `${key}`;
52
58
  if (Array.isArray(condition)) {
53
59
  if (condition.length === 0) {
54
60
  // if empty array, the condition is ignored
55
61
  return false;
56
62
  }
57
63
  if (typeof condition[0] === 'string') {
58
- const values = condition.map((v) => `'${v}'`).join(',');
59
- return `(custom_fields->>'${key}') IN (${values})`;
64
+ const values = condition.map((v) => {
65
+ const valRandom = generateRandomString();
66
+ replacements[`${valRandom}`] = `${v}`;
67
+ return ` :${valRandom} `;
68
+ }).join(',');
69
+ return `(custom_fields->> :${replacemetKey} ) IN ( ${values} )`;
60
70
  }
61
71
  return condition
62
- .map((c) => `(custom_fields->>'${key}')${castIfNeeded(c.value)} ${c.operator} '${c.value}'`).join(AND_DELIMETER);
72
+ .map((c, cIndex) => {
73
+ const valRep = `${replacementMapPrefix}Values${index}${cIndex}`;
74
+ replacements[valRep] = `${c.value}`;
75
+ return `(custom_fields->> :${replacemetKey} )${castIfNeeded(c.value)} ${c.operator} :${valRep}`;
76
+ }).join(AND_DELIMETER);
63
77
  }
64
78
  if (typeof condition === 'string') {
65
- return `(custom_fields->>'${key}')${castIfNeeded(condition)} = '${condition}'`;
79
+ const conditionRep = `${replacementMapPrefix}Condition${index}`;
80
+ replacements[conditionRep] = `${condition}`;
81
+ return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition)} = :${conditionRep}`;
66
82
  }
67
83
  if (condition?.operator) {
68
- return `(custom_fields->>'${key}')${castIfNeeded(condition.value)} ${condition.operator} '${condition.value}'`;
84
+ const valueRep = `${replacementMapPrefix}Values${index}`;
85
+ replacements[valueRep] = `${condition.value}`;
86
+ return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition.value)} ${condition.operator} :${valueRep}`;
69
87
  }
88
+
70
89
  return false;
71
90
  },
72
91
  )
@@ -75,21 +94,20 @@ export const customFieldsFilterScope = (
75
94
  return {};
76
95
  }
77
96
  const customFieldConditions = conditionsStrings.join(AND_DELIMETER);
78
-
79
97
  const subQuery = `${'SELECT model_id FROM ('
80
98
  + 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
81
99
  + 'FROM custom_field_values AS cv '
82
100
  + 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
83
- + `AND cd.model_type = '${name}' `
101
+ + `AND cd.model_type = :${`customFieldsFilterScopeConditionName_${randomStr}`} `
84
102
  + 'GROUP BY cv.model_id'
85
- + ') AS CustomFieldAggregation WHERE '}${customFieldConditions}`;
86
-
103
+ + ') AS CustomFieldAggregation WHERE '} ${customFieldConditions}`;
87
104
  return {
88
105
  where: {
89
106
  id: {
90
107
  [Op.in]: Sequelize.literal(`(${subQuery})`),
91
108
  },
92
109
  },
110
+ replacements,
93
111
  };
94
112
  };
95
113
 
@@ -101,8 +119,13 @@ export const customFieldsSortScope = (
101
119
  if (!sort || sort.length === 0) {
102
120
  return {};
103
121
  }
104
- const includes = Object.entries(sort).map(([key]) =>
105
- ([
122
+ const randomStr = generateRandomString();
123
+ const replacements: Record<string, string> = {};
124
+ const includes = Object.entries(sort).map(([key], index) => {
125
+ const keyReplacement = `key_${randomStr}_${index}`;
126
+ replacements[`keyCustomFields_${randomStr}_${index}`] = `customFields_${key}`;
127
+ replacements[keyReplacement] = `${key}`;
128
+ return ([
106
129
  Sequelize.literal(`(
107
130
  SELECT value
108
131
  FROM (SELECT cv.model_id, cv.value
@@ -110,18 +133,19 @@ export const customFieldsSortScope = (
110
133
  ON cv.custom_field_definition_id = cd.id
111
134
  AND cd.model_type = '${name}'
112
135
  WHERE cv.model_id = "${name}"."id"
113
- AND cd.name = '${key}'
136
+ AND cd.name = :${keyReplacement}
114
137
  ) AS CustomFieldAggregation
115
138
  )
116
- `),
117
- `customFields_${key}`,
118
- ]));
139
+ `), randomStr,
140
+ ]);
141
+ });
119
142
 
120
- const orders = Object.entries(sort).map(([key, value]) => Sequelize.literal(`"customFields_${key}" ${value}`));
143
+ const orders = Object.entries(sort).map(([, value]) => Sequelize.literal(`"${randomStr}" ${value}`));
121
144
  return {
122
145
  attributes: {
123
146
  include: includes,
124
147
  },
125
148
  order: orders,
149
+ replacements,
126
150
  };
127
151
  };
@@ -10,6 +10,6 @@ export default {
10
10
  underscored: true,
11
11
  underscoredAll: true,
12
12
  },
13
- logging: false,
13
+ logging: console.log,
14
14
  },
15
15
  };
@@ -16,6 +16,7 @@ export enum CustomFieldDefinitionType {
16
16
  IMAGE = 'image',
17
17
  SELECT = 'select',
18
18
  STATUS = 'status',
19
+ FILE = 'file',
19
20
  }
20
21
 
21
22
  export {
@@ -25,6 +25,11 @@ interface CustomFieldsSearchPayload {
25
25
  replacements: BindOrReplacements;
26
26
  }
27
27
 
28
+ export const generateRandomString = (length = 5): string => {
29
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
30
+ return Array.from({ length }, () => characters.charAt(Math.floor(Math.random() * characters.length))).join('');
31
+ };
32
+
28
33
  export const generateCustomFieldSearchQueryPayload = (
29
34
  searchTerm: string,
30
35
  model: ModelStatic,
@@ -28,4 +28,11 @@ export const validators: Validators = {
28
28
  [CustomFieldDefinitionType.IMAGE]: (value) => (!Joi.array().min(1).unique()
29
29
  .items(Joi.string().uri())
30
30
  .validate(value).error),
31
+ [CustomFieldDefinitionType.FILE]: (value) => (!Joi.array().min(1).unique().items(Joi.object({
32
+ name: Joi.string().required(),
33
+ type: Joi.string(),
34
+ size: Joi.string(),
35
+ addedBy: Joi.string().uuid(),
36
+ }))
37
+ .validate(value).error),
31
38
  };
@@ -1,15 +0,0 @@
1
- export declare const mustHaveCustomValidation: {
2
- select: boolean;
3
- };
4
- /**
5
- * Validates the given validations object against the supported field types and their validators.
6
- * @return true if the validation is valid, false otherwise.
7
- */
8
- export declare const validateValidation: (valueType: any, validation: any) => boolean;
9
- /**
10
- * Validates the given value against the custom validation rules for the specified field type.
11
- * If no custom validation rules are provided, it falls back to the default validation.
12
- * @returns true if the value is valid according to the validation rules, false otherwise.
13
- */
14
- declare const customValidation: (value: any, valueType: any, validation: any) => boolean;
15
- export default customValidation;
@@ -1,42 +0,0 @@
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.validateValidation = exports.mustHaveCustomValidation = void 0;
7
- /* eslint-disable no-shadow */
8
- const logger_1 = __importDefault(require("../logger"));
9
- const type_1 = require("./type");
10
- const validators_1 = __importDefault(require("./validators"));
11
- exports.mustHaveCustomValidation = {
12
- [type_1.CustomFieldDefinitionType.SELECT]: true,
13
- };
14
- /**
15
- * Validates the given validations object against the supported field types and their validators.
16
- * @return true if the validation is valid, false otherwise.
17
- */
18
- const validateValidation = (valueType, validation) => {
19
- if (!validation) {
20
- if (exports.mustHaveCustomValidation[valueType]) {
21
- logger_1.default.error(`No custom validation for custom field type ${valueType} found`);
22
- return false;
23
- }
24
- return true;
25
- }
26
- return true;
27
- };
28
- exports.validateValidation = validateValidation;
29
- /**
30
- * Validates the given value against the custom validation rules for the specified field type.
31
- * If no custom validation rules are provided, it falls back to the default validation.
32
- * @returns true if the value is valid according to the validation rules, false otherwise.
33
- */
34
- const customValidation = (value, valueType, validation) => {
35
- const validator = validators_1.default?.[valueType];
36
- if (!validation || !validator) {
37
- return (0, exports.validateValidation)(valueType, validation);
38
- }
39
- // Always allow null values
40
- return value === null || validator(value, validation);
41
- };
42
- exports.default = customValidation;
@@ -1,12 +0,0 @@
1
- export declare enum CustomValidations {
2
- SELECT = "select",
3
- RANGE = "between"
4
- }
5
- type Validator = (value: any, validation: any) => boolean;
6
- /**
7
- * Validators for custom fields
8
- */
9
- declare const validators: {
10
- [key: string]: Validator;
11
- };
12
- export default validators;
@@ -1,33 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CustomValidations = void 0;
4
- // eslint-disable-next-line no-shadow
5
- var CustomValidations;
6
- (function (CustomValidations) {
7
- CustomValidations["SELECT"] = "select";
8
- CustomValidations["RANGE"] = "between";
9
- })(CustomValidations || (exports.CustomValidations = CustomValidations = {}));
10
- /**
11
- * Validate {@link CustomValidations.ENUM Enum}
12
- */
13
- const validateEnum = (value, enumValues) => (Array.isArray(enumValues)
14
- && enumValues.length > 0
15
- && enumValues.includes(value));
16
- /**
17
- * Validate {@link CustomValidations.RANGE Range}
18
- */
19
- const validateRange = (value, range) => {
20
- const [min, max] = range;
21
- if (min === undefined || max === undefined) {
22
- return false;
23
- }
24
- return value >= range.min && value <= range.max;
25
- };
26
- /**
27
- * Validators for custom fields
28
- */
29
- const validators = {
30
- [CustomValidations.SELECT]: validateEnum,
31
- [CustomValidations.RANGE]: validateRange,
32
- };
33
- exports.default = validators;