@autofleet/sadot 0.5.5-beta.9 → 0.6.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 (51) hide show
  1. package/dist/hooks/create.d.ts +2 -1
  2. package/dist/hooks/create.js +8 -4
  3. package/dist/hooks/enrich.d.ts +2 -1
  4. package/dist/hooks/enrich.js +17 -4
  5. package/dist/hooks/update.d.ts +2 -1
  6. package/dist/hooks/update.js +4 -3
  7. package/dist/index.d.ts +0 -1
  8. package/dist/index.js +1 -1
  9. package/dist/models/index.d.ts +3 -1
  10. package/dist/models/index.js +8 -2
  11. package/dist/models/tests/contextAwareModels/ContextAwareTestModel.d.ts +10 -0
  12. package/dist/models/tests/contextAwareModels/ContextAwareTestModel.js +55 -0
  13. package/dist/models/tests/contextAwareModels/ContextTestModel.d.ts +13 -0
  14. package/dist/models/tests/contextAwareModels/ContextTestModel.js +47 -0
  15. package/dist/repository/definition.d.ts +7 -4
  16. package/dist/repository/definition.js +27 -15
  17. package/dist/repository/value.d.ts +5 -1
  18. package/dist/repository/value.js +13 -3
  19. package/dist/tests/helpers/database-config.d.ts +1 -0
  20. package/dist/tests/helpers/database-config.js +1 -0
  21. package/dist/tests/helpers/index.js +2 -0
  22. package/dist/tests/mocks/definition.mock.d.ts +6 -0
  23. package/dist/tests/mocks/definition.mock.js +7 -1
  24. package/dist/types/index.d.ts +20 -2
  25. package/dist/utils/init.d.ts +5 -4
  26. package/dist/utils/init.js +13 -7
  27. package/dist/utils/scopeAttributes.d.ts +2 -0
  28. package/dist/utils/scopeAttributes.js +11 -0
  29. package/package.json +20 -2
  30. package/src/api/v1/definition/index.ts +2 -4
  31. package/src/hooks/create.ts +13 -5
  32. package/src/hooks/enrich.ts +20 -4
  33. package/src/hooks/update.ts +6 -3
  34. package/src/index.ts +4 -2
  35. package/src/models/index.ts +7 -1
  36. package/src/models/tests/contextAwareModels/ContextAwareTestModel.ts +45 -0
  37. package/src/models/tests/contextAwareModels/ContextTestModel.ts +38 -0
  38. package/src/repository/definition.ts +38 -19
  39. package/src/repository/value.ts +18 -7
  40. package/src/tests/helpers/database-config.ts +1 -0
  41. package/src/tests/helpers/index.ts +5 -1
  42. package/src/tests/mocks/definition.mock.ts +7 -0
  43. package/src/types/index.ts +23 -3
  44. package/src/utils/init.ts +19 -10
  45. package/src/utils/scopeAttributes.ts +12 -0
  46. package/dist/tests/functional/searching/index.d.ts +0 -8
  47. package/dist/tests/functional/searching/index.js +0 -46
  48. package/dist/utils/helpers/index.d.ts +0 -22
  49. package/dist/utils/helpers/index.js +0 -28
  50. package/src/tests/functional/searching/index.ts +0 -39
  51. package/src/utils/helpers/index.ts +0 -50
@@ -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.addScopes = exports.removeHooks = exports.addHooks = void 0;
6
+ exports.applyCustomAssociation = exports.addScopes = exports.removeHooks = exports.addHooks = void 0;
7
7
  const sequelize_1 = require("sequelize");
8
8
  const common_types_1 = require("@autofleet/common-types");
9
9
  const models_1 = require("../models");
@@ -12,7 +12,7 @@ const scopes_1 = require("../scopes");
12
12
  const logger_1 = __importDefault(require("./logger"));
13
13
  const { CUSTOM_FIELDS_FILTER_SCOPE } = common_types_1.customFields;
