@autofleet/sadot 1.0.0-beta.1 → 1.0.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.
Files changed (135) hide show
  1. package/README.md +5 -0
  2. package/dist/api/index.d.ts +2 -1
  3. package/dist/api/index.js +3 -2
  4. package/dist/api/v1/definition/index.d.ts +2 -1
  5. package/dist/api/v1/definition/index.js +12 -14
  6. package/dist/api/v1/definition/validations.js +14 -12
  7. package/dist/api/v1/errors.d.ts +3 -1
  8. package/dist/api/v1/errors.js +1 -4
  9. package/dist/api/v1/index.d.ts +2 -1
  10. package/dist/api/v1/index.js +5 -2
  11. package/dist/api/v1/validator/index.d.ts +3 -0
  12. package/dist/api/v1/validator/index.js +143 -0
  13. package/dist/api/v1/validator/validations.d.ts +6 -0
  14. package/dist/api/v1/validator/validations.js +40 -0
  15. package/dist/errors/index.d.ts +9 -1
  16. package/dist/errors/index.js +25 -4
  17. package/dist/events/index.d.ts +2 -1
  18. package/dist/events/index.js +17 -11
  19. package/dist/hooks/create.d.ts +2 -2
  20. package/dist/hooks/create.js +40 -19
  21. package/dist/hooks/enrich.d.ts +20 -2
  22. package/dist/hooks/enrich.js +88 -16
  23. package/dist/hooks/hooks.d.ts +17 -0
  24. package/dist/hooks/hooks.js +379 -0
  25. package/dist/hooks/index.d.ts +2 -3
  26. package/dist/hooks/index.js +6 -7
  27. package/dist/hooks/update.d.ts +2 -2
  28. package/dist/hooks/update.js +16 -26
  29. package/dist/hooks/utils/updateInstanceValues.d.ts +15 -0
  30. package/dist/hooks/utils/updateInstanceValues.js +50 -0
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +19 -6
  33. package/dist/models/CustomFieldDefinition.d.ts +1 -0
  34. package/dist/models/CustomFieldDefinition.js +10 -2
  35. package/dist/models/CustomFieldEntries.d.ts +15 -0
  36. package/dist/models/CustomFieldEntries.js +123 -0
  37. package/dist/models/CustomFieldValue.d.ts +3 -2
  38. package/dist/models/CustomFieldValue.js +22 -14
  39. package/dist/models/CustomValidator.d.ts +17 -0
  40. package/dist/models/CustomValidator.js +98 -0
  41. package/dist/models/index.d.ts +10 -2
  42. package/dist/models/index.js +60 -22
  43. package/dist/repository/definition.d.ts +23 -7
  44. package/dist/repository/definition.js +36 -7
  45. package/dist/repository/entries.d.ts +13 -0
  46. package/dist/repository/entries.js +92 -0
  47. package/dist/repository/utils/formatValues.d.ts +3 -0
  48. package/dist/repository/utils/formatValues.js +16 -0
  49. package/dist/repository/validator.d.ts +21 -0
  50. package/dist/repository/validator.js +62 -0
  51. package/dist/repository/value.d.ts +1 -1
  52. package/dist/repository/value.js +7 -32
  53. package/dist/scopes/filter.d.ts +5 -22
  54. package/dist/scopes/filter.js +18 -65
  55. package/dist/scopes/helpers/filter.helpers.d.ts +42 -0
  56. package/dist/scopes/helpers/filter.helpers.js +204 -0
  57. package/dist/tests/api/test-api.js +6 -24
  58. package/dist/tests/helpers/commonHooks.d.ts +6 -0
  59. package/dist/tests/helpers/commonHooks.js +62 -0
  60. package/dist/tests/helpers/database-config.js +1 -1
  61. package/dist/tests/helpers/index.d.ts +6 -1
  62. package/dist/tests/helpers/index.js +15 -2
  63. package/dist/tests/mocks/definition.mock.d.ts +5 -2
  64. package/dist/tests/mocks/definition.mock.js +10 -1
  65. package/dist/tests/mocks/events.mock.d.ts +1 -0
  66. package/dist/tests/mocks/events.mock.js +4 -2
  67. package/dist/types/definition/index.d.ts +1 -0
  68. package/dist/types/entries/index.d.ts +25 -0
  69. package/dist/types/entries/index.js +2 -0
  70. package/dist/types/index.d.ts +19 -3
  71. package/dist/utils/constants/index.d.ts +1 -1
  72. package/dist/utils/helpers/index.d.ts +4 -3
  73. package/dist/utils/helpers/index.js +22 -30
  74. package/dist/utils/init.d.ts +5 -3
  75. package/dist/utils/init.js +13 -11
  76. package/dist/utils/logger/index.d.ts +1 -0
  77. package/dist/utils/logger/index.js +34 -0
  78. package/dist/utils/validations/index.d.ts +7 -1
  79. package/dist/utils/validations/index.js +28 -7
  80. package/dist/utils/validations/schema/validator-schema.d.ts +9 -0
  81. package/dist/utils/validations/schema/validator-schema.js +95 -0
  82. package/dist/utils/validations/type.d.ts +2 -1
  83. package/dist/utils/validations/validators/index.js +9 -9
  84. package/dist/utils/validations/validators/select.validator.js +5 -2
  85. package/dist/utils/validations/validators/status.validator.js +8 -2
  86. package/package.json +28 -12
  87. package/src/api/index.ts +3 -2
  88. package/src/api/v1/definition/index.ts +20 -23
  89. package/src/api/v1/definition/validations.ts +16 -16
  90. package/src/api/v1/errors.ts +4 -7
  91. package/src/api/v1/index.ts +5 -3
  92. package/src/api/v1/validator/index.ts +141 -0
  93. package/src/api/v1/validator/validations.ts +39 -0
  94. package/src/errors/index.ts +31 -3
  95. package/src/events/index.ts +25 -13
  96. package/src/hooks/create.ts +50 -28
  97. package/src/hooks/enrich.ts +137 -28
  98. package/src/hooks/hooks.ts +467 -0
  99. package/src/hooks/index.ts +10 -5
  100. package/src/hooks/update.ts +20 -7
  101. package/src/hooks/utils/updateInstanceValues.ts +63 -0
  102. package/src/index.ts +10 -8
  103. package/src/models/CustomFieldDefinition.ts +9 -2
  104. package/src/models/CustomFieldEntries.ts +81 -0
  105. package/src/models/CustomFieldValue.ts +25 -17
  106. package/src/models/CustomValidator.ts +78 -0
  107. package/src/models/index.ts +83 -25
  108. package/src/repository/definition.ts +62 -14
  109. package/src/repository/entries.ts +88 -0
  110. package/src/repository/utils/formatValues.ts +14 -0
  111. package/src/repository/validator.ts +104 -0
  112. package/src/repository/value.ts +5 -35
  113. package/src/scopes/filter.ts +33 -106
  114. package/src/scopes/helpers/filter.helpers.ts +227 -0
  115. package/src/tests/api/test-api.ts +4 -2
  116. package/src/tests/helpers/commonHooks.ts +43 -0
  117. package/src/tests/helpers/database-config.ts +1 -1
  118. package/src/tests/helpers/index.ts +18 -2
  119. package/src/tests/mocks/definition.mock.ts +18 -9
  120. package/src/tests/mocks/events.mock.ts +4 -1
  121. package/src/types/definition/index.ts +1 -0
  122. package/src/types/entries/index.ts +27 -0
  123. package/src/types/index.ts +20 -3
  124. package/src/utils/helpers/index.ts +28 -35
  125. package/src/utils/init.ts +17 -12
  126. package/src/utils/logger/index.ts +9 -0
  127. package/src/utils/validations/index.ts +30 -6
  128. package/src/utils/validations/schema/README.md +93 -0
  129. package/src/utils/validations/schema/validator-schema.ts +106 -0
  130. package/src/utils/validations/type.ts +2 -1
  131. package/src/utils/validations/validators/index.ts +9 -9
  132. package/src/utils/validations/validators/select.validator.ts +3 -2
  133. package/src/utils/validations/validators/status.validator.ts +6 -2
  134. package/tsconfig.build.json +7 -0
  135. package/tsconfig.json +1 -1
