@autofleet/sadot 1.0.0-beta.1 → 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.
Files changed (135) hide show
  1. package/README.md +5 -0
  2. package/dist/api/index.d.ts +2 -1
  3. package/dist/api/index.js +3 -2
  4. package/dist/api/v1/definition/index.d.ts +2 -1
  5. package/dist/api/v1/definition/index.js +12 -14
  6. package/dist/api/v1/definition/validations.js +14 -12
  7. package/dist/api/v1/errors.d.ts +3 -1
  8. package/dist/api/v1/errors.js +1 -4
  9. package/dist/api/v1/index.d.ts +2 -1
  10. package/dist/api/v1/index.js +5 -2
  11. package/dist/api/v1/validator/index.d.ts +3 -0
  12. package/dist/api/v1/validator/index.js +143 -0
  13. package/dist/api/v1/validator/validations.d.ts +6 -0
  14. package/dist/api/v1/validator/validations.js +40 -0
  15. package/dist/errors/index.d.ts +9 -1
  16. package/dist/errors/index.js +25 -4
  17. package/dist/events/index.d.ts +2 -1
  18. package/dist/events/index.js +17 -11
  19. package/dist/hooks/create.d.ts +2 -2
  20. package/dist/hooks/create.js +40 -19
  21. package/dist/hooks/enrich.d.ts +20 -2
  22. package/dist/hooks/enrich.js +88 -16
  23. package/dist/hooks/hooks.d.ts +17 -0
  24. package/dist/hooks/hooks.js +379 -0
  25. package/dist/hooks/index.d.ts +2 -3
  26. package/dist/hooks/index.js +6 -7
  27. package/dist/hooks/update.d.ts +2 -2
  28. package/dist/hooks/update.js +16 -26
  29. package/dist/hooks/utils/updateInstanceValues.d.ts +15 -0
  30. package/dist/hooks/utils/updateInstanceValues.js +50 -0
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +19 -6
  33. package/dist/models/CustomFieldDefinition.d.ts +1 -0
  34. package/dist/models/CustomFieldDefinition.js +10 -2
  35. package/dist/models/CustomFieldEntries.d.ts +15 -0
  36. package/dist/models/CustomFieldEntries.js +123 -0
  37. package/dist/models/CustomFieldValue.d.ts +3 -2
  38. package/dist/models/CustomFieldValue.js +22 -14
  39. package/dist/models/CustomValidator.d.ts +17 -0
  40. package/dist/models/CustomValidator.js +98 -0
  41. package/dist/models/index.d.ts +10 -2
  42. package/dist/models/index.js +60 -22
  43. package/dist/repository/definition.d.ts +23 -7
  44. package/dist/repository/definition.js +36 -7
  45. package/dist/repository/entries.d.ts +13 -0
  46. package/dist/repository/entries.js +92 -0
  47. package/dist/repository/utils/formatValues.d.ts +3 -0
  48. package/dist/repository/utils/formatValues.js +16 -0
  49. package/dist/repository/validator.d.ts +21 -0
  50. package/dist/repository/validator.js +62 -0
  51. package/dist/repository/value.d.ts +1 -1
  52. package/dist/repository/value.js +7 -32
  53. package/dist/scopes/filter.d.ts +5 -22
  54. package/dist/scopes/filter.js +18 -65
  55. package/dist/scopes/helpers/filter.helpers.d.ts +42 -0
  56. package/dist/scopes/helpers/filter.helpers.js +204 -0
  57. package/dist/tests/api/test-api.js +6 -24
  58. package/dist/tests/helpers/commonHooks.d.ts +6 -0
  59. package/dist/tests/helpers/commonHooks.js +62 -0
  60. package/dist/tests/helpers/database-config.js +1 -1
  61. package/dist/tests/helpers/index.d.ts +6 -1
  62. package/dist/tests/helpers/index.js +15 -2
  63. package/dist/tests/mocks/definition.mock.d.ts +5 -2
  64. package/dist/tests/mocks/definition.mock.js +10 -1
  65. package/dist/tests/mocks/events.mock.d.ts +1 -0
  66. package/dist/tests/mocks/events.mock.js +4 -2
  67. package/dist/types/definition/index.d.ts +1 -0
  68. package/dist/types/entries/index.d.ts +25 -0
  69. package/dist/types/entries/index.js +2 -0
  70. package/dist/types/index.d.ts +19 -3
  71. package/dist/utils/constants/index.d.ts +1 -1
  72. package/dist/utils/helpers/index.d.ts +4 -3
  73. package/dist/utils/helpers/index.js +22 -30
  74. package/dist/utils/init.d.ts +5 -3
  75. package/dist/utils/init.js +13 -11
  76. package/dist/utils/logger/index.d.ts +1 -0
  77. package/dist/utils/logger/index.js +34 -0
  78. package/dist/utils/validations/index.d.ts +7 -1
  79. package/dist/utils/validations/index.js +28 -7
  80. package/dist/utils/validations/schema/validator-schema.d.ts +9 -0
  81. package/dist/utils/validations/schema/validator-schema.js +95 -0
  82. package/dist/utils/validations/type.d.ts +2 -1
  83. package/dist/utils/validations/validators/index.js +9 -9
  84. package/dist/utils/validations/validators/select.validator.js +5 -2
  85. package/dist/utils/validations/validators/status.validator.js +8 -2
  86. package/package.json +28 -12
  87. package/src/api/index.ts +3 -2
  88. package/src/api/v1/definition/index.ts +20 -23
  89. package/src/api/v1/definition/validations.ts +16 -16
  90. package/src/api/v1/errors.ts +4 -7
  91. package/src/api/v1/index.ts +5 -3
  92. package/src/api/v1/validator/index.ts +141 -0
  93. package/src/api/v1/validator/validations.ts +39 -0
  94. package/src/errors/index.ts +31 -3
  95. package/src/events/index.ts +25 -13
  96. package/src/hooks/create.ts +50 -28
  97. package/src/hooks/enrich.ts +137 -28
  98. package/src/hooks/hooks.ts +467 -0
  99. package/src/hooks/index.ts +10 -5
  100. package/src/hooks/update.ts +20 -7
  101. package/src/hooks/utils/updateInstanceValues.ts +63 -0
  102. package/src/index.ts +10 -8
  103. package/src/models/CustomFieldDefinition.ts +9 -2
  104. package/src/models/CustomFieldEntries.ts +81 -0
  105. package/src/models/CustomFieldValue.ts +25 -17
  106. package/src/models/CustomValidator.ts +78 -0
  107. package/src/models/index.ts +83 -25
  108. package/src/repository/definition.ts +62 -14
  109. package/src/repository/entries.ts +88 -0
  110. package/src/repository/utils/formatValues.ts +14 -0
  111. package/src/repository/validator.ts +104 -0
  112. package/src/repository/value.ts +5 -35
  113. package/src/scopes/filter.ts +33 -106
  114. package/src/scopes/helpers/filter.helpers.ts +227 -0
  115. package/src/tests/api/test-api.ts +4 -2
  116. package/src/tests/helpers/commonHooks.ts +43 -0
  117. package/src/tests/helpers/database-config.ts +1 -1
  118. package/src/tests/helpers/index.ts +18 -2
  119. package/src/tests/mocks/definition.mock.ts +18 -9
  120. package/src/tests/mocks/events.mock.ts +4 -1
  121. package/src/types/definition/index.ts +1 -0
  122. package/src/types/entries/index.ts +27 -0
  123. package/src/types/index.ts +20 -3
  124. package/src/utils/helpers/index.ts +28 -35
  125. package/src/utils/init.ts +17 -12
  126. package/src/utils/logger/index.ts +9 -0
  127. package/src/utils/validations/index.ts +30 -6
  128. package/src/utils/validations/schema/README.md +93 -0
  129. package/src/utils/validations/schema/validator-schema.ts +106 -0
  130. package/src/utils/validations/type.ts +2 -1
  131. package/src/utils/validations/validators/index.ts +9 -9
  132. package/src/utils/validations/validators/select.validator.ts +3 -2
  133. package/src/utils/validations/validators/status.validator.ts +6 -2
  134. package/tsconfig.build.json +7 -0
  135. package/tsconfig.json +1 -1