14
14
  const addHooks = (models, getModel) => {
15
- models.forEach(async ({ name, scopeAttributes }) => {
15
+ models.forEach(async ({ name, scopeAttributes, modelOptions, }) => {
16
16
  try {
17
17
  const model = getModel(name);
18
18
  if (!model) {
@@ -31,11 +31,11 @@ const addHooks = (models, getModel) => {
31
31
  model.addHook('beforeFind', 'sadot-beforeFind', (0, hooks_1.beforeFind)(scopeAttributes));
32
32
  model.addHook('beforeBulkCreate', 'sadot-beforeBulkCreate', hooks_1.beforeBulkCreate);
33
33
  model.addHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate', hooks_1.beforeBulkUpdate);
34
- model.addHook('beforeCreate', 'sadot-beforeCreate', (0, hooks_1.beforeCreate)(scopeAttributes));
35
- model.addHook('beforeUpdate', 'sadot-beforeUpdate', (0, hooks_1.beforeUpdate)(scopeAttributes));
36
- model.addHook('afterFind', 'sadot-afterFind', (0, hooks_1.enrichResults)(name, scopeAttributes, 'afterFind'));
37
- model.addHook('afterUpdate', 'sadot-afterUpdate', (0, hooks_1.enrichResults)(name, scopeAttributes));
38
- model.addHook('afterCreate', 'sadot-afterCreate', (0, hooks_1.enrichResults)(name, scopeAttributes));
34
+ model.addHook('beforeCreate', 'sadot-beforeCreate', (0, hooks_1.beforeCreate)(scopeAttributes, modelOptions));
35
+ model.addHook('beforeUpdate', 'sadot-beforeUpdate', (0, hooks_1.beforeUpdate)(scopeAttributes, modelOptions));
36
+ model.addHook('afterFind', 'sadot-afterFind', (0, hooks_1.enrichResults)(name, scopeAttributes, 'afterFind', modelOptions));
37
+ model.addHook('afterUpdate', 'sadot-afterUpdate', (0, hooks_1.enrichResults)(name, scopeAttributes, null, modelOptions));
38
+ model.addHook('afterCreate', 'sadot-afterCreate', (0, hooks_1.enrichResults)(name, scopeAttributes, null, modelOptions));
39
39
  }
40
40
  catch (e) {
41
41
  logger_1.default.error(`Could not add custom fields hook to model ${name}. `, e);
@@ -98,3 +98,9 @@ const addScopes = (models, getModel) => {
98
98
  });
99
99
  };
100
100
  exports.addScopes = addScopes;
101
+ const applyCustomAssociation = (models) => {
102
+ models.forEach(({ modelOptions }) => {
103
+ modelOptions?.customAssociation?.(models_1.CustomFieldDefinition);
104
+ });
105
+ };
106
+ exports.applyCustomAssociation = applyCustomAssociation;
@@ -0,0 +1,2 @@
1
+ declare const applyScopeToInstance: (instance: any, scopeAttributes: string[]) => any[];
2
+ export default applyScopeToInstance;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const mapAttributeToInstance = (scopeAttributes, instance) => scopeAttributes.map((attr) => instance[attr]);
4
+ const applyScopeToInstance = (instance, scopeAttributes) => {
5
+ const uniqueAttributes = Array.from(new Set(scopeAttributes));
6
+ if (Array.isArray(instance)) {
7
+ return instance.flatMap((ins) => mapAttributeToInstance(uniqueAttributes, ins));
8
+ }
9
+ return mapAttributeToInstance(uniqueAttributes, instance);
10
+ };
11
+ exports.default = applyScopeToInstance;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/sadot",
3
- "version": "0.5.5-beta.9",
3
+ "version": "0.6.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -9,7 +9,24 @@
9
9
  "linter": "./node_modules/.bin/eslint .",
10
10
  "test": "jest --forceExit --runInBand",
11
11
  "coverage": "jest --coverage --forceExit --runInBand && rm -rf ./coverage",
12
- "dev": "nodemon"
12
+ "build-to-local-repo": "npm run build && cp -r dist/* ../$REPO/node_modules/$npm_package_name/dist",
13
+ "dev": "nodemon",
14
+ "watch": "npm-watch build-to-local-repo"
15
+ },
16
+ "watch": {
17
+ "build-to-local-repo": {
18
+ "extensions": [
19
+ "js",
20
+ "jsx",
21
+ "ts",
22
+ "tsx",
23
+ "css"
24
+ ],
25
+ "patterns": [
26
+ "src"
27
+ ],
28
+ "quiet": false
29
+ }
13
30
  },
14
31
  "dependencies": {
15
32
  "@autofleet/common-types": "^1.7.29",
@@ -34,6 +51,7 @@
34
51
  "eslint-config-airbnb-typescript": "^12.0.0",
35
52
  "eslint-plugin-import": "^2.22.1",
36
53
  "jest": "^27.0.5",
54
+ "npm-watch": "^0.11.0",
37
55
  "ts-jest": "^27.0.3",
38
56
  "ts-node": "^8.6.2",
39
57
  "typescript": "^4.9.5",
@@ -65,10 +65,8 @@ router.get('/', async (req, res) => {
65
65
  if (entityIds?.length > 0) {
66
66
  where.entityId = entityIds;
67
67
  }
68
- const customFieldDefinitions = await DefinitionRepo.findAll(
69
- { ...where },
70
- { withDisabled: true },
71
- );
68
+ const customFieldDefinitions = await DefinitionRepo.findAll({ ...where },
69
+ { withDisabled: true });
72
70
  return res.json(customFieldDefinitions);
73
71
  } catch (err) {
74
72
  logger.error('Failed to fetch custom field definitions', err);
@@ -2,6 +2,8 @@ import logger from '../utils/logger';
2
2
  import * as ValueRepo from '../repository/value';
3
3
  import * as DefinitionRepo from '../repository/definition';
4
4
  import { MissingRequiredCustomFieldError } from '../errors';
5
+ import { ModelOptions } from '../types';
6
+ import applyScopeToInstance from '../utils/scopeAttributes';
5
7
 
6
8
  /**
7
9
  * A hook to create the custom fields when updating a model (more then one instance).
@@ -15,18 +17,20 @@ export const beforeBulkCreate = (options): void => {
15
17
  * A hook to create the custom fields when updating a model instance.
16
18
  * TODO - cleanup if update fail
17
19
  */
18
- export const beforeCreate = (scopeAttributes: string[]) => async (
20
+ export const beforeCreate = (scopeAttributes: string[], modelOptions: ModelOptions = {}) => async (
19
21
  instance,
20
22
  options,
21
23
  ): Promise<void> => {
22
24
  logger.debug('sadot - before create hook');
23
25
  const { fields } = options;
24
26
  const modelType = instance.constructor.name;
25
- const identifiers = scopeAttributes.map((attribute) => instance[attribute]);
27
+
28
+ const identifiers = applyScopeToInstance(instance, scopeAttributes);
29
+
26
30
  // get all model's required definitions
27
31
  const requiredFieldsNames = await DefinitionRepo.getRequiredFields(modelType,
28
- instance.id,
29
- identifiers);
32
+ instance.id, identifiers, modelOptions);
33
+
30
34
  const customFieldsIdx = fields.indexOf('customFields');
31
35
  const { customFields } = instance;
32
36
  if (customFieldsIdx > -1 && customFields) {
@@ -41,8 +45,12 @@ export const beforeCreate = (scopeAttributes: string[]) => async (
41
45
  instance.id,
42
46
  identifiers,
43
47
  customFields,
44
- { transaction: options.transaction },
48
+ {
49
+ transaction: options.transaction,
50
+ modelOptions,
51
+ },
45
52
  );
53
+
46
54
  // eslint-disable-next-line no-param-reassign
47
55
  fields.splice(customFieldsIdx, 1);
48
56
  } else if (requiredFieldsNames?.length > 0) {
@@ -4,6 +4,8 @@ import * as DefinitionRepo from '../repository/definition';
4
4
  import CustomFieldValue from '../models/CustomFieldValue';
5
5
  import CustomFieldDefinition from '../models/CustomFieldDefinition';
6
6
  import { SerializedCustomFields } from '../types/definition';
7
+ import { ModelOptions } from '../types';
8
+ import applyScopeToInstance from '../utils/scopeAttributes';
7
9
 
8
10
  type SupportedHookTypes = 'afterFind' | 'afterCreate' | 'afterUpdate';
9
11
 
@@ -30,6 +32,7 @@ const enrichResults = (
30
32
  modelType: string,
31
33
  scopeAttributes: string[],
32
34
  hookType?: SupportedHookTypes,
35
+ modelOptions: ModelOptions = {},
33
36
  ) => async (
34
37
  instancesOrInstance: any | any[],
35
38
  options,
@@ -48,21 +51,33 @@ const enrichResults = (
48
51
 
49
52
  instances = instances.filter(Boolean);
50
53
 
51
- const identifiers = instances.map((instance) =>
52
- scopeAttributes
53
- .map((attr) => instance[attr])).flat();
54
+ const identifiers = applyScopeToInstance(instances, scopeAttributes);
54
55
 
55
56
  const uniqueIdentifiers = [...new Set(identifiers)].filter(Boolean);
57
+
56
58
  const identifierCustomFieldDefinitionsMapping = uniqueIdentifiers.reduce((map, identifier) => ({
57
59
  ...map,
58
60
  [identifier]: [],
61
+
59
62
  }), {});
63
+
60
64
  const customFieldDefinitions = await DefinitionRepo.findByEntityIds(
61
65
  modelType,
62
66
  uniqueIdentifiers,
63
- { transaction: options.transaction },
67
+ { transaction: options.transaction, modelOptions },
64
68
  );
65
69
 
70
+ if (modelOptions?.include && modelOptions.useEntityIdFromInclude) {
71
+ // if we pass useEntityIdFromInclude,
72
+ // map the entity from the options to the identifierCustomFieldDefinitionsMapping
73
+ modelOptions.include(identifiers).forEach(({ model }) => {
74
+ customFieldDefinitions.forEach((cfd) => {
75
+ const entityId = cfd[`${model.name}.entityId`];
76
+ identifierCustomFieldDefinitionsMapping[entityId] = [];
77
+ });
78
+ });
79
+ }
80
+
66
81
  const definitionsMap = customFieldDefinitions.reduce((map, definition) => ({
67
82
  ...map,
68
83
  [definition.id]: definition,
@@ -104,6 +119,7 @@ const enrichResults = (
104
119
 
105
120
  scopeAttributes.forEach((attribute) => {
106
121
  const identifier = instance[attribute];
122
+
107
123
  const entityCustomFieldDefinitions = identifierCustomFieldDefinitionsMapping[identifier];
108
124
  if (entityCustomFieldDefinitions?.length > 0) {
109
125
  entityCustomFieldDefinitions.forEach((customFieldDefinition) => {
@@ -1,5 +1,7 @@
1
1
  import logger from '../utils/logger';
2
2
  import * as ValueRepo from '../repository/value';
3
+ import { ModelOptions } from '../types';
4
+ import applyScopeToInstance from '../utils/scopeAttributes';
3
5
 
4
6
  /**
5
7
  * A hook to update the custom fields when updating a model (more then one instance).
@@ -14,14 +16,14 @@ export const beforeBulkUpdate = (options): void => {
14
16
  * A hook to update the custom fields when updating a model instance.
15
17
  * TODO - cleanup if update fail
16
18
  */
17
- export const beforeUpdate = (scopeAttributes: string[]) => async (
19
+ export const beforeUpdate = (scopeAttributes: string[], modelOptions: ModelOptions = {}) => async (
18
20
  instance,
19
21
  options,
20
22
  ): Promise<void> => {
21
23
  logger.debug('sadot - before update hook');
22
24
  const { fields } = options;
23
25
  const modelType = instance.constructor.name;
24
- const identifiers = scopeAttributes.map((attribute) => instance[attribute]);
26
+ const identifiers = applyScopeToInstance(instance, scopeAttributes);
25
27
 
26
28
  const customFieldsIdx = fields.indexOf('customFields');
27
29
  if (customFieldsIdx > -1) {
@@ -31,7 +33,8 @@ export const beforeUpdate = (scopeAttributes: string[]) => async (
31
33
  instance.id,
32
34
  identifiers,
33
35
  customFields,
34
- { transaction: options.transaction },
36
+ { transaction: options.transaction, modelOptions },
37
+
35
38
  );
36
39
  // eslint-disable-next-line no-param-reassign
37
40
  fields.splice(customFieldsIdx, 1);
package/src/index.ts CHANGED
@@ -7,13 +7,14 @@ import api from './api';
7
7
  import initDB from './utils/db';
8
8
  import logger from './utils/logger';
9
9
  import type { CustomFieldOptions, ModelFetcher } from './types';
10
- import { addHooks, addScopes, removeHooks } from './utils/init';
10
+ import {
11
+ addHooks, addScopes, applyCustomAssociation, removeHooks,
12
+ } from './utils/init';
11
13
 
12
14
  export * from './utils/validations/custom-fields';
13
15
 
14
16
  export * from './utils/constants';
15
17
 
16
- export * from './utils/helpers';
17
18
  /**
18
19
  * Adding custom fields enrichment to the models inside the MODELS_FILE_NAME json file
19
20
  * @see {@link 'custom-fields/config'} for configurations
@@ -35,6 +36,7 @@ const useCustomFields = async (
35
36
  addHooks(models, getModel);
36
37
  await initTables(sequelize, options.getUser);
37
38
  addScopes(models, getModel);
39
+ applyCustomAssociation(models);
38
40
  logger.debug('sadot - custom fields finished initializing with models', models);
39
41
  return sequelize;
40
42
  };
@@ -5,10 +5,12 @@ import logger from '../utils/logger';
5
5
  import CustomFieldDefinition from './CustomFieldDefinition';
6
6
  import CustomFieldValue from './CustomFieldValue';
7
7
  import TestModel from './tests/TestModel';
8
+ import ContextAwareTestModel from './tests/contextAwareModels/ContextAwareTestModel';
9
+ import ContextTestModel from './tests/contextAwareModels/ContextTestModel';
8
10
  import AssociatedTestModel from './tests/AssociatedTestModel';
9
11
 
10
12
  const productionModels = [CustomFieldDefinition, CustomFieldValue];
11
- const testModels = [TestModel, AssociatedTestModel];
13
+ const testModels = [TestModel, AssociatedTestModel, ContextAwareTestModel, ContextTestModel];
12
14
 
13
15
  const SADOT_MIGRATION_PREFIX = 'sadot-migration';
14
16
  const SCHEMA_VERSION = 1;
@@ -88,6 +90,8 @@ const initTestModels = async (sequelize: Sequelize): Promise<void> => {
88
90
  logger.info('custom-fields: test models added');
89
91
  await TestModel.sync({ alter: true });
90
92
  await AssociatedTestModel.sync({ alter: true });
93
+ await ContextTestModel.sync({ alter: true });
94
+ await ContextAwareTestModel.sync({ alter: true });
91
95
  logger.info('custom-fields: test models synced');
92
96
  };
93
97
 
@@ -96,6 +100,8 @@ export {
96
100
  CustomFieldDefinition,
97
101
  TestModel,
98
102
  AssociatedTestModel,
103
+ ContextAwareTestModel,
104
+ ContextTestModel,
99
105
  initTables,
100
106
  initTestModels,
101
107
  };
@@ -0,0 +1,45 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import {
3
+ Table,
4
+ Column,
5
+ Model,
6
+ PrimaryKey,
7
+ DataType,
8
+ HasMany,
9
+ BelongsTo,
10
+ ForeignKey,
11
+ } from 'sequelize-typescript';
12
+ import AssociatedTestModel from '../AssociatedTestModel';
13
+ import ContextTestModel from './ContextTestModel';
14
+
15
+ @Table({ createdAt: false, updatedAt: false })
16
+ class ContextAwareTestModel extends Model {
17
+ @PrimaryKey
18
+ @Column({
19
+ type: DataType.UUID,
20
+ defaultValue: DataType.UUIDV4,
21
+ allowNull: false,
22
+ })
23
+ id!: string;
24
+
25
+ @ForeignKey(() => ContextTestModel)
26
+ @Column({
27
+ type: DataType.UUID,
28
+ })
29
+ contextId: string;
30
+
31
+ @Column({
32
+ type: DataType.BOOLEAN,
33
+ })
34
+ coolAttribute?: boolean;
35
+
36
+ @Column({
37
+ type: DataType.VIRTUAL,
38
+ })
39
+ customFields?: any;
40
+
41
+ @BelongsTo(() => ContextTestModel)
42
+ context: ContextTestModel;
43
+ }
44
+
45
+ export default ContextAwareTestModel;
@@ -0,0 +1,38 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import {
3
+ Table,
4
+ Column,
5
+ Model,
6
+ PrimaryKey,
7
+ DataType,
8
+ } from 'sequelize-typescript';
9
+
10
+ // eslint-disable-next-line no-shadow
11
+ enum EEntityTypes {
12
+ BUSINESS_MODEL = 'businessModel',
13
+ CONTEXT = 'context',
14
+ DEMAND_SOURCE = 'demandSource',
15
+ FLEET = 'fleet',
16
+ }
17
+
18
+ @Table({ createdAt: false, updatedAt: false })
19
+ class ContextTestModel extends Model {
20
+ @PrimaryKey
21
+ @Column({
22
+ defaultValue: DataType.UUIDV4,
23
+ type: DataType.UUID,
24
+ })
25
+ id: string;
26
+
27
+ @Column({
28
+ type: DataType.UUID,
29
+ })
30
+ entityId: string;
31
+
32
+ @Column({
33
+ type: DataType.ENUM(...Object.values(EEntityTypes)),
34
+ })
35
+ entityType: EEntityTypes;
36
+ }
37
+
38
+ export default ContextTestModel;
@@ -1,21 +1,27 @@
1
- import { Op, WhereOptions } from 'sequelize';
1
+ import {
2
+ FindOptions,
3
+ Op, WhereOptions,
4
+ } from 'sequelize';
2
5
  import { CustomFieldDefinition } from '../models';
3
6
  import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../types/definition';
7
+ import { ModelOptions } from '../types';
4
8
 
5
9
  export const create = (data: CreateCustomFieldDefinition): Promise<CustomFieldDefinition> =>
6
10
  CustomFieldDefinition.create(data);
7
11
 
8
12
  export const findAll = (
9
- where: any,
13
+ where: WhereOptions,
10
14
  options: any = { withDisabled: false },
11
15
  ): Promise<CustomFieldDefinition[]> => {
12
16
  const queryModel = options.withDisabled
13
17
  ? CustomFieldDefinition.unscoped()
14
18
  : CustomFieldDefinition;
19
+
15
20
  return queryModel.scope('userScope').findAll({
16
21
  where,
17
22
  transaction: options.transaction,
18
23
  raw: true,
24
+ include: options.include,
19
25
  });
20
26
  };
21
27
 
@@ -35,26 +41,26 @@ export const findById = (
35
41
  return CustomFieldDefinition.scope('userScope').findByPk(id);
36
42
  };
37
43
 
38
- export const findByEntityId = async (
39
- entityId: string,
40
- options: any = {},
41
- ): Promise<CustomFieldDefinition[]> => CustomFieldDefinition.findAll({
42
- where: { entityId },
43
- transaction: options.transaction,
44
- });
45
-
46
44
  export const findByEntityIds = async (
47
45
  modelType: string,
48
46
  entityIds: string[],
49
- options: any = {},
50
- ): Promise<CustomFieldDefinition[]> => CustomFieldDefinition.findAll({
51
- where: {
47
+ options: FindOptions & { modelOptions?: ModelOptions } = {},
48
+ ): Promise<CustomFieldDefinition[]> => {
49
+ const { include, useEntityIdFromInclude } = options.modelOptions;
50
+ const where: WhereOptions = {
52
51
  modelType,
53
- entityId: entityIds,
54
- },
55
- transaction: options.transaction,
56
- raw: true,
57
- });
52
+ };
53
+
54
+ if (!useEntityIdFromInclude) {
55
+ where.entityId = entityIds;
56
+ }
57
+ return CustomFieldDefinition.findAll({
58
+ where,
59
+ transaction: options.transaction,
60
+ include: include?.(entityIds),
61
+ raw: true,
62
+ });
63
+ };
58
64
 
59
65
  export const findByWhere = (where): Promise<CustomFieldDefinition | null> =>
60
66
  CustomFieldDefinition.scope('userScope').findOne({
@@ -100,10 +106,23 @@ export const getRequiredFields = async (
100
106
  modelType: string,
101
107
  modelId: string | string[],
102
108
  entityId: string | string[],
109
+ modelOptions: ModelOptions = {},
103
110
  ): Promise<string[]> => {
104
111
  const entityIds = Array.isArray(entityId) ? entityId : [entityId];
112
+ const { include, useEntityIdFromInclude } = modelOptions;
113
+
114
+ const where: WhereOptions = {
115
+ modelType,
116
+ required: true,
117
+ };
118
+
119
+ if (!useEntityIdFromInclude) {
120
+ where.entityId = entityIds;
121
+ }
122
+
105
123
  const requiredFields = await CustomFieldDefinition.findAll({
106
- where: { required: true, modelType, entityId: { [Op.in]: entityIds } },
124
+ where,
125
+ include: include?.(entityIds),
107
126
  logging: true,
108
127
  });
109
128
  const requiredFieldsNames = requiredFields.map((definition) => definition.name);
@@ -1,9 +1,11 @@
1
1
  /* eslint-disable max-len */
2
+ import type { FindOptions, WhereOptions } from 'sequelize';
2
3
  import { CustomFieldValue, CustomFieldDefinition } from '../models';
3
4
  import * as DefinitionRepo from './definition';
4
5
  import { CreateCustomFieldValue, ValuesToUpdate } from '../types/value';
5
6
  import logger from '../utils/logger';
6
7
  import { MissingDefinitionError } from '../errors';
8
+ import { ModelOptions } from '../types';
7
9
 
8
10
  export const findByModelIdAndDefinition = async (modelId: string, customFieldDefinitionId: string) =>
9
11
  CustomFieldValue.findAll({ where: { modelId, customFieldDefinitionId }, include: [CustomFieldDefinition] });
@@ -50,18 +52,27 @@ export const updateValues = async (
50
52
  modelId: string,
51
53
  identifiers: string[],
52
54
  valuesToUpdate: ValuesToUpdate,
53
- options: any = {},
55
+ options: FindOptions & { modelOptions?: ModelOptions } = {},
54
56
  ): Promise<CustomFieldValue[]> => {
55
- logger.info(`custom-fields: updating values for ${modelType} ${modelId}`, { valuesToUpdate });
56
-
57
57
  const names = Object.keys(valuesToUpdate);
58
- const fieldDefinitions = await DefinitionRepo.findAll(
59
- { modelType, name: names, entityId: identifiers },
60
- { withDisabled: true, transaction: options.transaction },
61
- ) || [];
58
+ logger.info(`custom-fields: updating values for ${modelType} ${modelId}`, {
59
+ names, options, valuesToUpdate, identifiers,
60
+ });
61
+ const { modelOptions, transaction } = options;
62
+
63
+ const where: WhereOptions = {
64
+ modelType,
65
+ name: names,
66
+ };
67
+
68
+ if (!options.modelOptions?.useEntityIdFromInclude) {
69
+ where.entityId = identifiers;
70
+ }
71
+ const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) || [];
62
72
 
63
73
  const disabledDefinitions = fieldDefinitions.filter((def) => def.disabled);
64
74
  if (fieldDefinitions.length !== names.length) {
75
+ logger.info(`custom-fields: missing definitions for ${modelType} ${modelId}`, { names, fieldDefinitions });
65
76
  const missingDefinitions = names.filter((name) => !fieldDefinitions.some((def) => def.name === name));
66
77
  throw new MissingDefinitionError(missingDefinitions);
67
78
  }
@@ -4,6 +4,7 @@ export default {
4
4
  password: process.env.DB_PASSWORD || null,
5
5
  database: process.env.DB_NAME || 'sadot_package_test',
6
6
  host: process.env.DB_HOST || '127.0.0.1',
7
+ port: process.env.DB_PORT || 5432,
7
8
  dialect: process.env.DB_TYPE || 'postgres',
8
9
  define: {
9
10
  underscored: true,
@@ -1,10 +1,14 @@
1
- import { CustomFieldDefinition, TestModel } from '../../models';
1
+ import {
2
+ ContextAwareTestModel, ContextTestModel, CustomFieldDefinition, TestModel,
3
+ } from '../../models';
2
4
 
3
5
  // eslint-disable-next-line import/prefer-default-export
4
6
  export const cleanup = async (): Promise<void> => {
5
7
  if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') {
6
8
  await CustomFieldDefinition.unscoped().destroy({ where: {} });
7
9
  await TestModel.destroy({ where: {} });
10
+ await ContextAwareTestModel.destroy({ where: {} });
11
+ await ContextTestModel.destroy({ where: {} });
8
12
  }
9
13
  };
10
14
 
@@ -1,6 +1,13 @@
1
1
  import { v4 as uuidv4 } from 'uuid';
2
2
  import { CreateCustomFieldDefinition, CustomFieldDefinitionDTO } from '../../types/definition';
3
3
 
4
+ export const contextAwareFieldDefinition = {
5
+ name: 'cool field',
6
+ modelType: 'ContextAwareTestModel',
7
+ fieldType: 'number',
8
+ entityType: 'fleetId',
9
+ };
10
+
4
11
  export const coolFieldDefinition: CreateCustomFieldDefinition = {
5
12
  name: 'cool field',
6
13
  modelType: 'TestModel',
@@ -1,15 +1,35 @@
1
+ import { IncludeOptions } from 'sequelize';
2
+ import { ModelCtor } from 'sequelize-typescript';
3
+
1
4
  export type ModelFetcher = (name: string) => any;
2
5
 
3
6
  export type ModelOptions = {
7
+ /**
8
+ * Include options for the model
9
+ */
10
+ include?: (entityIds: string[]) => IncludeOptions[];
11
+ /**
12
+ * Custom association for the model
13
+ * @param model - The model to associate with
14
+ */
15
+
16
+ customAssociation?: (model: ModelCtor) => void;
17
+ /**
18
+ * Whether to use the entity id from the instance per scope attribute
19
+ */
20
+ useEntityIdFromInclude?: boolean
21
+ }
22
+ export type Models = {
4
23
  name: string;
5
- scopeAttributes: string[];
24
+ scopeAttributes: any[];
25
+ modelOptions?: ModelOptions;
6
26
  creationWebhookHandler?: (instance: any) => any;
7
27
  updateWebhookHandler?: (instance: any) => any;
8
28
  deletionWebhookHandler?: (instance: any) => any;
9
29
  }
10
30
 
11
- export type CustomFieldOptions= {
12
- models: ModelOptions[];
31
+ export type CustomFieldOptions = {
32
+ models: Models[];
13
33
  databaseConfig: any;
14
34
  getUser: () => any;
15
35
  };