@autofleet/sadot 0.7.7-beta.0 → 0.7.7

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 (99) hide show
  1. package/dist/api/v1/definition/validations.js +2 -24
  2. package/dist/events/index.js +20 -21
  3. package/dist/hooks/create.js +1 -1
  4. package/dist/models/CustomFieldDefinition.d.ts +0 -1
  5. package/dist/models/CustomFieldDefinition.js +0 -14
  6. package/dist/models/index.js +1 -1
  7. package/dist/models/tests/contextAwareModels/ContextAwareTestModel.js +3 -1
  8. package/dist/repository/value.d.ts +1 -1
  9. package/dist/repository/value.js +7 -36
  10. package/dist/scopes/filter.d.ts +3 -10
  11. package/dist/scopes/filter.js +40 -33
  12. package/dist/tests/mocks/definition.mock.d.ts +0 -2
  13. package/dist/tests/mocks/definition.mock.js +9 -10
  14. package/dist/types/definition/index.d.ts +0 -1
  15. package/dist/utils/logger/index.d.ts +1 -1
  16. package/dist/utils/logger/index.js +3 -5
  17. package/package.json +1 -2
  18. package/src/hooks/create.ts +42 -24
  19. package/src/repository/definition.ts +15 -6
  20. package/src/repository/value.ts +4 -20
  21. package/src/scopes/filter.ts +20 -18
  22. package/coverage/clover.xml +0 -1073
  23. package/coverage/coverage-final.json +0 -44
  24. package/coverage/lcov-report/base.css +0 -224
  25. package/coverage/lcov-report/block-navigation.js +0 -87
  26. package/coverage/lcov-report/favicon.png +0 -0
  27. package/coverage/lcov-report/index.html +0 -461
  28. package/coverage/lcov-report/prettify.css +0 -1
  29. package/coverage/lcov-report/prettify.js +0 -2
  30. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  31. package/coverage/lcov-report/sorter.js +0 -196
  32. package/coverage/lcov-report/src/api/index.html +0 -116
  33. package/coverage/lcov-report/src/api/index.ts.html +0 -112
  34. package/coverage/lcov-report/src/api/v1/definition/index.html +0 -131
  35. package/coverage/lcov-report/src/api/v1/definition/index.ts.html +0 -406
  36. package/coverage/lcov-report/src/api/v1/definition/validations.ts.html +0 -310
  37. package/coverage/lcov-report/src/api/v1/errors.ts.html +0 -133
  38. package/coverage/lcov-report/src/api/v1/index.html +0 -131
  39. package/coverage/lcov-report/src/api/v1/index.ts.html +0 -112
  40. package/coverage/lcov-report/src/errors/index.html +0 -116
  41. package/coverage/lcov-report/src/errors/index.ts.html +0 -211
  42. package/coverage/lcov-report/src/events/index.html +0 -116
  43. package/coverage/lcov-report/src/events/index.ts.html +0 -238
  44. package/coverage/lcov-report/src/hooks/create.ts.html +0 -262
  45. package/coverage/lcov-report/src/hooks/enrich.ts.html +0 -523
  46. package/coverage/lcov-report/src/hooks/find.ts.html +0 -166
  47. package/coverage/lcov-report/src/hooks/index.html +0 -191
  48. package/coverage/lcov-report/src/hooks/index.ts.html +0 -130
  49. package/coverage/lcov-report/src/hooks/update.ts.html +0 -211
  50. package/coverage/lcov-report/src/hooks/workaround.ts.html +0 -226
  51. package/coverage/lcov-report/src/index.html +0 -116
  52. package/coverage/lcov-report/src/index.ts.html +0 -235
  53. package/coverage/lcov-report/src/models/CustomFieldDefinition.ts.html +0 -550
  54. package/coverage/lcov-report/src/models/CustomFieldValue.ts.html +0 -415
  55. package/coverage/lcov-report/src/models/index.html +0 -146
  56. package/coverage/lcov-report/src/models/index.ts.html +0 -406
  57. package/coverage/lcov-report/src/models/tests/AssociatedTestModel.ts.html +0 -256
  58. package/coverage/lcov-report/src/models/tests/TestModel.ts.html +0 -247
  59. package/coverage/lcov-report/src/models/tests/contextAwareModels/ContextAwareTestModel.ts.html +0 -214
  60. package/coverage/lcov-report/src/models/tests/contextAwareModels/ContextTestModel.ts.html +0 -199
  61. package/coverage/lcov-report/src/models/tests/contextAwareModels/index.html +0 -131
  62. package/coverage/lcov-report/src/models/tests/index.html +0 -131
  63. package/coverage/lcov-report/src/repository/definition.ts.html +0 -466
  64. package/coverage/lcov-report/src/repository/index.html +0 -131
  65. package/coverage/lcov-report/src/repository/value.ts.html +0 -523
  66. package/coverage/lcov-report/src/scopes/filter.ts.html +0 -637
  67. package/coverage/lcov-report/src/scopes/index.html +0 -131
  68. package/coverage/lcov-report/src/scopes/index.ts.html +0 -103
  69. package/coverage/lcov-report/src/tests/api/index.html +0 -116
  70. package/coverage/lcov-report/src/tests/api/test-api.ts.html +0 -199
  71. package/coverage/lcov-report/src/tests/functional/searching/index.html +0 -116
  72. package/coverage/lcov-report/src/tests/functional/searching/index.ts.html +0 -202
  73. package/coverage/lcov-report/src/tests/helpers/database-config.ts.html +0 -130
  74. package/coverage/lcov-report/src/tests/helpers/index.html +0 -131
  75. package/coverage/lcov-report/src/tests/helpers/index.ts.html +0 -142
  76. package/coverage/lcov-report/src/tests/mocks/definition.mock.ts.html +0 -310
  77. package/coverage/lcov-report/src/tests/mocks/events.mock.ts.html +0 -139
  78. package/coverage/lcov-report/src/tests/mocks/index.html +0 -146
  79. package/coverage/lcov-report/src/tests/mocks/testModel.ts.html +0 -196
  80. package/coverage/lcov-report/src/utils/constants/index.html +0 -116
  81. package/coverage/lcov-report/src/utils/constants/index.ts.html +0 -160
  82. package/coverage/lcov-report/src/utils/db/index.html +0 -116
  83. package/coverage/lcov-report/src/utils/db/index.ts.html +0 -148
  84. package/coverage/lcov-report/src/utils/helpers/index.html +0 -116
  85. package/coverage/lcov-report/src/utils/helpers/index.ts.html +0 -283
  86. package/coverage/lcov-report/src/utils/index.html +0 -131
  87. package/coverage/lcov-report/src/utils/init.ts.html +0 -430
  88. package/coverage/lcov-report/src/utils/logger/index.html +0 -116
  89. package/coverage/lcov-report/src/utils/logger/index.ts.html +0 -100
  90. package/coverage/lcov-report/src/utils/scopeAttributes.ts.html +0 -121
  91. package/coverage/lcov-report/src/utils/validations/index.html +0 -116
  92. package/coverage/lcov-report/src/utils/validations/index.ts.html +0 -151
  93. package/coverage/lcov-report/src/utils/validations/schema/custom-fields.ts.html +0 -109
  94. package/coverage/lcov-report/src/utils/validations/schema/index.html +0 -116
  95. package/coverage/lcov-report/src/utils/validations/validators/index.html +0 -146
  96. package/coverage/lcov-report/src/utils/validations/validators/index.ts.html +0 -199
  97. package/coverage/lcov-report/src/utils/validations/validators/select.validator.ts.html +0 -118
  98. package/coverage/lcov-report/src/utils/validations/validators/status.validator.ts.html +0 -139
  99. package/coverage/lcov.info +0 -1818
