@autofleet/sadot 0.6.7 → 0.6.8-beta.2

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,67 @@
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 ConditionNameRandomStr = (0, helpers_1.generateRandomString)();
29
+ const replacements = {};
30
+ replacements[ConditionNameRandomStr] = `${name}`;
27
31
  // Build the WHERE clause for custom field filtering
28
32
  const conditionsStrings = Object.entries(conditions)
29
33
  .map(([key, condition]) => {
34
+ const replacemetKey = (0, helpers_1.generateRandomString)();
35
+ replacements[replacemetKey] = `${key}`;
30
36
  if (Array.isArray(condition)) {
31
37
  if (condition.length === 0) {
32
38
  // if empty array, the condition is ignored
33
39
  return false;
34
40
  }
35
41
  if (typeof condition[0] === 'string') {
36
- const values = condition.map((v) => `'${v}'`).join(',');
37
- return `(custom_fields->>'${key}') IN (${values})`;
42
+ const values = condition.map((v) => {
43
+ const valRandom = (0, helpers_1.generateRandomString)();
44
+ replacements[`${valRandom}`] = `${v}`;
45
+ return ` :${valRandom} `;
46
+ }).join(',');
47
+ return `(custom_fields->> :${replacemetKey} ) IN ( ${values} )`;
38
48
  }
39
49
  return condition
40
- .map((c) => `(custom_fields->>'${key}')${castIfNeeded(c.value)} ${c.operator} '${c.value}'`).join(AND_DELIMETER);
50
+ .map((c) => {
51
+ const valRep = (0, helpers_1.generateRandomString)();
52
+ replacements[valRep] = `${c.value}`;
53
+ return `(custom_fields->> :${replacemetKey} )${castIfNeeded(c.value)} ${c.operator} :${valRep}`;
54
+ }).join(AND_DELIMETER);
41
55
  }
42
56
  if (typeof condition === 'string') {
43
- return `(custom_fields->>'${key}')${castIfNeeded(condition)} = '${condition}'`;
57
+ const conditionRep = (0, helpers_1.generateRandomString)();
58
+ replacements[conditionRep] = `${condition}`;
59
+ return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition)} = :${conditionRep}`;
44
60
  }
45
61
  if (condition?.operator) {
46
- return `(custom_fields->>'${key}')${castIfNeeded(condition.value)} ${condition.operator} '${condition.value}'`;
62
+ const valueRep = (0, helpers_1.generateRandomString)();
63
+ replacements[valueRep] = `${condition.value}`;
64
+ return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition.value)} ${condition.operator} :${valueRep}`;
47
65
  }
48
66
  return false;
49
67
  })
@@ -56,15 +74,16 @@ const customFieldsFilterScope = (name) => (conditions) => {
56
74
  + 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
57
75
  + 'FROM custom_field_values AS cv '
58
76
  + 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
59
- + `AND cd.model_type = '${name}' `
77
+ + `AND cd.model_type = :${ConditionNameRandomStr} `
60
78
  + 'GROUP BY cv.model_id'
61
- + ') AS CustomFieldAggregation WHERE '}${customFieldConditions}`;
79
+ + ') AS CustomFieldAggregation WHERE '} ${customFieldConditions}`;
62
80
  return {
63
81
  where: {
64
82
  id: {
65
83
  [sequelize_1.Op.in]: sequelize_typescript_1.Sequelize.literal(`(${subQuery})`),
66
84
  },
67
85
  },
86
+ replacements,
68
87
  };
69
88
  };
70
89
  exports.customFieldsFilterScope = customFieldsFilterScope;
@@ -73,26 +92,32 @@ const customFieldsSortScope = (name) => (sort) => {
73
92
  if (!sort || sort.length === 0) {
74
93
  return {};
75
94
  }
76
- const includes = Object.entries(sort).map(([key]) => ([
77
- sequelize_typescript_1.Sequelize.literal(`(
95
+ const randomStr = (0, helpers_1.generateRandomString)();
96
+ const replacements = {};
97
+ const includes = Object.entries(sort).map(([key]) => {
98
+ const keyRandomReplacement = (0, helpers_1.generateRandomString)();
99
+ replacements[keyRandomReplacement] = `${key}`;
100
+ return ([
101
+ sequelize_typescript_1.Sequelize.literal(`(
78
102
  SELECT value
79
103
  FROM (SELECT cv.model_id, cv.value
80
104
  FROM custom_field_values AS cv INNER JOIN custom_field_definitions AS cd
81
105
  ON cv.custom_field_definition_id = cd.id
82
106
  AND cd.model_type = '${name}'
83
107
  WHERE cv.model_id = "${name}"."id"
84
- AND cd.name = '${key}'
108
+ AND cd.name = :${keyRandomReplacement}
85
109
  ) AS CustomFieldAggregation
86
110
  )
87
- `),
88
- `customFields_${key}`,
89
- ]));
90
- const orders = Object.entries(sort).map(([key, value]) => sequelize_typescript_1.Sequelize.literal(`"customFields_${key}" ${value}`));
111
+ `), randomStr,
112
+ ]);
113
+ });
114
+ const orders = Object.entries(sort).map(([, value]) => sequelize_typescript_1.Sequelize.literal(`"${randomStr}" ${value}`));
91
115
  return {
92
116
  attributes: {
93
117
  include: includes,
94
118
  },
95
119
  order: orders,
120
+ replacements,
96
121
  };
