@autofleet/sadot 0.0.1-beta → 0.0.1-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.
Files changed (152) hide show
  1. package/dist/{src/api → api}/index.js +1 -0
  2. package/dist/api/index.js.map +1 -0
  3. package/dist/{src/api → api}/v1/definition/index.js +28 -25
  4. package/dist/api/v1/definition/index.js.map +1 -0
  5. package/dist/api/v1/definition/validations.js +36 -0
  6. package/dist/api/v1/definition/validations.js.map +1 -0
  7. package/dist/{src/api → api}/v1/errors.js +1 -0
  8. package/dist/api/v1/errors.js.map +1 -0
  9. package/dist/{src/api → api}/v1/index.js +2 -1
  10. package/dist/api/v1/index.js.map +1 -0
  11. package/dist/{src/events → events}/index.js +2 -21
  12. package/dist/events/index.js.map +1 -0
  13. package/dist/{src/hooks → hooks}/create.js +18 -15
  14. package/dist/hooks/create.js.map +1 -0
  15. package/dist/{src/hooks/enrich.js → hooks/find.js} +29 -52
  16. package/dist/hooks/find.js.map +1 -0
  17. package/dist/{src/hooks → hooks}/index.js +4 -5
  18. package/dist/hooks/index.js.map +1 -0
  19. package/dist/{src/hooks → hooks}/update.js +23 -8
  20. package/dist/hooks/update.js.map +1 -0
  21. package/dist/{src/hooks → hooks}/workaround.js +15 -5
  22. package/dist/hooks/workaround.js.map +1 -0
  23. package/dist/index.js +80 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/{src/models → models}/CustomFieldDefinition.js +17 -22
  26. package/dist/models/CustomFieldDefinition.js.map +1 -0
  27. package/dist/{src/models → models}/CustomFieldValue.js +39 -24
  28. package/dist/models/CustomFieldValue.js.map +1 -0
  29. package/dist/models/index.js +50 -0
  30. package/dist/models/index.js.map +1 -0
  31. package/dist/{src/models → models}/tests/AssociatedTestModel.js +1 -0
  32. package/dist/models/tests/AssociatedTestModel.js.map +1 -0
  33. package/dist/{src/models → models}/tests/TestModel.js +1 -0
  34. package/dist/models/tests/TestModel.js.map +1 -0
  35. package/dist/repository/definition.js +107 -0
  36. package/dist/repository/definition.js.map +1 -0
  37. package/dist/{src/repository → repository}/value.js +36 -27
  38. package/dist/repository/value.js.map +1 -0
  39. package/dist/{src/tests → tests}/api/test-api.js +22 -12
  40. package/dist/tests/api/test-api.js.map +1 -0
  41. package/dist/tests/helpers/index.js +28 -0
  42. package/dist/tests/helpers/index.js.map +1 -0
  43. package/dist/tests/mocks/index.js +60 -0
  44. package/dist/tests/mocks/index.js.map +1 -0
  45. package/dist/{src/tests → tests}/mocks/testModel.js +18 -10
  46. package/dist/tests/mocks/testModel.js.map +1 -0
  47. package/dist/tsconfig.tsbuildinfo +1 -0
  48. package/dist/{src/types → types}/definition/index.js +1 -0
  49. package/dist/types/definition/index.js.map +1 -0
  50. package/dist/{src/types → types}/index.js +1 -0
  51. package/dist/types/index.js.map +1 -0
  52. package/dist/{src/types → types}/value/index.js +1 -0
  53. package/dist/types/value/index.js.map +1 -0
  54. package/dist/{src/utils → utils}/db/index.js +1 -8
  55. package/dist/utils/db/index.js.map +1 -0
  56. package/dist/{src/utils → utils}/logger/index.js +2 -2
  57. package/dist/utils/logger/index.js.map +1 -0
  58. package/dist/{src/utils → utils}/validations/custom-fields.js +1 -0
  59. package/dist/utils/validations/custom-fields.js.map +1 -0
  60. package/dist/utils/validations/custom.js +58 -0
  61. package/dist/utils/validations/custom.js.map +1 -0
  62. package/dist/{src/utils → utils}/validations/index.js +1 -0
  63. package/dist/utils/validations/index.js.map +1 -0
  64. package/dist/utils/validations/type.js +32 -0
  65. package/dist/utils/validations/type.js.map +1 -0
  66. package/package.json +5 -12
  67. package/src/api/v1/definition/index.ts +13 -20
  68. package/src/api/v1/definition/validations.ts +27 -13
  69. package/src/api/v1/index.ts +1 -1
  70. package/src/events/index.ts +1 -23
  71. package/src/hooks/create.ts +6 -12
  72. package/src/hooks/find.ts +82 -23
  73. package/src/hooks/index.ts +2 -4
  74. package/src/hooks/update.ts +11 -5
  75. package/src/hooks/workaround.ts +2 -2
  76. package/src/index.ts +17 -60
  77. package/src/models/CustomFieldDefinition.ts +15 -23
  78. package/src/models/CustomFieldValue.ts +10 -10
  79. package/src/models/index.ts +17 -79
  80. package/src/models/tests/AssociatedTestModel.ts +1 -0
  81. package/src/models/tests/TestModel.ts +1 -0
  82. package/src/repository/definition.ts +41 -27
  83. package/src/repository/value.ts +11 -12
  84. package/src/tests/mocks/events.mock.ts +1 -1
  85. package/src/tests/mocks/{definition.mock.ts → index.ts} +6 -5
  86. package/src/tests/mocks/testModel.ts +7 -12
  87. package/src/types/definition/index.ts +1 -0
  88. package/src/types/index.ts +6 -4
  89. package/src/types/value/index.ts +2 -1
  90. package/src/utils/db/index.ts +0 -7
  91. package/src/utils/logger/index.ts +1 -3
  92. package/src/utils/validations/custom.ts +45 -28
  93. package/src/utils/validations/type.ts +6 -23
  94. package/tsconfig.json +26 -9
  95. package/dist/jest.config.d.ts +0 -12
  96. package/dist/src/api/index.d.ts +0 -2
  97. package/dist/src/api/v1/definition/index.d.ts +0 -2
  98. package/dist/src/api/v1/definition/validations.d.ts +0 -2
  99. package/dist/src/api/v1/definition/validations.js +0 -36
  100. package/dist/src/api/v1/errors.d.ts +0 -2
  101. package/dist/src/api/v1/index.d.ts +0 -2
  102. package/dist/src/errors/index.d.ts +0 -16
  103. package/dist/src/errors/index.js +0 -45
  104. package/dist/src/events/index.d.ts +0 -4
  105. package/dist/src/hooks/create.d.ts +0 -9
  106. package/dist/src/hooks/enrich.d.ts +0 -5
  107. package/dist/src/hooks/find.d.ts +0 -1
  108. package/dist/src/hooks/find.js +0 -29
  109. package/dist/src/hooks/index.d.ts +0 -6
  110. package/dist/src/hooks/update.d.ts +0 -9
  111. package/dist/src/hooks/workaround.d.ts +0 -10
  112. package/dist/src/index.d.ts +0 -11
  113. package/dist/src/index.js +0 -105
  114. package/dist/src/models/CustomFieldDefinition.d.ts +0 -23
  115. package/dist/src/models/CustomFieldValue.d.ts +0 -15
  116. package/dist/src/models/index.d.ts +0 -8
  117. package/dist/src/models/index.js +0 -87
  118. package/dist/src/models/tests/AssociatedTestModel.d.ts +0 -12
  119. package/dist/src/models/tests/TestModel.d.ts +0 -11
  120. package/dist/src/repository/definition.d.ts +0 -17
  121. package/dist/src/repository/definition.js +0 -80
  122. package/dist/src/repository/value.d.ts +0 -24
  123. package/dist/src/tests/api/test-api.d.ts +0 -2
  124. package/dist/src/tests/helpers/database-config.d.ts +0 -15
  125. package/dist/src/tests/helpers/database-config.js +0 -16
  126. package/dist/src/tests/helpers/index.d.ts +0 -2
  127. package/dist/src/tests/helpers/index.js +0 -18
  128. package/dist/src/tests/mocks/definition.mock.d.ts +0 -37
  129. package/dist/src/tests/mocks/definition.mock.js +0 -64
  130. package/dist/src/tests/mocks/events.mock.d.ts +0 -3
  131. package/dist/src/tests/mocks/events.mock.js +0 -19
  132. package/dist/src/tests/mocks/testModel.d.ts +0 -12
  133. package/dist/src/types/definition/index.d.ts +0 -23
  134. package/dist/src/types/index.d.ts +0 -13
  135. package/dist/src/types/value/index.d.ts +0 -15
  136. package/dist/src/utils/constants/index.d.ts +0 -1
  137. package/dist/src/utils/constants/index.js +0 -5
  138. package/dist/src/utils/db/index.d.ts +0 -4
  139. package/dist/src/utils/logger/index.d.ts +0 -2
  140. package/dist/src/utils/validations/custom-fields.d.ts +0 -2
  141. package/dist/src/utils/validations/custom.d.ts +0 -15
  142. package/dist/src/utils/validations/custom.js +0 -42
  143. package/dist/src/utils/validations/index.d.ts +0 -2
  144. package/dist/src/utils/validations/type.d.ts +0 -18
  145. package/dist/src/utils/validations/type.js +0 -50
  146. package/dist/src/utils/validations/validators.d.ts +0 -12
  147. package/dist/src/utils/validations/validators.js +0 -33
  148. package/src/errors/index.ts +0 -42
  149. package/src/hooks/enrich.ts +0 -125
  150. package/src/tests/helpers/database-config.ts +0 -14
  151. package/src/utils/constants/index.ts +0 -2
  152. package/src/utils/validations/validators.ts +0 -34