@@ -1,3 +1,4 @@
1
+ import type { WhereOptions } from 'sequelize';
1
2
  import logger from '../utils/logger';
2
3
  import * as ValueRepo from '../repository/value';
3
4
  import * as DefinitionRepo from '../repository/definition';
@@ -23,37 +24,54 @@ export const beforeCreate = (scopeAttributes: string[], modelOptions: ModelOptio
23
24
  ): Promise<void> => {
24
25
  logger.debug('sadot - before create hook');
25
26
  const { fields } = options;
27
+ const { include, useEntityIdFromInclude } = modelOptions;
26
28
  const modelType = instance.constructor.name;
27
29
 
28
30
  const identifiers = applyScopeToInstance(instance, scopeAttributes);
29
31
 
30
- // get all model's required definitions
31
- const requiredFieldsNames = await DefinitionRepo.getRequiredFields(modelType, instance.id, identifiers, modelOptions);
32
+ const where: WhereOptions = {
33
+ modelType,
34
+ disabled: false,
35
+ ...(!useEntityIdFromInclude && { entityId: identifiers }),
36
+ };
37
+ const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: false, transaction: options.transaction, include: include?.(identifiers) });
38
+ const requiredFieldsNames = Array.from(new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)));
32
39
 
33
40
  const customFieldsIdx = fields.indexOf('customFields');
