@autofleet/sadot 0.7.7-beta.1 → 0.7.7

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.
@@ -6,17 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.validateCustomFieldDefinitionUpdate = exports.validateCustomFieldDefinitionCreation = void 0;
7
7
  const joi_1 = __importDefault(require("joi"));
8
8
  const constants_1 = require("../../../utils/constants");
9
- const FileValidationSchema = joi_1.default.object({
10
- name: joi_1.default.string().required(),
11
- type: joi_1.default.string(),
12
- size: joi_1.default.string(),
13
- addedBy: joi_1.default.string().uuid(),
14
- });
15
- const statusValidationObject = joi_1.default.object({
9
+ const statusValidationObjectSchema = joi_1.default.array().items(joi_1.default.object({
16
10
  value: joi_1.default.string().required(),
17
11
  color: joi_1.default.string().required(),
18
- });
19
- const statusValidationObjectSchema = joi_1.default.array().items(statusValidationObject).min(1).unique('value');
12
+ })).min(1).unique('value');
20
13
  /**
21
14
  * Schema for the validation of custom field definition
22
15
  * The only custom validation is for
@@ -35,24 +28,10 @@ const ValidationSchema = joi_1.default.when('fieldType', {
35
28
  then: statusValidationObjectSchema,
36
29
  otherwise: joi_1.default.any(),
37
30
  });
38
- const DefaultValueSchema = joi_1.default.when('fieldType', {
39
- switch: [
40
- { is: constants_1.CustomFieldDefinitionType.BOOLEAN, then: joi_1.default.boolean().allow(null) },
41
- { is: constants_1.CustomFieldDefinitionType.DATE, then: joi_1.default.date().allow(null) },
42
- { is: constants_1.CustomFieldDefinitionType.DATETIME, then: joi_1.default.date().allow(null) },
43
- { is: constants_1.CustomFieldDefinitionType.FILE, then: FileValidationSchema },
44
- { is: constants_1.CustomFieldDefinitionType.IMAGE, then: joi_1.default.string().uri().allow(null) },
45
- { is: constants_1.CustomFieldDefinitionType.NUMBER, then: joi_1.default.number().allow(null) },
46
- { is: constants_1.CustomFieldDefinitionType.SELECT, then: joi_1.default.string().allow(null) },
47
- { is: constants_1.CustomFieldDefinitionType.STATUS, then: statusValidationObject.allow(null) },
48
- { is: constants_1.CustomFieldDefinitionType.TEXT, then: joi_1.default.string().allow(null) },
49
- ],
50
- });
51
31
  const CustomFieldDefinitionCreationSchema = joi_1.default.object({
52
32
  name: joi_1.default.string().required(),
53
33
  displayName: joi_1.default.string().required(),
54
34
  validation: ValidationSchema,
55
- defaultValue: DefaultValueSchema,
56
35
  fieldType: joi_1.default.string().valid(...Object.values(constants_1.CustomFieldDefinitionType)).required(),
57
36
  entityId: joi_1.default.string().guid().required(),
58
37
  entityType: joi_1.default.string().required(),
@@ -63,7 +42,6 @@ const CustomFieldDefinitionCreationSchema = joi_1.default.object({
63
42
  const CustomFieldDefinitionUpdateSchema = joi_1.default.object({
64
43
  displayName: joi_1.default.string(),
65
44
  validation: ValidationSchema,
66
- defaultValue: DefaultValueSchema,
67
45
  fieldType: joi_1.default.string().valid(...Object.values(constants_1.CustomFieldDefinitionType)),
68
46
  description: joi_1.default.string().allow(null),
69
47
  required: joi_1.default.boolean(),
@@ -7,18 +7,18 @@ exports.sendDimEvent = void 0;
7
7
  const events_1 = __importDefault(require("@autofleet/events"));
8
8
  const logger_1 = __importDefault(require("../utils/logger"));
9
9
  const events = new events_1.default({ logger: logger_1.default });
10
- const KEYS_TO_CONVERT = ['value', 'defaultValue'];
11
- const stringifyBooleans = (savedObject, keysToConvert) => {
12
- if (!Object.keys(savedObject).some((key) => keysToConvert.includes(key))) {
13
- return savedObject;
10
+ const KEYS_TO_CONVERT = ['value'];
11
+ const stringifyBools = (savedObject, keysToConvert) => {
12
+ if (Object.keys(savedObject).some((key) => keysToConvert.includes(key))) {
13
+ const objectToReturn = { ...savedObject };
14
+ keysToConvert.forEach((key) => {
15
+ if (typeof savedObject[key] === 'boolean') {
16
+ objectToReturn[key] = savedObject[key].toString();
17
+ }
18
+ });
19
+ return objectToReturn;
14
20
  }
15
- const objectToReturn = { ...savedObject };
16
- keysToConvert.forEach((key) => {
17
- if (typeof savedObject[key] === 'boolean') {
18
- objectToReturn[key] = savedObject[key].toString();
19
- }
20
- });
21
- return objectToReturn;
21
+ return savedObject;
22
22
  };
23
23
  const modelTableMapping = {
24
24
  CustomFieldDefinition: {
@@ -32,17 +32,16 @@ const modelTableMapping = {
32
32
  };
33
33
  const sendDimEvent = (instance) => {
34
34
  const mapping = modelTableMapping[instance.constructor.name];
35
- if (!mapping) {
36
- return;
37
- }
38
- let objectToSend = instance.get();
39
- try {
40
- objectToSend = stringifyBooleans(instance.get(), KEYS_TO_CONVERT);
41
- }
42
- catch (err) {
43
- logger_1.default.error('Failed to convert booleans in dim event payload', err);
35
+ if (mapping) {
36
+ let objectToSend = instance.get();
37
+ try {
38
+ objectToSend = stringifyBools(instance.get(), KEYS_TO_CONVERT);
39
+ }
40
+ catch (err) {
41
+ logger_1.default.error('Failed to convert booleans in dim event payload', err);
42
+ }
43
+ events.sendObject(mapping.tableName, mapping.eventVersion, objectToSend);
44
44
  }
45
- events.sendObject(mapping.tableName, mapping.eventVersion, objectToSend);
46
45
  };
47
46
  exports.sendDimEvent = sendDimEvent;
48
47
  exports.default = events;
@@ -48,43 +48,27 @@ exports.beforeBulkCreate = beforeBulkCreate;
48
48
  const beforeCreate = (scopeAttributes, modelOptions = {}) => async (instance, options) => {
49
49
  logger_1.default.debug('sadot - before create hook');
50
50
  const { fields } = options;
51
- const { include, useEntityIdFromInclude } = modelOptions;
52
51
  const modelType = instance.constructor.name;
53
52
  const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
54
- const where = {
55
- modelType,
56
- disabled: false,
57
- ...(!useEntityIdFromInclude && { entityId: identifiers }),
58
- };
59
- const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: false, transaction: options.transaction, include: include?.(identifiers) });
60
- const requiredFieldsNames = Array.from(new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)));
53
+ // get all model's required definitions
54
+ const requiredFieldsNames = await DefinitionRepo.getRequiredFields(modelType, instance.id, identifiers, modelOptions);
61
55
  const customFieldsIdx = fields.indexOf('customFields');
62
- if ((customFieldsIdx === -1 || !instance.customFields) && requiredFieldsNames?.length > 0) {
63
- throw new errors_1.MissingRequiredCustomFieldError(requiredFieldsNames);
64
- }
65
- const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
66
- if (fieldsWithDefaultValue.length) {
67
- // eslint-disable-next-line no-param-reassign
68
- instance.customFields || (instance.customFields = {});
69
- fieldsWithDefaultValue.filter((def) => !(def.name in instance.customFields)).forEach(({ name, defaultValue }) => {
70
- // eslint-disable-next-line no-param-reassign
71
- instance.customFields[name] = defaultValue;
72
- });
73
- }
74
56
  const { customFields } = instance;
75
- if (customFieldsIdx === -1 || !customFields) {
76
- return;
57
+ if (customFieldsIdx > -1 && customFields) {
58
+ const fieldsNames = Object.keys(customFields);
59
+ const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
60
+ if (missingFields?.length > 0) {
61
+ throw new errors_1.MissingRequiredCustomFieldError(missingFields);
62
+ }
63
+ await ValueRepo.updateValues(modelType, instance.id, identifiers, customFields, {
64
+ transaction: options.transaction,
65
+ modelOptions,
66
+ });
67
+ // eslint-disable-next-line no-param-reassign
68
+ fields.splice(customFieldsIdx, 1);
77
69
  }
78
- const fieldsNames = Object.keys(customFields);
79
- const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
80
- if (missingFields?.length > 0) {
81
- throw new errors_1.MissingRequiredCustomFieldError(missingFields);
70
+ else if (requiredFieldsNames?.length > 0) {
71
+ throw new errors_1.MissingRequiredCustomFieldError(requiredFieldsNames);
82
72
  }
83
- await ValueRepo.updateValues(modelType, instance.id, identifiers, customFields, {
84
- transaction: options.transaction,
85
- modelOptions,
86
- });
87
- // eslint-disable-next-line no-param-reassign
88
- fields.splice(customFieldsIdx, 1);
89
73
  };
90
74
  exports.beforeCreate = beforeCreate;
@@ -13,7 +13,6 @@ declare class CustomFieldDefinition extends Model {
13
13
  description?: string;
14
14
  required?: boolean;
15
15
  disabled?: boolean;
16
- defaultValue?: any;
17
16
  createdAt?: Date;
18
17
  updatedAt?: Date;
19
18
  deletedAt?: Date;
@@ -19,19 +19,12 @@ const _1 = require(".");
19
19
  const events_1 = require("../events");
20
20
  const errors_1 = require("../errors");
21
21
  const logger_1 = __importDefault(require("../utils/logger"));
22
- const validations_1 = require("../utils/validations");
23
22
  let CustomFieldDefinition = class CustomFieldDefinition extends sequelize_typescript_1.Model {
24
23
  static displayNameDefaultValue(instance) {
25
24
  if (!instance?.displayName) {
26
25
  // eslint-disable-next-line no-param-reassign
27
26
  instance.displayName = instance.name;
28
27
  }
29
- if (![null, undefined].includes(instance.defaultValue)) {
30
- const isValid = (0, validations_1.validateValue)(instance.defaultValue, instance.fieldType, instance.validation);
31
- if (!isValid) {
32
- throw new errors_1.InvalidValueError(instance.defaultValue, instance.fieldType);
33
- }
34
- }
35
28
  }
36
29
  static afterSaveHandler(instance, options) {
37
30
  if (options.transaction) {
@@ -123,13 +116,6 @@ __decorate([
123
116
  }),
124
117
  __metadata("design:type", Boolean)
125
118
  ], CustomFieldDefinition.prototype, "disabled", void 0);
126
- __decorate([
127
- (0, sequelize_typescript_1.Column)({
128
- type: sequelize_typescript_1.DataType.JSONB,
129
- allowNull: true,
130
- }),
131
- __metadata("design:type", Object)
132
- ], CustomFieldDefinition.prototype, "defaultValue", void 0);
133
119
  __decorate([
134
120
  sequelize_typescript_1.Column,
135
121
  __metadata("design:type", Date)
@@ -22,7 +22,7 @@ exports.AssociatedTestModel = AssociatedTestModel_1.default;
22
22
  const productionModels = [CustomFieldDefinition_1.default, CustomFieldValue_1.default];
23
23
  const testModels = [TestModel_1.default, AssociatedTestModel_1.default, ContextAwareTestModel_1.default, ContextTestModel_1.default];
24
24
  const SADOT_MIGRATION_PREFIX = 'sadot-migration';
25
- const SCHEMA_VERSION = 2;
25
+ const SCHEMA_VERSION = 1;
26
26
  const CUSTOM_FIELDS_SCHEMA_VERSION = `${SADOT_MIGRATION_PREFIX}_${SCHEMA_VERSION}`;
27
27
  const initTables = async (sequelize, getUser) => {
28
28
  logger_1.default.info('custom-fields: initialize custom-fields tables');
@@ -28,7 +28,9 @@ __decorate([
28
28
  ], ContextAwareTestModel.prototype, "id", void 0);
29
29
  __decorate([
30
30
  (0, sequelize_typescript_1.ForeignKey)(() => ContextTestModel_1.default),
31
- (0, sequelize_typescript_1.Column)({ type: sequelize_typescript_1.DataType.UUID }),
31
+ (0, sequelize_typescript_1.Column)({
32
+ type: sequelize_typescript_1.DataType.UUID,
33
+ }),
32
34
  __metadata("design:type", String)
33
35
  ], ContextAwareTestModel.prototype, "contextId", void 0);
34
36
  __decorate([
@@ -1,26 +1,20 @@
1
- import { type Includeable, type Transaction, type FindOptions, type WhereOptions } from 'sequelize';
1
+ import { type FindOptions, type WhereOptions } from 'sequelize';
2
2
  import { CustomFieldDefinition } from '../models';
3
3
  import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../types/definition';
4
4
  import type { ModelOptions } from '../types';
5
5
  export declare const create: (data: CreateCustomFieldDefinition) => Promise<CustomFieldDefinition>;
6
- interface SadotFindOptions {
7
- withDisabled?: boolean;
8
- transaction?: Transaction;
9
- include?: Includeable | Includeable[];
10
- }
11
- export declare const findAll: (where: WhereOptions, options?: SadotFindOptions) => Promise<CustomFieldDefinition[]>;
12
- export declare const findByIds: (ids: string[], options?: SadotFindOptions) => Promise<CustomFieldDefinition[]>;
13
- export declare const findById: (id: string, options?: Pick<SadotFindOptions, 'withDisabled'>) => Promise<CustomFieldDefinition | null>;
6
+ export declare const findAll: (where: WhereOptions, options?: any) => Promise<CustomFieldDefinition[]>;
7
+ export declare const findByIds: (ids: string[], options?: any) => Promise<CustomFieldDefinition[]>;
8
+ export declare const findById: (id: string, options?: any) => Promise<CustomFieldDefinition | null>;
14
9
  export declare const findByEntityIds: (modelType: string, entityIds: string[], options?: FindOptions & {
15
10
  modelOptions?: ModelOptions;
16
11
  }) => Promise<CustomFieldDefinition[]>;
17
12
  export declare const findByWhere: (where: any) => Promise<CustomFieldDefinition | null>;
18
13
  export declare const findDefinitionsByModels: (modelTypes: string[], options?: any) => Promise<CustomFieldDefinition[]>;
19
14
  export declare const update: (id: string, data: UpdateCustomFieldDefinition) => Promise<CustomFieldDefinition>;
20
- export declare const disable: (id: string) => Promise<[affectedCount: number]>;
21
- export declare const destroy: (id: string) => Promise<number>;
15
+ export declare const disable: (id: string) => Promise<any>;
16
+ export declare const destroy: (id: string) => Promise<any>;
22
17
  /**
23
18
  * Return the names of the required fields for a given model
24
19
  */
