@autofleet/sadot 0.6.7 → 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.
@@ -15,23 +15,19 @@ export type CustomFieldSort = {
15
15
  };
16
16
  export type CustomFieldFilterOptions = {
17
17
  where?: WhereOptions;
18
+ replacements?: Record<string, string>;
18
19
  };
19
- /**
20
- * A Sequelize scope for filtering models by custom fields.
21
- * This scope builds a WHERE clause to be applied on the main query.
22
- *
23
- * @param {string} name - The model type name used to join custom_field_definitions.
24
- * @returns {Function} - A function that takes conditions and returns the Sequelize options object.
25
- */
26
20
  export declare const customFieldsFilterScope: (name: string) => (conditions: Record<string, ConditionValue>) => CustomFieldFilterOptions;
27
21
  export declare const scopeName = "filterByCustomFields";
28
22
  export declare const customFieldsSortScope: (name: string) => (sort: CustomFieldSort[]) => {
29
23
  attributes?: undefined;
30
24
  order?: undefined;
25
+ replacements?: undefined;
31
26
  } | {
32
27
  attributes: {
33
28
  include: (string | import("sequelize/types/utils").Literal)[][];
34
29
  };
35
30
  order: import("sequelize/types/utils").Literal[];
31
+ replacements: Record<string, string>;
36
32
  };
37
33
  export {};
@@ -1,49 +1,68 @@
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
14
  const castIfNeeded = (conditionValue) => {
10
- if (!Number.isNaN(Date.parse(conditionValue))) {
15
+ if (moment_1.default.isDate(conditionValue)) {
11
16
  return '::timestamp';
12
17
  }
18
+ if (!Number.isNaN(Number(conditionValue))) {
19
+ return '::numeric';
20
+ }
13
21
  return '';
14
22
  };
15
23
  const AND_DELIMETER = ' AND ';
16
- /**
17
- * A Sequelize scope for filtering models by custom fields.
18
- * This scope builds a WHERE clause to be applied on the main query.
19
- *
20
- * @param {string} name - The model type name used to join custom_field_definitions.
21
- * @returns {Function} - A function that takes conditions and returns the Sequelize options object.
22
- */
23
24
  const customFieldsFilterScope = (name) => (conditions) => {
24
25
  if (!conditions || Object.keys(conditions).length === 0) {
25
26
  return {};
26
27
  }
28
+ const randomStr = (0, helpers_1.generateRandomString)();
29
+ const replacementMapPrefix = `customFieldsFilterScopeConditionMap_${randomStr}`;
30
+ const replacements = {};
31
+ replacements[`customFieldsFilterScopeConditionName_${randomStr}`] = `${name}`;
27
32
  // Build the WHERE clause for custom field filtering
28
33
  const conditionsStrings = Object.entries(conditions)
29
- .map(([key, condition]) => {
34
+ .map(([key, condition], index) => {
35
+ const replacemetKey = `${replacementMapPrefix}Key${index}`;
36
+ replacements[replacemetKey] = `${key}`;
30
37
  if (Array.isArray(condition)) {
31
38
  if (condition.length === 0) {
32
39
  // if empty array, the condition is ignored
33
40
  return false;
34
41
  }
35
42
  if (typeof condition[0] === 'string') {
36
- const values = condition.map((v) => `'${v}'`).join(',');
37
- return `(custom_fields->>'${key}') IN (${values})`;
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} )`;
38
49
  }
39
50
  return condition
40
- .map((c) => `(custom_fields->>'${key}')${castIfNeeded(c.value)} ${c.operator} '${c.value}'`).join(AND_DELIMETER);
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);
41
56
  }
42
57
  if (typeof condition === 'string') {
43
- return `(custom_fields->>'${key}')${castIfNeeded(condition)} = '${condition}'`;
58
+ const conditionRep = `${replacementMapPrefix}Condition${index}`;
59
+ replacements[conditionRep] = `${condition}`;
60
+ return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition)} = :${conditionRep}`;
44
61
  }
45
62
  if (condition?.operator) {
46
- return `(custom_fields->>'${key}')${castIfNeeded(condition.value)} ${condition.operator} '${condition.value}'`;
63
+ const valueRep = `${replacementMapPrefix}Values${index}`;
64
+ replacements[valueRep] = `${condition.value}`;
65
+ return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition.value)} ${condition.operator} :${valueRep}`;
47
66
  }
48
67
  return false;
49
68
  })
@@ -56,15 +75,16 @@ const customFieldsFilterScope = (name) => (conditions) => {
56
75
  + 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
57
76
  + 'FROM custom_field_values AS cv '
58
77
  + 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
59
- + `AND cd.model_type = '${name}' `
78
+ + `AND cd.model_type = :${`customFieldsFilterScopeConditionName_${randomStr}`} `
60
79
  + 'GROUP BY cv.model_id'
61
- + ') AS CustomFieldAggregation WHERE '}${customFieldConditions}`;
80
+ + ') AS CustomFieldAggregation WHERE '} ${customFieldConditions}`;
62
81
  return {
63
82
  where: {
64
83
  id: {
65
84
  [sequelize_1.Op.in]: sequelize_typescript_1.Sequelize.literal(`(${subQuery})`),
66
85
  },
67
86
  },
87
+ replacements,
68
88
  };
69
89
  };
70
90
  exports.customFieldsFilterScope = customFieldsFilterScope;
@@ -73,26 +93,33 @@ const customFieldsSortScope = (name) => (sort) => {
73
93
  if (!sort || sort.length === 0) {
74
94
  return {};
75
95
  }
76
- const includes = Object.entries(sort).map(([key]) => ([
77
- 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(`(
78
104
  SELECT value
79
105
  FROM (SELECT cv.model_id, cv.value
80
106
  FROM custom_field_values AS cv INNER JOIN custom_field_definitions AS cd
81
107
  ON cv.custom_field_definition_id = cd.id
82
108
  AND cd.model_type = '${name}'
83
109
  WHERE cv.model_id = "${name}"."id"
84
- AND cd.name = '${key}'
110
+ AND cd.name = :${keyReplacement}
85
111
  ) AS CustomFieldAggregation
86
112
  )
87
- `),
88
- `customFields_${key}`,
89
- ]));
90
- 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}`));
91
117
  return {
92
118
  attributes: {
93
119
  include: includes,
94
120
  },
95
121
  order: orders,
122
+ replacements,
96
123
  };
97
124
  };
98
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
  };
@@ -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,10 +1,15 @@
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
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
14
  constants_1.CustomFieldDefinitionType.DATETIME,
10
15
  constants_1.CustomFieldDefinitionType.DATE,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/sadot",
3
- "version": "0.6.7",
3
+ "version": "0.6.8-beta.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -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,12 +53,12 @@
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"
60
61
  },
61
62
  "author": "Autofleet",
62
63
  "license": "ISC"
63
- }
64
+ }
@@ -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
  };
@@ -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,