@autofleet/sadot 0.7.10-beta.9 → 0.8.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/api/v1/definition/index.js +6 -8
- package/dist/api/v1/definition/validations.js +5 -3
- package/dist/errors/index.d.ts +1 -5
- package/dist/errors/index.js +4 -17
- package/dist/models/CustomFieldDefinition.d.ts +1 -0
- package/dist/models/CustomFieldDefinition.js +10 -2
- package/dist/models/CustomFieldValue.d.ts +2 -3
- package/dist/models/CustomFieldValue.js +14 -22
- package/dist/models/index.d.ts +2 -1
- package/dist/models/index.js +13 -14
- package/dist/repository/definition.js +2 -6
- package/dist/tests/mocks/definition.mock.d.ts +2 -1
- package/dist/tests/mocks/definition.mock.js +1 -9
- package/dist/types/definition/index.d.ts +1 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/utils/validations/index.d.ts +1 -2
- package/dist/utils/validations/index.js +7 -4
- package/dist/utils/validations/type.d.ts +1 -2
- package/dist/utils/validations/validators/index.js +9 -9
- package/dist/utils/validations/validators/select.validator.js +2 -5
- package/dist/utils/validations/validators/status.validator.js +2 -8
- package/package.json +1 -1
- package/src/api/v1/definition/index.ts +6 -8
- package/src/api/v1/definition/validations.ts +5 -3
- package/src/errors/index.ts +3 -20
- package/src/models/CustomFieldDefinition.ts +9 -2
- package/src/models/CustomFieldValue.ts +17 -25
- package/src/models/index.ts +15 -15
- package/src/repository/definition.ts +2 -7
- package/src/repository/value.ts +0 -1
- package/src/tests/mocks/definition.mock.ts +0 -8
- package/src/types/definition/index.ts +1 -0
- package/src/types/index.ts +2 -1
- package/src/utils/validations/index.ts +6 -3
- package/src/utils/validations/type.ts +1 -2
- package/src/utils/validations/validators/index.ts +9 -9
- package/src/utils/validations/validators/select.validator.ts +2 -3
- package/src/utils/validations/validators/status.validator.ts +2 -6
- package/.env +0 -3
|
@@ -75,15 +75,14 @@ router.get('/:customFieldDefinitionId', async (req, res) => {
|
|
|
75
75
|
* Get all
|
|
76
76
|
*/
|
|
77
77
|
router.get('/', async (req, res) => {
|
|
78
|
-
const { modelName } = req
|
|
79
|
-
const { entityIds } = req.query;
|
|
78
|
+
const { params: { modelName }, query: { entityIds } } = req;
|
|
80
79
|
const modelType = toPascalCase(modelName);
|
|
81
80
|
try {
|
|
82
|
-
const where = {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
const customFieldDefinitions = await DefinitionRepo.findAll(
|
|
81
|
+
const where = {
|
|
82
|
+
modelType,
|
|
83
|
+
...(entityIds?.length > 0 && { entityId: entityIds }),
|
|
84
|
+
};
|
|
85
|
+
const customFieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true });
|
|
87
86
|
return res.json(customFieldDefinitions);
|
|
88
87
|
}
|
|
89
88
|
catch (err) {
|
|
@@ -98,7 +97,6 @@ router.patch('/:customFieldDefinitionId', async (req, res) => {
|
|
|
98
97
|
const { customFieldDefinitionId, modelName } = req.params;
|
|
99
98
|
const modelType = toPascalCase(modelName);
|
|
100
99
|
try {
|
|
101
|
-
// eslint-disable-next-line max-len
|
|
102
100
|
const validatedPayload = await (0, validations_1.validateCustomFieldDefinitionUpdate)(req.body);
|
|
103
101
|
const customFieldDefinition = await DefinitionRepo.findByWhere({
|
|
104
102
|
id: customFieldDefinitionId,
|
|
@@ -40,11 +40,11 @@ const DefaultValueSchema = joi_1.default.when('fieldType', {
|
|
|
40
40
|
{ is: constants_1.CustomFieldDefinitionType.BOOLEAN, then: joi_1.default.boolean().allow(null) },
|
|
41
41
|
{ is: constants_1.CustomFieldDefinitionType.DATE, then: joi_1.default.date().allow(null) },
|
|
42
42
|
{ is: constants_1.CustomFieldDefinitionType.DATETIME, then: joi_1.default.date().allow(null) },
|
|
43
|
-
{ is: constants_1.CustomFieldDefinitionType.FILE, then:
|
|
44
|
-
{ is: constants_1.CustomFieldDefinitionType.IMAGE, then: joi_1.default.
|
|
43
|
+
{ is: constants_1.CustomFieldDefinitionType.FILE, then: FileValidationSchema },
|
|
44
|
+
{ is: constants_1.CustomFieldDefinitionType.IMAGE, then: joi_1.default.string().uri().allow(null) },
|
|
45
45
|
{ is: constants_1.CustomFieldDefinitionType.NUMBER, then: joi_1.default.number().allow(null) },
|
|
46
46
|
{ is: constants_1.CustomFieldDefinitionType.SELECT, then: joi_1.default.string().allow(null) },
|
|
47
|
-
{ is: constants_1.CustomFieldDefinitionType.STATUS, then:
|
|
47
|
+
{ is: constants_1.CustomFieldDefinitionType.STATUS, then: statusValidationObject.allow(null) },
|
|
48
48
|
{ is: constants_1.CustomFieldDefinitionType.TEXT, then: joi_1.default.string().allow(null) },
|
|
49
49
|
],
|
|
50
50
|
});
|
|
@@ -59,6 +59,7 @@ const CustomFieldDefinitionCreationSchema = joi_1.default.object({
|
|
|
59
59
|
description: joi_1.default.string(),
|
|
60
60
|
required: joi_1.default.boolean(),
|
|
61
61
|
disabled: joi_1.default.boolean(),
|
|
62
|
+
blockEditingFromUI: joi_1.default.boolean(),
|
|
62
63
|
});
|
|
63
64
|
const CustomFieldDefinitionUpdateSchema = joi_1.default.object({
|
|
64
65
|
displayName: joi_1.default.string(),
|
|
@@ -68,6 +69,7 @@ const CustomFieldDefinitionUpdateSchema = joi_1.default.object({
|
|
|
68
69
|
description: joi_1.default.string().allow(null),
|
|
69
70
|
required: joi_1.default.boolean(),
|
|
70
71
|
disabled: joi_1.default.boolean(),
|
|
72
|
+
blockEditingFromUI: joi_1.default.boolean(),
|
|
71
73
|
});
|
|
72
74
|
const validateCustomFieldDefinitionCreation = (payload) => CustomFieldDefinitionCreationSchema.validateAsync(payload, { abortEarly: false });
|
|
73
75
|
exports.validateCustomFieldDefinitionCreation = validateCustomFieldDefinitionCreation;
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { BadRequest } from '@autofleet/errors';
|
|
2
|
-
import type { ValidationError } from 'joi';
|
|
3
2
|
export declare class MissingRequiredCustomFieldError extends BadRequest {
|
|
4
3
|
constructor(missingFields: string[]);
|
|
5
4
|
}
|
|
@@ -9,11 +8,8 @@ export declare class UnsupportedCustomFieldTypeError extends BadRequest {
|
|
|
9
8
|
export declare class UnsupportedCustomValidationError extends BadRequest {
|
|
10
9
|
constructor(fieldType: string);
|
|
11
10
|
}
|
|
12
|
-
export declare class InvalidFieldTypeError extends BadRequest {
|
|
13
|
-
constructor(fieldType: string);
|
|
14
|
-
}
|
|
15
11
|
export declare class InvalidValueError extends BadRequest {
|
|
16
|
-
constructor(value: any,
|
|
12
|
+
constructor(value: any, fieldType: string);
|
|
17
13
|
}
|
|
18
14
|
export declare class MissingDefinitionError extends BadRequest {
|
|
19
15
|
constructor(fieldNames: string[]);
|
package/dist/errors/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MissingDefinitionError = exports.InvalidValueError = exports.
|
|
3
|
+
exports.MissingDefinitionError = exports.InvalidValueError = exports.UnsupportedCustomValidationError = exports.UnsupportedCustomFieldTypeError = exports.MissingRequiredCustomFieldError = void 0;
|
|
4
4
|
/* eslint-disable max-classes-per-file */
|
|
5
5
|
const errors_1 = require("@autofleet/errors");
|
|
6
6
|
class MissingRequiredCustomFieldError extends errors_1.BadRequest {
|
|
@@ -27,24 +27,11 @@ class UnsupportedCustomValidationError extends errors_1.BadRequest {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
exports.UnsupportedCustomValidationError = UnsupportedCustomValidationError;
|
|
30
|
-
class InvalidFieldTypeError extends errors_1.BadRequest {
|
|
31
|
-
constructor(fieldType) {
|
|
32
|
-
const err = new Error(`Invalid field type ${fieldType}`);
|
|
33
|
-
super([err], null, null);
|
|
34
|
-
this.message = 'INVALID_FIELD_TYPE';
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
exports.InvalidFieldTypeError = InvalidFieldTypeError;
|
|
38
30
|
class InvalidValueError extends errors_1.BadRequest {
|
|
39
|
-
constructor(value,
|
|
40
|
-
const
|
|
41
|
-
.replace(/"/g, '')
|
|
42
|
-
.replace('value', `'${fieldDefinitionName}'`);
|
|
43
|
-
const formattedValue = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
44
|
-
const invalidValueMessage = `Invalid Value on field '${fieldDefinitionName}'. ${formattedErrorMessage}. received: '${formattedValue}'`;
|
|
45
|
-
const err = new Error(invalidValueMessage);
|
|
31
|
+
constructor(value, fieldType) {
|
|
32
|
+
const err = new Error(`Invalid "${fieldType}" value ${JSON.stringify(value)}`);
|
|
46
33
|
super([err], null, null);
|
|
47
|
-
this.message =
|
|
34
|
+
this.message = 'INVALID_VALUE';
|
|
48
35
|
}
|
|
49
36
|
}
|
|
50
37
|
exports.InvalidValueError = InvalidValueError;
|
|
@@ -17,6 +17,7 @@ declare class CustomFieldDefinition extends Model {
|
|
|
17
17
|
createdAt?: Date;
|
|
18
18
|
updatedAt?: Date;
|
|
19
19
|
deletedAt?: Date;
|
|
20
|
+
blockEditingFromUI: boolean;
|
|
20
21
|
values: CustomFieldValue[];
|
|
21
22
|
static displayNameDefaultValue(instance: CustomFieldDefinition): void;
|
|
22
23
|
static afterSaveHandler(instance: CustomFieldDefinition, options: any): void;
|
|
@@ -28,8 +28,8 @@ let CustomFieldDefinition = class CustomFieldDefinition extends sequelize_typesc
|
|
|
28
28
|
}
|
|
29
29
|
if (![null, undefined].includes(instance.defaultValue)) {
|
|
30
30
|
const isValid = (0, validations_1.validateValue)(instance.defaultValue, instance.fieldType, instance.validation);
|
|
31
|
-
if (isValid
|
|
32
|
-
throw new errors_1.InvalidValueError(instance.defaultValue, instance.
|
|
31
|
+
if (!isValid) {
|
|
32
|
+
throw new errors_1.InvalidValueError(instance.defaultValue, instance.fieldType);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -142,6 +142,14 @@ __decorate([
|
|
|
142
142
|
sequelize_typescript_1.Column,
|
|
143
143
|
__metadata("design:type", Date)
|
|
144
144
|
], CustomFieldDefinition.prototype, "deletedAt", void 0);
|
|
145
|
+
__decorate([
|
|
146
|
+
(0, sequelize_typescript_1.Column)({
|
|
147
|
+
type: sequelize_typescript_1.DataType.BOOLEAN,
|
|
148
|
+
defaultValue: false,
|
|
149
|
+
allowNull: false,
|
|
150
|
+
}),
|
|
151
|
+
__metadata("design:type", Boolean)
|
|
152
|
+
], CustomFieldDefinition.prototype, "blockEditingFromUI", void 0);
|
|
145
153
|
__decorate([
|
|
146
154
|
(0, sequelize_typescript_1.HasMany)(() => _1.CustomFieldValue),
|
|
147
155
|
__metadata("design:type", Array)
|
|
@@ -8,9 +8,8 @@ declare class CustomFieldValue extends Model {
|
|
|
8
8
|
updatedAt?: Date;
|
|
9
9
|
deletedAt?: Date;
|
|
10
10
|
customFieldDefinition: CustomFieldDefinition;
|
|
11
|
-
|
|
12
|
-
static
|
|
13
|
-
static validateCustomFieldValue(instance: CustomFieldValue): Promise<void>;
|
|
11
|
+
static validateValues(instances: CustomFieldValue[]): Promise<void>;
|
|
12
|
+
static validateValue(instance: CustomFieldValue): Promise<void>;
|
|
14
13
|
static afterSaveHandler(instance: CustomFieldValue, options: any): void;
|
|
15
14
|
}
|
|
16
15
|
export default CustomFieldValue;
|
|
@@ -39,21 +39,7 @@ const validations_1 = require("../utils/validations");
|
|
|
39
39
|
const CustomFieldDefinitionRepo = __importStar(require("../repository/definition"));
|
|
40
40
|
const errors_1 = require("../errors");
|
|
41
41
|
let CustomFieldValue = class CustomFieldValue extends sequelize_typescript_1.Model {
|
|
42
|
-
static
|
|
43
|
-
const { validation, fieldType, name } = definition;
|
|
44
|
-
const isValidFieldType = (0, validations_1.validateFieldType)(fieldType);
|
|
45
|
-
if (!isValidFieldType) {
|
|
46
|
-
throw new errors_1.InvalidFieldTypeError(fieldType);
|
|
47
|
-
}
|
|
48
|
-
// Always allow null values
|
|
49
|
-
if (instance.value === null)
|
|
50
|
-
return;
|
|
51
|
-
const validateValueResponse = (0, validations_1.validateValue)(instance.value, fieldType, validation);
|
|
52
|
-
if (validateValueResponse.error) {
|
|
53
|
-
throw new errors_1.InvalidValueError(instance.value, name, validateValueResponse.error);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
static async validateCustomFieldValues(instances) {
|
|
42
|
+
static async validateValues(instances) {
|
|
57
43
|
const ids = instances.map((instance) => instance.customFieldDefinitionId);
|
|
58
44
|
const uniqueIds = [...new Set(ids)];
|
|
59
45
|
const definitions = await CustomFieldDefinitionRepo.findByIds(uniqueIds, { withDisabled: true });
|
|
@@ -61,17 +47,23 @@ let CustomFieldValue = class CustomFieldValue extends sequelize_typescript_1.Mod
|
|
|
61
47
|
throw new Error('Definitions not found');
|
|
62
48
|
}
|
|
63
49
|
instances.forEach((instance) => {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
50
|
+
const { validation, fieldType, } = definitions
|
|
51
|
+
.find((definition) => definition.id === instance.customFieldDefinitionId);
|
|
52
|
+
const isValid = (0, validations_1.validateValue)(instance.value, fieldType, validation);
|
|
53
|
+
if (!isValid) {
|
|
54
|
+
throw new errors_1.InvalidValueError(instance.value, fieldType);
|
|
67
55
|
}
|
|
68
56
|
});
|
|
69
57
|
}
|
|
70
|
-
static async
|
|
58
|
+
static async validateValue(instance) {
|
|
71
59
|
const { customFieldDefinitionId } = instance;
|
|
72
60
|
// eslint-disable-next-line max-len
|
|
73
61
|
const cfd = await CustomFieldDefinitionRepo.findById(customFieldDefinitionId, { withDisabled: true });
|
|
74
|
-
|
|
62
|
+
const { validation, fieldType } = cfd;
|
|
63
|
+
const isValid = (0, validations_1.validateValue)(instance.value, fieldType, validation);
|
|
64
|
+
if (!isValid) {
|
|
65
|
+
throw new errors_1.InvalidValueError(instance.value, fieldType);
|
|
66
|
+
}
|
|
75
67
|
}
|
|
76
68
|
static afterSaveHandler(instance, options) {
|
|
77
69
|
if (options.transaction) {
|
|
@@ -128,7 +120,7 @@ __decorate([
|
|
|
128
120
|
__metadata("design:type", Function),
|
|
129
121
|
__metadata("design:paramtypes", [Array]),
|
|
130
122
|
__metadata("design:returntype", Promise)
|
|
131
|
-
], CustomFieldValue, "
|
|
123
|
+
], CustomFieldValue, "validateValues", null);
|
|
132
124
|
__decorate([
|
|
133
125
|
sequelize_typescript_1.BeforeUpdate,
|
|
134
126
|
sequelize_typescript_1.BeforeCreate,
|
|
@@ -136,7 +128,7 @@ __decorate([
|
|
|
136
128
|
__metadata("design:type", Function),
|
|
137
129
|
__metadata("design:paramtypes", [CustomFieldValue]),
|
|
138
130
|
__metadata("design:returntype", Promise)
|
|
139
|
-
], CustomFieldValue, "
|
|
131
|
+
], CustomFieldValue, "validateValue", null);
|
|
140
132
|
__decorate([
|
|
141
133
|
sequelize_typescript_1.AfterUpsert,
|
|
142
134
|
__metadata("design:type", Function),
|
package/dist/models/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import TestModel from './tests/TestModel';
|
|
|
5
5
|
import ContextAwareTestModel from './tests/contextAwareModels/ContextAwareTestModel';
|
|
6
6
|
import ContextTestModel from './tests/contextAwareModels/ContextTestModel';
|
|
7
7
|
import AssociatedTestModel from './tests/AssociatedTestModel';
|
|
8
|
-
|
|
8
|
+
import type { CustomFieldOptions } from '../types';
|
|
9
|
+
declare const initTables: (sequelize: Sequelize, getUser: CustomFieldOptions['getUser']) => Promise<void>;
|
|
9
10
|
declare const initTestModels: (sequelize: Sequelize) => Promise<void>;
|
|
10
11
|
export { CustomFieldValue, CustomFieldDefinition, TestModel, AssociatedTestModel, ContextAwareTestModel, ContextTestModel, initTables, initTestModels, };
|
package/dist/models/index.js
CHANGED
|
@@ -22,7 +22,7 @@ exports.AssociatedTestModel = AssociatedTestModel_1.default;
|
|
|
22
22
|
const productionModels = [CustomFieldDefinition_1.default, CustomFieldValue_1.default];
|
|
23
23
|
const testModels = [TestModel_1.default, AssociatedTestModel_1.default, ContextAwareTestModel_1.default, ContextTestModel_1.default];
|
|
24
24
|
const SADOT_MIGRATION_PREFIX = 'sadot-migration';
|
|
25
|
-
const SCHEMA_VERSION =
|
|
25
|
+
const SCHEMA_VERSION = 'fb0fa867-1241-4816-b08d-5ed9060c7ae5';
|
|
26
26
|
const CUSTOM_FIELDS_SCHEMA_VERSION = `${SADOT_MIGRATION_PREFIX}_${SCHEMA_VERSION}`;
|
|
27
27
|
const initTables = async (sequelize, getUser) => {
|
|
28
28
|
logger_1.default.info('custom-fields: initialize custom-fields tables');
|
|
@@ -34,18 +34,18 @@ const initTables = async (sequelize, getUser) => {
|
|
|
34
34
|
sequelize.addModels(productionModels);
|
|
35
35
|
CustomFieldDefinition_1.default.addScope('userScope', () => {
|
|
36
36
|
const user = getUser();
|
|
37
|
-
if (user?.permissions) {
|
|
38
|
-
return {
|
|
39
|
-
where: {
|
|
40
|
-
entityId: [
|
|
41
|
-
...Object.keys(user.permissions.fleets),
|
|
42
|
-
...Object.keys(user.permissions.businessModels),
|
|
43
|
-
...Object.keys(user.permissions.demandSources),
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
};
|
|
37
|
+
if (!user?.permissions) {
|
|
38
|
+
return {};
|
|
47
39
|
}
|
|
48
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
where: {
|
|
42
|
+
entityId: [
|
|
43
|
+
...Object.keys(user.permissions.fleets),
|
|
44
|
+
...Object.keys(user.permissions.businessModels),
|
|
45
|
+
...Object.keys(user.permissions.demandSources),
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
49
|
});
|
|
50
50
|
logger_1.default.info('custom-fields: models added');
|
|
51
51
|
const SequelizeMeta = sequelize.define('SequelizeMeta', {
|
|
@@ -62,8 +62,7 @@ const initTables = async (sequelize, getUser) => {
|
|
|
62
62
|
schema: 'public',
|
|
63
63
|
});
|
|
64
64
|
const migrations = await SequelizeMeta.findAll({ raw: true });
|
|
65
|
-
const currentSadotSchemaVersion = migrations
|
|
66
|
-
.reverse().find((m) => m.name.includes(SADOT_MIGRATION_PREFIX));
|
|
65
|
+
const currentSadotSchemaVersion = migrations.reverse().find((m) => m.name.includes(SADOT_MIGRATION_PREFIX));
|
|
67
66
|
if (!currentSadotSchemaVersion
|
|
68
67
|
|| currentSadotSchemaVersion.name !== CUSTOM_FIELDS_SCHEMA_VERSION) {
|
|
69
68
|
await CustomFieldDefinition_1.default.sync({ alter: true });
|
|
@@ -31,10 +31,8 @@ const findByEntityIds = async (modelType, entityIds, options = {}) => {
|
|
|
31
31
|
const { include, useEntityIdFromInclude } = options.modelOptions;
|
|
32
32
|
const where = {
|
|
33
33
|
modelType,
|
|
34
|
+
...(!useEntityIdFromInclude && { entityId: { [sequelize_1.Op.in]: entityIds } }),
|
|
34
35
|
};
|
|
35
|
-
if (!useEntityIdFromInclude) {
|
|
36
|
-
where.entityId = entityIds;
|
|
37
|
-
}
|
|
38
36
|
return models_1.CustomFieldDefinition.findAll({
|
|
39
37
|
where,
|
|
40
38
|
transaction: options.transaction,
|
|
@@ -77,10 +75,8 @@ const getRequiredFields = async (modelType, modelId, entityId, modelOptions = {}
|
|
|
77
75
|
const where = {
|
|
78
76
|
modelType,
|
|
79
77
|
required: true,
|
|
78
|
+
...(!useEntityIdFromInclude && { entityId: { [sequelize_1.Op.in]: entityIds } }),
|
|
80
79
|
};
|
|
81
|
-
if (!useEntityIdFromInclude) {
|
|
82
|
-
where.entityId = entityIds;
|
|
83
|
-
}
|
|
84
80
|
const requiredFields = await models_1.CustomFieldDefinition.findAll({
|
|
85
81
|
where,
|
|
86
82
|
include: include?.(entityIds),
|
|
@@ -21,6 +21,7 @@ export declare const coolFieldDefinition2: {
|
|
|
21
21
|
entityId: string;
|
|
22
22
|
entityType: string;
|
|
23
23
|
modelType: string;
|
|
24
|
+
blockEditingFromUI?: boolean;
|
|
24
25
|
};
|
|
25
26
|
export declare const coolFieldDefinition3: {
|
|
26
27
|
name: string;
|
|
@@ -37,10 +38,10 @@ export declare const coolFieldDefinition3: {
|
|
|
37
38
|
entityId: string;
|
|
38
39
|
entityType: string;
|
|
39
40
|
modelType: string;
|
|
41
|
+
blockEditingFromUI?: boolean;
|
|
40
42
|
};
|
|
41
43
|
export declare const booleanField: (modelType: string) => CreateCustomFieldDefinition;
|
|
42
44
|
export declare const selectField: (modelType: string, options: any) => CreateCustomFieldDefinition;
|
|
43
45
|
export declare const statusField: (modelType: string, options: any) => CreateCustomFieldDefinition;
|
|
44
|
-
export declare const fileField: (modelType: string) => CreateCustomFieldDefinition;
|
|
45
46
|
export declare const createDefinition: (defaults: Partial<CustomFieldDefinitionDTO>) => CreateCustomFieldDefinition;
|
|
46
47
|
export declare const createDefinitions: (defaults: Partial<CustomFieldDefinitionDTO>, length?: number) => CreateCustomFieldDefinition[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createDefinitions = exports.createDefinition = exports.
|
|
3
|
+
exports.createDefinitions = exports.createDefinition = exports.statusField = exports.selectField = exports.booleanField = exports.coolFieldDefinition3 = exports.coolFieldDefinition2 = exports.coolFieldDefinition = exports.contextAwareFieldDefinition = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
exports.contextAwareFieldDefinition = {
|
|
6
6
|
name: 'cool field',
|
|
@@ -49,14 +49,6 @@ const statusField = (modelType, options) => ({
|
|
|
49
49
|
entityType: 'fleetId',
|
|
50
50
|
});
|
|
51
51
|
exports.statusField = statusField;
|
|
52
|
-
const fileField = (modelType) => ({
|
|
53
|
-
name: 'file',
|
|
54
|
-
modelType,
|
|
55
|
-
fieldType: 'file',
|
|
56
|
-
entityId: (0, node_crypto_1.randomUUID)(),
|
|
57
|
-
entityType: 'fleetId',
|
|
58
|
-
});
|
|
59
|
-
exports.fileField = fileField;
|
|
60
52
|
// eslint-disable-next-line max-len
|
|
61
53
|
const createDefinition = (defaults) => ({
|
|
62
54
|
name: defaults?.name || `def_${(0, node_crypto_1.randomUUID)()}`,
|
|
@@ -14,6 +14,7 @@ export interface CustomFieldDefinitionDTO {
|
|
|
14
14
|
createdAt?: Date;
|
|
15
15
|
updatedAt?: Date;
|
|
16
16
|
deletedAt?: Date;
|
|
17
|
+
blockEditingFromUI?: boolean;
|
|
17
18
|
}
|
|
18
19
|
export type CreateCustomFieldDefinition = Omit<CustomFieldDefinitionDTO, 'id'>;
|
|
19
20
|
export type UpdateCustomFieldDefinition = Partial<CreateCustomFieldDefinition>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IncludeOptions } from 'sequelize';
|
|
2
2
|
import type { ModelCtor } from 'sequelize-typescript';
|
|
3
|
+
import type { getUser as GetUserType } from '@autofleet/zehut';
|
|
3
4
|
export type ModelFetcher = (name: string) => any;
|
|
4
5
|
export type ModelOptions = {
|
|
5
6
|
/**
|
|
@@ -27,5 +28,5 @@ export type Models = {
|
|
|
27
28
|
export type CustomFieldOptions = {
|
|
28
29
|
models: Models[];
|
|
29
30
|
databaseConfig: any;
|
|
30
|
-
getUser:
|
|
31
|
+
getUser: typeof GetUserType;
|
|
31
32
|
};
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import type { CustomFieldDefinitionType } from '../constants';
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const validateValue: (value: unknown, valueType: CustomFieldDefinitionType, validation?: unknown) => import("joi").ValidationResult;
|
|
2
|
+
export declare const validateValue: (value: unknown, valueType: CustomFieldDefinitionType, validation?: unknown) => boolean;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateValue =
|
|
3
|
+
exports.validateValue = void 0;
|
|
4
4
|
const validators_1 = require("./validators");
|
|
5
|
-
const validateFieldType = (type) => Object.keys(validators_1.validators).includes(type);
|
|
6
|
-
exports.validateFieldType = validateFieldType;
|
|
7
5
|
const validateValue = (value, valueType, validation) => {
|
|
8
6
|
const validator = validators_1.validators[valueType];
|
|
9
|
-
|
|
7
|
+
if (!validator) {
|
|
8
|
+
// Unsupported field type
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
// Always allow null values
|
|
12
|
+
return value === null || validator(value, validation);
|
|
10
13
|
/** TODO: Add validation for required fields
|
|
11
14
|
* @example
|
|
12
15
|
* if (validations.required && !value) {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type { ValidationResult } from 'joi';
|
|
2
1
|
import type { CustomFieldDefinitionType } from '../constants';
|
|
3
2
|
/**
|
|
4
3
|
* Validator is a function that validates a custom-field `value`,
|
|
5
4
|
* against a custom-field definition `validation object`.
|
|
6
5
|
* @returns `true` if the value is valid, `false` otherwise.
|
|
7
6
|
*/
|
|
8
|
-
export type Validator<Value, DefinitionValidationObject> = (value: Value, validation?: DefinitionValidationObject) =>
|
|
7
|
+
export type Validator<Value, DefinitionValidationObject> = (value: Value, validation?: DefinitionValidationObject) => boolean;
|
|
9
8
|
/**
|
|
10
9
|
* Validators is a map of custom-field types to their respective validators.
|
|
11
10
|
* The key is the custom-field type, and the value is the validator function.
|
|
@@ -22,19 +22,19 @@ exports.CustomValidationTypes = {
|
|
|
22
22
|
exports.validators = {
|
|
23
23
|
[constants_1.CustomFieldDefinitionType.SELECT]: select_validator_1.validateSelect,
|
|
24
24
|
[constants_1.CustomFieldDefinitionType.STATUS]: status_validator_1.validateStatus,
|
|
25
|
-
[constants_1.CustomFieldDefinitionType.TEXT]: (value) =>
|
|
26
|
-
[constants_1.CustomFieldDefinitionType.NUMBER]: (value) =>
|
|
27
|
-
[constants_1.CustomFieldDefinitionType.BOOLEAN]: (value) =>
|
|
28
|
-
[constants_1.CustomFieldDefinitionType.DATE]: (value) => joi_1.default.date().validate(value),
|
|
29
|
-
[constants_1.CustomFieldDefinitionType.DATETIME]: (value) => joi_1.default.date().validate(value),
|
|
30
|
-
[constants_1.CustomFieldDefinitionType.IMAGE]: (value) => joi_1.default.array().min(1).unique()
|
|
25
|
+
[constants_1.CustomFieldDefinitionType.TEXT]: (value) => (typeof value === 'string'),
|
|
26
|
+
[constants_1.CustomFieldDefinitionType.NUMBER]: (value) => (typeof value === 'number'),
|
|
27
|
+
[constants_1.CustomFieldDefinitionType.BOOLEAN]: (value) => (typeof value === 'boolean'),
|
|
28
|
+
[constants_1.CustomFieldDefinitionType.DATE]: (value) => (!joi_1.default.date().validate(value).error),
|
|
29
|
+
[constants_1.CustomFieldDefinitionType.DATETIME]: (value) => (!joi_1.default.date().validate(value).error),
|
|
30
|
+
[constants_1.CustomFieldDefinitionType.IMAGE]: (value) => (!joi_1.default.array().min(1).unique()
|
|
31
31
|
.items(joi_1.default.string().uri())
|
|
32
|
-
.validate(value),
|
|
33
|
-
[constants_1.CustomFieldDefinitionType.FILE]: (value) => joi_1.default.array().min(1).unique().items(joi_1.default.object({
|
|
32
|
+
.validate(value).error),
|
|
33
|
+
[constants_1.CustomFieldDefinitionType.FILE]: (value) => (!joi_1.default.array().min(1).unique().items(joi_1.default.object({
|
|
34
34
|
name: joi_1.default.string().required(),
|
|
35
35
|
type: joi_1.default.string(),
|
|
36
36
|
size: joi_1.default.string(),
|
|
37
37
|
addedBy: joi_1.default.string().uuid(),
|
|
38
38
|
}))
|
|
39
|
-
.validate(value),
|
|
39
|
+
.validate(value).error),
|
|
40
40
|
};
|
|
@@ -1,12 +1,9 @@
|
|
|
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.validateSelect = void 0;
|
|
7
|
-
const joi_1 = __importDefault(require("joi"));
|
|
8
4
|
/**
|
|
9
5
|
* Validate that the value is one of the select values
|
|
10
6
|
*/
|
|
11
|
-
const validateSelect = (value, selectValues) => (
|
|
7
|
+
const validateSelect = (value, selectValues) => (Array.isArray(selectValues)
|
|
8
|
+
&& selectValues.includes(value));
|
|
12
9
|
exports.validateSelect = validateSelect;
|
|
@@ -1,15 +1,9 @@
|
|
|
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.validateStatus = void 0;
|
|
7
|
-
const joi_1 = __importDefault(require("joi"));
|
|
8
4
|
/**
|
|
9
5
|
* Validate that the value is one of the status values
|
|
10
6
|
*/
|
|
11
|
-
const validateStatus = (value, statusValues) => (
|
|
12
|
-
.
|
|
13
|
-
.valid(...statusValues.map((statusValue) => statusValue.value))
|
|
14
|
-
.validate(value));
|
|
7
|
+
const validateStatus = (value, statusValues) => (Array.isArray(statusValues)
|
|
8
|
+
&& statusValues.some((status) => status.value === value));
|
|
15
9
|
exports.validateStatus = validateStatus;
|
package/package.json
CHANGED
|
@@ -56,16 +56,15 @@ router.get('/:customFieldDefinitionId', async (req, res) => {
|
|
|
56
56
|
* Get all
|
|
57
57
|
*/
|
|
58
58
|
router.get('/', async (req, res) => {
|
|
59
|
-
const { modelName } = req
|
|
60
|
-
const { entityIds } = req.query as any;
|
|
59
|
+
const { params: { modelName }, query: { entityIds } } = req as any;
|
|
61
60
|
|
|
62
61
|
const modelType = toPascalCase(modelName);
|
|
63
62
|
try {
|
|
64
|
-
const where
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
const customFieldDefinitions = await DefinitionRepo.findAll(
|
|
63
|
+
const where = {
|
|
64
|
+
modelType,
|
|
65
|
+
...(entityIds?.length > 0 && { entityId: entityIds }),
|
|
66
|
+
};
|
|
67
|
+
const customFieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true });
|
|
69
68
|
return res.json(customFieldDefinitions);
|
|
70
69
|
} catch (err) {
|
|
71
70
|
logger.error('Failed to fetch custom field definitions', err);
|
|
@@ -80,7 +79,6 @@ router.patch('/:customFieldDefinitionId', async (req, res) => {
|
|
|
80
79
|
const { customFieldDefinitionId, modelName } = req.params as any;
|
|
81
80
|
const modelType = toPascalCase(modelName);
|
|
82
81
|
try {
|
|
83
|
-
// eslint-disable-next-line max-len
|
|
84
82
|
const validatedPayload: UpdateCustomFieldDefinition = await validateCustomFieldDefinitionUpdate(req.body);
|
|
85
83
|
|
|
86
84
|
const customFieldDefinition = await DefinitionRepo.findByWhere({
|
|
@@ -36,11 +36,11 @@ const DefaultValueSchema = Joi.when('fieldType', {
|
|
|
36
36
|
{ is: CustomFieldDefinitionType.BOOLEAN, then: Joi.boolean().allow(null) },
|
|
37
37
|
{ is: CustomFieldDefinitionType.DATE, then: Joi.date().allow(null) },
|
|
38
38
|
{ is: CustomFieldDefinitionType.DATETIME, then: Joi.date().allow(null) },
|
|
39
|
-
{ is: CustomFieldDefinitionType.FILE, then:
|
|
40
|
-
{ is: CustomFieldDefinitionType.IMAGE, then: Joi.
|
|
39
|
+
{ is: CustomFieldDefinitionType.FILE, then: FileValidationSchema },
|
|
40
|
+
{ is: CustomFieldDefinitionType.IMAGE, then: Joi.string().uri().allow(null) },
|
|
41
41
|
{ is: CustomFieldDefinitionType.NUMBER, then: Joi.number().allow(null) },
|
|
42
42
|
{ is: CustomFieldDefinitionType.SELECT, then: Joi.string().allow(null) },
|
|
43
|
-
{ is: CustomFieldDefinitionType.STATUS, then:
|
|
43
|
+
{ is: CustomFieldDefinitionType.STATUS, then: statusValidationObject.allow(null) },
|
|
44
44
|
{ is: CustomFieldDefinitionType.TEXT, then: Joi.string().allow(null) },
|
|
45
45
|
],
|
|
46
46
|
});
|
|
@@ -56,6 +56,7 @@ const CustomFieldDefinitionCreationSchema = Joi.object({
|
|
|
56
56
|
description: Joi.string(),
|
|
57
57
|
required: Joi.boolean(),
|
|
58
58
|
disabled: Joi.boolean(),
|
|
59
|
+
blockEditingFromUI: Joi.boolean(),
|
|
59
60
|
});
|
|
60
61
|
|
|
61
62
|
const CustomFieldDefinitionUpdateSchema = Joi.object({
|
|
@@ -66,6 +67,7 @@ const CustomFieldDefinitionUpdateSchema = Joi.object({
|
|
|
66
67
|
description: Joi.string().allow(null),
|
|
67
68
|
required: Joi.boolean(),
|
|
68
69
|
disabled: Joi.boolean(),
|
|
70
|
+
blockEditingFromUI: Joi.boolean(),
|
|
69
71
|
});
|
|
70
72
|
|
|
71
73
|
export const validateCustomFieldDefinitionCreation = (payload) =>
|
package/src/errors/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/* eslint-disable max-classes-per-file */
|
|
2
2
|
import { BadRequest } from '@autofleet/errors';
|
|
3
|
-
import type { ValidationError } from 'joi';
|
|
4
3
|
|
|
5
4
|
export class MissingRequiredCustomFieldError extends BadRequest {
|
|
6
5
|
constructor(missingFields: string[]) {
|
|
@@ -26,27 +25,11 @@ export class UnsupportedCustomValidationError extends BadRequest {
|
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
export class InvalidFieldTypeError extends BadRequest {
|
|
30
|
-
constructor(fieldType: string) {
|
|
31
|
-
const err = new Error(`Invalid field type ${fieldType}`);
|
|
32
|
-
super([err], null, null);
|
|
33
|
-
this.message = 'INVALID_FIELD_TYPE';
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
28
|
export class InvalidValueError extends BadRequest {
|
|
38
|
-
constructor(value: any,
|
|
39
|
-
const
|
|
40
|
-
.replace(/"/g, '')
|
|
41
|
-
.replace('value', `'${fieldDefinitionName}'`);
|
|
42
|
-
|
|
43
|
-
const formattedValue = typeof value === 'object' ? JSON.stringify(value) : value;
|
|
44
|
-
|
|
45
|
-
const invalidValueMessage = `Invalid Value on field '${fieldDefinitionName}'. ${formattedErrorMessage}. received: '${formattedValue}'`;
|
|
46
|
-
|
|
47
|
-
const err = new Error(invalidValueMessage);
|
|
29
|
+
constructor(value: any, fieldType: string) {
|
|
30
|
+
const err = new Error(`Invalid "${fieldType}" value ${JSON.stringify(value)}`);
|
|
48
31
|
super([err], null, null);
|
|
49
|
-
this.message =
|
|
32
|
+
this.message = 'INVALID_VALUE';
|
|
50
33
|
}
|
|
51
34
|
}
|
|
52
35
|
|
|
@@ -125,6 +125,13 @@ class CustomFieldDefinition extends Model {
|
|
|
125
125
|
@Column
|
|
126
126
|
deletedAt?: Date;
|
|
127
127
|
|
|
128
|
+
@Column({
|
|
129
|
+
type: DataType.BOOLEAN,
|
|
130
|
+
defaultValue: false,
|
|
131
|
+
allowNull: false,
|
|
132
|
+
})
|
|
133
|
+
blockEditingFromUI!: boolean;
|
|
134
|
+
|
|
128
135
|
@HasMany(() => CustomFieldValue)
|
|
129
136
|
values: CustomFieldValue[];
|
|
130
137
|
|
|
@@ -136,8 +143,8 @@ class CustomFieldDefinition extends Model {
|
|
|
136
143
|
}
|
|
137
144
|
if (![null, undefined].includes(instance.defaultValue)) {
|
|
138
145
|
const isValid = validateValue(instance.defaultValue, instance.fieldType, instance.validation);
|
|
139
|
-
if (isValid
|
|
140
|
-
throw new InvalidValueError(instance.defaultValue, instance.
|
|
146
|
+
if (!isValid) {
|
|
147
|
+
throw new InvalidValueError(instance.defaultValue, instance.fieldType);
|
|
141
148
|
}
|
|
142
149
|
}
|
|
143
150
|
}
|
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
} from 'sequelize-typescript';
|
|
16
16
|
import { sendDimEvent } from '../events';
|
|
17
17
|
import { CustomFieldDefinition } from '.';
|
|
18
|
-
import {
|
|
18
|
+
import { validateValue } from '../utils/validations';
|
|
19
19
|
import * as CustomFieldDefinitionRepo from '../repository/definition';
|
|
20
|
-
import {
|
|
20
|
+
import { InvalidValueError } from '../errors';
|
|
21
21
|
|
|
22
22
|
@Table({
|
|
23
23
|
timestamps: true,
|
|
@@ -56,26 +56,9 @@ class CustomFieldValue extends Model {
|
|
|
56
56
|
@BelongsTo(() => CustomFieldDefinition, { scope: { disabled: false } })
|
|
57
57
|
customFieldDefinition: CustomFieldDefinition;
|
|
58
58
|
|
|
59
|
-
private static validateValueAgainstDefinition(
|
|
60
|
-
instance: CustomFieldValue,
|
|
61
|
-
definition: CustomFieldDefinition,
|
|
62
|
-
): void {
|
|
63
|
-
const { validation, fieldType, name } = definition;
|
|
64
|
-
const isValidFieldType = validateFieldType(fieldType);
|
|
65
|
-
if (!isValidFieldType) {
|
|
66
|
-
throw new InvalidFieldTypeError(fieldType);
|
|
67
|
-
}
|
|
68
|
-
// Always allow null values
|
|
69
|
-
if (instance.value === null) return;
|
|
70
|
-
const validateValueResponse = validateValue(instance.value, fieldType, validation);
|
|
71
|
-
if (validateValueResponse.error) {
|
|
72
|
-
throw new InvalidValueError(instance.value, name, validateValueResponse.error);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
59
|
@BeforeBulkCreate
|
|
77
60
|
@BeforeBulkUpdate
|
|
78
|
-
static async
|
|
61
|
+
static async validateValues(instances: CustomFieldValue[]): Promise<void> {
|
|
79
62
|
const ids = instances.map((instance) => instance.customFieldDefinitionId);
|
|
80
63
|
const uniqueIds = [...new Set(ids)];
|
|
81
64
|
const definitions = await CustomFieldDefinitionRepo.findByIds(
|
|
@@ -88,9 +71,14 @@ class CustomFieldValue extends Model {
|
|
|
88
71
|
}
|
|
89
72
|
|
|
90
73
|
instances.forEach((instance) => {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
74
|
+
const {
|
|
75
|
+
validation,
|
|
76
|
+
fieldType,
|
|
77
|
+
} = definitions
|
|
78
|
+
.find((definition) => definition.id === instance.customFieldDefinitionId);
|
|
79
|
+
const isValid = validateValue(instance.value, fieldType, validation);
|
|
80
|
+
if (!isValid) {
|
|
81
|
+
throw new InvalidValueError(instance.value, fieldType);
|
|
94
82
|
}
|
|
95
83
|
});
|
|
96
84
|
}
|
|
@@ -98,11 +86,15 @@ class CustomFieldValue extends Model {
|
|
|
98
86
|
@BeforeUpdate
|
|
99
87
|
@BeforeCreate
|
|
100
88
|
@BeforeUpsert
|
|
101
|
-
static async
|
|
89
|
+
static async validateValue(instance: CustomFieldValue): Promise<void> {
|
|
102
90
|
const { customFieldDefinitionId } = instance;
|
|
103
91
|
// eslint-disable-next-line max-len
|
|
104
92
|
const cfd = await CustomFieldDefinitionRepo.findById(customFieldDefinitionId, { withDisabled: true });
|
|
105
|
-
|
|
93
|
+
const { validation, fieldType } = cfd;
|
|
94
|
+
const isValid = validateValue(instance.value, fieldType, validation);
|
|
95
|
+
if (!isValid) {
|
|
96
|
+
throw new InvalidValueError(instance.value, fieldType);
|
|
97
|
+
}
|
|
106
98
|
}
|
|
107
99
|
|
|
108
100
|
@AfterUpsert
|
package/src/models/index.ts
CHANGED
|
@@ -8,15 +8,16 @@ import TestModel from './tests/TestModel';
|
|
|
8
8
|
import ContextAwareTestModel from './tests/contextAwareModels/ContextAwareTestModel';
|
|
9
9
|
import ContextTestModel from './tests/contextAwareModels/ContextTestModel';
|
|
10
10
|
import AssociatedTestModel from './tests/AssociatedTestModel';
|
|
11
|
+
import type { CustomFieldOptions } from '../types';
|
|
11
12
|
|
|
12
13
|
const productionModels = [CustomFieldDefinition, CustomFieldValue];
|
|
13
14
|
const testModels = [TestModel, AssociatedTestModel, ContextAwareTestModel, ContextTestModel];
|
|
14
15
|
|
|
15
16
|
const SADOT_MIGRATION_PREFIX = 'sadot-migration';
|
|
16
|
-
const SCHEMA_VERSION =
|
|
17
|
+
const SCHEMA_VERSION = 'fb0fa867-1241-4816-b08d-5ed9060c7ae5';
|
|
17
18
|
const CUSTOM_FIELDS_SCHEMA_VERSION = `${SADOT_MIGRATION_PREFIX}_${SCHEMA_VERSION}`;
|
|
18
19
|
|
|
19
|
-
const initTables = async (sequelize: Sequelize, getUser): Promise<void> => {
|
|
20
|
+
const initTables = async (sequelize: Sequelize, getUser: CustomFieldOptions['getUser']): Promise<void> => {
|
|
20
21
|
logger.info('custom-fields: initialize custom-fields tables');
|
|
21
22
|
// Detect models and import them to the orm
|
|
22
23
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
@@ -27,18 +28,18 @@ const initTables = async (sequelize: Sequelize, getUser): Promise<void> => {
|
|
|
27
28
|
|
|
28
29
|
CustomFieldDefinition.addScope('userScope', () => {
|
|
29
30
|
const user = getUser();
|
|
30
|
-
if (user?.permissions) {
|
|
31
|
-
return {
|
|
32
|
-
where: {
|
|
33
|
-
entityId: [
|
|
34
|
-
...Object.keys(user.permissions.fleets),
|
|
35
|
-
...Object.keys(user.permissions.businessModels),
|
|
36
|
-
...Object.keys(user.permissions.demandSources),
|
|
37
|
-
],
|
|
38
|
-
},
|
|
39
|
-
};
|
|
31
|
+
if (!user?.permissions) {
|
|
32
|
+
return {};
|
|
40
33
|
}
|
|
41
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
where: {
|
|
36
|
+
entityId: [
|
|
37
|
+
...Object.keys(user.permissions.fleets),
|
|
38
|
+
...Object.keys(user.permissions.businessModels),
|
|
39
|
+
...Object.keys(user.permissions.demandSources),
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
logger.info('custom-fields: models added');
|
|
@@ -61,8 +62,7 @@ const initTables = async (sequelize: Sequelize, getUser): Promise<void> => {
|
|
|
61
62
|
},
|
|
62
63
|
);
|
|
63
64
|
const migrations = await SequelizeMeta.findAll({ raw: true });
|
|
64
|
-
const currentSadotSchemaVersion = migrations
|
|
65
|
-
.reverse().find((m: any) => m.name.includes(SADOT_MIGRATION_PREFIX));
|
|
65
|
+
const currentSadotSchemaVersion = migrations.reverse().find((m: any) => m.name.includes(SADOT_MIGRATION_PREFIX));
|
|
66
66
|
|
|
67
67
|
if (
|
|
68
68
|
!currentSadotSchemaVersion
|
|
@@ -55,11 +55,9 @@ export const findByEntityIds = async (
|
|
|
55
55
|
const { include, useEntityIdFromInclude } = options.modelOptions;
|
|
56
56
|
const where: WhereOptions = {
|
|
57
57
|
modelType,
|
|
58
|
+
...(!useEntityIdFromInclude && { entityId: { [Op.in]: entityIds } }),
|
|
58
59
|
};
|
|
59
60
|
|
|
60
|
-
if (!useEntityIdFromInclude) {
|
|
61
|
-
where.entityId = entityIds;
|
|
62
|
-
}
|
|
63
61
|
return CustomFieldDefinition.findAll({
|
|
64
62
|
where,
|
|
65
63
|
transaction: options.transaction,
|
|
@@ -120,12 +118,9 @@ export const getRequiredFields = async (
|
|
|
120
118
|
const where: WhereOptions = {
|
|
121
119
|
modelType,
|
|
122
120
|
required: true,
|
|
121
|
+
...(!useEntityIdFromInclude && { entityId: { [Op.in]: entityIds } }),
|
|
123
122
|
};
|
|
124
123
|
|
|
125
|
-
if (!useEntityIdFromInclude) {
|
|
126
|
-
where.entityId = entityIds;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
124
|
const requiredFields = await CustomFieldDefinition.findAll({
|
|
130
125
|
where,
|
|
131
126
|
include: include?.(entityIds),
|
package/src/repository/value.ts
CHANGED
|
@@ -52,14 +52,6 @@ export const statusField = (modelType: string, options): CreateCustomFieldDefini
|
|
|
52
52
|
entityType: 'fleetId',
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
export const fileField = (modelType: string): CreateCustomFieldDefinition => ({
|
|
56
|
-
name: 'file',
|
|
57
|
-
modelType,
|
|
58
|
-
fieldType: 'file',
|
|
59
|
-
entityId: uuidv4(),
|
|
60
|
-
entityType: 'fleetId',
|
|
61
|
-
});
|
|
62
|
-
|
|
63
55
|
// eslint-disable-next-line max-len
|
|
64
56
|
export const createDefinition = (defaults: Partial<CustomFieldDefinitionDTO>): CreateCustomFieldDefinition => ({
|
|
65
57
|
name: defaults?.name || `def_${uuidv4()}`,
|
|
@@ -14,6 +14,7 @@ export interface CustomFieldDefinitionDTO {
|
|
|
14
14
|
createdAt?: Date;
|
|
15
15
|
updatedAt?: Date;
|
|
16
16
|
deletedAt?: Date;
|
|
17
|
+
blockEditingFromUI?: boolean;
|
|
17
18
|
}
|
|
18
19
|
export type CreateCustomFieldDefinition = Omit<CustomFieldDefinitionDTO, 'id'>;
|
|
19
20
|
export type UpdateCustomFieldDefinition = Partial<CreateCustomFieldDefinition>;
|
package/src/types/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IncludeOptions } from 'sequelize';
|
|
2
2
|
import type { ModelCtor } from 'sequelize-typescript';
|
|
3
|
+
import type { getUser as GetUserType } from '@autofleet/zehut';
|
|
3
4
|
|
|
4
5
|
export type ModelFetcher = (name: string) => any;
|
|
5
6
|
|
|
@@ -31,5 +32,5 @@ export type Models = {
|
|
|
31
32
|
export type CustomFieldOptions = {
|
|
32
33
|
models: Models[];
|
|
33
34
|
databaseConfig: any;
|
|
34
|
-
getUser:
|
|
35
|
+
getUser: typeof GetUserType;
|
|
35
36
|
};
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import type { CustomFieldDefinitionType } from '../constants';
|
|
2
2
|
import { validators } from './validators';
|
|
3
3
|
|
|
4
|
-
export const validateFieldType = (type: CustomFieldDefinitionType): boolean => Object.keys(validators).includes(type);
|
|
5
|
-
|
|
6
4
|
export const validateValue = (
|
|
7
5
|
value: unknown,
|
|
8
6
|
valueType: CustomFieldDefinitionType,
|
|
9
7
|
validation?: unknown,
|
|
10
8
|
) => {
|
|
11
9
|
const validator = validators[valueType];
|
|
12
|
-
|
|
10
|
+
if (!validator) {
|
|
11
|
+
// Unsupported field type
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
// Always allow null values
|
|
15
|
+
return value === null || validator(value, validation);
|
|
13
16
|
/** TODO: Add validation for required fields
|
|
14
17
|
* @example
|
|
15
18
|
* if (validations.required && !value) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ValidationResult } from 'joi';
|
|
2
1
|
import type { CustomFieldDefinitionType } from '../constants';
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -9,7 +8,7 @@ import type { CustomFieldDefinitionType } from '../constants';
|
|
|
9
8
|
export type Validator<Value, DefinitionValidationObject> = (
|
|
10
9
|
value: Value,
|
|
11
10
|
validation?: DefinitionValidationObject
|
|
12
|
-
) =>
|
|
11
|
+
) => boolean;
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* Validators is a map of custom-field types to their respective validators.
|
|
@@ -20,19 +20,19 @@ export const CustomValidationTypes = {
|
|
|
20
20
|
export const validators: Validators = {
|
|
21
21
|
[CustomFieldDefinitionType.SELECT]: validateSelect,
|
|
22
22
|
[CustomFieldDefinitionType.STATUS]: validateStatus,
|
|
23
|
-
[CustomFieldDefinitionType.TEXT]: (value) =>
|
|
24
|
-
[CustomFieldDefinitionType.NUMBER]: (value) =>
|
|
25
|
-
[CustomFieldDefinitionType.BOOLEAN]: (value) =>
|
|
26
|
-
[CustomFieldDefinitionType.DATE]: (value) => Joi.date().validate(value),
|
|
27
|
-
[CustomFieldDefinitionType.DATETIME]: (value) => Joi.date().validate(value),
|
|
28
|
-
[CustomFieldDefinitionType.IMAGE]: (value) => Joi.array().min(1).unique()
|
|
23
|
+
[CustomFieldDefinitionType.TEXT]: (value) => (typeof value === 'string'),
|
|
24
|
+
[CustomFieldDefinitionType.NUMBER]: (value) => (typeof value === 'number'),
|
|
25
|
+
[CustomFieldDefinitionType.BOOLEAN]: (value) => (typeof value === 'boolean'),
|
|
26
|
+
[CustomFieldDefinitionType.DATE]: (value) => (!Joi.date().validate(value).error),
|
|
27
|
+
[CustomFieldDefinitionType.DATETIME]: (value) => (!Joi.date().validate(value).error),
|
|
28
|
+
[CustomFieldDefinitionType.IMAGE]: (value) => (!Joi.array().min(1).unique()
|
|
29
29
|
.items(Joi.string().uri())
|
|
30
|
-
.validate(value),
|
|
31
|
-
[CustomFieldDefinitionType.FILE]: (value) => Joi.array().min(1).unique().items(Joi.object({
|
|
30
|
+
.validate(value).error),
|
|
31
|
+
[CustomFieldDefinitionType.FILE]: (value) => (!Joi.array().min(1).unique().items(Joi.object({
|
|
32
32
|
name: Joi.string().required(),
|
|
33
33
|
type: Joi.string(),
|
|
34
34
|
size: Joi.string(),
|
|
35
35
|
addedBy: Joi.string().uuid(),
|
|
36
36
|
}))
|
|
37
|
-
.validate(value),
|
|
37
|
+
.validate(value).error),
|
|
38
38
|
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import Joi from 'joi';
|
|
2
1
|
import type { Validator } from '../type';
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -7,6 +6,6 @@ import type { Validator } from '../type';
|
|
|
7
6
|
export const validateSelect: Validator<string, string[]> = (
|
|
8
7
|
value,
|
|
9
8
|
selectValues,
|
|
10
|
-
) => (
|
|
11
|
-
|
|
9
|
+
) => (Array.isArray(selectValues)
|
|
10
|
+
&& selectValues.includes(value)
|
|
12
11
|
);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import Joi from 'joi';
|
|
2
1
|
import type { Validator } from '../type';
|
|
3
2
|
|
|
4
3
|
type StatusColor = string | null; // TODO: Takes from @autofleet/colors ?
|
|
@@ -14,9 +13,6 @@ type StatusOption = {
|
|
|
14
13
|
export const validateStatus: Validator<StatusValue, StatusOption[]> = (
|
|
15
14
|
value,
|
|
16
15
|
statusValues,
|
|
17
|
-
) => (
|
|
18
|
-
|
|
19
|
-
.allow(null)
|
|
20
|
-
.valid(...statusValues.map((statusValue) => statusValue.value))
|
|
21
|
-
.validate(value)
|
|
16
|
+
) => (Array.isArray(statusValues)
|
|
17
|
+
&& statusValues.some((status) => status.value === value)
|
|
22
18
|
);
|
package/.env
DELETED