@autofleet/sadot 0.13.1 → 0.13.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/hooks.js +36 -10
- package/package.json +2 -1
- package/src/hooks/hooks.ts +131 -81
- package/src/models/index.ts +1 -1
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,
|
|
@@ -195,16 +196,8 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
};
|
|
198
|
-
|
|
199
|
-
* Hook to handle validation and custom fields during creation
|
|
200
|
-
*/
|
|
201
|
-
const beforeCreate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCustomFieldsEntries: false }) => async (instance, options) => {
|
|
202
|
-
logger_1.default.debug('sadot - before create hook');
|
|
203
|
-
const { fields } = options;
|
|
199
|
+
const getFieldDefinitions = async ({ modelType, modelOptions, identifiers, options }) => {
|
|
204
200
|
const { include, useEntityIdFromInclude } = modelOptions;
|
|
205
|
-
const modelType = instance.constructor.name;
|
|
206
|
-
const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
|
|
207
|
-
// Step 1: Handle custom fields default values and required fields
|
|
208
201
|
const where = {
|
|
209
202
|
modelType,
|
|
210
203
|
disabled: false,
|
|
@@ -215,7 +208,32 @@ const beforeCreate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCu
|
|
|
215
208
|
transaction: options.transaction,
|
|
216
209
|
include: include?.(identifiers),
|
|
217
210
|
});
|
|
218
|
-
|
|
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
|
+
});
|
|
219
237
|
// Apply default values
|
|
220
238
|
const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
|
|
221
239
|
if (fieldsWithDefaultValue.length) {
|
|
@@ -229,6 +247,7 @@ const beforeCreate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCu
|
|
|
229
247
|
});
|
|
230
248
|
}
|
|
231
249
|
// Check for required fields
|
|
250
|
+
const requiredFieldsNames = Array.from(new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)));
|
|
232
251
|
const { customFields } = instance;
|
|
233
252
|
const fieldsNames = Object.keys(customFields ?? {});
|
|
234
253
|
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
@@ -237,6 +256,8 @@ const beforeCreate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCu
|
|
|
237
256
|
}
|
|
238
257
|
// Step 2: Validate the model data (including custom fields)
|
|
239
258
|
await validateModel(instance, options, scopeAttributes, true);
|
|
259
|
+
// format date and datetime fields
|
|
260
|
+
formatDates(fieldDefinitions, instance);
|
|
240
261
|
// Step 3: Save custom field values if they exist
|
|
241
262
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
242
263
|
if (customFieldsIdx === -1 || !customFields || !Object.keys(customFields).length) {
|
|
@@ -268,8 +289,13 @@ const beforeUpdate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCu
|
|
|
268
289
|
const { fields } = options;
|
|
269
290
|
const modelType = instance.constructor.name;
|
|
270
291
|
const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
|
|
292
|
+
const fieldDefinitions = await getFieldDefinitions({
|
|
293
|
+
modelType, modelOptions, identifiers, options
|
|
294
|
+
});
|
|
271
295
|
// Step 1: Validate the model data (including custom fields)
|
|
272
296
|
await validateModel(instance, options, scopeAttributes, false);
|
|
297
|
+
// format date and datetime fields
|
|
298
|
+
formatDates(fieldDefinitions, instance);
|
|
273
299
|
// Step 2: Update custom field values if they exist
|
|
274
300
|
const customFieldsIdx = fields.indexOf('customFields');
|
|
275
301
|
if (customFieldsIdx > -1) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sadot",
|
|
3
|
-
"version": "0.13.1",
|
|
3
|
+
"version": "0.13.2-beta.1",
|
|
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",
|
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({
|
|
@@ -218,25 +221,18 @@ const validateModel = async (
|
|
|
218
221
|
}
|
|
219
222
|
};
|
|
220
223
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
scopeAttributes: string[],
|
|
226
|
-
modelOptions: ModelOptions = {},
|
|
227
|
-
sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'> = { useCustomFieldsEntries: false },
|
|
228
|
-
) => async (
|
|
229
|
-
instance,
|
|
224
|
+
const getFieldDefinitions = async ({
|
|
225
|
+
modelType,
|
|
226
|
+
modelOptions,
|
|
227
|
+
identifiers,
|
|
230
228
|
options,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
229
|
+
}: {
|
|
230
|
+
modelType: any,
|
|
231
|
+
modelOptions: ModelOptions,
|
|
232
|
+
identifiers: any[],
|
|
233
|
+
options: any
|
|
234
|
+
}) => {
|
|
234
235
|
const { include, useEntityIdFromInclude } = modelOptions;
|
|
235
|
-
const modelType = instance.constructor.name;
|
|
236
|
-
|
|
237
|
-
const identifiers = applyScopeToInstance(instance, scopeAttributes);
|
|
238
|
-
|
|
239
|
-
// Step 1: Handle custom fields default values and required fields
|
|
240
236
|
const where: WhereOptions = {
|
|
241
237
|
modelType,
|
|
242
238
|
disabled: false,
|
|
@@ -248,64 +244,33 @@ export const beforeCreate = (
|
|
|
248
244
|
transaction: options.transaction,
|
|
249
245
|
include: include?.(identifiers),
|
|
250
246
|
});
|
|
247
|
+
return fieldDefinitions;
|
|
248
|
+
};
|
|
251
249
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
.filter((def) => (instance.customFields?.[def.name] === undefined))
|
|
263
|
-
.forEach(({ name, defaultValue }) => {
|
|
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 { value: joiValue, error: validationError } = Joi.date().validate(value);
|
|
257
|
+
if (validationError) {
|
|
258
|
+
throw new InvalidValueError(value, name, validationError);
|
|
259
|
+
}
|
|
264
260
|
// eslint-disable-next-line no-param-reassign
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const fieldsNames = Object.keys(customFields ?? {});
|
|
272
|
-
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
273
|
-
if (missingFields?.length) {
|
|
274
|
-
throw new MissingRequiredCustomFieldError(missingFields);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Step 2: Validate the model data (including custom fields)
|
|
278
|
-
await validateModel(instance, options, scopeAttributes, true);
|
|
279
|
-
|
|
280
|
-
// Step 3: Save custom field values if they exist
|
|
281
|
-
const customFieldsIdx = fields.indexOf('customFields');
|
|
282
|
-
if (customFieldsIdx === -1 || !customFields || !Object.keys(customFields).length) {
|
|
283
|
-
// No custom fields to update
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Save custom field values
|
|
288
|
-
await updateInstanceValues({
|
|
289
|
-
modelId: instance.id,
|
|
290
|
-
modelType,
|
|
291
|
-
identifiers,
|
|
292
|
-
customFields,
|
|
293
|
-
options: {
|
|
294
|
-
useCustomFieldsEntries: sadotOptions.useCustomFieldsEntries,
|
|
295
|
-
transaction: options.transaction,
|
|
296
|
-
modelOptions,
|
|
297
|
-
},
|
|
261
|
+
const typeofjoi = typeof joiValue;
|
|
262
|
+
logger.info('sadot - formatting date', { name, value, joiValue, type: typeof joiValue });
|
|
263
|
+
instance.customFields[name] = joiValue.toISOString();
|
|
264
|
+
new Date().toString
|
|
265
|
+
}
|
|
266
|
+
}
|
|
298
267
|
});
|
|
299
|
-
|
|
300
|
-
// Remove customFields from fields array after handling
|
|
301
|
-
// eslint-disable-next-line no-param-reassign
|
|
302
|
-
fields.splice(customFieldsIdx, 1);
|
|
303
268
|
};
|
|
304
269
|
|
|
305
270
|
/**
|
|
306
|
-
* Hook to handle validation and custom fields during
|
|
271
|
+
* Hook to handle validation and custom fields during creation
|
|
307
272
|
*/
|
|
308
|
-
export const
|
|
273
|
+
export const beforeCreate = (
|
|
309
274
|
scopeAttributes: string[],
|
|
310
275
|
modelOptions: ModelOptions = {},
|
|
311
276
|
sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'> = { useCustomFieldsEntries: false },
|
|
@@ -313,20 +278,52 @@ export const beforeUpdate = (
|
|
|
313
278
|
instance,
|
|
314
279
|
options,
|
|
315
280
|
): Promise<void> => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
281
|
+
logger.debug('sadot - before create hook');
|
|
282
|
+
const { fields } = options;
|
|
283
|
+
const modelType = instance.constructor.name;
|
|
284
|
+
|
|
285
|
+
const identifiers = applyScopeToInstance(instance, scopeAttributes);
|
|
286
|
+
|
|
287
|
+
// Step 1: Handle custom fields default values and required fields
|
|
288
|
+
|
|
289
|
+
const fieldDefinitions = await getFieldDefinitions({
|
|
290
|
+
modelType, modelOptions, identifiers, options,
|
|
291
|
+
});
|
|
320
292
|
|
|
321
|
-
|
|
322
|
-
|
|
293
|
+
// Apply default values
|
|
294
|
+
const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
|
|
295
|
+
if (fieldsWithDefaultValue.length) {
|
|
296
|
+
// eslint-disable-next-line no-param-reassign
|
|
297
|
+
instance.customFields ||= {};
|
|
298
|
+
fieldsWithDefaultValue
|
|
299
|
+
.filter((def) => (instance.customFields?.[def.name] === undefined))
|
|
300
|
+
.forEach(({ name, defaultValue }) => {
|
|
301
|
+
// eslint-disable-next-line no-param-reassign
|
|
302
|
+
instance.customFields[name] = defaultValue;
|
|
303
|
+
});
|
|
304
|
+
}
|
|
323
305
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
306
|
+
// Check for required fields
|
|
307
|
+
const requiredFieldsNames = Array.from(
|
|
308
|
+
new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)),
|
|
309
|
+
);
|
|
327
310
|
const { customFields } = instance;
|
|
311
|
+
const fieldsNames = Object.keys(customFields ?? {});
|
|
312
|
+
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
313
|
+
if (missingFields?.length) {
|
|
314
|
+
throw new MissingRequiredCustomFieldError(missingFields);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Step 2: Validate the model data (including custom fields)
|
|
318
|
+
await validateModel(instance, options, scopeAttributes, true);
|
|
319
|
+
|
|
320
|
+
// format date and datetime fields
|
|
321
|
+
formatDates(fieldDefinitions, instance);
|
|
328
322
|
|
|
329
|
-
if
|
|
323
|
+
// Step 3: Save custom field values if they exist
|
|
324
|
+
const customFieldsIdx = fields.indexOf('customFields');
|
|
325
|
+
if (customFieldsIdx === -1 || !customFields || !Object.keys(customFields).length) {
|
|
326
|
+
// No custom fields to update
|
|
330
327
|
return;
|
|
331
328
|
}
|
|
332
329
|
|
|
@@ -346,8 +343,61 @@ export const beforeUpdate = (
|
|
|
346
343
|
// Remove customFields from fields array after handling
|
|
347
344
|
// eslint-disable-next-line no-param-reassign
|
|
348
345
|
fields.splice(customFieldsIdx, 1);
|
|
349
|
-
}
|
|
350
|
-
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Hook to handle validation and custom fields during update
|
|
350
|
+
*/
|
|
351
|
+
export const beforeUpdate = (
|
|
352
|
+
scopeAttributes: string[],
|
|
353
|
+
modelOptions: ModelOptions = {},
|
|
354
|
+
sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'> = { useCustomFieldsEntries: false },
|
|
355
|
+
) => async (
|
|
356
|
+
instance,
|
|
357
|
+
options,
|
|
358
|
+
): Promise<void> => {
|
|
359
|
+
logger.debug('sadot - before update hook');
|
|
360
|
+
const { fields } = options;
|
|
361
|
+
const modelType = instance.constructor.name;
|
|
362
|
+
const identifiers = applyScopeToInstance(instance, scopeAttributes);
|
|
363
|
+
|
|
364
|
+
const fieldDefinitions = await getFieldDefinitions({
|
|
365
|
+
modelType, modelOptions, identifiers, options,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Step 1: Validate the model data (including custom fields)
|
|
369
|
+
await validateModel(instance, options, scopeAttributes, false);
|
|
370
|
+
|
|
371
|
+
// format date and datetime fields
|
|
372
|
+
formatDates(fieldDefinitions, instance);
|
|
373
|
+
|
|
374
|
+
// Step 2: Update custom field values if they exist
|
|
375
|
+
const customFieldsIdx = fields.indexOf('customFields');
|
|
376
|
+
if (customFieldsIdx > -1) {
|
|
377
|
+
const { customFields } = instance;
|
|
378
|
+
|
|
379
|
+
if (!Object.keys(customFields).length) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Save custom field values
|
|
384
|
+
await updateInstanceValues({
|
|
385
|
+
modelId: instance.id,
|
|
386
|
+
modelType,
|
|
387
|
+
identifiers,
|
|
388
|
+
customFields,
|
|
389
|
+
options: {
|
|
390
|
+
useCustomFieldsEntries: sadotOptions.useCustomFieldsEntries,
|
|
391
|
+
transaction: options.transaction,
|
|
392
|
+
modelOptions,
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Remove customFields from fields array after handling
|
|
397
|
+
// eslint-disable-next-line no-param-reassign
|
|
398
|
+
fields.splice(customFieldsIdx, 1);
|
|
399
|
+
}
|
|
400
|
+
};
|
|
351
401
|
|
|
352
402
|
/**
|
|
353
403
|
* Hook to enable individual hooks for bulk create operations
|
package/src/models/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ const productionModels: ProductionModel[] = [CustomFieldDefinition, CustomFieldV
|
|
|
23
23
|
const testModels = [TestModel, AssociatedTestModel, ContextAwareTestModel, ContextTestModel];
|
|
24
24
|
|
|
25
25
|
const SADOT_MIGRATION_PREFIX = 'sadot-migration';
|
|
26
|
-
const SCHEMA_VERSION = '
|
|
26
|
+
const SCHEMA_VERSION = '49c9dd1d-b1cc-445b-a911-fd349d783110';
|
|
27
27
|
|
|
28
28
|
const initTables = async (
|
|
29
29
|
sequelize: Sequelize,
|