@autofleet/sadot 1.0.0-beta.0 → 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,7 +1,7 @@
1
1
  import logger from '../utils/logger';
2
- import * as ValueRepo from '../repository/value';
3
- import type { ModelOptions } from '../types';
2
+ import type { CustomFieldOptions, ModelOptions } from '../types';
4
3
  import applyScopeToInstance from '../utils/scopeAttributes';
4
+ import updateInstanceValues from './utils/updateInstanceValues';
5
5
 
6
6
  /**
7
7
  * A hook to update the custom fields when updating a model (more then one instance).
@@ -16,7 +16,11 @@ export const beforeBulkUpdate = (options): void => {
16
16
  * A hook to update the custom fields when updating a model instance.
17
17
  * TODO - cleanup if update fail
18
18
  */
19
- export const beforeUpdate = (scopeAttributes: string[], modelOptions: ModelOptions = {}) => async (
19
+ export const beforeUpdate = (
20
+ scopeAttributes: string[],
21
+ modelOptions: ModelOptions = {},
22
+ sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'> = { useCustomFieldsEntries: false },
23
+ ) => async (
20
24
  instance,
21
25
  options,
22
26
  ): Promise<void> => {
@@ -28,14 +32,23 @@ export const beforeUpdate = (scopeAttributes: string[], modelOptions: ModelOptio
28
32
  const customFieldsIdx = fields.indexOf('customFields');
29
33
  if (customFieldsIdx > -1) {
30
34
  const { customFields } = instance;
31
- await ValueRepo.updateValues(
35
+
36
+ if (!Object.keys(customFields).length) {
37
+ return;
38
+ }
39
+
40
+ await updateInstanceValues({
41
+ modelId: instance.id,
32
42
  modelType,
33
- instance.id,
34
43
  identifiers,
35
44
  customFields,
36
- { transaction: options.transaction, modelOptions },
45
+ options: {
46
+ useCustomFieldsEntries: sadotOptions.useCustomFieldsEntries,
47
+ transaction: options.transaction,
48
+ modelOptions,
49
+ },
50
+ });
37
51
 
38
- );
39
52
  // eslint-disable-next-line no-param-reassign
40
53
  fields.splice(customFieldsIdx, 1);
41
54
  }
@@ -0,0 +1,63 @@
1
+ import type { Transaction } from 'sequelize';
2
+ import * as ValueRepo from '../../repository/value';
3
+ import * as EntriesRepo from '../../repository/entries';
4
+ import type { ModelOptions } from '../../types';
5
+
6
+ interface UpdateInstanceValuesParams {
7
+ modelId: string;
8
+ modelType: string;
9
+ identifiers: string[];
10
+ customFields: Record<string, any>;
11
+ options?: {
12
+ transaction?: Transaction;
13
+ modelOptions?: ModelOptions;
14
+ useCustomFieldsEntries?: boolean;
15
+ };
16
+ }
17
+
18
+ const updateInstanceValues = async ({
19
+ modelId,
20
+ modelType,
21
+ identifiers,
22
+ customFields,
23
+ options = {
24
+ modelOptions: {},
25
+ useCustomFieldsEntries: false,
26
+ },
27
+ }: UpdateInstanceValuesParams): Promise<void> => {
28
+ await ValueRepo.updateValues(
29
+ modelType,
30
+ modelId,
31
+ identifiers,
32
+ customFields,
33
+ {
34
+ ...options,
35
+ modelOptions: options.modelOptions ?? {},
36
+ },
37
+ );
38
+
39
+ /*
40
+ T.Y TODO - Once we're ready to switch from custom_field_values to custom_field_entries, we should remove the ValueRepo.updateValues call.
41
+ Currently, We're updating both tables to keep the data in sync, but not all microservices are using the new table yet.
42
+ */
43
+
44
+ if (!options?.useCustomFieldsEntries) {
45
+ return;
46
+ }
47
+
48
+ const { dataValues: { customFields: oldCustomFields } } = await EntriesRepo.findEntriesByModelId(modelId, options) ?? { dataValues: {} };
49
+ const newCustomFields = { ...oldCustomFields, ...customFields };
50
+
51
+ await EntriesRepo.updateEntries(
52
+ modelId,
53
+ modelType,
54
+ newCustomFields,
55
+ identifiers,
56
+ {
57
+ ...options,
58
+ modelOptions: options.modelOptions ?? {},
59
+ },
60
+ );
61
+ };
62
+
63
+ export default updateInstanceValues;
package/src/index.ts CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  } from './models';
6
6
  import api from './api';
7
7
  import initDB from './utils/db';
8
- import logger from './utils/logger';
9
- import type { CustomFieldOptions, ModelFetcher } from './types';
8
+ import logger, { tryAddingTraceIdMiddleware } from './utils/logger';
9
+ import type { CustomFieldOptions, ModelFetcher, Models } from './types';
10
10
  import {
11
11
  addHooks, addScopes, applyCustomAssociation, removeHooks,
12
12
  } from './utils/init';
@@ -26,25 +26,27 @@ const useCustomFields = async (
26
26
  getModel: ModelFetcher,
27
27
  options: CustomFieldOptions,
28
28
  ): Promise<Sequelize> => {
29
- const { models } = options;
29
+ tryAddingTraceIdMiddleware().catch(() => null);
30
+ const { models, useCustomFieldsEntries } = options;
30
31
  if (app) {
31
32
  app.use('/api', api);
32
33
  }
33
- const sequelize = initDB(options.databaseConfig);
34
+ const sequelize = options.sequelize ?? initDB(options.databaseConfig);
34
35
  if (process.env.NODE_ENV === 'test') {
35
36
  await initTestModels(sequelize);
36
37
  }
37
38
  // The order is important
38
- addHooks(models, getModel);
39
- await initTables(sequelize, options.getUser);
40
- addScopes(models, getModel);
39
+ addHooks(models, getModel, { useCustomFieldsEntries });
40
+ await initTables(sequelize, options.getUser, { useCustomFieldsEntries });
41
+ addScopes(models, getModel, { useCustomFieldsEntries });
41
42
  applyCustomAssociation(models);
43
+
42
44
  logger.debug('sadot - custom fields finished initializing with models', models);
43
45
  return sequelize;
44
46
  };
45
47
 
46
48
  export default useCustomFields;
47
49
 
48
- export const disableCustomFields = (models, getModel): void => {
50
+ export const disableCustomFields = (models: Models[], getModel: ModelFetcher): void => {
49
51
  removeHooks(models, getModel);
50
52
  };
@@ -125,6 +125,13 @@ class CustomFieldDefinition extends Model {
125
125
  @Column
126
126
  deletedAt?: Date;
127
127
 
128
+ @Column({
129
+ type: DataType.BOOLEAN,
130
+ defaultValue: false,
131
+ allowNull: false,
132
+ })
133
+ blockEditingFromUI!: boolean;
134
+
128
135
  @HasMany(() => CustomFieldValue)
129
136
  values: CustomFieldValue[];
130
137
 
@@ -136,8 +143,8 @@ class CustomFieldDefinition extends Model {
136
143
  }
137
144
  if (![null, undefined].includes(instance.defaultValue)) {
138
145
  const isValid = validateValue(instance.defaultValue, instance.fieldType, instance.validation);
139
- if (!isValid) {
140
- throw new InvalidValueError(instance.defaultValue, instance.fieldType);
146
+ if (isValid.error) {
147
+ throw new InvalidValueError(instance.defaultValue, instance.name, isValid.error);
141
148
  }
142
149
  }
143
150
  }
@@ -0,0 +1,81 @@
1
+ import {
2
+ Table,
3
+ Column,
4
+ Model,
5
+ PrimaryKey,
6
+ DataType,
7
+ AfterUpsert,
8
+ } from 'sequelize-typescript';
9
+ import { sendDimEvent } from '../events';
10
+ import * as CustomFieldDefinitionRepo from '../repository/definition';
11
+ import { validateInstanceCustomFieldEntries } from '../utils/validations';
12
+
13
+ @Table({
14
+ timestamps: true,
15
+ indexes: [
16
+ {
17
+ name: 'idx_cfe_custom_fields',
18
+ using: 'gin',
19
+ operator: 'jsonb_path_ops',
20
+ fields: ['custom_fields'],
21
+ },
22
+ ],
23
+ validate: {
24
+ async validationByType(this: CustomFieldEntries) {
25
+ if (!Object.keys(this.customFields ?? {}).length) {
26
+ return;
27
+ }
28
+
29
+ const definitionsByName = await CustomFieldDefinitionRepo.getCustomFieldDefinitionsDictionary([this]);
30
+ validateInstanceCustomFieldEntries(this, definitionsByName);
31
+ },
32
+ },
33
+ })
34
+ class CustomFieldEntries extends Model {
35
+ @PrimaryKey
36
+ @Column({
37
+ type: DataType.UUID,
38
+ allowNull: false,
39
+ })
40
+ /** The ID of the model of which this row hold the custom field entries of, e.g. vehicleId / stopPointId / etc. */
41
+ modelId!: string;
42
+
43
+ @Column({
44
+ type: DataType.UUID,
45
+ allowNull: false,
46
+ })
47
+ /** The ID of the entity of which this row hold the custom field entries of, e.g. fleetId / etc. */
48
+ entityId!: string;
49
+
50
+ @Column({
51
+ type: DataType.JSONB,
52
+ allowNull: false,
53
+ defaultValue: {},
54
+ })
55
+ /** A dictionary of customFields and values with the following structure: `{ customFieldName: 'CustomFieldValue' }` */
56
+ customFields!: Record<string, any>;
57
+
58
+ @Column({
59
+ type: DataType.STRING,
60
+ allowNull: false,
61
+ })
62
+ /** The type of model which this custom field entry represents. e.g. Vehicle / StopPoint / etc. */
63
+ modelType!: string;
64
+
65
+ @Column
66
+ createdAt?: Date;
67
+
68
+ @Column
69
+ updatedAt?: Date;
70
+
71
+ @AfterUpsert
72
+ static afterSaveHandler(instance: CustomFieldEntries, options): void {
73
+ if (options.transaction) {
74
+ options.transaction.afterCommit(() => sendDimEvent(instance[0]));
75
+ } else {
76
+ sendDimEvent(instance[0]);
77
+ }
78
+ }
79
+ }
80
+
81
+ export default CustomFieldEntries;
@@ -15,9 +15,9 @@ import {
15
15
  } from 'sequelize-typescript';
16
16
  import { sendDimEvent } from '../events';
17
17
  import { CustomFieldDefinition } from '.';
18
- import { validateValue } from '../utils/validations';
18
+ import { validateFieldType, validateValue } from '../utils/validations';
19
19
  import * as CustomFieldDefinitionRepo from '../repository/definition';
20
- import { InvalidValueError } from '../errors';
20
+ import { InvalidFieldTypeError, InvalidValueError } from '../errors';
21
21
 
22
22
  @Table({
23
23
  timestamps: true,
@@ -56,9 +56,26 @@ class CustomFieldValue extends Model {
56
56
  @BelongsTo(() => CustomFieldDefinition, { scope: { disabled: false } })
57
57
  customFieldDefinition: CustomFieldDefinition;
58
58
 
59
+ private static validateValueAgainstDefinition(
60
+ instance: CustomFieldValue,
61
+ definition: CustomFieldDefinition,
62
+ ): void {
63
+ const { validation, fieldType, name } = definition;
64
+ const isValidFieldType = validateFieldType(fieldType);
65
+ if (!isValidFieldType) {
66
+ throw new InvalidFieldTypeError(fieldType);
67
+ }
68
+ // Always allow null values
69
+ if (instance.value === null) return;
70
+ const validateValueResponse = validateValue(instance.value, fieldType, validation);
71
+ if (validateValueResponse.error) {
72
+ throw new InvalidValueError(instance.value, name, validateValueResponse.error);
73
+ }
74
+ }
75
+
59
76
  @BeforeBulkCreate
60
77
  @BeforeBulkUpdate
61
- static async validateValues(instances: CustomFieldValue[]): Promise<void> {
78
+ static async validateCustomFieldValues(instances: CustomFieldValue[]): Promise<void> {
62
79
  const ids = instances.map((instance) => instance.customFieldDefinitionId);
63
80
  const uniqueIds = [...new Set(ids)];
64
81
  const definitions = await CustomFieldDefinitionRepo.findByIds(
@@ -71,14 +88,9 @@ class CustomFieldValue extends Model {
71
88
  }
72
89
 
73
90
  instances.forEach((instance) => {
74
- const {
75
- validation,
76
- fieldType,
77
- } = definitions
78
- .find((definition) => definition.id === instance.customFieldDefinitionId);
79
- const isValid = validateValue(instance.value, fieldType, validation);
80
- if (!isValid) {
81
- throw new InvalidValueError(instance.value, fieldType);
91
+ const definition = definitions.find((d) => d.id === instance.customFieldDefinitionId);
92
+ if (definition) {
93
+ this.validateValueAgainstDefinition(instance, definition);
82
94
  }
83
95
  });
84
96
  }
@@ -86,15 +98,11 @@ class CustomFieldValue extends Model {
86
98
  @BeforeUpdate
87
99
  @BeforeCreate
88
100
  @BeforeUpsert
89
- static async validateValue(instance: CustomFieldValue): Promise<void> {
101
+ static async validateCustomFieldValue(instance: CustomFieldValue): Promise<void> {
90
102
  const { customFieldDefinitionId } = instance;
91
103
  // eslint-disable-next-line max-len
92
104
  const cfd = await CustomFieldDefinitionRepo.findById(customFieldDefinitionId, { withDisabled: true });
93
- const { validation, fieldType } = cfd;
94
- const isValid = validateValue(instance.value, fieldType, validation);
95
- if (!isValid) {
96
- throw new InvalidValueError(instance.value, fieldType);
97
- }
105
+ this.validateValueAgainstDefinition(instance, cfd);
98
106
  }
99
107
 
100
108
  @AfterUpsert
@@ -0,0 +1,78 @@
1
+ import {
2
+ Table,
3
+ Column,
4
+ Model,
5
+ PrimaryKey,
6
+ DataType,
7
+ Default,
8
+ AfterUpsert,
9
+ DefaultScope,
10
+ } from 'sequelize-typescript';
11
+ import { randomUUID } from 'node:crypto';
12
+ import { sendDimEvent } from '../events';
13
+
14
+ @DefaultScope(() => ({ where: { disabled: false } }))
15
+ @Table({
16
+ timestamps: true,
17
+ })
18
+ class CustomValidator extends Model {
19
+ @PrimaryKey
20
+ @Default(() => randomUUID())
21
+ @Column({
22
+ type: DataType.UUID,
23
+ allowNull: false,
24
+ })
25
+ id!: string;
26
+
27
+ @Column({
28
+ type: DataType.UUID,
29
+ allowNull: false,
30
+ })
31
+ /** The ID of the entity for which this validator is defined, e.g. fleetId / etc. */
32
+ entityId!: string;
33
+
34
+ @Column({
35
+ type: DataType.STRING,
36
+ allowNull: false,
37
+ })
38
+ /** The type of entity (fleet, businessModel, etc). */
39
+ entityType!: string;
40
+
41
+ @Column({
42
+ type: DataType.STRING,
43
+ allowNull: false,
44
+ })
45
+ /** The type of model this validator applies to. e.g. Vehicle / StopPoint / etc. */
46
+ modelType!: string;
47
+
48
+ @Column({
49
+ type: DataType.JSONB,
50
+ allowNull: false,
51
+ })
52
+ /** JSON Schema to validate models against */
53
+ schema!: Record<string, unknown>;
54
+
55
+ @Column({
56
+ type: DataType.BOOLEAN,
57
+ allowNull: false,
58
+ defaultValue: false,
59
+ })
60
+ disabled!: boolean;
61
+
62
+ @Column
63
+ createdAt?: Date;
64
+
65
+ @Column
66
+ updatedAt?: Date;
67
+
68
+ @AfterUpsert
69
+ static afterSaveHandler(instance: CustomValidator, options): void {
70
+ if (options.transaction) {
71
+ options.transaction.afterCommit(() => sendDimEvent(instance));
72
+ } else {
73
+ sendDimEvent(instance);
74
+ }
75
+ }
76
+ }
77
+
78
+ export default CustomValidator;
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable no-param-reassign */
2
- import { DataTypes } from 'sequelize';
2
+ import { DataTypes, Op } from 'sequelize';
3
3
  import type { Sequelize } from 'sequelize-typescript';
4
4
  import logger from '../utils/logger';
5
5
  import CustomFieldDefinition from './CustomFieldDefinition';
@@ -8,37 +8,76 @@ import TestModel from './tests/TestModel';
8
8
  import ContextAwareTestModel from './tests/contextAwareModels/ContextAwareTestModel';
9
9
  import ContextTestModel from './tests/contextAwareModels/ContextTestModel';
10
10
  import AssociatedTestModel from './tests/AssociatedTestModel';
11
+ import type { CustomFieldOptions } from '../types';
12
+ import CustomFieldEntries from './CustomFieldEntries';
13
+ import CustomValidator from './CustomValidator';
11
14
 
12
- const productionModels = [CustomFieldDefinition, CustomFieldValue];
15
+ type ProductionModel = typeof CustomFieldDefinition | typeof CustomFieldValue | typeof CustomFieldEntries | typeof CustomValidator
16
+ interface InitTablesOptions {
17
+ schemaPrefix?: string
18
+ schemaVersion?: string
19
+ useCustomFieldsEntries?: boolean
20
+ }
21
+
22
+ const productionModels: ProductionModel[] = [CustomFieldDefinition, CustomFieldValue, CustomValidator];
13
23
  const testModels = [TestModel, AssociatedTestModel, ContextAwareTestModel, ContextTestModel];
14
24
 
15
25
  const SADOT_MIGRATION_PREFIX = 'sadot-migration';
16
- const SCHEMA_VERSION = 2;
17
- const CUSTOM_FIELDS_SCHEMA_VERSION = `${SADOT_MIGRATION_PREFIX}_${SCHEMA_VERSION}`;
26
+ const SCHEMA_VERSION = '49c9dd1d-b1cc-445b-a911-fd349d783110';
18
27
 
19
- const initTables = async (sequelize: Sequelize, getUser): Promise<void> => {
28
+ const initTables = async (
29
+ sequelize: Sequelize,
30
+ getUser: CustomFieldOptions['getUser'],
31
+ {
32
+ schemaPrefix = SADOT_MIGRATION_PREFIX,
33
+ schemaVersion = SCHEMA_VERSION,
34
+ useCustomFieldsEntries = false,
35
+ }: InitTablesOptions = {},
36
+ ): Promise<void> => {
37
+ const CUSTOM_FIELDS_SCHEMA_VERSION = `${schemaPrefix}_${schemaVersion}${useCustomFieldsEntries ? '_withEntries' : ''}`;
20
38
  logger.info('custom-fields: initialize custom-fields tables');
21
39
  // Detect models and import them to the orm
22
40
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
23
41
  if (!sequelize.addModels) {
24
42
  throw new Error('sequelize instance must have addModels function');
25
43
  }
44
+
45
+ if (useCustomFieldsEntries) {
46
+ productionModels.push(CustomFieldEntries);
47
+ }
48
+
26
49
  sequelize.addModels(productionModels);
27
50
 
28
51
  CustomFieldDefinition.addScope('userScope', () => {
29
52
  const user = getUser();
30
- if (user?.permissions) {
31
- return {
32
- where: {
33
- entityId: [
34
- ...Object.keys(user.permissions.fleets),
35
- ...Object.keys(user.permissions.businessModels),
36
- ...Object.keys(user.permissions.demandSources),
37
- ],
38
- },
39
- };
53
+ if (!user?.permissions) {
54
+ return {};
40
55
  }
41
- return {};
56
+ return {
57
+ where: {
58
+ entityId: [
59
+ ...Object.keys(user.permissions.fleets),
60
+ ...Object.keys(user.permissions.businessModels),
61
+ ...Object.keys(user.permissions.demandSources),
62
+ ],
63
+ },
64
+ };
65
+ });
66
+
67
+ CustomValidator.addScope('userScope', () => {
68
+ const user = getUser();
69
+ if (!user?.permissions) {
70
+ return {};
71
+ }
72
+ return {
73
+ where: {
74
+ entityId: [
75
+ ...Object.keys(user.permissions.fleets),
76
+ ...Object.keys(user.permissions.businessModels),
77
+ ...Object.keys(user.permissions.demandSources),
78
+ ],
79
+ },
80
+ };
42
81
  });
43
82
 
44
83
  logger.info('custom-fields: models added');
@@ -60,18 +99,35 @@ const initTables = async (sequelize: Sequelize, getUser): Promise<void> => {
60
99
  schema: 'public',
61
100
  },
62
101
  );
63
- const migrations = await SequelizeMeta.findAll({ raw: true });
64
- const currentSadotSchemaVersion = migrations
65
- .reverse().find((m: any) => m.name.includes(SADOT_MIGRATION_PREFIX));
66
-
67
- if (
68
- !currentSadotSchemaVersion
69
- || (currentSadotSchemaVersion as any).name !== CUSTOM_FIELDS_SCHEMA_VERSION
70
- ) {
102
+
103
+ logger.info('custom-fields: starting migrations');
104
+ const migrations = await SequelizeMeta.findAll({ where: { name: { [Op.like]: `${schemaPrefix}%` } }, raw: true });
105
+ const currentSadotSchemaVersion = migrations.at(-1);
106
+ const expectedSchemaVersionIndex = migrations.findIndex((m) => (m as any).name === CUSTOM_FIELDS_SCHEMA_VERSION);
107
+
108
+ logger.info('custom-fields: migrations', { migrations, currentSadotSchemaVersion, expectedSchemaVersionIndex });
109
+ if (!currentSadotSchemaVersion || (currentSadotSchemaVersion as any).name !== CUSTOM_FIELDS_SCHEMA_VERSION) {
110
+ logger.info('custom-fields: syncing models');
71
111
  await CustomFieldDefinition.sync({ alter: true });
72
112
  await CustomFieldValue.sync({ alter: true });
73
- await SequelizeMeta.create({ name: CUSTOM_FIELDS_SCHEMA_VERSION });
113
+ // T.Y TODO: Remove the if statement once we're ready to add the new entries table for all MS
114
+ if (useCustomFieldsEntries) {
115
+ await CustomFieldEntries.sync({ alter: true });
116
+ }
117
+
118
+ // Always sync CustomValidator
119
+ await CustomValidator.sync({ alter: true });
120
+
121
+ if (expectedSchemaVersionIndex === -1) {
122
+ await SequelizeMeta.create({ name: CUSTOM_FIELDS_SCHEMA_VERSION });
123
+ }
74
124
  logger.info('custom-fields: models synced');
125
+ if (migrations.length && expectedSchemaVersionIndex !== -1 && expectedSchemaVersionIndex < migrations.length - 1) {
126
+ // We have existing migrations, and we are calling `sync`.
127
+ // This means we are in a `down` migration, and hence we should delete newer migrations to ensure we can reapply them.
128
+ const migrationsToDelete = migrations.slice(expectedSchemaVersionIndex + 1);
129
+ await SequelizeMeta.destroy({ where: { name: { [Op.in]: migrationsToDelete.map((m) => (m as any).name) } } });
130
+ }
75
131
  }
76
132
  };
77
133
 
@@ -98,6 +154,8 @@ const initTestModels = async (sequelize: Sequelize): Promise<void> => {
98
154
  export {
99
155
  CustomFieldValue,
100
156
  CustomFieldDefinition,
157
+ CustomFieldEntries,
158
+ CustomValidator,
101
159
  TestModel,
102
160
  AssociatedTestModel,
103
161
  ContextAwareTestModel,