package/package.json CHANGED
@@ -1,39 +1,32 @@
1
1
  {
2
2
  "name": "@autofleet/sadot",
3
- "version": "0.0.1-beta",
3
+ "version": "0.0.1-beta.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
7
  "start": "ts-node src/index.ts",
8
8
  "build": "rm -rf dist && tsc",
9
9
  "linter": "./node_modules/.bin/eslint .",
10
- "test": "jest --forceExit --runInBand",
11
- "coverage": "jest --coverage --forceExit --runInBand && rm -rf ./coverage",
10
+ "test": "jest --forceExit",
11
+ "coverage": "jest --coverage --forceExit && rm -rf ./coverage",
12
12
  "dev": "nodemon"
13
13
  },
14
14
  "dependencies": {
15
15
  "@autofleet/errors": "^1.0.10",
16
16
  "@autofleet/events": "^2.0.0",
17
- "@autofleet/logger": "^2.0.5",
18
17
  "@hapi/joi": "^17.1.1",
19
18
  "express": "^4.18.2",
20
- "pg": "^8.10.0",
21
- "reflect-metadata": "^0.1.13",
22
- "sequelize": "^6.31.1",
23
- "sequelize-typescript": "^2.1.5",
24
- "uuid": "^9.0.0"
19
+ "sequelize": "^6.31.1"
25
20
  },
