@autofleet/sadot 0.10.2 → 0.10.3
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/hooks/create.js +1 -1
- package/dist/hooks/enrich.d.ts +25 -2
- package/dist/hooks/enrich.js +49 -16
- package/dist/index.js +1 -1
- package/dist/repository/entries.js +9 -0
- package/dist/repository/utils/formatValues.d.ts +3 -0
- package/dist/repository/utils/formatValues.js +16 -0
- package/dist/repository/value.js +2 -14
- package/dist/scopes/filter.d.ts +4 -21
- package/dist/scopes/filter.js +15 -82
- package/dist/scopes/helpers/filter.helpers.d.ts +42 -0
- package/dist/scopes/helpers/filter.helpers.js +204 -0
- package/dist/utils/init.d.ts +2 -2
- package/dist/utils/init.js +6 -6
- package/package.json +1 -1
- package/src/hooks/create.ts +1 -1
- package/src/hooks/enrich.ts +90 -20
- package/src/index.ts +1 -1
- package/src/repository/entries.ts +11 -0
- package/src/repository/utils/formatValues.ts +14 -0
- package/src/repository/value.ts +1 -14
- package/src/scopes/filter.ts +27 -104
- package/src/scopes/helpers/filter.helpers.ts +227 -0
- package/src/utils/init.ts +7 -7
package/dist/utils/init.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { ModelFetcher, Models } from '../types';
|
|
1
|
+
import type { CustomFieldOptions, ModelFetcher, Models } from '../types';
|
|
2
2
|
export declare const addHooks: (models: Models[], getModel: ModelFetcher, sadotOptions?: {
|
|
3
3
|
useCustomFieldsEntries: boolean;
|
|
4
4
|
}) => void;
|
|
5
5
|
export declare const removeHooks: (models: Models[], getModel: ModelFetcher) => void;
|
|
6
|
-
export declare const addScopes: (models: Models[], getModel: ModelFetcher) => void;
|
|
6
|
+
export declare const addScopes: (models: Models[], getModel: ModelFetcher, options?: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>) => void;
|
|
7
7
|
export declare const applyCustomAssociation: (models: Models[]) => void;
|
package/dist/utils/init.js
CHANGED
|
@@ -35,9 +35,9 @@ const addHooks = (models, getModel, sadotOptions = { useCustomFieldsEntries: fal
|
|
|
35
35
|
model.addHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate', hooks_1.beforeBulkUpdate);
|
|
36
36
|
model.addHook('beforeCreate', 'sadot-beforeCreate', (0, hooks_1.beforeCreate)(scopeAttributes, modelOptions, sadotOptions));
|
|
37
37
|
model.addHook('beforeUpdate', 'sadot-beforeUpdate', (0, hooks_1.beforeUpdate)(scopeAttributes, modelOptions, sadotOptions));
|
|
38
|
-
model.addHook('afterFind', 'sadot-afterFind', (0, hooks_1.enrichResults)(name, scopeAttributes, 'afterFind', modelOptions));
|
|
39
|
-
model.addHook('afterUpdate', 'sadot-afterUpdate', (0, hooks_1.enrichResults)(name, scopeAttributes, null, modelOptions));
|
|
40
|
-
model.addHook('afterCreate', 'sadot-afterCreate', (0, hooks_1.enrichResults)(name, scopeAttributes, null, modelOptions));
|
|
38
|
+
model.addHook('afterFind', 'sadot-afterFind', (0, hooks_1.enrichResults)(name, scopeAttributes, 'afterFind', modelOptions, sadotOptions));
|
|
39
|
+
model.addHook('afterUpdate', 'sadot-afterUpdate', (0, hooks_1.enrichResults)(name, scopeAttributes, null, modelOptions, sadotOptions));
|
|
40
|
+
model.addHook('afterCreate', 'sadot-afterCreate', (0, hooks_1.enrichResults)(name, scopeAttributes, null, modelOptions, sadotOptions));
|
|
41
41
|
}
|
|
42
42
|
catch (e) {
|
|
43
43
|
logger_1.default.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
@@ -78,7 +78,7 @@ const addAssociations = (model, modelName) => {
|
|
|
78
78
|
// TBC: maybe can be removed
|
|
79
79
|
models_1.CustomFieldValue.belongsTo(model, { foreignKey: 'modelId', as: modelName });
|
|
80
80
|
};
|
|
81
|
-
const addScopes = (models, getModel) => {
|
|
81
|
+
const addScopes = (models, getModel, options = { useCustomFieldsEntries: false }) => {
|
|
82
82
|
models.forEach(async ({ name, scopeAttributes }) => {
|
|
83
83
|
try {
|
|
84
84
|
const model = getModel(name);
|
|
@@ -92,8 +92,8 @@ const addScopes = (models, getModel) => {
|
|
|
92
92
|
// Necessary associations for the filtering scope
|
|
93
93
|
addAssociations(model, name);
|
|
94
94
|
// Add filter scope
|
|
95
|
-
model.addScope(CUSTOM_FIELDS_FILTER_SCOPE, (0, scopes_1.customFieldsFilterScope)(name));
|
|
96
|
-
model.addScope(custom_fields_1.CUSTOM_FIELDS_SORT_SCOPE, (0, filter_1.customFieldsSortScope)(name));
|
|
95
|
+
model.addScope(CUSTOM_FIELDS_FILTER_SCOPE, (0, scopes_1.customFieldsFilterScope)(name, options));
|
|
96
|
+
model.addScope(custom_fields_1.CUSTOM_FIELDS_SORT_SCOPE, (0, filter_1.customFieldsSortScope)(name, options));
|
|
97
97
|
}
|
|
98
98
|
catch (e) {
|
|
99
99
|
logger_1.default.error(`Could not add custom fields scopes to model ${name}. `, e);
|
package/package.json
CHANGED
package/src/hooks/create.ts
CHANGED
|
@@ -45,7 +45,7 @@ export const beforeCreate = (
|
|
|
45
45
|
if (fieldsWithDefaultValue.length) {
|
|
46
46
|
// eslint-disable-next-line no-param-reassign
|
|
47
47
|
instance.customFields ||= {};
|
|
48
|
-
fieldsWithDefaultValue.filter((def) =>
|
|
48
|
+
fieldsWithDefaultValue.filter((def) => (instance.customFields?.[def.name] === undefined)).forEach(({ name, defaultValue }) => {
|
|
49
49
|
// eslint-disable-next-line no-param-reassign
|
|
50
50
|
instance.customFields[name] = defaultValue;
|
|
51
51
|
});
|
package/src/hooks/enrich.ts
CHANGED
|
@@ -1,14 +1,86 @@
|
|
|
1
1
|
/* eslint-disable no-param-reassign */
|
|
2
|
+
import type { Transaction } from 'sequelize';
|
|
2
3
|
import * as ValueRepo from '../repository/value';
|
|
3
4
|
import * as DefinitionRepo from '../repository/definition';
|
|
5
|
+
import * as EntriesRepo from '../repository/entries';
|
|
4
6
|
import type CustomFieldValue from '../models/CustomFieldValue';
|
|
5
7
|
import type CustomFieldDefinition from '../models/CustomFieldDefinition';
|
|
6
8
|
import type { SerializedCustomFields } from '../types/definition';
|
|
7
|
-
import type { ModelOptions } from '../types';
|
|
9
|
+
import type { CustomFieldOptions, ModelOptions } from '../types';
|
|
8
10
|
import applyScopeToInstance from '../utils/scopeAttributes';
|
|
9
11
|
|
|
10
12
|
type SupportedHookTypes = 'afterFind' | 'afterCreate' | 'afterUpdate';
|
|
11
13
|
|
|
14
|
+
type CustomFieldEntries = Record<string, any>;
|
|
15
|
+
|
|
16
|
+
interface GetValuesGroupByInstanceResponse {
|
|
17
|
+
[modelId: string]: CustomFieldValue[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface GetCustomFieldEntriesByInstanceIdResponse {
|
|
21
|
+
[modelId: string]: CustomFieldEntries;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const getCustomFieldEntriesByInstanceId = async ({
|
|
25
|
+
instancesIds,
|
|
26
|
+
options,
|
|
27
|
+
sadotOptions,
|
|
28
|
+
}: {
|
|
29
|
+
instancesIds: string[],
|
|
30
|
+
options?: { transaction: Transaction },
|
|
31
|
+
sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>,
|
|
32
|
+
}): Promise<GetCustomFieldEntriesByInstanceIdResponse> => {
|
|
33
|
+
if (!sadotOptions.useCustomFieldsEntries) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const customFieldEntries = await EntriesRepo.findEntriesByModelIds(
|
|
38
|
+
instancesIds,
|
|
39
|
+
options ?? {},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const customFieldEntriesByInstanceId = Object.fromEntries(customFieldEntries.map((instanceEntries) => {
|
|
43
|
+
const { modelId, customFields } = instanceEntries?.dataValues ?? {};
|
|
44
|
+
if (!modelId) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return [modelId, customFields];
|
|
48
|
+
}).filter(Boolean));
|
|
49
|
+
|
|
50
|
+
instancesIds.forEach((instanceId) => {
|
|
51
|
+
customFieldEntriesByInstanceId[instanceId] ??= {};
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return customFieldEntriesByInstanceId;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const getValuesGroupByInstance = async ({
|
|
58
|
+
instancesIds,
|
|
59
|
+
options,
|
|
60
|
+
sadotOptions,
|
|
61
|
+
}: {
|
|
62
|
+
instancesIds: string[],
|
|
63
|
+
options?: { transaction: Transaction },
|
|
64
|
+
sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>,
|
|
65
|
+
}): Promise<GetValuesGroupByInstanceResponse> => {
|
|
66
|
+
if (sadotOptions.useCustomFieldsEntries) {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const customFieldValues = await ValueRepo.findValuesByModelIds(
|
|
71
|
+
instancesIds,
|
|
72
|
+
options ?? {},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Group fields by modelId
|
|
76
|
+
return customFieldValues.reduce((acc, v) => {
|
|
77
|
+
const { modelId } = v;
|
|
78
|
+
acc[modelId] ??= [];
|
|
79
|
+
acc[modelId].push(v);
|
|
80
|
+
return acc;
|
|
81
|
+
}, {});
|
|
82
|
+
};
|
|
83
|
+
|
|
12
84
|
/**
|
|
13
85
|
* Serialize custom fields value into the format of {[name] -> [fieldData]}
|
|
14
86
|
*/
|
|
@@ -33,6 +105,7 @@ const enrichResults = (
|
|
|
33
105
|
scopeAttributes: string[],
|
|
34
106
|
hookType?: SupportedHookTypes,
|
|
35
107
|
modelOptions: ModelOptions = {},
|
|
108
|
+
sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'> = { useCustomFieldsEntries: false },
|
|
36
109
|
) => async (
|
|
37
110
|
instancesOrInstance: any | any[],
|
|
38
111
|
options,
|
|
@@ -90,32 +163,29 @@ const enrichResults = (
|
|
|
90
163
|
// Get the values per instates ids:
|
|
91
164
|
const instancesIds = instances.map((i) => i[primaryKey]);
|
|
92
165
|
|
|
93
|
-
|
|
166
|
+
// Group fields by modelId
|
|
167
|
+
const valuesGroupByInstance = await getValuesGroupByInstance({
|
|
94
168
|
instancesIds,
|
|
95
|
-
|
|
96
|
-
|
|
169
|
+
options,
|
|
170
|
+
sadotOptions,
|
|
171
|
+
});
|
|
97
172
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!acc[modelId]) {
|
|
104
|
-
acc[modelId] = [];
|
|
105
|
-
}
|
|
106
|
-
acc[modelId].push(v);
|
|
107
|
-
return acc;
|
|
108
|
-
}, {});
|
|
173
|
+
const customFieldEntriesByInstanceId = await getCustomFieldEntriesByInstanceId({
|
|
174
|
+
instancesIds,
|
|
175
|
+
options,
|
|
176
|
+
sadotOptions,
|
|
177
|
+
});
|
|
109
178
|
|
|
110
179
|
// Attach custom fields to the instances
|
|
111
180
|
instances.forEach((instance) => {
|
|
112
|
-
const customFields = {};
|
|
113
181
|
const { id } = instance;
|
|
182
|
+
|
|
114
183
|
const instanceValues = valuesGroupByInstance[id];
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
184
|
+
const serializedCustomFieldsValues = instanceValues ? serializeCustomFields(instanceValues, definitionsMap) : {};
|
|
185
|
+
|
|
186
|
+
const customFields = sadotOptions.useCustomFieldsEntries
|
|
187
|
+
? customFieldEntriesByInstanceId[id]
|
|
188
|
+
: serializedCustomFieldsValues;
|
|
119
189
|
|
|
120
190
|
scopeAttributes.forEach((attribute) => {
|
|
121
191
|
const identifier = instance[attribute];
|
package/src/index.ts
CHANGED
|
@@ -37,7 +37,7 @@ const useCustomFields = async (
|
|
|
37
37
|
// The order is important
|
|
38
38
|
addHooks(models, getModel, { useCustomFieldsEntries });
|
|
39
39
|
await initTables(sequelize, options.getUser, { useCustomFieldsEntries });
|
|
40
|
-
addScopes(models, getModel);
|
|
40
|
+
addScopes(models, getModel, { useCustomFieldsEntries });
|
|
41
41
|
applyCustomAssociation(models);
|
|
42
42
|
logger.debug('sadot - custom fields finished initializing with models', models);
|
|
43
43
|
return sequelize;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
1
2
|
import type {
|
|
2
3
|
FindOptions,
|
|
3
4
|
Includeable,
|
|
@@ -9,6 +10,7 @@ import type { ModelOptions } from '../types';
|
|
|
9
10
|
import logger from '../utils/logger';
|
|
10
11
|
import { MissingDefinitionError } from '../errors';
|
|
11
12
|
import * as DefinitionRepo from './definition';
|
|
13
|
+
import { formatFunctions } from './utils/formatValues';
|
|
12
14
|
|
|
13
15
|
type CustomFieldEntriesModelOptions = ModelOptions & { include?: Includeable, transaction?: Transaction };
|
|
14
16
|
|
|
@@ -65,6 +67,15 @@ export const updateEntries = async (
|
|
|
65
67
|
logger.warn(`custom-fields: trying to update disabled values: ${valuesWithDisabledDefinitions.join(', ')}`);
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
const definitionsByName = Object.fromEntries(fieldDefinitions.map((definition) => [definition.name, definition]));
|
|
71
|
+
// If we need to format the value before we save it
|
|
72
|
+
Object.entries(customFields)
|
|
73
|
+
.filter(([definitionName]) => formatFunctions[definitionsByName[definitionName].fieldType])
|
|
74
|
+
.forEach(([definitionName, value]) => {
|
|
75
|
+
const { fieldType } = definitionsByName[definitionName];
|
|
76
|
+
customFields[definitionName] = formatFunctions[fieldType](value);
|
|
77
|
+
});
|
|
78
|
+
|
|
68
79
|
return CustomFieldEntries.upsert(
|
|
69
80
|
{
|
|
70
81
|
modelId,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CustomFieldDefinitionType } from '../../utils/constants';
|
|
2
|
+
|
|
3
|
+
export const formatFunctions = {
|
|
4
|
+
[CustomFieldDefinitionType.DATE]: (value) => {
|
|
5
|
+
if (value) {
|
|
6
|
+
const date = new Date(value);
|
|
7
|
+
if (date.toString() === 'Invalid Date') {
|
|
8
|
+
throw new Error(`Invalid date value: ${value}`);
|
|
9
|
+
}
|
|
10
|
+
return date.toISOString();
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
},
|
|
14
|
+
};
|
package/src/repository/value.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { CreateCustomFieldValue, ValuesToUpdate } from '../types/value';
|
|
|
5
5
|
import logger from '../utils/logger';
|
|
6
6
|
import { MissingDefinitionError } from '../errors';
|
|
7
7
|
import type { ModelOptions } from '../types';
|
|
8
|
-
import {
|
|
8
|
+
import { formatFunctions } from './utils/formatValues';
|
|
9
9
|
|
|
10
10
|
export const findByModelIdAndDefinition = async (modelId: string, customFieldDefinitionId: string) =>
|
|
11
11
|
CustomFieldValue.findAll({ where: { modelId, customFieldDefinitionId }, include: [CustomFieldDefinition] });
|
|
@@ -42,19 +42,6 @@ export const findValuesByModelIds = async (modelIds: string[], options?): Promis
|
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
const formatFunctions = {
|
|
46
|
-
[CustomFieldDefinitionType.DATE]: (value) => {
|
|
47
|
-
if (value) {
|
|
48
|
-
const date = new Date(value);
|
|
49
|
-
if (date.toString() === 'Invalid Date') {
|
|
50
|
-
throw new Error(`Invalid date value: ${value}`);
|
|
51
|
-
}
|
|
52
|
-
return date.toISOString();
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
|
|
58
45
|
/**
|
|
59
46
|
* Try to update custom field values for a model instance.
|
|
60
47
|
* Create new value record if not exists, but fails if value's definition not exist.
|
package/src/scopes/filter.ts
CHANGED
|
@@ -1,58 +1,26 @@
|
|
|
1
1
|
/* eslint-disable import/prefer-default-export */
|
|
2
|
-
import { Op
|
|
2
|
+
import { Op } from 'sequelize';
|
|
3
3
|
import { Sequelize } from 'sequelize-typescript';
|
|
4
4
|
import { customFields } from '@autofleet/common-types';
|
|
5
5
|
import { generateRandomString } from '../utils/helpers';
|
|
6
|
+
import type { CustomFieldOptions } from '../types';
|
|
7
|
+
import {
|
|
8
|
+
formatConditionsForEntries,
|
|
9
|
+
formatConditionsForValues,
|
|
10
|
+
getFilterCustomFieldsSubQuery,
|
|
11
|
+
getSortCustomFieldsSubQuery,
|
|
12
|
+
SubQueryType,
|
|
13
|
+
type ConditionValue,
|
|
14
|
+
type CustomFieldFilterOptions,
|
|
15
|
+
} from './helpers/filter.helpers';
|
|
6
16
|
|
|
7
17
|
const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
|
|
8
18
|
|
|
9
|
-
/**
|
|
10
|
-
* Type representing possible condition values.
|
|
11
|
-
* Currently supporting strings and arrays of strings.
|
|
12
|
-
* More types to be added (TBA).
|
|
13
|
-
*/
|
|
14
|
-
type ConditionWithOperator = {
|
|
15
|
-
operator: string;
|
|
16
|
-
value: string;
|
|
17
|
-
};
|
|
18
|
-
export type ConditionValue = ConditionWithOperator | ConditionWithOperator[] | string | string[];
|
|
19
|
-
|
|
20
|
-
export type CustomFieldSort = {
|
|
21
|
-
field: string;
|
|
22
|
-
direction: 'ASC' | 'DESC';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type CustomFieldFilterOptions = {
|
|
26
|
-
where?: WhereOptions;
|
|
27
|
-
replacements?: Record<string, string>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
19
|
type customFieldsFilterScopeParams = {
|
|
31
20
|
replacementsMap: Record<string, string>;
|
|
32
21
|
scopeValue: Record<string, ConditionValue>;
|
|
33
22
|
}
|
|
34
23
|
|
|
35
|
-
const isConditionStringArray = (input: any): input is string[] => Array.isArray(input) && typeof input[0] === 'string';
|
|
36
|
-
const isBooleanString = (input: string): boolean => ['true', 'false'].includes(input.toString());
|
|
37
|
-
const isDate = (input: any): input is Date => input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
|
|
38
|
-
|
|
39
|
-
const castValueToJsonb = (value: string, type: string) => `to_jsonb(${value}::${type})`;
|
|
40
|
-
const castValueToJsonbText = (value: string) => castValueToJsonb(value, 'text');
|
|
41
|
-
const castValueToJsonbBoolean = (value: string) => castValueToJsonb(value, 'boolean');
|
|
42
|
-
const castValueToJsonbNumeric = (value: string) => castValueToJsonb(value, 'numeric');
|
|
43
|
-
const castIfNeeded = (columnName: string, conditionValue: string): string => {
|
|
44
|
-
if (isDate(conditionValue)) {
|
|
45
|
-
return castValueToJsonb(columnName, 'timestamp');
|
|
46
|
-
}
|
|
47
|
-
return columnName;
|
|
48
|
-
};
|
|
49
|
-
const AND_DELIMITER = ' AND ';
|
|
50
|
-
const OR_DELIMITER = ' OR ';
|
|
51
|
-
const CD_TABLE_ALIAS = 'cd';
|
|
52
|
-
const CD_NAME_COLUMN = `${CD_TABLE_ALIAS}.name`;
|
|
53
|
-
const CV_TABLE_ALIAS = 'cv';
|
|
54
|
-
const CV_VALUE_COLUMN = `${CV_TABLE_ALIAS}.value`;
|
|
55
|
-
|
|
56
24
|
/**
|
|
57
25
|
* A Sequelize scope for filtering models by custom fields.
|
|
58
26
|
* This scope builds a WHERE clause to be applied on the main query.
|
|
@@ -62,68 +30,29 @@ const CV_VALUE_COLUMN = `${CV_TABLE_ALIAS}.value`;
|
|
|
62
30
|
*/
|
|
63
31
|
export const customFieldsFilterScope = (
|
|
64
32
|
name: string,
|
|
33
|
+
options?: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>,
|
|
65
34
|
) => ({ replacementsMap: replacements, scopeValue: conditions }: customFieldsFilterScopeParams): CustomFieldFilterOptions => {
|
|
66
35
|
if (!conditions || Object.keys(conditions).length === 0) {
|
|
67
36
|
return {};
|
|
68
37
|
}
|
|
38
|
+
|
|
39
|
+
const queryType = options?.useCustomFieldsEntries ? SubQueryType.ENTRIES : SubQueryType.VALUES;
|
|
69
40
|
const reverseReplacementsMap = new Map(Object.entries(replacements).map(([key, value]) => [value, key]));
|
|
70
41
|
// Build the WHERE clause for custom field filtering
|
|
71
42
|
const conditionsStrings = Object.entries(conditions).map(([key, condition]) => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// if empty array, the condition is ignored
|
|
43
|
+
switch (queryType) {
|
|
44
|
+
case SubQueryType.ENTRIES:
|
|
45
|
+
return formatConditionsForEntries(key, condition, reverseReplacementsMap);
|
|
46
|
+
case SubQueryType.VALUES:
|
|
47
|
+
return formatConditionsForValues(key, condition, reverseReplacementsMap);
|
|
48
|
+
default:
|
|
79
49
|
return false;
|
|
80
|
-
}
|
|
81
|
-
if (isConditionStringArray(condition)) {
|
|
82
|
-
const values = condition.flatMap((v) => {
|
|
83
|
-
const valRandom = reverseReplacementsMap.get(v);
|
|
84
|
-
if (isBooleanString(v)) {
|
|
85
|
-
return [castValueToJsonbText(`:${valRandom}`), castValueToJsonbBoolean(`:${valRandom}`)];
|
|
86
|
-
}
|
|
87
|
-
if (!Number.isNaN(Number(v))) {
|
|
88
|
-
return castValueToJsonbNumeric(`:${valRandom}`);
|
|
89
|
-
}
|
|
90
|
-
return castValueToJsonbText(`:${valRandom}`);
|
|
91
|
-
}).join(',');
|
|
92
|
-
return `(${columnCondition}${AND_DELIMITER}${CV_VALUE_COLUMN} IN (${values}))`;
|
|
93
|
-
}
|
|
94
|
-
return condition.map((c) => {
|
|
95
|
-
const valRep = reverseReplacementsMap.get(c.value);
|
|
96
|
-
const valueAsJsonb = castValueToJsonbText(`:${valRep}`);
|
|
97
|
-
return `(${columnCondition}${AND_DELIMITER}${castIfNeeded(CV_VALUE_COLUMN, c.value)} ${c.operator} ${valueAsJsonb})`;
|
|
98
|
-
}).join(AND_DELIMITER);
|
|
99
|
-
}
|
|
100
|
-
if (typeof condition === 'string' || typeof condition === 'number') {
|
|
101
|
-
const conditionRep = reverseReplacementsMap.get(condition);
|
|
102
|
-
const valueAsJsonb = !Number.isNaN(Number(condition)) ? castValueToJsonbNumeric(`:${conditionRep}`) : castValueToJsonbText(`:${conditionRep}`);
|
|
103
|
-
const valueAsJsonbBoolean = isBooleanString(condition) ? `${OR_DELIMITER}${CV_VALUE_COLUMN} = ${castValueToJsonbBoolean(`:${conditionRep}`)}` : '';
|
|
104
|
-
return `(${columnCondition}${AND_DELIMITER}(${castIfNeeded(CV_VALUE_COLUMN, condition)} = ${valueAsJsonb}${valueAsJsonbBoolean}))`;
|
|
105
50
|
}
|
|
106
|
-
if (condition?.operator) {
|
|
107
|
-
const valueRep = reverseReplacementsMap.get(condition.value);
|
|
108
|
-
const valueAsJsonb = castValueToJsonbText(`:${valueRep}`);
|
|
109
|
-
return `( ${columnCondition}${AND_DELIMITER}${castIfNeeded(CV_VALUE_COLUMN, condition.value)} ${condition.operator} ${valueAsJsonb})`;
|
|
110
|
-
}
|
|
111
|
-
return false;
|
|
112
51
|
}).filter(Boolean);
|
|
113
52
|
if (conditionsStrings.length === 0) {
|
|
114
53
|
return {};
|
|
115
54
|
}
|
|
116
|
-
const
|
|
117
|
-
const subQuery = `
|
|
118
|
-
SELECT cv.model_id
|
|
119
|
-
FROM custom_field_values AS cv
|
|
120
|
-
INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
|
|
121
|
-
${AND_DELIMITER}cd.model_type = '${name}'
|
|
122
|
-
WHERE ${customFieldConditions}
|
|
123
|
-
${AND_DELIMITER}cv.deleted_at IS NULL${AND_DELIMITER}cd.deleted_at IS NULL
|
|
124
|
-
GROUP BY cv.model_id
|
|
125
|
-
HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
|
|
126
|
-
`.replace(/\n/g, '');
|
|
55
|
+
const subQuery = getFilterCustomFieldsSubQuery(queryType, name, conditionsStrings);
|
|
127
56
|
|
|
128
57
|
return {
|
|
129
58
|
where: {
|
|
@@ -139,27 +68,21 @@ export const scopeName = CUSTOM_FIELDS_FILTER_SCOPE;
|
|
|
139
68
|
|
|
140
69
|
export const customFieldsSortScope = (
|
|
141
70
|
name: string,
|
|
71
|
+
options?: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>,
|
|
142
72
|
) => ({ replacementsMap, scopeValue: sort }) => {
|
|
143
73
|
if (!sort || sort.length === 0) {
|
|
144
74
|
return {};
|
|
145
75
|
}
|
|
76
|
+
|
|
77
|
+
const queryType = options?.useCustomFieldsEntries ? SubQueryType.ENTRIES : SubQueryType.VALUES;
|
|
146
78
|
const randomStr = generateRandomString();
|
|
147
79
|
const includes = Object.entries(sort).map(([key]) => {
|
|
148
|
-
const
|
|
80
|
+
const replacementKey = Object.keys(replacementsMap).find(
|
|
149
81
|
(randomString) => replacementsMap[randomString] === key,
|
|
150
82
|
);
|
|
151
83
|
return ([
|
|
152
|
-
Sequelize.literal(
|
|
153
|
-
|
|
154
|
-
FROM (SELECT cv.model_id, cv.value
|
|
155
|
-
FROM custom_field_values AS cv INNER JOIN custom_field_definitions AS cd
|
|
156
|
-
ON cv.custom_field_definition_id = cd.id
|
|
157
|
-
${AND_DELIMITER}cd.model_type = '${name}'
|
|
158
|
-
WHERE cv.model_id = "${name}"."id"
|
|
159
|
-
${AND_DELIMITER}cd.name = :${replacemetKey}
|
|
160
|
-
) AS CustomFieldAggregation
|
|
161
|
-
)
|
|
162
|
-
`), randomStr,
|
|
84
|
+
Sequelize.literal(getSortCustomFieldsSubQuery(queryType, name, replacementKey)),
|
|
85
|
+
randomStr,
|
|
163
86
|
]);
|
|
164
87
|
});
|
|
165
88
|
|