@autofleet/sadot 0.13.0-beta.9 → 0.13.2-beta.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/validator/index.js +14 -3
- package/dist/api/v1/validator/validations.js +1 -0
- package/dist/hooks/hooks.js +112 -37
- 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 +2 -1
- package/src/api/v1/validator/index.ts +17 -3
- package/src/api/v1/validator/validations.ts +1 -0
- package/src/hooks/hooks.ts +154 -43
- 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
- package/validator-test.js +0 -79
|
@@ -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
|
@@ -36,6 +36,7 @@ const DefinitionRepo = __importStar(require("../repository/definition"));
|
|
|
36
36
|
const errors_2 = require("../errors");
|
|
37
37
|
const scopeAttributes_1 = __importDefault(require("../utils/scopeAttributes"));
|
|
38
38
|
const updateInstanceValues_1 = __importDefault(require("./utils/updateInstanceValues"));
|
|
39
|
+
const constants_1 = require("../utils/constants");
|
|
39
40
|
// Initialize Ajv with relaxed settings to avoid warnings
|
|
40
41
|
const ajv = new ajv_1.default({
|
|
41
42
|
allErrors: true,
|
|
@@ -44,6 +45,46 @@ const ajv = new ajv_1.default({
|
|
|
44
45
|
$data: true, // Enable $data references
|
|
45
46
|
});
|
|
46
47
|
(0, ajv_formats_1.default)(ajv);
|
|
48
|
+
/**
|
|
49
|
+
* Helper function to manually copy object properties
|
|
50
|
+
* This is more efficient for large objects and avoids excessive object creation
|
|
51
|
+
*/
|
|
52
|
+
// eslint-disable-next-line prefer-object-spread
|
|
53
|
+
const manualObjectCopy = (sourceObj, additionalProps) => ({ __proto__: null, ...sourceObj, ...additionalProps });
|
|
54
|
+
/**
|
|
55
|
+
* Fetches complete custom fields for an instance by merging DB values with update values
|
|
56
|
+
* This is needed for partial updates to ensure all related fields are available for validation
|
|
57
|
+
*/
|
|
58
|
+
const getCompleteCustomFields = async (instance, options) => {
|
|
59
|
+
// If we don't have an instance id or no custom fields being updated, return original fields
|
|
60
|
+
if (!instance.id || !instance.customFields || Object.keys(instance.customFields).length === 0) {
|
|
61
|
+
return instance.customFields || {};
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const ModelClass = instance.constructor;
|
|
65
|
+
// Only select the customFields column to minimize data transfer
|
|
66
|
+
const currentCustomFields = await ModelClass.findOne({
|
|
67
|
+
where: { id: instance.id },
|
|
68
|
+
attributes: ['customFields'],
|
|
69
|
+
transaction: options.transaction,
|
|
70
|
+
raw: true, // Get plain object instead of model instance for better performance
|
|
71
|
+
});
|
|
72
|
+
if (currentCustomFields?.customFields) {
|
|
73
|
+
// Merge existing fields with update fields using our helper function
|
|
74
|
+
const completeFields = manualObjectCopy(currentCustomFields.customFields, instance.customFields);
|
|
75
|
+
logger_1.default.debug('sadot - fetched complete custom fields for validation', {
|
|
76
|
+
fieldsCount: Object.keys(completeFields).length,
|
|
77
|
+
updateFieldsCount: Object.keys(instance.customFields).length,
|
|
78
|
+
});
|
|
79
|
+
return completeFields;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
logger_1.default.error('sadot - error fetching complete model for validation', { error });
|
|
84
|
+
// Continue with partial data if we can't fetch the complete model
|
|
85
|
+
}
|
|
86
|
+
return instance.customFields || {};
|
|
87
|
+
};
|
|
47
88
|
/**
|
|
48
89
|
* Validates the model using custom validators
|
|
49
90
|
*/
|
|
@@ -71,21 +112,26 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
|
|
|
71
112
|
return;
|
|
72
113
|
}
|
|
73
114
|
// For updates, get the previous values
|
|
74
|
-
|
|
75
|
-
? null
|
|
76
|
-
: {
|
|
77
|
-
...instance.previous(),
|
|
78
|
-
customFields: instance.previous('customFields') || {},
|
|
79
|
-
};
|
|
80
|
-
// For debugging in case of update
|
|
115
|
+
let originalValues = null;
|
|
81
116
|
if (!isCreate) {
|
|
117
|
+
// Create originalValues with our helper function
|
|
118
|
+
originalValues = manualObjectCopy(instance.previous());
|
|
119
|
+
// Add customFields separately
|
|
120
|
+
originalValues.customFields = instance.previous('customFields') || {};
|
|
121
|
+
}
|
|
122
|
+
// Get complete custom fields by merging DB values with update values
|
|
123
|
+
// This is especially important for partial updates to ensure all related fields are available
|
|
124
|
+
const completeCustomFields = !isCreate
|
|
125
|
+
? await getCompleteCustomFields(instance, options)
|
|
126
|
+
: instance.customFields || {};
|
|
127
|
+
// For debugging in case of update
|
|
128
|
+
if (!isCreate && process.env.NODE_ENV !== 'production') {
|
|
129
|
+
// Create after object for logging
|
|
130
|
+
const logAfterObj = manualObjectCopy(instance.dataValues, { customFields: completeCustomFields });
|
|
82
131
|
logger_1.default.debug('sadot - validate with values', {
|
|
83
132
|
before: originalValues,
|
|
84
|
-
after:
|
|
85
|
-
|
|
86
|
-
...instance.customFields,
|
|
87
|
-
},
|
|
88
|
-
schema: JSON.stringify(validators[0].schema),
|
|
133
|
+
after: logAfterObj,
|
|
134
|
+
schema: validators[0].schema,
|
|
89
135
|
});
|
|
90
136
|
}
|
|
91
137
|
// eslint-disable-next-line no-restricted-syntax
|
|
@@ -93,7 +139,7 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
|
|
|
93
139
|
const { schema } = validator;
|
|
94
140
|
const typedSchema = schema;
|
|
95
141
|
logger_1.default.debug('sadot - validating with schema', {
|
|
96
|
-
schema
|
|
142
|
+
schema,
|
|
97
143
|
hasAfterProps: !!typedSchema.properties?.after,
|
|
98
144
|
hasBeforeProps: !!typedSchema.properties?.before,
|
|
99
145
|
});
|
|
@@ -107,12 +153,12 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
|
|
|
107
153
|
after: typedSchema.properties.after,
|
|
108
154
|
},
|
|
109
155
|
});
|
|
110
|
-
const isValid = validateSchema({
|
|
156
|
+
const isValid = validateSchema(JSON.parse(JSON.stringify({
|
|
111
157
|
after: {
|
|
112
158
|
...instance.dataValues,
|
|
113
|
-
customFields:
|
|
159
|
+
customFields: completeCustomFields,
|
|
114
160
|
},
|
|
115
|
-
});
|
|
161
|
+
})));
|
|
116
162
|
if (!isValid) {
|
|
117
163
|
const errorDetails = validateSchema.errors?.map((err) => `${err.instancePath || ''} ${err.message || 'Invalid value'}`).join(', ');
|
|
118
164
|
throw new errors_1.BadRequest([new Error(`Validation failed for ${modelType}: ${errorDetails}`)]);
|
|
@@ -122,40 +168,36 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
|
|
|
122
168
|
else {
|
|
123
169
|
// For update operations, we need both before and after
|
|
124
170
|
const validateSchema = ajv.compile(typedSchema);
|
|
125
|
-
|
|
171
|
+
// Create after object with our helper function
|
|
172
|
+
const afterObj = manualObjectCopy(instance.dataValues);
|
|
173
|
+
// Add complete custom fields
|
|
174
|
+
afterObj.customFields = completeCustomFields;
|
|
175
|
+
// Create validation payload
|
|
176
|
+
const payload = {
|
|
126
177
|
before: originalValues,
|
|
127
|
-
after:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
178
|
+
after: afterObj,
|
|
179
|
+
};
|
|
180
|
+
// Validate
|
|
181
|
+
const isValid = validateSchema(JSON.parse(JSON.stringify(payload)));
|
|
132
182
|
logger_1.default.info('sadot - validation result', {
|
|
133
183
|
isValid,
|
|
134
184
|
test: {
|
|
135
185
|
before: originalValues,
|
|
136
|
-
after:
|
|
137
|
-
...instance.dataValues,
|
|
138
|
-
...instance.customFields,
|
|
139
|
-
},
|
|
186
|
+
after: afterObj,
|
|
140
187
|
},
|
|
141
188
|
});
|
|
142
189
|
if (!isValid) {
|
|
143
|
-
const errorDetails = validateSchema.errors?.map((err) =>
|
|
190
|
+
const errorDetails = validateSchema.errors?.map((err) => {
|
|
191
|
+
logger_1.default.info('dor', err);
|
|
192
|
+
return `${err.instancePath || ''} ${err.message || 'Invalid value'}`;
|
|
193
|
+
}).join(', ');
|
|
144
194
|
throw new errors_1.BadRequest([new Error(`Validation failed for ${modelType}: ${errorDetails}`)]);
|
|
145
195
|
}
|
|
146
196
|
}
|
|
147
197
|
}
|
|
148
198
|
};
|
|
149
|
-
|
|
150
|
-
* Hook to handle validation and custom fields during creation
|
|
151
|
-
*/
|
|
152
|
-
const beforeCreate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCustomFieldsEntries: false }) => async (instance, options) => {
|
|
153
|
-
logger_1.default.debug('sadot - before create hook');
|
|
154
|
-
const { fields } = options;
|
|
199
|
+
const getFieldDefinitions = async ({ modelType, modelOptions, identifiers, options }) => {
|
|
155
200
|
const { include, useEntityIdFromInclude } = modelOptions;
|
|
156
|
-
const modelType = instance.constructor.name;
|
|
157
|
-
const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
|
|
158
|
-
// Step 1: Handle custom fields default values and required fields
|
|
159
201
|
const where = {
|
|
160
202
|
modelType,
|
|
161
203
|
disabled: false,
|
|
@@ -166,7 +208,32 @@ const beforeCreate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCu
|
|
|
166
208
|
transaction: options.transaction,
|
|
167
209
|
include: include?.(identifiers),
|
|
168
210
|
});
|
|
169
|
-
|
|
211
|
+
return fieldDefinitions;
|
|
212
|
+
};
|
|
213
|
+
const formatDates = (fieldDefinitions = [], instance) => {
|
|
214
|
+
fieldDefinitions.forEach((fieldDefinition) => {
|
|
215
|
+
const { fieldType, name } = fieldDefinition;
|
|
216
|
+
if ([constants_1.CustomFieldDefinitionType.DATE, constants_1.CustomFieldDefinitionType.DATETIME].includes(fieldType)) {
|
|
217
|
+
const value = instance.customFields?.[name];
|
|
218
|
+
if (value) {
|
|
219
|
+
// eslint-disable-next-line no-param-reassign
|
|
220
|
+
instance.customFields[name] = new Date(value).toISOString();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* Hook to handle validation and custom fields during creation
|
|
227
|
+
*/
|
|
228
|
+
const beforeCreate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCustomFieldsEntries: false }) => async (instance, options) => {
|
|
229
|
+
logger_1.default.debug('sadot - before create hook');
|
|
230
|
+
const { fields } = options;
|
|
231
|
+
const modelType = instance.constructor.name;
|
|
232
|
+
const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
|
|
233
|
+
// Step 1: Handle custom fields default values and required fields
|
|
234
|
+
const fieldDefinitions = await getFieldDefinitions({
|
|
235
|
+
modelType, modelOptions, identifiers, options
|
|
236
|
+
});
|
|
170
237
|
// Apply default values
|
|
171
238
|
const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
|
|
172
239
|
if (fieldsWithDefaultValue.length) {
|
|
@@ -180,6 +247,7 @@ const beforeCreate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCu
|
|
|
180
247
|
});
|
|
181
248
|
}
|
|
182
249
|
// Check for required fields
|
|
250
|
+
const requiredFieldsNames = Array.from(new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)));
|
|
183
251
|
const { customFields } = instance;
|
|
184
252
|
const fieldsNames = Object.keys(customFields ?? {});
|
|
185
253
|
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
@@ -188,6 +256,8 @@ const beforeCreate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCu
|
|
|
188
256
|
}
|
|
189
257
|
// Step 2: Validate the model data (including custom fields)
|
|
190
258
|
await validateModel(instance, options, scopeAttributes, true);
|
|
259
|
+
// format date and datetime fields
|
|
260
|
+
formatDates(fieldDefinitions, instance);
|
|
191
261
|
// Step 3: Save custom field values if they exist
|
|
192
262
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
193
263
|
if (customFieldsIdx === -1 || !customFields || !Object.keys(customFields).length) {
|
|
@@ -219,8 +289,13 @@ const beforeUpdate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCu
|
|
|
219
289
|
const { fields } = options;
|
|
220
290
|
const modelType = instance.constructor.name;
|
|
221
291
|
const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
|
|
292
|
+
const fieldDefinitions = await getFieldDefinitions({
|
|
293
|
+
modelType, modelOptions, identifiers, options
|
|
294
|
+
});
|
|
222
295
|
// Step 1: Validate the model data (including custom fields)
|
|
223
296
|
await validateModel(instance, options, scopeAttributes, false);
|
|
297
|
+
// format date and datetime fields
|
|
298
|
+
formatDates(fieldDefinitions, instance);
|
|
224
299
|
// Step 2: Update custom field values if they exist
|
|
225
300
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
226
301
|
if (customFieldsIdx > -1) {
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sadot",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"build": "rm -rf dist && tsc",
|
|
9
9
|
"linter": "eslint .",
|
|
10
10
|
"test": "jest --runInBand",
|
|
11
|
+
"test-debug": "node --inspect-brk node_modules/.bin/jest --testTimeout=10000000",
|
|
11
12
|
"coverage": "jest --coverage --runInBand",
|
|
12
13
|
"build-to-local-repo": "node --run build && cp -r dist/* ../$REPO/node_modules/$npm_package_name/dist",
|
|
13
14
|
"dev": "nodemon",
|
|
@@ -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
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { WhereOptions } from 'sequelize';
|
|
2
2
|
import Ajv from 'ajv';
|
|
3
|
+
import Joi from 'joi';
|
|
3
4
|
import addFormats from 'ajv-formats';
|
|
4
5
|
import { BadRequest } from '@autofleet/errors';
|
|
5
6
|
import logger from '../utils/logger';
|
|
6
7
|
import * as ValidatorRepo from '../repository/validator';
|
|
7
8
|
import * as DefinitionRepo from '../repository/definition';
|
|
8
|
-
import { MissingRequiredCustomFieldError } from '../errors';
|
|
9
|
+
import { InvalidValueError, MissingRequiredCustomFieldError } from '../errors';
|
|
9
10
|
import type { CustomFieldOptions, ModelOptions } from '../types';
|
|
10
11
|
import applyScopeToInstance from '../utils/scopeAttributes';
|
|
11
12
|
import updateInstanceValues from './utils/updateInstanceValues';
|
|
13
|
+
import { CustomFieldDefinitionType } from '../utils/constants';
|
|
14
|
+
import type { CustomFieldDefinition } from '../models';
|
|
12
15
|
|
|
13
16
|
// Initialize Ajv with relaxed settings to avoid warnings
|
|
14
17
|
const ajv = new Ajv({
|
|
@@ -20,6 +23,56 @@ const ajv = new Ajv({
|
|
|
20
23
|
|
|
21
24
|
addFormats(ajv);
|
|
22
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Helper function to manually copy object properties
|
|
28
|
+
* This is more efficient for large objects and avoids excessive object creation
|
|
29
|
+
*/
|
|
30
|
+
// eslint-disable-next-line prefer-object-spread
|
|
31
|
+
const manualObjectCopy = (sourceObj: Record<string, any>, additionalProps?: Record<string, any>): Record<string, any> =>
|
|
32
|
+
({ __proto__: null, ...sourceObj, ...additionalProps });
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Fetches complete custom fields for an instance by merging DB values with update values
|
|
36
|
+
* This is needed for partial updates to ensure all related fields are available for validation
|
|
37
|
+
*/
|
|
38
|
+
const getCompleteCustomFields = async (instance, options): Promise<Record<string, any>> => {
|
|
39
|
+
// If we don't have an instance id or no custom fields being updated, return original fields
|
|
40
|
+
if (!instance.id || !instance.customFields || Object.keys(instance.customFields).length === 0) {
|
|
41
|
+
return instance.customFields || {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const ModelClass = instance.constructor;
|
|
46
|
+
// Only select the customFields column to minimize data transfer
|
|
47
|
+
const currentCustomFields = await ModelClass.findOne({
|
|
48
|
+
where: { id: instance.id },
|
|
49
|
+
attributes: ['customFields'],
|
|
50
|
+
transaction: options.transaction,
|
|
51
|
+
raw: true, // Get plain object instead of model instance for better performance
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (currentCustomFields?.customFields) {
|
|
55
|
+
// Merge existing fields with update fields using our helper function
|
|
56
|
+
const completeFields = manualObjectCopy(
|
|
57
|
+
currentCustomFields.customFields,
|
|
58
|
+
instance.customFields,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
logger.debug('sadot - fetched complete custom fields for validation', {
|
|
62
|
+
fieldsCount: Object.keys(completeFields).length,
|
|
63
|
+
updateFieldsCount: Object.keys(instance.customFields).length,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return completeFields;
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
logger.error('sadot - error fetching complete model for validation', { error });
|
|
70
|
+
// Continue with partial data if we can't fetch the complete model
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return instance.customFields || {};
|
|
74
|
+
};
|
|
75
|
+
|
|
23
76
|
/**
|
|
24
77
|
* Validates the model using custom validators
|
|
25
78
|
*/
|
|
@@ -66,22 +119,30 @@ const validateModel = async (
|
|
|
66
119
|
}
|
|
67
120
|
|
|
68
121
|
// For updates, get the previous values
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
122
|
+
let originalValues = null;
|
|
123
|
+
if (!isCreate) {
|
|
124
|
+
// Create originalValues with our helper function
|
|
125
|
+
originalValues = manualObjectCopy(instance.previous());
|
|
126
|
+
|
|
127
|
+
// Add customFields separately
|
|
128
|
+
originalValues.customFields = instance.previous('customFields') || {};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Get complete custom fields by merging DB values with update values
|
|
132
|
+
// This is especially important for partial updates to ensure all related fields are available
|
|
133
|
+
const completeCustomFields = !isCreate
|
|
134
|
+
? await getCompleteCustomFields(instance, options)
|
|
135
|
+
: instance.customFields || {};
|
|
75
136
|
|
|
76
137
|
// For debugging in case of update
|
|
77
|
-
if (!isCreate) {
|
|
138
|
+
if (!isCreate && process.env.NODE_ENV !== 'production') {
|
|
139
|
+
// Create after object for logging
|
|
140
|
+
const logAfterObj = manualObjectCopy(instance.dataValues, { customFields: completeCustomFields });
|
|
141
|
+
|
|
78
142
|
logger.debug('sadot - validate with values', {
|
|
79
143
|
before: originalValues,
|
|
80
|
-
after:
|
|
81
|
-
|
|
82
|
-
...instance.customFields,
|
|
83
|
-
},
|
|
84
|
-
schema: JSON.stringify(validators[0].schema),
|
|
144
|
+
after: logAfterObj,
|
|
145
|
+
schema: validators[0].schema,
|
|
85
146
|
});
|
|
86
147
|
}
|
|
87
148
|
|
|
@@ -91,7 +152,7 @@ const validateModel = async (
|
|
|
91
152
|
const typedSchema = schema as Record<string, any>;
|
|
92
153
|
|
|
93
154
|
logger.debug('sadot - validating with schema', {
|
|
94
|
-
schema
|
|
155
|
+
schema,
|
|
95
156
|
hasAfterProps: !!typedSchema.properties?.after,
|
|
96
157
|
hasBeforeProps: !!typedSchema.properties?.before,
|
|
97
158
|
});
|
|
@@ -107,12 +168,12 @@ const validateModel = async (
|
|
|
107
168
|
},
|
|
108
169
|
});
|
|
109
170
|
|
|
110
|
-
const isValid = validateSchema({
|
|
171
|
+
const isValid = validateSchema(JSON.parse(JSON.stringify({
|
|
111
172
|
after: {
|
|
112
173
|
...instance.dataValues,
|
|
113
|
-
customFields:
|
|
174
|
+
customFields: completeCustomFields,
|
|
114
175
|
},
|
|
115
|
-
});
|
|
176
|
+
})));
|
|
116
177
|
|
|
117
178
|
if (!isValid) {
|
|
118
179
|
const errorDetails = validateSchema.errors?.map((err) =>
|
|
@@ -125,28 +186,34 @@ const validateModel = async (
|
|
|
125
186
|
// For update operations, we need both before and after
|
|
126
187
|
const validateSchema = ajv.compile(typedSchema);
|
|
127
188
|
|
|
128
|
-
|
|
189
|
+
// Create after object with our helper function
|
|
190
|
+
const afterObj = manualObjectCopy(instance.dataValues);
|
|
191
|
+
|
|
192
|
+
// Add complete custom fields
|
|
193
|
+
afterObj.customFields = completeCustomFields;
|
|
194
|
+
|
|
195
|
+
// Create validation payload
|
|
196
|
+
const payload = {
|
|
129
197
|
before: originalValues,
|
|
130
|
-
after:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
198
|
+
after: afterObj,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Validate
|
|
202
|
+
const isValid = validateSchema(JSON.parse(JSON.stringify(payload)));
|
|
135
203
|
|
|
136
204
|
logger.info('sadot - validation result', {
|
|
137
205
|
isValid,
|
|
138
206
|
test: {
|
|
139
207
|
before: originalValues,
|
|
140
|
-
after:
|
|
141
|
-
...instance.dataValues,
|
|
142
|
-
...instance.customFields,
|
|
143
|
-
},
|
|
208
|
+
after: afterObj,
|
|
144
209
|
},
|
|
145
210
|
});
|
|
146
211
|
|
|
147
212
|
if (!isValid) {
|
|
148
|
-
const errorDetails = validateSchema.errors?.map((err) =>
|
|
149
|
-
|
|
213
|
+
const errorDetails = validateSchema.errors?.map((err) => {
|
|
214
|
+
logger.info('dor', err);
|
|
215
|
+
return `${(err as any).instancePath || ''} ${(err as any).message || 'Invalid value'}`;
|
|
216
|
+
}).join(', ');
|
|
150
217
|
|
|
151
218
|
throw new BadRequest([new Error(`Validation failed for ${modelType}: ${errorDetails}`)]);
|
|
152
219
|
}
|
|
@@ -154,6 +221,49 @@ const validateModel = async (
|
|
|
154
221
|
}
|
|
155
222
|
};
|
|
156
223
|
|
|
224
|
+
const getFieldDefinitions = async ({
|
|
225
|
+
modelType,
|
|
226
|
+
modelOptions,
|
|
227
|
+
identifiers,
|
|
228
|
+
options,
|
|
229
|
+
}: {
|
|
230
|
+
modelType: any,
|
|
231
|
+
modelOptions: ModelOptions,
|
|
232
|
+
identifiers: any[],
|
|
233
|
+
options: any
|
|
234
|
+
}) => {
|
|
235
|
+
const { include, useEntityIdFromInclude } = modelOptions;
|
|
236
|
+
const where: WhereOptions = {
|
|
237
|
+
modelType,
|
|
238
|
+
disabled: false,
|
|
239
|
+
...(!useEntityIdFromInclude && { entityId: identifiers }),
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const fieldDefinitions = await DefinitionRepo.findAll(where, {
|
|
243
|
+
withDisabled: false,
|
|
244
|
+
transaction: options.transaction,
|
|
245
|
+
include: include?.(identifiers),
|
|
246
|
+
});
|
|
247
|
+
return fieldDefinitions;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const formatDates = (fieldDefinitions: CustomFieldDefinition[], instance: any) => {
|
|
251
|
+
(fieldDefinitions || []).forEach((fieldDefinition) => {
|
|
252
|
+
const { fieldType, name } = fieldDefinition;
|
|
253
|
+
if ([CustomFieldDefinitionType.DATE, CustomFieldDefinitionType.DATETIME].includes(fieldType)) {
|
|
254
|
+
const value = instance.customFields?.[name];
|
|
255
|
+
if (value) {
|
|
256
|
+
const validationError = Joi.date().validate(value).error;
|
|
257
|
+
if (validationError) {
|
|
258
|
+
throw new InvalidValueError(value, name, validationError);
|
|
259
|
+
}
|
|
260
|
+
// eslint-disable-next-line no-param-reassign
|
|
261
|
+
instance.customFields[name] = new Date(value).toISOString();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
|
|
157
267
|
/**
|
|
158
268
|
* Hook to handle validation and custom fields during creation
|
|
159
269
|
*/
|
|
@@ -167,28 +277,16 @@ export const beforeCreate = (
|
|
|
167
277
|
): Promise<void> => {
|
|
168
278
|
logger.debug('sadot - before create hook');
|
|
169
279
|
const { fields } = options;
|
|
170
|
-
const { include, useEntityIdFromInclude } = modelOptions;
|
|
171
280
|
const modelType = instance.constructor.name;
|
|
172
281
|
|
|
173
282
|
const identifiers = applyScopeToInstance(instance, scopeAttributes);
|
|
174
283
|
|
|
175
284
|
// Step 1: Handle custom fields default values and required fields
|
|
176
|
-
const where: WhereOptions = {
|
|
177
|
-
modelType,
|
|
178
|
-
disabled: false,
|
|
179
|
-
...(!useEntityIdFromInclude && { entityId: identifiers }),
|
|
180
|
-
};
|
|
181
285
|
|
|
182
|
-
const fieldDefinitions = await
|
|
183
|
-
|
|
184
|
-
transaction: options.transaction,
|
|
185
|
-
include: include?.(identifiers),
|
|
286
|
+
const fieldDefinitions = await getFieldDefinitions({
|
|
287
|
+
modelType, modelOptions, identifiers, options,
|
|
186
288
|
});
|
|
187
289
|
|
|
188
|
-
const requiredFieldsNames = Array.from(
|
|
189
|
-
new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)),
|
|
190
|
-
);
|
|
191
|
-
|
|
192
290
|
// Apply default values
|
|
193
291
|
const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
|
|
194
292
|
if (fieldsWithDefaultValue.length) {
|
|
@@ -203,6 +301,9 @@ export const beforeCreate = (
|
|
|
203
301
|
}
|
|
204
302
|
|
|
205
303
|
// Check for required fields
|
|
304
|
+
const requiredFieldsNames = Array.from(
|
|
305
|
+
new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)),
|
|
306
|
+
);
|
|
206
307
|
const { customFields } = instance;
|
|
207
308
|
const fieldsNames = Object.keys(customFields ?? {});
|
|
208
309
|
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
@@ -213,6 +314,9 @@ export const beforeCreate = (
|
|
|
213
314
|
// Step 2: Validate the model data (including custom fields)
|
|
214
315
|
await validateModel(instance, options, scopeAttributes, true);
|
|
215
316
|
|
|
317
|
+
// format date and datetime fields
|
|
318
|
+
formatDates(fieldDefinitions, instance);
|
|
319
|
+
|
|
216
320
|
// Step 3: Save custom field values if they exist
|
|
217
321
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
218
322
|
if (customFieldsIdx === -1 || !customFields || !Object.keys(customFields).length) {
|
|
@@ -254,9 +358,16 @@ export const beforeUpdate = (
|
|
|
254
358
|
const modelType = instance.constructor.name;
|
|
255
359
|
const identifiers = applyScopeToInstance(instance, scopeAttributes);
|
|
256
360
|
|
|
361
|
+
const fieldDefinitions = await getFieldDefinitions({
|
|
362
|
+
modelType, modelOptions, identifiers, options,
|
|
363
|
+
});
|
|
364
|
+
|
|
257
365
|
// Step 1: Validate the model data (including custom fields)
|
|
258
366
|
await validateModel(instance, options, scopeAttributes, false);
|
|
259
367
|
|
|
368
|
+
// format date and datetime fields
|
|
369
|
+
formatDates(fieldDefinitions, instance);
|
|
370
|
+
|
|
260
371
|
// Step 2: Update custom field values if they exist
|
|
261
372
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
262
373
|
if (customFieldsIdx > -1) {
|
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',
|
package/validator-test.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
const Ajv = require("ajv");
|
|
2
|
-
const addFormats = require("ajv-formats");
|
|
3
|
-
const ajv = new Ajv({
|
|
4
|
-
allErrors: true,
|
|
5
|
-
strict: false, // Disable strict mode to avoid warnings
|
|
6
|
-
strictTypes: false, // Disable strict type checking
|
|
7
|
-
$data: true, // Enable $data references
|
|
8
|
-
}); // options can be passed, e.g. {allErrors: true}
|
|
9
|
-
addFormats(ajv);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const schema = {
|
|
13
|
-
"type": "object",
|
|
14
|
-
"properties": {
|
|
15
|
-
"after": {
|
|
16
|
-
"type": "object",
|
|
17
|
-
"properties": {
|
|
18
|
-
"customFields": {
|
|
19
|
-
"type": "object",
|
|
20
|
-
"properties": {
|
|
21
|
-
"actualStartTime": {
|
|
22
|
-
"type": "string",
|
|
23
|
-
"format": "date-time"
|
|
24
|
-
},
|
|
25
|
-
"actualEndTime": {
|
|
26
|
-
"type": "string",
|
|
27
|
-
"format": "date-time",
|
|
28
|
-
"formatMinimum": {
|
|
29
|
-
"$data": "/after/customFields/actualStartTime"
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const validate = ajv.compile(schema);
|
|
40
|
-
|
|
41
|
-
const data = {
|
|
42
|
-
"id": "c9c87cec-28c7-4a1f-9376-4d8a4ba0f49c",
|
|
43
|
-
"typeId": "4fcd7096-4c90-46a2-a20e-91bb5bff3ced",
|
|
44
|
-
"priorityId": null,
|
|
45
|
-
"statusId": "51d28a37-c042-429f-a9cd-fbc81bba2b39",
|
|
46
|
-
"subjectId": null,
|
|
47
|
-
"subjectType": null,
|
|
48
|
-
"title": "ff",
|
|
49
|
-
"description": null,
|
|
50
|
-
"dueTime": null,
|
|
51
|
-
"startTime": null,
|
|
52
|
-
"endTime": null,
|
|
53
|
-
"driverId": null,
|
|
54
|
-
"vendorId": null,
|
|
55
|
-
"userId": null,
|
|
56
|
-
"businessModelId": "72ecf740-42a1-46f4-81ca-8132b08c4f04",
|
|
57
|
-
"createdAt": "2025-03-12T12:58:05.529Z",
|
|
58
|
-
"updatedAt": "2025-03-12T12:58:05.529Z",
|
|
59
|
-
"deletedAt": null,
|
|
60
|
-
"actions": [],
|
|
61
|
-
"customFields": {
|
|
62
|
-
"actualStartTime": "2025-03-14T12:48:01.373Z",
|
|
63
|
-
"actualEndTime": "2025-03-01T12:51:46.685Z"
|
|
64
|
-
},
|
|
65
|
-
"isBlocker": false
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const valid = validate({after: data});
|
|
69
|
-
|
|
70
|
-
if (!valid) {
|
|
71
|
-
console.log("invalid, the errors is: ");
|
|
72
|
-
validate.errors.forEach((error) =>
|
|
73
|
-
console.log(error.instancePath, error.message)
|
|
74
|
-
);
|
|
75
|
-
} else {
|
|
76
|
-
console.log("valid");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
console.log("--------------------");
|