@@ -1,13 +1,13 @@
1
1
  import { ResourceNotFoundError } from '@autofleet/errors';
2
- import { Router } from 'express';
3
- import type { Request, Response } from 'express';
2
+ import { Router } from '@autofleet/node-common';
4
3
  import handleError from '../errors';
5
4
  import * as DefinitionRepo from '../../../repository/definition';
6
5
  import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../../../types/definition';
7
6
  import { validateCustomFieldDefinitionCreation, validateCustomFieldDefinitionUpdate } from './validations';
8
7
  import logger from '../../../utils/logger';
8
+ import type { CustomFieldDefinition } from '../../../models';
9
9
 
10
- const router = Router({ mergeParams: true });
10
+ const router = Router({ logger });
11
11
  const ENTITY = 'CustomFieldDefinition';
12
12
 
13
13
  const toPascalCase = (str: string): string => str.replace(/(^\w|-\w)/g, (subStr) => subStr.replace(/-/, '').toUpperCase());
@@ -15,12 +15,11 @@ const toPascalCase = (str: string): string => str.replace(/(^\w|-\w)/g, (subStr)
15
15
  /**
16
16
  * Create
17
17
  */
18
- router.post('/', async (req: Request, res: Response) => {
19
- const { modelName } = req.params as any;
18
+ router.post<{ modelName: string; }>('/', async (req, res) => {
19
+ const { modelName } = req.params;
20
20
  const modelType = toPascalCase(modelName);
21
21
  try {
22
- const validatedPayload: CreateCustomFieldDefinition = await
23
- validateCustomFieldDefinitionCreation(req.body);
22
+ const validatedPayload: CreateCustomFieldDefinition = await validateCustomFieldDefinitionCreation(req.body);
24
23
 
25
24
  const customFieldDefinition = await DefinitionRepo.create({
26
25
  ...validatedPayload,
@@ -29,14 +28,14 @@ router.post('/', async (req: Request, res: Response) => {
29
28
  return res.status(201).json(customFieldDefinition);
30
29
  } catch (err) {
31
30
  logger.error('Failed to create custom field definition', err);
32
- return handleError(err, res, { message: `Error in create ${ENTITY} request` });
31
+ return handleError(err, res, { logger, message: `Error in create ${ENTITY} request` });
33
32
  }
34
33
  });
35
34
 
36
35
  /**
37
36
  * Get by id
38
37
  */
39
- router.get('/:customFieldDefinitionId', async (req, res) => {
38
+ router.get<{ modelName: string; customFieldDefinitionId: string; }, CustomFieldDefinition>('/:customFieldDefinitionId', async (req, res) => {
40
39
  const { customFieldDefinitionId } = req.params;
41
40
  try {
42
41
  const customFieldDefinition = await DefinitionRepo.findById(customFieldDefinitionId);
@@ -48,39 +47,37 @@ router.get('/:customFieldDefinitionId', async (req, res) => {
48
47
  return res.json(customFieldDefinition);
49
48
  } catch (err) {
50
49
  logger.error('Failed to fetch custom field definition', err);
51
- return handleError(err, res, { message: `Error in get ${ENTITY} request` });
50
+ return handleError(err, res, { logger, message: `Error in get ${ENTITY} request` });
52
51
  }
53
52
  });
54
53
 
55
54
  /**
56
55
  * Get all
57
56
  */
58
- router.get('/', async (req, res) => {
59
- const { modelName } = req.params as any;
60
- const { entityIds } = req.query as any;
57
+ router.get<{ modelName: string; }, CustomFieldDefinition[], never, { entityIds?: string[]; }>('/', async (req, res) => {
58
+ const { params: { modelName }, query: { entityIds } } = req;
61
59
 
62
60
  const modelType = toPascalCase(modelName);
63
61
  try {
64
- const where: any = { modelType };
65
- if (entityIds?.length > 0) {
66
- where.entityId = entityIds;
67
- }
68
- const customFieldDefinitions = await DefinitionRepo.findAll({ ...where }, { withDisabled: true });
62
+ const where = {
63
+ modelType,
64
+ ...(entityIds?.length > 0 && { entityId: entityIds }),
65
+ };
66
+ const customFieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true });
69
67
  return res.json(customFieldDefinitions);
70
68
  } catch (err) {
71
69
  logger.error('Failed to fetch custom field definitions', err);
72
- return handleError(err, res, { message: `Error in get all ${ENTITY} request` });
70
+ return handleError(err, res, { logger, message: `Error in get all ${ENTITY} request` });
73
71
  }
74
72
  });
75
73
 
76
74
  /**
77
75
  * Update
78
76
  */
79
- router.patch('/:customFieldDefinitionId', async (req, res) => {
80
- const { customFieldDefinitionId, modelName } = req.params as any;
77
+ router.patch<{ modelName: string; customFieldDefinitionId: string; }, CustomFieldDefinition>('/:customFieldDefinitionId', async (req, res) => {
78
+ const { customFieldDefinitionId, modelName } = req.params;
81
79
  const modelType = toPascalCase(modelName);
82
80
  try {
83
- // eslint-disable-next-line max-len
84
81
  const validatedPayload: UpdateCustomFieldDefinition = await validateCustomFieldDefinitionUpdate(req.body);
85
82
 
86
83
  const customFieldDefinition = await DefinitionRepo.findByWhere({
@@ -100,7 +97,7 @@ router.patch('/:customFieldDefinitionId', async (req, res) => {
100
97
  return res.status(200).json(updatedCustomFieldDefinition);
101
98
  } catch (err) {
102
99
  logger.error('Failed to patch custom field definition', err);
103
- return handleError(err, res, { message: `Error in update ${ENTITY} request` });
100
+ return handleError(err, res, { logger, message: `Error in update ${ENTITY} request` });
104
101
  }
105
102
  });
106
103
 
@@ -1,3 +1,4 @@
1
+ /* eslint-disable newline-per-chained-call */
1
2
  import Joi from 'joi';
2
3
  import { CustomFieldDefinitionType } from '../../../utils/constants';
3
4
 
@@ -11,7 +12,6 @@ const statusValidationObject = Joi.object({
11
12
  value: Joi.string().required(),
12
13
  color: Joi.string().required(),
13
14
  });
14
- const statusValidationObjectSchema = Joi.array().items(statusValidationObject).min(1).unique('value');
15
15
  /**
16
16
  * Schema for the validation of custom field definition
17
17
  * The only custom validation is for
@@ -23,12 +23,12 @@ const statusValidationObjectSchema = Joi.array().items(statusValidationObject).m
23
23
  */
24
24
  const ValidationSchema = Joi.when('fieldType', {
25
25
  is: CustomFieldDefinitionType.SELECT,
26
- then: Joi.array().items(Joi.string()).min(1).unique(),
27
- otherwise: Joi.any(),
28
- }).when('fieldType', {
29
- is: CustomFieldDefinitionType.STATUS,
30
- then: statusValidationObjectSchema,
31
- otherwise: Joi.any(),
26
+ then: Joi.array().required().items(Joi.string()).min(1).unique(),
27
+ otherwise: Joi.when('fieldType', {
28
+ is: CustomFieldDefinitionType.STATUS,
29
+ then: Joi.array().required().items(statusValidationObject).min(1).unique('value'),
30
+ otherwise: Joi.forbidden(),
31
+ }),
32
32
  });
33
33
 
34
34
  const DefaultValueSchema = Joi.when('fieldType', {
@@ -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: FileValidationSchema },
40
- { is: CustomFieldDefinitionType.IMAGE, then: Joi.string().uri().allow(null) },
39
+ { is: CustomFieldDefinitionType.FILE, then: Joi.array().items(FileValidationSchema).allow(null) },
40
+ { is: CustomFieldDefinitionType.IMAGE, then: Joi.array().items(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: statusValidationObject.allow(null) },
43
+ { is: CustomFieldDefinitionType.STATUS, then: Joi.string().allow(null) },
44
44
  { is: CustomFieldDefinitionType.TEXT, then: Joi.string().allow(null) },
45
45
  ],
46
46
  });
@@ -56,7 +56,8 @@ const CustomFieldDefinitionCreationSchema = Joi.object({
56
56
  description: Joi.string(),
57
57
  required: Joi.boolean(),
58
58
  disabled: Joi.boolean(),
59
- });
59
+ blockEditingFromUI: Joi.boolean(),
60
+ }).oxor('required', 'blockEditingFromUI', { isPresent: (value) => value === true });
60
61
 
61
62
  const CustomFieldDefinitionUpdateSchema = Joi.object({
62
63
  displayName: Joi.string(),
@@ -66,10 +67,9 @@ const CustomFieldDefinitionUpdateSchema = Joi.object({
66
67
  description: Joi.string().allow(null),
67
68
  required: Joi.boolean(),
68
69
  disabled: Joi.boolean(),
69
- });
70
+ blockEditingFromUI: Joi.boolean(),
71
+ }).oxor('required', 'blockEditingFromUI', { isPresent: (value) => value === true });
70
72
 
71
- export const validateCustomFieldDefinitionCreation = (payload) =>
72
- CustomFieldDefinitionCreationSchema.validateAsync(payload, { abortEarly: false });
73
+ export const validateCustomFieldDefinitionCreation = (payload) => CustomFieldDefinitionCreationSchema.validateAsync(payload, { abortEarly: false });
73
74
 
74
- export const validateCustomFieldDefinitionUpdate = (payload) =>
75
- CustomFieldDefinitionUpdateSchema.validateAsync(payload, { abortEarly: false });
75
+ export const validateCustomFieldDefinitionUpdate = (payload) => CustomFieldDefinitionUpdateSchema.validateAsync(payload, { abortEarly: false });
@@ -1,14 +1,11 @@
1
- import { handleError, BadRequest } from '@autofleet/errors';
1
+ import type { Response } from 'express';
2
+ import { handleError, BadRequest, type LogPayload } from '@autofleet/errors';
2
3
  import { ValidationError as InputValidationError } from 'joi';
3
4
  import { ValidationError as DatabaseValidationError } from 'sequelize';
4
5
 
5
- export default (err, res, additionalData = undefined) => {
6
+ export default (err, res: Response, additionalData: LogPayload = undefined) => {
6
7
  let error = err;
7
- if (err instanceof InputValidationError) {
8
- error = new BadRequest([err], null);
9
- }
10
-
11
- if (err instanceof DatabaseValidationError) {
8
+ if ([InputValidationError, DatabaseValidationError].some((ErrClass) => err instanceof ErrClass)) {
12
9
  error = new BadRequest([err], null);
13
10
  }
14
11
 
@@ -1,9 +1,11 @@
1
- import { Router } from 'express';
2
-
1
+ import { Router } from '@autofleet/node-common';
2
+ import logger from '../../utils/logger';
3
3
  import definitionRouter from './definition';
4
+ import validatorRouter from './validator';
4
5
 
5
- const router = Router({ mergeParams: true });
6
+ const router = Router({ logger });
6
7
 
7
8
  router.use('/custom-field-definitions/:modelName', definitionRouter);
9
+ router.use('/custom-validators/:modelName', validatorRouter);
8
10
 
9
11
  export default router;
@@ -0,0 +1,141 @@
1
+ import { ResourceNotFoundError } from '@autofleet/errors';
2
+ import { Router } from '@autofleet/node-common';
3
+ import { StatusCodes } from 'http-status-codes';
4
+ import handleError from '../errors';
5
+ import * as ValidatorRepo from '../../../repository/validator';
6
+ import validations from './validations';
7
+ import logger from '../../../utils/logger';
8
+ import { validateValidatorSchema } from '../../../utils/validations/schema/validator-schema';
9
+ import type { CustomValidator } from '../../../models';
10
+
11
+ const router = Router({ logger });
12
+ const ENTITY = 'CustomValidator';
13
+
14
+ /**
15
+ * Create
16
+ */
17
+ router.post<{ modelName: string }>('/', async (req, res) => {
18
+ const { modelName } = req.params;
19
+ try {
20
+ // Validate the request body
21
+ const validatedPayload = await validations.create.validateAsync(req.body);
22
+
23
+ // Validate that the schema is a valid AJV schema
24
+ validateValidatorSchema(validatedPayload.schema);
25
+
26
+ const validator = await ValidatorRepo.create({
27
+ ...validatedPayload,
28
+ modelType: modelName,
29
+ });
30
+
31
+ return res.status(StatusCodes.CREATED).json(validator);
32
+ } catch (err) {
33
+ return handleError(err, res, { logger, message: `Error in create ${ENTITY} request` });
34
+ }
35
+ });
36
+
37
+ /**
38
+ * Get all
39
+ */
40
+ router.get<
41
+ { modelName: string },
42
+ { validators: CustomValidator[] },
43
+ never,
44
+ { entityId?: string; entityType?: string }
45
+ >('/', async (req, res) => {
46
+ try {
47
+ const { modelName } = req.params;
48
+ const { entityId, entityType } = req.query;
49
+
50
+ const where = {
51
+ modelType: modelName,
52
+ ...(entityId && { entityId }),
53
+ ...(entityType && { entityType }),
54
+ };
55
+
56
+ const validators = await ValidatorRepo.findAll(where);
57
+
58
+ return res.status(StatusCodes.OK).json({ validators });
59
+ } catch (err) {
60
+ return handleError(err, res, { logger, message: `Error in get all ${ENTITY} request` });
61
+ }
62
+ });
63
+
64
+ /**
65
+ * Get by id
66
+ */
67
+ router.get<{ modelName: string; validatorId: string }, CustomValidator>('/:validatorId', async (req, res) => {
68
+ try {
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 });
72
+
73
+ if (!validators.length) {
74
+ throw new ResourceNotFoundError('Validator not found');
75
+ }
76
+
77
+ return res.status(StatusCodes.OK).json(validators[0]);
78
+ } catch (err) {
79
+ return handleError(err, res, { logger, message: `Error in get ${ENTITY} request` });
80
+ }
81
+ });
82
+
83
+ /**
84
+ * Update
85
+ */
86
+ router.patch<{ modelName: string; validatorId: string }, CustomValidator>('/:validatorId', async (req, res) => {
87
+ try {
88
+ const { validatorId } = req.params;
89
+
90
+ // Validate the request body
91
+ const validatedPayload = await validations.update.validateAsync(req.body);
92
+
93
+ // If schema is included in the update, validate that it's a valid AJV schema
94
+ if (validatedPayload.schema) {
95
+ validateValidatorSchema(validatedPayload.schema);
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
+
104
+ const [count, validators] = await ValidatorRepo.update(validatorId, validatedPayload);
105
+
106
+ if (!count) {
107
+ throw new ResourceNotFoundError('Validator not found');
108
+ }
109
+
110
+ return res.status(StatusCodes.OK).json(validators[0]);
111
+ } catch (err) {
112
+ return handleError(err, res, { logger, message: `Error in update ${ENTITY} request` });
113
+ }
114
+ });
115
+
116
+ /**
117
+ * Delete (disable)
118
+ */
119
+ router.delete<{ modelName: string; validatorId: string }>('/:validatorId', async (req, res) => {
120
+ try {
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
+
129
+ const [count] = await ValidatorRepo.disable(validatorId);
130
+
131
+ if (!count) {
132
+ throw new ResourceNotFoundError('Validator failed to be disabled');
133
+ }
134
+
135
+ return res.status(StatusCodes.NO_CONTENT).send();
136
+ } catch (err) {
137
+ return handleError(err, res, { logger, message: `Error in delete ${ENTITY} request` });
138
+ }
139
+ });
140
+
141
+ export default router;
@@ -0,0 +1,39 @@
1
+ import Joi from 'joi';
2
+
3
+ // Schema for validating JSON Schema objects
4
+ const jsonSchemaValidation = Joi.object().unknown(true);
5
+
6
+ const validationSchemas = {
7
+ create: Joi.object({
8
+ entityId: Joi.string().uuid().required(),
9
+ entityType: Joi.string().required(),
10
+ schema: Joi.object({
11
+ type: Joi.string().valid('object').required(),
12
+ properties: Joi.object({
13
+ before: jsonSchemaValidation,
14
+ after: jsonSchemaValidation,
15
+ }).required(),
16
+ if: Joi.object().optional(),
17
+ then: Joi.object().optional(),
18
+ else: Joi.object().optional(),
19
+ }).required(),
20
+ }),
21
+
22
+ update: Joi.object({
23
+ entityId: Joi.string().uuid(),
24
+ entityType: Joi.string(),
25
+ schema: Joi.object({
26
+ type: Joi.string().valid('object'),
27
+ properties: Joi.object({
28
+ before: jsonSchemaValidation,
29
+ after: jsonSchemaValidation,
30
+ }),
31
+ if: Joi.object().optional(),
32
+ then: Joi.object().optional(),
33
+ else: Joi.object().optional(),
34
+ }),
35
+ disabled: Joi.boolean(),
36
+ }).min(1),
37
+ };
38
+
39
+ export default validationSchemas;
@@ -1,5 +1,7 @@
1
1
  /* eslint-disable max-classes-per-file */
2
2
  import { BadRequest } from '@autofleet/errors';
3
+ import type { ValidationError } from 'joi';
4
+ import type { EntriesValidationError } from '../types/entries';
3
5
 
4
6
  export class MissingRequiredCustomFieldError extends BadRequest {
5
7
  constructor(missingFields: string[]) {
@@ -25,11 +27,37 @@ export class UnsupportedCustomValidationError extends BadRequest {
25
27
  }
26
28
  }
27
29
 
30
+ export class InvalidFieldTypeError extends BadRequest {
31
+ constructor(fieldType: string) {
32
+ const err = new Error(`Invalid field type ${fieldType}`);
33
+ super([err], null, null);
34
+ this.message = 'INVALID_FIELD_TYPE';
35
+ }
36
+ }
37
+
28
38
  export class InvalidValueError extends BadRequest {
29
- constructor(value: any, fieldType: string) {
30
- const err = new Error(`Invalid "${fieldType}" value ${JSON.stringify(value)}`);
39
+ constructor(value: any, fieldDefinitionName: string, joiValidationError: ValidationError) {
40
+ const formattedErrorMessage = joiValidationError.message
41
+ .replace(/"/g, '')
42
+ .replace('value', `'${fieldDefinitionName}'`);
43
+
44
+ const formattedValue = typeof value === 'object' ? JSON.stringify(value) : value;
45
+
46
+ const invalidValueMessage = `Invalid Value on field '${fieldDefinitionName}'. ${formattedErrorMessage}. received: '${formattedValue}'`;
47
+
48
+ const err = new Error(invalidValueMessage);
31
49
  super([err], null, null);
32
- this.message = 'INVALID_VALUE';
50
+ this.message = invalidValueMessage;
51
+ }
52
+ }
53
+
54
+ export class InvalidEntriesError extends BadRequest {
55
+ constructor(modelId: string, validationErrors: EntriesValidationError[]) {
56
+ const errors = validationErrors.map((validationError) => new InvalidValueError(validationError.value, validationError.fieldDefinitionName, validationError.joiValidationError));
57
+ super(errors, null, null);
58
+ this.message = `Invalid entries on ${modelId}\n${validationErrors.map((validationError) => (
59
+ `${validationError.fieldDefinitionName} - ${validationError.joiValidationError.message}`
60
+ )).join('\n')}`;
33
61
  }
34
62
  }
35
63
 
@@ -1,21 +1,29 @@
1
1
  import Events from '@autofleet/events';
2
+ import { getUser } from '@autofleet/zehut';
2
3
  import logger from '../utils/logger';
4
+ import type {
5
+ CustomFieldDefinition,
6
+ CustomFieldEntries,
7
+ CustomFieldValue,
8
+ CustomValidator,
9
+ } from '../models';
3
10
 
4
- const events = new Events({ logger });
11
+ const events = new Events({
12
+ logger,
13
+ getUserId: (payload) => payload?.user_id ?? getUser()?.id ?? null,
14
+ });
5
15
 
6
- const KEYS_TO_CONVERT = ['value', 'defaultValue'];
16
+ const KEYS_TO_CONVERT = ['value', 'defaultValue', 'blockEditingFromUI'];
7
17
 
8
- const stringifyBooleans = (savedObject: any, keysToConvert: Array<string>) => {
9
- if (!Object.keys(savedObject).some((key) => keysToConvert.includes(key))) {
18
+ const stringifyBooleans = (savedObject: any, keysToConvert: string[]) => {
19
+ const savedObjectKeySet = new Set(Object.keys(savedObject));
20
+ if (keysToConvert.every((key) => !savedObjectKeySet.has(key))) {
10
21
  return savedObject;
11
22
  }
12
- const objectToReturn = { ...savedObject };
13
- keysToConvert.forEach((key) => {
14
- if (typeof savedObject[key] === 'boolean') {
15
- objectToReturn[key] = savedObject[key].toString();
16
- }
17
- });
18
- return objectToReturn;
23
+ return {
24
+ ...savedObject,
25
+ ...Object.fromEntries(keysToConvert.map((key) => [key, typeof savedObject[key] === 'boolean' ? savedObject[key].toString() : savedObject[key]])),
26
+ };
19
27
  };
20
28
 
21
29
  const modelTableMapping = {
@@ -27,9 +35,13 @@ const modelTableMapping = {
27
35
  tableName: 'dim_custom_field_value',
28
36
  eventVersion: '1',
29
37
  },
38
+ CustomFieldEntries: {
39
+ tableName: 'dim_custom_field_entries',
40
+ eventVersion: '1',
41
+ },
30
42
  };
31
43
 
32
- export const sendDimEvent = (instance): void => {
44
+ export const sendDimEvent = (instance: CustomFieldDefinition | CustomFieldValue | CustomFieldEntries | CustomValidator): void => {
33
45
  const mapping = modelTableMapping[instance.constructor.name];
34
46
  if (!mapping) {
35
47
  return;
@@ -45,7 +57,7 @@ export const sendDimEvent = (instance): void => {
45
57
  mapping.tableName,
46
58
  mapping.eventVersion,
47
59
  objectToSend,
48
- );
60
+ ).catch(() => {});
49
61
  };
50
62
 
51
63
  export default events;
@@ -1,9 +1,10 @@
1
+ import type { WhereOptions } from 'sequelize';
1
2
  import logger from '../utils/logger';
2
- import * as ValueRepo from '../repository/value';
3
3
  import * as DefinitionRepo from '../repository/definition';
4
4
  import { MissingRequiredCustomFieldError } from '../errors';
5
- import type { ModelOptions } from '../types';
5
+ import type { CustomFieldOptions, ModelOptions } from '../types';
6
6
  import applyScopeToInstance from '../utils/scopeAttributes';
7
+ import updateInstanceValues from './utils/updateInstanceValues';
7
8
 
8
9
  /**
9
10
  * A hook to create the custom fields when updating a model (more then one instance).
@@ -17,43 +18,64 @@ export const beforeBulkCreate = (options): void => {
17
18
  * A hook to create the custom fields when updating a model instance.
18
19
  * TODO - cleanup if update fail
19
20
  */
20
- export const beforeCreate = (scopeAttributes: string[], modelOptions: ModelOptions = {}) => async (
21
+ export const beforeCreate = (
22
+ scopeAttributes: string[],
23
+ modelOptions: ModelOptions = {},
24
+ sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'> = { useCustomFieldsEntries: false },
25
+ ) => async (
21
26
  instance,
22
27
  options,
23
28
  ): Promise<void> => {
24
29
  logger.debug('sadot - before create hook');
25
30
  const { fields } = options;
31
+ const { include, useEntityIdFromInclude } = modelOptions;
26
32
  const modelType = instance.constructor.name;
27
33
 
28
34
  const identifiers = applyScopeToInstance(instance, scopeAttributes);
29
35
 
30
- // get all model's required definitions
31
- const requiredFieldsNames = await DefinitionRepo.getRequiredFields(modelType, instance.id, identifiers, modelOptions);
36
+ const where: WhereOptions = {
37
+ modelType,
38
+ disabled: false,
39
+ ...(!useEntityIdFromInclude && { entityId: identifiers }),
40
+ };
41
+ const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: false, transaction: options.transaction, include: include?.(identifiers) });
42
+ const requiredFieldsNames = Array.from(new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)));
43
+
44
+ const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
45
+ if (fieldsWithDefaultValue.length) {
46
+ // eslint-disable-next-line no-param-reassign
47
+ instance.customFields ||= {};
48
+ fieldsWithDefaultValue.filter((def) => (instance.customFields?.[def.name] === undefined)).forEach(({ name, defaultValue }) => {
49
+ // eslint-disable-next-line no-param-reassign
50
+ instance.customFields[name] = defaultValue;
51
+ });
52
+ }
32
53
 
33
- const customFieldsIdx = fields.indexOf('customFields');
34
54
  const { customFields } = instance;
35
- if (customFieldsIdx > -1 && customFields) {
36
- const fieldsNames = Object.keys(customFields);
37
- const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
38
- if (missingFields?.length > 0) {
39
- throw new MissingRequiredCustomFieldError(missingFields);
40
- }
41
-
42
- await ValueRepo.updateValues(
43
- modelType,
44
- instance.id,
45
- identifiers,
46
- customFields,
47
- {
48
- transaction: options.transaction,
49
- modelOptions,
50
- },
51
- true,
52
- );
55
+ const fieldsNames = Object.keys(customFields ?? {});
56
+ const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
57
+ if (missingFields?.length) {
58
+ throw new MissingRequiredCustomFieldError(missingFields);
59
+ }
53
60
 
54
- // eslint-disable-next-line no-param-reassign
55
- fields.splice(customFieldsIdx, 1);
56
- } else if (requiredFieldsNames?.length > 0) {
57
- throw new MissingRequiredCustomFieldError(requiredFieldsNames);
61
+ const customFieldsIdx = fields.indexOf('customFields');
62
+ if (customFieldsIdx === -1 || !customFields || !Object.keys(customFields).length) {
63
+ // After checking for required fields and fields with default values, and we have no custom fields.
64
+ return;
58
65
  }
66
+
67
+ await updateInstanceValues({
68
+ modelId: instance.id,
69
+ modelType,
70
+ identifiers,
71
+ customFields,
72
+ options: {
73
+ useCustomFieldsEntries: sadotOptions.useCustomFieldsEntries,
74
+ transaction: options.transaction,
75
+ modelOptions,
76
+ },
77
+ });
78
+
79
+ // eslint-disable-next-line no-param-reassign
80
+ fields.splice(customFieldsIdx, 1);
59
81
  };