34
- 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
- );
53
41
 
54
- // eslint-disable-next-line no-param-reassign
55
- fields.splice(customFieldsIdx, 1);
56
- } else if (requiredFieldsNames?.length > 0) {
42
+ if ((customFieldsIdx === -1 || !instance.customFields) && requiredFieldsNames?.length > 0) {
57
43
  throw new MissingRequiredCustomFieldError(requiredFieldsNames);
58
44
  }
45
+ const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
46
+ if (fieldsWithDefaultValue.length) {
47
+ // eslint-disable-next-line no-param-reassign
48
+ instance.customFields ||= {};
49
+ fieldsWithDefaultValue.filter((def) => !(def.name in instance.customFields)).forEach(({ name, defaultValue }) => {
50
+ // eslint-disable-next-line no-param-reassign
51
+ instance.customFields[name] = defaultValue;
52
+ });
53
+ }
54
+ const { customFields } = instance;
55
+ if (customFieldsIdx === -1 || !customFields) {
56
+ return;
57
+ }
58
+ const fieldsNames = Object.keys(customFields);
59
+ const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
60
+ if (missingFields?.length > 0) {
61
+ throw new MissingRequiredCustomFieldError(missingFields);
62
+ }
63
+
64
+ await ValueRepo.updateValues(
65
+ modelType,
66
+ instance.id,
67
+ identifiers,
68
+ customFields,
69
+ {
70
+ transaction: options.transaction,
71
+ modelOptions,
72
+ },
73
+ );
74
+
75
+ // eslint-disable-next-line no-param-reassign
76
+ fields.splice(customFieldsIdx, 1);
59
77
  };
@@ -1,4 +1,7 @@
1
- import { Op, type FindOptions, type WhereOptions } from 'sequelize';
1
+ import {
2
+ Op,
3
+ type Includeable, type Transaction, type FindOptions, type WhereOptions,
4
+ } from 'sequelize';
2
5
  import { CustomFieldDefinition } from '../models';
3
6
  import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../types/definition';
4
7
  import type { ModelOptions } from '../types';
@@ -6,9 +9,15 @@ import type { ModelOptions } from '../types';
6
9
  export const create = (data: CreateCustomFieldDefinition): Promise<CustomFieldDefinition> =>
7
10
  CustomFieldDefinition.create(data);
8
11
 
