@autofleet/sadot 0.7.7-beta.1 → 0.7.7
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/api/v1/definition/validations.js +2 -24
- package/dist/events/index.js +20 -21
- package/dist/hooks/create.js +16 -32
- package/dist/models/CustomFieldDefinition.d.ts +0 -1
- package/dist/models/CustomFieldDefinition.js +0 -14
- package/dist/models/index.js +1 -1
- package/dist/models/tests/contextAwareModels/ContextAwareTestModel.js +3 -1
- package/dist/repository/definition.d.ts +6 -12
- package/dist/repository/value.js +10 -26
- package/dist/scopes/filter.d.ts +3 -10
- package/dist/scopes/filter.js +40 -39
- package/dist/tests/mocks/definition.mock.d.ts +0 -2
- package/dist/tests/mocks/definition.mock.js +9 -10
- package/dist/types/definition/index.d.ts +0 -1
- package/dist/utils/logger/index.d.ts +1 -1
- package/dist/utils/logger/index.js +3 -5
- package/package.json +1 -2
- package/src/scopes/filter.ts +20 -26
|
@@ -6,17 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.validateCustomFieldDefinitionUpdate = exports.validateCustomFieldDefinitionCreation = void 0;
|
|
7
7
|
const joi_1 = __importDefault(require("joi"));
|
|
8
8
|
const constants_1 = require("../../../utils/constants");
|
|
9
|
-
const
|
|
10
|
-
name: joi_1.default.string().required(),
|
|
11
|
-
type: joi_1.default.string(),
|
|
12
|
-
size: joi_1.default.string(),
|
|
13
|
-
addedBy: joi_1.default.string().uuid(),
|
|
14
|
-
});
|
|
15
|
-
const statusValidationObject = joi_1.default.object({
|
|
9
|
+
const statusValidationObjectSchema = joi_1.default.array().items(joi_1.default.object({
|
|
16
10
|
value: joi_1.default.string().required(),
|
|
17
11
|
color: joi_1.default.string().required(),
|
|
18
|
-
});
|
|
19
|
-
const statusValidationObjectSchema = joi_1.default.array().items(statusValidationObject).min(1).unique('value');
|
|
12
|
+
})).min(1).unique('value');
|
|
20
13
|
/**
|
|
21
14
|
* Schema for the validation of custom field definition
|
|
22
15
|
* The only custom validation is for
|
|
@@ -35,24 +28,10 @@ const ValidationSchema = joi_1.default.when('fieldType', {
|
|
|
35
28
|
then: statusValidationObjectSchema,
|
|
36
29
|
otherwise: joi_1.default.any(),
|
|
37
30
|
});
|
|
38
|
-
const DefaultValueSchema = joi_1.default.when('fieldType', {
|
|
39
|
-
switch: [
|
|
40
|
-
{ is: constants_1.CustomFieldDefinitionType.BOOLEAN, then: joi_1.default.boolean().allow(null) },
|
|
41
|
-
{ is: constants_1.CustomFieldDefinitionType.DATE, then: joi_1.default.date().allow(null) },
|
|
42
|
-
{ is: constants_1.CustomFieldDefinitionType.DATETIME, then: joi_1.default.date().allow(null) },
|
|
43
|
-
{ is: constants_1.CustomFieldDefinitionType.FILE, then: FileValidationSchema },
|
|
44
|
-
{ is: constants_1.CustomFieldDefinitionType.IMAGE, then: joi_1.default.string().uri().allow(null) },
|
|
45
|
-
{ is: constants_1.CustomFieldDefinitionType.NUMBER, then: joi_1.default.number().allow(null) },
|
|
46
|
-
{ is: constants_1.CustomFieldDefinitionType.SELECT, then: joi_1.default.string().allow(null) },
|
|
47
|
-
{ is: constants_1.CustomFieldDefinitionType.STATUS, then: statusValidationObject.allow(null) },
|
|
48
|
-
{ is: constants_1.CustomFieldDefinitionType.TEXT, then: joi_1.default.string().allow(null) },
|
|
49
|
-
],
|
|
50
|
-
});
|
|
51
31
|
const CustomFieldDefinitionCreationSchema = joi_1.default.object({
|
|
52
32
|
name: joi_1.default.string().required(),
|
|
53
33
|
displayName: joi_1.default.string().required(),
|
|
54
34
|
validation: ValidationSchema,
|
|
55
|
-
defaultValue: DefaultValueSchema,
|
|
56
35
|
fieldType: joi_1.default.string().valid(...Object.values(constants_1.CustomFieldDefinitionType)).required(),
|
|
57
36
|
entityId: joi_1.default.string().guid().required(),
|
|
58
37
|
entityType: joi_1.default.string().required(),
|
|
@@ -63,7 +42,6 @@ const CustomFieldDefinitionCreationSchema = joi_1.default.object({
|
|
|
63
42
|
const CustomFieldDefinitionUpdateSchema = joi_1.default.object({
|
|
64
43
|
displayName: joi_1.default.string(),
|
|
65
44
|
validation: ValidationSchema,
|
|
66
|
-
defaultValue: DefaultValueSchema,
|
|
67
45
|
fieldType: joi_1.default.string().valid(...Object.values(constants_1.CustomFieldDefinitionType)),
|
|
68
46
|
description: joi_1.default.string().allow(null),
|
|
69
47
|
required: joi_1.default.boolean(),
|
package/dist/events/index.js
CHANGED
|
@@ -7,18 +7,18 @@ exports.sendDimEvent = void 0;
|
|
|
7
7
|
const events_1 = __importDefault(require("@autofleet/events"));
|
|
8
8
|
const logger_1 = __importDefault(require("../utils/logger"));
|
|
9
9
|
const events = new events_1.default({ logger: logger_1.default });
|
|
10
|
-
const KEYS_TO_CONVERT = ['value'
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
13
|
-
|
|
10
|
+
const KEYS_TO_CONVERT = ['value'];
|
|
11
|
+
const stringifyBools = (savedObject, keysToConvert) => {
|
|
12
|
+
if (Object.keys(savedObject).some((key) => keysToConvert.includes(key))) {
|
|
13
|
+
const objectToReturn = { ...savedObject };
|
|
14
|
+
keysToConvert.forEach((key) => {
|
|
15
|
+
if (typeof savedObject[key] === 'boolean') {
|
|
16
|
+
objectToReturn[key] = savedObject[key].toString();
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
return objectToReturn;
|
|
14
20
|
}
|
|
15
|
-
|
|
16
|
-
keysToConvert.forEach((key) => {
|
|
17
|
-
if (typeof savedObject[key] === 'boolean') {
|
|
18
|
-
objectToReturn[key] = savedObject[key].toString();
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
return objectToReturn;
|
|
21
|
+
return savedObject;
|
|
22
22
|
};
|
|
23
23
|
const modelTableMapping = {
|
|
24
24
|
CustomFieldDefinition: {
|
|
@@ -32,17 +32,16 @@ const modelTableMapping = {
|
|
|
32
32
|
};
|
|
33
33
|
const sendDimEvent = (instance) => {
|
|
34
34
|
const mapping = modelTableMapping[instance.constructor.name];
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
if (mapping) {
|
|
36
|
+
let objectToSend = instance.get();
|
|
37
|
+
try {
|
|
38
|
+
objectToSend = stringifyBools(instance.get(), KEYS_TO_CONVERT);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
logger_1.default.error('Failed to convert booleans in dim event payload', err);
|
|
42
|
+
}
|
|
43
|
+
events.sendObject(mapping.tableName, mapping.eventVersion, objectToSend);
|
|
44
44
|
}
|
|
45
|
-
events.sendObject(mapping.tableName, mapping.eventVersion, objectToSend);
|
|
46
45
|
};
|
|
47
46
|
exports.sendDimEvent = sendDimEvent;
|
|
48
47
|
exports.default = events;
|
package/dist/hooks/create.js
CHANGED
|
@@ -48,43 +48,27 @@ exports.beforeBulkCreate = beforeBulkCreate;
|
|
|
48
48
|
const beforeCreate = (scopeAttributes, modelOptions = {}) => async (instance, options) => {
|
|
49
49
|
logger_1.default.debug('sadot - before create hook');
|
|
50
50
|
const { fields } = options;
|
|
51
|
-
const { include, useEntityIdFromInclude } = modelOptions;
|
|
52
51
|
const modelType = instance.constructor.name;
|
|
53
52
|
const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
disabled: false,
|
|
57
|
-
...(!useEntityIdFromInclude && { entityId: identifiers }),
|
|
58
|
-
};
|
|
59
|
-
const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: false, transaction: options.transaction, include: include?.(identifiers) });
|
|
60
|
-
const requiredFieldsNames = Array.from(new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)));
|
|
53
|
+
// get all model's required definitions
|
|
54
|
+
const requiredFieldsNames = await DefinitionRepo.getRequiredFields(modelType, instance.id, identifiers, modelOptions);
|
|
61
55
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
62
|
-
if ((customFieldsIdx === -1 || !instance.customFields) && requiredFieldsNames?.length > 0) {
|
|
63
|
-
throw new errors_1.MissingRequiredCustomFieldError(requiredFieldsNames);
|
|
64
|
-
}
|
|
65
|
-
const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
|
|
66
|
-
if (fieldsWithDefaultValue.length) {
|
|
67
|
-
// eslint-disable-next-line no-param-reassign
|
|
68
|
-
instance.customFields || (instance.customFields = {});
|
|
69
|
-
fieldsWithDefaultValue.filter((def) => !(def.name in instance.customFields)).forEach(({ name, defaultValue }) => {
|
|
70
|
-
// eslint-disable-next-line no-param-reassign
|
|
71
|
-
instance.customFields[name] = defaultValue;
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
56
|
const { customFields } = instance;
|
|
75
|
-
if (customFieldsIdx
|
|
76
|
-
|
|
57
|
+
if (customFieldsIdx > -1 && customFields) {
|
|
58
|
+
const fieldsNames = Object.keys(customFields);
|
|
59
|
+
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
60
|
+
if (missingFields?.length > 0) {
|
|
61
|
+
throw new errors_1.MissingRequiredCustomFieldError(missingFields);
|
|
62
|
+
}
|
|
63
|
+
await ValueRepo.updateValues(modelType, instance.id, identifiers, customFields, {
|
|
64
|
+
transaction: options.transaction,
|
|
65
|
+
modelOptions,
|
|
66
|
+
});
|
|
67
|
+
// eslint-disable-next-line no-param-reassign
|
|
68
|
+
fields.splice(customFieldsIdx, 1);
|
|
77
69
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (missingFields?.length > 0) {
|
|
81
|
-
throw new errors_1.MissingRequiredCustomFieldError(missingFields);
|
|
70
|
+
else if (requiredFieldsNames?.length > 0) {
|
|
71
|
+
throw new errors_1.MissingRequiredCustomFieldError(requiredFieldsNames);
|
|
82
72
|
}
|
|
83
|
-
await ValueRepo.updateValues(modelType, instance.id, identifiers, customFields, {
|
|
84
|
-
transaction: options.transaction,
|
|
85
|
-
modelOptions,
|
|
86
|
-
});
|
|
87
|
-
// eslint-disable-next-line no-param-reassign
|
|
88
|
-
fields.splice(customFieldsIdx, 1);
|
|
89
73
|
};
|
|
90
74
|
exports.beforeCreate = beforeCreate;
|
|
@@ -19,19 +19,12 @@ const _1 = require(".");
|
|
|
19
19
|
const events_1 = require("../events");
|
|
20
20
|
const errors_1 = require("../errors");
|
|
21
21
|
const logger_1 = __importDefault(require("../utils/logger"));
|
|
22
|
-
const validations_1 = require("../utils/validations");
|
|
23
22
|
let CustomFieldDefinition = class CustomFieldDefinition extends sequelize_typescript_1.Model {
|
|
24
23
|
static displayNameDefaultValue(instance) {
|
|
25
24
|
if (!instance?.displayName) {
|
|
26
25
|
// eslint-disable-next-line no-param-reassign
|
|
27
26
|
instance.displayName = instance.name;
|
|
28
27
|
}
|
|
29
|
-
if (![null, undefined].includes(instance.defaultValue)) {
|
|
30
|
-
const isValid = (0, validations_1.validateValue)(instance.defaultValue, instance.fieldType, instance.validation);
|
|
31
|
-
if (!isValid) {
|
|
32
|
-
throw new errors_1.InvalidValueError(instance.defaultValue, instance.fieldType);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
28
|
}
|
|
36
29
|
static afterSaveHandler(instance, options) {
|
|
37
30
|
if (options.transaction) {
|
|
@@ -123,13 +116,6 @@ __decorate([
|
|
|
123
116
|
}),
|
|
124
117
|
__metadata("design:type", Boolean)
|
|
125
118
|
], CustomFieldDefinition.prototype, "disabled", void 0);
|
|
126
|
-
__decorate([
|
|
127
|
-
(0, sequelize_typescript_1.Column)({
|
|
128
|
-
type: sequelize_typescript_1.DataType.JSONB,
|
|
129
|
-
allowNull: true,
|
|
130
|
-
}),
|
|
131
|
-
__metadata("design:type", Object)
|
|
132
|
-
], CustomFieldDefinition.prototype, "defaultValue", void 0);
|
|
133
119
|
__decorate([
|
|
134
120
|
sequelize_typescript_1.Column,
|
|
135
121
|
__metadata("design:type", Date)
|
package/dist/models/index.js
CHANGED
|
@@ -22,7 +22,7 @@ exports.AssociatedTestModel = AssociatedTestModel_1.default;
|
|
|
22
22
|
const productionModels = [CustomFieldDefinition_1.default, CustomFieldValue_1.default];
|
|
23
23
|
const testModels = [TestModel_1.default, AssociatedTestModel_1.default, ContextAwareTestModel_1.default, ContextTestModel_1.default];
|
|
24
24
|
const SADOT_MIGRATION_PREFIX = 'sadot-migration';
|
|
25
|
-
const SCHEMA_VERSION =
|
|
25
|
+
const SCHEMA_VERSION = 1;
|
|
26
26
|
const CUSTOM_FIELDS_SCHEMA_VERSION = `${SADOT_MIGRATION_PREFIX}_${SCHEMA_VERSION}`;
|
|
27
27
|
const initTables = async (sequelize, getUser) => {
|
|
28
28
|
logger_1.default.info('custom-fields: initialize custom-fields tables');
|
|
@@ -28,7 +28,9 @@ __decorate([
|
|
|
28
28
|
], ContextAwareTestModel.prototype, "id", void 0);
|
|
29
29
|
__decorate([
|
|
30
30
|
(0, sequelize_typescript_1.ForeignKey)(() => ContextTestModel_1.default),
|
|
31
|
-
(0, sequelize_typescript_1.Column)({
|
|
31
|
+
(0, sequelize_typescript_1.Column)({
|
|
32
|
+
type: sequelize_typescript_1.DataType.UUID,
|
|
33
|
+
}),
|
|
32
34
|
__metadata("design:type", String)
|
|
33
35
|
], ContextAwareTestModel.prototype, "contextId", void 0);
|
|
34
36
|
__decorate([
|
|
@@ -1,26 +1,20 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type FindOptions, type WhereOptions } from 'sequelize';
|
|
2
2
|
import { CustomFieldDefinition } from '../models';
|
|
3
3
|
import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../types/definition';
|
|
4
4
|
import type { ModelOptions } from '../types';
|
|
5
5
|
export declare const create: (data: CreateCustomFieldDefinition) => Promise<CustomFieldDefinition>;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
include?: Includeable | Includeable[];
|
|
10
|
-
}
|
|
11
|
-
export declare const findAll: (where: WhereOptions, options?: SadotFindOptions) => Promise<CustomFieldDefinition[]>;
|
|
12
|
-
export declare const findByIds: (ids: string[], options?: SadotFindOptions) => Promise<CustomFieldDefinition[]>;
|
|
13
|
-
export declare const findById: (id: string, options?: Pick<SadotFindOptions, 'withDisabled'>) => Promise<CustomFieldDefinition | null>;
|
|
6
|
+
export declare const findAll: (where: WhereOptions, options?: any) => Promise<CustomFieldDefinition[]>;
|
|
7
|
+
export declare const findByIds: (ids: string[], options?: any) => Promise<CustomFieldDefinition[]>;
|
|
8
|
+
export declare const findById: (id: string, options?: any) => Promise<CustomFieldDefinition | null>;
|
|
14
9
|
export declare const findByEntityIds: (modelType: string, entityIds: string[], options?: FindOptions & {
|
|
15
10
|
modelOptions?: ModelOptions;
|
|
16
11
|
}) => Promise<CustomFieldDefinition[]>;
|
|
17
12
|
export declare const findByWhere: (where: any) => Promise<CustomFieldDefinition | null>;
|
|
18
13
|
export declare const findDefinitionsByModels: (modelTypes: string[], options?: any) => Promise<CustomFieldDefinition[]>;
|
|
19
14
|
export declare const update: (id: string, data: UpdateCustomFieldDefinition) => Promise<CustomFieldDefinition>;
|
|
20
|
-
export declare const disable: (id: string) => Promise<
|
|
21
|
-
export declare const destroy: (id: string) => Promise<
|
|
15
|
+
export declare const disable: (id: string) => Promise<any>;
|
|
16
|
+
export declare const destroy: (id: string) => Promise<any>;
|
|
22
17
|
/**
|
|
23
18
|
* Return the names of the required fields for a given model
|
|
24
19
|
*/
|
|
25
20
|
export declare const getRequiredFields: (modelType: string, modelId: string | string[], entityId: string | string[], modelOptions?: ModelOptions) => Promise<string[]>;
|
|
26
|
-
export {};
|
package/dist/repository/value.js
CHANGED
|
@@ -31,7 +31,6 @@ const models_1 = require("../models");
|
|
|
31
31
|
const DefinitionRepo = __importStar(require("./definition"));
|
|
32
32
|
const logger_1 = __importDefault(require("../utils/logger"));
|
|
33
33
|
const errors_1 = require("../errors");
|
|
34
|
-
const constants_1 = require("../utils/constants");
|
|
35
34
|
const findByModelIdAndDefinition = async (modelId, customFieldDefinitionId) => models_1.CustomFieldValue.findAll({ where: { modelId, customFieldDefinitionId }, include: [models_1.CustomFieldDefinition] });
|
|
36
35
|
exports.findByModelIdAndDefinition = findByModelIdAndDefinition;
|
|
37
36
|
const create = async (data, withAssociations = false) => {
|
|
@@ -67,18 +66,6 @@ const findValuesByModelIds = async (modelIds, options) => {
|
|
|
67
66
|
});
|
|
68
67
|
};
|
|
69
68
|
exports.findValuesByModelIds = findValuesByModelIds;
|
|
70
|
-
const formatFunctions = {
|
|
71
|
-
[constants_1.CustomFieldDefinitionType.DATE]: (value) => {
|
|
72
|
-
if (value) {
|
|
73
|
-
const date = new Date(value);
|
|
74
|
-
if (date.toString() === 'Invalid Date') {
|
|
75
|
-
throw new Error(`Invalid date value: ${value}`);
|
|
76
|
-
}
|
|
77
|
-
return date.toISOString();
|
|
78
|
-
}
|
|
79
|
-
return null;
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
69
|
/**
|
|
83
70
|
* Try to update custom field values for a model instance.
|
|
84
71
|
* Create new value record if not exists, but fails if value's definition not exist.
|
|
@@ -96,9 +83,11 @@ const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, opt
|
|
|
96
83
|
const where = {
|
|
97
84
|
modelType,
|
|
98
85
|
name: names,
|
|
99
|
-
...(!options.modelOptions?.useEntityIdFromInclude && { entityId: identifiers }),
|
|
100
86
|
};
|
|
101
|
-
|
|
87
|
+
if (!options.modelOptions?.useEntityIdFromInclude) {
|
|
88
|
+
where.entityId = identifiers;
|
|
89
|
+
}
|
|
90
|
+
const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) || [];
|
|
102
91
|
const disabledDefinitions = fieldDefinitions.filter((def) => def.disabled);
|
|
103
92
|
if (fieldDefinitions.length !== names.length) {
|
|
104
93
|
logger_1.default.warn(`custom-fields: missing definitions for ${modelType} ${modelId}`, { names, fieldDefinitions });
|
|
@@ -110,17 +99,12 @@ const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, opt
|
|
|
110
99
|
if (valuesWithDisabledDefinitions?.length > 0) {
|
|
111
100
|
logger_1.default.warn(`custom-fields: trying to update disabled values: ${valuesWithDisabledDefinitions.join(', ')}`);
|
|
112
101
|
}
|
|
113
|
-
const values = names.map((name) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
updatedAt: new Date(),
|
|
120
|
-
customFieldDefinitionId: fieldDefinition.id,
|
|
121
|
-
value: value !== undefined ? value : fieldDefinition.defaultValue,
|
|
122
|
-
};
|
|
123
|
-
});
|
|
102
|
+
const values = names.map((name) => ({
|
|
103
|
+
modelId,
|
|
104
|
+
value: valuesToUpdate[name],
|
|
105
|
+
updatedAt: new Date(),
|
|
106
|
+
customFieldDefinitionId: fieldDefinitions.find((def) => def.name === name).id,
|
|
107
|
+
}));
|
|
124
108
|
return Promise.all(values.map(async (value) => {
|
|
125
109
|
const [cfv] = await models_1.CustomFieldValue.upsert(value, {
|
|
126
110
|
transaction: options.transaction,
|
package/dist/scopes/filter.d.ts
CHANGED
|
@@ -17,10 +17,6 @@ export type CustomFieldFilterOptions = {
|
|
|
17
17
|
where?: WhereOptions;
|
|
18
18
|
replacements?: Record<string, string>;
|
|
19
19
|
};
|
|
20
|
-
type customFieldsFilterScopeParams = {
|
|
21
|
-
replacementsMap: Record<string, string>;
|
|
22
|
-
scopeValue: Record<string, ConditionValue>;
|
|
23
|
-
};
|
|
24
20
|
/**
|
|
25
21
|
* A Sequelize scope for filtering models by custom fields.
|
|
26
22
|
* This scope builds a WHERE clause to be applied on the main query.
|
|
@@ -28,12 +24,9 @@ type customFieldsFilterScopeParams = {
|
|
|
28
24
|
* @param name - The model type name used to join custom_field_definitions.
|
|
29
25
|
* @returns A function that takes conditions and returns the Sequelize options object.
|
|
30
26
|
*/
|
|
31
|
-
export declare const customFieldsFilterScope: (name: string) => (
|
|
27
|
+
export declare const customFieldsFilterScope: (name: string) => (conditions: Record<string, ConditionValue>) => CustomFieldFilterOptions;
|
|
32
28
|
export declare const scopeName = "filterByCustomFields";
|
|
33
|
-
export declare const customFieldsSortScope: (name: string) => (
|
|
34
|
-
replacementsMap: any;
|
|
35
|
-
scopeValue: any;
|
|
36
|
-
}) => {
|
|
29
|
+
export declare const customFieldsSortScope: (name: string) => (sort: CustomFieldSort[]) => {
|
|
37
30
|
attributes?: undefined;
|
|
38
31
|
order?: undefined;
|
|
39
32
|
replacements?: undefined;
|
|
@@ -42,6 +35,6 @@ export declare const customFieldsSortScope: (name: string) => ({ replacementsMap
|
|
|
42
35
|
include: (string | import("sequelize/types/utils").Literal)[][];
|
|
43
36
|
};
|
|
44
37
|
order: import("sequelize/types/utils").Literal[];
|
|
45
|
-
replacements:
|
|
38
|
+
replacements: Record<string, string>;
|
|
46
39
|
};
|
|
47
40
|
export {};
|
package/dist/scopes/filter.js
CHANGED
|
@@ -8,12 +8,11 @@ exports.customFieldsSortScope = exports.scopeName = exports.customFieldsFilterSc
|
|
|
8
8
|
const sequelize_1 = require("sequelize");
|
|
9
9
|
const sequelize_typescript_1 = require("sequelize-typescript");
|
|
10
10
|
const common_types_1 = require("@autofleet/common-types");
|
|
11
|
+
const moment_1 = __importDefault(require("moment"));
|
|
11
12
|
const helpers_1 = require("../utils/helpers");
|
|
12
|
-
const logger_1 = __importDefault(require("../utils/logger"));
|
|
13
13
|
const { CUSTOM_FIELDS_FILTER_SCOPE } = common_types_1.customFields;
|
|
14
|
-
const isDate = (input) => input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
|
|
15
14
|
const castIfNeeded = (conditionValue) => {
|
|
16
|
-
if (isDate(conditionValue)) {
|
|
15
|
+
if (moment_1.default.isDate(conditionValue)) {
|
|
17
16
|
return '::timestamp';
|
|
18
17
|
}
|
|
19
18
|
if (!Number.isNaN(Number(conditionValue))) {
|
|
@@ -21,18 +20,13 @@ const castIfNeeded = (conditionValue) => {
|
|
|
21
20
|
}
|
|
22
21
|
return '';
|
|
23
22
|
};
|
|
24
|
-
const addQuotationIfNeeded = (conditionValue, valRep) => {
|
|
25
|
-
if (typeof conditionValue === 'string') {
|
|
26
|
-
return `'"' || :${valRep} || '"'`;
|
|
27
|
-
}
|
|
28
|
-
return `:${valRep}`;
|
|
29
|
-
};
|
|
30
23
|
const AND_DELIMETER = ' AND ';
|
|
31
24
|
const OR_DELIMETER = ' OR ';
|
|
32
25
|
const CD_TABLE_ALIAS = 'cd';
|
|
33
26
|
const CD_NAME_COLUMN = `${CD_TABLE_ALIAS}.name`;
|
|
34
27
|
const CV_TABLE_ALIAS = 'cv';
|
|
35
|
-
const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value
|
|
28
|
+
const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value)`;
|
|
29
|
+
const castValueToJsonb = (value) => `to_jsonb(${value}::text)`;
|
|
36
30
|
/**
|
|
37
31
|
* A Sequelize scope for filtering models by custom fields.
|
|
38
32
|
* This scope builds a WHERE clause to be applied on the main query.
|
|
@@ -40,16 +34,18 @@ const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value::varchar)`;
|
|
|
40
34
|
* @param name - The model type name used to join custom_field_definitions.
|
|
41
35
|
* @returns A function that takes conditions and returns the Sequelize options object.
|
|
42
36
|
*/
|
|
43
|
-
const customFieldsFilterScope = (name) => (
|
|
37
|
+
const customFieldsFilterScope = (name) => (conditions) => {
|
|
44
38
|
if (!conditions || Object.keys(conditions).length === 0) {
|
|
45
39
|
return {};
|
|
46
40
|
}
|
|
41
|
+
const ConditionNameRandomStr = (0, helpers_1.generateRandomString)();
|
|
42
|
+
const replacements = {};
|
|
43
|
+
replacements[ConditionNameRandomStr] = `${name}`;
|
|
47
44
|
// Build the WHERE clause for custom field filtering
|
|
48
45
|
const conditionsStrings = Object.entries(conditions)
|
|
49
46
|
.map(([key, condition]) => {
|
|
50
|
-
const replacemetKey =
|
|
51
|
-
|
|
52
|
-
return false;
|
|
47
|
+
const replacemetKey = (0, helpers_1.generateRandomString)();
|
|
48
|
+
replacements[replacemetKey] = `${key}`;
|
|
53
49
|
const columnCondition = `(${CD_NAME_COLUMN} = :${replacemetKey})`;
|
|
54
50
|
if (Array.isArray(condition)) {
|
|
55
51
|
if (condition.length === 0) {
|
|
@@ -58,24 +54,31 @@ const customFieldsFilterScope = (name) => ({ replacementsMap: replacements, scop
|
|
|
58
54
|
}
|
|
59
55
|
if (typeof condition[0] === 'string') {
|
|
60
56
|
const values = condition.map((v) => {
|
|
61
|
-
const valRandom =
|
|
62
|
-
|
|
57
|
+
const valRandom = (0, helpers_1.generateRandomString)();
|
|
58
|
+
replacements[`${valRandom}`] = `${v}`;
|
|
59
|
+
return castValueToJsonb(`:${valRandom}`);
|
|
63
60
|
}).join(',');
|
|
64
61
|
return `(${columnCondition} AND ${CV_VALUE_COLUMN} IN ( ${values} ))`;
|
|
65
62
|
}
|
|
66
63
|
return condition
|
|
67
64
|
.map((c) => {
|
|
68
|
-
const valRep =
|
|
69
|
-
|
|
65
|
+
const valRep = (0, helpers_1.generateRandomString)();
|
|
66
|
+
replacements[valRep] = `${c.value}`;
|
|
67
|
+
const valueAsJsonb = castValueToJsonb(`:${valRep}`);
|
|
68
|
+
return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(c.value)} ${c.operator} ${valueAsJsonb})`;
|
|
70
69
|
}).join(AND_DELIMETER);
|
|
71
70
|
}
|
|
72
71
|
if (typeof condition === 'string' || typeof condition === 'number') {
|
|
73
|
-
const conditionRep =
|
|
74
|
-
|
|
72
|
+
const conditionRep = (0, helpers_1.generateRandomString)();
|
|
73
|
+
replacements[conditionRep] = `${condition}`;
|
|
74
|
+
const valueAsJsonb = castValueToJsonb(`:${conditionRep}`);
|
|
75
|
+
return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition)} = ${valueAsJsonb})`;
|
|
75
76
|
}
|
|
76
77
|
if (condition?.operator) {
|
|
77
|
-
const valueRep =
|
|
78
|
-
|
|
78
|
+
const valueRep = (0, helpers_1.generateRandomString)();
|
|
79
|
+
replacements[valueRep] = `${condition.value}`;
|
|
80
|
+
const valueAsJsonb = castValueToJsonb(`:${valueRep}`);
|
|
81
|
+
return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition.value)} ${condition.operator} ${valueAsJsonb})`;
|
|
79
82
|
}
|
|
80
83
|
return false;
|
|
81
84
|
})
|
|
@@ -85,15 +88,14 @@ const customFieldsFilterScope = (name) => ({ replacementsMap: replacements, scop
|
|
|
85
88
|
}
|
|
86
89
|
const customFieldConditions = conditionsStrings.join(OR_DELIMETER);
|
|
87
90
|
const subQuery = `
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
`.replace(/\n/g, '');
|
|
96
|
-
logger_1.default.info('custom fields filter scope', { subQuery, replacements });
|
|
91
|
+
SELECT cv.model_id
|
|
92
|
+
FROM custom_field_values AS cv
|
|
93
|
+
INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
|
|
94
|
+
AND cd.model_type = :${ConditionNameRandomStr}
|
|
95
|
+
WHERE ${customFieldConditions}
|
|
96
|
+
GROUP BY cv.model_id
|
|
97
|
+
HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
|
|
98
|
+
`.replace(/\n/g, '');
|
|
97
99
|
return {
|
|
98
100
|
where: {
|
|
99
101
|
id: {
|
|
@@ -105,13 +107,15 @@ const customFieldsFilterScope = (name) => ({ replacementsMap: replacements, scop
|
|
|
105
107
|
};
|
|
106
108
|
exports.customFieldsFilterScope = customFieldsFilterScope;
|
|
107
109
|
exports.scopeName = CUSTOM_FIELDS_FILTER_SCOPE;
|
|
108
|
-
const customFieldsSortScope = (name) => (
|
|
110
|
+
const customFieldsSortScope = (name) => (sort) => {
|
|
109
111
|
if (!sort || sort.length === 0) {
|
|
110
112
|
return {};
|
|
111
113
|
}
|
|
112
114
|
const randomStr = (0, helpers_1.generateRandomString)();
|
|
115
|
+
const replacements = {};
|
|
113
116
|
const includes = Object.entries(sort).map(([key]) => {
|
|
114
|
-
const
|
|
117
|
+
const keyRandomReplacement = (0, helpers_1.generateRandomString)();
|
|
118
|
+
replacements[keyRandomReplacement] = `${key}`;
|
|
115
119
|
return ([
|
|
116
120
|
sequelize_typescript_1.Sequelize.literal(`(
|
|
117
121
|
SELECT value
|
|
@@ -120,22 +124,19 @@ const customFieldsSortScope = (name) => ({ replacementsMap, scopeValue: sort })
|
|
|
120
124
|
ON cv.custom_field_definition_id = cd.id
|
|
121
125
|
AND cd.model_type = '${name}'
|
|
122
126
|
WHERE cv.model_id = "${name}"."id"
|
|
123
|
-
AND cd.name = :${
|
|
127
|
+
AND cd.name = :${keyRandomReplacement}
|
|
124
128
|
) AS CustomFieldAggregation
|
|
125
129
|
)
|
|
126
130
|
`), randomStr,
|
|
127
131
|
]);
|
|
128
132
|
});
|
|
129
|
-
const orders = Object.entries(sort).map(([,
|
|
130
|
-
const direction = typeof sortObject === 'string' ? sortObject : Object.values(sortObject)[0];
|
|
131
|
-
return sequelize_typescript_1.Sequelize.literal(`"${randomStr}" ${direction || 'ASC'}`);
|
|
132
|
-
});
|
|
133
|
+
const orders = Object.entries(sort).map(([, value]) => sequelize_typescript_1.Sequelize.literal(`"${randomStr}" ${value}`));
|
|
133
134
|
return {
|
|
134
135
|
attributes: {
|
|
135
136
|
include: includes,
|
|
136
137
|
},
|
|
137
138
|
order: orders,
|
|
138
|
-
replacements
|
|
139
|
+
replacements,
|
|
139
140
|
};
|
|
140
141
|
};
|
|
141
142
|
exports.customFieldsSortScope = customFieldsSortScope;
|
|
@@ -14,7 +14,6 @@ export declare const coolFieldDefinition2: {
|
|
|
14
14
|
createdAt?: Date;
|
|
15
15
|
updatedAt?: Date;
|
|
16
16
|
deletedAt?: Date;
|
|
17
|
-
defaultValue?: any;
|
|
18
17
|
displayName?: string;
|
|
19
18
|
validation?: any;
|
|
20
19
|
fieldType: string;
|
|
@@ -30,7 +29,6 @@ export declare const coolFieldDefinition3: {
|
|
|
30
29
|
createdAt?: Date;
|
|
31
30
|
updatedAt?: Date;
|
|
32
31
|
deletedAt?: Date;
|
|
33
|
-
defaultValue?: any;
|
|
34
32
|
displayName?: string;
|
|
35
33
|
validation?: any;
|
|
36
34
|
fieldType: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createDefinitions = exports.createDefinition = exports.statusField = exports.selectField = exports.booleanField = exports.coolFieldDefinition3 = exports.coolFieldDefinition2 = exports.coolFieldDefinition = exports.contextAwareFieldDefinition = void 0;
|
|
4
|
-
const
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
5
|
exports.contextAwareFieldDefinition = {
|
|
6
6
|
name: 'cool field',
|
|
7
7
|
modelType: 'ContextAwareTestModel',
|
|
@@ -12,7 +12,7 @@ exports.coolFieldDefinition = {
|
|
|
12
12
|
name: 'cool field',
|
|
13
13
|
modelType: 'TestModel',
|
|
14
14
|
fieldType: 'number',
|
|
15
|
-
entityId: (0,
|
|
15
|
+
entityId: (0, uuid_1.v4)(),
|
|
16
16
|
entityType: 'fleetId',
|
|
17
17
|
};
|
|
18
18
|
exports.coolFieldDefinition2 = {
|
|
@@ -27,7 +27,7 @@ const booleanField = (modelType) => ({
|
|
|
27
27
|
name: 'shapeless',
|
|
28
28
|
modelType,
|
|
29
29
|
fieldType: 'boolean',
|
|
30
|
-
entityId: (0,
|
|
30
|
+
entityId: (0, uuid_1.v4)(),
|
|
31
31
|
entityType: 'fleetId',
|
|
32
32
|
});
|
|
33
33
|
exports.booleanField = booleanField;
|
|
@@ -36,7 +36,7 @@ const selectField = (modelType, options) => ({
|
|
|
36
36
|
modelType,
|
|
37
37
|
fieldType: 'select',
|
|
38
38
|
validation: options,
|
|
39
|
-
entityId: (0,
|
|
39
|
+
entityId: (0, uuid_1.v4)(),
|
|
40
40
|
entityType: 'fleetId',
|
|
41
41
|
});
|
|
42
42
|
exports.selectField = selectField;
|
|
@@ -45,25 +45,24 @@ const statusField = (modelType, options) => ({
|
|
|
45
45
|
modelType,
|
|
46
46
|
fieldType: 'status',
|
|
47
47
|
validation: options,
|
|
48
|
-
entityId: (0,
|
|
48
|
+
entityId: (0, uuid_1.v4)(),
|
|
49
49
|
entityType: 'fleetId',
|
|
50
50
|
});
|
|
51
51
|
exports.statusField = statusField;
|
|
52
52
|
// eslint-disable-next-line max-len
|
|
53
53
|
const createDefinition = (defaults) => ({
|
|
54
|
-
name: defaults?.name || `def_${(0,
|
|
54
|
+
name: defaults?.name || `def_${(0, uuid_1.v4)()}`,
|
|
55
55
|
modelType: defaults?.modelType || 'TestModel',
|
|
56
56
|
fieldType: defaults?.fieldType || 'boolean',
|
|
57
|
-
entityId: defaults?.entityId || (0,
|
|
57
|
+
entityId: defaults?.entityId || (0, uuid_1.v4)(),
|
|
58
58
|
entityType: defaults?.entityType || 'fleetId',
|
|
59
|
-
...(defaults?.defaultValue && { defaultValue: defaults.defaultValue }),
|
|
60
59
|
});
|
|
61
60
|
exports.createDefinition = createDefinition;
|
|
62
61
|
const createDefinitions = (defaults, length = 1) => (Array(length).fill({}).map((_) => ({
|
|
63
|
-
name: defaults?.name || `def_${(0,
|
|
62
|
+
name: defaults?.name || `def_${(0, uuid_1.v4)()}`,
|
|
64
63
|
modelType: defaults?.modelType || 'TestModel',
|
|
65
64
|
fieldType: defaults?.fieldType || 'boolean',
|
|
66
|
-
entityId: defaults?.entityId || (0,
|
|
65
|
+
entityId: defaults?.entityId || (0, uuid_1.v4)(),
|
|
67
66
|
entityType: defaults?.entityType || 'fleetId',
|
|
68
67
|
})));
|
|
69
68
|
exports.createDefinitions = createDefinitions;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const logger:
|
|
1
|
+
declare const logger: any;
|
|
2
2
|
export default logger;
|
|
@@ -1,8 +1,6 @@
|
|
|
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
|
-
|
|
7
|
-
const
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
4
|
+
const Logger = require('@autofleet/logger');
|
|
5
|
+
const logger = Logger();
|
|
8
6
|
exports.default = logger;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sadot",
|
|
3
|
-
"version": "0.7.7
|
|
3
|
+
"version": "0.7.7",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
"build": "rm -rf dist && tsc",
|
|
9
9
|
"linter": "eslint .",
|
|
10
10
|
"test": "jest --forceExit --runInBand",
|
|
11
|
-
"test-debug": "node --inspect-brk node_modules/.bin/jest --runInBand --testTimeout=10000000",
|
|
12
11
|
"coverage": "jest --coverage --forceExit --runInBand && rm -rf ./coverage",
|
|
13
12
|
"build-to-local-repo": "node --run build && cp -r dist/* ../$REPO/node_modules/$npm_package_name/dist",
|
|
14
13
|
"dev": "nodemon",
|
package/src/scopes/filter.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { Op, type WhereOptions } 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 logger from '../utils/logger';
|
|
7
6
|
|
|
8
7
|
const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
|
|
9
8
|
|
|
@@ -44,21 +43,13 @@ const castIfNeeded = (conditionValue: string): string => {
|
|
|
44
43
|
}
|
|
45
44
|
return '';
|
|
46
45
|
};
|
|
47
|
-
|
|
48
|
-
const addQuotationIfNeeded = (conditionValue: string, valRep: string): string => {
|
|
49
|
-
if (typeof conditionValue === 'string') {
|
|
50
|
-
return `'"' || :${valRep} || '"'`;
|
|
51
|
-
}
|
|
52
|
-
return `:${valRep}`;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
46
|
const AND_DELIMETER = ' AND ';
|
|
56
47
|
const OR_DELIMETER = ' OR ';
|
|
57
|
-
|
|
58
48
|
const CD_TABLE_ALIAS = 'cd';
|
|
59
49
|
const CD_NAME_COLUMN = `${CD_TABLE_ALIAS}.name`;
|
|
60
50
|
const CV_TABLE_ALIAS = 'cv';
|
|
61
|
-
const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value
|
|
51
|
+
const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value)`;
|
|
52
|
+
const castValueToJsonb = (value) => `to_jsonb(${value}::text)`;
|
|
62
53
|
|
|
63
54
|
/**
|
|
64
55
|
* A Sequelize scope for filtering models by custom fields.
|
|
@@ -85,8 +76,8 @@ export const customFieldsFilterScope = (
|
|
|
85
76
|
const replacemetKey = Object.keys(replacements).find(
|
|
86
77
|
(randomString) => replacements[randomString] === key,
|
|
87
78
|
);
|
|
88
|
-
if (!replacemetKey) return false;
|
|
89
79
|
const columnCondition = `(${CD_NAME_COLUMN} = :${replacemetKey})`;
|
|
80
|
+
if (!replacemetKey) return false;
|
|
90
81
|
|
|
91
82
|
if (Array.isArray(condition)) {
|
|
92
83
|
if (condition.length === 0) {
|
|
@@ -98,29 +89,32 @@ export const customFieldsFilterScope = (
|
|
|
98
89
|
const valRandom = Object.keys(replacements).find(
|
|
99
90
|
(randomString) => replacements[randomString] === v,
|
|
100
91
|
);
|
|
101
|
-
return
|
|
92
|
+
return castValueToJsonb(`:${valRandom}`);
|
|
102
93
|
}).join(',');
|
|
103
|
-
return `(${columnCondition} AND ${CV_VALUE_COLUMN} IN (
|
|
94
|
+
return `(${columnCondition} AND ${CV_VALUE_COLUMN} IN (${values}))`;
|
|
104
95
|
}
|
|
105
96
|
return condition
|
|
106
97
|
.map((c) => {
|
|
107
98
|
const valRep = Object.keys(replacements).find(
|
|
108
99
|
(replacementKey) => replacements[replacementKey] === c.value,
|
|
109
100
|
);
|
|
110
|
-
|
|
101
|
+
const valueAsJsonb = castValueToJsonb(`:${valRep}`);
|
|
102
|
+
return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(c.value)} ${c.operator} ${valueAsJsonb})`;
|
|
111
103
|
}).join(AND_DELIMETER);
|
|
112
104
|
}
|
|
113
105
|
if (typeof condition === 'string' || typeof condition === 'number') {
|
|
114
106
|
const conditionRep = Object.keys(replacements).find(
|
|
115
107
|
(replacementKey) => replacements[replacementKey] === condition,
|
|
116
108
|
);
|
|
117
|
-
|
|
109
|
+
const valueAsJsonb = castValueToJsonb(`:${conditionRep}`);
|
|
110
|
+
return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition)} = ${valueAsJsonb})`;
|
|
118
111
|
}
|
|
119
112
|
if (condition?.operator) {
|
|
120
113
|
const valueRep = Object.keys(replacements).find(
|
|
121
114
|
(replacementKey) => replacements[replacementKey] === condition.value,
|
|
122
115
|
);
|
|
123
|
-
|
|
116
|
+
const valueAsJsonb = castValueToJsonb(`:${valueRep}`);
|
|
117
|
+
return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition.value)} ${condition.operator} ${valueAsJsonb})`;
|
|
124
118
|
}
|
|
125
119
|
return false;
|
|
126
120
|
},
|
|
@@ -131,15 +125,15 @@ export const customFieldsFilterScope = (
|
|
|
131
125
|
}
|
|
132
126
|
const customFieldConditions = conditionsStrings.join(OR_DELIMETER);
|
|
133
127
|
const subQuery = `
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
`.replace(/\n/g, '');
|
|
142
|
-
|
|
128
|
+
SELECT cv.model_id
|
|
129
|
+
FROM custom_field_values AS cv
|
|
130
|
+
INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
|
|
131
|
+
|
|
132
|
+
WHERE ${customFieldConditions}
|
|
133
|
+
GROUP BY cv.model_id
|
|
134
|
+
HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
|
|
135
|
+
`.replace(/\n/g, '');
|
|
136
|
+
console.log(subQuery);
|
|
143
137
|
return {
|
|
144
138
|
where: {
|
|
145
139
|
id: {
|