@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.
- package/dist/jest.config.d.ts +12 -0
- package/dist/src/api/index.d.ts +2 -0
- package/dist/src/api/index.js +11 -0
- package/dist/src/api/v1/definition/index.d.ts +2 -0
- package/dist/src/api/v1/definition/index.js +118 -0
- package/dist/src/api/v1/definition/validations.d.ts +2 -0
- package/dist/src/api/v1/definition/validations.js +36 -0
- package/dist/src/api/v1/errors.d.ts +2 -0
- package/dist/src/api/v1/errors.js +15 -0
- package/dist/src/api/v1/index.d.ts +2 -0
- package/dist/src/api/v1/index.js +10 -0
- package/dist/src/errors/index.d.ts +16 -0
- package/dist/src/errors/index.js +45 -0
- package/dist/src/events/index.d.ts +4 -0
- package/dist/src/events/index.js +47 -0
- package/dist/src/hooks/create.d.ts +9 -0
- package/dist/src/hooks/create.js +70 -0
- package/dist/src/hooks/enrich.d.ts +5 -0
- package/dist/src/hooks/enrich.js +118 -0
- package/dist/src/hooks/find.d.ts +1 -0
- package/dist/src/hooks/find.js +29 -0
- package/dist/src/hooks/index.d.ts +6 -0
- package/dist/src/hooks/index.js +18 -0
- package/dist/src/hooks/update.d.ts +9 -0
- package/dist/src/hooks/update.js +58 -0
- package/dist/src/hooks/workaround.d.ts +10 -0
- package/dist/src/hooks/workaround.js +37 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +105 -0
- package/dist/src/models/CustomFieldDefinition.d.ts +23 -0
- package/dist/src/models/CustomFieldDefinition.js +165 -0
- package/dist/src/models/CustomFieldValue.d.ts +15 -0
- package/dist/src/models/CustomFieldValue.js +148 -0
- package/dist/src/models/index.d.ts +8 -0
- package/dist/src/models/index.js +87 -0
- package/dist/src/models/tests/AssociatedTestModel.d.ts +12 -0
- package/dist/src/models/tests/AssociatedTestModel.js +71 -0
- package/dist/src/models/tests/TestModel.d.ts +11 -0
- package/dist/src/models/tests/TestModel.js +63 -0
- package/dist/src/repository/definition.d.ts +17 -0
- package/dist/src/repository/definition.js +80 -0
- package/dist/src/repository/value.d.ts +24 -0
- package/dist/src/repository/value.js +107 -0
- package/dist/src/tests/api/test-api.d.ts +2 -0
- package/dist/src/tests/api/test-api.js +56 -0
- package/dist/src/tests/helpers/database-config.d.ts +15 -0
- package/dist/src/tests/helpers/database-config.js +16 -0
- package/dist/src/tests/helpers/index.d.ts +2 -0
- package/dist/src/tests/helpers/index.js +18 -0
- package/dist/src/tests/mocks/definition.mock.d.ts +37 -0
- package/dist/src/tests/mocks/definition.mock.js +64 -0
- package/dist/src/tests/mocks/events.mock.d.ts +3 -0
- package/dist/src/tests/mocks/events.mock.js +19 -0
- package/dist/src/tests/mocks/testModel.d.ts +12 -0
- package/dist/src/tests/mocks/testModel.js +35 -0
- package/dist/src/types/definition/index.d.ts +23 -0
- package/dist/src/types/definition/index.js +2 -0
- package/dist/src/types/index.d.ts +13 -0
- package/dist/src/types/index.js +2 -0
- package/dist/src/types/value/index.d.ts +15 -0
- package/dist/src/types/value/index.js +2 -0
- package/dist/src/utils/constants/index.d.ts +1 -0
- package/dist/src/utils/constants/index.js +5 -0
- package/dist/src/utils/db/index.d.ts +4 -0
- package/dist/src/utils/db/index.js +24 -0
- package/dist/src/utils/logger/index.d.ts +2 -0
- package/dist/src/utils/logger/index.js +6 -0
- package/dist/src/utils/validations/custom-fields.d.ts +2 -0
- package/dist/src/utils/validations/custom-fields.js +10 -0
- package/dist/src/utils/validations/custom.d.ts +15 -0
- package/dist/src/utils/validations/custom.js +42 -0
- package/dist/src/utils/validations/index.d.ts +2 -0
- package/dist/src/utils/validations/index.js +20 -0
- package/dist/src/utils/validations/type.d.ts +18 -0
- package/dist/src/utils/validations/type.js +50 -0
- package/dist/src/utils/validations/validators.d.ts +12 -0
- package/dist/src/utils/validations/validators.js +33 -0
- package/package.json +44 -0
- package/src/api/index.ts +9 -0
- package/src/api/v1/definition/index.ts +110 -0
- package/src/api/v1/definition/validations.ts +35 -0
- package/src/api/v1/errors.ts +16 -0
- package/src/api/v1/index.ts +9 -0
- package/src/errors/index.ts +42 -0
- package/src/events/index.ts +50 -0
- package/src/hooks/create.ts +51 -0
- package/src/hooks/enrich.ts +125 -0
- package/src/hooks/find.ts +27 -0
- package/src/hooks/index.ts +15 -0
- package/src/hooks/update.ts +39 -0
- package/src/hooks/workaround.ts +47 -0
- package/src/index.ts +101 -0
- package/src/models/CustomFieldDefinition.ts +140 -0
- package/src/models/CustomFieldValue.ts +114 -0
- package/src/models/index.ts +101 -0
- package/src/models/tests/AssociatedTestModel.ts +57 -0
- package/src/models/tests/TestModel.ts +49 -0
- package/src/repository/definition.ts +111 -0
- package/src/repository/value.ts +99 -0
- package/src/tests/api/test-api.ts +38 -0
- package/src/tests/helpers/database-config.ts +14 -0
- package/src/tests/helpers/index.ts +15 -0
- package/src/tests/mocks/definition.mock.ts +69 -0
- package/src/tests/mocks/events.mock.ts +18 -0
- package/src/tests/mocks/testModel.ts +37 -0
- package/src/types/definition/index.ts +22 -0
- package/src/types/index.ts +15 -0
- package/src/types/value/index.ts +14 -0
- package/src/utils/constants/index.ts +2 -0
- package/src/utils/db/index.ts +21 -0
- package/src/utils/logger/index.ts +6 -0
- package/src/utils/validations/custom-fields.ts +9 -0
- package/src/utils/validations/custom.ts +39 -0
- package/src/utils/validations/index.ts +19 -0
- package/src/utils/validations/type.ts +45 -0
- package/src/utils/validations/validators.ts +34 -0
- 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;
|