25
20
  export declare const getRequiredFields: (modelType: string, modelId: string | string[], entityId: string | string[], modelOptions?: ModelOptions) => Promise<string[]>;
26
- export {};
@@ -31,7 +31,6 @@ const models_1 = require("../models");
31
31
  const DefinitionRepo = __importStar(require("./definition"));
32
32
  const logger_1 = __importDefault(require("../utils/logger"));
33
33
  const errors_1 = require("../errors");
34
- const constants_1 = require("../utils/constants");
35
34
  const findByModelIdAndDefinition = async (modelId, customFieldDefinitionId) => models_1.CustomFieldValue.findAll({ where: { modelId, customFieldDefinitionId }, include: [models_1.CustomFieldDefinition] });
36
35
  exports.findByModelIdAndDefinition = findByModelIdAndDefinition;
37
36
  const create = async (data, withAssociations = false) => {
@@ -67,18 +66,6 @@ const findValuesByModelIds = async (modelIds, options) => {
67
66
  });
68
67
  };
69
68
  exports.findValuesByModelIds = findValuesByModelIds;
70
- const formatFunctions = {
71
- [constants_1.CustomFieldDefinitionType.DATE]: (value) => {
72
- if (value) {
73
- const date = new Date(value);
74
- if (date.toString() === 'Invalid Date') {
75
- throw new Error(`Invalid date value: ${value}`);
76
- }
77
- return date.toISOString();
78
- }
79
- return null;
80
- },
81
- };
82
69
  /**
83
70
  * Try to update custom field values for a model instance.
84
71
  * Create new value record if not exists, but fails if value's definition not exist.
@@ -96,9 +83,11 @@ const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, opt
96
83
  const where = {
97
84
  modelType,
98
85
  name: names,
99
- ...(!options.modelOptions?.useEntityIdFromInclude && { entityId: identifiers }),
100
86
  };
101
- const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) ?? [];
87
+ if (!options.modelOptions?.useEntityIdFromInclude) {
88
+ where.entityId = identifiers;
89
+ }
90
+ const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) || [];
102
91
  const disabledDefinitions = fieldDefinitions.filter((def) => def.disabled);
103
92
  if (fieldDefinitions.length !== names.length) {
104
93
  logger_1.default.warn(`custom-fields: missing definitions for ${modelType} ${modelId}`, { names, fieldDefinitions });
@@ -110,17 +99,12 @@ const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, opt
110
99
  if (valuesWithDisabledDefinitions?.length > 0) {
111
100
  logger_1.default.warn(`custom-fields: trying to update disabled values: ${valuesWithDisabledDefinitions.join(', ')}`);
112
101
  }
113
- const values = names.map((name) => {
114
- const fieldDefinition = fieldDefinitions.find((def) => def.name === name);
115
- const formatFunction = formatFunctions[fieldDefinition.fieldType];
116
- const value = formatFunction ? formatFunction(valuesToUpdate[name]) : valuesToUpdate[name];
117
- return {
118
- modelId,
119
- updatedAt: new Date(),
120
- customFieldDefinitionId: fieldDefinition.id,
121
- value: value !== undefined ? value : fieldDefinition.defaultValue,
122
- };
123
- });
102
+ const values = names.map((name) => ({
103
+ modelId,
104
+ value: valuesToUpdate[name],
105
+ updatedAt: new Date(),
106
+ customFieldDefinitionId: fieldDefinitions.find((def) => def.name === name).id,
107
+ }));
124
108
  return Promise.all(values.map(async (value) => {
125
109
  const [cfv] = await models_1.CustomFieldValue.upsert(value, {
126
110
  transaction: options.transaction,
@@ -17,10 +17,6 @@ export type CustomFieldFilterOptions = {
17
17
  where?: WhereOptions;
18
18
  replacements?: Record<string, string>;
19
19
  };
20
- type customFieldsFilterScopeParams = {
21
- replacementsMap: Record<string, string>;
22
- scopeValue: Record<string, ConditionValue>;
23
- };
24
20
  /**
25
21
  * A Sequelize scope for filtering models by custom fields.
26
22
  * This scope builds a WHERE clause to be applied on the main query.
@@ -28,12 +24,9 @@ type customFieldsFilterScopeParams = {
28
24
  * @param name - The model type name used to join custom_field_definitions.
29
25
  * @returns A function that takes conditions and returns the Sequelize options object.
30
26
  */