@@ -1,6 +1,15 @@
1
- import type { IncludeOptions } from 'sequelize';
2
- import type { ModelCtor } from 'sequelize-typescript';
1
+ import type { IncludeOptions, Transaction } from 'sequelize';
2
+ import type { ModelCtor, Sequelize } from 'sequelize-typescript';
3
+ import type { getUser as GetUserType } from '@autofleet/zehut';
4
+ import type CustomFieldDefinition from '../models/CustomFieldDefinition';
5
+ import type CustomValidator from '../models/CustomValidator';
3
6
  export type ModelFetcher = (name: string) => any;
7
+ export interface TransactionOptions extends Record<string, any> {
8
+ transaction?: Transaction & {
9
+ definitionCache?: Map<string, CustomFieldDefinition[]>;
10
+ validationsCache?: Map<string, CustomValidator[]>;
11
+ };
12
+ }
4
13
  export type ModelOptions = {
5
14
  /**
6
15
  * Include options for the model
@@ -15,6 +24,10 @@ export type ModelOptions = {
15
24
  * Whether to use the entity id from the instance per scope attribute
16
25
  */
17
26
  useEntityIdFromInclude?: boolean;
27
+ /**
28
+ * Which attributes to include in the model
29
+ */
30
+ attributes?: string[];
18
31
  };