97
122
  };
98
123
  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 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
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.2",
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,67 @@ 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 ConditionNameRandomStr = generateRandomString();
48
+ const replacements: Record<string, string> = {};
49
+ replacements[ConditionNameRandomStr] = `${name}`;
50
+
48
51
  // Build the WHERE clause for custom field filtering
49
52
  const conditionsStrings = Object.entries(conditions)
50
53
  .map(
51
54
  ([key, condition]) => {
55
+ const replacemetKey = generateRandomString();
56
+ replacements[replacemetKey] = `${key}`;
52
57
  if (Array.isArray(condition)) {
53
58
  if (condition.length === 0) {
54
59
  // if empty array, the condition is ignored
55
60
  return false;
56
61
  }
57
62
  if (typeof condition[0] === 'string') {
58
- const values = condition.map((v) => `'${v}'`).join(',');
59
- return `(custom_fields->>'${key}') IN (${values})`;
63
+ const values = condition.map((v) => {
64
+ const valRandom = generateRandomString();
65
+ replacements[`${valRandom}`] = `${v}`;
66
+ return ` :${valRandom} `;
67
+ }).join(',');
68
+ return `(custom_fields->> :${replacemetKey} ) IN ( ${values} )`;
60
69
  }
61
70
  return condition
62
- .map((c) => `(custom_fields->>'${key}')${castIfNeeded(c.value)} ${c.operator} '${c.value}'`).join(AND_DELIMETER);
71
+ .map((c) => {
72
+ const valRep = generateRandomString();
73
+ replacements[valRep] = `${c.value}`;
74
+ return `(custom_fields->> :${replacemetKey} )${castIfNeeded(c.value)} ${c.operator} :${valRep}`;
75
+ }).join(AND_DELIMETER);
63
76
  }
64
77
  if (typeof condition === 'string') {
65
- return `(custom_fields->>'${key}')${castIfNeeded(condition)} = '${condition}'`;
78
+ const conditionRep = generateRandomString();
79
+ replacements[conditionRep] = `${condition}`;
80
+ return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition)} = :${conditionRep}`;
66
81
  }
67
82
  if (condition?.operator) {
68
- return `(custom_fields->>'${key}')${castIfNeeded(condition.value)} ${condition.operator} '${condition.value}'`;
83
+ const valueRep = generateRandomString();
84
+ replacements[valueRep] = `${condition.value}`;
85
+ return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition.value)} ${condition.operator} :${valueRep}`;
69
86
  }
87
+
70
88
  return false;
71
89
  },
72
90
  )
@@ -75,21 +93,20 @@ export const customFieldsFilterScope = (
75
93
  return {};
76
94
  }
77
95
  const customFieldConditions = conditionsStrings.join(AND_DELIMETER);
78
-
79
96
  const subQuery = `${'SELECT model_id FROM ('
80
97
  + 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
81
98
  + 'FROM custom_field_values AS cv '
82
99
  + 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
83
- + `AND cd.model_type = '${name}' `
100
+ + `AND cd.model_type = :${ConditionNameRandomStr} `
84
101
  + 'GROUP BY cv.model_id'
85
- + ') AS CustomFieldAggregation WHERE '}${customFieldConditions}`;
86
-
102
+ + ') AS CustomFieldAggregation WHERE '} ${customFieldConditions}`;
87
103
  return {
88
104
  where: {
89
105
  id: {
90
106
  [Op.in]: Sequelize.literal(`(${subQuery})`),
91
107
  },
92
108
  },
109
+ replacements,
93
110
  };
94
111
  };
95
112
 
@@ -101,8 +118,12 @@ export const customFieldsSortScope = (
101
118
  if (!sort || sort.length === 0) {
102
119
  return {};
103
120
  }
104
- const includes = Object.entries(sort).map(([key]) =>
105
- ([
121
+ const randomStr = generateRandomString();
122
+ const replacements: Record<string, string> = {};
123
+ const includes = Object.entries(sort).map(([key]) => {
124
+ const keyRandomReplacement = generateRandomString();
125
+ replacements[keyRandomReplacement] = `${key}`;
126
+ return ([
106
127
  Sequelize.literal(`(
107
128
  SELECT value
108
129
  FROM (SELECT cv.model_id, cv.value
@@ -110,18 +131,19 @@ export const customFieldsSortScope = (
110
131
  ON cv.custom_field_definition_id = cd.id
111
132
  AND cd.model_type = '${name}'
112
133
  WHERE cv.model_id = "${name}"."id"
113
- AND cd.name = '${key}'
134
+ AND cd.name = :${keyRandomReplacement}
114
135
  ) AS CustomFieldAggregation
115
136
  )
116
- `),
117
- `customFields_${key}`,
118
- ]));
137
+ `), randomStr,
138
+ ]);
139
+ });
119
140
 
120
- const orders = Object.entries(sort).map(([key, value]) => Sequelize.literal(`"customFields_${key}" ${value}`));
141
+ const orders = Object.entries(sort).map(([, value]) => Sequelize.literal(`"${randomStr}" ${value}`));
121
142
  return {
122
143
  attributes: {
123
144
  include: includes,
124
145
  },
125
146
  order: orders,
147
+ replacements,
126
148
  };
127
149
  };
@@ -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 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
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,