@autofleet/sadot 0.7.6-beta-0ecad376.4 → 0.7.6-beta.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.
@@ -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
+ }, true);
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;
@@ -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 {};
@@ -24,5 +24,5 @@ export declare const findValuesByModelIds: (modelIds: string[], options?: any) =
24
24
  */
25
25
  export declare const updateValues: (modelType: string, modelId: string, identifiers: string[], valuesToUpdate: ValuesToUpdate, options?: FindOptions & {
26
26
  modelOptions?: ModelOptions;
27
- }) => Promise<CustomFieldValue[]>;
27
+ }, defineAllDefaults?: boolean) => Promise<CustomFieldValue[]>;
28
28
  export declare const deleteValue: (id: string, options?: any) => Promise<any>;
@@ -84,7 +84,7 @@ const formatFunctions = {
84
84
  * Create new value record if not exists, but fails if value's definition not exist.
85
85
  * Return the updated values
86
86
  */
87
- const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, options = {}) => {
87
+ const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, options = {}, defineAllDefaults = false) => {
88
88
  const names = Object.keys(valuesToUpdate);
89
89
  logger_1.default.debug(`custom-fields: updating values for ${modelType} ${modelId}`, {
90
90
  names,
@@ -96,9 +96,11 @@ const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, opt
96
96
  const where = {
97
97
  modelType,
98
98
  name: names,
99
- ...(!options.modelOptions?.useEntityIdFromInclude && { entityId: identifiers }),
100
99
  };
101
- const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) ?? [];
100
+ if (!options.modelOptions?.useEntityIdFromInclude) {
101
+ where.entityId = identifiers;
102
+ }
103
+ const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) || [];
102
104
  const disabledDefinitions = fieldDefinitions.filter((def) => def.disabled);
103
105
  if (fieldDefinitions.length !== names.length) {
104
106
  logger_1.default.warn(`custom-fields: missing definitions for ${modelType} ${modelId}`, { names, fieldDefinitions });
@@ -110,17 +112,28 @@ const updateValues = async (modelType, modelId, identifiers, valuesToUpdate, opt
110
112
  if (valuesWithDisabledDefinitions?.length > 0) {
111
113
  logger_1.default.warn(`custom-fields: trying to update disabled values: ${valuesWithDisabledDefinitions.join(', ')}`);
112
114
  }
115
+ const visitedFields = new Set();
113
116
  const values = names.map((name) => {
114
117
  const fieldDefinition = fieldDefinitions.find((def) => def.name === name);
118
+ visitedFields.add(fieldDefinition);
115
119
  const formatFunction = formatFunctions[fieldDefinition.fieldType];
116
- const value = formatFunction ? formatFunction(valuesToUpdate[name]) : valuesToUpdate[name];
117
120
  return {
118
121
  modelId,
122
+ value: (formatFunction ? formatFunction(valuesToUpdate[name]) : valuesToUpdate[name]) ?? fieldDefinition.defaultValue,
119
123
  updatedAt: new Date(),
120
124
  customFieldDefinitionId: fieldDefinition.id,
121
- value: value !== undefined ? value : fieldDefinition.defaultValue,
122
125
  };
123
126
  });
127
+ if (defineAllDefaults) {
128
+ fieldDefinitions.filter((def) => !visitedFields.has(def) && ![null, undefined].includes(def.defaultValue)).forEach(({ id, defaultValue }) => {
129
+ values.push({
130
+ modelId,
131
+ value: defaultValue,
132
+ updatedAt: new Date(),
133
+ customFieldDefinitionId: id,
134
+ });
135
+ });
136
+ }
124
137
  return Promise.all(values.map(async (value) => {
125
138
  const [cfv] = await models_1.CustomFieldValue.upsert(value, {
126
139
  transaction: options.transaction,
@@ -1,4 +1,4 @@
1
- import { type WhereOptions, type BindOrReplacements } from 'sequelize';
1
+ import { type WhereOptions, type IncludeOptions } from 'sequelize';
2
2
  import { type ModelStatic } from 'sequelize-typescript';
3
3
  import { CustomFieldDefinitionType } from '../constants';
4
4
  /**
@@ -14,12 +14,12 @@ import { CustomFieldDefinitionType } from '../constants';
14
14
  * @param {CustomFieldDefinitionType[]} excludedCustomFieldsTypes - An array of custom field types
15
15
  * to exclude from the search
16
16
  *
17
- * @returns {CustomFieldsSearchPayload} - An object containing the WHERE clause and replacements
17
+ * @returns {CustomFieldsSearchPayload} - An object containing the INCLUDE clause and WHERE clause
18
18
  * for Sequelize.
19
19
  */
20
20
  interface CustomFieldsSearchPayload {
21
21
  where: WhereOptions;
22
- replacements: BindOrReplacements;
22
+ include: IncludeOptions;
23
23
  }
24
24
  export declare const generateRandomString: (length?: number) => string;
25
25
  export declare const generateCustomFieldSearchQueryPayload: (searchTerm: string, model: ModelStatic, entityId: string, customFieldsTypesToExclude?: CustomFieldDefinitionType[]) => CustomFieldsSearchPayload;
@@ -6,6 +6,7 @@ const sequelize_1 = require("sequelize");
6
6
  const sequelize_typescript_1 = require("sequelize-typescript");
7
7
  const node_crypto_1 = require("node:crypto");
8
8
  const constants_1 = require("../constants");
9
+ const models_1 = require("../../models");
9
10
  const generateRandomString = (length = 5) => {
10
11
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
11
12
  return Array.from({ length }, () => characters.charAt((0, node_crypto_1.randomInt)(characters.length))).join('');
@@ -14,27 +15,28 @@ exports.generateRandomString = generateRandomString;
14
15
  const generateCustomFieldSearchQueryPayload = (searchTerm, model, entityId, customFieldsTypesToExclude = [
15
16
  constants_1.CustomFieldDefinitionType.DATETIME,
16
17
  constants_1.CustomFieldDefinitionType.DATE,
17
- ]) => {
18
- const excludedTypesString = customFieldsTypesToExclude.map((type) => `'${type}'`).join(',');
19
- const subQuery = 'EXISTS ('
20
- + ' SELECT 1'
21
- + ' FROM "custom_field_values" AS "cv"'
22
- + ' INNER JOIN custom_field_definitions AS cd '
23
- + ` ON cd.entity_id = '${entityId}'`
24
- + ' AND cv.custom_field_definition_id = cd.id'
25
- + ` AND cd.model_type = '${model.name}'`
26
- + ` ${excludedTypesString ? `AND cd.field_type NOT IN (${excludedTypesString})` : ''}`
27
- + ' WHERE'
28
- + ' "cv"."deleted_at" IS NULL'
29
- + ` AND "cv"."model_id" = "${model.name}"."id"`
30
- + ' AND CAST("cv"."value" AS TEXT) ILIKE :searchTerm)';
31
- return {
18
+ ]) => ({
19
+ include: {
20
+ model: models_1.CustomFieldValue,
21
+ as: 'cff',
22
+ required: false,
23
+ include: [
24
+ {
25
+ model: models_1.CustomFieldDefinition,
26
+ attributes: [],
27
+ required: true,
28
+ where: {
29
+ entityId,
30
+ modelType: model,
31
+ fieldType: { [sequelize_1.Op.notIn]: customFieldsTypesToExclude },
32
+ },
33
+ },
34
+ ],
32
35
  where: {
33
- [sequelize_1.Op.or]: [
34
- sequelize_typescript_1.Sequelize.where(sequelize_typescript_1.Sequelize.literal(subQuery), true),
35
- ],
36
+ deletedAt: null,
37
+ value: { [sequelize_1.Op.iLike]: `%${searchTerm}%` },
36
38
  },
37
- replacements: { searchTerm: `%${searchTerm}%` },
38
- };
39
- };
39
+ },
40
+ where: sequelize_typescript_1.Sequelize.where(sequelize_typescript_1.Sequelize.col('cff.model_id'), { [sequelize_1.Op.not]: null }),
41
+ });
40
42
  exports.generateCustomFieldSearchQueryPayload = generateCustomFieldSearchQueryPayload;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/sadot",
3
- "version": "0.7.6-beta-0ecad376.4",
3
+ "version": "0.7.6-beta.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -1,4 +1,3 @@
1
- import type { WhereOptions } from 'sequelize';
2
1
  import logger from '../utils/logger';
3
2
  import * as ValueRepo from '../repository/value';
4
3
  import * as DefinitionRepo from '../repository/definition';
@@ -24,54 +23,37 @@ export const beforeCreate = (scopeAttributes: string[], modelOptions: ModelOptio
24
23
  ): Promise<void> => {
25
24
  logger.debug('sadot - before create hook');
26
25
  const { fields } = options;
27
- const { include, useEntityIdFromInclude } = modelOptions;
28
26
  const modelType = instance.constructor.name;
29
27
 
30
28
  const identifiers = applyScopeToInstance(instance, scopeAttributes);
31
29
 
32
- const where: WhereOptions = {
33
- modelType,
34
- disabled: false,
35
- ...(!useEntityIdFromInclude && { entityId: identifiers }),
36
- };
37
- const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: false, transaction: options.transaction, include: include?.(identifiers) });
38
- const requiredFieldsNames = Array.from(new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)));
30
+ // get all model's required definitions
31
+ const requiredFieldsNames = await DefinitionRepo.getRequiredFields(modelType, instance.id, identifiers, modelOptions);
39
32
 
40
33
  const customFieldsIdx = fields.indexOf('customFields');
41
-
42
- if ((customFieldsIdx === -1 || !instance.customFields) && requiredFieldsNames?.length > 0) {
43
- throw new MissingRequiredCustomFieldError(requiredFieldsNames);
44
- }
45
- const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
46
- if (fieldsWithDefaultValue.length) {
47
- // eslint-disable-next-line no-param-reassign
48
- instance.customFields ||= {};
49
- fieldsWithDefaultValue.filter((def) => !(def.name in instance.customFields)).forEach(({ name, defaultValue }) => {
50
- // eslint-disable-next-line no-param-reassign
51
- instance.customFields[name] = defaultValue;
52
- });
53
- }
54
34
  const { customFields } = instance;
55
- if (customFieldsIdx === -1 || !customFields) {
56
- return;
57
- }
58
- const fieldsNames = Object.keys(customFields);
59
- const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
60
- if (missingFields?.length > 0) {
61
- throw new MissingRequiredCustomFieldError(missingFields);
62
- }
35
+ if (customFieldsIdx > -1 && customFields) {
36
+ const fieldsNames = Object.keys(customFields);
37
+ const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
38
+ if (missingFields?.length > 0) {
39
+ throw new MissingRequiredCustomFieldError(missingFields);
40
+ }
63
41
 
64
- await ValueRepo.updateValues(
65
- modelType,
66
- instance.id,
67
- identifiers,
68
- customFields,
69
- {
70
- transaction: options.transaction,
71
- modelOptions,
72
- },
73
- );
42
+ await ValueRepo.updateValues(
43
+ modelType,
44
+ instance.id,
45
+ identifiers,
46
+ customFields,
47
+ {
48
+ transaction: options.transaction,
49
+ modelOptions,
50
+ },
51
+ true,
52
+ );
74
53
 
75
- // eslint-disable-next-line no-param-reassign
76
- fields.splice(customFieldsIdx, 1);
54
+ // eslint-disable-next-line no-param-reassign
55
+ fields.splice(customFieldsIdx, 1);
56
+ } else if (requiredFieldsNames?.length > 0) {
57
+ throw new MissingRequiredCustomFieldError(requiredFieldsNames);
58
+ }
77
59
  };
@@ -1,7 +1,4 @@
1
- import {
2
- Op,
3
- type Includeable, type Transaction, type FindOptions, type WhereOptions,
4
- } from 'sequelize';
1
+ import { Op, type FindOptions, type WhereOptions } from 'sequelize';
5
2
  import { CustomFieldDefinition } from '../models';
6
3
  import type { CreateCustomFieldDefinition, UpdateCustomFieldDefinition } from '../types/definition';
7
4
  import type { ModelOptions } from '../types';
@@ -9,15 +6,9 @@ import type { ModelOptions } from '../types';
9
6
  export const create = (data: CreateCustomFieldDefinition): Promise<CustomFieldDefinition> =>
10
7
  CustomFieldDefinition.create(data);
11
8
 
12
- interface SadotFindOptions {
13
- withDisabled?: boolean;
14
- transaction?: Transaction;
15
- include?: Includeable | Includeable[];
16
- }
17
-
18
9
  export const findAll = (
19
10
  where: WhereOptions,
20
- options: SadotFindOptions = { withDisabled: false },
11
+ options: any = { withDisabled: false },
21
12
  ): Promise<CustomFieldDefinition[]> => {
22
13
  const queryModel = options.withDisabled
23
14
  ? CustomFieldDefinition.unscoped()
@@ -33,12 +24,12 @@ export const findAll = (
33
24
 
34
25
  export const findByIds = (
35
26
  ids: string[],
36
- options: SadotFindOptions = { withDisabled: false },
27
+ options: any = { withDisabled: false },
37
28
  ): Promise<CustomFieldDefinition[]> => findAll({ id: { [Op.in]: ids } }, options);
38
29
 
39
30
  export const findById = (
40
31
  id: string,
41
- options: Pick<SadotFindOptions, 'withDisabled'> = { withDisabled: false },
32
+ options: any = { withDisabled: false },
42
33
  ): Promise<CustomFieldDefinition | null> => {
43
34
  const { withDisabled } = options;
44
35
  if (withDisabled) {
@@ -96,13 +87,13 @@ export const update = async (
96
87
  return updatedDefinition;
97
88
  };
98
89
 
99
- export const disable = (id: string): Promise<[affectedCount: number]> =>
90
+ export const disable = (id: string): Promise<any> =>
100
91
  CustomFieldDefinition.update(
101
92
  { disabled: true },
102
93
  { where: { id } },
103
94
  );
104
95
 
105
- export const destroy = (id: string): Promise<number> =>
96
+ export const destroy = (id: string): Promise<any> =>
106
97
  CustomFieldDefinition.destroy({ where: { id } });
107
98
 
108
99
  /**
@@ -67,6 +67,7 @@ export const updateValues = async (
67
67
  identifiers: string[],
68
68
  valuesToUpdate: ValuesToUpdate,
69
69
  options: FindOptions & { modelOptions?: ModelOptions } = {},
70
+ defineAllDefaults = false,
70
71
  ): Promise<CustomFieldValue[]> => {
71
72
  const names = Object.keys(valuesToUpdate);
72
73
  logger.debug(`custom-fields: updating values for ${modelType} ${modelId}`, {
@@ -80,10 +81,12 @@ export const updateValues = async (
80
81
  const where: WhereOptions = {
81
82
  modelType,
82
83
  name: names,
83
- ...(!options.modelOptions?.useEntityIdFromInclude && { entityId: identifiers }),
84
84
  };
85
85
 
86
- const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) ?? [];
86
+ if (!options.modelOptions?.useEntityIdFromInclude) {
87
+ where.entityId = identifiers;
88
+ }
89
+ const fieldDefinitions = await DefinitionRepo.findAll(where, { withDisabled: true, transaction, include: modelOptions.include?.(identifiers) }) || [];
87
90
 
88
91
  const disabledDefinitions = fieldDefinitions.filter((def) => def.disabled);
89
92
  if (fieldDefinitions.length !== names.length) {
@@ -98,18 +101,31 @@ export const updateValues = async (
98
101
  logger.warn(`custom-fields: trying to update disabled values: ${valuesWithDisabledDefinitions.join(', ')}`);
99
102
  }
100
103
 
104
+ const visitedFields = new Set<CustomFieldDefinition>();
105
+
101
106
  const values: CreateCustomFieldValue[] = names.map((name) => {
102
107
  const fieldDefinition = fieldDefinitions.find((def) => def.name === name);
108
+ visitedFields.add(fieldDefinition);
103
109
  const formatFunction = formatFunctions[fieldDefinition.fieldType];
104
- const value = formatFunction ? formatFunction(valuesToUpdate[name]) : valuesToUpdate[name];
105
110
  return {
106
111
  modelId,
112
+ value: (formatFunction ? formatFunction(valuesToUpdate[name]) : valuesToUpdate[name]) ?? fieldDefinition.defaultValue,
107
113
  updatedAt: new Date(),
108
114
  customFieldDefinitionId: fieldDefinition.id,
109
- value: value !== undefined ? value : fieldDefinition.defaultValue,
110
115
  };
111
116
  });
112
117
 
118
+ if (defineAllDefaults) {
119
+ fieldDefinitions.filter((def) => !visitedFields.has(def) && ![null, undefined].includes(def.defaultValue)).forEach(({ id, defaultValue }) => {
120
+ values.push({
121
+ modelId,
122
+ value: defaultValue,
123
+ updatedAt: new Date(),
124
+ customFieldDefinitionId: id,
125
+ });
126
+ });
127
+ }
128
+
113
129
  return Promise.all(values.map(async (value) => {
114
130
  const [cfv] = await CustomFieldValue.upsert(value, {
115
131
  transaction: options.transaction,
@@ -1,8 +1,11 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
- import { type WhereOptions, Op, type BindOrReplacements } from 'sequelize';
2
+ import {
3
+ type WhereOptions, Op, type IncludeOptions,
4
+ } from 'sequelize';
3
5
  import { type ModelStatic, Sequelize } from 'sequelize-typescript';
4
6
  import { randomInt } from 'node:crypto';
5
7
  import { CustomFieldDefinitionType } from '../constants';
8
+ import { CustomFieldDefinition, CustomFieldValue } from '../../models';
6
9
 
7
10
  /**
8
11
  * Builds a WHERE clause and replacements for free-text search by custom fields.
@@ -17,13 +20,13 @@ import { CustomFieldDefinitionType } from '../constants';
17
20
  * @param {CustomFieldDefinitionType[]} excludedCustomFieldsTypes - An array of custom field types
18
21
  * to exclude from the search
19
22
  *
20
- * @returns {CustomFieldsSearchPayload} - An object containing the WHERE clause and replacements
23
+ * @returns {CustomFieldsSearchPayload} - An object containing the INCLUDE clause and WHERE clause
21
24
  * for Sequelize.
22
25
  */
23
26
 
24
27
  interface CustomFieldsSearchPayload {
25
28
  where: WhereOptions;
26
- replacements: BindOrReplacements;
29
+ include: IncludeOptions;
27
30
  }
28
31
 
29
32
  export const generateRandomString = (length = 5): string => {
@@ -39,28 +42,27 @@ export const generateCustomFieldSearchQueryPayload = (
39
42
  CustomFieldDefinitionType.DATETIME,
40
43
  CustomFieldDefinitionType.DATE,
41
44
  ],
42
- ): CustomFieldsSearchPayload => {
43
- const excludedTypesString = customFieldsTypesToExclude.map((type) => `'${type}'`).join(',');
44
-
45
- const subQuery = 'EXISTS ('
46
- + ' SELECT 1'
47
- + ' FROM "custom_field_values" AS "cv"'
48
- + ' INNER JOIN custom_field_definitions AS cd '
49
- + ` ON cd.entity_id = '${entityId}'`
50
- + ' AND cv.custom_field_definition_id = cd.id'
51
- + ` AND cd.model_type = '${model.name}'`
52
- + ` ${excludedTypesString ? `AND cd.field_type NOT IN (${excludedTypesString})` : ''}`
53
- + ' WHERE'
54
- + ' "cv"."deleted_at" IS NULL'
55
- + ` AND "cv"."model_id" = "${model.name}"."id"`
56
- + ' AND CAST("cv"."value" AS TEXT) ILIKE :searchTerm)';
57
-
58
- return {
45
+ ): CustomFieldsSearchPayload => ({
46
+ include: {
47
+ model: CustomFieldValue,
48
+ as: 'cff',
49
+ required: false,
50
+ include: [
51
+ {
52
+ model: CustomFieldDefinition,
53
+ attributes: [],
54
+ required: true,
55
+ where: {
56
+ entityId,
57
+ modelType: model,
58
+ fieldType: { [Op.notIn]: customFieldsTypesToExclude },
59
+ },
60
+ },
61
+ ],
59
62
  where: {
60
- [Op.or]: [
61
- Sequelize.where(Sequelize.literal(subQuery), true),
62
- ],
63
+ deletedAt: null,
64
+ value: { [Op.iLike]: `%${searchTerm}%` },
63
65
  },
64
- replacements: { searchTerm: `%${searchTerm}%` },
65
- };
66
- };
66
+ },
67
+ where: Sequelize.where(Sequelize.col('cff.model_id'), { [Op.not]: null }),
68
+ });