@autofleet/sadot 0.9.0 → 0.9.1-beta-8053a0cc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env ADDED
@@ -0,0 +1,4 @@
1
+ AF_SERVICE_NAME=sadot
2
+ NODE_ENV=test
3
+ DB_USERNAME=postgres
4
+ DB_PASSWORD=postgres
@@ -27,10 +27,6 @@ const modelTableMapping = {
27
27
  tableName: 'dim_custom_field_value',
28
28
  eventVersion: '1',
29
29
  },
30
- CustomFieldEntries: {
31
- tableName: 'dim_custom_field_entries',
32
- eventVersion: '1',
33
- },
34
30
  };
35
31
  const sendDimEvent = (instance) => {
36
32
  const mapping = modelTableMapping[instance.constructor.name];
@@ -1,11 +1,11 @@
1
1
  import { Model } from 'sequelize-typescript';
2
2
  declare class CustomFieldEntries extends Model {
3
- /** The ID of the model of which this row hold the custom field entries of, e.g. vehicleId / stopPointId / etc. */
3
+ /** The ID of the entity of which this row hold the custom field entries of, e.g. vehicleId / stopPointId / etc. */
4
4
  modelId: string;
5
- /** The ID of the entity of which this row hold the custom field entries of, e.g. fleetId / etc. */
6
- entityId: string;
7
5
  /** A dictionary of customFields and values with the following structure: `{ customFieldName: 'CustomFieldValue' }` */
8
- customFields: Record<string, any>;
6
+ customFields: {
7
+ [customFieldName: string]: any;
8
+ };
9
9
  /** The type of model which this custom field entry represents. e.g. Vehicle / StopPoint / etc. */
10
10
  modelType: string;
11
11
  createdAt?: Date;
@@ -31,11 +31,15 @@ var __importStar = (this && this.__importStar) || function (mod) {
31
31
  var __metadata = (this && this.__metadata) || function (k, v) {
32
32
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
33
33
  };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
34
37
  Object.defineProperty(exports, "__esModule", { value: true });
35
38
  const sequelize_typescript_1 = require("sequelize-typescript");
36
39
  const events_1 = require("../events");
37
40
  const CustomFieldDefinitionRepo = __importStar(require("../repository/definition"));
38
41
  const validations_1 = require("../utils/validations");
42
+ const logger_1 = __importDefault(require("../utils/logger"));
39
43
  let CustomFieldEntries = class CustomFieldEntries extends sequelize_typescript_1.Model {
40
44
  static afterSaveHandler(instance, options) {
41
45
  if (options.transaction) {
@@ -52,19 +56,10 @@ __decorate([
52
56
  type: sequelize_typescript_1.DataType.UUID,
53
57
  allowNull: false,
54
58
  })
55
- /** The ID of the model of which this row hold the custom field entries of, e.g. vehicleId / stopPointId / etc. */
59
+ /** The ID of the entity of which this row hold the custom field entries of, e.g. vehicleId / stopPointId / etc. */
56
60
  ,
57
61
  __metadata("design:type", String)
58
62
  ], CustomFieldEntries.prototype, "modelId", void 0);
59
- __decorate([
60
- (0, sequelize_typescript_1.Column)({
61
- type: sequelize_typescript_1.DataType.UUID,
62
- allowNull: false,
63
- })
64
- /** The ID of the entity of which this row hold the custom field entries of, e.g. fleetId / etc. */
65
- ,
66
- __metadata("design:type", String)
67
- ], CustomFieldEntries.prototype, "entityId", void 0);
68
63
  __decorate([
69
64
  (0, sequelize_typescript_1.Column)({
70
65
  type: sequelize_typescript_1.DataType.JSONB,
@@ -93,6 +88,11 @@ __decorate([
93
88
  __metadata("design:type", Date)
94
89
  ], CustomFieldEntries.prototype, "updatedAt", void 0);
95
90
  __decorate([
91
+ sequelize_typescript_1.BeforeBulkCreate,
92
+ sequelize_typescript_1.BeforeBulkUpdate,
93
+ sequelize_typescript_1.BeforeUpdate,
94
+ sequelize_typescript_1.BeforeCreate,
95
+ sequelize_typescript_1.BeforeUpsert,
96
96
  sequelize_typescript_1.AfterUpsert,
97
97
  __metadata("design:type", Function),
98
98
  __metadata("design:paramtypes", [CustomFieldEntries, Object]),
@@ -110,12 +110,17 @@ CustomFieldEntries = __decorate([
110
110
  },
111
111
  ],
112
112
  validate: {
113
- async validationByType() {
114
- if (!Object.keys(this.customFields ?? {}).length) {
115
- return;
113
+ validationByType() {
114
+ if (Object.keys(this.customFields ?? {}).length) {
115
+ CustomFieldDefinitionRepo.getCustomFieldDefinitionsDictionary([this])
116
+ .then((definitionsByName) => {
117
+ (0, validations_1.validateInstanceCustomFieldEntries)(this, definitionsByName);
118
+ })
119
+ .catch((error) => {
120
+ logger_1.default.error('Validation error on custom_field_entries', error);
121
+ throw error;
122
+ });
116
123
  }
117
- const definitionsByName = await CustomFieldDefinitionRepo.getCustomFieldDefinitionsDictionary([this]);
118
- (0, validations_1.validateInstanceCustomFieldEntries)(this, definitionsByName);
119
124
  },
120
125
  },
121
126
  })
@@ -6,12 +6,8 @@ import ContextAwareTestModel from './tests/contextAwareModels/ContextAwareTestMo
6
6
  import ContextTestModel from './tests/contextAwareModels/ContextTestModel';
7
7
  import AssociatedTestModel from './tests/AssociatedTestModel';
8
8
  import type { CustomFieldOptions } from '../types';
9
- import CustomFieldEntries from './CustomFieldEntries';
10
- interface InitTablesOptions {
11
- schemaPrefix?: string;
12
- schemaVersion?: string;
13
- useCustomFieldsEntries?: boolean;
14
- }
15
- declare const initTables: (sequelize: Sequelize, getUser: CustomFieldOptions['getUser'], { schemaPrefix, schemaVersion, useCustomFieldsEntries, }?: InitTablesOptions) => Promise<void>;
9
+ declare const initTables: (sequelize: Sequelize, getUser: CustomFieldOptions['getUser'], options: {
10
+ useCustomFieldsEntries: boolean;
11
+ }) => Promise<void>;
16
12
  declare const initTestModels: (sequelize: Sequelize) => Promise<void>;
17
- export { CustomFieldValue, CustomFieldDefinition, CustomFieldEntries, TestModel, AssociatedTestModel, ContextAwareTestModel, ContextTestModel, initTables, initTestModels, };
13
+ export { CustomFieldValue, CustomFieldDefinition, TestModel, AssociatedTestModel, ContextAwareTestModel, ContextTestModel, initTables, initTestModels, };
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.initTestModels = exports.initTables = exports.ContextTestModel = exports.ContextAwareTestModel = exports.AssociatedTestModel = exports.TestModel = exports.CustomFieldEntries = exports.CustomFieldDefinition = exports.CustomFieldValue = void 0;
6
+ exports.initTestModels = exports.initTables = exports.ContextTestModel = exports.ContextAwareTestModel = exports.AssociatedTestModel = exports.TestModel = exports.CustomFieldDefinition = exports.CustomFieldValue = void 0;
7
7
  /* eslint-disable no-param-reassign */
8
8
  const sequelize_1 = require("sequelize");
9
9
  const logger_1 = __importDefault(require("../utils/logger"));
@@ -20,23 +20,21 @@ exports.ContextTestModel = ContextTestModel_1.default;
20
20
  const AssociatedTestModel_1 = __importDefault(require("./tests/AssociatedTestModel"));
21
21
  exports.AssociatedTestModel = AssociatedTestModel_1.default;
22
22
  const CustomFieldEntries_1 = __importDefault(require("./CustomFieldEntries"));
23
- exports.CustomFieldEntries = CustomFieldEntries_1.default;
24
23
  const productionModels = [CustomFieldDefinition_1.default, CustomFieldValue_1.default];
25
24
  const testModels = [TestModel_1.default, AssociatedTestModel_1.default, ContextAwareTestModel_1.default, ContextTestModel_1.default];
26
25
  const SADOT_MIGRATION_PREFIX = 'sadot-migration';
27
26
  const SCHEMA_VERSION = 'fb0fa867-1241-4816-b08d-5ed9060c7ae5';
28
- const initTables = async (sequelize, getUser, { schemaPrefix, schemaVersion, useCustomFieldsEntries, } = {
29
- schemaPrefix: SADOT_MIGRATION_PREFIX,
30
- schemaVersion: SCHEMA_VERSION,
31
- useCustomFieldsEntries: false,
32
- }) => {
33
- const CUSTOM_FIELDS_SCHEMA_VERSION = `${schemaPrefix}_${schemaVersion}${useCustomFieldsEntries ? '_withEntries' : ''}`;
27
+ const CUSTOM_FIELDS_SCHEMA_VERSION = `${SADOT_MIGRATION_PREFIX}_${SCHEMA_VERSION}`;
28
+ const CUSTOM_FIELDS_SCHEMA_VERSION_WITH_ENTRIES = `${CUSTOM_FIELDS_SCHEMA_VERSION}_withEntries`;
29
+ const initTables = async (sequelize, getUser, options) => {
30
+ const { useCustomFieldsEntries } = options;
34
31
  logger_1.default.info('custom-fields: initialize custom-fields tables');
35
32
  // Detect models and import them to the orm
36
33
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
37
34
  if (!sequelize.addModels) {
38
35
  throw new Error('sequelize instance must have addModels function');
39
36
  }
37
+ const schemaVersion = useCustomFieldsEntries ? CUSTOM_FIELDS_SCHEMA_VERSION_WITH_ENTRIES : CUSTOM_FIELDS_SCHEMA_VERSION;
40
38
  if (useCustomFieldsEntries) {
41
39
  productionModels.push(CustomFieldEntries_1.default);
42
40
  }
@@ -70,26 +68,17 @@ const initTables = async (sequelize, getUser, { schemaPrefix, schemaVersion, use
70
68
  timestamps: false,
71
69
  schema: 'public',
72
70
  });
73
- const migrations = await SequelizeMeta.findAll({ where: { name: { [sequelize_1.Op.like]: `${schemaPrefix}%` } }, raw: true });
74
- const currentSadotSchemaVersion = migrations.at(-1);
75
- const expectedSchemaVersionIndex = migrations.findIndex((m) => m.name === CUSTOM_FIELDS_SCHEMA_VERSION);
76
- if (!currentSadotSchemaVersion || currentSadotSchemaVersion.name !== CUSTOM_FIELDS_SCHEMA_VERSION) {
71
+ const migrations = await SequelizeMeta.findAll({ raw: true });
72
+ const currentSadotSchemaVersion = migrations.reverse().find((m) => m.name.includes(SADOT_MIGRATION_PREFIX));
73
+ if (!currentSadotSchemaVersion
74
+ || currentSadotSchemaVersion.name !== schemaVersion) {
77
75
  await CustomFieldDefinition_1.default.sync({ alter: true });
78
76
  await CustomFieldValue_1.default.sync({ alter: true });
79
- // T.Y TODO: Remove the if statement once we're ready to add the new entries table for all MS
80
77
  if (useCustomFieldsEntries) {
81
78
  await CustomFieldEntries_1.default.sync({ alter: true });
82
79
  }
83
- if (expectedSchemaVersionIndex === -1) {
84
- await SequelizeMeta.create({ name: CUSTOM_FIELDS_SCHEMA_VERSION });
85
- }
80
+ await SequelizeMeta.create({ name: schemaVersion });
86
81
  logger_1.default.info('custom-fields: models synced');
87
- if (migrations.length && expectedSchemaVersionIndex !== -1 && expectedSchemaVersionIndex < migrations.length - 1) {
88
- // We have existing migrations, and we are calling `sync`.
89
- // This means we are in a `down` migration, and hence we should delete newer migrations to ensure we can reapply them.
90
- const migrationsToDelete = migrations.slice(expectedSchemaVersionIndex + 1);
91
- await SequelizeMeta.destroy({ where: { name: { [sequelize_1.Op.in]: migrationsToDelete.map((m) => m.name) } } });
92
- }
93
82
  }
94
83
  };
95
84
  exports.initTables = initTables;
@@ -1,7 +1,8 @@
1
1
  import { type Includeable, type Transaction, type FindOptions, type WhereOptions } from 'sequelize';
2
- import { CustomFieldDefinition, type CustomFieldEntries } from '../models';
2
+ import { CustomFieldDefinition } from '../models';
3
3
  import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../types/definition';
4
4
  import type { ModelOptions } from '../types';
5
+ import type { CustomFieldEntriesDTO } from '../types/entries';
5
6
  export declare const create: (data: CreateCustomFieldDefinition) => Promise<CustomFieldDefinition>;
6
7
  interface SadotFindOptions {
7
8
  withDisabled?: boolean;
@@ -27,10 +28,10 @@ export declare const destroy: (id: string) => Promise<number>;
27
28
  */
28
29
  export declare const getRequiredFields: (modelType: string, modelId: string | string[], entityId: string | string[], modelOptions?: ModelOptions) => Promise<string[]>;
29
30
  /**
30
- * @returns A promise resolving with a dictionary of custom field definitions by name.
31
- * @throws A {@link MissingDefinitionError} if any of the custom fields doesn't have a definition.
31
+ * @returns A dictionary of custom field definitions by name.
32
+ * @throws A MissingDefinitionError if any of the custom fields doesn't have a definition.
32
33
  */
33
- export declare const getCustomFieldDefinitionsDictionary: (instances: CustomFieldEntries[], options?: SadotGetDefinitionsByEntityIdsOptions) => Promise<{
34
+ export declare const getCustomFieldDefinitionsDictionary: (instances: CustomFieldEntriesDTO[], options?: SadotGetDefinitionsByEntityIdsOptions) => Promise<{
34
35
  [definitionName: string]: CustomFieldDefinition;
35
36
  }>;
36
37
  export {};
@@ -88,23 +88,30 @@ const getRequiredFields = async (modelType, modelId, entityId, modelOptions = {}
88
88
  };
89
89
  exports.getRequiredFields = getRequiredFields;
90
90
  /**
91
- * @returns A promise resolving with a dictionary of custom field definitions by name.
92
- * @throws A {@link MissingDefinitionError} if any of the custom fields doesn't have a definition.
91
+ * @returns A dictionary of custom field definitions by name.
92
+ * @throws A MissingDefinitionError if any of the custom fields doesn't have a definition.
93
93
  */
94
94
  const getCustomFieldDefinitionsDictionary = async (instances, options = { withDisabled: false, modelOptions: {} }) => {
95
- const { modelType } = instances[0]?.dataValues ?? {};
95
+ const { modelType } = instances[0];
96
96
  const customFields = new Set();
97
97
  const modelIds = [];
98
- const entityIds = new Set();
99
98
  instances.forEach((instance) => {
100
- const { dataValues: { modelId, entityId, customFields: instanceCustomFields } } = instance;
101
- modelIds.push(modelId);
102
- entityIds.add(entityId);
103
- Object.keys(instanceCustomFields ?? {}).forEach((fieldName) => {
99
+ modelIds.push(instance.modelId);
100
+ Object.keys(instance.customFields).forEach((fieldName) => {
104
101
  customFields.add(fieldName);
105
102
  });
106
103
  });
107
- const definitions = await (0, exports.findByEntityIds)(modelType, Array.from(entityIds), { ...options });
104
+ const { include, useEntityIdFromInclude } = options.modelOptions;
105
+ const where = {
106
+ modelType,
107
+ ...(!useEntityIdFromInclude && { entityId: modelIds }),
108
+ };
109
+ const definitions = await (0, exports.findAll)({
110
+ where,
111
+ transaction: options.transaction,
112
+ include: include?.(modelIds),
113
+ raw: true,
114
+ }, options);
108
115
  const matchedDefinitions = definitions.filter((def) => customFields.has(def.name));
109
116
  const matchedDefinitionsByName = Object.fromEntries(matchedDefinitions.map((definition) => [definition.name, definition]));
110
117
  if (!definitions?.length || matchedDefinitions.length !== customFields.size) {
@@ -18,6 +18,9 @@ const castIfNeeded = (columnName, conditionValue) => {
18
18
  if (isDate(conditionValue)) {
19
19
  return castValueToJsonb(columnName, 'timestamp');
20
20
  }
21
+ if (!Number.isNaN(Number(conditionValue))) {
22
+ return castValueToJsonbNumeric(columnName);
23
+ }
21
24
  return columnName;
22
25
  };
23
26
  const AND_DELIMITER = ' AND ';
@@ -64,7 +64,6 @@ const createDefinition = (defaults) => ({
64
64
  fieldType: defaults?.fieldType || 'boolean',
65
65
  entityId: defaults?.entityId || (0, node_crypto_1.randomUUID)(),
66
66
  entityType: defaults?.entityType || 'fleetId',
67
- ...(defaults?.validation && { validation: defaults.validation }),
68
67
  ...(defaults?.defaultValue && { defaultValue: defaults.defaultValue }),
69
68
  });
70
69
  exports.createDefinition = createDefinition;
@@ -1,4 +1,3 @@
1
1
  export declare const mockEvent: (events: any, eventName: any, numberOfEvents: any) => any[];
2
2
  export declare const mockDimCustomFieldDefinitionEvent: (events: any, numberOfEvents: any) => any[];
3
3
  export declare const mockDimCustomFieldValueEvent: (events: any, numberOfEvents: any) => any[];
4
- export declare const mockDimCustomFieldEntriesEvent: (events: any, numberOfEvents: any) => any[];
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /* eslint-disable no-param-reassign */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.mockDimCustomFieldEntriesEvent = exports.mockDimCustomFieldValueEvent = exports.mockDimCustomFieldDefinitionEvent = exports.mockEvent = void 0;
4
+ exports.mockDimCustomFieldValueEvent = exports.mockDimCustomFieldDefinitionEvent = exports.mockEvent = void 0;
5
5
  const mockEvent = (events, eventName, numberOfEvents) => {
6
6
  events.sendObject = jest.fn();
7
7
  return [
@@ -17,5 +17,3 @@ const mockDimCustomFieldDefinitionEvent = (events, numberOfEvents) => (0, export
17
17
  exports.mockDimCustomFieldDefinitionEvent = mockDimCustomFieldDefinitionEvent;
18
18
  const mockDimCustomFieldValueEvent = (events, numberOfEvents) => (0, exports.mockEvent)(events, 'dim_custom_field_value', numberOfEvents);
19
19
  exports.mockDimCustomFieldValueEvent = mockDimCustomFieldValueEvent;
20
- const mockDimCustomFieldEntriesEvent = (events, numberOfEvents) => (0, exports.mockEvent)(events, 'dim_custom_field_entries', numberOfEvents);
21
- exports.mockDimCustomFieldEntriesEvent = mockDimCustomFieldEntriesEvent;
@@ -1,20 +1,10 @@
1
1
  import type { ValidationError } from 'joi';
2
2
  export interface CustomFieldEntriesDTO {
3
3
  modelId: string;
4
- entityId: string;
5
4
  modelType: string;
6
- /**
7
- * A collection of custom fields where each key is the name of a CustomFieldDefinition and
8
- * each value is the value of that custom field for this specific `modelId`.
9
- *
10
- * Example:
11
- * {
12
- * "vehicleColor": "Red",
13
- * "vehicleType": "premium",
14
- * "isActive": true
15
- * }
16
- */
17
- customFields: Record<string, any>;
5
+ customFields: {
6
+ [customFieldName: string]: any;
7
+ };
18
8
  createdAt?: Date;
19
9
  updatedAt?: Date;
20
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/sadot",
3
- "version": "0.9.0",
3
+ "version": "0.9.1-beta-8053a0cc.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -25,10 +25,6 @@ const modelTableMapping = {
25
25
  tableName: 'dim_custom_field_value',
26
26
  eventVersion: '1',
27
27
  },
28
- CustomFieldEntries: {
29
- tableName: 'dim_custom_field_entries',
30
- eventVersion: '1',
31
- },
32
28
  };
33
29
 
34
30
  export const sendDimEvent = (instance): void => {
@@ -4,11 +4,17 @@ import {
4
4
  Model,
5
5
  PrimaryKey,
6
6
  DataType,
7
+ BeforeCreate,
8
+ BeforeUpsert,
7
9
  AfterUpsert,
10
+ BeforeUpdate,
11
+ BeforeBulkCreate,
12
+ BeforeBulkUpdate,
8
13
  } from 'sequelize-typescript';
9
14
  import { sendDimEvent } from '../events';
10
15
  import * as CustomFieldDefinitionRepo from '../repository/definition';
11
16
  import { validateInstanceCustomFieldEntries } from '../utils/validations';
17
+ import logger from '../utils/logger';
12
18
 
13
19
  @Table({
14
20
  timestamps: true,
@@ -21,13 +27,17 @@ import { validateInstanceCustomFieldEntries } from '../utils/validations';
21
27
  },
22
28
  ],
23
29
  validate: {
24
- async validationByType(this: CustomFieldEntries) {
25
- if (!Object.keys(this.customFields ?? {}).length) {
26
- return;
30
+ validationByType(this: CustomFieldEntries) {
31
+ if (Object.keys(this.customFields ?? {}).length) {
32
+ CustomFieldDefinitionRepo.getCustomFieldDefinitionsDictionary([this])
33
+ .then((definitionsByName) => {
34
+ validateInstanceCustomFieldEntries(this, definitionsByName);
35
+ })
36
+ .catch((error) => {
37
+ logger.error('Validation error on custom_field_entries', error);
38
+ throw error;
39
+ });
27
40
  }
28
-
29
- const definitionsByName = await CustomFieldDefinitionRepo.getCustomFieldDefinitionsDictionary([this]);
30
- validateInstanceCustomFieldEntries(this, definitionsByName);
31
41
  },
32
42
  },
33
43
  })
@@ -37,23 +47,18 @@ class CustomFieldEntries extends Model {
37
47
  type: DataType.UUID,
38
48
  allowNull: false,
39
49
  })
40
- /** The ID of the model of which this row hold the custom field entries of, e.g. vehicleId / stopPointId / etc. */
50
+ /** The ID of the entity of which this row hold the custom field entries of, e.g. vehicleId / stopPointId / etc. */
41
51
  modelId!: string;
42
52
 
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
53
  @Column({
51
54
  type: DataType.JSONB,
52
55
  allowNull: false,
53
56
  defaultValue: {},
54
57
  })
55
58
  /** A dictionary of customFields and values with the following structure: `{ customFieldName: 'CustomFieldValue' }` */
56
- customFields!: Record<string, any>;
59
+ customFields!: {
60
+ [customFieldName: string]: any
61
+ };
57
62
 
58
63
  @Column({
59
64
  type: DataType.STRING,
@@ -68,6 +73,13 @@ class CustomFieldEntries extends Model {
68
73
  @Column
69
74
  updatedAt?: Date;
70
75
 
76
+ @BeforeBulkCreate
77
+ @BeforeBulkUpdate
78
+
79
+ @BeforeUpdate
80
+ @BeforeCreate
81
+ @BeforeUpsert
82
+
71
83
  @AfterUpsert
72
84
  static afterSaveHandler(instance: CustomFieldEntries, options): void {
73
85
  if (options.transaction) {
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable no-param-reassign */
2
- import { DataTypes, Op } from 'sequelize';
2
+ import { DataTypes } from 'sequelize';
3
3
  import type { Sequelize } from 'sequelize-typescript';
4
4
  import logger from '../utils/logger';
5
5
  import CustomFieldDefinition from './CustomFieldDefinition';
@@ -12,32 +12,23 @@ import type { CustomFieldOptions } from '../types';
12
12
  import CustomFieldEntries from './CustomFieldEntries';
13
13
 
14
14
  type ProductionModel = typeof CustomFieldDefinition | typeof CustomFieldValue | typeof CustomFieldEntries
15
- interface InitTablesOptions {
16
- schemaPrefix?: string
17
- schemaVersion?: string
18
- useCustomFieldsEntries?: boolean
19
- }
20
15
 
21
16
  const productionModels: ProductionModel[] = [CustomFieldDefinition, CustomFieldValue];
22
17
  const testModels = [TestModel, AssociatedTestModel, ContextAwareTestModel, ContextTestModel];
23
18
 
24
19
  const SADOT_MIGRATION_PREFIX = 'sadot-migration';
25
20
  const SCHEMA_VERSION = 'fb0fa867-1241-4816-b08d-5ed9060c7ae5';
21
+ const CUSTOM_FIELDS_SCHEMA_VERSION = `${SADOT_MIGRATION_PREFIX}_${SCHEMA_VERSION}`;
22
+ const CUSTOM_FIELDS_SCHEMA_VERSION_WITH_ENTRIES = `${CUSTOM_FIELDS_SCHEMA_VERSION}_withEntries`;
26
23
 
27
24
  const initTables = async (
28
25
  sequelize: Sequelize,
29
26
  getUser: CustomFieldOptions['getUser'],
30
- {
31
- schemaPrefix,
32
- schemaVersion,
33
- useCustomFieldsEntries,
34
- }: InitTablesOptions = {
35
- schemaPrefix: SADOT_MIGRATION_PREFIX,
36
- schemaVersion: SCHEMA_VERSION,
37
- useCustomFieldsEntries: false,
27
+ options: {
28
+ useCustomFieldsEntries: boolean
38
29
  },
39
30
  ): Promise<void> => {
40
- const CUSTOM_FIELDS_SCHEMA_VERSION = `${schemaPrefix}_${schemaVersion}${useCustomFieldsEntries ? '_withEntries' : ''}`;
31
+ const { useCustomFieldsEntries } = options;
41
32
  logger.info('custom-fields: initialize custom-fields tables');
42
33
  // Detect models and import them to the orm
43
34
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
@@ -45,6 +36,7 @@ const initTables = async (
45
36
  throw new Error('sequelize instance must have addModels function');
46
37
  }
47
38
 
39
+ const schemaVersion = useCustomFieldsEntries ? CUSTOM_FIELDS_SCHEMA_VERSION_WITH_ENTRIES : CUSTOM_FIELDS_SCHEMA_VERSION;
48
40
  if (useCustomFieldsEntries) {
49
41
  productionModels.push(CustomFieldEntries);
50
42
  }
@@ -86,29 +78,22 @@ const initTables = async (
86
78
  schema: 'public',
87
79
  },
88
80
  );
81
+ const migrations = await SequelizeMeta.findAll({ raw: true });
82
+ const currentSadotSchemaVersion = migrations.reverse().find((m: any) => m.name.includes(SADOT_MIGRATION_PREFIX));
89
83
 
90
- const migrations = await SequelizeMeta.findAll({ where: { name: { [Op.like]: `${schemaPrefix}%` } }, raw: true });
91
- const currentSadotSchemaVersion = migrations.at(-1);
92
- const expectedSchemaVersionIndex = migrations.findIndex((m) => (m as any).name === CUSTOM_FIELDS_SCHEMA_VERSION);
93
-
94
- if (!currentSadotSchemaVersion || (currentSadotSchemaVersion as any).name !== CUSTOM_FIELDS_SCHEMA_VERSION) {
84
+ if (
85
+ !currentSadotSchemaVersion
86
+ || (currentSadotSchemaVersion as any).name !== schemaVersion
87
+ ) {
95
88
  await CustomFieldDefinition.sync({ alter: true });
96
89
  await CustomFieldValue.sync({ alter: true });
97
- // T.Y TODO: Remove the if statement once we're ready to add the new entries table for all MS
90
+
98
91
  if (useCustomFieldsEntries) {
99
92
  await CustomFieldEntries.sync({ alter: true });
100
93
  }
101
94
 
102
- if (expectedSchemaVersionIndex === -1) {
103
- await SequelizeMeta.create({ name: CUSTOM_FIELDS_SCHEMA_VERSION });
104
- }
95
+ await SequelizeMeta.create({ name: schemaVersion });
105
96
  logger.info('custom-fields: models synced');
106
- if (migrations.length && expectedSchemaVersionIndex !== -1 && expectedSchemaVersionIndex < migrations.length - 1) {
107
- // We have existing migrations, and we are calling `sync`.
108
- // This means we are in a `down` migration, and hence we should delete newer migrations to ensure we can reapply them.
109
- const migrationsToDelete = migrations.slice(expectedSchemaVersionIndex + 1);
110
- await SequelizeMeta.destroy({ where: { name: { [Op.in]: migrationsToDelete.map((m) => (m as any).name) } } });
111
- }
112
97
  }
113
98
  };
114
99
 
@@ -135,7 +120,6 @@ const initTestModels = async (sequelize: Sequelize): Promise<void> => {
135
120
  export {
136
121
  CustomFieldValue,
137
122
  CustomFieldDefinition,
138
- CustomFieldEntries,
139
123
  TestModel,
140
124
  AssociatedTestModel,
141
125
  ContextAwareTestModel,
@@ -2,10 +2,11 @@ import {
2
2
  Op,
3
3
  type Includeable, type Transaction, type FindOptions, type WhereOptions,
4
4
  } from 'sequelize';
5
- import { CustomFieldDefinition, type CustomFieldEntries } from '../models';
5
+ import { CustomFieldDefinition } from '../models';
6
6
  import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../types/definition';
7
7
  import type { ModelOptions } from '../types';
8
8
  import { MissingDefinitionError } from '../errors';
9
+ import type { CustomFieldEntriesDTO } from '../types/entries';
9
10
 
10
11
  export const create = (data: CreateCustomFieldDefinition): Promise<CustomFieldDefinition> =>
11
12
  CustomFieldDefinition.create(data);
@@ -134,28 +135,36 @@ export const getRequiredFields = async (
134
135
  };
135
136
 
136
137
  /**
137
- * @returns A promise resolving with a dictionary of custom field definitions by name.
138
- * @throws A {@link MissingDefinitionError} if any of the custom fields doesn't have a definition.
138
+ * @returns A dictionary of custom field definitions by name.
139
+ * @throws A MissingDefinitionError if any of the custom fields doesn't have a definition.
139
140
  */
140
141
  export const getCustomFieldDefinitionsDictionary = async (
141
- instances: CustomFieldEntries[],
142
+ instances: CustomFieldEntriesDTO[],
142
143
  options: SadotGetDefinitionsByEntityIdsOptions = { withDisabled: false, modelOptions: {} },
143
144
  ): Promise<{ [definitionName: string]: CustomFieldDefinition }> => {
144
- const { modelType } = instances[0]?.dataValues ?? {};
145
+ const { modelType } = instances[0];
145
146
  const customFields = new Set<string>();
146
147
  const modelIds = [];
147
- const entityIds = new Set<string>();
148
148
  instances.forEach((instance) => {
149
- const { dataValues: { modelId, entityId, customFields: instanceCustomFields } } = instance;
150
- modelIds.push(modelId);
151
- entityIds.add(entityId);
149
+ modelIds.push(instance.modelId);
152
150
 
153
- Object.keys(instanceCustomFields ?? {}).forEach((fieldName) => {
151
+ Object.keys(instance.customFields).forEach((fieldName) => {
154
152
  customFields.add(fieldName);
155
153
  });
156
154
  });
157
155
 
158
- const definitions = await findByEntityIds(modelType, Array.from(entityIds), { ...options });
156
+ const { include, useEntityIdFromInclude } = options.modelOptions;
157
+ const where: WhereOptions = {
158
+ modelType,
159
+ ...(!useEntityIdFromInclude && { entityId: modelIds }),
160
+ };
161
+
162
+ const definitions = await findAll({
163
+ where,
164
+ transaction: options.transaction,
165
+ include: include?.(modelIds),
166
+ raw: true,
167
+ }, options);
159
168
 
160
169
  const matchedDefinitions = definitions.filter((def) => customFields.has(def.name));
161
170
  const matchedDefinitionsByName = Object.fromEntries(matchedDefinitions.map((definition) => [definition.name, definition]));
@@ -44,6 +44,9 @@ const castIfNeeded = (columnName: string, conditionValue: string): string => {
44
44
  if (isDate(conditionValue)) {
45
45
  return castValueToJsonb(columnName, 'timestamp');
46
46
  }
47
+ if (!Number.isNaN(Number(conditionValue))) {
48
+ return castValueToJsonbNumeric(columnName);
49
+ }
47
50
  return columnName;
48
51
  };
49
52
  const AND_DELIMITER = ' AND ';
@@ -1,4 +1,4 @@
1
- import { randomUUID } from 'node:crypto';
1
+ import { randomUUID as uuidv4 } from 'node:crypto';
2
2
  import type { CreateCustomFieldDefinition, CustomFieldDefinitionDTO } from '../../types/definition';
3
3
 
4
4
  export const contextAwareFieldDefinition = {
@@ -12,7 +12,7 @@ export const coolFieldDefinition: CreateCustomFieldDefinition = {
12
12
  name: 'cool field',
13
13
  modelType: 'TestModel',
14
14
  fieldType: 'number',
15
- entityId: randomUUID(),
15
+ entityId: uuidv4(),
16
16
  entityType: 'fleetId',
17
17
  };
18
18
 
@@ -30,7 +30,7 @@ export const booleanField = (modelType: string): CreateCustomFieldDefinition =>
30
30
  name: 'shapeless',
31
31
  modelType,
32
32
  fieldType: 'boolean',
33
- entityId: randomUUID(),
33
+ entityId: uuidv4(),
34
34
  entityType: 'fleetId',
35
35
  });
36
36
 
@@ -39,7 +39,7 @@ export const selectField = (modelType: string, options): CreateCustomFieldDefini
39
39
  modelType,
40
40
  fieldType: 'select',
41
41
  validation: options,
42
- entityId: randomUUID(),
42
+ entityId: uuidv4(),
43
43
  entityType: 'fleetId',
44
44
  });
45
45
 
@@ -48,7 +48,7 @@ export const statusField = (modelType: string, options): CreateCustomFieldDefini
48
48
  modelType,
49
49
  fieldType: 'status',
50
50
  validation: options,
51
- entityId: randomUUID(),
51
+ entityId: uuidv4(),
52
52
  entityType: 'fleetId',
53
53
  });
54
54
 
@@ -56,18 +56,17 @@ export const fileField = (modelType: string): CreateCustomFieldDefinition => ({
56
56
  name: 'file',
57
57
  modelType,
58
58
  fieldType: 'file',
59
- entityId: randomUUID(),
59
+ entityId: uuidv4(),
60
60
  entityType: 'fleetId',
61
61
  });
62
62
 
63
63
  // eslint-disable-next-line max-len
64
64
  export const createDefinition = (defaults: Partial<CustomFieldDefinitionDTO>): CreateCustomFieldDefinition => ({
65
- name: defaults?.name || `def_${randomUUID()}`,
65
+ name: defaults?.name || `def_${uuidv4()}`,
66
66
  modelType: defaults?.modelType || 'TestModel',
67
67
  fieldType: defaults?.fieldType || 'boolean',
68
- entityId: defaults?.entityId || randomUUID(),
68
+ entityId: defaults?.entityId || uuidv4(),
69
69
  entityType: defaults?.entityType || 'fleetId',
70
- ...(defaults?.validation && { validation: defaults.validation }),
71
70
  ...(defaults?.defaultValue && { defaultValue: defaults.defaultValue }),
72
71
  });
73
72
 
@@ -76,9 +75,9 @@ export const createDefinitions = (
76
75
  length = 1,
77
76
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
78
77
  ): CreateCustomFieldDefinition[] => (Array(length).fill({}).map((_) => ({
79
- name: defaults?.name || `def_${randomUUID()}`,
78
+ name: defaults?.name || `def_${uuidv4()}`,
80
79
  modelType: defaults?.modelType || 'TestModel',
81
80
  fieldType: defaults?.fieldType || 'boolean',
82
- entityId: defaults?.entityId || randomUUID(),
81
+ entityId: defaults?.entityId || uuidv4(),
83
82
  entityType: defaults?.entityType || 'fleetId',
84
83
  })));
@@ -16,6 +16,3 @@ export const mockDimCustomFieldDefinitionEvent = (events, numberOfEvents) =>
16
16
 
17
17
  export const mockDimCustomFieldValueEvent = (events, numberOfEvents) =>
18
18
  mockEvent(events, 'dim_custom_field_value', numberOfEvents);
19
-
20
- export const mockDimCustomFieldEntriesEvent = (events, numberOfEvents) =>
21
- mockEvent(events, 'dim_custom_field_entries', numberOfEvents);
@@ -2,20 +2,10 @@ import type { ValidationError } from 'joi';
2
2
 
3
3
  export interface CustomFieldEntriesDTO {
4
4
  modelId: string;
5
- entityId: string;
6
5
  modelType: string;
7
- /**
8
- * A collection of custom fields where each key is the name of a CustomFieldDefinition and
9
- * each value is the value of that custom field for this specific `modelId`.
10
- *
11
- * Example:
12
- * {
13
- * "vehicleColor": "Red",
14
- * "vehicleType": "premium",
15
- * "isActive": true
16
- * }
17
- */
18
- customFields: Record<string, any>;
6
+ customFields: {
7
+ [customFieldName: string]: any;
8
+ };
19
9
  createdAt?: Date;
20
10
  updatedAt?: Date;
21
11
  }