31
- export declare const customFieldsFilterScope: (name: string) => ({ replacementsMap: replacements, scopeValue: conditions, }: customFieldsFilterScopeParams) => CustomFieldFilterOptions;
27
+ export declare const customFieldsFilterScope: (name: string) => (conditions: Record<string, ConditionValue>) => CustomFieldFilterOptions;
32
28
  export declare const scopeName = "filterByCustomFields";
33
- export declare const customFieldsSortScope: (name: string) => ({ replacementsMap, scopeValue: sort }: {
34
- replacementsMap: any;
35
- scopeValue: any;
36
- }) => {
29
+ export declare const customFieldsSortScope: (name: string) => (sort: CustomFieldSort[]) => {
37
30
  attributes?: undefined;
38
31
  order?: undefined;
39
32
  replacements?: undefined;
@@ -42,6 +35,6 @@ export declare const customFieldsSortScope: (name: string) => ({ replacementsMap
42
35
  include: (string | import("sequelize/types/utils").Literal)[][];
43
36
  };
44
37
  order: import("sequelize/types/utils").Literal[];
45
- replacements: any;
38
+ replacements: Record<string, string>;
46
39
  };
47
40
  export {};
@@ -8,12 +8,11 @@ exports.customFieldsSortScope = exports.scopeName = exports.customFieldsFilterSc
8
8
  const sequelize_1 = require("sequelize");
9
9
  const sequelize_typescript_1 = require("sequelize-typescript");
10
10
  const common_types_1 = require("@autofleet/common-types");
11
+ const moment_1 = __importDefault(require("moment"));
11
12
  const helpers_1 = require("../utils/helpers");
12
- const logger_1 = __importDefault(require("../utils/logger"));
13
13
  const { CUSTOM_FIELDS_FILTER_SCOPE } = common_types_1.customFields;
14
- const isDate = (input) => input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
15
14
  const castIfNeeded = (conditionValue) => {
16
- if (isDate(conditionValue)) {
15
+ if (moment_1.default.isDate(conditionValue)) {
17
16
  return '::timestamp';
18
17
  }
19
18
  if (!Number.isNaN(Number(conditionValue))) {
@@ -21,18 +20,13 @@ const castIfNeeded = (conditionValue) => {
21
20
  }
22
21
  return '';
23
22
  };
24
- const addQuotationIfNeeded = (conditionValue, valRep) => {
25
- if (typeof conditionValue === 'string') {
26
- return `'"' || :${valRep} || '"'`;
27
- }
28
- return `:${valRep}`;
29
- };
30
23
  const AND_DELIMETER = ' AND ';
31
24
  const OR_DELIMETER = ' OR ';
32
25
  const CD_TABLE_ALIAS = 'cd';
33
26
  const CD_NAME_COLUMN = `${CD_TABLE_ALIAS}.name`;
34
27
  const CV_TABLE_ALIAS = 'cv';
35
- const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value::varchar)`;
28
+ const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value)`;
29
+ const castValueToJsonb = (value) => `to_jsonb(${value}::text)`;
36
30
  /**
37
31
  * A Sequelize scope for filtering models by custom fields.
38
32
  * This scope builds a WHERE clause to be applied on the main query.
@@ -40,16 +34,18 @@ const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value::varchar)`;
40
34
  * @param name - The model type name used to join custom_field_definitions.
41
35
  * @returns A function that takes conditions and returns the Sequelize options object.
42
36
  */
43
- const customFieldsFilterScope = (name) => ({ replacementsMap: replacements, scopeValue: conditions, }) => {
37
+ const customFieldsFilterScope = (name) => (conditions) => {
44
38
  if (!conditions || Object.keys(conditions).length === 0) {
45
39
  return {};
46
40
  }
41
+ const ConditionNameRandomStr = (0, helpers_1.generateRandomString)();
42
+ const replacements = {};
43
+ replacements[ConditionNameRandomStr] = `${name}`;
47
44
  // Build the WHERE clause for custom field filtering
48
45
  const conditionsStrings = Object.entries(conditions)
49
46
  .map(([key, condition]) => {
50
- const replacemetKey = Object.keys(replacements).find((randomString) => replacements[randomString] === key);
51
- if (!replacemetKey)
52
- return false;
47
+ const replacemetKey = (0, helpers_1.generateRandomString)();
48
+ replacements[replacemetKey] = `${key}`;
53
49
  const columnCondition = `(${CD_NAME_COLUMN} = :${replacemetKey})`;
54
50
  if (Array.isArray(condition)) {
55
51
  if (condition.length === 0) {
@@ -58,24 +54,31 @@ const customFieldsFilterScope = (name) => ({ replacementsMap: replacements, scop
58
54
  }
59
55
  if (typeof condition[0] === 'string') {
60
56
  const values = condition.map((v) => {
61
- const valRandom = Object.keys(replacements).find((randomString) => replacements[randomString] === v);
62
- return addQuotationIfNeeded(v, valRandom);
57
+ const valRandom = (0, helpers_1.generateRandomString)();
58
+ replacements[`${valRandom}`] = `${v}`;
59
+ return castValueToJsonb(`:${valRandom}`);
63
60
  }).join(',');
64
61
  return `(${columnCondition} AND ${CV_VALUE_COLUMN} IN ( ${values} ))`;
65
62
  }
66
63
  return condition
67
64
  .map((c) => {
68
- const valRep = Object.keys(replacements).find((replacementKey) => replacements[replacementKey] === c.value);
69
- return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(c.value)} ${c.operator} ${addQuotationIfNeeded(c.value, valRep)} )`;
65
+ const valRep = (0, helpers_1.generateRandomString)();
66
+ replacements[valRep] = `${c.value}`;
67
+ const valueAsJsonb = castValueToJsonb(`:${valRep}`);
68
+ return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(c.value)} ${c.operator} ${valueAsJsonb})`;
70
69
  }).join(AND_DELIMETER);
71
70
  }
72
71
  if (typeof condition === 'string' || typeof condition === 'number') {
73
- const conditionRep = Object.keys(replacements).find((replacementKey) => replacements[replacementKey] === condition);
74
- return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition)} = ${addQuotationIfNeeded(condition, conditionRep)})`;
72
+ const conditionRep = (0, helpers_1.generateRandomString)();
73
+ replacements[conditionRep] = `${condition}`;
74
+ const valueAsJsonb = castValueToJsonb(`:${conditionRep}`);
75
+ return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition)} = ${valueAsJsonb})`;
75
76
  }
76
77
  if (condition?.operator) {
77
- const valueRep = Object.keys(replacements).find((replacementKey) => replacements[replacementKey] === condition.value);
78
- return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition.value)} ${condition.operator} ${addQuotationIfNeeded(condition.value, valueRep)})`;
78
+ const valueRep = (0, helpers_1.generateRandomString)();
79
+ replacements[valueRep] = `${condition.value}`;
80
+ const valueAsJsonb = castValueToJsonb(`:${valueRep}`);
81
+ return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition.value)} ${condition.operator} ${valueAsJsonb})`;
79
82
  }
80
83
  return false;
81
84
  })
