@autofleet/sadot 0.0.1-beta

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 (117) hide show
  1. package/dist/jest.config.d.ts +12 -0
  2. package/dist/src/api/index.d.ts +2 -0
  3. package/dist/src/api/index.js +11 -0
  4. package/dist/src/api/v1/definition/index.d.ts +2 -0
  5. package/dist/src/api/v1/definition/index.js +118 -0
  6. package/dist/src/api/v1/definition/validations.d.ts +2 -0
  7. package/dist/src/api/v1/definition/validations.js +36 -0
  8. package/dist/src/api/v1/errors.d.ts +2 -0
  9. package/dist/src/api/v1/errors.js +15 -0
  10. package/dist/src/api/v1/index.d.ts +2 -0
  11. package/dist/src/api/v1/index.js +10 -0
  12. package/dist/src/errors/index.d.ts +16 -0
  13. package/dist/src/errors/index.js +45 -0
  14. package/dist/src/events/index.d.ts +4 -0
  15. package/dist/src/events/index.js +47 -0
  16. package/dist/src/hooks/create.d.ts +9 -0
  17. package/dist/src/hooks/create.js +70 -0
  18. package/dist/src/hooks/enrich.d.ts +5 -0
  19. package/dist/src/hooks/enrich.js +118 -0
  20. package/dist/src/hooks/find.d.ts +1 -0
  21. package/dist/src/hooks/find.js +29 -0
  22. package/dist/src/hooks/index.d.ts +6 -0
  23. package/dist/src/hooks/index.js +18 -0
  24. package/dist/src/hooks/update.d.ts +9 -0
  25. package/dist/src/hooks/update.js +58 -0
  26. package/dist/src/hooks/workaround.d.ts +10 -0
  27. package/dist/src/hooks/workaround.js +37 -0
  28. package/dist/src/index.d.ts +11 -0
  29. package/dist/src/index.js +105 -0
  30. package/dist/src/models/CustomFieldDefinition.d.ts +23 -0
  31. package/dist/src/models/CustomFieldDefinition.js +165 -0
  32. package/dist/src/models/CustomFieldValue.d.ts +15 -0
  33. package/dist/src/models/CustomFieldValue.js +148 -0
  34. package/dist/src/models/index.d.ts +8 -0
  35. package/dist/src/models/index.js +87 -0
  36. package/dist/src/models/tests/AssociatedTestModel.d.ts +12 -0
  37. package/dist/src/models/tests/AssociatedTestModel.js +71 -0
  38. package/dist/src/models/tests/TestModel.d.ts +11 -0
  39. package/dist/src/models/tests/TestModel.js +63 -0
  40. package/dist/src/repository/definition.d.ts +17 -0
  41. package/dist/src/repository/definition.js +80 -0
  42. package/dist/src/repository/value.d.ts +24 -0
  43. package/dist/src/repository/value.js +107 -0
  44. package/dist/src/tests/api/test-api.d.ts +2 -0
  45. package/dist/src/tests/api/test-api.js +56 -0
  46. package/dist/src/tests/helpers/database-config.d.ts +15 -0
  47. package/dist/src/tests/helpers/database-config.js +16 -0
  48. package/dist/src/tests/helpers/index.d.ts +2 -0
  49. package/dist/src/tests/helpers/index.js +18 -0
  50. package/dist/src/tests/mocks/definition.mock.d.ts +37 -0
  51. package/dist/src/tests/mocks/definition.mock.js +64 -0
  52. package/dist/src/tests/mocks/events.mock.d.ts +3 -0
  53. package/dist/src/tests/mocks/events.mock.js +19 -0
  54. package/dist/src/tests/mocks/testModel.d.ts +12 -0
  55. package/dist/src/tests/mocks/testModel.js +35 -0
  56. package/dist/src/types/definition/index.d.ts +23 -0
  57. package/dist/src/types/definition/index.js +2 -0
  58. package/dist/src/types/index.d.ts +13 -0
  59. package/dist/src/types/index.js +2 -0
  60. package/dist/src/types/value/index.d.ts +15 -0
  61. package/dist/src/types/value/index.js +2 -0
  62. package/dist/src/utils/constants/index.d.ts +1 -0
  63. package/dist/src/utils/constants/index.js +5 -0
  64. package/dist/src/utils/db/index.d.ts +4 -0
  65. package/dist/src/utils/db/index.js +24 -0
  66. package/dist/src/utils/logger/index.d.ts +2 -0
  67. package/dist/src/utils/logger/index.js +6 -0
  68. package/dist/src/utils/validations/custom-fields.d.ts +2 -0
  69. package/dist/src/utils/validations/custom-fields.js +10 -0
  70. package/dist/src/utils/validations/custom.d.ts +15 -0
  71. package/dist/src/utils/validations/custom.js +42 -0
  72. package/dist/src/utils/validations/index.d.ts +2 -0
  73. package/dist/src/utils/validations/index.js +20 -0
  74. package/dist/src/utils/validations/type.d.ts +18 -0
  75. package/dist/src/utils/validations/type.js +50 -0
  76. package/dist/src/utils/validations/validators.d.ts +12 -0
  77. package/dist/src/utils/validations/validators.js +33 -0
  78. package/package.json +44 -0
  79. package/src/api/index.ts +9 -0
  80. package/src/api/v1/definition/index.ts +110 -0
  81. package/src/api/v1/definition/validations.ts +35 -0
  82. package/src/api/v1/errors.ts +16 -0
  83. package/src/api/v1/index.ts +9 -0
  84. package/src/errors/index.ts +42 -0
  85. package/src/events/index.ts +50 -0
  86. package/src/hooks/create.ts +51 -0
  87. package/src/hooks/enrich.ts +125 -0
  88. package/src/hooks/find.ts +27 -0
  89. package/src/hooks/index.ts +15 -0
  90. package/src/hooks/update.ts +39 -0
  91. package/src/hooks/workaround.ts +47 -0
  92. package/src/index.ts +101 -0
  93. package/src/models/CustomFieldDefinition.ts +140 -0
  94. package/src/models/CustomFieldValue.ts +114 -0
  95. package/src/models/index.ts +101 -0
  96. package/src/models/tests/AssociatedTestModel.ts +57 -0
  97. package/src/models/tests/TestModel.ts +49 -0
  98. package/src/repository/definition.ts +111 -0
  99. package/src/repository/value.ts +99 -0
  100. package/src/tests/api/test-api.ts +38 -0
  101. package/src/tests/helpers/database-config.ts +14 -0
  102. package/src/tests/helpers/index.ts +15 -0
  103. package/src/tests/mocks/definition.mock.ts +69 -0
  104. package/src/tests/mocks/events.mock.ts +18 -0
  105. package/src/tests/mocks/testModel.ts +37 -0
  106. package/src/types/definition/index.ts +22 -0
  107. package/src/types/index.ts +15 -0
  108. package/src/types/value/index.ts +14 -0
  109. package/src/utils/constants/index.ts +2 -0
  110. package/src/utils/db/index.ts +21 -0
  111. package/src/utils/logger/index.ts +6 -0
  112. package/src/utils/validations/custom-fields.ts +9 -0
  113. package/src/utils/validations/custom.ts +39 -0
  114. package/src/utils/validations/index.ts +19 -0
  115. package/src/utils/validations/type.ts +45 -0
  116. package/src/utils/validations/validators.ts +34 -0
  117. package/tsconfig.json +13 -0
