@autofleet/sadot 0.7.6 → 0.7.7-beta.0
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/coverage/clover.xml +1073 -0
- package/coverage/coverage-final.json +44 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +461 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/src/api/index.html +116 -0
- package/coverage/lcov-report/src/api/index.ts.html +112 -0
- package/coverage/lcov-report/src/api/v1/definition/index.html +131 -0
- package/coverage/lcov-report/src/api/v1/definition/index.ts.html +406 -0
- package/coverage/lcov-report/src/api/v1/definition/validations.ts.html +310 -0
- package/coverage/lcov-report/src/api/v1/errors.ts.html +133 -0
- package/coverage/lcov-report/src/api/v1/index.html +131 -0
- package/coverage/lcov-report/src/api/v1/index.ts.html +112 -0
- package/coverage/lcov-report/src/errors/index.html +116 -0
- package/coverage/lcov-report/src/errors/index.ts.html +211 -0
- package/coverage/lcov-report/src/events/index.html +116 -0
- package/coverage/lcov-report/src/events/index.ts.html +238 -0
- package/coverage/lcov-report/src/hooks/create.ts.html +262 -0
- package/coverage/lcov-report/src/hooks/enrich.ts.html +523 -0
- package/coverage/lcov-report/src/hooks/find.ts.html +166 -0
- package/coverage/lcov-report/src/hooks/index.html +191 -0
- package/coverage/lcov-report/src/hooks/index.ts.html +130 -0
- package/coverage/lcov-report/src/hooks/update.ts.html +211 -0
- package/coverage/lcov-report/src/hooks/workaround.ts.html +226 -0
- package/coverage/lcov-report/src/index.html +116 -0
- package/coverage/lcov-report/src/index.ts.html +235 -0
- package/coverage/lcov-report/src/models/CustomFieldDefinition.ts.html +550 -0
- package/coverage/lcov-report/src/models/CustomFieldValue.ts.html +415 -0
- package/coverage/lcov-report/src/models/index.html +146 -0
- package/coverage/lcov-report/src/models/index.ts.html +406 -0
- package/coverage/lcov-report/src/models/tests/AssociatedTestModel.ts.html +256 -0
- package/coverage/lcov-report/src/models/tests/TestModel.ts.html +247 -0
- package/coverage/lcov-report/src/models/tests/contextAwareModels/ContextAwareTestModel.ts.html +214 -0
- package/coverage/lcov-report/src/models/tests/contextAwareModels/ContextTestModel.ts.html +199 -0
- package/coverage/lcov-report/src/models/tests/contextAwareModels/index.html +131 -0
- package/coverage/lcov-report/src/models/tests/index.html +131 -0
- package/coverage/lcov-report/src/repository/definition.ts.html +466 -0
- package/coverage/lcov-report/src/repository/index.html +131 -0
- package/coverage/lcov-report/src/repository/value.ts.html +523 -0
- package/coverage/lcov-report/src/scopes/filter.ts.html +637 -0
- package/coverage/lcov-report/src/scopes/index.html +131 -0
- package/coverage/lcov-report/src/scopes/index.ts.html +103 -0
- package/coverage/lcov-report/src/tests/api/index.html +116 -0
- package/coverage/lcov-report/src/tests/api/test-api.ts.html +199 -0
- package/coverage/lcov-report/src/tests/functional/searching/index.html +116 -0
- package/coverage/lcov-report/src/tests/functional/searching/index.ts.html +202 -0
- package/coverage/lcov-report/src/tests/helpers/database-config.ts.html +130 -0
- package/coverage/lcov-report/src/tests/helpers/index.html +131 -0
- package/coverage/lcov-report/src/tests/helpers/index.ts.html +142 -0
- package/coverage/lcov-report/src/tests/mocks/definition.mock.ts.html +310 -0
- package/coverage/lcov-report/src/tests/mocks/events.mock.ts.html +139 -0
- package/coverage/lcov-report/src/tests/mocks/index.html +146 -0
- package/coverage/lcov-report/src/tests/mocks/testModel.ts.html +196 -0
- package/coverage/lcov-report/src/utils/constants/index.html +116 -0
- package/coverage/lcov-report/src/utils/constants/index.ts.html +160 -0
- package/coverage/lcov-report/src/utils/db/index.html +116 -0
- package/coverage/lcov-report/src/utils/db/index.ts.html +148 -0
- package/coverage/lcov-report/src/utils/helpers/index.html +116 -0
- package/coverage/lcov-report/src/utils/helpers/index.ts.html +283 -0
- package/coverage/lcov-report/src/utils/index.html +131 -0
- package/coverage/lcov-report/src/utils/init.ts.html +430 -0
- package/coverage/lcov-report/src/utils/logger/index.html +116 -0
- package/coverage/lcov-report/src/utils/logger/index.ts.html +100 -0
- package/coverage/lcov-report/src/utils/scopeAttributes.ts.html +121 -0
- package/coverage/lcov-report/src/utils/validations/index.html +116 -0
- package/coverage/lcov-report/src/utils/validations/index.ts.html +151 -0
- package/coverage/lcov-report/src/utils/validations/schema/custom-fields.ts.html +109 -0
- package/coverage/lcov-report/src/utils/validations/schema/index.html +116 -0
- package/coverage/lcov-report/src/utils/validations/validators/index.html +146 -0
- package/coverage/lcov-report/src/utils/validations/validators/index.ts.html +199 -0
- package/coverage/lcov-report/src/utils/validations/validators/select.validator.ts.html +118 -0
- package/coverage/lcov-report/src/utils/validations/validators/status.validator.ts.html +139 -0
- package/coverage/lcov.info +1818 -0
- package/dist/hooks/create.js +16 -32
- package/dist/repository/definition.d.ts +6 -12
- package/dist/repository/value.d.ts +1 -1
- package/dist/repository/value.js +18 -5
- package/dist/scopes/filter.js +26 -13
- package/package.json +2 -1
- package/src/hooks/create.ts +24 -42
- package/src/repository/definition.ts +6 -15
- package/src/repository/value.ts +20 -4
- package/src/scopes/filter.ts +24 -13
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
|
+
}, true);
|
|
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;
|
|
@@ -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 {};
|
|
@@ -24,5 +24,5 @@ export declare const findValuesByModelIds: (modelIds: string[], options?: any) =
|
|
|
24
24
|
*/
|
|
25
25
|
export declare const updateValues: (modelType: string, modelId: string, identifiers: string[], valuesToUpdate: ValuesToUpdate, options?: FindOptions & {
|
|
26
26
|
modelOptions?: ModelOptions;
|
|
27
|
-
}) => Promise<CustomFieldValue[]>;
|
|
27
|
+
}, defineAllDefaults?: boolean) => Promise<CustomFieldValue[]>;
|
|
28
28
|
export declare const deleteValue: (id: string, options?: any) => Promise<any>;
|
package/dist/repository/value.js
CHANGED
|
@@ -84,7 +84,7 @@ const formatFunctions = {
|
|
|
84
84
|
* Create new value record if not exists, but fails if value's definition not exist.
|
|
85
85
|
* Return the updated values
|
|
86
86
|
*/
|
|
87
|
-
const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, options = {}) => {
|
|
87
|
+
const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, options = {}, defineAllDefaults = false) => {
|
|
88
88
|
const names = Object.keys(valuesToUpdate);
|
|
89
89
|
logger_1.default.debug(`custom-fields: updating values for ${modelType} ${modelId}`, {
|
|
90
90
|
names,
|
|
@@ -96,9 +96,11 @@ const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, opt
|
|
|
96
96
|
const where = {
|
|
97
97
|
modelType,
|
|
98
98
|
name: names,
|
|
99
|
-
...(!options.modelOptions?.useEntityIdFromInclude && { entityId: identifiers }),
|
|
100
99
|
};
|
|
101
|
-
|
|
100
|
+
if (!options.modelOptions?.useEntityIdFromInclude) {
|
|
101
|
+
where.entityId = identifiers;
|
|
102
|
+
}
|
|
103
|
+
const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) || [];
|
|
102
104
|
const disabledDefinitions = fieldDefinitions.filter((def) => def.disabled);
|
|
103
105
|
if (fieldDefinitions.length !== names.length) {
|
|
104
106
|
logger_1.default.warn(`custom-fields: missing definitions for ${modelType} ${modelId}`, { names, fieldDefinitions });
|
|
@@ -110,17 +112,28 @@ const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, opt
|
|
|
110
112
|
if (valuesWithDisabledDefinitions?.length > 0) {
|
|
111
113
|
logger_1.default.warn(`custom-fields: trying to update disabled values: ${valuesWithDisabledDefinitions.join(', ')}`);
|
|
112
114
|
}
|
|
115
|
+
const visitedFields = new Set();
|
|
113
116
|
const values = names.map((name) => {
|
|
114
117
|
const fieldDefinition = fieldDefinitions.find((def) => def.name === name);
|
|
118
|
+
visitedFields.add(fieldDefinition);
|
|
115
119
|
const formatFunction = formatFunctions[fieldDefinition.fieldType];
|
|
116
|
-
const value = formatFunction ? formatFunction(valuesToUpdate[name]) : valuesToUpdate[name];
|
|
117
120
|
return {
|
|
118
121
|
modelId,
|
|
122
|
+
value: (formatFunction ? formatFunction(valuesToUpdate[name]) : valuesToUpdate[name]) ?? fieldDefinition.defaultValue,
|
|
119
123
|
updatedAt: new Date(),
|
|
120
124
|
customFieldDefinitionId: fieldDefinition.id,
|
|
121
|
-
value: value !== undefined ? value : fieldDefinition.defaultValue,
|
|
122
125
|
};
|
|
123
126
|
});
|
|
127
|
+
if (defineAllDefaults) {
|
|
128
|
+
fieldDefinitions.filter((def) => !visitedFields.has(def) && ![null, undefined].includes(def.defaultValue)).forEach(({ id, defaultValue }) => {
|
|
129
|
+
values.push({
|
|
130
|
+
modelId,
|
|
131
|
+
value: defaultValue,
|
|
132
|
+
updatedAt: new Date(),
|
|
133
|
+
customFieldDefinitionId: id,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
124
137
|
return Promise.all(values.map(async (value) => {
|
|
125
138
|
const [cfv] = await models_1.CustomFieldValue.upsert(value, {
|
|
126
139
|
transaction: options.transaction,
|
package/dist/scopes/filter.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
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 */
|
|
@@ -6,6 +9,7 @@ const sequelize_1 = require("sequelize");
|
|
|
6
9
|
const sequelize_typescript_1 = require("sequelize-typescript");
|
|
7
10
|
const common_types_1 = require("@autofleet/common-types");
|
|
8
11
|
const helpers_1 = require("../utils/helpers");
|
|
12
|
+
const logger_1 = __importDefault(require("../utils/logger"));
|
|
9
13
|
const { CUSTOM_FIELDS_FILTER_SCOPE } = common_types_1.customFields;
|
|
10
14
|
const isDate = (input) => input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
|
|
11
15
|
const castIfNeeded = (conditionValue) => {
|
|
@@ -18,6 +22,11 @@ const castIfNeeded = (conditionValue) => {
|
|
|
18
22
|
return '';
|
|
19
23
|
};
|
|
20
24
|
const AND_DELIMETER = ' AND ';
|
|
25
|
+
const OR_DELIMETER = ' OR ';
|
|
26
|
+
const CD_TABLE_ALIAS = 'cd';
|
|
27
|
+
const CD_NAME_COLUMN = `${CD_TABLE_ALIAS}.name`;
|
|
28
|
+
const CV_TABLE_ALIAS = 'cv';
|
|
29
|
+
const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value::varchar)`;
|
|
21
30
|
/**
|
|
22
31
|
* A Sequelize scope for filtering models by custom fields.
|
|
23
32
|
* This scope builds a WHERE clause to be applied on the main query.
|
|
@@ -35,6 +44,7 @@ const customFieldsFilterScope = (name) => ({ replacementsMap: replacements, scop
|
|
|
35
44
|
const replacemetKey = Object.keys(replacements).find((randomString) => replacements[randomString] === key);
|
|
36
45
|
if (!replacemetKey)
|
|
37
46
|
return false;
|
|
47
|
+
const columnCondition = `(${CD_NAME_COLUMN} = :${replacemetKey})`;
|
|
38
48
|
if (Array.isArray(condition)) {
|
|
39
49
|
if (condition.length === 0) {
|
|
40
50
|
// if empty array, the condition is ignored
|
|
@@ -43,23 +53,23 @@ const customFieldsFilterScope = (name) => ({ replacementsMap: replacements, scop
|
|
|
43
53
|
if (typeof condition[0] === 'string') {
|
|
44
54
|
const values = condition.map((v) => {
|
|
45
55
|
const valRandom = Object.keys(replacements).find((randomString) => replacements[randomString] === v);
|
|
46
|
-
return ` :${valRandom} `;
|
|
56
|
+
return `'"' || :${valRandom} || '"'`;
|
|
47
57
|
}).join(',');
|
|
48
|
-
return `(
|
|
58
|
+
return `(${columnCondition} AND ${CV_VALUE_COLUMN} IN ( ${values} ))`;
|
|
49
59
|
}
|
|
50
60
|
return condition
|
|
51
61
|
.map((c) => {
|
|
52
62
|
const valRep = Object.keys(replacements).find((replacementKey) => replacements[replacementKey] === c.value);
|
|
53
|
-
return `(
|
|
63
|
+
return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(c.value)} ${c.operator} '"' || :${valRep} || '"' )`;
|
|
54
64
|
}).join(AND_DELIMETER);
|
|
55
65
|
}
|
|
56
66
|
if (typeof condition === 'string' || typeof condition === 'number') {
|
|
57
67
|
const conditionRep = Object.keys(replacements).find((replacementKey) => replacements[replacementKey] === condition);
|
|
58
|
-
return `(
|
|
68
|
+
return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition)} = '"' || :${conditionRep} || '"')`;
|
|
59
69
|
}
|
|
60
70
|
if (condition?.operator) {
|
|
61
71
|
const valueRep = Object.keys(replacements).find((replacementKey) => replacements[replacementKey] === condition.value);
|
|
62
|
-
return `(
|
|
72
|
+
return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition.value)} ${condition.operator} '"' || :${valueRep} || '"')`;
|
|
63
73
|
}
|
|
64
74
|
return false;
|
|
65
75
|
})
|
|
@@ -67,14 +77,17 @@ const customFieldsFilterScope = (name) => ({ replacementsMap: replacements, scop
|
|
|
67
77
|
if (conditionsStrings.length === 0) {
|
|
68
78
|
return {};
|
|
69
79
|
}
|
|
70
|
-
const customFieldConditions = conditionsStrings.join(
|
|
71
|
-
const subQuery =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
const customFieldConditions = conditionsStrings.join(OR_DELIMETER);
|
|
81
|
+
const subQuery = `
|
|
82
|
+
SELECT cv.model_id
|
|
83
|
+
FROM custom_field_values AS cv
|
|
84
|
+
INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
|
|
85
|
+
AND cd.model_type = '${name}'
|
|
86
|
+
WHERE ${customFieldConditions}
|
|
87
|
+
GROUP BY cv.model_id
|
|
88
|
+
HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
|
|
89
|
+
`.replace(/\n/g, '');
|
|
90
|
+
logger_1.default.info('custom fields filter scope', { subQuery, replacements });
|
|
78
91
|
return {
|
|
79
92
|
where: {
|
|
80
93
|
id: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sadot",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,6 +8,7 @@
|
|
|
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",
|
|
11
12
|
"coverage": "jest --coverage --forceExit --runInBand && rm -rf ./coverage",
|
|
12
13
|
"build-to-local-repo": "node --run build && cp -r dist/* ../$REPO/node_modules/$npm_package_name/dist",
|
|
13
14
|
"dev": "nodemon",
|
package/src/hooks/create.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { WhereOptions } from 'sequelize';
|
|
2
1
|
import logger from '../utils/logger';
|
|
3
2
|
import * as ValueRepo from '../repository/value';
|
|
4
3
|
import * as DefinitionRepo from '../repository/definition';
|
|
@@ -24,54 +23,37 @@ export const beforeCreate = (scopeAttributes: string[], modelOptions: ModelOptio
|
|
|
24
23
|
): Promise<void> => {
|
|
25
24
|
logger.debug('sadot - before create hook');
|
|
26
25
|
const { fields } = options;
|
|
27
|
-
const { include, useEntityIdFromInclude } = modelOptions;
|
|
28
26
|
const modelType = instance.constructor.name;
|
|
29
27
|
|
|
30
28
|
const identifiers = applyScopeToInstance(instance, scopeAttributes);
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
disabled: false,
|
|
35
|
-
...(!useEntityIdFromInclude && { entityId: identifiers }),
|
|
36
|
-
};
|
|
37
|
-
const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: false, transaction: options.transaction, include: include?.(identifiers) });
|
|
38
|
-
const requiredFieldsNames = Array.from(new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)));
|
|
30
|
+
// get all model's required definitions
|
|
31
|
+
const requiredFieldsNames = await DefinitionRepo.getRequiredFields(modelType, instance.id, identifiers, modelOptions);
|
|
39
32
|
|
|
40
33
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
41
|
-
|
|
42
|
-
if ((customFieldsIdx === -1 || !instance.customFields) && requiredFieldsNames?.length > 0) {
|
|
43
|
-
throw new MissingRequiredCustomFieldError(requiredFieldsNames);
|
|
44
|
-
}
|
|
45
|
-
const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
|
|
46
|
-
if (fieldsWithDefaultValue.length) {
|
|
47
|
-
// eslint-disable-next-line no-param-reassign
|
|
48
|
-
instance.customFields ||= {};
|
|
49
|
-
fieldsWithDefaultValue.filter((def) => !(def.name in instance.customFields)).forEach(({ name, defaultValue }) => {
|
|
50
|
-
// eslint-disable-next-line no-param-reassign
|
|
51
|
-
instance.customFields[name] = defaultValue;
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
34
|
const { customFields } = instance;
|
|
55
|
-
if (customFieldsIdx
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
throw new MissingRequiredCustomFieldError(missingFields);
|
|
62
|
-
}
|
|
35
|
+
if (customFieldsIdx > -1 && customFields) {
|
|
36
|
+
const fieldsNames = Object.keys(customFields);
|
|
37
|
+
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
38
|
+
if (missingFields?.length > 0) {
|
|
39
|
+
throw new MissingRequiredCustomFieldError(missingFields);
|
|
40
|
+
}
|
|
63
41
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
42
|
+
await ValueRepo.updateValues(
|
|
43
|
+
modelType,
|
|
44
|
+
instance.id,
|
|
45
|
+
identifiers,
|
|
46
|
+
customFields,
|
|
47
|
+
{
|
|
48
|
+
transaction: options.transaction,
|
|
49
|
+
modelOptions,
|
|
50
|
+
},
|
|
51
|
+
true,
|
|
52
|
+
);
|
|
74
53
|
|
|
75
|
-
|
|
76
|
-
|
|
54
|
+
// eslint-disable-next-line no-param-reassign
|
|
55
|
+
fields.splice(customFieldsIdx, 1);
|
|
56
|
+
} else if (requiredFieldsNames?.length > 0) {
|
|
57
|
+
throw new MissingRequiredCustomFieldError(requiredFieldsNames);
|
|
58
|
+
}
|
|
77
59
|
};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Op,
|
|
3
|
-
type Includeable, type Transaction, type FindOptions, type WhereOptions,
|
|
4
|
-
} from 'sequelize';
|
|
1
|
+
import { Op, type FindOptions, type WhereOptions } from 'sequelize';
|
|
5
2
|
import { CustomFieldDefinition } from '../models';
|
|
6
3
|
import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../types/definition';
|
|
7
4
|
import type { ModelOptions } from '../types';
|
|
@@ -9,15 +6,9 @@ import type { ModelOptions } from '../types';
|
|
|
9
6
|
export const create = (data: CreateCustomFieldDefinition): Promise<CustomFieldDefinition> =>
|
|
10
7
|
CustomFieldDefinition.create(data);
|
|
11
8
|
|
|
12
|
-
interface SadotFindOptions {
|
|
13
|
-
withDisabled?: boolean;
|
|
14
|
-
transaction?: Transaction;
|
|
15
|
-
include?: Includeable | Includeable[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
9
|
export const findAll = (
|
|
19
10
|
where: WhereOptions,
|
|
20
|
-
options:
|
|
11
|
+
options: any = { withDisabled: false },
|
|
21
12
|
): Promise<CustomFieldDefinition[]> => {
|
|
22
13
|
const queryModel = options.withDisabled
|
|
23
14
|
? CustomFieldDefinition.unscoped()
|
|
@@ -33,12 +24,12 @@ export const findAll = (
|
|
|
33
24
|
|
|
34
25
|
export const findByIds = (
|
|
35
26
|
ids: string[],
|
|
36
|
-
options:
|
|
27
|
+
options: any = { withDisabled: false },
|
|
37
28
|
): Promise<CustomFieldDefinition[]> => findAll({ id: { [Op.in]: ids } }, options);
|
|
38
29
|
|
|
39
30
|
export const findById = (
|
|
40
31
|
id: string,
|
|
41
|
-
options:
|
|
32
|
+
options: any = { withDisabled: false },
|
|
42
33
|
): Promise<CustomFieldDefinition | null> => {
|
|
43
34
|
const { withDisabled } = options;
|
|
44
35
|
if (withDisabled) {
|
|
@@ -96,13 +87,13 @@ export const update = async (
|
|
|
96
87
|
return updatedDefinition;
|
|
97
88
|
};
|
|
98
89
|
|
|
99
|
-
export const disable = (id: string): Promise<
|
|
90
|
+
export const disable = (id: string): Promise<any> =>
|
|
100
91
|
CustomFieldDefinition.update(
|
|
101
92
|
{ disabled: true },
|
|
102
93
|
{ where: { id } },
|
|
103
94
|
);
|
|
104
95
|
|
|
105
|
-
export const destroy = (id: string): Promise<
|
|
96
|
+
export const destroy = (id: string): Promise<any> =>
|
|
106
97
|
CustomFieldDefinition.destroy({ where: { id } });
|
|
107
98
|
|
|
108
99
|
/**
|
package/src/repository/value.ts
CHANGED
|
@@ -67,6 +67,7 @@ export const updateValues = async (
|
|
|
67
67
|
identifiers: string[],
|
|
68
68
|
valuesToUpdate: ValuesToUpdate,
|
|
69
69
|
options: FindOptions & { modelOptions?: ModelOptions } = {},
|
|
70
|
+
defineAllDefaults = false,
|
|
70
71
|
): Promise<CustomFieldValue[]> => {
|
|
71
72
|
const names = Object.keys(valuesToUpdate);
|
|
72
73
|
logger.debug(`custom-fields: updating values for ${modelType} ${modelId}`, {
|
|
@@ -80,10 +81,12 @@ export const updateValues = async (
|
|
|
80
81
|
const where: WhereOptions = {
|
|
81
82
|
modelType,
|
|
82
83
|
name: names,
|
|
83
|
-
...(!options.modelOptions?.useEntityIdFromInclude && { entityId: identifiers }),
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
if (!options.modelOptions?.useEntityIdFromInclude) {
|
|
87
|
+
where.entityId = identifiers;
|
|
88
|
+
}
|
|
89
|
+
const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) || [];
|
|
87
90
|
|
|
88
91
|
const disabledDefinitions = fieldDefinitions.filter((def) => def.disabled);
|
|
89
92
|
if (fieldDefinitions.length !== names.length) {
|
|
@@ -98,18 +101,31 @@ export const updateValues = async (
|
|
|
98
101
|
logger.warn(`custom-fields: trying to update disabled values: ${valuesWithDisabledDefinitions.join(', ')}`);
|
|
99
102
|
}
|
|
100
103
|
|
|
104
|
+
const visitedFields = new Set<CustomFieldDefinition>();
|
|
105
|
+
|
|
101
106
|
const values: CreateCustomFieldValue[] = names.map((name) => {
|
|
102
107
|
const fieldDefinition = fieldDefinitions.find((def) => def.name === name);
|
|
108
|
+
visitedFields.add(fieldDefinition);
|
|
103
109
|
const formatFunction = formatFunctions[fieldDefinition.fieldType];
|
|
104
|
-
const value = formatFunction ? formatFunction(valuesToUpdate[name]) : valuesToUpdate[name];
|
|
105
110
|
return {
|
|
106
111
|
modelId,
|
|
112
|
+
value: (formatFunction ? formatFunction(valuesToUpdate[name]) : valuesToUpdate[name]) ?? fieldDefinition.defaultValue,
|
|
107
113
|
updatedAt: new Date(),
|
|
108
114
|
customFieldDefinitionId: fieldDefinition.id,
|
|
109
|
-
value: value !== undefined ? value : fieldDefinition.defaultValue,
|
|
110
115
|
};
|
|
111
116
|
});
|
|
112
117
|
|
|
118
|
+
if (defineAllDefaults) {
|
|
119
|
+
fieldDefinitions.filter((def) => !visitedFields.has(def) && ![null, undefined].includes(def.defaultValue)).forEach(({ id, defaultValue }) => {
|
|
120
|
+
values.push({
|
|
121
|
+
modelId,
|
|
122
|
+
value: defaultValue,
|
|
123
|
+
updatedAt: new Date(),
|
|
124
|
+
customFieldDefinitionId: id,
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
113
129
|
return Promise.all(values.map(async (value) => {
|
|
114
130
|
const [cfv] = await CustomFieldValue.upsert(value, {
|
|
115
131
|
transaction: options.transaction,
|
package/src/scopes/filter.ts
CHANGED
|
@@ -3,6 +3,7 @@ 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';
|
|
6
7
|
|
|
7
8
|
const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
|
|
8
9
|
|
|
@@ -44,6 +45,12 @@ const castIfNeeded = (conditionValue: string): string => {
|
|
|
44
45
|
return '';
|
|
45
46
|
};
|
|
46
47
|
const AND_DELIMETER = ' AND ';
|
|
48
|
+
const OR_DELIMETER = ' OR ';
|
|
49
|
+
|
|
50
|
+
const CD_TABLE_ALIAS = 'cd';
|
|
51
|
+
const CD_NAME_COLUMN = `${CD_TABLE_ALIAS}.name`;
|
|
52
|
+
const CV_TABLE_ALIAS = 'cv';
|
|
53
|
+
const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value::varchar)`;
|
|
47
54
|
|
|
48
55
|
/**
|
|
49
56
|
* A Sequelize scope for filtering models by custom fields.
|
|
@@ -71,6 +78,7 @@ export const customFieldsFilterScope = (
|
|
|
71
78
|
(randomString) => replacements[randomString] === key,
|
|
72
79
|
);
|
|
73
80
|
if (!replacemetKey) return false;
|
|
81
|
+
const columnCondition = `(${CD_NAME_COLUMN} = :${replacemetKey})`;
|
|
74
82
|
|
|
75
83
|
if (Array.isArray(condition)) {
|
|
76
84
|
if (condition.length === 0) {
|
|
@@ -82,29 +90,29 @@ export const customFieldsFilterScope = (
|
|
|
82
90
|
const valRandom = Object.keys(replacements).find(
|
|
83
91
|
(randomString) => replacements[randomString] === v,
|
|
84
92
|
);
|
|
85
|
-
return ` :${valRandom} `;
|
|
93
|
+
return `'"' || :${valRandom} || '"'`;
|
|
86
94
|
}).join(',');
|
|
87
|
-
return `(
|
|
95
|
+
return `(${columnCondition} AND ${CV_VALUE_COLUMN} IN ( ${values} ))`;
|
|
88
96
|
}
|
|
89
97
|
return condition
|
|
90
98
|
.map((c) => {
|
|
91
99
|
const valRep = Object.keys(replacements).find(
|
|
92
100
|
(replacementKey) => replacements[replacementKey] === c.value,
|
|
93
101
|
);
|
|
94
|
-
return `(
|
|
102
|
+
return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(c.value)} ${c.operator} '"' || :${valRep} || '"' )`;
|
|
95
103
|
}).join(AND_DELIMETER);
|
|
96
104
|
}
|
|
97
105
|
if (typeof condition === 'string' || typeof condition === 'number') {
|
|
98
106
|
const conditionRep = Object.keys(replacements).find(
|
|
99
107
|
(replacementKey) => replacements[replacementKey] === condition,
|
|
100
108
|
);
|
|
101
|
-
return `(
|
|
109
|
+
return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition)} = '"' || :${conditionRep} || '"')`;
|
|
102
110
|
}
|
|
103
111
|
if (condition?.operator) {
|
|
104
112
|
const valueRep = Object.keys(replacements).find(
|
|
105
113
|
(replacementKey) => replacements[replacementKey] === condition.value,
|
|
106
114
|
);
|
|
107
|
-
return `(
|
|
115
|
+
return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition.value)} ${condition.operator} '"' || :${valueRep} || '"')`;
|
|
108
116
|
}
|
|
109
117
|
return false;
|
|
110
118
|
},
|
|
@@ -113,14 +121,17 @@ export const customFieldsFilterScope = (
|
|
|
113
121
|
if (conditionsStrings.length === 0) {
|
|
114
122
|
return {};
|
|
115
123
|
}
|
|
116
|
-
const customFieldConditions = conditionsStrings.join(
|
|
117
|
-
const subQuery =
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
const customFieldConditions = conditionsStrings.join(OR_DELIMETER);
|
|
125
|
+
const subQuery = `
|
|
126
|
+
SELECT cv.model_id
|
|
127
|
+
FROM custom_field_values AS cv
|
|
128
|
+
INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
|
|
129
|
+
AND cd.model_type = '${name}'
|
|
130
|
+
WHERE ${customFieldConditions}
|
|
131
|
+
GROUP BY cv.model_id
|
|
132
|
+
HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
|
|
133
|
+
`.replace(/\n/g, '');
|
|
134
|
+
logger.info('custom fields filter scope', { subQuery, replacements });
|
|
124
135
|
return {
|
|
125
136
|
where: {
|
|
126
137
|
id: {
|