@@ -85,15 +88,14 @@ const customFieldsFilterScope = (name) => ({ replacementsMap: replacements, scop
85
88
  }
86
89
  const customFieldConditions = conditionsStrings.join(OR_DELIMETER);
87
90
  const subQuery = `
88
- SELECT cv.model_id
89
- FROM custom_field_values AS cv
90
- INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
91
- AND cd.model_type = '${name}'
92
- WHERE ${customFieldConditions}
93
- GROUP BY cv.model_id
94
- HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
95
- `.replace(/\n/g, '');
96
- logger_1.default.info('custom fields filter scope', { subQuery, replacements });
91
+ SELECT cv.model_id
92
+ FROM custom_field_values AS cv
93
+ INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
94
+ AND cd.model_type = :${ConditionNameRandomStr}
95
+ WHERE ${customFieldConditions}
96
+ GROUP BY cv.model_id
97
+ HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
98
+ `.replace(/\n/g, '');
97
99
  return {
98
100
  where: {
99
101
  id: {
@@ -105,13 +107,15 @@ const customFieldsFilterScope = (name) => ({ replacementsMap: replacements, scop
105
107
  };
106
108
  exports.customFieldsFilterScope = customFieldsFilterScope;
107
109
  exports.scopeName = CUSTOM_FIELDS_FILTER_SCOPE;
108
- const customFieldsSortScope = (name) => ({ replacementsMap, scopeValue: sort }) => {
110
+ const customFieldsSortScope = (name) => (sort) => {
109
111
  if (!sort || sort.length === 0) {
110
112
  return {};
111
113
  }
112
114
  const randomStr = (0, helpers_1.generateRandomString)();
115
+ const replacements = {};
113
116
  const includes = Object.entries(sort).map(([key]) => {
114
- const replacemetKey = Object.keys(replacementsMap).find((randomString) => replacementsMap[randomString] === key);
117
+ const keyRandomReplacement = (0, helpers_1.generateRandomString)();
118
+ replacements[keyRandomReplacement] = `${key}`;
115
119
  return ([
116
120
  sequelize_typescript_1.Sequelize.literal(`(
117
121
  SELECT value
@@ -120,22 +124,19 @@ const customFieldsSortScope = (name) => ({ replacementsMap, scopeValue: sort })
120
124
  ON cv.custom_field_definition_id = cd.id
121
125
  AND cd.model_type = '${name}'
122
126
  WHERE cv.model_id = "${name}"."id"
123
- AND cd.name = :${replacemetKey}
127
+ AND cd.name = :${keyRandomReplacement}
124
128
  ) AS CustomFieldAggregation
125
129
  )