12
+ interface SadotFindOptions {
13
+ withDisabled?: boolean;
14
+ transaction?: Transaction;
15
+ include?: Includeable | Includeable[];
16
+ }
17
+
9
18
  export const findAll = (
10
19
  where: WhereOptions,
11
- options: any = { withDisabled: false },
20
+ options: SadotFindOptions = { withDisabled: false },
12
21
  ): Promise<CustomFieldDefinition[]> => {
13
22
  const queryModel = options.withDisabled
14
23
  ? CustomFieldDefinition.unscoped()
@@ -24,12 +33,12 @@ export const findAll = (
24
33
 
25
34
  export const findByIds = (
26
35
  ids: string[],
27
- options: any = { withDisabled: false },
36
+ options: SadotFindOptions = { withDisabled: false },
28
37
  ): Promise<CustomFieldDefinition[]> => findAll({ id: { [Op.in]: ids } }, options);
29
38
 
30
39
  export const findById = (
31
40
  id: string,
32
- options: any = { withDisabled: false },
41
+ options: Pick<SadotFindOptions, 'withDisabled'> = { withDisabled: false },
33
42
  ): Promise<CustomFieldDefinition | null> => {
34
43
  const { withDisabled } = options;
35
44
  if (withDisabled) {
@@ -87,13 +96,13 @@ export const update = async (
87
96
  return updatedDefinition;
88
97
  };
89
98
 
90
- export const disable = (id: string): Promise<any> =>
99
+ export const disable = (id: string): Promise<[affectedCount: number]> =>
91
100
  CustomFieldDefinition.update(
92
101
  { disabled: true },
93
102
  { where: { id } },
94
103
  );
95
104
 
96
- export const destroy = (id: string): Promise<any> =>
105
+ export const destroy = (id: string): Promise<number> =>
97
106
  CustomFieldDefinition.destroy({ where: { id } });
98
107
 
99
108
  /**
@@ -67,7 +67,6 @@ export const updateValues = async (
67
67
  identifiers: string[],
68
68
  valuesToUpdate: ValuesToUpdate,
69
69
  options: FindOptions & { modelOptions?: ModelOptions } = {},
70
- defineAllDefaults = false,
71
70
  ): Promise<CustomFieldValue[]> => {
72
71
  const names = Object.keys(valuesToUpdate);
73
72
  logger.debug(`custom-fields: updating values for ${modelType} ${modelId}`, {
@@ -81,12 +80,10 @@ export const updateValues = async (
81
80
  const where: WhereOptions = {
82
81
  modelType,
83
82
  name: names,
83
+ ...(!options.modelOptions?.useEntityIdFromInclude && { entityId: identifiers }),
84
84
  };
85
85
 
86
- if (!options.modelOptions?.useEntityIdFromInclude) {
87
- where.entityId = identifiers;
88
- }
89
- const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) || [];
86
+ const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) ?? [];
90
87
 
91
88
  const disabledDefinitions = fieldDefinitions.filter((def) => def.disabled);
92
89
  if (fieldDefinitions.length !== names.length) {
@@ -101,31 +98,18 @@ export const updateValues = async (
101
98
  logger.warn(`custom-fields: trying to update disabled values: ${valuesWithDisabledDefinitions.join(', ')}`);
102
99
  }
103
100
 
104
- const visitedFields = new Set<CustomFieldDefinition>();
105
-
106
101
  const values: CreateCustomFieldValue[] = names.map((name) => {
107
102
  const fieldDefinition = fieldDefinitions.find((def) => def.name === name);
108
- visitedFields.add(fieldDefinition);
109
103
  const formatFunction = formatFunctions[fieldDefinition.fieldType];
104
+ const value = formatFunction ? formatFunction(valuesToUpdate[name]) : valuesToUpdate[name];
110
105
  return {
111
106
  modelId,
112
- value: (formatFunction ? formatFunction(valuesToUpdate[name]) : valuesToUpdate[name]) ?? fieldDefinition.defaultValue,
113
107
  updatedAt: new Date(),
114
108
  customFieldDefinitionId: fieldDefinition.id,
109
+ value: value !== undefined ? value : fieldDefinition.defaultValue,
115
110
  };
116
111
  });
117
112
 
118
- if (defineAllDefaults) {
119
- fieldDefinitions.filter((def) => !visitedFields.has(def) && ![null, undefined].includes(def.defaultValue)).forEach(({ id, defaultValue }) => {
120
- values.push({
121
- modelId,
122
- value: defaultValue,
123
- updatedAt: new Date(),
124
- customFieldDefinitionId: id,
125
- });
126
- });
127
- }
128
-
129
113
  return Promise.all(values.map(async (value) => {
130
114
  const [cfv] = await CustomFieldValue.upsert(value, {
131
115
  transaction: options.transaction,
@@ -3,7 +3,6 @@ import { Op, type WhereOptions } from 'sequelize';
3
3
  import { Sequelize } from 'sequelize-typescript';
4
4
  import { customFields } from '@autofleet/common-types';
5
5
  import { generateRandomString } from '../utils/helpers';
6
- import logger from '../utils/logger';
7
6
 
8
7
  const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
9
8
 
@@ -46,11 +45,11 @@ const castIfNeeded = (conditionValue: string): string => {
46
45
  };
47
46
  const AND_DELIMETER = ' AND ';
48
47
  const OR_DELIMETER = ' OR ';
49
-
50
48
  const CD_TABLE_ALIAS = 'cd';
51
49
  const CD_NAME_COLUMN = `${CD_TABLE_ALIAS}.name`;
52
50
  const CV_TABLE_ALIAS = 'cv';
53
- const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value::varchar)`;
51
+ const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value)`;
52
+ const castValueToJsonb = (value) => `to_jsonb(${value}::text)`;
54
53
 
55
54
  /**
56
55
  * A Sequelize scope for filtering models by custom fields.
@@ -77,8 +76,8 @@ export const customFieldsFilterScope = (
77
76
  const replacemetKey = Object.keys(replacements).find(
78
77
  (randomString) => replacements[randomString] === key,
79
78
  );
80
- if (!replacemetKey) return false;
81
79
  const columnCondition = `(${CD_NAME_COLUMN} = :${replacemetKey})`;
80
+ if (!replacemetKey) return false;
82
81
 
83
82
  if (Array.isArray(condition)) {
84
83
  if (condition.length === 0) {
@@ -90,29 +89,32 @@ export const customFieldsFilterScope = (
90
89
  const valRandom = Object.keys(replacements).find(
91
90
  (randomString) => replacements[randomString] === v,
92
91
  );
93
- return `'"' || :${valRandom} || '"'`;
92
+ return castValueToJsonb(`:${valRandom}`);
94
93
  }).join(',');
95
- return `(${columnCondition} AND ${CV_VALUE_COLUMN} IN ( ${values} ))`;
94
+ return `(${columnCondition} AND ${CV_VALUE_COLUMN} IN (${values}))`;
96
95
  }
97
96
  return condition
98
97
  .map((c) => {
99
98
  const valRep = Object.keys(replacements).find(
100
99
  (replacementKey) => replacements[replacementKey] === c.value,
101
100
  );
102
- return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(c.value)} ${c.operator} '"' || :${valRep} || '"' )`;
101
+ const valueAsJsonb = castValueToJsonb(`:${valRep}`);
102
+ return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(c.value)} ${c.operator} ${valueAsJsonb})`;
103
103
  }).join(AND_DELIMETER);
104
104
  }
105
105
  if (typeof condition === 'string' || typeof condition === 'number') {
106
106
  const conditionRep = Object.keys(replacements).find(
107
107
  (replacementKey) => replacements[replacementKey] === condition,
108
108
  );
109
- return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition)} = '"' || :${conditionRep} || '"')`;
109
+ const valueAsJsonb = castValueToJsonb(`:${conditionRep}`);
110
+ return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition)} = ${valueAsJsonb})`;
110
111
  }
111
112
  if (condition?.operator) {
112
113
  const valueRep = Object.keys(replacements).find(
113
114
  (replacementKey) => replacements[replacementKey] === condition.value,
114
115
  );
115
- return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition.value)} ${condition.operator} '"' || :${valueRep} || '"')`;
116
+ const valueAsJsonb = castValueToJsonb(`:${valueRep}`);
117
+ return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition.value)} ${condition.operator} ${valueAsJsonb})`;
116
118
  }
117
119
  return false;
118
120
  },
@@ -123,15 +125,15 @@ export const customFieldsFilterScope = (
123
125
  }
124
126
  const customFieldConditions = conditionsStrings.join(OR_DELIMETER);
125
127
  const subQuery = `
126
- SELECT cv.model_id
127
- FROM custom_field_values AS cv
128
- INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
129
- AND cd.model_type = '${name}'
130
- WHERE ${customFieldConditions}
131
- GROUP BY cv.model_id
132
- HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
133
- `.replace(/\n/g, '');
134
- logger.info('custom fields filter scope', { subQuery, replacements });
128
+ SELECT cv.model_id
129
+ FROM custom_field_values AS cv
130
+ INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
131
+
132
+ WHERE ${customFieldConditions}
133
+ GROUP BY cv.model_id
134
+ HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
135
+ `.replace(/\n/g, '');
136
+ console.log(subQuery);
135
137
  return {
136
138
  where: {
137
139
  id: {