@autofleet/sadot 0.13.0-beta.10 → 0.13.0-beta.12
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/validator/index.js +14 -3
- package/dist/api/v1/validator/validations.js +1 -0
- package/dist/hooks/hooks.js +85 -25
- package/dist/index.d.ts +0 -3
- package/dist/index.js +1 -5
- package/dist/models/CustomValidator.js +1 -0
- package/dist/models/index.js +15 -0
- package/dist/repository/validator.d.ts +1 -1
- package/dist/repository/validator.js +17 -7
- package/dist/tests/helpers/database-config.js +1 -1
- package/package.json +1 -1
- package/src/api/v1/validator/index.ts +17 -3
- package/src/api/v1/validator/validations.ts +1 -0
- package/src/hooks/hooks.ts +103 -25
- package/src/index.ts +0 -5
- package/src/models/CustomValidator.ts +2 -0
- package/src/models/index.ts +16 -0
- package/src/repository/validator.ts +17 -9
- package/src/tests/helpers/database-config.ts +1 -1
|
@@ -80,8 +80,9 @@ router.get('/', async (req, res) => {
|
|
|
80
80
|
*/
|
|
81
81
|
router.get('/:validatorId', async (req, res) => {
|
|
82
82
|
try {
|
|
83
|
-
const { validatorId } = req.params;
|
|
84
|
-
|
|
83
|
+
const { validatorId, modelName } = req.params;
|
|
84
|
+
// Include disabled validators when fetching by ID
|
|
85
|
+
const validators = await ValidatorRepo.findAll({ id: validatorId, modelType: modelName }, { withDisabled: true });
|
|
85
86
|
if (!validators.length) {
|
|
86
87
|
throw new errors_1.ResourceNotFoundError('Validator not found');
|
|
87
88
|
}
|
|
@@ -103,6 +104,11 @@ router.patch('/:validatorId', async (req, res) => {
|
|
|
103
104
|
if (validatedPayload.schema) {
|
|
104
105
|
(0, validator_schema_1.validateValidatorSchema)(validatedPayload.schema);
|
|
105
106
|
}
|
|
107
|
+
// First verify the validator exists, including disabled ones
|
|
108
|
+
const existingValidators = await ValidatorRepo.findAll({ id: validatorId }, { withDisabled: true });
|
|
109
|
+
if (!existingValidators.length) {
|
|
110
|
+
throw new errors_1.ResourceNotFoundError('Validator not found');
|
|
111
|
+
}
|
|
106
112
|
const [count, validators] = await ValidatorRepo.update(validatorId, validatedPayload);
|
|
107
113
|
if (!count) {
|
|
108
114
|
throw new errors_1.ResourceNotFoundError('Validator not found');
|
|
@@ -119,9 +125,14 @@ router.patch('/:validatorId', async (req, res) => {
|
|
|
119
125
|
router.delete('/:validatorId', async (req, res) => {
|
|
120
126
|
try {
|
|
121
127
|
const { validatorId } = req.params;
|
|
128
|
+
// First verify the validator exists, including disabled ones
|
|
129
|
+
const existingValidators = await ValidatorRepo.findAll({ id: validatorId }, { withDisabled: true });
|
|
130
|
+
if (!existingValidators.length) {
|
|
131
|
+
throw new errors_1.ResourceNotFoundError('Validator not found');
|
|
132
|
+
}
|
|
122
133
|
const [count] = await ValidatorRepo.disable(validatorId);
|
|
123
134
|
if (!count) {
|
|
124
|
-
throw new errors_1.ResourceNotFoundError('Validator
|
|
135
|
+
throw new errors_1.ResourceNotFoundError('Validator failed to be disabled');
|
|
125
136
|
}
|
|
126
137
|
return res.status(http_status_codes_1.StatusCodes.NO_CONTENT).send();
|
|
127
138
|
}
|
|
@@ -23,6 +23,7 @@ const validationSchemas = {
|
|
|
23
23
|
}),
|
|
24
24
|
update: joi_1.default.object({
|
|
25
25
|
entityId: joi_1.default.string().uuid(),
|
|
26
|
+
entityType: joi_1.default.string(),
|
|
26
27
|
schema: joi_1.default.object({
|
|
27
28
|
type: joi_1.default.string().valid('object'),
|
|
28
29
|
properties: joi_1.default.object({
|
package/dist/hooks/hooks.js
CHANGED
|
@@ -44,6 +44,60 @@ const ajv = new ajv_1.default({
|
|
|
44
44
|
$data: true, // Enable $data references
|
|
45
45
|
});
|
|
46
46
|
(0, ajv_formats_1.default)(ajv);
|
|
47
|
+
/**
|
|
48
|
+
* Helper function to manually copy object properties
|
|
49
|
+
* This is more efficient for large objects and avoids excessive object creation
|
|
50
|
+
*/
|
|
51
|
+
const manualObjectCopy = (sourceObj, additionalProps) => {
|
|
52
|
+
const resultObj = Object.create(null);
|
|
53
|
+
// Copy properties from source object
|
|
54
|
+
if (sourceObj) {
|
|
55
|
+
Object.keys(sourceObj).forEach((key) => {
|
|
56
|
+
resultObj[key] = sourceObj[key];
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Add additional properties if provided
|
|
60
|
+
if (additionalProps) {
|
|
61
|
+
Object.keys(additionalProps).forEach((key) => {
|
|
62
|
+
resultObj[key] = additionalProps[key];
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return resultObj;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Fetches complete custom fields for an instance by merging DB values with update values
|
|
69
|
+
* This is needed for partial updates to ensure all related fields are available for validation
|
|
70
|
+
*/
|
|
71
|
+
const getCompleteCustomFields = async (instance, options) => {
|
|
72
|
+
// If we don't have an instance id or no custom fields being updated, return original fields
|
|
73
|
+
if (!instance.id || !instance.customFields || Object.keys(instance.customFields).length === 0) {
|
|
74
|
+
return instance.customFields || {};
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const ModelClass = instance.constructor;
|
|
78
|
+
// Only select the customFields column to minimize data transfer
|
|
79
|
+
const currentCustomFields = await ModelClass.findOne({
|
|
80
|
+
where: { id: instance.id },
|
|
81
|
+
attributes: ['customFields'],
|
|
82
|
+
transaction: options.transaction,
|
|
83
|
+
raw: true, // Get plain object instead of model instance for better performance
|
|
84
|
+
});
|
|
85
|
+
if (currentCustomFields?.customFields) {
|
|
86
|
+
// Merge existing fields with update fields using our helper function
|
|
87
|
+
const completeFields = manualObjectCopy(currentCustomFields.customFields, instance.customFields);
|
|
88
|
+
logger_1.default.debug('sadot - fetched complete custom fields for validation', {
|
|
89
|
+
fieldsCount: Object.keys(completeFields).length,
|
|
90
|
+
updateFieldsCount: Object.keys(instance.customFields).length,
|
|
91
|
+
});
|
|
92
|
+
return completeFields;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
logger_1.default.error('sadot - error fetching complete model for validation', { error });
|
|
97
|
+
// Continue with partial data if we can't fetch the complete model
|
|
98
|
+
}
|
|
99
|
+
return instance.customFields || {};
|
|
100
|
+
};
|
|
47
101
|
/**
|
|
48
102
|
* Validates the model using custom validators
|
|
49
103
|
*/
|
|
@@ -71,21 +125,26 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
|
|
|
71
125
|
return;
|
|
72
126
|
}
|
|
73
127
|
// For updates, get the previous values
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
};
|
|
128
|
+
let originalValues = null;
|
|
129
|
+
if (!isCreate) {
|
|
130
|
+
// Create originalValues with our helper function
|
|
131
|
+
originalValues = manualObjectCopy(instance.previous());
|
|
132
|
+
// Add customFields separately
|
|
133
|
+
originalValues.customFields = instance.previous('customFields') || {};
|
|
134
|
+
}
|
|
135
|
+
// Get complete custom fields by merging DB values with update values
|
|
136
|
+
// This is especially important for partial updates to ensure all related fields are available
|
|
137
|
+
const completeCustomFields = !isCreate
|
|
138
|
+
? await getCompleteCustomFields(instance, options)
|
|
139
|
+
: instance.customFields || {};
|
|
80
140
|
// For debugging in case of update
|
|
81
141
|
if (!isCreate) {
|
|
142
|
+
// Create after object for logging
|
|
143
|
+
const logAfterObj = manualObjectCopy(instance.dataValues, { customFields: completeCustomFields });
|
|
82
144
|
logger_1.default.debug('sadot - validate with values', {
|
|
83
145
|
before: originalValues,
|
|
84
|
-
after:
|
|
85
|
-
|
|
86
|
-
...instance.customFields,
|
|
87
|
-
},
|
|
88
|
-
schema: JSON.stringify(validators[0].schema),
|
|
146
|
+
after: logAfterObj,
|
|
147
|
+
schema: validators[0].schema,
|
|
89
148
|
});
|
|
90
149
|
}
|
|
91
150
|
// eslint-disable-next-line no-restricted-syntax
|
|
@@ -93,7 +152,7 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
|
|
|
93
152
|
const { schema } = validator;
|
|
94
153
|
const typedSchema = schema;
|
|
95
154
|
logger_1.default.debug('sadot - validating with schema', {
|
|
96
|
-
schema
|
|
155
|
+
schema,
|
|
97
156
|
hasAfterProps: !!typedSchema.properties?.after,
|
|
98
157
|
hasBeforeProps: !!typedSchema.properties?.before,
|
|
99
158
|
});
|
|
@@ -107,12 +166,12 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
|
|
|
107
166
|
after: typedSchema.properties.after,
|
|
108
167
|
},
|
|
109
168
|
});
|
|
110
|
-
const isValid = validateSchema(JSON.stringify({
|
|
169
|
+
const isValid = validateSchema(JSON.parse(JSON.stringify({
|
|
111
170
|
after: {
|
|
112
171
|
...instance.dataValues,
|
|
113
|
-
customFields:
|
|
172
|
+
customFields: completeCustomFields,
|
|
114
173
|
},
|
|
115
|
-
}));
|
|
174
|
+
})));
|
|
116
175
|
if (!isValid) {
|
|
117
176
|
const errorDetails = validateSchema.errors?.map((err) => `${err.instancePath || ''} ${err.message || 'Invalid value'}`).join(', ');
|
|
118
177
|
throw new errors_1.BadRequest([new Error(`Validation failed for ${modelType}: ${errorDetails}`)]);
|
|
@@ -122,21 +181,22 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
|
|
|
122
181
|
else {
|
|
123
182
|
// For update operations, we need both before and after
|
|
124
183
|
const validateSchema = ajv.compile(typedSchema);
|
|
125
|
-
|
|
184
|
+
// Create after object with our helper function
|
|
185
|
+
const afterObj = manualObjectCopy(instance.dataValues);
|
|
186
|
+
// Add complete custom fields
|
|
187
|
+
afterObj.customFields = completeCustomFields;
|
|
188
|
+
// Create validation payload
|
|
189
|
+
const payload = {
|
|
126
190
|
before: originalValues,
|
|
127
|
-
after:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}));
|
|
191
|
+
after: afterObj,
|
|
192
|
+
};
|
|
193
|
+
// Validate
|
|
194
|
+
const isValid = validateSchema(JSON.parse(JSON.stringify(payload)));
|
|
132
195
|
logger_1.default.info('sadot - validation result', {
|
|
133
196
|
isValid,
|
|
134
197
|
test: {
|
|
135
198
|
before: originalValues,
|
|
136
|
-
after:
|
|
137
|
-
...instance.dataValues,
|
|
138
|
-
...instance.customFields,
|
|
139
|
-
},
|
|
199
|
+
after: afterObj,
|
|
140
200
|
},
|
|
141
201
|
});
|
|
142
202
|
if (!isValid) {
|
package/dist/index.d.ts
CHANGED
|
@@ -4,9 +4,6 @@ import type { CustomFieldOptions, ModelFetcher, Models } from './types';
|
|
|
4
4
|
export * from './utils/validations/schema/custom-fields';
|
|
5
5
|
export * from './utils/constants';
|
|
6
6
|
export * from './utils/helpers';
|
|
7
|
-
export * as DefinitionRepo from './repository/definition';
|
|
8
|
-
export * as ValueRepo from './repository/value';
|
|
9
|
-
export * as ValidatorRepo from './repository/validator';
|
|
10
7
|
/**
|
|
11
8
|
* Adding custom fields enrichment to the models inside the MODELS_FILE_NAME json file
|
|
12
9
|
* @see {@link 'custom-fields/config'} for configurations
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
29
29
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
30
30
|
};
|
|
31
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
-
exports.disableCustomFields =
|
|
32
|
+
exports.disableCustomFields = void 0;
|
|
33
33
|
const models_1 = require("./models");
|
|
34
34
|
const api_1 = __importDefault(require("./api"));
|
|
35
35
|
const db_1 = __importDefault(require("./utils/db"));
|
|
@@ -38,10 +38,6 @@ const init_1 = require("./utils/init");
|
|
|
38
38
|
__exportStar(require("./utils/validations/schema/custom-fields"), exports);
|
|
39
39
|
__exportStar(require("./utils/constants"), exports);
|
|
40
40
|
__exportStar(require("./utils/helpers"), exports);
|
|
41
|
-
// Export repositories
|
|
42
|
-
exports.DefinitionRepo = __importStar(require("./repository/definition"));
|
|
43
|
-
exports.ValueRepo = __importStar(require("./repository/value"));
|
|
44
|
-
exports.ValidatorRepo = __importStar(require("./repository/validator"));
|
|
45
41
|
/**
|
|
46
42
|
* Adding custom fields enrichment to the models inside the MODELS_FILE_NAME json file
|
|
47
43
|
* @see {@link 'custom-fields/config'} for configurations
|
|
@@ -90,6 +90,7 @@ __decorate([
|
|
|
90
90
|
__metadata("design:returntype", void 0)
|
|
91
91
|
], CustomValidator, "afterSaveHandler", null);
|
|
92
92
|
CustomValidator = __decorate([
|
|
93
|
+
(0, sequelize_typescript_1.DefaultScope)(() => ({ where: { disabled: false } })),
|
|
93
94
|
(0, sequelize_typescript_1.Table)({
|
|
94
95
|
timestamps: true,
|
|
95
96
|
})
|
package/dist/models/index.js
CHANGED
|
@@ -58,6 +58,21 @@ const initTables = async (sequelize, getUser, { schemaPrefix, schemaVersion, use
|
|
|
58
58
|
},
|
|
59
59
|
};
|
|
60
60
|
});
|
|
61
|
+
CustomValidator_1.default.addScope('userScope', () => {
|
|
62
|
+
const user = getUser();
|
|
63
|
+
if (!user?.permissions) {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
where: {
|
|
68
|
+
entityId: [
|
|
69
|
+
...Object.keys(user.permissions.fleets),
|
|
70
|
+
...Object.keys(user.permissions.businessModels),
|
|
71
|
+
...Object.keys(user.permissions.demandSources),
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
});
|
|
61
76
|
logger_1.default.info('custom-fields: models added');
|
|
62
77
|
const SequelizeMeta = sequelize.define('SequelizeMeta', {
|
|
63
78
|
name: {
|
|
@@ -16,13 +16,23 @@ exports.create = create;
|
|
|
16
16
|
const findAll = async (where = {}, options = { withDisabled: false }) => {
|
|
17
17
|
logger_1.default.debug('custom-validator - find all validators');
|
|
18
18
|
const { transaction, withDisabled } = options;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
let validators;
|
|
20
|
+
if (withDisabled) {
|
|
21
|
+
// If withDisabled is true, use unscoped to ignore the default scope that filters disabled items
|
|
22
|
+
// Apply the userScope separately to maintain permission filtering
|
|
23
|
+
validators = await models_1.CustomValidator.unscoped().scope('userScope').findAll({
|
|
24
|
+
where,
|
|
25
|
+
transaction,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Use defaultScope and userScope to filter both disabled and by permissions
|
|
30
|
+
// The defaultScope keeps only non-disabled validators
|
|
31
|
+
validators = await models_1.CustomValidator.scope(['defaultScope', 'userScope']).findAll({
|
|
32
|
+
where,
|
|
33
|
+
transaction,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
26
36
|
return validators;
|
|
27
37
|
};
|
|
28
38
|
exports.findAll = findAll;
|
|
@@ -4,7 +4,7 @@ exports.default = {
|
|
|
4
4
|
test: {
|
|
5
5
|
username: process.env.DB_USERNAME || '',
|
|
6
6
|
password: process.env.DB_PASSWORD || null,
|
|
7
|
-
database: process.env.DB_NAME || '
|
|
7
|
+
database: process.env.DB_NAME || 'postgres',
|
|
8
8
|
host: process.env.DB_HOST || '127.0.0.1',
|
|
9
9
|
port: process.env.DB_PORT || 5432,
|
|
10
10
|
dialect: process.env.DB_TYPE || 'postgres',
|
package/package.json
CHANGED
|
@@ -66,8 +66,9 @@ router.get<
|
|
|
66
66
|
*/
|
|
67
67
|
router.get<{ modelName: string; validatorId: string }, CustomValidator>('/:validatorId', async (req, res) => {
|
|
68
68
|
try {
|
|
69
|
-
const { validatorId } = req.params;
|
|
70
|
-
|
|
69
|
+
const { validatorId, modelName } = req.params;
|
|
70
|
+
// Include disabled validators when fetching by ID
|
|
71
|
+
const validators = await ValidatorRepo.findAll({ id: validatorId, modelType: modelName }, { withDisabled: true });
|
|
71
72
|
|
|
72
73
|
if (!validators.length) {
|
|
73
74
|
throw new ResourceNotFoundError('Validator not found');
|
|
@@ -94,6 +95,12 @@ router.patch<{ modelName: string; validatorId: string }, CustomValidator>('/:val
|
|
|
94
95
|
validateValidatorSchema(validatedPayload.schema);
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
// First verify the validator exists, including disabled ones
|
|
99
|
+
const existingValidators = await ValidatorRepo.findAll({ id: validatorId }, { withDisabled: true });
|
|
100
|
+
if (!existingValidators.length) {
|
|
101
|
+
throw new ResourceNotFoundError('Validator not found');
|
|
102
|
+
}
|
|
103
|
+
|
|
97
104
|
const [count, validators] = await ValidatorRepo.update(validatorId, validatedPayload);
|
|
98
105
|
|
|
99
106
|
if (!count) {
|
|
@@ -112,10 +119,17 @@ router.patch<{ modelName: string; validatorId: string }, CustomValidator>('/:val
|
|
|
112
119
|
router.delete<{ modelName: string; validatorId: string }>('/:validatorId', async (req, res) => {
|
|
113
120
|
try {
|
|
114
121
|
const { validatorId } = req.params;
|
|
122
|
+
|
|
123
|
+
// First verify the validator exists, including disabled ones
|
|
124
|
+
const existingValidators = await ValidatorRepo.findAll({ id: validatorId }, { withDisabled: true });
|
|
125
|
+
if (!existingValidators.length) {
|
|
126
|
+
throw new ResourceNotFoundError('Validator not found');
|
|
127
|
+
}
|
|
128
|
+
|
|
115
129
|
const [count] = await ValidatorRepo.disable(validatorId);
|
|
116
130
|
|
|
117
131
|
if (!count) {
|
|
118
|
-
throw new ResourceNotFoundError('Validator
|
|
132
|
+
throw new ResourceNotFoundError('Validator failed to be disabled');
|
|
119
133
|
}
|
|
120
134
|
|
|
121
135
|
return res.status(StatusCodes.NO_CONTENT).send();
|
package/src/hooks/hooks.ts
CHANGED
|
@@ -20,6 +20,72 @@ const ajv = new Ajv({
|
|
|
20
20
|
|
|
21
21
|
addFormats(ajv);
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Helper function to manually copy object properties
|
|
25
|
+
* This is more efficient for large objects and avoids excessive object creation
|
|
26
|
+
*/
|
|
27
|
+
const manualObjectCopy = (sourceObj: Record<string, any>, additionalProps?: Record<string, any>): Record<string, any> => {
|
|
28
|
+
const resultObj = Object.create(null);
|
|
29
|
+
|
|
30
|
+
// Copy properties from source object
|
|
31
|
+
if (sourceObj) {
|
|
32
|
+
Object.keys(sourceObj).forEach((key) => {
|
|
33
|
+
resultObj[key] = sourceObj[key];
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Add additional properties if provided
|
|
38
|
+
if (additionalProps) {
|
|
39
|
+
Object.keys(additionalProps).forEach((key) => {
|
|
40
|
+
resultObj[key] = additionalProps[key];
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return resultObj;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Fetches complete custom fields for an instance by merging DB values with update values
|
|
49
|
+
* This is needed for partial updates to ensure all related fields are available for validation
|
|
50
|
+
*/
|
|
51
|
+
const getCompleteCustomFields = async (instance, options): Promise<Record<string, any>> => {
|
|
52
|
+
// If we don't have an instance id or no custom fields being updated, return original fields
|
|
53
|
+
if (!instance.id || !instance.customFields || Object.keys(instance.customFields).length === 0) {
|
|
54
|
+
return instance.customFields || {};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const ModelClass = instance.constructor;
|
|
59
|
+
// Only select the customFields column to minimize data transfer
|
|
60
|
+
const currentCustomFields = await ModelClass.findOne({
|
|
61
|
+
where: { id: instance.id },
|
|
62
|
+
attributes: ['customFields'],
|
|
63
|
+
transaction: options.transaction,
|
|
64
|
+
raw: true, // Get plain object instead of model instance for better performance
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (currentCustomFields?.customFields) {
|
|
68
|
+
// Merge existing fields with update fields using our helper function
|
|
69
|
+
const completeFields = manualObjectCopy(
|
|
70
|
+
currentCustomFields.customFields,
|
|
71
|
+
instance.customFields,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
logger.debug('sadot - fetched complete custom fields for validation', {
|
|
75
|
+
fieldsCount: Object.keys(completeFields).length,
|
|
76
|
+
updateFieldsCount: Object.keys(instance.customFields).length,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return completeFields;
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger.error('sadot - error fetching complete model for validation', { error });
|
|
83
|
+
// Continue with partial data if we can't fetch the complete model
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return instance.customFields || {};
|
|
87
|
+
};
|
|
88
|
+
|
|
23
89
|
/**
|
|
24
90
|
* Validates the model using custom validators
|
|
25
91
|
*/
|
|
@@ -66,22 +132,30 @@ const validateModel = async (
|
|
|
66
132
|
}
|
|
67
133
|
|
|
68
134
|
// For updates, get the previous values
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
135
|
+
let originalValues = null;
|
|
136
|
+
if (!isCreate) {
|
|
137
|
+
// Create originalValues with our helper function
|
|
138
|
+
originalValues = manualObjectCopy(instance.previous());
|
|
139
|
+
|
|
140
|
+
// Add customFields separately
|
|
141
|
+
originalValues.customFields = instance.previous('customFields') || {};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Get complete custom fields by merging DB values with update values
|
|
145
|
+
// This is especially important for partial updates to ensure all related fields are available
|
|
146
|
+
const completeCustomFields = !isCreate
|
|
147
|
+
? await getCompleteCustomFields(instance, options)
|
|
148
|
+
: instance.customFields || {};
|
|
75
149
|
|
|
76
150
|
// For debugging in case of update
|
|
77
151
|
if (!isCreate) {
|
|
152
|
+
// Create after object for logging
|
|
153
|
+
const logAfterObj = manualObjectCopy(instance.dataValues, { customFields: completeCustomFields });
|
|
154
|
+
|
|
78
155
|
logger.debug('sadot - validate with values', {
|
|
79
156
|
before: originalValues,
|
|
80
|
-
after:
|
|
81
|
-
|
|
82
|
-
...instance.customFields,
|
|
83
|
-
},
|
|
84
|
-
schema: JSON.stringify(validators[0].schema),
|
|
157
|
+
after: logAfterObj,
|
|
158
|
+
schema: validators[0].schema,
|
|
85
159
|
});
|
|
86
160
|
}
|
|
87
161
|
|
|
@@ -91,7 +165,7 @@ const validateModel = async (
|
|
|
91
165
|
const typedSchema = schema as Record<string, any>;
|
|
92
166
|
|
|
93
167
|
logger.debug('sadot - validating with schema', {
|
|
94
|
-
schema
|
|
168
|
+
schema,
|
|
95
169
|
hasAfterProps: !!typedSchema.properties?.after,
|
|
96
170
|
hasBeforeProps: !!typedSchema.properties?.before,
|
|
97
171
|
});
|
|
@@ -107,12 +181,12 @@ const validateModel = async (
|
|
|
107
181
|
},
|
|
108
182
|
});
|
|
109
183
|
|
|
110
|
-
const isValid = validateSchema(JSON.stringify({
|
|
184
|
+
const isValid = validateSchema(JSON.parse(JSON.stringify({
|
|
111
185
|
after: {
|
|
112
186
|
...instance.dataValues,
|
|
113
|
-
customFields:
|
|
187
|
+
customFields: completeCustomFields,
|
|
114
188
|
},
|
|
115
|
-
}));
|
|
189
|
+
})));
|
|
116
190
|
|
|
117
191
|
if (!isValid) {
|
|
118
192
|
const errorDetails = validateSchema.errors?.map((err) =>
|
|
@@ -125,22 +199,26 @@ const validateModel = async (
|
|
|
125
199
|
// For update operations, we need both before and after
|
|
126
200
|
const validateSchema = ajv.compile(typedSchema);
|
|
127
201
|
|
|
128
|
-
|
|
202
|
+
// Create after object with our helper function
|
|
203
|
+
const afterObj = manualObjectCopy(instance.dataValues);
|
|
204
|
+
|
|
205
|
+
// Add complete custom fields
|
|
206
|
+
afterObj.customFields = completeCustomFields;
|
|
207
|
+
|
|
208
|
+
// Create validation payload
|
|
209
|
+
const payload = {
|
|
129
210
|
before: originalValues,
|
|
130
|
-
after:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
211
|
+
after: afterObj,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Validate
|
|
215
|
+
const isValid = validateSchema(JSON.parse(JSON.stringify(payload)));
|
|
135
216
|
|
|
136
217
|
logger.info('sadot - validation result', {
|
|
137
218
|
isValid,
|
|
138
219
|
test: {
|
|
139
220
|
before: originalValues,
|
|
140
|
-
after:
|
|
141
|
-
...instance.dataValues,
|
|
142
|
-
...instance.customFields,
|
|
143
|
-
},
|
|
221
|
+
after: afterObj,
|
|
144
222
|
},
|
|
145
223
|
});
|
|
146
224
|
|
package/src/index.ts
CHANGED
|
@@ -17,11 +17,6 @@ export * from './utils/constants';
|
|
|
17
17
|
|
|
18
18
|
export * from './utils/helpers';
|
|
19
19
|
|
|
20
|
-
// Export repositories
|
|
21
|
-
export * as DefinitionRepo from './repository/definition';
|
|
22
|
-
export * as ValueRepo from './repository/value';
|
|
23
|
-
export * as ValidatorRepo from './repository/validator';
|
|
24
|
-
|
|
25
20
|
/**
|
|
26
21
|
* Adding custom fields enrichment to the models inside the MODELS_FILE_NAME json file
|
|
27
22
|
* @see {@link 'custom-fields/config'} for configurations
|
|
@@ -6,10 +6,12 @@ import {
|
|
|
6
6
|
DataType,
|
|
7
7
|
Default,
|
|
8
8
|
AfterUpsert,
|
|
9
|
+
DefaultScope,
|
|
9
10
|
} from 'sequelize-typescript';
|
|
10
11
|
import { randomUUID } from 'node:crypto';
|
|
11
12
|
import { sendDimEvent } from '../events';
|
|
12
13
|
|
|
14
|
+
@DefaultScope(() => ({ where: { disabled: false } }))
|
|
13
15
|
@Table({
|
|
14
16
|
timestamps: true,
|
|
15
17
|
})
|
package/src/models/index.ts
CHANGED
|
@@ -68,6 +68,22 @@ const initTables = async (
|
|
|
68
68
|
};
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
CustomValidator.addScope('userScope', () => {
|
|
72
|
+
const user = getUser();
|
|
73
|
+
if (!user?.permissions) {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
where: {
|
|
78
|
+
entityId: [
|
|
79
|
+
...Object.keys(user.permissions.fleets),
|
|
80
|
+
...Object.keys(user.permissions.businessModels),
|
|
81
|
+
...Object.keys(user.permissions.demandSources),
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
|
|
71
87
|
logger.info('custom-fields: models added');
|
|
72
88
|
|
|
73
89
|
const SequelizeMeta = sequelize.define(
|
|
@@ -11,7 +11,7 @@ export interface ValidatorAttributes {
|
|
|
11
11
|
entityId: string;
|
|
12
12
|
entityType: string;
|
|
13
13
|
modelType: string;
|
|
14
|
-
schema:
|
|
14
|
+
schema: CustomValidator['schema'];
|
|
15
15
|
disabled?: boolean;
|
|
16
16
|
[key: string]: unknown; // Add index signature for Sequelize compatibility
|
|
17
17
|
}
|
|
@@ -36,14 +36,22 @@ export const findAll = async (
|
|
|
36
36
|
|
|
37
37
|
const { transaction, withDisabled } = options;
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
let validators;
|
|
40
|
+
if (withDisabled) {
|
|
41
|
+
// If withDisabled is true, use unscoped to ignore the default scope that filters disabled items
|
|
42
|
+
// Apply the userScope separately to maintain permission filtering
|
|
43
|
+
validators = await CustomValidator.unscoped().scope('userScope').findAll({
|
|
44
|
+
where,
|
|
45
|
+
transaction,
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
// Use defaultScope and userScope to filter both disabled and by permissions
|
|
49
|
+
// The defaultScope keeps only non-disabled validators
|
|
50
|
+
validators = await CustomValidator.scope(['defaultScope', 'userScope']).findAll({
|
|
51
|
+
where,
|
|
52
|
+
transaction,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
47
55
|
|
|
48
56
|
return validators;
|
|
49
57
|
};
|
|
@@ -2,7 +2,7 @@ export default {
|
|
|
2
2
|
test: {
|
|
3
3
|
username: process.env.DB_USERNAME || '',
|
|
4
4
|
password: process.env.DB_PASSWORD || null,
|
|
5
|
-
database: process.env.DB_NAME || '
|
|
5
|
+
database: process.env.DB_NAME || 'postgres',
|
|
6
6
|
host: process.env.DB_HOST || '127.0.0.1',
|
|
7
7
|
port: process.env.DB_PORT || 5432,
|
|
8
8
|
dialect: process.env.DB_TYPE || 'postgres',
|