mirror of
https://github.com/hexastack/hexabot
synced 2025-05-31 10:57:06 +00:00
Merge pull request #563 from Hexastack/562-issue-strict-null-check
feat: implement strict null checks
This commit is contained in:
commit
47e8056a15
@ -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';
|
||||
@ -55,14 +55,12 @@ describe('AttachmentController', () => {
|
||||
attachmentController =
|
||||
module.get<AttachmentController>(AttachmentController);
|
||||
attachmentService = module.get<AttachmentService>(AttachmentService);
|
||||
attachmentToDelete = await attachmentService.findOne({
|
||||
attachmentToDelete = (await attachmentService.findOne({
|
||||
name: 'store1.jpg',
|
||||
});
|
||||
}))!;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await closeInMongodConnection();
|
||||
});
|
||||
afterAll(closeInMongodConnection);
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
|
||||
@ -79,7 +77,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'),
|
||||
@ -117,9 +115,9 @@ describe('AttachmentController', () => {
|
||||
|
||||
it('should download the attachment by id', async () => {
|
||||
jest.spyOn(attachmentService, 'findOne');
|
||||
const storedAttachment = await attachmentService.findOne({
|
||||
const storedAttachment = (await attachmentService.findOne({
|
||||
name: 'store1.jpg',
|
||||
});
|
||||
}))!;
|
||||
const result = await attachmentController.download({
|
||||
id: storedAttachment.id,
|
||||
});
|
||||
@ -127,7 +125,7 @@ describe('AttachmentController', () => {
|
||||
expect(attachmentService.findOne).toHaveBeenCalledWith(
|
||||
storedAttachment.id,
|
||||
);
|
||||
expect(result.options).toEqual({
|
||||
expect(result?.options).toEqual({
|
||||
type: storedAttachment.type,
|
||||
length: storedAttachment.size,
|
||||
disposition: `attachment; filename="${encodeURIComponent(
|
||||
|
@ -256,9 +256,15 @@ export class AttachmentService extends BaseService<Attachment> {
|
||||
async download(
|
||||
attachment: Attachment,
|
||||
rootDir = config.parameters.uploadDir,
|
||||
) {
|
||||
): Promise<StreamableFile> {
|
||||
if (this.getStoragePlugin()) {
|
||||
return await this.getStoragePlugin()?.download(attachment);
|
||||
const streamableFile =
|
||||
await this.getStoragePlugin()?.download(attachment);
|
||||
if (!streamableFile) {
|
||||
throw new NotFoundException('No file was found');
|
||||
}
|
||||
|
||||
return streamableFile;
|
||||
} else {
|
||||
const path = resolve(join(rootDir, attachment.location));
|
||||
|
||||
|
@ -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<NLU.ParseEntities> {
|
||||
@ -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<string>(
|
||||
const language = await helper.generateStructuredResponse<string>?.(
|
||||
`input text: ${text}`,
|
||||
settings.model,
|
||||
this.languageClassifierPrompt,
|
||||
@ -147,13 +146,13 @@ export default class LlmNluHelper
|
||||
{
|
||||
entity: 'language',
|
||||
value: language || defaultLanguage.code,
|
||||
confidence: undefined,
|
||||
confidence: 100,
|
||||
},
|
||||
];
|
||||
for await (const { name, doc, prompt, values } of this
|
||||
.traitClassifierPrompts) {
|
||||
const allowedValues = values.map(({ value }) => value);
|
||||
const result = await helper.generateStructuredResponse<string>(
|
||||
const result = await helper.generateStructuredResponse<string>?.(
|
||||
`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: 100,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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) };
|
||||
}
|
||||
|
@ -191,7 +191,7 @@ describe('MigrationService', () => {
|
||||
|
||||
await service.run({
|
||||
action: MigrationAction.UP,
|
||||
version: null,
|
||||
version: undefined,
|
||||
isAutoMigrate: false,
|
||||
});
|
||||
|
||||
|
@ -29,6 +29,7 @@ import { Migration, MigrationDocument } from './migration.schema';
|
||||
import {
|
||||
MigrationAction,
|
||||
MigrationName,
|
||||
MigrationRunOneParams,
|
||||
MigrationRunParams,
|
||||
MigrationSuccessCallback,
|
||||
MigrationVersion,
|
||||
@ -239,7 +240,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,
|
||||
@ -258,7 +259,7 @@ module.exports = {
|
||||
attachmentService: this.attachmentService,
|
||||
});
|
||||
|
||||
if (result) {
|
||||
if (result && migrationDocument) {
|
||||
await this.successCallback({
|
||||
version,
|
||||
action,
|
||||
|
@ -442,7 +442,10 @@ const migrateAttachmentMessages = async ({
|
||||
type: response.headers['content-type'],
|
||||
channel: {},
|
||||
});
|
||||
await updateAttachmentId(msg._id, attachment.id);
|
||||
|
||||
if (attachment) {
|
||||
await updateAttachmentId(msg._id, attachment.id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn(
|
||||
|
@ -28,6 +28,10 @@ export interface MigrationRunParams {
|
||||
isAutoMigrate?: boolean;
|
||||
}
|
||||
|
||||
export interface MigrationRunOneParams extends MigrationRunParams {
|
||||
version: MigrationVersion;
|
||||
}
|
||||
|
||||
export interface MigrationSuccessCallback extends MigrationRunParams {
|
||||
migrationDocument: MigrationDocument;
|
||||
}
|
||||
|
@ -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!,
|
||||
|
@ -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: curr.entity ? nlpEntities[parseInt(curr.entity!)].id : null,
|
||||
expressions: curr.expressions!,
|
||||
metadata: curr.metadata!,
|
||||
builtin: curr.builtin!,
|
||||
@ -133,7 +133,7 @@ describe('NlpValueController', () => {
|
||||
acc.push(ValueWithEntities);
|
||||
return acc;
|
||||
},
|
||||
[] as TFixtures<NlpValue>[],
|
||||
[] as TFixtures<NlpValueCreateDto>[],
|
||||
);
|
||||
expect(result).toEqualPayload(nlpValueFixturesWithEntities);
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -6,7 +6,6 @@
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import {
|
||||
IsArray,
|
||||
@ -49,11 +48,42 @@ export class NlpValueCreateDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsObjectId({ message: 'Entity must be a valid ObjectId' })
|
||||
entity: string;
|
||||
entity: string | null;
|
||||
}
|
||||
|
||||
export class NlpValueUpdateDto extends PartialType(NlpValueCreateDto) {}
|
||||
export class NlpValueUpdateDto {
|
||||
@ApiPropertyOptional({ description: 'Foreign ID', type: String })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
foreign_id?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Nlp value', type: String })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
value?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Nlp value expressions',
|
||||
isArray: true,
|
||||
type: Array,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
expressions?: string[];
|
||||
|
||||
@ApiPropertyOptional({ description: 'Nlp value entity', type: String })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsObjectId({ message: 'Entity must be a valid ObjectId' })
|
||||
entity?: string | null;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Nlp value is builtin', type: Boolean })
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
builtin?: boolean;
|
||||
}
|
||||
|
||||
export type NlpValueDto = DtoConfig<{
|
||||
create: NlpValueCreateDto;
|
||||
update: NlpValueUpdateDto;
|
||||
}>;
|
||||
|
@ -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!,
|
||||
|
@ -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;
|
||||
|
@ -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!,
|
||||
|
@ -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);
|
||||
|
@ -478,7 +478,7 @@ export abstract class BaseRepository<
|
||||
|
||||
async updateOne<D extends Partial<U>>(
|
||||
criteria: string | TFilterQuery<T>,
|
||||
dto: UpdateQuery<D>,
|
||||
dto: UpdateQuery<DtoInfer<DtoAction.Update, Dto, D>>,
|
||||
options: QueryOptions<D> | null = {
|
||||
new: true,
|
||||
},
|
||||
|
@ -177,7 +177,7 @@ export abstract class BaseService<
|
||||
|
||||
async updateOne(
|
||||
criteria: string | TFilterQuery<T>,
|
||||
dto: Partial<U>,
|
||||
dto: DtoInfer<DtoAction.Update, Dto, Partial<U>>,
|
||||
options?: QueryOptions<Partial<U>> | null,
|
||||
): Promise<T | null> {
|
||||
return await this.repository.updateOne(criteria, dto, options);
|
||||
|
@ -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);
|
||||
|
@ -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,8 +33,13 @@ export class ObjectIdPipe implements PipeTransform<string, Promise<string>> {
|
||||
async transform(value: string, { type, data }: ArgumentMetadata) {
|
||||
if (typeof value === 'string' && data === 'id' && type === 'param') {
|
||||
const errors = await this.getErrors(value);
|
||||
if (errors)
|
||||
throw new BadRequestException(Object.values(errors.constraints)[0]);
|
||||
if (errors) {
|
||||
throw new BadRequestException(
|
||||
errors?.constraints
|
||||
? Object.values(errors.constraints)[0]
|
||||
: errors.toString(),
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
typeof value === 'object' &&
|
||||
Object.keys(value).length > 1 &&
|
||||
@ -45,10 +50,13 @@ export class ObjectIdPipe implements PipeTransform<string, Promise<string>> {
|
||||
if (param.startsWith('id')) {
|
||||
const errors = await this.getErrors(String(paramValue));
|
||||
|
||||
if (errors)
|
||||
if (errors) {
|
||||
throw new BadRequestException(
|
||||
Object.values(errors.constraints)[0],
|
||||
errors?.constraints
|
||||
? Object.values(errors.constraints)[0]
|
||||
: errors.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
2
api/src/utils/test/fixtures/conversation.ts
vendored
2
api/src/utils/test/fixtures/conversation.ts
vendored
@ -142,7 +142,7 @@ export const installConversationTypeFixtures = async () => {
|
||||
conversationFixtures.map((conversationFixture) => ({
|
||||
...conversationFixture,
|
||||
sender: subscribers[parseInt(conversationFixture.sender)].id,
|
||||
current: conversationFixture?.current
|
||||
current: conversationFixture.current
|
||||
? blocks[parseInt(conversationFixture.current)]?.id
|
||||
: undefined,
|
||||
next: conversationFixture.next?.map((n) => blocks[parseInt(n)].id),
|
||||
|
10
api/src/utils/test/fixtures/nlpvalue.ts
vendored
10
api/src/utils/test/fixtures/nlpvalue.ts
vendored
@ -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 };
|
||||
};
|
||||
|
@ -13,7 +13,7 @@
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
|
Loading…
Reference in New Issue
Block a user