@autofleet/sadot 0.7.4 → 0.7.6-beta-0ecad376.1
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 -1
- package/dist/hooks/create.js +27 -16
- package/dist/models/tests/contextAwareModels/ContextAwareTestModel.js +1 -3
- package/dist/repository/definition.d.ts +12 -6
- package/dist/repository/value.d.ts +1 -1
- package/dist/repository/value.js +3 -17
- package/dist/scopes/filter.js +2 -5
- package/dist/tests/mocks/definition.mock.js +9 -9
- package/dist/utils/logger/index.d.ts +1 -1
- package/dist/utils/logger/index.js +5 -3
- package/package.json +15 -21
- package/src/api/v1/definition/index.ts +1 -2
- package/src/hooks/create.ts +37 -25
- package/src/models/CustomFieldDefinition.ts +20 -20
- package/src/models/CustomFieldValue.ts +7 -7
- package/src/models/tests/AssociatedTestModel.ts +7 -7
- package/src/models/tests/TestModel.ts +7 -7
- package/src/models/tests/contextAwareModels/ContextAwareTestModel.ts +15 -17
- package/src/repository/definition.ts +15 -6
- package/src/repository/value.ts +2 -19
- package/src/scopes/filter.ts +3 -2
- package/src/tests/mocks/definition.mock.ts +1 -1
- package/src/utils/logger/index.ts +1 -2
package/.nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
22.9.0
|
package/dist/hooks/create.js
CHANGED
|
@@ -48,27 +48,38 @@ 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;
|
|
51
52
|
const modelType = instance.constructor.name;
|
|
52
53
|
const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
const where = {
|
|
55
|
+
modelType,
|
|
56
|
+
...(!useEntityIdFromInclude && { entityId: identifiers }),
|
|
57
|
+
};
|
|
58
|
+
const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: false, transaction: options.transaction, include: include?.(identifiers) });
|
|
59
|
+
const requiredFieldsNames = Array.from(new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)));
|
|
55
60
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
61
|
+
if ((customFieldsIdx === -1 || !instance.customFields) && requiredFieldsNames?.length > 0) {
|
|
62
|
+
throw new errors_1.MissingRequiredCustomFieldError(requiredFieldsNames);
|
|
63
|
+
}
|
|
64
|
+
// eslint-disable-next-line no-param-reassign
|
|
65
|
+
instance.customFields || (instance.customFields = {});
|
|
56
66
|
const { customFields } = instance;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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);
|
|
67
|
+
fieldDefinitions.filter((def) => !(def.name in customFields) && ![null, undefined].includes(def.defaultValue)).forEach(({ name, defaultValue }) => {
|
|
68
|
+
customFields[name] = defaultValue;
|
|
69
|
+
});
|
|
70
|
+
if (customFieldsIdx === -1) {
|
|
71
|
+
return;
|
|
69
72
|
}
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
const fieldsNames = Object.keys(customFields);
|
|
74
|
+
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
75
|
+
if (missingFields?.length > 0) {
|
|
76
|
+
throw new errors_1.MissingRequiredCustomFieldError(missingFields);
|
|
72
77
|
}
|
|
78
|
+
await ValueRepo.updateValues(modelType, instance.id, identifiers, customFields, {
|
|
79
|
+
transaction: options.transaction,
|
|
80
|
+
modelOptions,
|
|
81
|
+
});
|
|
82
|
+
// eslint-disable-next-line no-param-reassign
|
|
83
|
+
fields.splice(customFieldsIdx, 1);
|
|
73
84
|
};
|
|
74
85
|
exports.beforeCreate = beforeCreate;
|
|
@@ -28,9 +28,7 @@ __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)({
|
|
32
|
-
type: sequelize_typescript_1.DataType.UUID,
|
|
33
|
-
}),
|
|
31
|
+
(0, sequelize_typescript_1.Column)({ type: sequelize_typescript_1.DataType.UUID }),
|
|
34
32
|
__metadata("design:type", String)
|
|
35
33
|
], ContextAwareTestModel.prototype, "contextId", void 0);
|
|
36
34
|
__decorate([
|
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
import { type FindOptions, type WhereOptions } from 'sequelize';
|
|
1
|
+
import { type Includeable, type Transaction, 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
|
-
|
|
6
|
+
interface SadotFindOptions {
|
|
7
|
+
withDisabled?: boolean;
|
|
8
|
+
transaction?: Transaction;
|
|
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>;
|
|
9
14
|
export declare const findByEntityIds: (modelType: string, entityIds: string[], options?: FindOptions & {
|
|
10
15
|
modelOptions?: ModelOptions;
|
|
11
16
|
}) => Promise<CustomFieldDefinition[]>;
|
|
12
17
|
export declare const findByWhere: (where: any) => Promise<CustomFieldDefinition | null>;
|
|
13
18
|
export declare const findDefinitionsByModels: (modelTypes: string[], options?: any) => Promise<CustomFieldDefinition[]>;
|
|
14
19
|
export declare const update: (id: string, data: UpdateCustomFieldDefinition) => Promise<CustomFieldDefinition>;
|
|
15
|
-
export declare const disable: (id: string) => Promise<
|
|
16
|
-
export declare const destroy: (id: string) => Promise<
|
|
20
|
+
export declare const disable: (id: string) => Promise<[affectedCount: number]>;
|
|
21
|
+
export declare const destroy: (id: string) => Promise<number>;
|
|
17
22
|
/**
|
|
18
23
|
* Return the names of the required fields for a given model
|
|
19
24
|
*/
|
|
20
25
|
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
|
-
}
|
|
27
|
+
}) => 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 = {}) => {
|
|
88
88
|
const names = Object.keys(valuesToUpdate);
|
|
89
89
|
logger_1.default.debug(`custom-fields: updating values for ${modelType} ${modelId}`, {
|
|
90
90
|
names,
|
|
@@ -96,11 +96,9 @@ 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 }),
|
|
99
100
|
};
|
|
100
|
-
|
|
101
|
-
where.entityId = identifiers;
|
|
102
|
-
}
|
|
103
|
-
const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) || [];
|
|
101
|
+
const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) ?? [];
|
|
104
102
|
const disabledDefinitions = fieldDefinitions.filter((def) => def.disabled);
|
|
105
103
|
if (fieldDefinitions.length !== names.length) {
|
|
106
104
|
logger_1.default.warn(`custom-fields: missing definitions for ${modelType} ${modelId}`, { names, fieldDefinitions });
|
|
@@ -112,10 +110,8 @@ const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, opt
|
|
|
112
110
|
if (valuesWithDisabledDefinitions?.length > 0) {
|
|
113
111
|
logger_1.default.warn(`custom-fields: trying to update disabled values: ${valuesWithDisabledDefinitions.join(', ')}`);
|
|
114
112
|
}
|
|
115
|
-
const visitedFields = new Set();
|
|
116
113
|
const values = names.map((name) => {
|
|
117
114
|
const fieldDefinition = fieldDefinitions.find((def) => def.name === name);
|
|
118
|
-
visitedFields.add(fieldDefinition);
|
|
119
115
|
const formatFunction = formatFunctions[fieldDefinition.fieldType];
|
|
120
116
|
return {
|
|
121
117
|
modelId,
|
|
@@ -124,16 +120,6 @@ const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, opt
|
|
|
124
120
|
customFieldDefinitionId: fieldDefinition.id,
|
|
125
121
|
};
|
|
126
122
|
});
|
|
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
|
-
}
|
|
137
123
|
return Promise.all(values.map(async (value) => {
|
|
138
124
|
const [cfv] = await models_1.CustomFieldValue.upsert(value, {
|
|
139
125
|
transaction: options.transaction,
|
package/dist/scopes/filter.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
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
3
|
exports.customFieldsSortScope = exports.scopeName = exports.customFieldsFilterScope = void 0;
|
|
7
4
|
/* eslint-disable import/prefer-default-export */
|
|
8
5
|
const sequelize_1 = require("sequelize");
|
|
9
6
|
const sequelize_typescript_1 = require("sequelize-typescript");
|
|
10
7
|
const common_types_1 = require("@autofleet/common-types");
|
|
11
|
-
const moment_1 = __importDefault(require("moment"));
|
|
12
8
|
const helpers_1 = require("../utils/helpers");
|
|
13
9
|
const { CUSTOM_FIELDS_FILTER_SCOPE } = common_types_1.customFields;
|
|
10
|
+
const isDate = (input) => input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
|
|
14
11
|
const castIfNeeded = (conditionValue) => {
|
|
15
|
-
if (
|
|
12
|
+
if (isDate(conditionValue)) {
|
|
16
13
|
return '::timestamp';
|
|
17
14
|
}
|
|
18
15
|
if (!Number.isNaN(Number(conditionValue))) {
|
|
@@ -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 node_crypto_1 = require("node:crypto");
|
|
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, node_crypto_1.randomUUID)(),
|
|
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, node_crypto_1.randomUUID)(),
|
|
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, node_crypto_1.randomUUID)(),
|
|
40
40
|
entityType: 'fleetId',
|
|
41
41
|
});
|
|
42
42
|
exports.selectField = selectField;
|
|
@@ -45,25 +45,25 @@ const statusField = (modelType, options) => ({
|
|
|
45
45
|
modelType,
|
|
46
46
|
fieldType: 'status',
|
|
47
47
|
validation: options,
|
|
48
|
-
entityId: (0,
|
|
48
|
+
entityId: (0, node_crypto_1.randomUUID)(),
|
|
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, node_crypto_1.randomUUID)()}`,
|
|
55
55
|
modelType: defaults?.modelType || 'TestModel',
|
|
56
56
|
fieldType: defaults?.fieldType || 'boolean',
|
|
57
|
-
entityId: defaults?.entityId || (0,
|
|
57
|
+
entityId: defaults?.entityId || (0, node_crypto_1.randomUUID)(),
|
|
58
58
|
entityType: defaults?.entityType || 'fleetId',
|
|
59
59
|
...(defaults?.defaultValue && { defaultValue: defaults.defaultValue }),
|
|
60
60
|
});
|
|
61
61
|
exports.createDefinition = createDefinition;
|
|
62
62
|
const createDefinitions = (defaults, length = 1) => (Array(length).fill({}).map((_) => ({
|
|
63
|
-
name: defaults?.name || `def_${(0,
|
|
63
|
+
name: defaults?.name || `def_${(0, node_crypto_1.randomUUID)()}`,
|
|
64
64
|
modelType: defaults?.modelType || 'TestModel',
|
|
65
65
|
fieldType: defaults?.fieldType || 'boolean',
|
|
66
|
-
entityId: defaults?.entityId || (0,
|
|
66
|
+
entityId: defaults?.entityId || (0, node_crypto_1.randomUUID)(),
|
|
67
67
|
entityType: defaults?.entityType || 'fleetId',
|
|
68
68
|
})));
|
|
69
69
|
exports.createDefinitions = createDefinitions;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const logger:
|
|
1
|
+
declare const logger: import("@autofleet/logger").LoggerInstanceManager;
|
|
2
2
|
export default logger;
|
|
@@ -1,6 +1,8 @@
|
|
|
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
|
-
|
|
4
|
-
const
|
|
5
|
-
const logger = Logger();
|
|
6
|
+
const logger_1 = __importDefault(require("@autofleet/logger"));
|
|
7
|
+
const logger = (0, logger_1.default)();
|
|
6
8
|
exports.default = logger;
|
package/package.json
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sadot",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6-beta-0ecad376.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"start": "ts-node
|
|
7
|
+
"start": "ts-node src/index.ts",
|
|
8
8
|
"build": "rm -rf dist && tsc",
|
|
9
|
-
"linter": "
|
|
9
|
+
"linter": "eslint .",
|
|
10
10
|
"test": "jest --forceExit --runInBand",
|
|
11
11
|
"coverage": "jest --coverage --forceExit --runInBand && rm -rf ./coverage",
|
|
12
|
-
"build-to-local-repo": "
|
|
12
|
+
"build-to-local-repo": "node --run build && cp -r dist/* ../$REPO/node_modules/$npm_package_name/dist",
|
|
13
13
|
"dev": "nodemon",
|
|
14
14
|
"watch": "npm-watch build-to-local-repo",
|
|
15
|
-
"publish-dev": "
|
|
15
|
+
"publish-dev": "node --run build && npm publish --tag dev"
|
|
16
16
|
},
|
|
17
17
|
"watch": {
|
|
18
18
|
"build-to-local-repo": {
|
|
19
19
|
"extensions": [
|
|
20
20
|
"js",
|
|
21
|
-
"
|
|
22
|
-
"ts",
|
|
23
|
-
"tsx",
|
|
24
|
-
"css"
|
|
21
|
+
"ts"
|
|
25
22
|
],
|
|
26
23
|
"patterns": [
|
|
27
24
|
"src"
|
|
@@ -30,27 +27,24 @@
|
|
|
30
27
|
}
|
|
31
28
|
},
|
|
32
29
|
"dependencies": {
|
|
33
|
-
"@autofleet/common-types": "^1.7.
|
|
34
|
-
"@autofleet/errors": "^1.
|
|
30
|
+
"@autofleet/common-types": "^1.7.58",
|
|
31
|
+
"@autofleet/errors": "^1.2.3",
|
|
35
32
|
"@autofleet/events": "^2.0.0",
|
|
36
|
-
"@autofleet/logger": "^
|
|
33
|
+
"@autofleet/logger": "^4.1.0",
|
|
37
34
|
"express": "^4.18.2",
|
|
38
35
|
"joi": "^17.7.0",
|
|
39
|
-
"moment": "^2.30.1",
|
|
40
36
|
"pg": "^8.10.0",
|
|
41
37
|
"reflect-metadata": "^0.1.13",
|
|
42
38
|
"sequelize": "^6.31.1",
|
|
43
|
-
"sequelize-typescript": "^2.1.5"
|
|
44
|
-
"uuid": "^9.0.0"
|
|
39
|
+
"sequelize-typescript": "^2.1.5"
|
|
45
40
|
},
|
|
46
41
|
"devDependencies": {
|
|
47
42
|
"@types/express": "^4.17.17",
|
|
48
|
-
"@types/jest": "^
|
|
49
|
-
"@
|
|
50
|
-
"@typescript-eslint/
|
|
51
|
-
"
|
|
52
|
-
"eslint": "^
|
|
53
|
-
"eslint-config-airbnb-typescript": "^12.0.0",
|
|
43
|
+
"@types/jest": "^29.5.13",
|
|
44
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
45
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
46
|
+
"eslint": "^8.57.0",
|
|
47
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
|
54
48
|
"eslint-plugin-import": "^2.22.1",
|
|
55
49
|
"jest": "^29.7.0",
|
|
56
50
|
"npm-watch": "^0.11.0",
|
|
@@ -65,8 +65,7 @@ router.get('/', async (req, res) => {
|
|
|
65
65
|
if (entityIds?.length > 0) {
|
|
66
66
|
where.entityId = entityIds;
|
|
67
67
|
}
|
|
68
|
-
const customFieldDefinitions = await DefinitionRepo.findAll({ ...where },
|
|
69
|
-
{ withDisabled: true });
|
|
68
|
+
const customFieldDefinitions = await DefinitionRepo.findAll({ ...where }, { withDisabled: true });
|
|
70
69
|
return res.json(customFieldDefinitions);
|
|
71
70
|
} catch (err) {
|
|
72
71
|
logger.error('Failed to fetch custom field definitions', err);
|
package/src/hooks/create.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WhereOptions } from 'sequelize';
|
|
1
2
|
import logger from '../utils/logger';
|
|
2
3
|
import * as ValueRepo from '../repository/value';
|
|
3
4
|
import * as DefinitionRepo from '../repository/definition';
|
|
@@ -23,38 +24,49 @@ export const beforeCreate = (scopeAttributes: string[], modelOptions: ModelOptio
|
|
|
23
24
|
): Promise<void> => {
|
|
24
25
|
logger.debug('sadot - before create hook');
|
|
25
26
|
const { fields } = options;
|
|
27
|
+
const { include, useEntityIdFromInclude } = modelOptions;
|
|
26
28
|
const modelType = instance.constructor.name;
|
|
27
29
|
|
|
28
30
|
const identifiers = applyScopeToInstance(instance, scopeAttributes);
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
const where: WhereOptions = {
|
|
33
|
+
modelType,
|
|
34
|
+
...(!useEntityIdFromInclude && { entityId: identifiers }),
|
|
35
|
+
};
|
|
36
|
+
const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: false, transaction: options.transaction, include: include?.(identifiers) });
|
|
37
|
+
const requiredFieldsNames = Array.from(new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)));
|
|
33
38
|
|
|
34
39
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
35
|
-
const { customFields } = instance;
|
|
36
|
-
if (customFieldsIdx > -1 && customFields) {
|
|
37
|
-
const fieldsNames = Object.keys(customFields);
|
|
38
|
-
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
39
|
-
if (missingFields?.length > 0) {
|
|
40
|
-
throw new MissingRequiredCustomFieldError(missingFields);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
await ValueRepo.updateValues(
|
|
44
|
-
modelType,
|
|
45
|
-
instance.id,
|
|
46
|
-
identifiers,
|
|
47
|
-
customFields,
|
|
48
|
-
{
|
|
49
|
-
transaction: options.transaction,
|
|
50
|
-
modelOptions,
|
|
51
|
-
},
|
|
52
|
-
true,
|
|
53
|
-
);
|
|
54
40
|
|
|
55
|
-
|
|
56
|
-
fields.splice(customFieldsIdx, 1);
|
|
57
|
-
} else if (requiredFieldsNames?.length > 0) {
|
|
41
|
+
if ((customFieldsIdx === -1 || !instance.customFields) && requiredFieldsNames?.length > 0) {
|
|
58
42
|
throw new MissingRequiredCustomFieldError(requiredFieldsNames);
|
|
59
43
|
}
|
|
44
|
+
// eslint-disable-next-line no-param-reassign
|
|
45
|
+
instance.customFields ||= {};
|
|
46
|
+
const { customFields } = instance;
|
|
47
|
+
fieldDefinitions.filter((def) => !(def.name in customFields) && ![null, undefined].includes(def.defaultValue)).forEach(({ name, defaultValue }) => {
|
|
48
|
+
customFields[name] = defaultValue;
|
|
49
|
+
});
|
|
50
|
+
if (customFieldsIdx === -1) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const fieldsNames = Object.keys(customFields);
|
|
54
|
+
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
55
|
+
if (missingFields?.length > 0) {
|
|
56
|
+
throw new MissingRequiredCustomFieldError(missingFields);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await ValueRepo.updateValues(
|
|
60
|
+
modelType,
|
|
61
|
+
instance.id,
|
|
62
|
+
identifiers,
|
|
63
|
+
customFields,
|
|
64
|
+
{
|
|
65
|
+
transaction: options.transaction,
|
|
66
|
+
modelOptions,
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// eslint-disable-next-line no-param-reassign
|
|
71
|
+
fields.splice(customFieldsIdx, 1);
|
|
60
72
|
};
|
|
@@ -46,18 +46,18 @@ class CustomFieldDefinition extends Model {
|
|
|
46
46
|
defaultValue: DataType.UUIDV4,
|
|
47
47
|
allowNull: false,
|
|
48
48
|
})
|
|
49
|
-
|
|
49
|
+
id!: string;
|
|
50
50
|
|
|
51
51
|
@Column({
|
|
52
52
|
type: DataType.STRING,
|
|
53
53
|
allowNull: false,
|
|
54
54
|
})
|
|
55
|
-
|
|
55
|
+
name!: string;
|
|
56
56
|
|
|
57
57
|
@Column({
|
|
58
58
|
type: DataType.STRING,
|
|
59
59
|
})
|
|
60
|
-
|
|
60
|
+
displayName?: string; // Defaulted to name with beforeCreate hook
|
|
61
61
|
|
|
62
62
|
@Is('SupportedType', (value) => {
|
|
63
63
|
if (!Object.values(CustomFieldDefinitionType).includes(value as CustomFieldDefinitionType)) {
|
|
@@ -68,65 +68,65 @@ class CustomFieldDefinition extends Model {
|
|
|
68
68
|
type: DataType.STRING,
|
|
69
69
|
allowNull: false,
|
|
70
70
|
})
|
|
71
|
-
|
|
71
|
+
fieldType!: CustomFieldDefinitionType;
|
|
72
72
|
|
|
73
73
|
@Column({
|
|
74
74
|
type: DataType.JSONB,
|
|
75
75
|
})
|
|
76
|
-
|
|
76
|
+
validation?: any;
|
|
77
77
|
|
|
78
78
|
@Column({
|
|
79
79
|
type: DataType.UUID,
|
|
80
80
|
allowNull: false,
|
|
81
81
|
})
|
|
82
|
-
|
|
82
|
+
entityId!: string; /** Client association entity id */
|
|
83
83
|
|
|
84
84
|
@Column({
|
|
85
85
|
type: DataType.STRING,
|
|
86
86
|
allowNull: false,
|
|
87
87
|
})
|
|
88
|
-
|
|
88
|
+
entityType!: string; /** Client association entity type (demand source / fleet / etc.) */
|
|
89
89
|
|
|
90
90
|
@Column({
|
|
91
91
|
type: DataType.STRING,
|
|
92
92
|
allowNull: false,
|
|
93
93
|
})
|
|
94
|
-
|
|
94
|
+
modelType!: string; /** Model type. e.g. Vehicle / StopPoint / etc. */
|
|
95
95
|
|
|
96
96
|
@Column({
|
|
97
97
|
type: DataType.TEXT,
|
|
98
98
|
})
|
|
99
|
-
|
|
99
|
+
description?: string;
|
|
100
100
|
|
|
101
101
|
@Column({
|
|
102
102
|
type: DataType.BOOLEAN,
|
|
103
103
|
defaultValue: false,
|
|
104
104
|
})
|
|
105
|
-
|
|
105
|
+
required?: boolean;
|
|
106
106
|
|
|
107
107
|
@Column({
|
|
108
108
|
type: DataType.BOOLEAN,
|
|
109
109
|
defaultValue: false,
|
|
110
110
|
})
|
|
111
|
-
|
|
111
|
+
disabled?: boolean;
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
@Column({
|
|
114
|
+
type: DataType.JSONB,
|
|
115
|
+
allowNull: true,
|
|
116
|
+
})
|
|
117
|
+
defaultValue?: any;
|
|
118
118
|
|
|
119
119
|
@Column
|
|
120
|
-
|
|
120
|
+
createdAt?: Date;
|
|
121
121
|
|
|
122
122
|
@Column
|
|
123
|
-
|
|
123
|
+
updatedAt?: Date;
|
|
124
124
|
|
|
125
125
|
@Column
|
|
126
|
-
|
|
126
|
+
deletedAt?: Date;
|
|
127
127
|
|
|
128
128
|
@HasMany(() => CustomFieldValue)
|
|
129
|
-
|
|
129
|
+
values: CustomFieldValue[];
|
|
130
130
|
|
|
131
131
|
@BeforeCreate
|
|
132
132
|
static displayNameDefaultValue(instance: CustomFieldDefinition): void {
|
|
@@ -28,7 +28,7 @@ class CustomFieldValue extends Model {
|
|
|
28
28
|
type: DataType.UUID,
|
|
29
29
|
allowNull: false,
|
|
30
30
|
})
|
|
31
|
-
|
|
31
|
+
modelId!: string;
|
|
32
32
|
|
|
33
33
|
@PrimaryKey
|
|
34
34
|
@ForeignKey(() => CustomFieldDefinition)
|
|
@@ -36,25 +36,25 @@ class CustomFieldValue extends Model {
|
|
|
36
36
|
type: DataType.UUID,
|
|
37
37
|
allowNull: false,
|
|
38
38
|
})
|
|
39
|
-
|
|
39
|
+
customFieldDefinitionId!: string;
|
|
40
40
|
|
|
41
41
|
@Column({
|
|
42
42
|
type: DataType.JSONB,
|
|
43
43
|
allowNull: true,
|
|
44
44
|
})
|
|
45
|
-
|
|
45
|
+
value!: any;
|
|
46
46
|
|
|
47
47
|
@Column
|
|
48
|
-
|
|
48
|
+
createdAt?: Date;
|
|
49
49
|
|
|
50
50
|
@Column
|
|
51
|
-
|
|
51
|
+
updatedAt?: Date;
|
|
52
52
|
|
|
53
53
|
@Column
|
|
54
|
-
|
|
54
|
+
deletedAt?: Date;
|
|
55
55
|
|
|
56
56
|
@BelongsTo(() => CustomFieldDefinition, { scope: { disabled: false } })
|
|
57
|
-
|
|
57
|
+
customFieldDefinition: CustomFieldDefinition;
|
|
58
58
|
|
|
59
59
|
@BeforeBulkCreate
|
|
60
60
|
@BeforeBulkUpdate
|
|
@@ -18,40 +18,40 @@ class AssociatedTestModel extends Model {
|
|
|
18
18
|
defaultValue: DataType.UUIDV4,
|
|
19
19
|
allowNull: false,
|
|
20
20
|
})
|
|
21
|
-
|
|
21
|
+
id!: string;
|
|
22
22
|
|
|
23
23
|
@ForeignKey(() => TestModel)
|
|
24
24
|
@Column({
|
|
25
25
|
type: DataType.UUID,
|
|
26
26
|
allowNull: false,
|
|
27
27
|
})
|
|
28
|
-
|
|
28
|
+
testModelId!: string;
|
|
29
29
|
|
|
30
30
|
@Column({
|
|
31
31
|
type: DataType.UUID,
|
|
32
32
|
allowNull: false,
|
|
33
33
|
})
|
|
34
|
-
|
|
34
|
+
fleetId: string;
|
|
35
35
|
|
|
36
36
|
@Column({
|
|
37
37
|
type: DataType.UUID,
|
|
38
38
|
allowNull: true,
|
|
39
39
|
})
|
|
40
|
-
|
|
40
|
+
businessModelId: string;
|
|
41
41
|
|
|
42
42
|
@Column({
|
|
43
43
|
type: DataType.UUID,
|
|
44
44
|
allowNull: true,
|
|
45
45
|
})
|
|
46
|
-
|
|
46
|
+
demandSourceId: string;
|
|
47
47
|
|
|
48
48
|
@Column({
|
|
49
49
|
type: DataType.BOOLEAN,
|
|
50
50
|
})
|
|
51
|
-
|
|
51
|
+
anotherAttribute?: boolean;
|
|
52
52
|
|
|
53
53
|
@BelongsTo(() => TestModel)
|
|
54
|
-
|
|
54
|
+
testModel: TestModel;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export default AssociatedTestModel;
|
|
@@ -17,38 +17,38 @@ class TestModel extends Model {
|
|
|
17
17
|
defaultValue: DataType.UUIDV4,
|
|
18
18
|
allowNull: false,
|
|
19
19
|
})
|
|
20
|
-
|
|
20
|
+
id!: string;
|
|
21
21
|
|
|
22
22
|
@Column({
|
|
23
23
|
type: DataType.UUID,
|
|
24
24
|
allowNull: false,
|
|
25
25
|
})
|
|
26
|
-
|
|
26
|
+
fleetId: string;
|
|
27
27
|
|
|
28
28
|
@Column({
|
|
29
29
|
type: DataType.UUID,
|
|
30
30
|
allowNull: true,
|
|
31
31
|
})
|
|
32
|
-
|
|
32
|
+
businessModelId: string;
|
|
33
33
|
|
|
34
34
|
@Column({
|
|
35
35
|
type: DataType.UUID,
|
|
36
36
|
allowNull: true,
|
|
37
37
|
})
|
|
38
|
-
|
|
38
|
+
demandSourceId: string;
|
|
39
39
|
|
|
40
40
|
@Column({
|
|
41
41
|
type: DataType.BOOLEAN,
|
|
42
42
|
})
|
|
43
|
-
|
|
43
|
+
coolAttribute?: boolean;
|
|
44
44
|
|
|
45
45
|
@HasMany(() => AssociatedTestModel)
|
|
46
|
-
|
|
46
|
+
associatedModels: AssociatedTestModel[];
|
|
47
47
|
|
|
48
48
|
@Column({
|
|
49
49
|
type: DataType.VIRTUAL,
|
|
50
50
|
})
|
|
51
|
-
|
|
51
|
+
customFields?: any;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export default TestModel;
|
|
@@ -14,32 +14,30 @@ import ContextTestModel from './ContextTestModel';
|
|
|
14
14
|
|
|
15
15
|
@Table({ createdAt: false, updatedAt: false })
|
|
16
16
|
class ContextAwareTestModel extends Model {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
@PrimaryKey
|
|
18
|
+
@Column({
|
|
19
|
+
type: DataType.UUID,
|
|
20
|
+
defaultValue: DataType.UUIDV4,
|
|
21
|
+
allowNull: false,
|
|
22
|
+
})
|
|
23
23
|
id!: string;
|
|
24
24
|
|
|
25
25
|
@ForeignKey(() => ContextTestModel)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
})
|
|
29
|
-
contextId: string;
|
|
26
|
+
@Column({ type: DataType.UUID })
|
|
27
|
+
contextId: string;
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
@Column({
|
|
30
|
+
type: DataType.BOOLEAN,
|
|
31
|
+
})
|
|
34
32
|
coolAttribute?: boolean;
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
@Column({
|
|
35
|
+
type: DataType.VIRTUAL,
|
|
36
|
+
})
|
|
39
37
|
customFields?: any;
|
|
40
38
|
|
|
41
39
|
@BelongsTo(() => ContextTestModel)
|
|
42
|
-
|
|
40
|
+
context: ContextTestModel;
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
export default ContextAwareTestModel;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Op,
|
|
3
|
+
type Includeable, type Transaction, type FindOptions, type WhereOptions,
|
|
4
|
+
} from 'sequelize';
|
|
2
5
|
import { CustomFieldDefinition } from '../models';
|
|
3
6
|
import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../types/definition';
|
|
4
7
|
import type { ModelOptions } from '../types';
|
|
@@ -6,9 +9,15 @@ import type { ModelOptions } from '../types';
|
|
|
6
9
|
export const create = (data: CreateCustomFieldDefinition): Promise<CustomFieldDefinition> =>
|
|
7
10
|
CustomFieldDefinition.create(data);
|
|
8
11
|
|
|
12
|
+
interface SadotFindOptions {
|
|
13
|
+
withDisabled?: boolean;
|
|
14
|
+
transaction?: Transaction;
|
|
15
|
+
include?: Includeable | Includeable[];
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
export const findAll = (
|
|
10
19
|
where: WhereOptions,
|
|
11
|
-
options:
|
|
20
|
+
options: SadotFindOptions = { withDisabled: false },
|
|
12
21
|
): Promise<CustomFieldDefinition[]> => {
|
|
13
22
|
const queryModel = options.withDisabled
|
|
14
23
|
? CustomFieldDefinition.unscoped()
|
|
@@ -24,12 +33,12 @@ export const findAll = (
|
|
|
24
33
|
|
|
25
34
|
export const findByIds = (
|
|
26
35
|
ids: string[],
|
|
27
|
-
options:
|
|
36
|
+
options: SadotFindOptions = { withDisabled: false },
|
|
28
37
|
): Promise<CustomFieldDefinition[]> => findAll({ id: { [Op.in]: ids } }, options);
|
|
29
38
|
|
|
30
39
|
export const findById = (
|
|
31
40
|
id: string,
|
|
32
|
-
options:
|
|
41
|
+
options: Pick<SadotFindOptions, 'withDisabled'> = { withDisabled: false },
|
|
33
42
|
): Promise<CustomFieldDefinition | null> => {
|
|
34
43
|
const { withDisabled } = options;
|
|
35
44
|
if (withDisabled) {
|
|
@@ -87,13 +96,13 @@ export const update = async (
|
|
|
87
96
|
return updatedDefinition;
|
|
88
97
|
};
|
|
89
98
|
|
|
90
|
-
export const disable = (id: string): Promise<
|
|
99
|
+
export const disable = (id: string): Promise<[affectedCount: number]> =>
|
|
91
100
|
CustomFieldDefinition.update(
|
|
92
101
|
{ disabled: true },
|
|
93
102
|
{ where: { id } },
|
|
94
103
|
);
|
|
95
104
|
|
|
96
|
-
export const destroy = (id: string): Promise<
|
|
105
|
+
export const destroy = (id: string): Promise<number> =>
|
|
97
106
|
CustomFieldDefinition.destroy({ where: { id } });
|
|
98
107
|
|
|
99
108
|
/**
|
package/src/repository/value.ts
CHANGED
|
@@ -67,7 +67,6 @@ export const updateValues = async (
|
|
|
67
67
|
identifiers: string[],
|
|
68
68
|
valuesToUpdate: ValuesToUpdate,
|
|
69
69
|
options: FindOptions & { modelOptions?: ModelOptions } = {},
|
|
70
|
-
defineAllDefaults = false,
|
|
71
70
|
): Promise<CustomFieldValue[]> => {
|
|
72
71
|
const names = Object.keys(valuesToUpdate);
|
|
73
72
|
logger.debug(`custom-fields: updating values for ${modelType} ${modelId}`, {
|
|
@@ -81,12 +80,10 @@ export const updateValues = async (
|
|
|
81
80
|
const where: WhereOptions = {
|
|
82
81
|
modelType,
|
|
83
82
|
name: names,
|
|
83
|
+
...(!options.modelOptions?.useEntityIdFromInclude && { entityId: identifiers }),
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
where.entityId = identifiers;
|
|
88
|
-
}
|
|
89
|
-
const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) || [];
|
|
86
|
+
const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) ?? [];
|
|
90
87
|
|
|
91
88
|
const disabledDefinitions = fieldDefinitions.filter((def) => def.disabled);
|
|
92
89
|
if (fieldDefinitions.length !== names.length) {
|
|
@@ -101,11 +98,8 @@ export const updateValues = async (
|
|
|
101
98
|
logger.warn(`custom-fields: trying to update disabled values: ${valuesWithDisabledDefinitions.join(', ')}`);
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
const visitedFields = new Set<CustomFieldDefinition>();
|
|
105
|
-
|
|
106
101
|
const values: CreateCustomFieldValue[] = names.map((name) => {
|
|
107
102
|
const fieldDefinition = fieldDefinitions.find((def) => def.name === name);
|
|
108
|
-
visitedFields.add(fieldDefinition);
|
|
109
103
|
const formatFunction = formatFunctions[fieldDefinition.fieldType];
|
|
110
104
|
return {
|
|
111
105
|
modelId,
|
|
@@ -115,17 +109,6 @@ export const updateValues = async (
|
|
|
115
109
|
};
|
|
116
110
|
});
|
|
117
111
|
|
|
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
|
-
|
|
129
112
|
return Promise.all(values.map(async (value) => {
|
|
130
113
|
const [cfv] = await CustomFieldValue.upsert(value, {
|
|
131
114
|
transaction: options.transaction,
|
package/src/scopes/filter.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { Op, type WhereOptions } from 'sequelize';
|
|
3
3
|
import { Sequelize } from 'sequelize-typescript';
|
|
4
4
|
import { customFields } from '@autofleet/common-types';
|
|
5
|
-
import moment from 'moment';
|
|
6
5
|
import { generateRandomString } from '../utils/helpers';
|
|
7
6
|
|
|
8
7
|
const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
|
|
@@ -33,8 +32,10 @@ type customFieldsFilterScopeParams = {
|
|
|
33
32
|
scopeValue: Record<string, ConditionValue>;
|
|
34
33
|
}
|
|
35
34
|
|
|
35
|
+
const isDate = (input: any): input is Date => input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
|
|
36
|
+
|
|
36
37
|
const castIfNeeded = (conditionValue: string): string => {
|
|
37
|
-
if (
|
|
38
|
+
if (isDate(conditionValue)) {
|
|
38
39
|
return '::timestamp';
|
|
39
40
|
}
|
|
40
41
|
if (!Number.isNaN(Number(conditionValue))) {
|