26
21
  "devDependencies": {
27
22
  "@types/express": "^4.17.17",
28
23
  "@types/jest": "^27.0.9",
29
- "@types/node": "^20.5.6",
30
- "@types/uuid": "^9.0.2",
31
24
  "@typescript-eslint/eslint-plugin": "^4.8.1",
32
- "@typescript-eslint/parser": "^4.33.0",
33
25
  "eslint": "^7.13.0",
34
26
  "eslint-config-airbnb-typescript": "^12.0.0",
35
27
  "eslint-plugin-import": "^2.22.1",
36
28
  "jest": "^27.0.5",
29
+ "sequelize-typescript": "^2.1.5",
37
30
  "ts-jest": "^27.0.3",
38
31
  "ts-node": "^8.6.2",
39
32
  "typescript": "^4.9.5",
@@ -10,7 +10,7 @@ import logger from '../../../utils/logger';
10
10
  const router = Router({ mergeParams: true });
11
11
  const ENTITY = 'CustomFieldDefinition';
12
12
 
13
- const toPascalCase = (str: string): string => str.replace(/(^\w|-\w)/g, (subStr) => subStr.replace(/-/, '').toUpperCase());
13
+ const toPascalCase = (str: string): string => str.replace(/(^\w|-\w)/g, subStr => subStr.replace(/-/, '').toUpperCase());
14
14
 
15
15
  /**
16
16
  * Create
@@ -19,13 +19,13 @@ router.post('/', async (req: Request, res: Response) => {
19
19
  const { modelName } = req.params as any;
20
20
  const modelType = toPascalCase(modelName);
21
21
  try {
22
- const validatedPayload: CreateCustomFieldDefinition = await
23
- validateCustomFieldDefinitionCreation(req.body);
22
+ const validatedPayload: CreateCustomFieldDefinition =
23
+ await validateCustomFieldDefinitionCreation({
24
+ ...req.body,
25
+ modelType,
26
+ });
24
27
 
25
- const customFieldDefinition = await DefinitionRepo.create({
26
- ...validatedPayload,
27
- modelType,
28
- });
28
+ const customFieldDefinition = await DefinitionRepo.create(validatedPayload);
29
29
  return res.status(201).json(customFieldDefinition);
30
30
  } catch (err) {
31
31
  logger.error('Failed to create custom field definition', err);
@@ -57,18 +57,11 @@ router.get('/:customFieldDefinitionId', async (req, res) => {
57
57
  */
58
58
  router.get('/', async (req, res) => {
59
59
  const { modelName } = req.params as any;
60
- const { entityIds } = req.query as any;
61
-
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(
69
- { ...where },
70
- { withDisabled: true },
71
- );
62
+ const customFieldDefinitions = await DefinitionRepo.findAll({
63
+ modelType,
64
+ });
72
65
  return res.json(customFieldDefinitions);
73
66
  } catch (err) {
74
67
  logger.error('Failed to fetch custom field definitions', err);
@@ -83,8 +76,8 @@ router.patch('/:customFieldDefinitionId', async (req, res) => {
83
76
  const { customFieldDefinitionId, modelName } = req.params as any;
84
77
  const modelType = toPascalCase(modelName);
85
78
  try {
86
- // eslint-disable-next-line max-len
87
- const validatedPayload: UpdateCustomFieldDefinition = await validateCustomFieldDefinitionUpdate(req.body);
79
+ const validatedPayload: UpdateCustomFieldDefinition =
80
+ await validateCustomFieldDefinitionUpdate(req.body);
88
81
 
89
82
  const customFieldDefinition = await DefinitionRepo.findByWhere({
90
83
  id: customFieldDefinitionId,
@@ -97,7 +90,7 @@ router.patch('/:customFieldDefinitionId', async (req, res) => {
97
90
 
98
91
  const updatedCustomFieldDefinition = await DefinitionRepo.update(
99
92
  customFieldDefinitionId,
100
- { ...validatedPayload, modelType },
93
+ validatedPayload,
101
94
  );
102
95
 
103
96
  return res.status(200).json(updatedCustomFieldDefinition);
@@ -1,19 +1,22 @@
1
1
  import Joi from '@hapi/joi';
2
- import { CustomFieldDefinitionType } from '../../../utils/validations/type';
3
-
4
- const ValidationSchema = Joi.when('fieldType', {
5
- is: CustomFieldDefinitionType.SELECT,
6
- then: Joi.array().items(Joi.string()).min(1).unique(),
7
- otherwise: Joi.any(),
8
- });
2
+ import { CustomFieldDefinitionType } from '../../../models/CustomFieldDefinition';
9
3
 
10
4
  const CustomFieldDefinitionCreationSchema = Joi.object({
11
5
  name: Joi.string().required(),
12
6
  displayName: Joi.string().required(),
13
- validation: ValidationSchema,
14
- fieldType: Joi.string().valid(...Object.values(CustomFieldDefinitionType)).required(),
7
+ validation: Joi.object().required(),
8
+ fieldType: Joi.string().valid(
9
+ CustomFieldDefinitionType.BOOLEAN,
10
+ CustomFieldDefinitionType.NUMBER,
11
+ CustomFieldDefinitionType.DATE,
12
+ CustomFieldDefinitionType.DATETIME,
13
+ CustomFieldDefinitionType.TEXT,
14
+ CustomFieldDefinitionType.IMAGE,
15
+ CustomFieldDefinitionType.ENUM,
16
+ ).required(),
15
17
  entityId: Joi.string().guid().required(),
16
18
  entityType: Joi.string().required(),
19
+ modelType: Joi.string().required(),
17
20
  description: Joi.string(),
18
21
  required: Joi.boolean(),
19
22
  disabled: Joi.boolean(),
@@ -21,15 +24,26 @@ const CustomFieldDefinitionCreationSchema = Joi.object({
21
24
 
22
25
  const CustomFieldDefinitionUpdateSchema = Joi.object({
23
26
  displayName: Joi.string(),
24
- validation: ValidationSchema,
25
- fieldType: Joi.string().valid(...Object.values(CustomFieldDefinitionType)),
27
+ validation: Joi.object(),
28
+ fieldType: Joi.string().valid(
29
+ CustomFieldDefinitionType.BOOLEAN,
30
+ CustomFieldDefinitionType.NUMBER,
31
+ CustomFieldDefinitionType.DATE,
32
+ CustomFieldDefinitionType.DATETIME,
33
+ CustomFieldDefinitionType.TEXT,
34
+ CustomFieldDefinitionType.IMAGE,
35
+ CustomFieldDefinitionType.ENUM,
36
+ ),
37
+ entityId: Joi.string().guid(),
38
+ entityType: Joi.string(),
39
+ modelType: Joi.string(),
26
40
  description: Joi.string(),
27
41
  required: Joi.boolean(),
28
42
  disabled: Joi.boolean(),
29
43
  });
30
44
 
31
- export const validateCustomFieldDefinitionCreation = (payload) =>
45
+ export const validateCustomFieldDefinitionCreation = payload =>
32
46
  CustomFieldDefinitionCreationSchema.validateAsync(payload, { abortEarly: false });
33
47
 
34
- export const validateCustomFieldDefinitionUpdate = (payload) =>
48
+ export const validateCustomFieldDefinitionUpdate = payload =>
35
49
  CustomFieldDefinitionUpdateSchema.validateAsync(payload, { abortEarly: false });
@@ -4,6 +4,6 @@ import definitionRouter from './definition';
4
4
 
5
5
  const router = Router({ mergeParams: true });
6
6
 
7
- router.use('/custom-field-definitions/:modelName', definitionRouter);
7
+ router.use('/:modelName/custom-field-definitions', definitionRouter);
8
8
 
9
9
  export default router;
@@ -3,21 +3,6 @@ import logger from '../utils/logger';
3
3
 
4
4
  const events = new Events({ logger });
5
5
 
6
- const KEYS_TO_CONVERT = ['value'];
7
-
8
- const stringifyBools = (savedObject: any, keysToConvert: Array<string>) => {
9
- if (Object.keys(savedObject).some((key) => keysToConvert.includes(key))) {
10
- const objectToReturn = { ...savedObject };
11
- keysToConvert.forEach((key) => {
12
- if (typeof savedObject[key] === 'boolean') {
13
- objectToReturn[key] = savedObject[key].toString();
14
- }
15
- });
16
- return objectToReturn;
17
- }
18
- return savedObject;
19
- };
20
-
21
6
  const modelTableMapping = {
22
7
  CustomFieldDefinition: {
23
8
  tableName: 'dim_custom_field_definition',
@@ -32,17 +17,10 @@ const modelTableMapping = {
32
17
  export const sendDimEvent = (instance): void => {
33
18
  const mapping = modelTableMapping[instance.constructor.name];
34
19
  if (mapping) {
35
- let objectToSend = instance.get();
36
- try {
37
- objectToSend = stringifyBools(instance.get(), KEYS_TO_CONVERT);
38
- } catch (err) {
39
- logger.error('Failed to convert booleans in dim event payload', err);
40
- }
41
-
42
20
  events.sendObject(
43
21
  mapping.tableName,
44
22
  mapping.eventVersion,
45
- objectToSend,
23
+ instance.get(),
46
24
  );
47
25
  }
48
26
  };
@@ -1,7 +1,5 @@
1
- import logger from '../utils/logger';
2
1
  import * as ValueRepo from '../repository/value';
3
2
  import * as DefinitionRepo from '../repository/definition';
4
- import { MissingRequiredCustomFieldError } from '../errors';
5
3
 
6
4
  /**
7
5
  * A hook to create the custom fields when updating a model (more then one instance).
@@ -19,33 +17,29 @@ export const beforeCreate = (scopeAttributes: string[]) => async (
19
17
  instance,
20
18
  options,
21
19
  ): Promise<void> => {
22
- logger.debug('sadot - before create hook');
23
20
  const { fields } = options;
24
21
  const modelType = instance.constructor.name;
25
- const identifiers = scopeAttributes.map((attribute) => instance[attribute]);
22
+ const identifiers = scopeAttributes.map(attribute => instance[attribute]);
26
23
  // get all model's required definitions
27
- const requiredFieldsNames = await DefinitionRepo.getRequiredFields(modelType,
28
- instance.id,
29
- identifiers);
24
+ const requiredFieldsNames = await DefinitionRepo.getRequiredFields(modelType, instance.id, identifiers);
30
25
  const customFieldsIdx = fields.indexOf('customFields');
31
26
  const { customFields } = instance;
32
27
  if (customFieldsIdx > -1 && customFields) {
33
28
  const fieldsNames = Object.keys(customFields);
34
- const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
35
- if (missingFields?.length > 0) {
36
- throw new MissingRequiredCustomFieldError(missingFields);
29
+ if (requiredFieldsNames.some(name => !fieldsNames.includes(name))) {
30
+ throw new Error('some fields are required');
37
31
  }
38
32
 
39
33
  await ValueRepo.updateValues(
40
34
  modelType,
41
35
  instance.id,
42
- identifiers,
43
36
  customFields,
44
37
  { transaction: options.transaction },
45
38
  );
46
39
  // eslint-disable-next-line no-param-reassign
47
40
  fields.splice(customFieldsIdx, 1);
48
41
  } else if (requiredFieldsNames?.length > 0) {
49
- throw new MissingRequiredCustomFieldError(requiredFieldsNames);
42
+ throw new Error('some fields are required');
50
43
  }
51
44
  };
45
+
package/src/hooks/find.ts CHANGED
@@ -1,27 +1,86 @@
1
- /* eslint-disable no-param-reassign */
2
- import logger from '../utils/logger';
3
-
4
- const doScopeAttributesMissing = (
5
- scopeAttributes: string[],
6
- queryAttributes: (string | string[])[],
7
- ): string[] => {
8
- const attributes = scopeAttributes
9
- .filter((attribute) => !queryAttributes.includes(attribute));
10
- if (!queryAttributes.includes?.('id')) {
11
- attributes.push('id');
12
- }
13
- return attributes;
1
+
2
+ import * as ValueRepo from '../repository/value';
3
+ import * as DefinitionRepo from '../repository/definition';
4
+ import CustomFieldValue from '../models/CustomFieldValue';
5
+ import { SerializedCustomFields } from '../types/definition';
6
+
7
+ /**
8
+ * Serialize custom fields value into the format of {[name] -> [fieldData]}
9
+ */
10
+ const serializeCustomFields = (customFieldValues: CustomFieldValue[]): SerializedCustomFields => {
11
+ const customFields = customFieldValues.reduce((acc, cfv) => ({
12
+ ...acc,
13
+ [cfv.customFieldDefinition.name]: cfv.value,
14
+ }), {});
15
+ return customFields;
14
16
  };
15
17
 
16
- // eslint-disable-next-line import/prefer-default-export
17
- export const beforeFind = (scopeAttributes: string[]) => (options): void => {
18
- const { attributes: queryAttributes } = options;
19
- if (queryAttributes?.includes('customFields')) {
20
- const missingScopeAttributes = doScopeAttributesMissing(scopeAttributes, queryAttributes);
21
- logger.debug('sadot - before find hook');
22
- if (missingScopeAttributes?.length > 0) {
23
- queryAttributes.push(...missingScopeAttributes);
24
- options.attributesToRemove = missingScopeAttributes;
18
+ /**
19
+ * A hook to attach the custom fields when fetching a model instances.
20
+ */
21
+ const afterFind = (scopeAttributes: string[]) => async (
22
+ instancesOrInstance: any | any[],
23
+ options,
24
+ ): Promise<void> => {
25
+ const primaryKey = 'id';
26
+ const instances = Array.isArray(instancesOrInstance)
27
+ ? instancesOrInstance
28
+ : [instancesOrInstance];
29
+
30
+ const identifiers = instances.map(instance => scopeAttributes.map(attr => instance[attr])).flat();
31
+ const uniqueIdentifiers = [...new Set(identifiers)];
32
+ const identifierCustomFieldDefinitionsMapping = {};
33
+ await Promise.all(uniqueIdentifiers.map(async (identifier) => {
34
+ const customFieldDefinitions = await DefinitionRepo.findByEntityId(
35
+ identifier,
36
+ options.transaction,
37
+ );
38
+ identifierCustomFieldDefinitionsMapping[identifier] = customFieldDefinitions;
39
+ }));
40
+
41
+ // Get the values per instates ids:
42
+ const instancesIds = instances.map(i => i[primaryKey]);
43
+
44
+ const customFieldValues = await ValueRepo.findValuesByModelIds(
45
+ instancesIds,
46
+ { transaction: options.transaction },
47
+ );
48
+
49
+ // Group fields by modelId
50
+ const valuesGroupByInstance: {
51
+ [modelId: string]: CustomFieldValue[];
52
+ } = customFieldValues.reduce((acc, v) => {
53
+ const { modelId } = v;
54
+ if (!acc[modelId]) {
55
+ acc[modelId] = [];
56
+ }
57
+ acc[modelId].push(v);
58
+ return acc;
59
+ }, {});
60
+
61
+ // Attach custom fields to the instances
62
+ instances.forEach((instance) => {
63
+ const customFields = {};
64
+ const { id } = instance;
65
+ const instanceValues = valuesGroupByInstance[id];
66
+ if (instanceValues) {
67
+ const serializedCustomFields = serializeCustomFields(instanceValues);
68
+ Object.assign(customFields, serializedCustomFields);
25
69
  }
26
- }
70
+
71
+ scopeAttributes.forEach((attribute) => {
72
+ const identifier = instance[attribute];
73
+ const customFieldDefinitions = identifierCustomFieldDefinitionsMapping[identifier];
74
+ if (customFieldDefinitions?.length > 0) {
75
+ customFieldDefinitions.forEach((customFieldDefinition) => {
76
+ if (customFields[customFieldDefinition.name] === undefined) {
77
+ customFields[customFieldDefinition.name] = null;
78
+ }
79
+ });
80
+ }
81
+ });
82
+ instance.setDataValue('customFields', customFields);
83
+ });
27
84
  };
85
+
86
+ export default afterFind;
@@ -1,12 +1,10 @@
1
- import enrichResults from './enrich';
2
- import { beforeFind } from './find';
1
+ import afterFind from './find';
3
2
  import { beforeBulkUpdate, beforeUpdate } from './update';
4
3
  import { beforeBulkCreate, beforeCreate } from './create';
5
4
  import workaround from './workaround';
6
5
 
7
6
  export {
8
- enrichResults,
9
- beforeFind,
7
+ afterFind,
10
8
  beforeBulkUpdate,
11
9
  beforeUpdate,
12
10
  beforeBulkCreate,
@@ -1,5 +1,5 @@
1
- import logger from '../utils/logger';
2
1
  import * as ValueRepo from '../repository/value';
2
+ import * as DefinitionRepo from '../repository/definition';
3
3
 
4
4
  /**
5
5
  * A hook to update the custom fields when updating a model (more then one instance).
@@ -9,7 +9,6 @@ export const beforeBulkUpdate = (options): void => {
9
9
  // eslint-disable-next-line no-param-reassign
10
10
  options.individualHooks = true;
11
11
  };
12
-
13
12
  /**
14
13
  * A hook to update the custom fields when updating a model instance.
15
14
  * TODO - cleanup if update fail
@@ -18,22 +17,29 @@ export const beforeUpdate = (scopeAttributes: string[]) => async (
18
17
  instance,
19
18
  options,
20
19
  ): Promise<void> => {
21
- logger.debug('sadot - before update hook');
22
20
  const { fields } = options;
23
21
  const modelType = instance.constructor.name;
24
- const identifiers = scopeAttributes.map((attribute) => instance[attribute]);
22
+ const identifiers = scopeAttributes.map(attribute => instance[attribute]);
23
+ // get all model's required definitions
24
+ const requiredNullFieldsNames = await DefinitionRepo.getRequiredFields(modelType, instance.id, identifiers, true);
25
25
 
26
26
  const customFieldsIdx = fields.indexOf('customFields');
27
27
  if (customFieldsIdx > -1) {
28
28
  const { customFields } = instance;
29
+ const fieldsNames = Object.keys(customFields);
30
+ if (requiredNullFieldsNames.some(name => !fieldsNames.includes(name))) {
31
+ throw new Error('some fields are required');
32
+ }
29
33
  await ValueRepo.updateValues(
30
34
  modelType,
31
35
  instance.id,
32
- identifiers,
33
36
  customFields,
34
37
  { transaction: options.transaction },
35
38
  );
36
39
  // eslint-disable-next-line no-param-reassign
37
40
  fields.splice(customFieldsIdx, 1);
41
+ } else if (requiredNullFieldsNames?.length > 0) {
42
+ throw new Error('some fields are required');
38
43
  }
39
44
  };
45
+
@@ -33,11 +33,11 @@ const handleChildrenAfterFindHook = async (instances, options, level = 0) => {
33
33
  }
34
34
 
35
35
  const { associations } = constructor;
36
- const associatedNames = Object.keys(instance).filter((attribute) =>
36
+ const associatedNames = Object.keys(instance).filter(attribute =>
37
37
  Object.keys(associations).includes(attribute));
38
38
 
39
39
  if (associatedNames.length) {
40
- const childInstances = associatedNames.map((name) => instance[name]);
40
+ const childInstances = associatedNames.map(name => instance[name]);
41
41
  return handleChildrenAfterFindHook(childInstances, options, level + 1);
42
42
  }
43
43
 
package/src/index.ts CHANGED
@@ -1,16 +1,8 @@
1
1
  import type { Application } from 'express';
2
2
  import { DataTypes } from 'sequelize';
3
- import { Sequelize } from 'sequelize-typescript';
4
- import { initTables, initTestModels } from './models';
3
+ import { initTables } from './models';
5
4
  import api from './api';
6
- import {
7
- beforeFind,
8
- enrichResults,
9
- beforeBulkUpdate,
10
- beforeUpdate,
11
- beforeCreate,
12
- beforeBulkCreate,
13
- } from './hooks';
5
+ import { afterFind, beforeBulkUpdate, beforeUpdate, beforeCreate, beforeBulkCreate } from './hooks';
14
6
  import initDB from './utils/db';
15
7
  import logger from './utils/logger';
16
8
  import { CustomFieldOptions, ModelFetcher, ModelOptions } from './types';
@@ -21,27 +13,19 @@ const addHooks = (models: ModelOptions[], getModel: ModelFetcher) => {
21
13
  models.forEach(async ({ name, scopeAttributes }) => {
22
14
  try {
23
15
  const model = getModel(name);
24
- if (!model) {
25
- logger.warn('sadot - tried to addHooks to a model that does not exist yet', {
26
- name,
27
- scopeAttributes,
28
- });
29
- return;
30
- }
16
+ if (!model) return;
31
17
  model.rawAttributes.customFields = {
32
18
  type: DataTypes.VIRTUAL,
33
19
  };
34
20
  model.refreshAttributes();
35
21
  // TODO: Uncomment after tests are passed
36
22
  // model.addHook('afterFind', workaround);
37
- model.addHook('beforeFind', 'sadot-beforeFind', beforeFind(scopeAttributes));
38
- model.addHook('beforeBulkCreate', 'sadot-beforeBulkCreate', beforeBulkCreate);
39
- model.addHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate', beforeBulkUpdate);
40
- model.addHook('beforeCreate', 'sadot-beforeCreate', beforeCreate(scopeAttributes));
41
- model.addHook('beforeUpdate', 'sadot-beforeUpdate', beforeUpdate(scopeAttributes));
42
- model.addHook('afterFind', 'sadot-afterFind', enrichResults(name, scopeAttributes));
43
- model.addHook('afterUpdate', 'sadot-afterUpdate', enrichResults(name, scopeAttributes));
44
- model.addHook('afterCreate', 'sadot-afterCreate', enrichResults(name, scopeAttributes));
23
+ model.addHook('afterFind', afterFind);
24
+ model.addHook('beforeBulkCreate', beforeBulkCreate);
25
+ model.addHook('afterFind', afterFind(scopeAttributes));
26
+ model.addHook('beforeBulkUpdate', beforeBulkUpdate);
27
+ model.addHook('beforeCreate', beforeCreate(scopeAttributes));
28
+ model.addHook('beforeUpdate', beforeUpdate(scopeAttributes));
45
29
  } catch (e) {
46
30
  logger.error(`Could not add custom fields hook to model ${name}. `, e);
47
31
  }
@@ -56,46 +40,19 @@ const useCustomFields = async (
56
40
  app: Application | null,
57
41
  getModel: ModelFetcher,
58
42
  options: CustomFieldOptions,
59
- ): Promise<Sequelize> => {
60
- const { models } = options;
43
+ ): Promise<void> => {
44
+ const { innerSequelize, models } = options;
45
+ // deepAfterFindWorkAround(sequelize);
61
46
  if (app) {
62
47
  app.use('/api', api);
63
48
  }
64
- const sequelize = initDB(options.databaseConfig);
65
- if (process.env.NODE_ENV === 'test') {
66
- await initTestModels(sequelize);
49
+ if (!innerSequelize) {
50
+ const moreInnerSequelize = initDB(options.databaseConfig);
51
+ await initTables(moreInnerSequelize);
52
+ } else {
53
+ await initTables(innerSequelize);
67
54
  }
68
55
  addHooks(models, getModel);
69
- await initTables(sequelize, options.getUser);
70
- logger.debug('sadot - custom fields finished initializing with models', models);
71
- return sequelize;
72
56
  };
73
57
 
74
58
  export default useCustomFields;
75
-
76
- const removeHooks = (models: ModelOptions[], getModel: ModelFetcher) => {
77
- models.forEach(async ({ name }) => {
78
- try {
79
- const model = getModel(name);
80
- if (!model) return;
81
- if (model.rawAttributes.customFields) {
82
- delete model.rawAttributes.customFields;
83
- model.refreshAttributes();
84
- }
85
- // model.removeHook('afterFind', 'sadot-workaround');
86
- model.removeHook('beforeFind', 'sadot-beforeFind');
87
- model.removeHook('beforeBulkCreate', 'sadot-beforeBulkCreate');
88
- model.removeHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate');
89
- model.removeHook('beforeCreate', 'sadot-beforeCreate');
90
- model.removeHook('beforeUpdate', 'sadot-beforeUpdate');
91
- model.removeHook('afterFind', 'sadot-afterFind');
92
- model.removeHook('afterUpdate', 'sadot-afterUpdate');
93
- } catch (e) {
94
- logger.error(`Could not add custom fields hook to model ${name}. `, e);
95
- }
96
- });
97
- };
98
-
99
- export const disableCustomFields = (models, getModel) => {
100
- removeHooks(models, getModel);
101
- };
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-vars */
2
- /* eslint-disable indent */
3
2
  import {
4
3
  Table,
5
4
  Column,
@@ -10,32 +9,30 @@ import {
10
9
  BeforeCreate,
11
10
  DefaultScope,
12
11
  AfterSave,
13
- Is,
14
12
  } from 'sequelize-typescript';
15
- import { validateValidation } from '../utils/validations/custom';
16
- import { CustomFieldDefinitionType } from '../utils/validations/type';
17
13
  import { CustomFieldValue } from '.';
18
14
  import { sendDimEvent } from '../events';
19
- import { UnsupportedCustomFieldTypeError, UnsupportedCustomValidationError } from '../errors';
15
+
16
+ /**
17
+ * Supported custom field types
18
+ */
19
+ export enum CustomFieldDefinitionType {
20
+ NUMBER = 'number',
21
+ BOOLEAN = 'boolean',
22
+ DATE = 'date',
23
+ DATETIME = 'datetime',
24
+ TEXT = 'text',
25
+ IMAGE = 'image',
26
+ ENUM = 'enum',
27
+ }
20
28
 
21
29
  @DefaultScope(() => ({ where: { disabled: false } }))
22
30
  @Table({
23
31
  indexes: [
24
- {
25
- name: 'unique_name_model_type',
26
- fields: ['model_type', 'entity_id', 'name'],
27
- unique: true,
28
- },
32
+ { name: "unique_name_model_type", fields: ["name", "model_type", "entity_id"], unique: true },
29
33
  ],
30
34
  timestamps: true,
31
- validate: {
32
- validationByType(this: CustomFieldDefinition) {
33
- if (!validateValidation(this.fieldType, this.validation)) {
34
- throw new UnsupportedCustomValidationError(`Validation provided for "${this.fieldType}" is not supported`);
35
- }
36
- },
37
- },
38
- })
35
+ })
39
36
  class CustomFieldDefinition extends Model {
40
37
  @PrimaryKey
41
38
  @Column({
@@ -56,11 +53,6 @@ class CustomFieldDefinition extends Model {
56
53
  })
57
54
  displayName?: string; // Defaulted to name with beforeCreate hook
58
55
 
59
- @Is('SupportedType', (value) => {
60
- if (!Object.values(CustomFieldDefinitionType).includes(value as CustomFieldDefinitionType)) {
61
- throw new UnsupportedCustomFieldTypeError(`"${value}" is not a supported type.`);
62
- }
63
- })
64
56
  @Column({
65
57
  type: DataType.STRING,
66
58
  allowNull: false,