@@ -0,0 +1,140 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /* eslint-disable indent */
3
+ import {
4
+ Table,
5
+ Column,
6
+ Model,
7
+ DataType,
8
+ HasMany,
9
+ PrimaryKey,
10
+ BeforeCreate,
11
+ DefaultScope,
12
+ AfterSave,
13
+ Is,
14
+ } from 'sequelize-typescript';
15
+ import { validateValidation } from '../utils/validations/custom';
16
+ import { CustomFieldDefinitionType } from '../utils/validations/type';
17
+ import { CustomFieldValue } from '.';
18
+ import { sendDimEvent } from '../events';
19
+ import { UnsupportedCustomFieldTypeError, UnsupportedCustomValidationError } from '../errors';
20
+
21
+ @DefaultScope(() => ({ where: { disabled: false } }))
22
+ @Table({
23
+ indexes: [
24
+ {
25
+ name: 'unique_name_model_type',
26
+ fields: ['model_type', 'entity_id', 'name'],
27
+ unique: true,
28
+ },
29
+ ],
30
+ timestamps: true,
31
+ validate: {
32
+ validationByType(this: CustomFieldDefinition) {
33
+ if (!validateValidation(this.fieldType, this.validation)) {
34
+ throw new UnsupportedCustomValidationError(`Validation provided for "${this.fieldType}" is not supported`);
35
+ }
36
+ },
37
+ },
38
+ })
39
+ class CustomFieldDefinition extends Model {
40
+ @PrimaryKey
41
+ @Column({
42
+ type: DataType.UUID,
43
+ defaultValue: DataType.UUIDV4,
44
+ allowNull: false,
45
+ })
46
+ id!: string;
47
+
48
+ @Column({
49
+ type: DataType.STRING,
50
+ allowNull: false,
51
+ })
52
+ name!: string;
53
+
54
+ @Column({
55
+ type: DataType.STRING,
56
+ })
57
+ displayName?: string; // Defaulted to name with beforeCreate hook
58
+
59
+ @Is('SupportedType', (value) => {
60
+ if (!Object.values(CustomFieldDefinitionType).includes(value as CustomFieldDefinitionType)) {
61
+ throw new UnsupportedCustomFieldTypeError(`"${value}" is not a supported type.`);
62
+ }
63
+ })
64
+ @Column({
65
+ type: DataType.STRING,
66
+ allowNull: false,
67
+ })
68
+ fieldType!: CustomFieldDefinitionType;
69
+
70
+ @Column({
71
+ type: DataType.JSONB,
72
+ })
73
+ validation?: any;
74
+
75
+ @Column({
76
+ type: DataType.UUID,
77
+ allowNull: false,
78
+ })
79
+ entityId!: string; /** Client association entity id */
80
+
81
+ @Column({
82
+ type: DataType.STRING,
83
+ allowNull: false,
84
+ })
85
+ entityType!: string; /** Client association entity type (demand source / fleet / etc.) */
86
+
87
+ @Column({
88
+ type: DataType.STRING,
89
+ allowNull: false,
90
+ })
91
+ modelType!: string; /** Model type. e.g. Vehicle / StopPoint / etc. */
92
+
93
+ @Column({
94
+ type: DataType.TEXT,
95
+ })
96
+ description?: string;
97
+
98
+ @Column({
99
+ type: DataType.BOOLEAN,
100
+ defaultValue: false,
101
+ })
102
+ required?: boolean;
103
+
104
+ @Column({
105
+ type: DataType.BOOLEAN,
106
+ defaultValue: false,
107
+ })
108
+ disabled?: boolean;
109
+
110
+ @Column
111
+ createdAt?: Date;
112
+
113
+ @Column
114
+ updatedAt?: Date;
115
+
116
+ @Column
117
+ deletedAt?: Date;
118
+
119
+ @HasMany(() => CustomFieldValue)
120
+ values: CustomFieldValue[];
121
+
122
+ @BeforeCreate
123
+ static displayNameDefaultValue(instance: CustomFieldDefinition): void {
124
+ if (!instance?.displayName) {
125
+ // eslint-disable-next-line no-param-reassign
126
+ instance.displayName = instance.name;
127
+ }
128
+ }
129
+
130
+ @AfterSave
131
+ static afterSaveHandler(instance: CustomFieldDefinition, options): void {
132
+ if (options.transaction) {
133
+ options.transaction.afterCommit(() => sendDimEvent(instance));
134
+ } else {
135
+ sendDimEvent(instance);
136
+ }
137
+ }
138
+ }
139
+
140
+ export default CustomFieldDefinition;
@@ -0,0 +1,114 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /* eslint-disable indent */
3
+ import {
4
+ Table,
5
+ Column,
6
+ Model,
7
+ PrimaryKey,
8
+ DataType,
9
+ BelongsTo,
10
+ ForeignKey,
11
+ BeforeCreate,
12
+ BeforeUpsert,
13
+ AfterUpsert,
14
+ BeforeUpdate,
15
+ BeforeBulkCreate,
16
+ BeforeBulkUpdate,
17
+ Scopes,
18
+ } from 'sequelize-typescript';
19
+ import { sendDimEvent } from '../events';
20
+ import { CustomFieldDefinition } from '.';
21
+ import validateValue from '../utils/validations';
22
+ import * as CustomFieldDefinitionRepo from '../repository/definition';
23
+ import logger from '../utils/logger';
24
+ import { InvalidValueError } from '../errors';
25
+
26
+ @Table({
27
+ timestamps: true,
28
+ })
29
+ class CustomFieldValue extends Model {
30
+ @PrimaryKey
31
+ @Column({
32
+ type: DataType.UUID,
33
+ allowNull: false,
34
+ })
35
+ modelId!: string;
36
+
37
+ @PrimaryKey
38
+ @ForeignKey(() => CustomFieldDefinition)
39
+ @Column({
40
+ type: DataType.UUID,
41
+ allowNull: false,
42
+ })
43
+ customFieldDefinitionId!: string;
44
+
45
+ @Column({
46
+ type: DataType.JSONB,
47
+ allowNull: true,
48
+ })
49
+ value!: any;
50
+
51
+ @Column
52
+ createdAt?: Date;
53
+
54
+ @Column
55
+ updatedAt?: Date;
56
+
57
+ @Column
58
+ deletedAt?: Date;
59
+
60
+ @BelongsTo(() => CustomFieldDefinition, { scope: { disabled: false } })
61
+ customFieldDefinition: CustomFieldDefinition;
62
+
63
+ @BeforeBulkCreate
64
+ @BeforeBulkUpdate
65
+ static async validateValues(instances: CustomFieldValue[]): Promise<void> {
66
+ const ids = instances.map((instance) => instance.customFieldDefinitionId);
67
+ const uniqueIds = [...new Set(ids)];
68
+ const definitions = await CustomFieldDefinitionRepo.findByIds(
69
+ uniqueIds,
70
+ { withDisabled: true },
71
+ );
72
+
73
+ if (!definitions || definitions.length !== uniqueIds.length) {
74
+ throw new Error('Definitions not found');
75
+ }
76
+
77
+ instances.forEach((instance) => {
78
+ const {
79
+ validation,
80
+ fieldType,
81
+ } = definitions
82
+ .find((definition) => definition.id === instance.customFieldDefinitionId);
83
+ const isValid = validateValue(instance.value, fieldType, validation);
84
+ if (!isValid) {
85
+ throw new InvalidValueError(instance.value, fieldType);
86
+ }
87
+ });
88
+ }
89
+
90
+ @BeforeUpdate
91
+ @BeforeCreate
92
+ @BeforeUpsert
93
+ static async validateValue(instance: CustomFieldValue): Promise<void> {
94
+ const { customFieldDefinitionId } = instance;
95
+ // eslint-disable-next-line max-len
96
+ const cfd = await CustomFieldDefinitionRepo.findById(customFieldDefinitionId, { withDisabled: true });
97
+ const { validation, fieldType } = cfd;
98
+ const isValid = validateValue(instance.value, fieldType, validation);
99
+ if (!isValid) {
100
+ throw new InvalidValueError(instance.value, fieldType);
101
+ }
102
+ }
103
+
104
+ @AfterUpsert
105
+ static afterSaveHandler(instance: CustomFieldValue, options): void {
106
+ if (options.transaction) {
107
+ options.transaction.afterCommit(() => sendDimEvent(instance[0]));
108
+ } else {
109
+ sendDimEvent(instance[0]);
110
+ }
111
+ }
112
+ }
113
+
114
+ export default CustomFieldValue;
@@ -0,0 +1,101 @@
1
+ /* eslint-disable no-param-reassign */
2
+ import { DataTypes } from 'sequelize';
3
+ import type { Sequelize } from 'sequelize-typescript';
4
+ import logger from '../utils/logger';
5
+ import CustomFieldDefinition from './CustomFieldDefinition';
6
+ import CustomFieldValue from './CustomFieldValue';
7
+ import TestModel from './tests/TestModel';
8
+ import AssociatedTestModel from './tests/AssociatedTestModel';
9
+
10
+ const productionModels = [CustomFieldDefinition, CustomFieldValue];
11
+ const testModels = [TestModel, AssociatedTestModel];
12
+
13
+ const SADOT_MIGRATION_PREFIX = 'sadot-migration';
14
+ const SCHEMA_VERSION = 1;
15
+ const CUSTOM_FIELDS_SCHEMA_VERSION = `${SADOT_MIGRATION_PREFIX}_${SCHEMA_VERSION}`;
16
+
17
+ const initTables = async (sequelize: Sequelize, getUser): Promise<void> => {
18
+ logger.info('custom-fields: initialize custom-fields tables');
19
+ // Detect models and import them to the orm
20
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
21
+ if (!sequelize.addModels) {
22
+ throw new Error('sequelize instance must have addModels function');
23
+ }
24
+ sequelize.addModels(productionModels);
25
+
26
+ CustomFieldDefinition.addScope('userScope', () => {
27
+ const user = getUser();
28
+ if (user?.permissions) {
29
+ return {
30
+ where: {
31
+ entityId: [
32
+ ...Object.keys(user.permissions.fleets),
33
+ ...Object.keys(user.permissions.businessModels),
34
+ ...Object.keys(user.permissions.demandSources),
35
+ ],
36
+ },
37
+ };
38
+ }
39
+ return {};
40
+ });
41
+
42
+ logger.info('custom-fields: models added');
43
+
44
+ const SequelizeMeta = sequelize.define(
45
+ 'SequelizeMeta',
46
+ {
47
+ name: {
48
+ type: DataTypes.STRING,
49
+ allowNull: false,
50
+ unique: true,
51
+ primaryKey: true,
52
+ autoIncrement: false,
53
+ },
54
+ },
55
+ {
56
+ tableName: 'SequelizeMeta',
57
+ timestamps: false,
58
+ schema: 'public',
59
+ },
60
+ );
61
+ const migrations = await SequelizeMeta.findAll({ raw: true });
62
+ const currentSadotSchemaVersion = migrations
63
+ .reverse().find((m: any) => m.name.includes(SADOT_MIGRATION_PREFIX));
64
+
65
+ if (
66
+ !currentSadotSchemaVersion
67
+ || (currentSadotSchemaVersion as any).name !== CUSTOM_FIELDS_SCHEMA_VERSION
68
+ ) {
69
+ await CustomFieldDefinition.sync({ alter: true });
70
+ await CustomFieldValue.sync({ alter: true });
71
+ await SequelizeMeta.create({ name: CUSTOM_FIELDS_SCHEMA_VERSION });
72
+ logger.info('custom-fields: models synced');
73
+ }
74
+ };
75
+
76
+ const initTestModels = async (sequelize: Sequelize): Promise<void> => {
77
+ logger.info('custom-fields: initialize custom-fields test models');
78
+ // Detect models and import them to the orm
79
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
80
+ if (!sequelize.addModels) {
81
+ throw new Error('sequelize instance must have addModels function');
82
+ }
83
+
84
+ sequelize.addModels(testModels);
85
+ await sequelize.dropSchema('custom-fields', { logging: false });
86
+ await sequelize.createSchema('custom-fields', { logging: false });
87
+
88
+ logger.info('custom-fields: test models added');
89
+ await TestModel.sync({ alter: true });
90
+ await AssociatedTestModel.sync({ alter: true });
91
+ logger.info('custom-fields: test models synced');
92
+ };
93
+
94
+ export {
95
+ CustomFieldValue,
96
+ CustomFieldDefinition,
97
+ TestModel,
98
+ AssociatedTestModel,
99
+ initTables,
100
+ initTestModels,
101
+ };
@@ -0,0 +1,57 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import {
3
+ Table,
4
+ Column,
5
+ Model,
6
+ PrimaryKey,
7
+ DataType,
8
+ ForeignKey,
9
+ BelongsTo,
10
+ } from 'sequelize-typescript';
11
+ import TestModel from './TestModel';
12
+
13
+ @Table({ schema: 'custom-fields', createdAt: false, updatedAt: false })
14
+ class AssociatedTestModel extends Model {
15
+ @PrimaryKey
16
+ @Column({
17
+ type: DataType.UUID,
18
+ defaultValue: DataType.UUIDV4,
19
+ allowNull: false,
20
+ })
21
+ id!: string;
22
+
23
+ @ForeignKey(() => TestModel)
24
+ @Column({
25
+ type: DataType.UUID,
26
+ allowNull: false,
27
+ })
28
+ testModelId!: string;
29
+
30
+ @Column({
31
+ type: DataType.UUID,
32
+ allowNull: false,
33
+ })
34
+ fleetId: string;
35
+
36
+ @Column({
37
+ type: DataType.UUID,
38
+ allowNull: true,
39
+ })
40
+ businessModelId: string;
41
+
42
+ @Column({
43
+ type: DataType.UUID,
44
+ allowNull: true,
45
+ })
46
+ demandSourceId: string;
47
+
48
+ @Column({
49
+ type: DataType.BOOLEAN,
50
+ })
51
+ anotherAttribute?: boolean;
52
+
53
+ @BelongsTo(() => TestModel)
54
+ testModel: TestModel;
55
+ }
56
+
57
+ export default AssociatedTestModel;
@@ -0,0 +1,49 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import {
3
+ Table,
4
+ Column,
5
+ Model,
6
+ PrimaryKey,
7
+ DataType,
8
+ HasMany,
9
+ } from 'sequelize-typescript';
10
+ import AssociatedTestModel from './AssociatedTestModel';
11
+
12
+ @Table({ schema: 'custom-fields', createdAt: false, updatedAt: false })
13
+ class TestModel extends Model {
14
+ @PrimaryKey
15
+ @Column({
16
+ type: DataType.UUID,
17
+ defaultValue: DataType.UUIDV4,
18
+ allowNull: false,
19
+ })
20
+ id!: string;
21
+
22
+ @Column({
23
+ type: DataType.UUID,
24
+ allowNull: false,
25
+ })
26
+ fleetId: string;
27
+
28
+ @Column({
29
+ type: DataType.UUID,
30
+ allowNull: true,
31
+ })
32
+ businessModelId: string;
33
+
34
+ @Column({
35
+ type: DataType.UUID,
36
+ allowNull: true,
37
+ })
38
+ demandSourceId: string;
39
+
40
+ @Column({
41
+ type: DataType.BOOLEAN,
42
+ })
43
+ coolAttribute?: boolean;
44
+
45
+ @HasMany(() => AssociatedTestModel)
46
+ associatedModels: AssociatedTestModel[];
47
+ }
48
+
49
+ export default TestModel;
@@ -0,0 +1,111 @@
1
+ import { Op, WhereOptions } from 'sequelize';
2
+ import { CustomFieldDefinition } from '../models';
3
+ import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../types/definition';
4
+
5
+ export const create = (data: CreateCustomFieldDefinition): Promise<CustomFieldDefinition> =>
6
+ CustomFieldDefinition.create(data);
7
+
8
+ export const findAll = (
9
+ where: any,
10
+ options: any = { withDisabled: false },
11
+ ): Promise<CustomFieldDefinition[]> => {
12
+ const queryModel = options.withDisabled
13
+ ? CustomFieldDefinition.unscoped()
14
+ : CustomFieldDefinition;
15
+ return queryModel.scope('userScope').findAll({
16
+ where,
17
+ transaction: options.transaction,
18
+ raw: true,
19
+ });
20
+ };
21
+
22
+ export const findByIds = (
23
+ ids: string[],
24
+ options: any = { withDisabled: false },
25
+ ): Promise<CustomFieldDefinition[]> => findAll({ id: { [Op.in]: ids } }, options);
26
+
27
+ export const findById = (
28
+ id: string,
29
+ options: any = { withDisabled: false },
30
+ ): Promise<CustomFieldDefinition | null> => {
31
+ const { withDisabled } = options;
32
+ if (withDisabled) {
33
+ return CustomFieldDefinition.unscoped().scope('userScope').findByPk(id);
34
+ }
35
+ return CustomFieldDefinition.scope('userScope').findByPk(id);
36
+ };
37
+
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
+ export const findByEntityIds = async (
47
+ modelType: string,
48
+ entityIds: string[],
49
+ options: any = {},
50
+ ): Promise<CustomFieldDefinition[]> => CustomFieldDefinition.findAll({
51
+ where: {
52
+ modelType,
53
+ entityId: entityIds,
54
+ },
55
+ transaction: options.transaction,
56
+ raw: true,
57
+ });
58
+
59
+ export const findByWhere = (where): Promise<CustomFieldDefinition | null> =>
60
+ CustomFieldDefinition.scope('userScope').findOne({
61
+ where,
62
+ });
63
+
64
+ export const findDefinitionsByModels = async (
65
+ modelTypes: string[],
66
+ options?,
67
+ ): Promise<CustomFieldDefinition[]> => {
68
+ const query: WhereOptions<CreateCustomFieldDefinition> = { modelType: { [Op.in]: modelTypes } };
69
+ return CustomFieldDefinition.findAll({
70
+ where: query,
71
+ transaction: options?.transaction,
72
+ });
73
+ };
74
+
75
+ export const update = async (
76
+ id: string,
77
+ data: UpdateCustomFieldDefinition,
78
+ ): Promise<CustomFieldDefinition> => {
79
+ const updatedDefinition = (await CustomFieldDefinition.scope('userScope').update(data, {
80
+ where: { id },
81
+ returning: true,
82
+ individualHooks: true,
83
+ }))[1][0];
84
+ return updatedDefinition;
85
+ };
86
+
87
+ export const disable = (id: string): Promise<any> =>
88
+ CustomFieldDefinition.update(
89
+ { disabled: true },
90
+ { where: { id } },
91
+ );
92
+
93
+ export const destroy = (id: string): Promise<any> =>
94
+ CustomFieldDefinition.destroy({ where: { id } });
95
+
96
+ /**
97
+ * Return the names of the required fields for a given model
98
+ */
99
+ export const getRequiredFields = async (
100
+ modelType: string,
101
+ modelId: string | string[],
102
+ entityId: string | string[],
103
+ ): Promise<string[]> => {
104
+ const entityIds = Array.isArray(entityId) ? entityId : [entityId];
105
+ const requiredFields = await CustomFieldDefinition.findAll({
106
+ where: { required: true, modelType, entityId: { [Op.in]: entityIds } },
107
+ logging: true,
108
+ });
109
+ const requiredFieldsNames = requiredFields.map((definition) => definition.name);
110
+ return [...new Set(requiredFieldsNames)];
111
+ };
@@ -0,0 +1,99 @@
1
+ /* eslint-disable max-len */
2
+ import { CustomFieldValue, CustomFieldDefinition } from '../models';
3
+ import * as DefinitionRepo from './definition';
4
+ import { CreateCustomFieldValue, ValuesToUpdate } from '../types/value';
5
+ import logger from '../utils/logger';
6
+ import { MissingDefinitionError } from '../errors';
7
+
8
+ export const findByModelIdAndDefinition = async (modelId: string, customFieldDefinitionId: string) =>
9
+ CustomFieldValue.findAll({ where: { modelId, customFieldDefinitionId }, include: [CustomFieldDefinition] });
10
+
11
+ export const create = async (data: CreateCustomFieldValue, withAssociations = false) => {
12
+ const created = await CustomFieldValue.create(data);
13
+ if (withAssociations) {
14
+ const createdWithAssociations = await findByModelIdAndDefinition(created.modelId, created.customFieldDefinitionId);
15
+ return createdWithAssociations?.[0];
16
+ }
17
+ return created;
18
+ };
19
+
20
+ export const findAllValues = async () => CustomFieldValue.findAll({ include: [CustomFieldDefinition] });
21
+ /**
22
+ * Get all values for model instance id (with their definitions)
23
+ * @param modelId
24
+ * @returns CustomFieldValue[]
25
+ */
26
+ export const findValuesByModelId = async (modelId: string) => CustomFieldValue.findAll({ where: { modelId }, include: [CustomFieldDefinition] });
27
+
28
+ /**
29
+ * Retrieves custom field values for given model IDs
30
+ * @param modelIds - An array of model IDs to query custom field values for.
31
+ * @param options - Optional configuration object.
32
+ */
33
+ export const findValuesByModelIds = async (modelIds: string[], options?): Promise<CustomFieldValue[]> => {
34
+ const { transaction } = options;
35
+ return CustomFieldValue.findAll({
36
+ where: { modelId: modelIds },
37
+ transaction,
38
+ raw: true,
39
+ nest: true,
40
+ });
41
+ };
42
+
43
+ /**
44
+ * Try to update custom field values for a model instance.
45
+ * Create new value record if not exists, but fails if value's definition not exist.
46
+ * Return the updated values
47
+ */
48
+ export const updateValues = async (
49
+ modelType: string,
50
+ modelId: string,
51
+ identifiers: string[],
52
+ valuesToUpdate: ValuesToUpdate,
53
+ options: any = {},
54
+ ): Promise<CustomFieldValue[]> => {
55
+ logger.info(`custom-fields: updating values for ${modelType} ${modelId}`, { valuesToUpdate });
56
+
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
+ ) || [];
62
+
63
+ const disabledDefinitions = fieldDefinitions.filter((def) => def.disabled);
64
+ if (fieldDefinitions.length !== names.length) {
65
+ const missingDefinitions = names.filter((name) => !fieldDefinitions.some((def) => def.name === name));
66
+ throw new MissingDefinitionError(missingDefinitions);
67
+ }
68
+
69
+ const disabledNames = disabledDefinitions?.map((def) => def.name) || [];
70
+ const valuesWithDisabledDefinitions = names.filter((name) => disabledNames.includes(name));
71
+ if (valuesWithDisabledDefinitions?.length > 0) {
72
+ logger.warn(`custom-fields: trying to update disabled values: ${valuesWithDisabledDefinitions.join(', ')}`);
73
+ }
74
+
75
+ const values: CreateCustomFieldValue[] = names.map((name) => ({
76
+ modelId,
77
+ value: valuesToUpdate[name],
78
+ updatedAt: new Date(),
79
+ customFieldDefinitionId: fieldDefinitions.find((def) => def.name === name).id,
80
+ }));
81
+
82
+ return Promise.all(values.map(async (value) => {
83
+ const [cfv] = await CustomFieldValue.upsert(value, {
84
+ transaction: options.transaction,
85
+ });
86
+ return cfv;
87
+ }));
88
+ };
89
+
90
+ export const deleteValue = (
91
+ id: string,
92
+ options: any = {},
93
+ ): Promise<any> => CustomFieldValue.update(
94
+ { deletedAt: new Date() },
95
+ {
96
+ where: { id },
97
+ transaction: options.transaction,
98
+ },
99
+ );
@@ -0,0 +1,38 @@
1
+ import express, { Router } from 'express';
2
+ import { TestModel } from '../../models';
3
+
4
+ const app = express();
5
+ app.use(express.json());
6
+ const api = Router();
7
+
8
+ api.get('/v1/test-models', async (req, res) => {
9
+ const testModels = await TestModel.findAll();
10
+ return res.json(testModels);
11
+ });
12
+
13
+ api.get('/v1/test-models/:testModelId', async (req, res) => {
14
+ const { params: { testModelId } } = req;
15
+ const testModel = await TestModel.findByPk(testModelId);
16
+ return res.json(testModel);
17
+ });
18
+
19
+ api.post('/v1/test-models', async (req, res) => {
20
+ const { body } = req;
21
+ const testModel = await TestModel.create(body);
22
+ return res.json(testModel);
23
+ });
24
+
25
+ api.patch('/v1/test-models/:testModelId', async (req, res) => {
26
+ const { body, params: { testModelId } } = req;
27
+ const testModel = await TestModel.update(body, {
28
+ where: {
29
+ id: testModelId,
30
+ },
31
+ returning: true,
32
+ });
33
+ return res.json(testModel[1][0]);
34
+ });
35
+
36
+ app.use('/api', api);
37
+
38
+ export default app;