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