126
130
  `), randomStr,
127
131
  ]);
128
132
  });
129
- const orders = Object.entries(sort).map(([, sortObject]) => {
130
- const direction = typeof sortObject === 'string' ? sortObject : Object.values(sortObject)[0];
131
- return sequelize_typescript_1.Sequelize.literal(`"${randomStr}" ${direction || 'ASC'}`);
132
- });
133
+ const orders = Object.entries(sort).map(([, value]) => sequelize_typescript_1.Sequelize.literal(`"${randomStr}" ${value}`));
133
134
  return {
134
135
  attributes: {
135
136
  include: includes,
136
137
  },
137
138
  order: orders,
138
- replacements: replacementsMap,
139
+ replacements,
139
140
  };
140
141
  };
141
142
  exports.customFieldsSortScope = customFieldsSortScope;
@@ -14,7 +14,6 @@ export declare const coolFieldDefinition2: {
14
14
  createdAt?: Date;
15
15
  updatedAt?: Date;
16
16
  deletedAt?: Date;
17
- defaultValue?: any;
18
17
  displayName?: string;
19
18
  validation?: any;
20
19
  fieldType: string;
@@ -30,7 +29,6 @@ export declare const coolFieldDefinition3: {
30
29
  createdAt?: Date;
31
30
  updatedAt?: Date;
32
31
  deletedAt?: Date;
33
- defaultValue?: any;
34
32
  displayName?: string;
35
33
  validation?: any;
36
34
  fieldType: string;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createDefinitions = exports.createDefinition = exports.statusField = exports.selectField = exports.booleanField = exports.coolFieldDefinition3 = exports.coolFieldDefinition2 = exports.coolFieldDefinition = exports.contextAwareFieldDefinition = void 0;
4
- const node_crypto_1 = require("node:crypto");
4
+ const uuid_1 = require("uuid");
5
5
  exports.contextAwareFieldDefinition = {
6
6
  name: 'cool field',
7
7
  modelType: 'ContextAwareTestModel',
@@ -12,7 +12,7 @@ exports.coolFieldDefinition = {
12
12
  name: 'cool field',
13
13
  modelType: 'TestModel',
14
14
  fieldType: 'number',
15
- entityId: (0, node_crypto_1.randomUUID)(),
15
+ entityId: (0, uuid_1.v4)(),
16
16
  entityType: 'fleetId',
17
17
  };
18
18
  exports.coolFieldDefinition2 = {
@@ -27,7 +27,7 @@ const booleanField = (modelType) => ({
27
27
  name: 'shapeless',
28
28
  modelType,
29
29
  fieldType: 'boolean',
30
- entityId: (0, node_crypto_1.randomUUID)(),
30
+ entityId: (0, uuid_1.v4)(),
31
31
  entityType: 'fleetId',
32
32
  });
33
33
  exports.booleanField = booleanField;
@@ -36,7 +36,7 @@ const selectField = (modelType, options) => ({
36
36
  modelType,
37
37
  fieldType: 'select',
38
38
  validation: options,
39
- entityId: (0, node_crypto_1.randomUUID)(),
39
+ entityId: (0, uuid_1.v4)(),
40
40
  entityType: 'fleetId',
41
41
  });
42
42
  exports.selectField = selectField;
@@ -45,25 +45,24 @@ const statusField = (modelType, options) => ({
45
45
  modelType,
46
46
  fieldType: 'status',
47
47
  validation: options,
48
- entityId: (0, node_crypto_1.randomUUID)(),
48
+ entityId: (0, uuid_1.v4)(),
49
49
  entityType: 'fleetId',
50
50
  });
51
51
  exports.statusField = statusField;
52
52
  // eslint-disable-next-line max-len
53
53
  const createDefinition = (defaults) => ({
54
- name: defaults?.name || `def_${(0, node_crypto_1.randomUUID)()}`,
54
+ name: defaults?.name || `def_${(0, uuid_1.v4)()}`,
55
55
  modelType: defaults?.modelType || 'TestModel',
56
56
  fieldType: defaults?.fieldType || 'boolean',
57
- entityId: defaults?.entityId || (0, node_crypto_1.randomUUID)(),
57
+ entityId: defaults?.entityId || (0, uuid_1.v4)(),
58
58
  entityType: defaults?.entityType || 'fleetId',
59
- ...(defaults?.defaultValue && { defaultValue: defaults.defaultValue }),
60
59
  });
61
60
  exports.createDefinition = createDefinition;
62
61
  const createDefinitions = (defaults, length = 1) => (Array(length).fill({}).map((_) => ({
63
- name: defaults?.name || `def_${(0, node_crypto_1.randomUUID)()}`,
62
+ name: defaults?.name || `def_${(0, uuid_1.v4)()}`,
64
63
  modelType: defaults?.modelType || 'TestModel',
65
64
  fieldType: defaults?.fieldType || 'boolean',
66
- entityId: defaults?.entityId || (0, node_crypto_1.randomUUID)(),
65
+ entityId: defaults?.entityId || (0, uuid_1.v4)(),
67
66
  entityType: defaults?.entityType || 'fleetId',
68
67
  })));
69
68
  exports.createDefinitions = createDefinitions;
@@ -3,7 +3,6 @@ export interface CustomFieldDefinitionDTO {
3
3
  name: string;
4
4
  displayName?: string;
5
5
  validation?: any;
6
- defaultValue?: any;
7
6
  fieldType: string;
8
7
  entityId: string;
9
8
  entityType: string;
@@ -1,2 +1,2 @@
1
- declare const logger: import("@autofleet/logger").LoggerInstanceManager;
1
+ declare const logger: any;
2
2
  export default logger;
@@ -1,8 +1,6 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const logger_1 = __importDefault(require("@autofleet/logger"));
7
- const logger = (0, logger_1.default)();
3
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
4
+ const Logger = require('@autofleet/logger');
5
+ const logger = Logger();
8
6
  exports.default = logger;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/sadot",
3
- "version": "0.7.7-beta.1",
3
+ "version": "0.7.7",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -8,7 +8,6 @@
8
8
  "build": "rm -rf dist && tsc",
9
9
  "linter": "eslint .",
10
10
  "test": "jest --forceExit --runInBand",
11
- "test-debug": "node --inspect-brk node_modules/.bin/jest --runInBand --testTimeout=10000000",
12
11
  "coverage": "jest --coverage --forceExit --runInBand && rm -rf ./coverage",
13
12
  "build-to-local-repo": "node --run build && cp -r dist/* ../$REPO/node_modules/$npm_package_name/dist",
14
13
  "dev": "nodemon",
@@ -3,7 +3,6 @@ import { Op, type WhereOptions } from 'sequelize';
3
3
  import { Sequelize } from 'sequelize-typescript';
4
4
  import { customFields } from '@autofleet/common-types';
5
5
  import { generateRandomString } from '../utils/helpers';
6
- import logger from '../utils/logger';
7
6
 
8
7
  const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
9
8
 
@@ -44,21 +43,13 @@ const castIfNeeded = (conditionValue: string): string => {
44
43
  }
45
44
  return '';
46
45
  };
47
-
48
- const addQuotationIfNeeded = (conditionValue: string, valRep: string): string => {
49
- if (typeof conditionValue === 'string') {
50
- return `'"' || :${valRep} || '"'`;
51
- }
52
- return `:${valRep}`;
53
- };
54
-
55
46
  const AND_DELIMETER = ' AND ';
56
47
  const OR_DELIMETER = ' OR ';
57
-
58
48
  const CD_TABLE_ALIAS = 'cd';
59
49
  const CD_NAME_COLUMN = `${CD_TABLE_ALIAS}.name`;
60
50
  const CV_TABLE_ALIAS = 'cv';
61
- const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value::varchar)`;
51
+ const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value)`;
52
+ const castValueToJsonb = (value) => `to_jsonb(${value}::text)`;
62
53
 
63
54
  /**
64
55
  * A Sequelize scope for filtering models by custom fields.
@@ -85,8 +76,8 @@ export const customFieldsFilterScope = (
85
76
  const replacemetKey = Object.keys(replacements).find(
86
77
  (randomString) => replacements[randomString] === key,
87
78
  );
88
- if (!replacemetKey) return false;
89
79
  const columnCondition = `(${CD_NAME_COLUMN} = :${replacemetKey})`;
80
+ if (!replacemetKey) return false;
90
81
 
91
82
  if (Array.isArray(condition)) {
92
83
  if (condition.length === 0) {
@@ -98,29 +89,32 @@ export const customFieldsFilterScope = (
98
89
  const valRandom = Object.keys(replacements).find(
99
90
  (randomString) => replacements[randomString] === v,
100
91
  );
101
- return addQuotationIfNeeded(v, valRandom);
92
+ return castValueToJsonb(`:${valRandom}`);
102
93
  }).join(',');
103
- return `(${columnCondition} AND ${CV_VALUE_COLUMN} IN ( ${values} ))`;
94
+ return `(${columnCondition} AND ${CV_VALUE_COLUMN} IN (${values}))`;
104
95
  }
105
96
  return condition
106
97
  .map((c) => {
107
98
  const valRep = Object.keys(replacements).find(
108
99
  (replacementKey) => replacements[replacementKey] === c.value,
109
100
  );
110
- return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(c.value)} ${c.operator} ${addQuotationIfNeeded(c.value, valRep)} )`;
101
+ const valueAsJsonb = castValueToJsonb(`:${valRep}`);
102
+ return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(c.value)} ${c.operator} ${valueAsJsonb})`;
111
103
  }).join(AND_DELIMETER);
112
104
  }
113
105
  if (typeof condition === 'string' || typeof condition === 'number') {
114
106
  const conditionRep = Object.keys(replacements).find(
115
107
  (replacementKey) => replacements[replacementKey] === condition,
116
108
  );
117
- return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition)} = ${addQuotationIfNeeded(condition, conditionRep)})`;
109
+ const valueAsJsonb = castValueToJsonb(`:${conditionRep}`);
110
+ return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition)} = ${valueAsJsonb})`;
118
111
  }
119
112
  if (condition?.operator) {
120
113
  const valueRep = Object.keys(replacements).find(
121
114
  (replacementKey) => replacements[replacementKey] === condition.value,
122
115
  );
123
- return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition.value)} ${condition.operator} ${addQuotationIfNeeded(condition.value, valueRep)})`;
116
+ const valueAsJsonb = castValueToJsonb(`:${valueRep}`);
117
+ return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition.value)} ${condition.operator} ${valueAsJsonb})`;
124
118
  }
125
119
  return false;
126
120
  },
@@ -131,15 +125,15 @@ export const customFieldsFilterScope = (
131
125
  }
132
126
  const customFieldConditions = conditionsStrings.join(OR_DELIMETER);
133
127
  const subQuery = `
134
- SELECT cv.model_id
135
- FROM custom_field_values AS cv
136
- INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
137
- AND cd.model_type = '${name}'
138
- WHERE ${customFieldConditions}
139
- GROUP BY cv.model_id
140
- HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
141
- `.replace(/\n/g, '');
142
- logger.info('custom fields filter scope', { subQuery, replacements });
128
+ SELECT cv.model_id
129
+ FROM custom_field_values AS cv
130
+ INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
131
+
132
+ WHERE ${customFieldConditions}
133
+ GROUP BY cv.model_id
134
+ HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
135
+ `.replace(/\n/g, '');
136
+ console.log(subQuery);
143
137
  return {
144
138
  where: {
145
139
  id: {