diff --git a/api/src/channel/lib/Handler.ts b/api/src/channel/lib/Handler.ts index c454b365..f797537d 100644 --- a/api/src/channel/lib/Handler.ts +++ b/api/src/channel/lib/Handler.ts @@ -214,7 +214,7 @@ export default abstract class ChannelHandler< envelope: StdOutgoingEnvelope, options: any, context: any, - ): Promise<{ mid: string }>; + ): Promise<{ mid: string | string[] }>; /** * Calls the channel handler to fetch attachments and stores them diff --git a/api/src/chat/controllers/message.controller.spec.ts b/api/src/chat/controllers/message.controller.spec.ts index 0bc9e2a5..92a4787a 100644 --- a/api/src/chat/controllers/message.controller.spec.ts +++ b/api/src/chat/controllers/message.controller.spec.ts @@ -144,6 +144,10 @@ describe('MessageController', () => { afterAll(closeInMongodConnection); + function toArray(value?: string | string[]): string[] { + return value ? (Array.isArray(value) ? value : [value]) : []; + } + describe('count', () => { it('should count messages', async () => { jest.spyOn(messageService, 'count'); @@ -165,8 +169,13 @@ describe('MessageController', () => { expect(messageService.findOneAndPopulate).toHaveBeenCalledWith( message.id, ); + const expectedFixture = messageFixtures.find( + ({ mid }) => + JSON.stringify(toArray(mid)) === JSON.stringify(message.mid), + ); expect(result).toEqualPayload({ - ...messageFixtures.find(({ mid }) => mid === message.mid), + ...expectedFixture, + mid: toArray(expectedFixture?.mid), sender, recipient, sentBy: user.id, @@ -177,8 +186,13 @@ describe('MessageController', () => { const result = await messageController.findOne(message.id, []); expect(messageService.findOne).toHaveBeenCalledWith(message.id); + const expectedFixture = messageFixtures.find( + ({ mid }) => + JSON.stringify(toArray(mid)) === JSON.stringify(message.mid), + ); expect(result).toEqualPayload({ - ...messageFixtures.find(({ mid }) => mid === message.mid), + ...expectedFixture, + mid: toArray(expectedFixture?.mid), sender: sender.id, recipient: recipient.id, sentBy: user.id, diff --git a/api/src/chat/dto/message.dto.ts b/api/src/chat/dto/message.dto.ts index a5855bde..3e1b6e14 100644 --- a/api/src/chat/dto/message.dto.ts +++ b/api/src/chat/dto/message.dto.ts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -11,8 +11,8 @@ import { IsBoolean, IsNotEmpty, IsObject, - IsString, IsOptional, + IsString, } from 'class-validator'; import { IsObjectId } from '@/utils/validation-rules/is-object-id'; @@ -27,7 +27,7 @@ export class MessageCreateDto { @ApiProperty({ description: 'Message id', type: String }) @IsOptional() @IsString() - mid?: string; + mid?: string | string[]; @ApiProperty({ description: 'Reply to Message id', type: String }) @IsOptional() diff --git a/api/src/chat/repositories/message.repository.spec.ts b/api/src/chat/repositories/message.repository.spec.ts index 784df2cd..10cc1b7e 100644 --- a/api/src/chat/repositories/message.repository.spec.ts +++ b/api/src/chat/repositories/message.repository.spec.ts @@ -56,7 +56,11 @@ describe('MessageRepository', () => { afterAll(closeInMongodConnection); describe('findOneAndPopulate', () => { - it('should find one message by id, and populate its sender and recipient', async () => { + function toArray(value?: string | string[]): string[] { + return value ? (Array.isArray(value) ? value : [value]) : []; + } + + it('should find one message by id, and populate its sender and recipient (with mid being a string)', async () => { jest.spyOn(messageModel, 'findById'); const message = (await messageRepository.findOne({ mid: 'mid-1' }))!; const sender = await subscriberRepository.findOne(message!['sender']); @@ -67,8 +71,38 @@ describe('MessageRepository', () => { const result = await messageRepository.findOneAndPopulate(message.id); expect(messageModel.findById).toHaveBeenCalledWith(message.id, undefined); + + const expectedFixture = messageFixtures.find( + ({ mid }) => + JSON.stringify(toArray(mid)) === JSON.stringify(message.mid), + ); expect(result).toEqualPayload({ - ...messageFixtures.find(({ mid }) => mid === message.mid), + ...expectedFixture, + mid: toArray(expectedFixture?.mid), + sender, + recipient, + sentBy: user.id, + }); + }); + it('should find one message by id, and populate its sender and recipient (with mid being a string array)', async () => { + jest.spyOn(messageModel, 'findById'); + const message = (await messageRepository.findOne({ mid: 'mid-2' }))!; + const sender = await subscriberRepository.findOne(message!['sender']); + const recipient = await subscriberRepository.findOne( + message!['recipient'], + ); + const user = (await userRepository.findOne(message!['sentBy']))!; + const result = await messageRepository.findOneAndPopulate(message.id); + + expect(messageModel.findById).toHaveBeenCalledWith(message.id, undefined); + + const expectedFixture = messageFixtures.find( + ({ mid }) => + JSON.stringify(toArray(mid)) === JSON.stringify(message.mid), + ); + expect(result).toEqualPayload({ + ...expectedFixture, + mid: toArray(expectedFixture?.mid), sender, recipient, sentBy: user.id, diff --git a/api/src/chat/schemas/message.schema.ts b/api/src/chat/schemas/message.schema.ts index 302db508..4200e367 100644 --- a/api/src/chat/schemas/message.schema.ts +++ b/api/src/chat/schemas/message.schema.ts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -20,11 +20,11 @@ import { StdIncomingMessage, StdOutgoingMessage } from './types/message'; @Schema({ timestamps: true }) export class MessageStub extends BaseSchema { @Prop({ - type: String, + type: [String], required: false, //TODO : add default value for mid }) - mid?: string; + mid?: string | string[]; @Prop({ type: MongooseSchema.Types.ObjectId, @@ -96,9 +96,12 @@ export class MessageFull extends MessageStub { sentBy?: string; // sendBy is never populate } +const MessageSchema = SchemaFactory.createForClass(MessageStub); +MessageSchema.index({ mid: 1 }); + export const MessageModel: ModelDefinition = LifecycleHookManager.attach({ name: Message.name, - schema: SchemaFactory.createForClass(MessageStub), + schema: MessageSchema, }); export default MessageModel.schema; diff --git a/api/src/chat/services/message.service.spec.ts b/api/src/chat/services/message.service.spec.ts index 8952c244..b47a6872 100644 --- a/api/src/chat/services/message.service.spec.ts +++ b/api/src/chat/services/message.service.spec.ts @@ -107,6 +107,9 @@ describe('MessageService', () => { afterEach(jest.clearAllMocks); afterAll(closeInMongodConnection); + function toArray(value?: string | string[]): string[] { + return value ? (Array.isArray(value) ? value : [value]) : []; + } describe('findOneAndPopulate', () => { it('should find message by id, and populate its corresponding sender and recipient', async () => { jest.spyOn(messageRepository, 'findOneAndPopulate'); @@ -116,8 +119,13 @@ describe('MessageService', () => { message.id, undefined, ); + const expectedFixture = messageFixtures.find( + ({ mid }) => + JSON.stringify(toArray(mid)) === JSON.stringify(message.mid), + ); expect(result).toEqualPayload({ - ...messageFixtures.find(({ mid }) => mid === message.mid), + ...expectedFixture, + mid: toArray(expectedFixture?.mid), sender, recipient, sentBy: user.id, diff --git a/api/src/extensions/channels/web/base-web-channel.ts b/api/src/extensions/channels/web/base-web-channel.ts index 97ab1944..52383de2 100644 --- a/api/src/extensions/channels/web/base-web-channel.ts +++ b/api/src/extensions/channels/web/base-web-channel.ts @@ -241,7 +241,7 @@ export default abstract class BaseWebChannelHandler< ...message, author: anyMessage.sender, read: true, // Temporary fix as read is false in the bd - mid: anyMessage.mid, + mid: anyMessage.mid?.[0], createdAt: anyMessage.createdAt, }); } else { @@ -250,7 +250,7 @@ export default abstract class BaseWebChannelHandler< ...message, author: 'chatbot', read: true, // Temporary fix as read is false in the bd - mid: anyMessage.mid || this.generateId(), + mid: anyMessage.mid?.[0] || this.generateId(), handover: !!anyMessage.handover, createdAt: anyMessage.createdAt, }); diff --git a/api/src/utils/test/fixtures/message.ts b/api/src/utils/test/fixtures/message.ts index 5f70796a..d2496bbd 100644 --- a/api/src/utils/test/fixtures/message.ts +++ b/api/src/utils/test/fixtures/message.ts @@ -9,7 +9,7 @@ import mongoose from 'mongoose'; import { MessageCreateDto } from '@/chat/dto/message.dto'; -import { MessageModel, Message } from '@/chat/schemas/message.schema'; +import { Message, MessageModel } from '@/chat/schemas/message.schema'; import { getFixturesWithDefaultValues } from '../defaultValues'; import { TFixturesDefaultValues } from '../types'; @@ -27,7 +27,7 @@ const messages: MessageCreateDto[] = [ delivery: true, }, { - mid: 'mid-2', + mid: ['mid-2', 'mid-2.1'], sender: '1', recipient: '1', sentBy: '0',