@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.
- package/dist/scopes/filter.d.ts +3 -7
- package/dist/scopes/filter.js +47 -22
- package/dist/tests/helpers/database-config.d.ts +4 -1
- package/dist/tests/helpers/database-config.js +1 -1
- package/dist/utils/helpers/index.d.ts +1 -0
- package/dist/utils/helpers/index.js +6 -1
- package/package.json +4 -3
- package/src/scopes/filter.ts +47 -25
- package/src/tests/helpers/database-config.ts +1 -1
- package/src/utils/helpers/index.ts +5 -0
package/dist/scopes/filter.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/scopes/filter.js
CHANGED
|
@@ -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 (
|
|
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) =>
|
|
37
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
77
|
-
|
|
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 =
|
|
108
|
+
AND cd.name = :${keyRandomReplacement}
|
|
85
109
|
) AS CustomFieldAggregation
|
|
86
110
|
)
|
|
87
|
-
`),
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const orders = Object.entries(sort).map(([
|
|
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;
|
|
@@ -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.
|
|
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
|
+
}
|
package/src/scopes/filter.ts
CHANGED
|
@@ -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
|
-
|
|
27
|
+
where?: WhereOptions;
|
|
28
|
+
replacements?: Record<string, string>;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
const castIfNeeded = (conditionValue: string): string => {
|
|
29
|
-
if (
|
|
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) =>
|
|
59
|
-
|
|
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) =>
|
|
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
|
-
|
|
78
|
+
const conditionRep = generateRandomString();
|
|
79
|
+
replacements[conditionRep] = `${condition}`;
|
|
80
|
+
return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition)} = :${conditionRep}`;
|
|
66
81
|
}
|
|
67
82
|
if (condition?.operator) {
|
|
68
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
134
|
+
AND cd.name = :${keyRandomReplacement}
|
|
114
135
|
) AS CustomFieldAggregation
|
|
115
136
|
)
|
|
116
|
-
`),
|
|
117
|
-
|
|
118
|
-
|
|
137
|
+
`), randomStr,
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
119
140
|
|
|
120
|
-
const orders = Object.entries(sort).map(([
|
|
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
|
};
|
|
@@ -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,
|