diff --git a/api/src/channel/channel.service.ts b/api/src/channel/channel.service.ts index 49af6016..0b7d497e 100644 --- a/api/src/channel/channel.service.ts +++ b/api/src/channel/channel.service.ts @@ -162,8 +162,8 @@ export class ChannelService { }, { foreign_id: req.session.passport.user.id, - first_name: req.session.passport.user.first_name, - last_name: req.session.passport.user.last_name, + first_name: req.session.passport.user.first_name || 'Anonymous', + last_name: req.session.passport.user.last_name || 'Anonymous', locale: '', language: '', gender: '', diff --git a/api/src/channel/lib/__test__/subscriber.mock.ts b/api/src/channel/lib/__test__/subscriber.mock.ts index 5f2bda7e..ea144104 100644 --- a/api/src/channel/lib/__test__/subscriber.mock.ts +++ b/api/src/channel/lib/__test__/subscriber.mock.ts @@ -28,6 +28,8 @@ export const subscriberInstance: Subscriber = { name: 'web-channel', }, labels: [], + avatar: null, + context: {}, ...modelInstance, }; diff --git a/api/src/chat/controllers/block.controller.spec.ts b/api/src/chat/controllers/block.controller.spec.ts index 403744b0..767e69b4 100644 --- a/api/src/chat/controllers/block.controller.spec.ts +++ b/api/src/chat/controllers/block.controller.spec.ts @@ -142,18 +142,19 @@ describe('BlockController', () => { blockController = module.get(BlockController); blockService = module.get(BlockService); categoryService = module.get(CategoryService); - category = await categoryService.findOne({ label: 'default' }); - block = await blockService.findOne({ name: 'first' }); - blockToDelete = await blockService.findOne({ name: 'buttons' }); - hasNextBlocks = await blockService.findOne({ + category = (await categoryService.findOne({ label: 'default' }))!; + block = (await blockService.findOne({ name: 'first' }))!; + blockToDelete = (await blockService.findOne({ name: 'buttons' }))!; + hasNextBlocks = (await blockService.findOne({ name: 'hasNextBlocks', - }); - hasPreviousBlocks = await blockService.findOne({ + }))!; + hasPreviousBlocks = (await blockService.findOne({ name: 'hasPreviousBlocks', - }); + }))!; }); afterEach(jest.clearAllMocks); + afterAll(closeInMongodConnection); describe('find', () => { @@ -185,6 +186,7 @@ describe('BlockController', () => { blockFixture.name === 'hasPreviousBlocks' ? [hasNextBlocks] : [], nextBlocks: blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks] : [], + attachedToBlock: null, })); expect(blockService.findAndPopulate).toHaveBeenCalledWith({}, undefined); @@ -220,12 +222,13 @@ describe('BlockController', () => { ...blockFixtures.find(({ name }) => name === 'hasPreviousBlocks'), category, previousBlocks: [hasNextBlocks], + attachedToBlock: null, }); }); it('should find one block by id, and populate its category and an empty previousBlocks', async () => { jest.spyOn(blockService, 'findOneAndPopulate'); - block = await blockService.findOne({ name: 'attachment' }); + block = (await blockService.findOne({ name: 'attachment' }))!; const result = await blockController.findOne( block.id, FIELDS_TO_POPULATE, @@ -235,6 +238,7 @@ describe('BlockController', () => { ...blockFixtures.find(({ name }) => name === 'attachment'), category, previousBlocks: [], + attachedToBlock: null, }); }); }); diff --git a/api/src/chat/controllers/block.controller.ts b/api/src/chat/controllers/block.controller.ts index 945032f6..949410d3 100644 --- a/api/src/chat/controllers/block.controller.ts +++ b/api/src/chat/controllers/block.controller.ts @@ -219,9 +219,12 @@ export class BlockController extends BaseController< this.validate({ dto: block, allowedIds: { - category: (await this.categoryService.findOne(block.category))?.id, - attachedBlock: (await this.blockService.findOne(block.attachedBlock)) - ?.id, + category: block.category + ? (await this.categoryService.findOne(block.category))?.id + : null, + attachedBlock: block.attachedBlock + ? (await this.blockService.findOne(block.attachedBlock))?.id + : null, nextBlocks: ( await this.blockService.find({ _id: { diff --git a/api/src/chat/controllers/context-var.controller.spec.ts b/api/src/chat/controllers/context-var.controller.spec.ts index 6098ac05..d14a7dd6 100644 --- a/api/src/chat/controllers/context-var.controller.spec.ts +++ b/api/src/chat/controllers/context-var.controller.spec.ts @@ -56,15 +56,16 @@ describe('ContextVarController', () => { contextVarController = module.get(ContextVarController); contextVarService = module.get(ContextVarService); - contextVar = await contextVarService.findOne({ + contextVar = (await contextVarService.findOne({ label: 'test context var 1', - }); - contextVarToDelete = await contextVarService.findOne({ + }))!; + contextVarToDelete = (await contextVarService.findOne({ label: 'test context var 2', - }); + }))!; }); afterEach(jest.clearAllMocks); + afterAll(closeInMongodConnection); describe('count', () => { @@ -95,7 +96,7 @@ describe('ContextVarController', () => { expect(contextVarService.findOne).toHaveBeenCalledWith(contextVar.id); expect(result).toEqualPayload( - contextVarFixtures.find(({ label }) => label === contextVar.label), + contextVarFixtures.find(({ label }) => label === contextVar.label)!, ); }); }); diff --git a/api/src/chat/controllers/message.controller.spec.ts b/api/src/chat/controllers/message.controller.spec.ts index 0105343b..d2e9c2d3 100644 --- a/api/src/chat/controllers/message.controller.spec.ts +++ b/api/src/chat/controllers/message.controller.spec.ts @@ -128,16 +128,17 @@ describe('MessageController', () => { userService = module.get(UserService); subscriberService = module.get(SubscriberService); messageController = module.get(MessageController); - message = await messageService.findOne({ mid: 'mid-1' }); - sender = await subscriberService.findOne(message.sender); - recipient = await subscriberService.findOne(message.recipient); - user = await userService.findOne(message.sentBy); + message = (await messageService.findOne({ mid: 'mid-1' }))!; + sender = (await subscriberService.findOne(message.sender!))!; + recipient = (await subscriberService.findOne(message.recipient!))!; + user = (await userService.findOne(message.sentBy!))!; allSubscribers = await subscriberService.findAll(); allUsers = await userService.findAll(); allMessages = await messageService.findAll(); }); afterEach(jest.clearAllMocks); + afterAll(closeInMongodConnection); describe('count', () => { @@ -189,10 +190,10 @@ describe('MessageController', () => { const result = await messageController.findPage(pageQuery, [], {}); const messagesWithSenderAndRecipient = allMessages.map((message) => ({ ...message, - sender: allSubscribers.find(({ id }) => id === message['sender']).id, - recipient: allSubscribers.find(({ id }) => id === message['recipient']) - .id, - sentBy: allUsers.find(({ id }) => id === message['sentBy']).id, + sender: allSubscribers.find(({ id }) => id === message.sender)?.id, + recipient: allSubscribers.find(({ id }) => id === message.recipient) + ?.id, + sentBy: allUsers.find(({ id }) => id === message.sentBy)?.id, })); expect(messageService.find).toHaveBeenCalledWith({}, pageQuery); @@ -208,9 +209,9 @@ describe('MessageController', () => { ); const messages = allMessages.map((message) => ({ ...message, - sender: allSubscribers.find(({ id }) => id === message['sender']), - recipient: allSubscribers.find(({ id }) => id === message['recipient']), - sentBy: allUsers.find(({ id }) => id === message['sentBy']).id, + sender: allSubscribers.find(({ id }) => id === message.sender), + recipient: allSubscribers.find(({ id }) => id === message.recipient), + sentBy: allUsers.find(({ id }) => id === message.sentBy)?.id, })); expect(messageService.findAndPopulate).toHaveBeenCalledWith( diff --git a/api/src/chat/controllers/subscriber.controller.spec.ts b/api/src/chat/controllers/subscriber.controller.spec.ts index b5a2e6b7..6e280f99 100644 --- a/api/src/chat/controllers/subscriber.controller.spec.ts +++ b/api/src/chat/controllers/subscriber.controller.spec.ts @@ -90,15 +90,16 @@ describe('SubscriberController', () => { subscriberController = module.get(SubscriberController); - subscriber = await subscriberService.findOne({ + subscriber = (await subscriberService.findOne({ first_name: 'Jhon', - }); + }))!; allLabels = await labelService.findAll(); allSubscribers = await subscriberService.findAll(); allUsers = await userService.findAll(); }); afterEach(jest.clearAllMocks); + afterAll(closeInMongodConnection); describe('count', () => { @@ -125,7 +126,7 @@ describe('SubscriberController', () => { ({ first_name }) => first_name === subscriber.first_name, ), labels: labelIDs, - assignedTo: allUsers.find(({ id }) => subscriber.assignedTo === id).id, + assignedTo: allUsers.find(({ id }) => subscriber.assignedTo === id)?.id, }); }); diff --git a/api/src/chat/controllers/subscriber.controller.ts b/api/src/chat/controllers/subscriber.controller.ts index f7e53806..d0688df0 100644 --- a/api/src/chat/controllers/subscriber.controller.ts +++ b/api/src/chat/controllers/subscriber.controller.ts @@ -145,7 +145,9 @@ export class SubscriberController extends BaseController< * @returns A streamable file containing the avatar image. */ @Get(':id/profile_pic') - async getAvatar(@Param('id') id: string): Promise { + async getAvatar( + @Param('id') id: string, + ): Promise { const subscriber = await this.subscriberService.findOneAndPopulate(id); if (!subscriber) { diff --git a/api/src/chat/dto/block.dto.ts b/api/src/chat/dto/block.dto.ts index 3b2da43e..b7564714 100644 --- a/api/src/chat/dto/block.dto.ts +++ b/api/src/chat/dto/block.dto.ts @@ -84,13 +84,13 @@ export class BlockCreateDto { @IsObjectId({ message: 'Attached block must be a valid objectId', }) - attachedBlock?: string; + attachedBlock?: string | null; @ApiProperty({ description: 'Block category', type: String }) @IsNotEmpty() @IsString() @IsObjectId({ message: 'Category must be a valid objectId' }) - category: string; + category: string | null; @ApiPropertyOptional({ description: 'Block has started conversation', diff --git a/api/src/chat/repositories/block.repository.spec.ts b/api/src/chat/repositories/block.repository.spec.ts index 472a3659..8556266c 100644 --- a/api/src/chat/repositories/block.repository.spec.ts +++ b/api/src/chat/repositories/block.repository.spec.ts @@ -51,13 +51,13 @@ describe('BlockRepository', () => { validIds = ['64abc1234def567890fedcba', '64abc1234def567890fedcbc']; validCategory = '64def5678abc123490fedcba'; - category = await categoryRepository.findOne({ label: 'default' }); - hasPreviousBlocks = await blockRepository.findOne({ + category = (await categoryRepository.findOne({ label: 'default' }))!; + hasPreviousBlocks = (await blockRepository.findOne({ name: 'hasPreviousBlocks', - }); - hasNextBlocks = await blockRepository.findOne({ + }))!; + hasNextBlocks = (await blockRepository.findOne({ name: 'hasNextBlocks', - }); + }))!; }); afterEach(jest.clearAllMocks); @@ -77,6 +77,7 @@ describe('BlockRepository', () => { category, nextBlocks: [hasPreviousBlocks], previousBlocks: [], + attachedToBlock: null, }); }); }); @@ -93,6 +94,7 @@ describe('BlockRepository', () => { blockFixture.name === 'hasPreviousBlocks' ? [hasNextBlocks] : [], nextBlocks: blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks] : [], + attachedToBlock: null, })); expect(blockModel.find).toHaveBeenCalledWith({}, undefined); @@ -110,6 +112,7 @@ describe('BlockRepository', () => { blockFixture.name === 'hasPreviousBlocks' ? [hasNextBlocks] : [], nextBlocks: blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks] : [], + attachedToBlock: null, })); expect(blockModel.find).toHaveBeenCalledWith({}, undefined); @@ -191,7 +194,7 @@ describe('BlockRepository', () => { category: validCategory, nextBlocks: [], attachedBlock: null, - } as Block); + } as unknown as Block); const mockUpdateOne = jest.spyOn(blockRepository, 'updateOne'); @@ -233,7 +236,7 @@ describe('BlockRepository', () => { attachedBlock: null, nextBlocks: [validIds[0], validIds[1]], }, - ] as Block[]; + ] as unknown as Block[]; const mockUpdateOne = jest.spyOn(blockRepository, 'updateOne'); diff --git a/api/src/chat/repositories/context-var.repository.ts b/api/src/chat/repositories/context-var.repository.ts index 20ea8640..3432351e 100644 --- a/api/src/chat/repositories/context-var.repository.ts +++ b/api/src/chat/repositories/context-var.repository.ts @@ -38,7 +38,7 @@ export class ContextVarRepository extends BaseRepository< @Optional() blockService?: BlockService, ) { super(eventEmitter, model, ContextVar); - this.blockService = blockService; + if (blockService) this.blockService = blockService; } /** diff --git a/api/src/chat/repositories/message.repository.spec.ts b/api/src/chat/repositories/message.repository.spec.ts index 7c3b2e62..f817f2a9 100644 --- a/api/src/chat/repositories/message.repository.spec.ts +++ b/api/src/chat/repositories/message.repository.spec.ts @@ -62,12 +62,12 @@ describe('MessageRepository', () => { describe('findOneAndPopulate', () => { it('should find one message by id, and populate its sender and recipient', async () => { jest.spyOn(messageModel, 'findById'); - const message = await messageRepository.findOne({ mid: 'mid-1' }); - const sender = await subscriberRepository.findOne(message['sender']); + const message = (await messageRepository.findOne({ mid: 'mid-1' }))!; + const sender = await subscriberRepository.findOne(message!['sender']); const recipient = await subscriberRepository.findOne( - message['recipient'], + message!['recipient'], ); - const user = await userRepository.findOne(message['sentBy']); + const user = (await userRepository.findOne(message!['sentBy']))!; const result = await messageRepository.findOneAndPopulate(message.id); expect(messageModel.findById).toHaveBeenCalledWith(message.id, undefined); @@ -92,7 +92,7 @@ describe('MessageRepository', () => { ...message, sender: allSubscribers.find(({ id }) => id === message['sender']), recipient: allSubscribers.find(({ id }) => id === message['recipient']), - sentBy: allUsers.find(({ id }) => id === message['sentBy']).id, + sentBy: allUsers.find(({ id }) => id === message['sentBy'])?.id, })); expect(messageModel.find).toHaveBeenCalledWith({}, undefined); diff --git a/api/src/chat/repositories/subscriber.repository.spec.ts b/api/src/chat/repositories/subscriber.repository.spec.ts index b497002f..fd553e5f 100644 --- a/api/src/chat/repositories/subscriber.repository.spec.ts +++ b/api/src/chat/repositories/subscriber.repository.spec.ts @@ -97,14 +97,15 @@ describe('SubscriberRepository', () => { }); afterEach(jest.clearAllMocks); + afterAll(closeInMongodConnection); describe('findOneAndPopulate', () => { it('should find one subscriber by id,and populate its labels', async () => { jest.spyOn(subscriberModel, 'findById'); - const subscriber = await subscriberRepository.findOne({ + const subscriber = (await subscriberRepository.findOne({ first_name: 'Jhon', - }); + }))!; const allLabels = await labelRepository.findAll(); const result = await subscriberRepository.findOneAndPopulate( subscriber.id, diff --git a/api/src/chat/repositories/subscriber.repository.ts b/api/src/chat/repositories/subscriber.repository.ts index be088bf1..2d690766 100644 --- a/api/src/chat/repositories/subscriber.repository.ts +++ b/api/src/chat/repositories/subscriber.repository.ts @@ -135,7 +135,7 @@ export class SubscriberRepository extends BaseRepository< */ async findOneByForeignIdAndPopulate(id: string): Promise { const query = this.findByForeignIdQuery(id).populate(this.populate); - const [result] = await this.execute(query, this.clsPopulate); + const [result] = await this.execute(query, SubscriberFull); return result; } @@ -150,7 +150,7 @@ export class SubscriberRepository extends BaseRepository< async updateOneByForeignIdQuery( id: string, updates: SubscriberUpdateDto, - ): Promise { + ): Promise { return await this.updateOne({ foreign_id: id }, updates); } @@ -161,7 +161,9 @@ export class SubscriberRepository extends BaseRepository< * * @returns The updated subscriber entity. */ - async handBackByForeignIdQuery(foreignId: string): Promise { + async handBackByForeignIdQuery( + foreignId: string, + ): Promise { return await this.updateOne( { foreign_id: foreignId, @@ -184,7 +186,7 @@ export class SubscriberRepository extends BaseRepository< async handOverByForeignIdQuery( foreignId: string, userId: string, - ): Promise { + ): Promise { return await this.updateOne( { foreign_id: foreignId, diff --git a/api/src/chat/schemas/block.schema.ts b/api/src/chat/schemas/block.schema.ts index 88d7c0bf..9f155d0e 100644 --- a/api/src/chat/schemas/block.schema.ts +++ b/api/src/chat/schemas/block.schema.ts @@ -139,17 +139,19 @@ export class Block extends BlockStub { @Transform(({ obj }) => obj.nextBlocks.map((elem) => elem.toString())) nextBlocks: string[]; - @Transform(({ obj }) => obj.attachedBlock?.toString() || null) - attachedBlock: string; + @Transform(({ obj }) => + obj.attachedBlock ? obj.attachedBlock.toString() : null, + ) + attachedBlock: string | null; - @Transform(({ obj }) => obj.category.toString()) - category: string; + @Transform(({ obj }) => (obj.category ? obj.category.toString() : null)) + category: string | null; @Exclude() previousBlocks?: never; @Exclude() - attachedToBlock?: never | null; + attachedToBlock?: never; } @Schema({ timestamps: true }) @@ -164,10 +166,10 @@ export class BlockFull extends BlockStub { nextBlocks: Block[]; @Type(() => Block) - attachedBlock: Block; + attachedBlock: Block | null; @Type(() => Category) - category: Category; + category: Category | null; @Type(() => Block) previousBlocks?: Block[]; diff --git a/api/src/chat/schemas/subscriber.schema.ts b/api/src/chat/schemas/subscriber.schema.ts index 5302df41..ba3ea80a 100644 --- a/api/src/chat/schemas/subscriber.schema.ts +++ b/api/src/chat/schemas/subscriber.schema.ts @@ -80,13 +80,13 @@ export class SubscriberStub extends BaseSchema { ref: 'User', default: null, }) - assignedTo?: unknown; + assignedTo: unknown; @Prop({ type: Date, default: null, }) - assignedAt?: Date; + assignedAt: Date | null; @Prop({ type: Date, @@ -110,13 +110,13 @@ export class SubscriberStub extends BaseSchema { ref: 'Attachment', default: null, }) - avatar?: unknown; + avatar: unknown; @Prop({ type: Object, default: { vars: {} }, }) - context?: SubscriberContext; + context: SubscriberContext; static getChannelData< C extends ChannelName, @@ -131,11 +131,11 @@ export class Subscriber extends SubscriberStub { @Transform(({ obj }) => obj.labels.map((label) => label.toString())) labels: string[]; - @Transform(({ obj }) => (obj.assignedTo ? obj.assignedTo.toString() : null)) - assignedTo?: string; + @Transform(({ obj }) => obj.assignedTo?.toString() || null) + assignedTo: string | null; @Transform(({ obj }) => obj.avatar?.toString() || null) - avatar?: string; + avatar: string | null; } @Schema({ timestamps: true }) @@ -144,7 +144,7 @@ export class SubscriberFull extends SubscriberStub { labels: Label[]; @Type(() => User) - assignedTo?: User | null; + assignedTo: User | null; @Type(() => Attachment) avatar: Attachment | null; diff --git a/api/src/chat/schemas/types/channel.ts b/api/src/chat/schemas/types/channel.ts index d53a3c60..06359493 100644 --- a/api/src/chat/schemas/types/channel.ts +++ b/api/src/chat/schemas/types/channel.ts @@ -8,14 +8,12 @@ import { ChannelName } from '@/channel/types'; -export type SubscriberChannelData< - C extends ChannelName = null, - // K extends keyof SubscriberChannelDict[C] = keyof SubscriberChannelDict[C], -> = C extends null - ? { name: ChannelName } - : { - name: C; - } & { - // Channel's specific attributes - [P in keyof SubscriberChannelDict[C]]: SubscriberChannelDict[C][P]; - }; +export type SubscriberChannelData = + C extends 'unknown-channel' + ? { name: ChannelName } + : { + name: C; + } & { + // Channel's specific attributes + [P in keyof SubscriberChannelDict[C]]: SubscriberChannelDict[C][P]; + }; diff --git a/api/src/chat/services/block.service.spec.ts b/api/src/chat/services/block.service.spec.ts index e81840bb..cf436248 100644 --- a/api/src/chat/services/block.service.spec.ts +++ b/api/src/chat/services/block.service.spec.ts @@ -154,11 +154,11 @@ describe('BlockService', () => { contentTypeService = module.get(ContentTypeService); categoryRepository = module.get(CategoryRepository); blockRepository = module.get(BlockRepository); - category = await categoryRepository.findOne({ label: 'default' }); - hasPreviousBlocks = await blockRepository.findOne({ + category = (await categoryRepository.findOne({ label: 'default' }))!; + hasPreviousBlocks = (await blockRepository.findOne({ name: 'hasPreviousBlocks', - }); - block = await blockRepository.findOne({ name: 'hasNextBlocks' }); + }))!; + block = (await blockRepository.findOne({ name: 'hasNextBlocks' }))!; settings = await settingService.getSettings(); }); @@ -179,6 +179,7 @@ describe('BlockService', () => { category, nextBlocks: [hasPreviousBlocks], previousBlocks: [], + attachedToBlock: null, }); }); }); @@ -194,6 +195,7 @@ describe('BlockService', () => { blockFixture.name === 'hasPreviousBlocks' ? [block] : [], nextBlocks: blockFixture.name === 'hasNextBlocks' ? [hasPreviousBlocks] : [], + attachedToBlock: null, })); expect(blockRepository.findAndPopulate).toHaveBeenCalledWith( @@ -380,7 +382,7 @@ describe('BlockService', () => { }, blockGetStarted, ); - expect(result).toEqual(blockGetStarted.patterns[3]); + expect(result).toEqual(blockGetStarted.patterns?.[3]); }); it("should match payload when it's an attachment file", () => { @@ -397,7 +399,7 @@ describe('BlockService', () => { }, blockGetStarted, ); - expect(result).toEqual(blockGetStarted.patterns[4]); + expect(result).toEqual(blockGetStarted.patterns?.[4]); }); }); @@ -439,8 +441,10 @@ describe('BlockService', () => { describe('processMessage', () => { it('should process list message (with limit = 2 and skip = 0)', async () => { - const contentType = await contentTypeService.findOne({ name: 'Product' }); - blockProductListMock.options.content.entity = contentType.id; + const contentType = (await contentTypeService.findOne({ + name: 'Product', + }))!; + blockProductListMock.options.content!.entity = contentType.id; const result = await blockService.processMessage( blockProductListMock, { @@ -457,13 +461,13 @@ describe('BlockService', () => { ); const flattenedElements = elements.map(Content.toElement); expect(result.format).toEqualPayload( - blockProductListMock.options.content?.display, + blockProductListMock.options.content!.display, ); expect( (result.message as StdOutgoingListMessage).elements, ).toEqualPayload(flattenedElements); expect((result.message as StdOutgoingListMessage).options).toEqualPayload( - blockProductListMock.options.content, + blockProductListMock.options.content!, ); expect( (result.message as StdOutgoingListMessage).pagination, @@ -471,8 +475,10 @@ describe('BlockService', () => { }); it('should process list message (with limit = 2 and skip = 2)', async () => { - const contentType = await contentTypeService.findOne({ name: 'Product' }); - blockProductListMock.options.content.entity = contentType.id; + const contentType = (await contentTypeService.findOne({ + name: 'Product', + }))!; + blockProductListMock.options.content!.entity = contentType.id; const result = await blockService.processMessage( blockProductListMock, { diff --git a/api/src/chat/services/block.service.ts b/api/src/chat/services/block.service.ts index 736c5595..2b6f6346 100644 --- a/api/src/chat/services/block.service.ts +++ b/api/src/chat/services/block.service.ts @@ -440,7 +440,7 @@ export class BlockService extends BaseService< subscriberContext: SubscriberContext, fallback = false, conversationId?: string, - ) { + ): Promise { const settings = await this.settingService.getSettings(); const blockMessage: BlockMessage = fallback && block.options?.fallback @@ -592,13 +592,18 @@ export class BlockService extends BaseService< ); // Process custom plugin block try { - return await plugin?.process(block, context, conversationId); + const envelope = await plugin?.process(block, context, conversationId); + + if (!envelope) { + throw new Error('Unable to find envelope'); + } + + return envelope; } catch (e) { this.logger.error('Plugin was unable to load/process ', e); throw new Error(`Unknown plugin - ${JSON.stringify(blockMessage)}`); } - } else { - throw new Error('Invalid message format.'); } + throw new Error('Invalid message format.'); } } diff --git a/api/src/chat/services/bot.service.spec.ts b/api/src/chat/services/bot.service.spec.ts index bf71c0b3..c4bf517a 100644 --- a/api/src/chat/services/bot.service.spec.ts +++ b/api/src/chat/services/bot.service.spec.ts @@ -193,9 +193,9 @@ describe('BlockService', () => { }); const [block] = await blockService.findAndPopulate({ patterns: ['Hi'] }); - const webSubscriber = await subscriberService.findOne({ + const webSubscriber = (await subscriberService.findOne({ foreign_id: 'foreign-id-web-1', - }); + }))!; event.setSender(webSubscriber); @@ -260,9 +260,9 @@ describe('BlockService', () => { ipAddress: '1.1.1.1', agent: 'Chromium', }); - const webSubscriber = await subscriberService.findOne({ + const webSubscriber = (await subscriberService.findOne({ foreign_id: 'foreign-id-web-1', - }); + }))!; event.setSender(webSubscriber); const clearMock = jest @@ -314,9 +314,9 @@ describe('BlockService', () => { ipAddress: '1.1.1.1', agent: 'Chromium', }); - const webSubscriber = await subscriberService.findOne({ + const webSubscriber = (await subscriberService.findOne({ foreign_id: 'foreign-id-web-2', - }); + }))!; event.setSender(webSubscriber); const captured = await botService.processConversationMessage(event); diff --git a/api/src/chat/services/bot.service.ts b/api/src/chat/services/bot.service.ts index 93cb4f1b..8a9ab5ed 100644 --- a/api/src/chat/services/bot.service.ts +++ b/api/src/chat/services/bot.service.ts @@ -21,10 +21,8 @@ import { getDefaultConversationContext, } from '../schemas/conversation.schema'; import { Context } from '../schemas/types/context'; -import { - IncomingMessageType, - StdOutgoingEnvelope, -} from '../schemas/types/message'; +import { IncomingMessageType } from '../schemas/types/message'; +import { SubscriberContext } from '../schemas/types/subscriberContext'; import { BlockService } from './block.service'; import { ConversationService } from './conversation.service'; @@ -70,14 +68,13 @@ export class BotService { ); // Process message : Replace tokens with context data and then send the message const recipient = event.getSender(); - const envelope: StdOutgoingEnvelope = - await this.blockService.processMessage( - block, - context, - recipient?.context, - fallback, - conservationId, - ); + const envelope = await this.blockService.processMessage( + block, + context, + recipient?.context as SubscriberContext, + fallback, + conservationId, + ); // Send message through the right channel const response = await event @@ -251,8 +248,8 @@ export class BotService { // If there's labels, they should be already have been assigned assign_labels: [], trigger_labels: [], - attachedBlock: undefined, - category: undefined, + attachedBlock: null, + category: null, previousBlocks: [], }; convo.context.attempt++; diff --git a/api/src/chat/services/chat.service.ts b/api/src/chat/services/chat.service.ts index a8012550..5fa9fcc2 100644 --- a/api/src/chat/services/chat.service.ts +++ b/api/src/chat/services/chat.service.ts @@ -117,6 +117,12 @@ export class ChatService { try { const msg = await this.messageService.create(received); const populatedMsg = await this.messageService.findOneAndPopulate(msg.id); + + if (!populatedMsg) { + this.logger.warn('Unable to find populated message.', event); + throw new Error(`Unable to find Message by ID ${msg.id} not found`); + } + this.websocketGateway.broadcastMessageReceived(populatedMsg, subscriber); this.eventEmitter.emit('hook:stats:entry', 'incoming', 'Incoming'); this.eventEmitter.emit( @@ -290,7 +296,9 @@ export class ChatService { @OnEvent('hook:subscriber:postCreate') async onSubscriberCreate({ _id }: SubscriberDocument) { const subscriber = await this.subscriberService.findOne(_id); - this.websocketGateway.broadcastSubscriberNew(subscriber); + if (subscriber) { + this.websocketGateway.broadcastSubscriberNew(subscriber); + } } /** @@ -301,6 +309,8 @@ export class ChatService { @OnEvent('hook:subscriber:postUpdate') async onSubscriberUpdate({ _id }: SubscriberDocument) { const subscriber = await this.subscriberService.findOne(_id); - this.websocketGateway.broadcastSubscriberUpdate(subscriber); + if (subscriber) { + this.websocketGateway.broadcastSubscriberUpdate(subscriber); + } } } diff --git a/api/src/chat/services/conversation.service.ts b/api/src/chat/services/conversation.service.ts index 035c54fc..118f367f 100644 --- a/api/src/chat/services/conversation.service.ts +++ b/api/src/chat/services/conversation.service.ts @@ -70,6 +70,7 @@ export class ConversationService extends BaseService< ) { const msgType = event.getMessageType(); const profile = event.getSender(); + // Capture channel specific context data convo.context.channel = event.getHandler().getName(); convo.context.text = event.getText(); @@ -83,7 +84,7 @@ export class ConversationService extends BaseService< // Capture user entry in context vars if (captureVars && next.capture_vars && next.capture_vars.length > 0) { next.capture_vars.forEach((capture) => { - let contextValue: string | Payload; + let contextValue: string | Payload | undefined; const nlp = event.getNLP(); @@ -105,7 +106,7 @@ export class ConversationService extends BaseService< if (capture.entity === -1) { // Capture the whole message contextValue = - ['message', 'quick_reply'].indexOf(msgType) !== -1 + msgType && ['message', 'quick_reply'].indexOf(msgType) !== -1 ? event.getText() : event.getPayload(); } else if (capture.entity === -2) { @@ -115,13 +116,16 @@ export class ConversationService extends BaseService< contextValue = typeof contextValue === 'string' ? contextValue.trim() : contextValue; - if (contextVars[capture.context_var]?.permanent) { + if ( + profile.context?.vars && + contextVars[capture.context_var]?.permanent + ) { Logger.debug( `Adding context var to subscriber: ${capture.context_var} = ${contextValue}`, ); profile.context.vars[capture.context_var] = contextValue; } else { - convo.context.vars[capture.context_var] = contextValue; + convo.context!.vars[capture.context_var] = contextValue; } }); } @@ -160,6 +164,7 @@ export class ConversationService extends BaseService< // Deal with load more in the case of a list display if ( + next.options && next.options.content && (next.options.content.display === OutgoingMessageFormat.list || next.options.content.display === OutgoingMessageFormat.carousel) diff --git a/api/src/chat/services/message.service.spec.ts b/api/src/chat/services/message.service.spec.ts index e464cb2e..1e7899b8 100644 --- a/api/src/chat/services/message.service.spec.ts +++ b/api/src/chat/services/message.service.spec.ts @@ -90,16 +90,15 @@ describe('MessageService', () => { allSubscribers = await subscriberRepository.findAll(); allUsers = await userRepository.findAll(); allMessages = await messageRepository.findAll(); - message = await messageRepository.findOne({ mid: 'mid-1' }); - sender = await subscriberRepository.findOne(message['sender']); - recipient = await subscriberRepository.findOne(message['recipient']); - user = await userRepository.findOne(message['sentBy']); + message = (await messageRepository.findOne({ mid: 'mid-1' }))!; + sender = (await subscriberRepository.findOne(message.sender!))!; + recipient = (await subscriberRepository.findOne(message.recipient!))!; + user = (await userRepository.findOne(message.sentBy!))!; messagesWithSenderAndRecipient = allMessages.map((message) => ({ ...message, - sender: allSubscribers.find(({ id }) => id === message['sender']).id, - recipient: allSubscribers.find(({ id }) => id === message['recipient']) - .id, - sentBy: allUsers.find(({ id }) => id === message['sentBy']).id, + sender: allSubscribers.find(({ id }) => id === message.sender)?.id, + recipient: allSubscribers.find(({ id }) => id === message.recipient)?.id, + sentBy: allUsers.find(({ id }) => id === message.sentBy)?.id, })); }); @@ -131,9 +130,9 @@ describe('MessageService', () => { const result = await messageService.findPageAndPopulate({}, pageQuery); const messagesWithSenderAndRecipient = allMessages.map((message) => ({ ...message, - sender: allSubscribers.find(({ id }) => id === message['sender']), - recipient: allSubscribers.find(({ id }) => id === message['recipient']), - sentBy: allUsers.find(({ id }) => id === message['sentBy']).id, + sender: allSubscribers.find(({ id }) => id === message.sender), + recipient: allSubscribers.find(({ id }) => id === message.recipient), + sentBy: allUsers.find(({ id }) => id === message.sentBy)?.id, })); expect(messageRepository.findPageAndPopulate).toHaveBeenCalledWith( @@ -150,7 +149,7 @@ describe('MessageService', () => { new Date().setMonth(new Date().getMonth() + 1), ); const result = await messageService.findHistoryUntilDate( - sender, + sender!, until, 30, ); @@ -166,16 +165,16 @@ describe('MessageService', () => { it('should return history since given date', async () => { const since: Date = new Date(); const result = await messageService.findHistorySinceDate( - sender, + sender!, since, 30, ); const messagesWithSenderAndRecipient = allMessages.map((message) => ({ ...message, - sender: allSubscribers.find(({ id }) => id === message['sender']).id, - recipient: allSubscribers.find(({ id }) => id === message['recipient']) - .id, - sentBy: allUsers.find(({ id }) => id === message['sentBy']).id, + sender: allSubscribers.find(({ id }) => id === message.sender)?.id, + recipient: allSubscribers.find(({ id }) => id === message.recipient) + ?.id, + sentBy: allUsers.find(({ id }) => id === message.sentBy)?.id, })); const historyMessages = messagesWithSenderAndRecipient.filter( (message) => message.createdAt > since, diff --git a/api/src/chat/services/message.service.ts b/api/src/chat/services/message.service.ts index 67474daa..6ee4463c 100644 --- a/api/src/chat/services/message.service.ts +++ b/api/src/chat/services/message.service.ts @@ -46,8 +46,8 @@ export class MessageService extends BaseService< @Optional() gateway?: WebsocketGateway, ) { super(messageRepository); - this.logger = logger; - this.gateway = gateway; + if (logger) this.logger = logger; + if (gateway) this.gateway = gateway; } /** diff --git a/api/src/chat/services/subscriber.service.spec.ts b/api/src/chat/services/subscriber.service.spec.ts index bae61da4..9eec453c 100644 --- a/api/src/chat/services/subscriber.service.spec.ts +++ b/api/src/chat/services/subscriber.service.spec.ts @@ -90,9 +90,9 @@ describe('SubscriberService', () => { describe('findOneAndPopulate', () => { it('should find subscribers, and foreach subscriber populate its corresponding labels', async () => { jest.spyOn(subscriberService, 'findOneAndPopulate'); - const subscriber = await subscriberRepository.findOne({ + const subscriber = (await subscriberRepository.findOne({ first_name: 'Jhon', - }); + }))!; const result = await subscriberService.findOneAndPopulate(subscriber.id); expect(subscriberService.findOneAndPopulate).toHaveBeenCalledWith( @@ -133,7 +133,7 @@ describe('SubscriberService', () => { await subscriberService.findOneByForeignId('foreign-id-dimelo'); const subscriber = allSubscribers.find( ({ foreign_id }) => foreign_id === 'foreign-id-dimelo', - ); + )!; expect(subscriberRepository.findOneByForeignId).toHaveBeenCalled(); expect(result).toEqualPayload({ diff --git a/api/src/chat/services/subscriber.service.ts b/api/src/chat/services/subscriber.service.ts index 6af757e4..0f6e4e89 100644 --- a/api/src/chat/services/subscriber.service.ts +++ b/api/src/chat/services/subscriber.service.ts @@ -52,7 +52,7 @@ export class SubscriberService extends BaseService< @Optional() gateway?: WebsocketGateway, ) { super(repository); - this.gateway = gateway; + if (gateway) this.gateway = gateway; } /** diff --git a/api/src/extensions/channels/web/base-web-channel.ts b/api/src/extensions/channels/web/base-web-channel.ts index 83fb9e3a..99d2992f 100644 --- a/api/src/extensions/channels/web/base-web-channel.ts +++ b/api/src/extensions/channels/web/base-web-channel.ts @@ -6,7 +6,7 @@ * 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 { Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { EventEmitter2, OnEvent } from '@nestjs/event-emitter'; import { Request, Response } from 'express'; import multer, { diskStorage, memoryStorage } from 'multer'; @@ -236,7 +236,7 @@ export default abstract class BaseWebChannelHandler< ...message, author: 'chatbot', read: true, // Temporary fix as read is false in the bd - mid: anyMessage.mid, + mid: anyMessage.mid || this.generateId(), handover: !!anyMessage.handover, createdAt: anyMessage.createdAt, }); @@ -524,6 +524,9 @@ export default abstract class BaseWebChannelHandler< const fetchMessages = async (req: Request, res: Response, retrials = 1) => { try { + if (!req.query.since) + throw new BadRequestException(`QueryParam 'since' is missing`); + const since = new Date(req.query.since.toString()); const messages = await this.pollMessages(req, since); if (messages.length === 0 && retrials <= 5) { @@ -618,7 +621,12 @@ export default abstract class BaseWebChannelHandler< size: Buffer.byteLength(data.file), type: data.type, }); - return attachment; + + if (attachment) { + return attachment; + } else { + throw new Error('Unable to retrieve stored attachment'); + } } catch (err) { this.logger.error( 'Web Channel Handler : Unable to store uploaded file', @@ -636,7 +644,7 @@ export default abstract class BaseWebChannelHandler< async handleWebUpload( req: Request, res: Response, - ): Promise { + ): Promise { try { const upload = multer({ limits: { @@ -662,7 +670,9 @@ export default abstract class BaseWebChannelHandler< reject(new Error('Unable to upload file!')); } - resolve(req.file); + if (req.file) { + resolve(req.file); + } }); }, ); @@ -675,12 +685,18 @@ export default abstract class BaseWebChannelHandler< return null; } - const attachment = await this.attachmentService.store(file, { - name: file.originalname, - size: file.size, - type: file.mimetype, - }); - return attachment; + if (file) { + const attachment = await this.attachmentService.store(file, { + name: file.originalname, + size: file.size, + type: file.mimetype, + }); + if (attachment) { + return attachment; + } + + throw new Error('Unable to store uploaded file'); + } } catch (err) { this.logger.error( 'Web Channel Handler : Unable to store uploaded file', @@ -700,7 +716,7 @@ export default abstract class BaseWebChannelHandler< async handleUpload( req: Request | SocketRequest, res: Response | SocketResponse, - ): Promise { + ): Promise { // Check if any file is provided if (!req.session.web) { this.logger.debug('Web Channel Handler : No session provided'); @@ -747,7 +763,7 @@ export default abstract class BaseWebChannelHandler< return { isSocket: this.isSocketRequest(req), ipAddress: this.getIpAddress(req), - agent: req.headers['user-agent'], + agent: req.headers['user-agent'] || 'browser', }; } @@ -998,7 +1014,13 @@ export default abstract class BaseWebChannelHandler< }, }; if (message.quickReplies && message.quickReplies.length > 0) { - payload.data.quick_replies = message.quickReplies; + return { + ...payload, + data: { + ...payload.data, + quick_replies: message.quickReplies, + } as Web.OutgoingFileMessageData, + }; } return payload; } diff --git a/api/src/extensions/channels/web/wrapper.ts b/api/src/extensions/channels/web/wrapper.ts index b26cf7ac..094ee5ae 100644 --- a/api/src/extensions/channels/web/wrapper.ts +++ b/api/src/extensions/channels/web/wrapper.ts @@ -27,26 +27,31 @@ type WebEventAdapter = eventType: StdEventType.unknown; messageType: never; raw: Web.Event; + attachment: never; } | { eventType: StdEventType.read; messageType: never; raw: Web.StatusReadEvent; + attachment: never; } | { eventType: StdEventType.delivery; messageType: never; raw: Web.StatusDeliveryEvent; + attachment: never; } | { eventType: StdEventType.typing; messageType: never; raw: Web.StatusTypingEvent; + attachment: never; } | { eventType: StdEventType.message; messageType: IncomingMessageType.message; raw: Web.IncomingMessage; + attachment: never; } | { eventType: StdEventType.message; @@ -54,11 +59,13 @@ type WebEventAdapter = | IncomingMessageType.postback | IncomingMessageType.quick_reply; raw: Web.IncomingMessage; + attachment: never; } | { eventType: StdEventType.message; messageType: IncomingMessageType.location; raw: Web.IncomingMessage; + attachment: never; } | { eventType: StdEventType.message; @@ -68,11 +75,9 @@ type WebEventAdapter = }; // eslint-disable-next-line prettier/prettier -export default class WebEventWrapper extends EventWrapper< - WebEventAdapter, - Web.Event, - N -> { +export default class WebEventWrapper< + N extends ChannelName, +> extends EventWrapper { /** * Constructor : channel's event wrapper * diff --git a/api/src/nlp/services/nlp.service.ts b/api/src/nlp/services/nlp.service.ts index e8fe92ad..4b4cae0a 100644 --- a/api/src/nlp/services/nlp.service.ts +++ b/api/src/nlp/services/nlp.service.ts @@ -6,7 +6,7 @@ * 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 { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { HelperService } from '@/helper/helper.service'; @@ -77,9 +77,14 @@ export class NlpService { async handleEntityDelete(entity: NlpEntity) { // Synchonize new entity with NLP provider try { - const helper = await this.helperService.getDefaultNluHelper(); - await helper.deleteEntity(entity.foreign_id); - this.logger.debug('Deleted entity successfully synced!', entity); + if (entity.foreign_id) { + const helper = await this.helperService.getDefaultNluHelper(); + await helper.deleteEntity(entity.foreign_id); + this.logger.debug('Deleted entity successfully synced!', entity); + } else { + this.logger.error(`Entity ${entity} is missing foreign_id`); + throw new NotFoundException(`Entity ${entity} is missing foreign_id`); + } } catch (err) { this.logger.error('Unable to sync deleted entity', err); } @@ -138,8 +143,10 @@ export class NlpService { const populatedValue = await this.nlpValueService.findOneAndPopulate( value.id, ); - await helper.deleteValue(populatedValue); - this.logger.debug('Deleted value successfully synced!', value); + if (populatedValue) { + await helper.deleteValue(populatedValue); + this.logger.debug('Deleted value successfully synced!', value); + } } catch (err) { this.logger.error('Unable to sync deleted value', err); } diff --git a/api/src/utils/test/fixtures/block.ts b/api/src/utils/test/fixtures/block.ts index a1163ed0..a73bc1e3 100644 --- a/api/src/utils/test/fixtures/block.ts +++ b/api/src/utils/test/fixtures/block.ts @@ -29,8 +29,6 @@ export const blockDefaultValues: TBlockFixtures['defaultValues'] = { trigger_channels: [], builtin: false, starts_conversation: false, - attachedBlock: null, - attachedToBlock: null, }; export const blocks: TBlockFixtures['values'][] = [ diff --git a/api/src/utils/test/fixtures/conversation.ts b/api/src/utils/test/fixtures/conversation.ts index fca24ce1..ba469949 100644 --- a/api/src/utils/test/fixtures/conversation.ts +++ b/api/src/utils/test/fixtures/conversation.ts @@ -58,6 +58,9 @@ const conversations: ConversationCreateDto[] = [ labels: [], assignedTo: null, channel: { name: 'messenger-channel' }, + avatar: null, + context: {}, + assignedAt: new Date(), }, skip: {}, attempt: 0, @@ -104,6 +107,9 @@ const conversations: ConversationCreateDto[] = [ labels: [], assignedTo: null, channel: { name: 'web-channel' }, + avatar: null, + context: {}, + assignedAt: new Date(), }, skip: {}, attempt: 0, @@ -136,8 +142,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 + : undefined, + next: conversationFixture.next?.map((n) => blocks[parseInt(n)].id), })), ); }; diff --git a/api/src/utils/test/mocks/block.ts b/api/src/utils/test/mocks/block.ts index 867b5ec3..556d0399 100644 --- a/api/src/utils/test/mocks/block.ts +++ b/api/src/utils/test/mocks/block.ts @@ -99,23 +99,23 @@ export const baseBlockInstance = { ...modelInstance, }; -export const blockEmpty: BlockFull = { +export const blockEmpty = { ...baseBlockInstance, name: 'Empty', patterns: [], message: [''], nextBlocks: [], -}; +} as unknown as BlockFull; // Translation Data export const textResult = ['Hi back !']; -export const textBlock: BlockFull = { +export const textBlock = { name: 'message', patterns: ['Hi'], message: textResult, ...baseBlockInstance, -}; +} as unknown as BlockFull; export const quickRepliesResult = [ "What's your favorite color?", @@ -124,7 +124,7 @@ export const quickRepliesResult = [ 'Red', ]; -export const quickRepliesBlock: BlockFull = { +export const quickRepliesBlock = { name: 'message', patterns: ['colors'], message: { @@ -148,7 +148,7 @@ export const quickRepliesBlock: BlockFull = { ], }, ...baseBlockInstance, -}; +} as unknown as BlockFull; export const buttonsResult = [ 'What would you like to know about us?', @@ -157,7 +157,7 @@ export const buttonsResult = [ 'Approach', ]; -export const buttonsBlock: BlockFull = { +export const buttonsBlock = { name: 'message', patterns: ['about'], message: { @@ -181,9 +181,9 @@ export const buttonsBlock: BlockFull = { ], }, ...baseBlockInstance, -}; +} as unknown as BlockFull; -export const attachmentBlock: BlockFull = { +export const attachmentBlock = { name: 'message', patterns: ['image'], message: { @@ -197,7 +197,7 @@ export const attachmentBlock: BlockFull = { quickReplies: [], }, ...baseBlockInstance, -}; +} as unknown as BlockFull; export const allBlocksStringsResult = [ 'Hi back !', @@ -216,7 +216,7 @@ export const allBlocksStringsResult = [ ///////// -export const blockGetStarted: BlockFull = { +export const blockGetStarted = { ...baseBlockInstance, name: 'Get Started', patterns: [ @@ -247,7 +247,7 @@ export const blockGetStarted: BlockFull = { ], trigger_labels: customerLabelsMock, message: ['Welcome! How are you ? '], -}; +} as unknown as BlockFull; const patternsProduct: Pattern[] = [ 'produit', @@ -264,7 +264,7 @@ const patternsProduct: Pattern[] = [ ], ]; -export const blockProductListMock: BlockFull = { +export const blockProductListMock = { ...baseBlockInstance, name: 'test_list', patterns: patternsProduct, @@ -280,11 +280,11 @@ export const blockProductListMock: BlockFull = { limit: 0, }, }, -}; +} as unknown as BlockFull; -export const blockCarouselMock: BlockFull = { +export const blockCarouselMock = { ...blockProductListMock, options: blockCarouselOptions, -}; +} as unknown as BlockFull; export const blocks: BlockFull[] = [blockGetStarted, blockEmpty]; diff --git a/api/src/utils/test/mocks/subscriber.ts b/api/src/utils/test/mocks/subscriber.ts index 8ec8ee83..490a6bd5 100644 --- a/api/src/utils/test/mocks/subscriber.ts +++ b/api/src/utils/test/mocks/subscriber.ts @@ -28,6 +28,8 @@ export const subscriberInstance: Subscriber = { name: 'web-channel', }, labels: [], + avatar: null, + context: {}, ...modelInstance, }; diff --git a/api/src/utils/test/sort.ts b/api/src/utils/test/sort.ts index ddffd00f..28215170 100644 --- a/api/src/utils/test/sort.ts +++ b/api/src/utils/test/sort.ts @@ -13,14 +13,20 @@ type TSortProps = { order?: 'desc' | 'asc'; }; -const sort = ({ +type TCreatedAt = { createdAt?: string | Date }; + +const sort = ({ row1, row2, field = 'createdAt', order = 'desc', }: TSortProps) => (order === 'asc' && row1[field] > row2[field] ? 1 : -1); -export const sortRowsBy = ( +export const sortRowsBy = < + R extends TCreatedAt, + S, + T extends TCreatedAt = R & S, +>( row1: T, row2: T, field?: keyof T, diff --git a/api/src/utils/types/filter.types.ts b/api/src/utils/types/filter.types.ts index 764d5542..e859d3a7 100644 --- a/api/src/utils/types/filter.types.ts +++ b/api/src/utils/types/filter.types.ts @@ -65,12 +65,16 @@ type TAllowedKeys = { >]: TValue; }; +type TVirtualFields = Pick>; + export type TValidateProps = { dto: | Partial> | Partial>; - allowedIds: TAllowedKeys & - TAllowedKeys; + allowedIds: Omit< + TAllowedKeys, + keyof TVirtualFields + >; }; //populate types