@autofleet/sadot 0.3.3 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -0
- package/dist/api/v1/errors.d.ts +1 -1
- package/dist/api/v1/errors.js +1 -1
- package/dist/hooks/find.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -59
- package/dist/models/tests/TestModel.d.ts +1 -0
- package/dist/models/tests/TestModel.js +6 -0
- package/dist/scopes/filter.d.ts +23 -0
- package/dist/scopes/filter.js +43 -0
- package/dist/scopes/index.d.ts +2 -0
- package/dist/scopes/index.js +6 -0
- package/dist/tests/helpers/database-config.d.ts +1 -1
- package/dist/tests/mocks/definition.mock.d.ts +14 -14
- package/dist/tests/mocks/testModel.d.ts +1 -1
- package/dist/utils/constants/index.d.ts +1 -0
- package/dist/utils/constants/index.js +2 -1
- package/dist/utils/init.d.ts +4 -0
- package/dist/utils/init.js +99 -0
- package/package.json +2 -1
- package/src/api/v1/errors.ts +1 -1
- package/src/hooks/find.ts +1 -1
- package/src/index.ts +9 -65
- package/src/models/tests/TestModel.ts +5 -0
- package/src/scopes/filter.ts +63 -0
- package/src/scopes/index.ts +6 -0
- package/src/utils/constants/index.ts +2 -0
- package/src/utils/init.ts +101 -0
- package/tsconfig.json +5 -2
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
18.17.1
|
package/dist/api/v1/errors.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: (err: any, res: any, additionalData?:
|
|
1
|
+
declare const _default: (err: any, res: any, additionalData?: any) => Promise<any>;
|
|
2
2
|
export default _default;
|
package/dist/api/v1/errors.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const errors_1 = require("@autofleet/errors");
|
|
4
4
|
const joi_1 = require("@hapi/joi");
|
|
5
5
|
const sequelize_1 = require("sequelize");
|
|
6
|
-
exports.default = (err, res, additionalData =
|
|
6
|
+
exports.default = (err, res, additionalData = undefined) => {
|
|
7
7
|
let error = err;
|
|
8
8
|
if (err instanceof joi_1.ValidationError) {
|
|
9
9
|
error = new errors_1.BadRequest([err], null);
|
package/dist/hooks/find.js
CHANGED
|
@@ -17,7 +17,7 @@ const doScopeAttributesMissing = (scopeAttributes, queryAttributes) => {
|
|
|
17
17
|
// eslint-disable-next-line import/prefer-default-export
|
|
18
18
|
const beforeFind = (scopeAttributes) => (options) => {
|
|
19
19
|
const { attributes: queryAttributes } = options;
|
|
20
|
-
if (queryAttributes?.includes('customFields')) {
|
|
20
|
+
if (queryAttributes?.includes?.('customFields')) {
|
|
21
21
|
const missingScopeAttributes = doScopeAttributesMissing(scopeAttributes, queryAttributes);
|
|
22
22
|
logger_1.default.debug('sadot - before find hook');
|
|
23
23
|
if (missingScopeAttributes?.length > 0) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Application } from 'express';
|
|
2
2
|
import { Sequelize } from 'sequelize-typescript';
|
|
3
|
-
import { CustomFieldOptions, ModelFetcher } from './types';
|
|
3
|
+
import type { CustomFieldOptions, ModelFetcher } from './types';
|
|
4
4
|
export * from './utils/validations/custom-fields';
|
|
5
|
+
export * from './utils/constants';
|
|
5
6
|
/**
|
|
6
7
|
* Adding custom fields enrichment to the models inside the MODELS_FILE_NAME json file
|
|
7
8
|
* @see {@link 'custom-fields/config'} for configurations
|
package/dist/index.js
CHANGED
|
@@ -18,44 +18,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
20
|
exports.disableCustomFields = void 0;
|
|
21
|
-
const sequelize_1 = require("sequelize");
|
|
22
21
|
const models_1 = require("./models");
|
|
23
22
|
const api_1 = __importDefault(require("./api"));
|
|
24
|
-
const hooks_1 = require("./hooks");
|
|
25
23
|
const db_1 = __importDefault(require("./utils/db"));
|
|
26
24
|
const logger_1 = __importDefault(require("./utils/logger"));
|
|
25
|
+
const init_1 = require("./utils/init");
|
|
27
26
|
__exportStar(require("./utils/validations/custom-fields"), exports);
|
|
28
|
-
|
|
29
|
-
models.forEach(async ({ name, scopeAttributes }) => {
|
|
30
|
-
try {
|
|
31
|
-
const model = getModel(name);
|
|
32
|
-
if (!model) {
|
|
33
|
-
logger_1.default.warn('sadot - tried to addHooks to a model that does not exist yet', {
|
|
34
|
-
name,
|
|
35
|
-
scopeAttributes,
|
|
36
|
-
});
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
model.rawAttributes.customFields = {
|
|
40
|
-
type: sequelize_1.DataTypes.VIRTUAL,
|
|
41
|
-
};
|
|
42
|
-
model.refreshAttributes();
|
|
43
|
-
// TODO: Uncomment after tests are passed
|
|
44
|
-
// model.addHook('afterFind', workaround);
|
|
45
|
-
model.addHook('beforeFind', 'sadot-beforeFind', (0, hooks_1.beforeFind)(scopeAttributes));
|
|
46
|
-
model.addHook('beforeBulkCreate', 'sadot-beforeBulkCreate', hooks_1.beforeBulkCreate);
|
|
47
|
-
model.addHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate', hooks_1.beforeBulkUpdate);
|
|
48
|
-
model.addHook('beforeCreate', 'sadot-beforeCreate', (0, hooks_1.beforeCreate)(scopeAttributes));
|
|
49
|
-
model.addHook('beforeUpdate', 'sadot-beforeUpdate', (0, hooks_1.beforeUpdate)(scopeAttributes));
|
|
50
|
-
model.addHook('afterFind', 'sadot-afterFind', (0, hooks_1.enrichResults)(name, scopeAttributes));
|
|
51
|
-
model.addHook('afterUpdate', 'sadot-afterUpdate', (0, hooks_1.enrichResults)(name, scopeAttributes));
|
|
52
|
-
model.addHook('afterCreate', 'sadot-afterCreate', (0, hooks_1.enrichResults)(name, scopeAttributes));
|
|
53
|
-
}
|
|
54
|
-
catch (e) {
|
|
55
|
-
logger_1.default.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
};
|
|
27
|
+
__exportStar(require("./utils/constants"), exports);
|
|
59
28
|
/**
|
|
60
29
|
* Adding custom fields enrichment to the models inside the MODELS_FILE_NAME json file
|
|
61
30
|
* @see {@link 'custom-fields/config'} for configurations
|
|
@@ -69,37 +38,15 @@ const useCustomFields = async (app, getModel, options) => {
|
|
|
69
38
|
if (process.env.NODE_ENV === 'test') {
|
|
70
39
|
await (0, models_1.initTestModels)(sequelize);
|
|
71
40
|
}
|
|
72
|
-
|
|
41
|
+
// The order is important
|
|
42
|
+
(0, init_1.addHooks)(models, getModel);
|
|
73
43
|
await (0, models_1.initTables)(sequelize, options.getUser);
|
|
44
|
+
(0, init_1.addScopes)(models, getModel);
|
|
74
45
|
logger_1.default.debug('sadot - custom fields finished initializing with models', models);
|
|
75
46
|
return sequelize;
|
|
76
47
|
};
|
|
77
48
|
exports.default = useCustomFields;
|
|
78
|
-
const removeHooks = (models, getModel) => {
|
|
79
|
-
models.forEach(async ({ name }) => {
|
|
80
|
-
try {
|
|
81
|
-
const model = getModel(name);
|
|
82
|
-
if (!model)
|
|
83
|
-
return;
|
|
84
|
-
if (model.rawAttributes.customFields) {
|
|
85
|
-
delete model.rawAttributes.customFields;
|
|
86
|
-
model.refreshAttributes();
|
|
87
|
-
}
|
|
88
|
-
// model.removeHook('afterFind', 'sadot-workaround');
|
|
89
|
-
model.removeHook('beforeFind', 'sadot-beforeFind');
|
|
90
|
-
model.removeHook('beforeBulkCreate', 'sadot-beforeBulkCreate');
|
|
91
|
-
model.removeHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate');
|
|
92
|
-
model.removeHook('beforeCreate', 'sadot-beforeCreate');
|
|
93
|
-
model.removeHook('beforeUpdate', 'sadot-beforeUpdate');
|
|
94
|
-
model.removeHook('afterFind', 'sadot-afterFind');
|
|
95
|
-
model.removeHook('afterUpdate', 'sadot-afterUpdate');
|
|
96
|
-
}
|
|
97
|
-
catch (e) {
|
|
98
|
-
logger_1.default.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
};
|
|
102
49
|
const disableCustomFields = (models, getModel) => {
|
|
103
|
-
removeHooks(models, getModel);
|
|
50
|
+
(0, init_1.removeHooks)(models, getModel);
|
|
104
51
|
};
|
|
105
52
|
exports.disableCustomFields = disableCustomFields;
|
|
@@ -57,6 +57,12 @@ __decorate([
|
|
|
57
57
|
(0, sequelize_typescript_1.HasMany)(() => AssociatedTestModel_1.default),
|
|
58
58
|
__metadata("design:type", Array)
|
|
59
59
|
], TestModel.prototype, "associatedModels", void 0);
|
|
60
|
+
__decorate([
|
|
61
|
+
(0, sequelize_typescript_1.Column)({
|
|
62
|
+
type: sequelize_typescript_1.DataType.VIRTUAL,
|
|
63
|
+
}),
|
|
64
|
+
__metadata("design:type", Object)
|
|
65
|
+
], TestModel.prototype, "customFields", void 0);
|
|
60
66
|
TestModel = __decorate([
|
|
61
67
|
(0, sequelize_typescript_1.Table)({ schema: 'custom-fields', createdAt: false, updatedAt: false })
|
|
62
68
|
], TestModel);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { WhereOptions } from 'sequelize';
|
|
2
|
+
/**
|
|
3
|
+
* Type representing possible condition values.
|
|
4
|
+
* Currently supporting strings and arrays of strings.
|
|
5
|
+
* More types to be added (TBA).
|
|
6
|
+
*/
|
|
7
|
+
export type ConditionValue = string | string[];
|
|
8
|
+
export type CustomFieldSort = {
|
|
9
|
+
field: string;
|
|
10
|
+
direction: 'ASC' | 'DESC';
|
|
11
|
+
};
|
|
12
|
+
export type CustomFieldFilterOptions = {
|
|
13
|
+
where?: WhereOptions;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A Sequelize scope for filtering models by custom fields.
|
|
17
|
+
* This scope builds a WHERE clause to be applied on the main query.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} name - The model type name used to join custom_field_definitions.
|
|
20
|
+
* @returns {Function} - A function that takes conditions and returns the Sequelize options object.
|
|
21
|
+
*/
|
|
22
|
+
export declare const customFieldsFilterScope: (name: string) => (conditions: Record<string, ConditionValue>) => CustomFieldFilterOptions;
|
|
23
|
+
export declare const scopeName = "filterByCustomFields";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scopeName = exports.customFieldsFilterScope = 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 constants_1 = require("../utils/constants");
|
|
8
|
+
/**
|
|
9
|
+
* A Sequelize scope for filtering models by custom fields.
|
|
10
|
+
* This scope builds a WHERE clause to be applied on the main query.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} name - The model type name used to join custom_field_definitions.
|
|
13
|
+
* @returns {Function} - A function that takes conditions and returns the Sequelize options object.
|
|
14
|
+
*/
|
|
15
|
+
const customFieldsFilterScope = (name) => (conditions) => {
|
|
16
|
+
// Build the WHERE clause for custom field filtering
|
|
17
|
+
const customFieldConditions = Object.entries(conditions)
|
|
18
|
+
.map(([key, value]) => {
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
const values = value.map((v) => `'${v}'`).join(',');
|
|
21
|
+
return `custom_fields->>'${key}' IN (${values})`;
|
|
22
|
+
}
|
|
23
|
+
return `custom_fields->>'${key}' = '${value}'`;
|
|
24
|
+
})
|
|
25
|
+
.join(' AND ');
|
|
26
|
+
const subQuery = `${'SELECT model_id FROM ('
|
|
27
|
+
+ 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
|
|
28
|
+
+ 'FROM custom_field_values AS cv '
|
|
29
|
+
+ 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
|
|
30
|
+
+ `AND cd.model_type = '${name}' `
|
|
31
|
+
+ 'GROUP BY cv.model_id'
|
|
32
|
+
+ ') AS CustomFieldAggregation '
|
|
33
|
+
+ 'WHERE '}${customFieldConditions}`;
|
|
34
|
+
return {
|
|
35
|
+
where: {
|
|
36
|
+
id: {
|
|
37
|
+
[sequelize_1.Op.in]: sequelize_typescript_1.Sequelize.literal(`(${subQuery})`),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
exports.customFieldsFilterScope = customFieldsFilterScope;
|
|
43
|
+
exports.scopeName = constants_1.CUSTOM_FIELDS_FILTER_SCOPE;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.customFieldsFilterScope = void 0;
|
|
4
|
+
/* eslint-disable import/prefer-default-export */
|
|
5
|
+
const filter_1 = require("./filter");
|
|
6
|
+
Object.defineProperty(exports, "customFieldsFilterScope", { enumerable: true, get: function () { return filter_1.customFieldsFilterScope; } });
|
|
@@ -2,13 +2,13 @@ import { CreateCustomFieldDefinition, CustomFieldDefinitionDTO } from '../../typ
|
|
|
2
2
|
export declare const coolFieldDefinition: CreateCustomFieldDefinition;
|
|
3
3
|
export declare const coolFieldDefinition2: {
|
|
4
4
|
name: string;
|
|
5
|
-
required?: boolean
|
|
6
|
-
disabled?: boolean
|
|
7
|
-
description?: string
|
|
8
|
-
createdAt?: Date
|
|
9
|
-
updatedAt?: Date
|
|
10
|
-
deletedAt?: Date
|
|
11
|
-
displayName?: string
|
|
5
|
+
required?: boolean;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
description?: string;
|
|
8
|
+
createdAt?: Date;
|
|
9
|
+
updatedAt?: Date;
|
|
10
|
+
deletedAt?: Date;
|
|
11
|
+
displayName?: string;
|
|
12
12
|
validation?: any;
|
|
13
13
|
fieldType: string;
|
|
14
14
|
entityId: string;
|
|
@@ -17,13 +17,13 @@ export declare const coolFieldDefinition2: {
|
|
|
17
17
|
};
|
|
18
18
|
export declare const coolFieldDefinition3: {
|
|
19
19
|
name: string;
|
|
20
|
-
required?: boolean
|
|
21
|
-
disabled?: boolean
|
|
22
|
-
description?: string
|
|
23
|
-
createdAt?: Date
|
|
24
|
-
updatedAt?: Date
|
|
25
|
-
deletedAt?: Date
|
|
26
|
-
displayName?: string
|
|
20
|
+
required?: boolean;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
description?: string;
|
|
23
|
+
createdAt?: Date;
|
|
24
|
+
updatedAt?: Date;
|
|
25
|
+
deletedAt?: Date;
|
|
26
|
+
displayName?: string;
|
|
27
27
|
validation?: any;
|
|
28
28
|
fieldType: string;
|
|
29
29
|
entityId: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TestModel } from '../../models';
|
|
2
2
|
export declare const createTestModel: (payload?: {}) => Promise<TestModel>;
|
|
3
3
|
export declare const createTestModels: (fleetId: any, total: number) => Promise<TestModel[]>;
|
|
4
|
-
export declare const upsertTestModel: (payload: any) => Promise<[TestModel, boolean
|
|
4
|
+
export declare const upsertTestModel: (payload: any) => Promise<[TestModel, boolean]>;
|
|
5
5
|
export declare const destroyTestModels: () => Promise<number>;
|
|
6
6
|
export declare const getTestModel: (id: string, options?: {}) => Promise<TestModel>;
|
|
7
7
|
export declare const getSomeTestModels: (options?: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.supportedEntities = void 0;
|
|
3
|
+
exports.CUSTOM_FIELDS_FILTER_SCOPE = exports.supportedEntities = void 0;
|
|
4
4
|
// eslint-disable-next-line import/prefer-default-export
|
|
5
5
|
exports.supportedEntities = ['businessModelId', 'fleetId', 'demandSourceId'];
|
|
6
|
+
exports.CUSTOM_FIELDS_FILTER_SCOPE = 'filterByCustomFields';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ModelFetcher, ModelOptions } from '../types';
|
|
2
|
+
export declare const addHooks: (models: ModelOptions[], getModel: ModelFetcher) => void;
|
|
3
|
+
export declare const removeHooks: (models: ModelOptions[], getModel: ModelFetcher) => void;
|
|
4
|
+
export declare const addScopes: (models: ModelOptions[], getModel: ModelFetcher) => void;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.addScopes = exports.removeHooks = exports.addHooks = void 0;
|
|
7
|
+
const sequelize_1 = require("sequelize");
|
|
8
|
+
const models_1 = require("../models");
|
|
9
|
+
const hooks_1 = require("../hooks");
|
|
10
|
+
const scopes_1 = require("../scopes");
|
|
11
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
12
|
+
const constants_1 = require("./constants");
|
|
13
|
+
const addHooks = (models, getModel) => {
|
|
14
|
+
models.forEach(async ({ name, scopeAttributes }) => {
|
|
15
|
+
try {
|
|
16
|
+
const model = getModel(name);
|
|
17
|
+
if (!model) {
|
|
18
|
+
logger_1.default.warn('sadot - tried to addHooks to a model that does not exist yet', {
|
|
19
|
+
name,
|
|
20
|
+
scopeAttributes,
|
|
21
|
+
});
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
model.rawAttributes.customFields = {
|
|
25
|
+
type: sequelize_1.DataTypes.VIRTUAL,
|
|
26
|
+
};
|
|
27
|
+
model.refreshAttributes();
|
|
28
|
+
// TODO: Uncomment after tests are passed
|
|
29
|
+
// model.addHook('afterFind', workaround);
|
|
30
|
+
model.addHook('beforeFind', 'sadot-beforeFind', (0, hooks_1.beforeFind)(scopeAttributes));
|
|
31
|
+
model.addHook('beforeBulkCreate', 'sadot-beforeBulkCreate', hooks_1.beforeBulkCreate);
|
|
32
|
+
model.addHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate', hooks_1.beforeBulkUpdate);
|
|
33
|
+
model.addHook('beforeCreate', 'sadot-beforeCreate', (0, hooks_1.beforeCreate)(scopeAttributes));
|
|
34
|
+
model.addHook('beforeUpdate', 'sadot-beforeUpdate', (0, hooks_1.beforeUpdate)(scopeAttributes));
|
|
35
|
+
model.addHook('afterFind', 'sadot-afterFind', (0, hooks_1.enrichResults)(name, scopeAttributes));
|
|
36
|
+
model.addHook('afterUpdate', 'sadot-afterUpdate', (0, hooks_1.enrichResults)(name, scopeAttributes));
|
|
37
|
+
model.addHook('afterCreate', 'sadot-afterCreate', (0, hooks_1.enrichResults)(name, scopeAttributes));
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
logger_1.default.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
exports.addHooks = addHooks;
|
|
45
|
+
const removeHooks = (models, getModel) => {
|
|
46
|
+
models.forEach(async ({ name }) => {
|
|
47
|
+
try {
|
|
48
|
+
const model = getModel(name);
|
|
49
|
+
if (!model)
|
|
50
|
+
return;
|
|
51
|
+
if (model.rawAttributes.customFields) {
|
|
52
|
+
delete model.rawAttributes.customFields;
|
|
53
|
+
model.refreshAttributes();
|
|
54
|
+
}
|
|
55
|
+
// model.removeHook('afterFind', 'sadot-workaround');
|
|
56
|
+
model.removeHook('beforeFind', 'sadot-beforeFind');
|
|
57
|
+
model.removeHook('beforeBulkCreate', 'sadot-beforeBulkCreate');
|
|
58
|
+
model.removeHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate');
|
|
59
|
+
model.removeHook('beforeCreate', 'sadot-beforeCreate');
|
|
60
|
+
model.removeHook('beforeUpdate', 'sadot-beforeUpdate');
|
|
61
|
+
model.removeHook('afterFind', 'sadot-afterFind');
|
|
62
|
+
model.removeHook('afterUpdate', 'sadot-afterUpdate');
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
logger_1.default.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
exports.removeHooks = removeHooks;
|
|
70
|
+
/**
|
|
71
|
+
* Necessary associations for the {@link customFieldsFilterScope} scope
|
|
72
|
+
*/
|
|
73
|
+
const addAssociations = (model, modelName) => {
|
|
74
|
+
model.hasMany(models_1.CustomFieldValue, { foreignKey: 'modelId', as: 'customFieldValue' });
|
|
75
|
+
// TBC: maybe can be removed
|
|
76
|
+
models_1.CustomFieldValue.belongsTo(model, { foreignKey: 'modelId', as: modelName });
|
|
77
|
+
};
|
|
78
|
+
const addScopes = (models, getModel) => {
|
|
79
|
+
models.forEach(async ({ name, scopeAttributes }) => {
|
|
80
|
+
try {
|
|
81
|
+
const model = getModel(name);
|
|
82
|
+
if (!model) {
|
|
83
|
+
logger_1.default.warn('sadot - tried to addScopes to a model that does not exist yet', {
|
|
84
|
+
name,
|
|
85
|
+
scopeAttributes,
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Necessary associations for the filtering scope
|
|
90
|
+
addAssociations(model, name);
|
|
91
|
+
// Add filter scope
|
|
92
|
+
model.addScope(constants_1.CUSTOM_FIELDS_FILTER_SCOPE, (0, scopes_1.customFieldsFilterScope)(name));
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
logger_1.default.error(`Could not add custom fields scopes to model ${name}. `, e);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
exports.addScopes = addScopes;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sadot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/express": "^4.17.17",
|
|
28
28
|
"@types/jest": "^27.0.9",
|
|
29
|
+
"@types/uuid": "^9.0.2",
|
|
29
30
|
"@typescript-eslint/eslint-plugin": "^4.8.1",
|
|
30
31
|
"@typescript-eslint/parser": "^4.33.0",
|
|
31
32
|
"eslint": "^7.13.0",
|
package/src/api/v1/errors.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { handleError, BadRequest } from '@autofleet/errors';
|
|
|
2
2
|
import { ValidationError as InputValidationError } from '@hapi/joi';
|
|
3
3
|
import { ValidationError as DatabaseValidationError } from 'sequelize';
|
|
4
4
|
|
|
5
|
-
export default (err, res, additionalData =
|
|
5
|
+
export default (err, res, additionalData = undefined) => {
|
|
6
6
|
let error = err;
|
|
7
7
|
if (err instanceof InputValidationError) {
|
|
8
8
|
error = new BadRequest([err], null);
|
package/src/hooks/find.ts
CHANGED
|
@@ -16,7 +16,7 @@ const doScopeAttributesMissing = (
|
|
|
16
16
|
// eslint-disable-next-line import/prefer-default-export
|
|
17
17
|
export const beforeFind = (scopeAttributes: string[]) => (options): void => {
|
|
18
18
|
const { attributes: queryAttributes } = options;
|
|
19
|
-
if (queryAttributes?.includes('customFields')) {
|
|
19
|
+
if (queryAttributes?.includes?.('customFields')) {
|
|
20
20
|
const missingScopeAttributes = doScopeAttributesMissing(scopeAttributes, queryAttributes);
|
|
21
21
|
logger.debug('sadot - before find hook');
|
|
22
22
|
if (missingScopeAttributes?.length > 0) {
|
package/src/index.ts
CHANGED
|
@@ -1,52 +1,17 @@
|
|
|
1
1
|
import type { Application } from 'express';
|
|
2
|
-
import { DataTypes } from 'sequelize';
|
|
3
2
|
import { Sequelize } from 'sequelize-typescript';
|
|
4
|
-
import { initTables, initTestModels } from './models';
|
|
5
|
-
import api from './api';
|
|
6
3
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
beforeUpdate,
|
|
11
|
-
beforeCreate,
|
|
12
|
-
beforeBulkCreate,
|
|
13
|
-
} from './hooks';
|
|
4
|
+
initTables, initTestModels,
|
|
5
|
+
} from './models';
|
|
6
|
+
import api from './api';
|
|
14
7
|
import initDB from './utils/db';
|
|
15
8
|
import logger from './utils/logger';
|
|
16
|
-
import { CustomFieldOptions, ModelFetcher
|
|
9
|
+
import type { CustomFieldOptions, ModelFetcher } from './types';
|
|
10
|
+
import { addHooks, addScopes, removeHooks } from './utils/init';
|
|
17
11
|
|
|
18
12
|
export * from './utils/validations/custom-fields';
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
models.forEach(async ({ name, scopeAttributes }) => {
|
|
22
|
-
try {
|
|
23
|
-
const model = getModel(name);
|
|
24
|
-
if (!model) {
|
|
25
|
-
logger.warn('sadot - tried to addHooks to a model that does not exist yet', {
|
|
26
|
-
name,
|
|
27
|
-
scopeAttributes,
|
|
28
|
-
});
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
model.rawAttributes.customFields = {
|
|
32
|
-
type: DataTypes.VIRTUAL,
|
|
33
|
-
};
|
|
34
|
-
model.refreshAttributes();
|
|
35
|
-
// TODO: Uncomment after tests are passed
|
|
36
|
-
// model.addHook('afterFind', workaround);
|
|
37
|
-
model.addHook('beforeFind', 'sadot-beforeFind', beforeFind(scopeAttributes));
|
|
38
|
-
model.addHook('beforeBulkCreate', 'sadot-beforeBulkCreate', beforeBulkCreate);
|
|
39
|
-
model.addHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate', beforeBulkUpdate);
|
|
40
|
-
model.addHook('beforeCreate', 'sadot-beforeCreate', beforeCreate(scopeAttributes));
|
|
41
|
-
model.addHook('beforeUpdate', 'sadot-beforeUpdate', beforeUpdate(scopeAttributes));
|
|
42
|
-
model.addHook('afterFind', 'sadot-afterFind', enrichResults(name, scopeAttributes));
|
|
43
|
-
model.addHook('afterUpdate', 'sadot-afterUpdate', enrichResults(name, scopeAttributes));
|
|
44
|
-
model.addHook('afterCreate', 'sadot-afterCreate', enrichResults(name, scopeAttributes));
|
|
45
|
-
} catch (e) {
|
|
46
|
-
logger.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
};
|
|
14
|
+
export * from './utils/constants';
|
|
50
15
|
|
|
51
16
|
/**
|
|
52
17
|
* Adding custom fields enrichment to the models inside the MODELS_FILE_NAME json file
|
|
@@ -65,37 +30,16 @@ const useCustomFields = async (
|
|
|
65
30
|
if (process.env.NODE_ENV === 'test') {
|
|
66
31
|
await initTestModels(sequelize);
|
|
67
32
|
}
|
|
33
|
+
// The order is important
|
|
68
34
|
addHooks(models, getModel);
|
|
69
35
|
await initTables(sequelize, options.getUser);
|
|
36
|
+
addScopes(models, getModel);
|
|
70
37
|
logger.debug('sadot - custom fields finished initializing with models', models);
|
|
71
38
|
return sequelize;
|
|
72
39
|
};
|
|
73
40
|
|
|
74
41
|
export default useCustomFields;
|
|
75
42
|
|
|
76
|
-
const
|
|
77
|
-
models.forEach(async ({ name }) => {
|
|
78
|
-
try {
|
|
79
|
-
const model = getModel(name);
|
|
80
|
-
if (!model) return;
|
|
81
|
-
if (model.rawAttributes.customFields) {
|
|
82
|
-
delete model.rawAttributes.customFields;
|
|
83
|
-
model.refreshAttributes();
|
|
84
|
-
}
|
|
85
|
-
// model.removeHook('afterFind', 'sadot-workaround');
|
|
86
|
-
model.removeHook('beforeFind', 'sadot-beforeFind');
|
|
87
|
-
model.removeHook('beforeBulkCreate', 'sadot-beforeBulkCreate');
|
|
88
|
-
model.removeHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate');
|
|
89
|
-
model.removeHook('beforeCreate', 'sadot-beforeCreate');
|
|
90
|
-
model.removeHook('beforeUpdate', 'sadot-beforeUpdate');
|
|
91
|
-
model.removeHook('afterFind', 'sadot-afterFind');
|
|
92
|
-
model.removeHook('afterUpdate', 'sadot-afterUpdate');
|
|
93
|
-
} catch (e) {
|
|
94
|
-
logger.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
export const disableCustomFields = (models, getModel) => {
|
|
43
|
+
export const disableCustomFields = (models, getModel): void => {
|
|
100
44
|
removeHooks(models, getModel);
|
|
101
45
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* eslint-disable import/prefer-default-export */
|
|
2
|
+
import { Op, WhereOptions } from 'sequelize';
|
|
3
|
+
import { Sequelize } from 'sequelize-typescript';
|
|
4
|
+
import { CUSTOM_FIELDS_FILTER_SCOPE } from '../utils/constants';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Type representing possible condition values.
|
|
8
|
+
* Currently supporting strings and arrays of strings.
|
|
9
|
+
* More types to be added (TBA).
|
|
10
|
+
*/
|
|
11
|
+
export type ConditionValue = string | string[];
|
|
12
|
+
|
|
13
|
+
export type CustomFieldSort = {
|
|
14
|
+
field: string;
|
|
15
|
+
direction: 'ASC' | 'DESC';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type CustomFieldFilterOptions = {
|
|
19
|
+
where?: WhereOptions;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A Sequelize scope for filtering models by custom fields.
|
|
24
|
+
* This scope builds a WHERE clause to be applied on the main query.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} name - The model type name used to join custom_field_definitions.
|
|
27
|
+
* @returns {Function} - A function that takes conditions and returns the Sequelize options object.
|
|
28
|
+
*/
|
|
29
|
+
export const customFieldsFilterScope = (
|
|
30
|
+
name: string,
|
|
31
|
+
) => (conditions: Record<string, ConditionValue>): CustomFieldFilterOptions => {
|
|
32
|
+
// Build the WHERE clause for custom field filtering
|
|
33
|
+
const customFieldConditions = Object.entries(conditions)
|
|
34
|
+
.map(
|
|
35
|
+
([key, value]) => {
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
const values = value.map((v) => `'${v}'`).join(',');
|
|
38
|
+
return `custom_fields->>'${key}' IN (${values})`;
|
|
39
|
+
}
|
|
40
|
+
return `custom_fields->>'${key}' = '${value}'`;
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
.join(' AND ');
|
|
44
|
+
|
|
45
|
+
const subQuery = `${'SELECT model_id FROM ('
|
|
46
|
+
+ 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
|
|
47
|
+
+ 'FROM custom_field_values AS cv '
|
|
48
|
+
+ 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
|
|
49
|
+
+ `AND cd.model_type = '${name}' `
|
|
50
|
+
+ 'GROUP BY cv.model_id'
|
|
51
|
+
+ ') AS CustomFieldAggregation '
|
|
52
|
+
+ 'WHERE '}${customFieldConditions}`;
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
where: {
|
|
56
|
+
id: {
|
|
57
|
+
[Op.in]: Sequelize.literal(`(${subQuery})`),
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const scopeName = CUSTOM_FIELDS_FILTER_SCOPE;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { DataTypes } from 'sequelize';
|
|
2
|
+
import { ModelCtor } from 'sequelize-typescript';
|
|
3
|
+
import {
|
|
4
|
+
CustomFieldValue,
|
|
5
|
+
} from '../models';
|
|
6
|
+
import {
|
|
7
|
+
beforeFind,
|
|
8
|
+
enrichResults,
|
|
9
|
+
beforeBulkUpdate,
|
|
10
|
+
beforeUpdate,
|
|
11
|
+
beforeCreate,
|
|
12
|
+
beforeBulkCreate,
|
|
13
|
+
} from '../hooks';
|
|
14
|
+
import { customFieldsFilterScope } from '../scopes';
|
|
15
|
+
import logger from './logger';
|
|
16
|
+
import type { ModelFetcher, ModelOptions } from '../types';
|
|
17
|
+
import { CUSTOM_FIELDS_FILTER_SCOPE } from './constants';
|
|
18
|
+
|
|
19
|
+
export const addHooks = (models: ModelOptions[], getModel: ModelFetcher): void => {
|
|
20
|
+
models.forEach(async ({ name, scopeAttributes }) => {
|
|
21
|
+
try {
|
|
22
|
+
const model = getModel(name);
|
|
23
|
+
if (!model) {
|
|
24
|
+
logger.warn('sadot - tried to addHooks to a model that does not exist yet', {
|
|
25
|
+
name,
|
|
26
|
+
scopeAttributes,
|
|
27
|
+
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
model.rawAttributes.customFields = {
|
|
31
|
+
type: DataTypes.VIRTUAL,
|
|
32
|
+
};
|
|
33
|
+
model.refreshAttributes();
|
|
34
|
+
// TODO: Uncomment after tests are passed
|
|
35
|
+
// model.addHook('afterFind', workaround);
|
|
36
|
+
model.addHook('beforeFind', 'sadot-beforeFind', beforeFind(scopeAttributes));
|
|
37
|
+
model.addHook('beforeBulkCreate', 'sadot-beforeBulkCreate', beforeBulkCreate);
|
|
38
|
+
model.addHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate', beforeBulkUpdate);
|
|
39
|
+
model.addHook('beforeCreate', 'sadot-beforeCreate', beforeCreate(scopeAttributes));
|
|
40
|
+
model.addHook('beforeUpdate', 'sadot-beforeUpdate', beforeUpdate(scopeAttributes));
|
|
41
|
+
model.addHook('afterFind', 'sadot-afterFind', enrichResults(name, scopeAttributes));
|
|
42
|
+
model.addHook('afterUpdate', 'sadot-afterUpdate', enrichResults(name, scopeAttributes));
|
|
43
|
+
model.addHook('afterCreate', 'sadot-afterCreate', enrichResults(name, scopeAttributes));
|
|
44
|
+
} catch (e) {
|
|
45
|
+
logger.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const removeHooks = (models: ModelOptions[], getModel: ModelFetcher): void => {
|
|
51
|
+
models.forEach(async ({ name }) => {
|
|
52
|
+
try {
|
|
53
|
+
const model = getModel(name);
|
|
54
|
+
if (!model) return;
|
|
55
|
+
if (model.rawAttributes.customFields) {
|
|
56
|
+
delete model.rawAttributes.customFields;
|
|
57
|
+
model.refreshAttributes();
|
|
58
|
+
}
|
|
59
|
+
// model.removeHook('afterFind', 'sadot-workaround');
|
|
60
|
+
model.removeHook('beforeFind', 'sadot-beforeFind');
|
|
61
|
+
model.removeHook('beforeBulkCreate', 'sadot-beforeBulkCreate');
|
|
62
|
+
model.removeHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate');
|
|
63
|
+
model.removeHook('beforeCreate', 'sadot-beforeCreate');
|
|
64
|
+
model.removeHook('beforeUpdate', 'sadot-beforeUpdate');
|
|
65
|
+
model.removeHook('afterFind', 'sadot-afterFind');
|
|
66
|
+
model.removeHook('afterUpdate', 'sadot-afterUpdate');
|
|
67
|
+
} catch (e) {
|
|
68
|
+
logger.error(`Could not add custom fields hook to model ${name}. `, e);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Necessary associations for the {@link customFieldsFilterScope} scope
|
|
75
|
+
*/
|
|
76
|
+
const addAssociations = (model: ModelCtor, modelName: string): void => {
|
|
77
|
+
model.hasMany(CustomFieldValue, { foreignKey: 'modelId', as: 'customFieldValue' });
|
|
78
|
+
// TBC: maybe can be removed
|
|
79
|
+
CustomFieldValue.belongsTo(model, { foreignKey: 'modelId', as: modelName });
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const addScopes = (models: ModelOptions[], getModel: ModelFetcher): void => {
|
|
83
|
+
models.forEach(async ({ name, scopeAttributes }) => {
|
|
84
|
+
try {
|
|
85
|
+
const model = getModel(name);
|
|
86
|
+
if (!model) {
|
|
87
|
+
logger.warn('sadot - tried to addScopes to a model that does not exist yet', {
|
|
88
|
+
name,
|
|
89
|
+
scopeAttributes,
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Necessary associations for the filtering scope
|
|
94
|
+
addAssociations(model, name);
|
|
95
|
+
// Add filter scope
|
|
96
|
+
model.addScope(CUSTOM_FIELDS_FILTER_SCOPE, customFieldsFilterScope(name));
|
|
97
|
+
} catch (e) {
|
|
98
|
+
logger.error(`Could not add custom fields scopes to model ${name}. `, e);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
};
|
package/tsconfig.json
CHANGED
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
"module": "commonjs",
|
|
5
5
|
"declaration": true,
|
|
6
6
|
"outDir": "./dist",
|
|
7
|
-
"strict": true,
|
|
8
7
|
"esModuleInterop": true,
|
|
9
8
|
"experimentalDecorators": true,
|
|
10
|
-
"emitDecoratorMetadata": true
|
|
9
|
+
"emitDecoratorMetadata": true,
|
|
10
|
+
"allowJs": true
|
|
11
11
|
},
|
|
12
|
+
"include": [
|
|
13
|
+
"src/**/*",
|
|
14
|
+
],
|
|
12
15
|
"exclude": ["node_modules", "**/*.test.ts"]
|
|
13
16
|
}
|