@autofleet/sadot 0.5.5-beta.9 → 0.6.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/dist/hooks/create.d.ts +2 -1
- package/dist/hooks/create.js +8 -4
- package/dist/hooks/enrich.d.ts +2 -1
- package/dist/hooks/enrich.js +17 -4
- package/dist/hooks/update.d.ts +2 -1
- package/dist/hooks/update.js +4 -3
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/models/index.d.ts +3 -1
- package/dist/models/index.js +8 -2
- package/dist/models/tests/contextAwareModels/ContextAwareTestModel.d.ts +10 -0
- package/dist/models/tests/contextAwareModels/ContextAwareTestModel.js +55 -0
- package/dist/models/tests/contextAwareModels/ContextTestModel.d.ts +13 -0
- package/dist/models/tests/contextAwareModels/ContextTestModel.js +47 -0
- package/dist/repository/definition.d.ts +7 -4
- package/dist/repository/definition.js +27 -15
- package/dist/repository/value.d.ts +5 -1
- package/dist/repository/value.js +13 -3
- package/dist/tests/helpers/database-config.d.ts +1 -0
- package/dist/tests/helpers/database-config.js +1 -0
- package/dist/tests/helpers/index.js +2 -0
- package/dist/tests/mocks/definition.mock.d.ts +6 -0
- package/dist/tests/mocks/definition.mock.js +7 -1
- package/dist/types/index.d.ts +20 -2
- package/dist/utils/init.d.ts +5 -4
- package/dist/utils/init.js +13 -7
- package/dist/utils/scopeAttributes.d.ts +2 -0
- package/dist/utils/scopeAttributes.js +11 -0
- package/package.json +20 -2
- package/src/api/v1/definition/index.ts +2 -4
- package/src/hooks/create.ts +13 -5
- package/src/hooks/enrich.ts +20 -4
- package/src/hooks/update.ts +6 -3
- package/src/index.ts +4 -2
- package/src/models/index.ts +7 -1
- package/src/models/tests/contextAwareModels/ContextAwareTestModel.ts +45 -0
- package/src/models/tests/contextAwareModels/ContextTestModel.ts +38 -0
- package/src/repository/definition.ts +38 -19
- package/src/repository/value.ts +18 -7
- package/src/tests/helpers/database-config.ts +1 -0
- package/src/tests/helpers/index.ts +5 -1
- package/src/tests/mocks/definition.mock.ts +7 -0
- package/src/types/index.ts +23 -3
- package/src/utils/init.ts +19 -10
- package/src/utils/scopeAttributes.ts +12 -0
- package/dist/tests/functional/searching/index.d.ts +0 -8
- package/dist/tests/functional/searching/index.js +0 -46
- package/dist/utils/helpers/index.d.ts +0 -22
- package/dist/utils/helpers/index.js +0 -28
- package/src/tests/functional/searching/index.ts +0 -39
- package/src/utils/helpers/index.ts +0 -50
package/src/utils/init.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { DataTypes } from 'sequelize';
|
|
|
2
2
|
import { ModelCtor } from 'sequelize-typescript';
|
|
3
3
|
import { customFields } from '@autofleet/common-types';
|
|
4
4
|
import {
|
|
5
|
+
CustomFieldDefinition,
|
|
5
6
|
CustomFieldValue,
|
|
6
7
|
} from '../models';
|
|
7
8
|
import {
|
|
@@ -14,12 +15,14 @@ import {
|
|
|
14
15
|
} from '../hooks';
|
|
15
16
|
import { customFieldsFilterScope } from '../scopes';
|
|
16
17
|
import logger from './logger';
|
|
17
|
-
import type { ModelFetcher,
|
|
18
|
+
import type { ModelFetcher, Models } from '../types';
|
|
18
19
|
|
|
19
20
|
const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
|
|
20
21
|
|
|
21
|
-
export const addHooks = (models:
|
|
22
|
-
models.forEach(async ({
|
|
22
|
+
export const addHooks = (models: Models[], getModel: ModelFetcher): void => {
|
|
23
|
+
models.forEach(async ({
|
|
24
|
+
name, scopeAttributes, modelOptions,
|
|
25
|
+
}) => {
|
|
23
26
|
try {
|
|
24
27
|
const model = getModel(name);
|
|
25
28
|
if (!model) {
|
|
@@ -38,18 +41,18 @@ export const addHooks = (models: ModelOptions[], getModel: ModelFetcher): void =
|
|
|
38
41
|
model.addHook('beforeFind', 'sadot-beforeFind', beforeFind(scopeAttributes));
|
|
39
42
|
model.addHook('beforeBulkCreate', 'sadot-beforeBulkCreate', beforeBulkCreate);
|
|
40
43
|
model.addHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate', beforeBulkUpdate);
|
|
41
|
-
model.addHook('beforeCreate', 'sadot-beforeCreate', beforeCreate(scopeAttributes));
|
|
42
|
-
model.addHook('beforeUpdate', 'sadot-beforeUpdate', beforeUpdate(scopeAttributes));
|
|
43
|
-
model.addHook('afterFind', 'sadot-afterFind', enrichResults(name, scopeAttributes, 'afterFind'));
|
|
44
|
-
model.addHook('afterUpdate', 'sadot-afterUpdate', enrichResults(name, scopeAttributes));
|
|
45
|
-
model.addHook('afterCreate', 'sadot-afterCreate', enrichResults(name, scopeAttributes));
|
|
44
|
+
model.addHook('beforeCreate', 'sadot-beforeCreate', beforeCreate(scopeAttributes, modelOptions));
|
|
45
|
+
model.addHook('beforeUpdate', 'sadot-beforeUpdate', beforeUpdate(scopeAttributes, modelOptions));
|
|
46
|
+
model.addHook('afterFind', 'sadot-afterFind', enrichResults(name, scopeAttributes, 'afterFind', modelOptions));
|
|
47
|
+
model.addHook('afterUpdate', 'sadot-afterUpdate', enrichResults(name, scopeAttributes, null, modelOptions));
|
|
48
|
+
model.addHook('afterCreate', 'sadot-afterCreate', enrichResults(name, scopeAttributes, null, modelOptions));
|
|
46
49
|
} catch (e) {
|
|
47
50
|
logger.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
48
51
|
}
|
|
49
52
|
});
|
|
50
53
|
};
|
|
51
54
|
|
|
52
|
-
export const removeHooks = (models:
|
|
55
|
+
export const removeHooks = (models: Models[], getModel: ModelFetcher): void => {
|
|
53
56
|
models.forEach(async ({ name }) => {
|
|
54
57
|
try {
|
|
55
58
|
const model = getModel(name);
|
|
@@ -81,7 +84,7 @@ const addAssociations = (model: ModelCtor, modelName: string): void => {
|
|
|
81
84
|
CustomFieldValue.belongsTo(model, { foreignKey: 'modelId', as: modelName });
|
|
82
85
|
};
|
|
83
86
|
|
|
84
|
-
export const addScopes = (models:
|
|
87
|
+
export const addScopes = (models: Models[], getModel: ModelFetcher): void => {
|
|
85
88
|
models.forEach(async ({ name, scopeAttributes }) => {
|
|
86
89
|
try {
|
|
87
90
|
const model = getModel(name);
|
|
@@ -101,3 +104,9 @@ export const addScopes = (models: ModelOptions[], getModel: ModelFetcher): void
|
|
|
101
104
|
}
|
|
102
105
|
});
|
|
103
106
|
};
|
|
107
|
+
|
|
108
|
+
export const applyCustomAssociation = (models: Models[]): void => {
|
|
109
|
+
models.forEach(({ modelOptions }) => {
|
|
110
|
+
modelOptions?.customAssociation?.(CustomFieldDefinition);
|
|
111
|
+
});
|
|
112
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const mapAttributeToInstance = (scopeAttributes: string[], instance: any) =>
|
|
2
|
+
scopeAttributes.map((attr) => instance[attr]);
|
|
3
|
+
|
|
4
|
+
const applyScopeToInstance = (instance: any, scopeAttributes: string[]) => {
|
|
5
|
+
const uniqueAttributes = Array.from(new Set(scopeAttributes));
|
|
6
|
+
if (Array.isArray(instance)) {
|
|
7
|
+
return instance.flatMap((ins) => mapAttributeToInstance(uniqueAttributes, ins));
|
|
8
|
+
}
|
|
9
|
+
return mapAttributeToInstance(uniqueAttributes, instance);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default applyScopeToInstance;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
interface CustomFieldsSearchTestFlowInput {
|
|
2
|
-
fieldType: string;
|
|
3
|
-
fieldValue: string | number;
|
|
4
|
-
searchTerm: string;
|
|
5
|
-
expectedNumberOfQueryResults: number;
|
|
6
|
-
}
|
|
7
|
-
declare const customFieldsSearchTestFlow: ({ fieldType, fieldValue, searchTerm, expectedNumberOfQueryResults, }: CustomFieldsSearchTestFlowInput) => Promise<void>;
|
|
8
|
-
export default customFieldsSearchTestFlow;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
const sequelize_1 = require("sequelize");
|
|
27
|
-
const index_1 = require("../../../index");
|
|
28
|
-
const DefinitionRepo = __importStar(require("../../../repository/definition"));
|
|
29
|
-
const definition_mock_1 = require("../../mocks/definition.mock");
|
|
30
|
-
const testModel_1 = require("../../mocks/testModel");
|
|
31
|
-
const models_1 = require("../../../models");
|
|
32
|
-
const customFieldsSearchTestFlow = async ({ fieldType, fieldValue, searchTerm, expectedNumberOfQueryResults, }) => {
|
|
33
|
-
const definition = (0, definition_mock_1.createDefinition)({ fieldType, name: 'coolDefinition' });
|
|
34
|
-
await DefinitionRepo.create({ ...definition });
|
|
35
|
-
const [testModel1] = await (0, testModel_1.createTestModels)(definition.entityId, 2);
|
|
36
|
-
await testModel1.update({ customFields: { [definition.name]: fieldValue } });
|
|
37
|
-
const models = await models_1.TestModel.findAndCountAll({
|
|
38
|
-
where: { [sequelize_1.Op.or]: [(0, index_1.buildCustomFieldsSearchWhereClause)(models_1.TestModel, definition.entityId)] },
|
|
39
|
-
replacements: { searchTerm: `%${searchTerm}%` },
|
|
40
|
-
});
|
|
41
|
-
expect(models.count).toBe(expectedNumberOfQueryResults);
|
|
42
|
-
if (expectedNumberOfQueryResults > 0) {
|
|
43
|
-
expect(models.rows[0].dataValues.id).toBe(testModel1.id);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
exports.default = customFieldsSearchTestFlow;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { WhereOptions, BindOrReplacements } from 'sequelize';
|
|
2
|
-
import { ModelStatic } from 'sequelize-typescript';
|
|
3
|
-
/**
|
|
4
|
-
* Builds a WHERE clause and replacements for free-text search by custom fields.
|
|
5
|
-
*
|
|
6
|
-
* This function constructs a WHERE clause and replacement bindings that allow searching
|
|
7
|
-
* for a given term within custom fields associated with a specific model type and entity ID.
|
|
8
|
-
* The WHERE clause and replacements are designed to be added to the main query.
|
|
9
|
-
*
|
|
10
|
-
* @param {string} searchTerm - The term to search for within custom fields.
|
|
11
|
-
* @param {ModelStatic} model - The Sequelize model representing the entity type to search for.
|
|
12
|
-
* @param {string} entityId - The entity ID to filter the custom fields by.
|
|
13
|
-
*
|
|
14
|
-
* @returns {CustomFieldsSearchPayload} - An object containing the WHERE clause and replacements
|
|
15
|
-
* for Sequelize.
|
|
16
|
-
*/
|
|
17
|
-
interface CustomFieldsSearchPayload {
|
|
18
|
-
where: WhereOptions;
|
|
19
|
-
replacements: BindOrReplacements;
|
|
20
|
-
}
|
|
21
|
-
export declare const buildCustomFieldsSearchPayload: (searchTerm: string, model: ModelStatic, entityId: string) => CustomFieldsSearchPayload;
|
|
22
|
-
export {};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildCustomFieldsSearchPayload = void 0;
|
|
4
|
-
/* eslint-disable import/prefer-default-export */
|
|
5
|
-
const sequelize_1 = require("sequelize");
|
|
6
|
-
const sequelize_typescript_1 = require("sequelize-typescript");
|
|
7
|
-
const buildCustomFieldsSearchPayload = (searchTerm, model, entityId) => {
|
|
8
|
-
const subQuery = 'EXISTS ('
|
|
9
|
-
+ ' SELECT 1'
|
|
10
|
-
+ ' FROM "custom_field_values" AS "cv"'
|
|
11
|
-
+ ' INNER JOIN custom_field_definitions AS cd '
|
|
12
|
-
+ ` ON cd.entity_id = '${entityId}'`
|
|
13
|
-
+ ' AND cv.custom_field_definition_id = cd.id '
|
|
14
|
-
+ ` AND cd.model_type = '${model.name}' `
|
|
15
|
-
+ ' WHERE'
|
|
16
|
-
+ ' "cv"."deleted_at" IS NULL'
|
|
17
|
-
+ ` AND "cv"."model_id" = "${model.name}"."id"`
|
|
18
|
-
+ ' AND CAST("cv"."value" AS TEXT) ILIKE :searchTerm)';
|
|
19
|
-
return {
|
|
20
|
-
where: {
|
|
21
|
-
[sequelize_1.Op.or]: [
|
|
22
|
-
sequelize_typescript_1.Sequelize.where(sequelize_typescript_1.Sequelize.literal(subQuery), true),
|
|
23
|
-
],
|
|
24
|
-
},
|
|
25
|
-
replacements: { searchTerm: `%${searchTerm}%` },
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
exports.buildCustomFieldsSearchPayload = buildCustomFieldsSearchPayload;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { generateCustomFieldSearchQueryPayload } from '../../../index';
|
|
2
|
-
import * as DefinitionRepo from '../../../repository/definition';
|
|
3
|
-
import { createDefinition } from '../../mocks/definition.mock';
|
|
4
|
-
import { createTestModels } from '../../mocks/testModel';
|
|
5
|
-
import { TestModel } from '../../../models';
|
|
6
|
-
|
|
7
|
-
interface CustomFieldsSearchTestFlowInput {
|
|
8
|
-
fieldType: string;
|
|
9
|
-
fieldValue: string | number;
|
|
10
|
-
searchTerm: string;
|
|
11
|
-
expectedNumberOfQueryResults: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const customFieldsSearchTestFlow = async ({
|
|
15
|
-
fieldType,
|
|
16
|
-
fieldValue,
|
|
17
|
-
searchTerm,
|
|
18
|
-
expectedNumberOfQueryResults,
|
|
19
|
-
}: CustomFieldsSearchTestFlowInput) : Promise<void> => {
|
|
20
|
-
const definition = createDefinition({ fieldType, name: 'coolDefinition' });
|
|
21
|
-
await DefinitionRepo.create({ ...definition });
|
|
22
|
-
const [testModel1] = await createTestModels(definition.entityId, 2);
|
|
23
|
-
await testModel1.update({ customFields: { [definition.name]: fieldValue } });
|
|
24
|
-
const models = await TestModel.findAndCountAll(
|
|
25
|
-
{
|
|
26
|
-
...generateCustomFieldSearchQueryPayload(
|
|
27
|
-
searchTerm,
|
|
28
|
-
TestModel,
|
|
29
|
-
definition.entityId,
|
|
30
|
-
),
|
|
31
|
-
},
|
|
32
|
-
);
|
|
33
|
-
expect(models.count).toBe(expectedNumberOfQueryResults);
|
|
34
|
-
if (expectedNumberOfQueryResults > 0) {
|
|
35
|
-
expect(models.rows[0].dataValues.id).toBe(testModel1.id);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export default customFieldsSearchTestFlow;
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/* eslint-disable import/prefer-default-export */
|
|
2
|
-
import { WhereOptions, Op, BindOrReplacements } from 'sequelize';
|
|
3
|
-
import { ModelStatic, Sequelize } from 'sequelize-typescript';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Builds a WHERE clause and replacements for free-text search by custom fields.
|
|
7
|
-
*
|
|
8
|
-
* This function constructs a WHERE clause and replacement bindings that allow searching
|
|
9
|
-
* for a given term within custom fields associated with a specific model type and entity ID.
|
|
10
|
-
* The WHERE clause and replacements are designed to be added to the main query.
|
|
11
|
-
*
|
|
12
|
-
* @param {string} searchTerm - The term to search for within custom fields.
|
|
13
|
-
* @param {ModelStatic} model - The Sequelize model representing the entity type to search for.
|
|
14
|
-
* @param {string} entityId - The entity ID to filter the custom fields by.
|
|
15
|
-
*
|
|
16
|
-
* @returns {CustomFieldsSearchPayload} - An object containing the WHERE clause and replacements
|
|
17
|
-
* for Sequelize.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
interface CustomFieldsSearchPayload {
|
|
21
|
-
where: WhereOptions;
|
|
22
|
-
replacements: BindOrReplacements;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const generateCustomFieldSearchQueryPayload = (
|
|
26
|
-
searchTerm: string,
|
|
27
|
-
model: ModelStatic,
|
|
28
|
-
entityId : string,
|
|
29
|
-
): CustomFieldsSearchPayload => {
|
|
30
|
-
const subQuery = 'EXISTS ('
|
|
31
|
-
+ ' SELECT 1'
|
|
32
|
-
+ ' FROM "custom_field_values" AS "cv"'
|
|
33
|
-
+ ' INNER JOIN custom_field_definitions AS cd '
|
|
34
|
-
+ ` ON cd.entity_id = '${entityId}'`
|
|
35
|
-
+ ' AND cv.custom_field_definition_id = cd.id '
|
|
36
|
-
+ ` AND cd.model_type = '${model.name}' `
|
|
37
|
-
+ ' WHERE'
|
|
38
|
-
+ ' "cv"."deleted_at" IS NULL'
|
|
39
|
-
+ ` AND "cv"."model_id" = "${model.name}"."id"`
|
|
40
|
-
+ ' AND CAST("cv"."value" AS TEXT) ILIKE :searchTerm)';
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
where: {
|
|
44
|
-
[Op.or]: [
|
|
45
|
-
Sequelize.where(Sequelize.literal(subQuery), true),
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
replacements: { searchTerm: `%${searchTerm}%` },
|
|
49
|
-
};
|
|
50
|
-
};
|