diff --git a/api/src/attachment/controllers/attachment.controller.spec.ts b/api/src/attachment/controllers/attachment.controller.spec.ts index a0cf19e8..55103b72 100644 --- a/api/src/attachment/controllers/attachment.controller.spec.ts +++ b/api/src/attachment/controllers/attachment.controller.spec.ts @@ -27,7 +27,7 @@ import { import { attachment, attachmentFile } from '../mocks/attachment.mock'; import { AttachmentRepository } from '../repositories/attachment.repository'; -import { AttachmentModel, Attachment } from '../schemas/attachment.schema'; +import { Attachment, AttachmentModel } from '../schemas/attachment.schema'; import { AttachmentService } from '../services/attachment.service'; import { AttachmentController } from './attachment.controller'; @@ -35,7 +35,7 @@ import { AttachmentController } from './attachment.controller'; describe('AttachmentController', () => { let attachmentController: AttachmentController; let attachmentService: AttachmentService; - let attachmentToDelete: Attachment; + let attachmentToDelete: Attachment | null; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -79,7 +79,7 @@ describe('AttachmentController', () => { describe('Upload', () => { it('should throw BadRequestException if no file is selected to be uploaded', async () => { const promiseResult = attachmentController.uploadFile({ - file: undefined, + file: [], }); await expect(promiseResult).rejects.toThrow( new BadRequestException('No file was selected'), @@ -121,17 +121,17 @@ describe('AttachmentController', () => { name: 'store1.jpg', }); const result = await attachmentController.download({ - id: storedAttachment.id, + id: storedAttachment!.id, }); expect(attachmentService.findOne).toHaveBeenCalledWith( - storedAttachment.id, + storedAttachment!.id, ); - expect(result.options).toEqual({ - type: storedAttachment.type, - length: storedAttachment.size, + expect(result?.options).toEqual({ + type: storedAttachment!.type, + length: storedAttachment!.size, disposition: `attachment; filename="${encodeURIComponent( - storedAttachment.name, + storedAttachment!.name, )}"`, }); }); @@ -141,11 +141,11 @@ describe('AttachmentController', () => { it('should delete an attachment by id', async () => { jest.spyOn(attachmentService, 'deleteOne'); const result = await attachmentController.deleteOne( - attachmentToDelete.id, + attachmentToDelete!.id, ); expect(attachmentService.deleteOne).toHaveBeenCalledWith( - attachmentToDelete.id, + attachmentToDelete!.id, ); expect(result).toEqual({ acknowledged: true, @@ -155,10 +155,10 @@ describe('AttachmentController', () => { it('should throw a NotFoundException when attempting to delete an attachment by id', async () => { await expect( - attachmentController.deleteOne(attachmentToDelete.id), + attachmentController.deleteOne(attachmentToDelete!.id), ).rejects.toThrow( new NotFoundException( - `Attachment with ID ${attachmentToDelete.id} not found`, + `Attachment with ID ${attachmentToDelete!.id} not found`, ), ); }); diff --git a/api/src/attachment/controllers/attachment.controller.ts b/api/src/attachment/controllers/attachment.controller.ts index 3607903f..90964dea 100644 --- a/api/src/attachment/controllers/attachment.controller.ts +++ b/api/src/attachment/controllers/attachment.controller.ts @@ -146,7 +146,7 @@ export class AttachmentController extends BaseController { @Get('download/:id/:filename?') async download( @Param() params: AttachmentDownloadDto, - ): Promise { + ): Promise { const attachment = await this.attachmentService.findOne(params.id); if (!attachment) { diff --git a/api/src/extensions/helpers/llm-nlu/index.helper.ts b/api/src/extensions/helpers/llm-nlu/index.helper.ts index 814f24cf..8591d954 100644 --- a/api/src/extensions/helpers/llm-nlu/index.helper.ts +++ b/api/src/extensions/helpers/llm-nlu/index.helper.ts @@ -100,32 +100,31 @@ export default class LlmNluHelper * * @returns An array of objects representing the found entities, with their `value`, `start`, and `end` positions. */ - private findKeywordEntities( - text: string, - entity: NlpEntityFull, - ): NLU.ParseEntity[] { - return entity.values - .flatMap(({ value, expressions }) => { - const allValues = [value, ...expressions]; + private findKeywordEntities(text: string, entity: NlpEntityFull) { + return ( + entity.values + .flatMap(({ value, expressions }) => { + const allValues = [value, ...expressions]; - // Filter the terms that are found in the text - return allValues - .flatMap((term) => { - const regex = new RegExp(`\\b${term}\\b`, 'g'); - const matches = [...text.matchAll(regex)]; + // Filter the terms that are found in the text + return allValues + .flatMap((term) => { + const regex = new RegExp(`\\b${term}\\b`, 'g'); + const matches = [...text.matchAll(regex)]; - // Map matches to FoundEntity format - return matches.map((match) => ({ - entity: entity.name, - value: term, - start: match.index!, - end: match.index! + term.length, - confidence: 1, - })); - }) - .shift(); - }) - .filter((v) => !!v); + // Map matches to FoundEntity format + return matches.map((match) => ({ + entity: entity.name, + value: term, + start: match.index!, + end: match.index! + term.length, + confidence: 1, + })); + }) + .shift(); + }) + .filter((v) => !!v) || [] + ); } async predict(text: string): Promise { @@ -133,7 +132,7 @@ export default class LlmNluHelper const helper = await this.helperService.getDefaultLlmHelper(); const defaultLanguage = await this.languageService.getDefaultLanguage(); // Detect language - const language = await helper.generateStructuredResponse( + const language = await helper.generateStructuredResponse?.( `input text: ${text}`, settings.model, this.languageClassifierPrompt, @@ -147,13 +146,13 @@ export default class LlmNluHelper { entity: 'language', value: language || defaultLanguage.code, - confidence: undefined, + confidence: 0.0, }, ]; for await (const { name, doc, prompt, values } of this .traitClassifierPrompts) { const allowedValues = values.map(({ value }) => value); - const result = await helper.generateStructuredResponse( + const result = await helper.generateStructuredResponse?.( `input text: ${text}`, settings.model, prompt, @@ -163,12 +162,13 @@ export default class LlmNluHelper enum: allowedValues.concat('unknown'), }, ); - const safeValue = result.toLowerCase().trim(); - const value = allowedValues.includes(safeValue) ? safeValue : ''; + const safeValue = result?.toLowerCase().trim(); + const value = + safeValue && allowedValues.includes(safeValue) ? safeValue : ''; traits.push({ entity: name, value, - confidence: undefined, + confidence: 0.0, }); } @@ -179,7 +179,7 @@ export default class LlmNluHelper }); const entities = keywordEntities.flatMap((keywordEntity) => this.findKeywordEntities(text, keywordEntity), - ); + ) as NLU.ParseEntity[]; return { entities: traits.concat(entities) }; } diff --git a/api/src/migration/migration.service.spec.ts b/api/src/migration/migration.service.spec.ts index 87a64ba3..1ef257fd 100644 --- a/api/src/migration/migration.service.spec.ts +++ b/api/src/migration/migration.service.spec.ts @@ -186,7 +186,7 @@ describe('MigrationService', () => { await service.run({ action: MigrationAction.UP, - version: null, + version: undefined, isAutoMigrate: false, }); diff --git a/api/src/migration/migration.service.ts b/api/src/migration/migration.service.ts index e74a9b0e..df16f53c 100644 --- a/api/src/migration/migration.service.ts +++ b/api/src/migration/migration.service.ts @@ -28,6 +28,7 @@ import { Migration, MigrationDocument } from './migration.schema'; import { MigrationAction, MigrationName, + MigrationRunOneParams, MigrationRunParams, MigrationSuccessCallback, MigrationVersion, @@ -237,7 +238,7 @@ module.exports = { * * @returns Resolves when the migration action is successfully executed or stops if the migration already exists. */ - private async runOne({ version, action }: MigrationRunParams) { + private async runOne({ version, action }: MigrationRunOneParams) { // Verify DB status const { exist, migrationDocument } = await this.verifyStatus({ version, @@ -255,7 +256,7 @@ module.exports = { http: this.httpService, }); - if (result) { + if (result && migrationDocument) { await this.successCallback({ version, action, diff --git a/api/src/migration/types.ts b/api/src/migration/types.ts index 2bf93cdd..92134800 100644 --- a/api/src/migration/types.ts +++ b/api/src/migration/types.ts @@ -27,6 +27,10 @@ export interface MigrationRunParams { isAutoMigrate?: boolean; } +export interface MigrationRunOneParams extends MigrationRunParams { + version: MigrationVersion; +} + export interface MigrationSuccessCallback extends MigrationRunParams { migrationDocument: MigrationDocument; } diff --git a/api/src/nlp/controllers/nlp-entity.controller.spec.ts b/api/src/nlp/controllers/nlp-entity.controller.spec.ts index c33f8955..d678dcac 100644 --- a/api/src/nlp/controllers/nlp-entity.controller.spec.ts +++ b/api/src/nlp/controllers/nlp-entity.controller.spec.ts @@ -109,7 +109,7 @@ describe('NlpEntityController', () => { acc.push({ ...curr, values: nlpValueFixtures.filter( - ({ entity }) => parseInt(entity) === index, + ({ entity }) => parseInt(entity!) === index, ) as NlpEntityFull['values'], lookups: curr.lookups!, builtin: curr.builtin!, diff --git a/api/src/nlp/controllers/nlp-value.controller.spec.ts b/api/src/nlp/controllers/nlp-value.controller.spec.ts index 58be7aa8..97b7c437 100644 --- a/api/src/nlp/controllers/nlp-value.controller.spec.ts +++ b/api/src/nlp/controllers/nlp-value.controller.spec.ts @@ -98,7 +98,7 @@ describe('NlpValueController', () => { acc.push({ ...curr, entity: nlpEntityFixtures[ - parseInt(curr.entity) + parseInt(curr.entity!) ] as NlpValueFull['entity'], builtin: curr.builtin!, expressions: curr.expressions!, @@ -125,7 +125,7 @@ describe('NlpValueController', () => { (acc, curr) => { const ValueWithEntities = { ...curr, - entity: nlpEntities[parseInt(curr.entity)].id, + entity: nlpEntities[parseInt(curr.entity!)].id, expressions: curr.expressions!, metadata: curr.metadata!, builtin: curr.builtin!, diff --git a/api/src/nlp/controllers/nlp-value.controller.ts b/api/src/nlp/controllers/nlp-value.controller.ts index 1d241ce0..3bae3093 100644 --- a/api/src/nlp/controllers/nlp-value.controller.ts +++ b/api/src/nlp/controllers/nlp-value.controller.ts @@ -75,8 +75,9 @@ export class NlpValueController extends BaseController< this.validate({ dto: createNlpValueDto, allowedIds: { - entity: (await this.nlpEntityService.findOne(createNlpValueDto.entity)) - ?.id, + entity: createNlpValueDto.entity + ? (await this.nlpEntityService.findOne(createNlpValueDto.entity))?.id + : null, }, }); return await this.nlpValueService.create(createNlpValueDto); @@ -167,7 +168,10 @@ export class NlpValueController extends BaseController< @Param('id') id: string, @Body() updateNlpValueDto: NlpValueUpdateDto, ): Promise { - const result = await this.nlpValueService.updateOne(id, updateNlpValueDto); + const result = await this.nlpValueService.updateOne(id, { + ...updateNlpValueDto, + entity: updateNlpValueDto.entity || undefined, + }); if (!result) { this.logger.warn(`Unable to update NLP Value by id ${id}`); throw new NotFoundException(`NLP Value with ID ${id} not found`); diff --git a/api/src/nlp/dto/nlp-value.dto.ts b/api/src/nlp/dto/nlp-value.dto.ts index 3848718c..a60ffa60 100644 --- a/api/src/nlp/dto/nlp-value.dto.ts +++ b/api/src/nlp/dto/nlp-value.dto.ts @@ -49,7 +49,7 @@ export class NlpValueCreateDto { @IsString() @IsNotEmpty() @IsObjectId({ message: 'Entity must be a valid ObjectId' }) - entity: string; + entity: string | null; } export class NlpValueUpdateDto extends PartialType(NlpValueCreateDto) {} diff --git a/api/src/nlp/repositories/nlp-value.repository.spec.ts b/api/src/nlp/repositories/nlp-value.repository.spec.ts index 3186cc26..0b3a7ce7 100644 --- a/api/src/nlp/repositories/nlp-value.repository.spec.ts +++ b/api/src/nlp/repositories/nlp-value.repository.spec.ts @@ -87,7 +87,7 @@ describe('NlpValueRepository', () => { const ValueWithEntities = { ...curr, entity: nlpEntityFixtures[ - parseInt(curr.entity) + parseInt(curr.entity!) ] as NlpValueFull['entity'], builtin: curr.builtin!, expressions: curr.expressions!, diff --git a/api/src/nlp/seeds/nlp-value.seed.ts b/api/src/nlp/seeds/nlp-value.seed.ts index d8fd5f7a..d78bf29e 100644 --- a/api/src/nlp/seeds/nlp-value.seed.ts +++ b/api/src/nlp/seeds/nlp-value.seed.ts @@ -38,7 +38,7 @@ export class NlpValueSeeder extends BaseSeeder< const entities = await this.nlpEntityRepository.findAll(); const modelDtos = models.map((v) => ({ ...v, - entity: entities.find(({ name }) => name === v.entity)?.id, + entity: entities.find(({ name }) => name === v.entity)?.id || null, })); await this.repository.createMany(modelDtos); return true; diff --git a/api/src/nlp/services/nlp-value.service.spec.ts b/api/src/nlp/services/nlp-value.service.spec.ts index 35202015..f62ed7cb 100644 --- a/api/src/nlp/services/nlp-value.service.spec.ts +++ b/api/src/nlp/services/nlp-value.service.spec.ts @@ -69,9 +69,7 @@ describe('NlpValueService', () => { nlpValues = await nlpValueRepository.findAll(); }); - afterAll(async () => { - await closeInMongodConnection(); - }); + afterAll(closeInMongodConnection); afterEach(jest.clearAllMocks); @@ -94,9 +92,7 @@ describe('NlpValueService', () => { (acc, curr) => { const ValueWithEntities = { ...curr, - entity: nlpEntityFixtures[ - parseInt(curr.entity) - ] as NlpValueFull['entity'], + entity: nlpEntityFixtures[parseInt(curr.entity!)] as NlpEntity, expressions: curr.expressions!, metadata: curr.metadata!, builtin: curr.builtin!, diff --git a/api/src/nlp/services/nlp-value.service.ts b/api/src/nlp/services/nlp-value.service.ts index 2d20bde5..d721efd3 100644 --- a/api/src/nlp/services/nlp-value.service.ts +++ b/api/src/nlp/services/nlp-value.service.ts @@ -11,11 +11,7 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { DeleteResult } from '@/utils/generics/base-repository'; import { BaseService } from '@/utils/generics/base-service'; -import { - NlpValueCreateDto, - NlpValueDto, - NlpValueUpdateDto, -} from '../dto/nlp-value.dto'; +import { NlpValueCreateDto, NlpValueDto } from '../dto/nlp-value.dto'; import { NlpValueRepository } from '../repositories/nlp-value.repository'; import { NlpEntity } from '../schemas/nlp-entity.schema'; import { @@ -139,7 +135,7 @@ export class NlpValueService extends BaseService< expressions: vMap[e.value].expressions?.concat([ sampleText.slice(e.start, e.end), ]), - } as NlpValueUpdateDto); + }); }); await Promise.all(synonymsToAdd); diff --git a/api/src/utils/helpers/fs.ts b/api/src/utils/helpers/fs.ts index d9c3ae39..653aaa7b 100644 --- a/api/src/utils/helpers/fs.ts +++ b/api/src/utils/helpers/fs.ts @@ -48,7 +48,7 @@ export async function moveFiles( const files = await fs.promises.readdir(sourceFolder); // Filter only files (skip directories) - const filePaths = []; + const filePaths: string[] = []; for (const file of files) { const filePath = join(sourceFolder, file); const stat = await fs.promises.stat(filePath); diff --git a/api/src/utils/pipes/object-id.pipe.ts b/api/src/utils/pipes/object-id.pipe.ts index 9c504c4f..fd2d1527 100644 --- a/api/src/utils/pipes/object-id.pipe.ts +++ b/api/src/utils/pipes/object-id.pipe.ts @@ -7,10 +7,10 @@ */ import { - Injectable, - PipeTransform, ArgumentMetadata, BadRequestException, + Injectable, + PipeTransform, } from '@nestjs/common'; import { plainToClass } from 'class-transformer'; import { validate } from 'class-validator'; @@ -33,7 +33,7 @@ export class ObjectIdPipe implements PipeTransform> { async transform(value: string, { type, data }: ArgumentMetadata) { if (typeof value === 'string' && data === 'id' && type === 'param') { const errors = await this.getErrors(value); - if (errors) + if (errors.constraints) throw new BadRequestException(Object.values(errors.constraints)[0]); } else if ( typeof value === 'object' && @@ -45,7 +45,7 @@ export class ObjectIdPipe implements PipeTransform> { if (param.startsWith('id')) { const errors = await this.getErrors(String(paramValue)); - if (errors) + if (errors.constraints) throw new BadRequestException( Object.values(errors.constraints)[0], ); diff --git a/api/src/utils/test/fixtures/conversation.ts b/api/src/utils/test/fixtures/conversation.ts index fca24ce1..c438f301 100644 --- a/api/src/utils/test/fixtures/conversation.ts +++ b/api/src/utils/test/fixtures/conversation.ts @@ -136,8 +136,10 @@ export const installConversationTypeFixtures = async () => { conversationFixtures.map((conversationFixture) => ({ ...conversationFixture, sender: subscribers[parseInt(conversationFixture.sender)].id, - current: blocks[parseInt(conversationFixture.current)].id, - next: conversationFixture.next.map((n) => blocks[parseInt(n)].id), + current: conversationFixture?.current + ? blocks[parseInt(conversationFixture.current)].id + : null, + next: conversationFixture.next?.map((n) => blocks[parseInt(n)].id), })), ); }; diff --git a/api/src/utils/test/fixtures/nlpvalue.ts b/api/src/utils/test/fixtures/nlpvalue.ts index 94b4c28c..fa882014 100644 --- a/api/src/utils/test/fixtures/nlpvalue.ts +++ b/api/src/utils/test/fixtures/nlpvalue.ts @@ -51,12 +51,10 @@ export const installNlpValueFixtures = async () => { const NlpValue = mongoose.model(NlpValueModel.name, NlpValueModel.schema); const nlpValues = await NlpValue.insertMany( - nlpValueFixtures.map((v) => { - return { - ...v, - entity: nlpEntities[parseInt(v.entity)].id, - }; - }), + nlpValueFixtures.map((v) => ({ + ...v, + entity: v?.entity ? nlpEntities[parseInt(v.entity)].id : null, + })), ); return { nlpEntities, nlpValues }; }; diff --git a/api/tsconfig.json b/api/tsconfig.json index 4cf9e50c..0201e55c 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -13,7 +13,7 @@ "baseUrl": "./", "incremental": true, "skipLibCheck": true, - "strictNullChecks": false, + "strictNullChecks": true, "strictPropertyInitialization": false, "noImplicitAny": false, "strictBindCallApply": false,