19
32
  export type Models = {
20
33
  name: string;
@@ -27,5 +40,8 @@ export type Models = {
27
40
  export type CustomFieldOptions = {
28
41
  models: Models[];
29
42
  databaseConfig: any;
30
- getUser: () => any;
43
+ getUser: typeof GetUserType;
44
+ sequelize?: Sequelize;
45
+ useCustomFieldsEntries?: boolean;
46
+ useValidators?: boolean;
31
47
  };
@@ -1,4 +1,4 @@
1
- declare const CUSTOM_FIELDS_FILTER_SCOPE: string;
1
+ declare const CUSTOM_FIELDS_FILTER_SCOPE: "filterByCustomFields";
2
2
  export declare const supportedEntities: string[];
3
3
  /**
4
4
  * Supported custom field types
@@ -1,4 +1,4 @@
1
- import { type WhereOptions, type IncludeOptions } from 'sequelize';
1
+ import { type WhereOptions, type BindOrReplacements } from 'sequelize';
2
2
  import { type ModelStatic } from 'sequelize-typescript';
3
3
  import { CustomFieldDefinitionType } from '../constants';
4
4
  /**
@@ -14,11 +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 INCLUDE clause and WHERE clause to add to query payload
17
+ * @returns {CustomFieldsSearchPayload} - An object containing the WHERE clause and replacements
18
+ * for Sequelize.
18
19
  */
19
20
  interface CustomFieldsSearchPayload {
20
21
  where: WhereOptions;
21
- include: IncludeOptions[];
22
+ replacements: BindOrReplacements;
22
23
  }
23
24
  export declare const generateRandomString: (length?: number) => string;
24
25
  export declare const generateCustomFieldSearchQueryPayload: (searchTerm: string, model: ModelStatic, entityId: string, customFieldsTypesToExclude?: CustomFieldDefinitionType[]) => CustomFieldsSearchPayload;
@@ -6,7 +6,6 @@ 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");
10
9
  const generateRandomString = (length = 5) => {
11
10
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
12
11
  return Array.from({ length }, () => characters.charAt((0, node_crypto_1.randomInt)(characters.length))).join('');
@@ -15,34 +14,27 @@ exports.generateRandomString = generateRandomString;
15
14
  const generateCustomFieldSearchQueryPayload = (searchTerm, model, entityId, customFieldsTypesToExclude = [
16
15
  constants_1.CustomFieldDefinitionType.DATETIME,
17
16
  constants_1.CustomFieldDefinitionType.DATE,
18
- ]) => ({
19
- include: [{
20
- attributes: ['value', 'model_id'],
21
- model: models_1.CustomFieldValue,
22
- as: 'customFieldValue',
23
- required: false,
24
- include: [
25
- {
26
- model: models_1.CustomFieldDefinition,
27
- as: 'customFieldDefinition',
28
- attributes: ['entity_id', 'field_type', 'model_type'],
29
- required: true,
30
- on: {
31
- id: sequelize_typescript_1.Sequelize.where(sequelize_typescript_1.Sequelize.col('id'), { [sequelize_1.Op.eq]: sequelize_typescript_1.Sequelize.col('customFieldValue.custom_field_definition_id') }),
32
- entityId: sequelize_typescript_1.Sequelize.where(sequelize_typescript_1.Sequelize.col('entity_id'), { [sequelize_1.Op.eq]: `${entityId}` }),
33
- modelType: sequelize_typescript_1.Sequelize.where(sequelize_typescript_1.Sequelize.col('model_type'), { [sequelize_1.Op.eq]: `${model.name}` }),
34
- fieldType: sequelize_typescript_1.Sequelize.where(sequelize_typescript_1.Sequelize.col('field_type'), { [sequelize_1.Op.notIn]: customFieldsTypesToExclude }),
35
- },
36
- where: {
37
- deleted_at: null,
38
- },
39
- },
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 {
32
+ where: {
33
+ [sequelize_1.Op.or]: [
34
+ sequelize_typescript_1.Sequelize.where(sequelize_typescript_1.Sequelize.literal(subQuery), true),
40
35
  ],
41
- on: {
42
- value: sequelize_typescript_1.Sequelize.where(sequelize_typescript_1.Sequelize.cast(sequelize_typescript_1.Sequelize.col('value'), 'text'), { [sequelize_1.Op.iLike]: `%${searchTerm}%` }),
43
- model_id: sequelize_typescript_1.Sequelize.where(sequelize_typescript_1.Sequelize.col('model_id'), { [sequelize_1.Op.eq]: sequelize_typescript_1.Sequelize.col(`${model.name}.id`) }),
44
- },
45
- }],
46
- where: sequelize_typescript_1.Sequelize.where(sequelize_typescript_1.Sequelize.col('customFieldValue.model_id'), { [sequelize_1.Op.not]: null }),
47
- });
36
+ },
37
+ replacements: { searchTerm: `%${searchTerm}%` },
38
+ };
39
+ };
48
40
  exports.generateCustomFieldSearchQueryPayload = generateCustomFieldSearchQueryPayload;
@@ -1,5 +1,7 @@
1
- import type { ModelFetcher, Models } from '../types';
2
- export declare const addHooks: (models: Models[], getModel: ModelFetcher) => void;
1
+ import type { CustomFieldOptions, ModelFetcher, Models } from '../types';
2
+ export declare const addHooks: (models: Models[], getModel: ModelFetcher, sadotOptions?: {
3
+ useCustomFieldsEntries: boolean;
4
+ }) => void;
3
5
  export declare const removeHooks: (models: Models[], getModel: ModelFetcher) => void;
4
- export declare const addScopes: (models: Models[], getModel: ModelFetcher) => void;
6
+ export declare const addScopes: (models: Models[], getModel: ModelFetcher, options?: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>) => void;
5
7
  export declare const applyCustomAssociation: (models: Models[]) => void;
@@ -13,7 +13,7 @@ const scopes_1 = require("../scopes");
13
13
  const logger_1 = __importDefault(require("./logger"));
14
14
  const filter_1 = require("../scopes/filter");
15
15
  const { CUSTOM_FIELDS_FILTER_SCOPE } = common_types_1.customFields;
16
- const addHooks = (models, getModel) => {
16
+ const addHooks = (models, getModel, sadotOptions = { useCustomFieldsEntries: false }) => {
17
17
  models.forEach(async ({ name, scopeAttributes, modelOptions, }) => {
18
18
  try {
19
19
  const model = getModel(name);
@@ -33,11 +33,11 @@ const addHooks = (models, getModel) => {
33
33
  model.addHook('beforeFind', 'sadot-beforeFind', (0, hooks_1.beforeFind)(scopeAttributes));
34
34
  model.addHook('beforeBulkCreate', 'sadot-beforeBulkCreate', hooks_1.beforeBulkCreate);
35
35
  model.addHook('beforeBulkUpdate', 'sadot-beforeBulkUpdate', hooks_1.beforeBulkUpdate);
36
- model.addHook('beforeCreate', 'sadot-beforeCreate', (0, hooks_1.beforeCreate)(scopeAttributes, modelOptions));
37
- model.addHook('beforeUpdate', 'sadot-beforeUpdate', (0, hooks_1.beforeUpdate)(scopeAttributes, modelOptions));
38
- model.addHook('afterFind', 'sadot-afterFind', (0, hooks_1.enrichResults)(name, scopeAttributes, 'afterFind', modelOptions));
39
- model.addHook('afterUpdate', 'sadot-afterUpdate', (0, hooks_1.enrichResults)(name, scopeAttributes, null, modelOptions));
40
- model.addHook('afterCreate', 'sadot-afterCreate', (0, hooks_1.enrichResults)(name, scopeAttributes, null, modelOptions));
36
+ model.addHook('beforeCreate', 'sadot-beforeCreate', (0, hooks_1.beforeCreate)(scopeAttributes, modelOptions, sadotOptions));
37
+ model.addHook('beforeUpdate', 'sadot-beforeUpdate', (0, hooks_1.beforeUpdate)(scopeAttributes, modelOptions, sadotOptions));
38
+ model.addHook('afterFind', 'sadot-afterFind', (0, hooks_1.enrichResults)(name, scopeAttributes, 'afterFind', modelOptions, sadotOptions));
39
+ model.addHook('afterUpdate', 'sadot-afterUpdate', (0, hooks_1.enrichResults)(name, scopeAttributes, null, modelOptions, sadotOptions));
40
+ model.addHook('afterCreate', 'sadot-afterCreate', (0, hooks_1.enrichResults)(name, scopeAttributes, null, modelOptions, sadotOptions));
41
41
  }
42
42
  catch (e) {
43
43
  logger_1.default.error(`Could not add custom fields hook to model ${name}. `, e);
@@ -73,12 +73,14 @@ exports.removeHooks = removeHooks;
73
73
  /**
74
74
  * Necessary associations for the {@link customFieldsFilterScope} scope
75
75
  */
76
- const addAssociations = (model, modelName) => {
76
+ const addAssociations = (model, modelName, options) => {
77
+ if (options?.useCustomFieldsEntries)
78
+ return;
77
79
  model.hasMany(models_1.CustomFieldValue, { foreignKey: 'modelId', as: 'customFieldValue' });
78
80
  // TBC: maybe can be removed
79
81
  models_1.CustomFieldValue.belongsTo(model, { foreignKey: 'modelId', as: modelName });
80
82
  };
81
- const addScopes = (models, getModel) => {
83
+ const addScopes = (models, getModel, options = { useCustomFieldsEntries: false }) => {
82
84
  models.forEach(async ({ name, scopeAttributes }) => {
83
85
  try {
84
86
  const model = getModel(name);
@@ -90,10 +92,10 @@ const addScopes = (models, getModel) => {
90
92
  return;
91
93
  }
92
94
  // Necessary associations for the filtering scope
93
- addAssociations(model, name);
95
+ addAssociations(model, name, options);
94
96
  // Add filter scope
95
- model.addScope(CUSTOM_FIELDS_FILTER_SCOPE, (0, scopes_1.customFieldsFilterScope)(name));
96
- model.addScope(custom_fields_1.CUSTOM_FIELDS_SORT_SCOPE, (0, filter_1.customFieldsSortScope)(name));
97
+ model.addScope(CUSTOM_FIELDS_FILTER_SCOPE, (0, scopes_1.customFieldsFilterScope)(name, options));
98
+ model.addScope(custom_fields_1.CUSTOM_FIELDS_SORT_SCOPE, (0, filter_1.customFieldsSortScope)(name, options));
97
99
  }
98
100
  catch (e) {
99
101
  logger_1.default.error(`Could not add custom fields scopes to model ${name}. `, e);
@@ -1,2 +1,3 @@
1
1
  declare const logger: import("@autofleet/logger").LoggerInstanceManager;
2
+ export declare function tryAddingTraceIdMiddleware(): Promise<void>;
2
3
  export default logger;
@@ -1,8 +1,42 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.tryAddingTraceIdMiddleware = void 0;
6
30
  const logger_1 = __importDefault(require("@autofleet/logger"));
7
31
  const logger = (0, logger_1.default)();
32
+ async function tryAddingTraceIdMiddleware() {
33
+ try {
34
+ const { outbreak } = await Promise.resolve().then(() => __importStar(require('@autofleet/zehut')));
35
+ logger.addContextMiddleware(() => ({ traceId: outbreak.getCurrentContextTraceId() }));
36
+ }
37
+ catch (err) {
38
+ logger.error('Failed to add traceId middleware', { err });
39
+ }
40
+ }
41
+ exports.tryAddingTraceIdMiddleware = tryAddingTraceIdMiddleware;
8
42
  exports.default = logger;
@@ -1,2 +1,8 @@
1
+ import type { CustomFieldDefinition } from '../../models';
2
+ import type { CustomFieldEntriesDTO } from '../../types/entries';
1
3
  import type { CustomFieldDefinitionType } from '../constants';
2
- export declare const validateValue: (value: unknown, valueType: CustomFieldDefinitionType, validation?: unknown) => boolean;
4
+ export declare const validateFieldType: (type: CustomFieldDefinitionType) => boolean;
5
+ export declare const validateValue: (value: unknown, valueType: CustomFieldDefinitionType, validation?: unknown) => import("joi").ValidationResult;
6
+ export declare const validateInstanceCustomFieldEntries: (instance: CustomFieldEntriesDTO, definitionsByName: {
7
+ [defName: string]: CustomFieldDefinition;
8
+ }) => void;
@@ -1,15 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateValue = void 0;
3
+ exports.validateInstanceCustomFieldEntries = exports.validateValue = exports.validateFieldType = void 0;
4
+ const errors_1 = require("../../errors");
4
5
  const validators_1 = require("./validators");
6
+ const validateFieldType = (type) => Object.keys(validators_1.validators).includes(type);
7
+ exports.validateFieldType = validateFieldType;
5
8
  const validateValue = (value, valueType, validation) => {
6
9
  const validator = validators_1.validators[valueType];
7
- if (!validator) {
8
- // Unsupported field type
9
- return false;
10
- }
11
- // Always allow null values
12
- return value === null || validator(value, validation);
10
+ return validator(value, validation);
13
11
  /** TODO: Add validation for required fields
14
12
  * @example
15
13
  * if (validations.required && !value) {
@@ -18,3 +16,26 @@ const validateValue = (value, valueType, validation) => {
18
16
  */
19
17
  };
20
18
  exports.validateValue = validateValue;
19
+ const validateInstanceCustomFieldEntries = (instance, definitionsByName) => {
20
+ const validationErrors = Object.entries(instance.customFields)
21
+ .map(([customFieldName, value]) => {
22
+ // Allow NULL values, just like we do in custom_field_values.
23
+ if (value === null)
24
+ return null;
25
+ const { validation, fieldType } = definitionsByName[customFieldName];
26
+ const result = (0, exports.validateValue)(value, fieldType, validation);
27
+ if (result?.error) {
28
+ return {
29
+ joiValidationError: result.error,
30
+ fieldDefinitionName: customFieldName,
31
+ value,
32
+ };
33
+ }
34
+ return null;
35
+ })
36
+ .filter((result) => !!result);
37
+ if (validationErrors?.length) {
38
+ throw new errors_1.InvalidEntriesError(instance.modelId, validationErrors);
39
+ }
40
+ };
41
+ exports.validateInstanceCustomFieldEntries = validateInstanceCustomFieldEntries;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Validates that a given schema is a valid Ajv schema
3
+ * This function is used to validate schemas passed to custom validators
4
+ *
5
+ * @param schema The schema to validate
6
+ * @returns true if valid, throws an error if invalid
7
+ */
8
+ export declare const validateValidatorSchema: (schema: Record<string, unknown>) => boolean;
9
+ export default validateValidatorSchema;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validateValidatorSchema = void 0;
7
+ const ajv_1 = __importDefault(require("ajv"));
8
+ const ajv_formats_1 = __importDefault(require("ajv-formats"));
9
+ const errors_1 = require("@autofleet/errors");
10
+ const logger_1 = __importDefault(require("../../logger"));
11
+ // Instantiate Ajv for meta-validation
12
+ const metaValidator = new ajv_1.default({
13
+ allErrors: true,
14
+ strict: false,
15
+ strictTypes: false,
16
+ $data: true, // Enable $data references
17
+ });
18
+ (0, ajv_formats_1.default)(metaValidator);
19
+ /**
20
+ * Schema for validating JSON Schema objects in custom validators
21
+ * This is a meta-schema to ensure that custom validator schemas are valid Ajv schemas
22
+ */
23
+ const validatorMetaSchema = {
24
+ type: 'object',
25
+ properties: {
26
+ type: { type: 'string', enum: ['object'] },
27
+ properties: {
28
+ type: 'object',
29
+ properties: {
30
+ before: {
31
+ type: 'object',
32
+ properties: {
33
+ type: { type: 'string', enum: ['object'] },
34
+ properties: { type: 'object' },
35
+ },
36
+ },
37
+ after: {
38
+ type: 'object',
39
+ properties: {
40
+ type: { type: 'string', enum: ['object'] },
41
+ properties: { type: 'object' },
42
+ },
43
+ },
44
+ },
45
+ },
46
+ required: {
47
+ type: 'array',
48
+ items: { type: 'string' },
49
+ },
50
+ if: { type: 'object' },
51
+ then: { type: 'object' },
52
+ else: { type: 'object' },
53
+ },
54
+ required: ['type', 'properties'],
55
+ };
56
+ /**
57
+ * Validates that a given schema is a valid Ajv schema
58
+ * This function is used to validate schemas passed to custom validators
59
+ *
60
+ * @param schema The schema to validate
61
+ * @returns true if valid, throws an error if invalid
62
+ */
63
+ const validateValidatorSchema = (schema) => {
64
+ try {
65
+ // First validate the schema structure
66
+ const validateMetaSchema = metaValidator.compile(validatorMetaSchema);
67
+ const isValidStructure = validateMetaSchema(schema);
68
+ if (!isValidStructure) {
69
+ const errorDetails = validateMetaSchema.errors?.map((err) => `${err.instancePath || ''} ${err.message || 'Invalid schema structure'}`).join(', ');
70
+ logger_1.default.error('Invalid validator schema structure', {
71
+ errors: validateMetaSchema.errors,
72
+ schema,
73
+ });
74
+ throw new errors_1.BadRequest([new Error(`Invalid validator schema structure: ${errorDetails}`)], ['Invalid validator schema structure']);
75
+ }
76
+ // Then try to compile the schema with Ajv to verify it's a valid JSON Schema
77
+ try {
78
+ metaValidator.compile(schema);
79
+ return true;
80
+ }
81
+ catch (compileError) {
82
+ logger_1.default.error('Failed to compile validator schema', { error: compileError, schema });
83
+ throw new errors_1.BadRequest([new Error(`Failed to compile validator schema: ${compileError.message}`)], ['Invalid validator schema']);
84
+ }
85
+ }
86
+ catch (error) {
87
+ if (error instanceof errors_1.BadRequest) {
88
+ throw error;
89
+ }
90
+ logger_1.default.error('Error validating validator schema', { error, schema });
91
+ throw new errors_1.BadRequest([new Error(`Error validating validator schema: ${error.message}`)], ['Invalid validator schema']);
92
+ }
93
+ };
94
+ exports.validateValidatorSchema = validateValidatorSchema;
95
+ exports.default = exports.validateValidatorSchema;
@@ -1,10 +1,11 @@
1
+ import type { ValidationResult } from 'joi';
1
2
  import type { CustomFieldDefinitionType } from '../constants';
2
3
  /**
3
4
  * Validator is a function that validates a custom-field `value`,
4
5
  * against a custom-field definition `validation object`.
5
6
  * @returns `true` if the value is valid, `false` otherwise.
6
7
  */
7
- export type Validator<Value, DefinitionValidationObject> = (value: Value, validation?: DefinitionValidationObject) => boolean;
8
+ export type Validator<Value, DefinitionValidationObject> = (value: Value, validation?: DefinitionValidationObject) => ValidationResult;
8
9
  /**
9
10
  * Validators is a map of custom-field types to their respective validators.
10
11
  * The key is the custom-field type, and the value is the validator function.
@@ -22,19 +22,19 @@ exports.CustomValidationTypes = {
22
22
  exports.validators = {
23
23
  [constants_1.CustomFieldDefinitionType.SELECT]: select_validator_1.validateSelect,
24
24
  [constants_1.CustomFieldDefinitionType.STATUS]: status_validator_1.validateStatus,
25
- [constants_1.CustomFieldDefinitionType.TEXT]: (value) => (typeof value === 'string'),
26
- [constants_1.CustomFieldDefinitionType.NUMBER]: (value) => (typeof value === 'number'),
27
- [constants_1.CustomFieldDefinitionType.BOOLEAN]: (value) => (typeof value === 'boolean'),
28
- [constants_1.CustomFieldDefinitionType.DATE]: (value) => (!joi_1.default.date().validate(value).error),
29
- [constants_1.CustomFieldDefinitionType.DATETIME]: (value) => (!joi_1.default.date().validate(value).error),
30
- [constants_1.CustomFieldDefinitionType.IMAGE]: (value) => (!joi_1.default.array().min(1).unique()
25
+ [constants_1.CustomFieldDefinitionType.TEXT]: (value) => joi_1.default.string().min(0).validate(value),
26
+ [constants_1.CustomFieldDefinitionType.NUMBER]: (value) => joi_1.default.number().strict(true).validate(value),
27
+ [constants_1.CustomFieldDefinitionType.BOOLEAN]: (value) => joi_1.default.boolean().strict().validate(value),
28
+ [constants_1.CustomFieldDefinitionType.DATE]: (value) => joi_1.default.date().validate(value),
29
+ [constants_1.CustomFieldDefinitionType.DATETIME]: (value) => joi_1.default.date().validate(value),
30
+ [constants_1.CustomFieldDefinitionType.IMAGE]: (value) => joi_1.default.array().min(1).unique()
31
31
  .items(joi_1.default.string().uri())
32
- .validate(value).error),
33
- [constants_1.CustomFieldDefinitionType.FILE]: (value) => (!joi_1.default.array().min(1).unique().items(joi_1.default.object({
32
+ .validate(value),
33
+ [constants_1.CustomFieldDefinitionType.FILE]: (value) => joi_1.default.array().min(1).unique().items(joi_1.default.object({
34
34
  name: joi_1.default.string().required(),
35
35
  type: joi_1.default.string(),
36
36
  size: joi_1.default.string(),
37
37
  addedBy: joi_1.default.string().uuid(),
38
38
  }))
39
- .validate(value).error),
39
+ .validate(value),
40
40
  };
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.validateSelect = void 0;
7
+ const joi_1 = __importDefault(require("joi"));
4
8
  /**
5
9
  * Validate that the value is one of the select values
6
10
  */
7
- const validateSelect = (value, selectValues) => (Array.isArray(selectValues)
8
- && selectValues.includes(value));
11
+ const validateSelect = (value, selectValues) => (joi_1.default.string().allow(null).valid(...selectValues).validate(value));
9
12
  exports.validateSelect = validateSelect;
@@ -1,9 +1,15 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.validateStatus = void 0;
7
+ const joi_1 = __importDefault(require("joi"));
4
8
  /**
5
9
  * Validate that the value is one of the status values
6
10
  */
7
- const validateStatus = (value, statusValues) => (Array.isArray(statusValues)
8
- && statusValues.some((status) => status.value === value));
11
+ const validateStatus = (value, statusValues) => (joi_1.default.string()
12
+ .allow(null)
13
+ .valid(...statusValues.map((statusValue) => statusValue.value))
14
+ .validate(value));
9
15
  exports.validateStatus = validateStatus;
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@autofleet/sadot",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
7
  "start": "ts-node src/index.ts",
8
8
  "build": "rm -rf dist && tsc",
9
9
  "linter": "eslint .",
10
- "test": "jest --forceExit --runInBand",
11
- "coverage": "jest --coverage --forceExit --runInBand && rm -rf ./coverage",
10
+ "test": "jest --runInBand",
11
+ "test-debug": "node --inspect-brk node_modules/.bin/jest --testTimeout=10000000",
12
+ "coverage": "jest --coverage --runInBand",
12
13
  "build-to-local-repo": "node --run build && cp -r dist/* ../$REPO/node_modules/$npm_package_name/dist",
13
14
  "dev": "nodemon",
14
15
  "watch": "npm-watch build-to-local-repo",
@@ -27,11 +28,12 @@
27
28
  }
28
29
  },
29
30
  "dependencies": {
30
- "@autofleet/common-types": "^1.7.58",
31
- "@autofleet/errors": "^1.2.3",
32
- "@autofleet/events": "^2.0.0",
33
- "@autofleet/logger": "^4.1.0",
34
- "express": "^4.18.2",
31
+ "@autofleet/common-types": "^4.4.0",
32
+ "@autofleet/events": "^5.2.0",
33
+ "ajv": "^8.12.0",
34
+ "ajv-errors": "^3.0.0",
35
+ "ajv-formats": "^3.0.1",
36
+ "http-status-codes": "^2.3.0",
35
37
  "joi": "^17.7.0",
36
38
  "pg": "^8.10.0",
37
39
  "reflect-metadata": "^0.1.13",
@@ -39,6 +41,10 @@
39
41
  "sequelize-typescript": "^2.1.5"
40
42
  },
41
43
  "devDependencies": {
44
+ "@autofleet/errors": "^3.0.1",
45
+ "@autofleet/logger": "^4.2.1",
46
+ "@autofleet/node-common": "^4.0.2",
47
+ "@autofleet/zehut": "^4.0.1",
42
48
  "@types/express": "^4.17.17",
43
49
  "@types/jest": "^29.5.13",
44
50
  "@typescript-eslint/eslint-plugin": "^7.18.0",
@@ -46,18 +52,28 @@
46
52
  "eslint": "^8.57.0",
47
53
  "eslint-config-airbnb-base": "^15.0.0",
48
54
  "eslint-plugin-import": "^2.22.1",
55
+ "express": "^4.21.2",
49
56
  "jest": "^29.7.0",
50
57
  "npm-watch": "^0.11.0",
58
+ "supertest": "^7.0.0",
51
59
  "ts-jest": "^29.1.2",
52
60
  "ts-node": "^8.6.2",
53
- "typescript": "^5.3.3",
54
- "typescript-eslint": "^0.0.1-alpha.0"
61
+ "typescript": "^5.3.3"
55
62
  },
56
63
  "peerDependencies": {
57
- "@autofleet/sheilta": ">=1.4.0"
64
+ "@autofleet/errors": "^3",
65
+ "@autofleet/logger": "^4",
66
+ "@autofleet/node-common": "^4",
67
+ "@autofleet/sheilta": "^2",
68
+ "@autofleet/zehut": "^4"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "@autofleet/zehut": {
72
+ "optional": true
73
+ }
58
74
  },
59
75
  "engines": {
60
- "node": ">=16.0.0"
76
+ "node": ">=18.0.0"
61
77
  },
62
78
  "author": "Autofleet",
63
79
  "license": "ISC"
package/src/api/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  // export the api object
2
- import { Router } from 'express';
2
+ import { Router } from '@autofleet/node-common';
3
3
  import v1 from './v1';
4
+ import logger from '../utils/logger';
4
5
 
5
- const router = Router({ mergeParams: true });
6
+ const router = Router({ logger });
6
7
 
7
8
  router.use('/v1', v1);
8
9