mirror of
https://github.com/hexastack/hexabot
synced 2025-06-03 11:06:34 +00:00
Merge pull request #547 from Hexastack/545-issue-chat-module-strictnullchecks-issues
fix: apply strict null checks updates to the Chat Module
This commit is contained in:
commit
0211153a82
@ -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: '',
|
||||
|
@ -28,6 +28,8 @@ export const subscriberInstance: Subscriber = {
|
||||
name: 'web-channel',
|
||||
},
|
||||
labels: [],
|
||||
avatar: null,
|
||||
context: {},
|
||||
...modelInstance,
|
||||
};
|
||||
|
||||
|
@ -142,18 +142,19 @@ describe('BlockController', () => {
|
||||
blockController = module.get<BlockController>(BlockController);
|
||||
blockService = module.get<BlockService>(BlockService);
|
||||
categoryService = module.get<CategoryService>(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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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: {
|
||||
|
@ -56,15 +56,16 @@ describe('ContextVarController', () => {
|
||||
contextVarController =
|
||||
module.get<ContextVarController>(ContextVarController);
|
||||
contextVarService = module.get<ContextVarService>(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)!,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -128,16 +128,17 @@ describe('MessageController', () => {
|
||||
userService = module.get<UserService>(UserService);
|
||||
subscriberService = module.get<SubscriberService>(SubscriberService);
|
||||
messageController = module.get<MessageController>(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(
|
||||
|
@ -90,15 +90,16 @@ describe('SubscriberController', () => {
|
||||
|
||||
subscriberController =
|
||||
module.get<SubscriberController>(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,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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<StreamableFile> {
|
||||
async getAvatar(
|
||||
@Param('id') id: string,
|
||||
): Promise<StreamableFile | undefined> {
|
||||
const subscriber = await this.subscriberService.findOneAndPopulate(id);
|
||||
|
||||
if (!subscriber) {
|
||||
|
@ -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',
|
||||
|
@ -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');
|
||||
|
||||
|
@ -38,7 +38,7 @@ export class ContextVarRepository extends BaseRepository<
|
||||
@Optional() blockService?: BlockService,
|
||||
) {
|
||||
super(eventEmitter, model, ContextVar);
|
||||
this.blockService = blockService;
|
||||
if (blockService) this.blockService = blockService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -135,7 +135,7 @@ export class SubscriberRepository extends BaseRepository<
|
||||
*/
|
||||
async findOneByForeignIdAndPopulate(id: string): Promise<SubscriberFull> {
|
||||
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<Subscriber> {
|
||||
): Promise<Subscriber | null> {
|
||||
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<Subscriber> {
|
||||
async handBackByForeignIdQuery(
|
||||
foreignId: string,
|
||||
): Promise<Subscriber | null> {
|
||||
return await this.updateOne(
|
||||
{
|
||||
foreign_id: foreignId,
|
||||
@ -184,7 +186,7 @@ export class SubscriberRepository extends BaseRepository<
|
||||
async handOverByForeignIdQuery(
|
||||
foreignId: string,
|
||||
userId: string,
|
||||
): Promise<Subscriber> {
|
||||
): Promise<Subscriber | null> {
|
||||
return await this.updateOne(
|
||||
{
|
||||
foreign_id: foreignId,
|
||||
|
@ -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[];
|
||||
|
@ -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;
|
||||
|
@ -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 ChannelName = 'unknown-channel'> =
|
||||
C extends 'unknown-channel'
|
||||
? { name: ChannelName }
|
||||
: {
|
||||
name: C;
|
||||
} & {
|
||||
// Channel's specific attributes
|
||||
[P in keyof SubscriberChannelDict[C]]: SubscriberChannelDict[C][P];
|
||||
};
|
||||
|
@ -154,11 +154,11 @@ describe('BlockService', () => {
|
||||
contentTypeService = module.get<ContentTypeService>(ContentTypeService);
|
||||
categoryRepository = module.get<CategoryRepository>(CategoryRepository);
|
||||
blockRepository = module.get<BlockRepository>(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,
|
||||
{
|
||||
|
@ -440,7 +440,7 @@ export class BlockService extends BaseService<
|
||||
subscriberContext: SubscriberContext,
|
||||
fallback = false,
|
||||
conversationId?: string,
|
||||
) {
|
||||
): Promise<StdOutgoingEnvelope> {
|
||||
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.');
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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++;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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({
|
||||
|
@ -52,7 +52,7 @@ export class SubscriberService extends BaseService<
|
||||
@Optional() gateway?: WebsocketGateway,
|
||||
) {
|
||||
super(repository);
|
||||
this.gateway = gateway;
|
||||
if (gateway) this.gateway = gateway;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<Attachment | null> {
|
||||
): Promise<Attachment | null | undefined> {
|
||||
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<Attachment | null> {
|
||||
): Promise<Attachment | null | undefined> {
|
||||
// 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;
|
||||
}
|
||||
|
@ -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<Web.IncomingTextMessage>;
|
||||
attachment: never;
|
||||
}
|
||||
| {
|
||||
eventType: StdEventType.message;
|
||||
@ -54,11 +59,13 @@ type WebEventAdapter =
|
||||
| IncomingMessageType.postback
|
||||
| IncomingMessageType.quick_reply;
|
||||
raw: Web.IncomingMessage<Web.IncomingPayloadMessage>;
|
||||
attachment: never;
|
||||
}
|
||||
| {
|
||||
eventType: StdEventType.message;
|
||||
messageType: IncomingMessageType.location;
|
||||
raw: Web.IncomingMessage<Web.IncomingLocationMessage>;
|
||||
attachment: never;
|
||||
}
|
||||
| {
|
||||
eventType: StdEventType.message;
|
||||
@ -68,11 +75,9 @@ type WebEventAdapter =
|
||||
};
|
||||
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
export default class WebEventWrapper<N extends ChannelName> extends EventWrapper<
|
||||
WebEventAdapter,
|
||||
Web.Event,
|
||||
N
|
||||
> {
|
||||
export default class WebEventWrapper<
|
||||
N extends ChannelName,
|
||||
> extends EventWrapper<WebEventAdapter, Web.Event, N> {
|
||||
/**
|
||||
* Constructor : channel's event wrapper
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
|
2
api/src/utils/test/fixtures/block.ts
vendored
2
api/src/utils/test/fixtures/block.ts
vendored
@ -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'][] = [
|
||||
|
12
api/src/utils/test/fixtures/conversation.ts
vendored
12
api/src/utils/test/fixtures/conversation.ts
vendored
@ -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),
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
@ -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];
|
||||
|
@ -28,6 +28,8 @@ export const subscriberInstance: Subscriber = {
|
||||
name: 'web-channel',
|
||||
},
|
||||
labels: [],
|
||||
avatar: null,
|
||||
context: {},
|
||||
...modelInstance,
|
||||
};
|
||||
|
||||
|
@ -13,14 +13,20 @@ type TSortProps<T> = {
|
||||
order?: 'desc' | 'asc';
|
||||
};
|
||||
|
||||
const sort = <R, S, T extends { createdAt?: string } = R & S>({
|
||||
type TCreatedAt = { createdAt?: string | Date };
|
||||
|
||||
const sort = <R extends TCreatedAt, S, T extends TCreatedAt = R & S>({
|
||||
row1,
|
||||
row2,
|
||||
field = 'createdAt',
|
||||
order = 'desc',
|
||||
}: TSortProps<T>) => (order === 'asc' && row1[field] > row2[field] ? 1 : -1);
|
||||
|
||||
export const sortRowsBy = <R, S, T = R & S>(
|
||||
export const sortRowsBy = <
|
||||
R extends TCreatedAt,
|
||||
S,
|
||||
T extends TCreatedAt = R & S,
|
||||
>(
|
||||
row1: T,
|
||||
row2: T,
|
||||
field?: keyof T,
|
||||
|
@ -65,12 +65,16 @@ type TAllowedKeys<T, TStub, TValue = (string | null | undefined)[]> = {
|
||||
>]: TValue;
|
||||
};
|
||||
|
||||
type TVirtualFields<T> = Pick<T, TFilterKeysOfType<T, undefined>>;
|
||||
|
||||
export type TValidateProps<T, TStub> = {
|
||||
dto:
|
||||
| Partial<TAllowedKeys<T, TStub>>
|
||||
| Partial<TAllowedKeys<T, TStub, string>>;
|
||||
allowedIds: TAllowedKeys<T, TStub> &
|
||||
TAllowedKeys<T, TStub, string | null | undefined>;
|
||||
allowedIds: Omit<
|
||||
TAllowedKeys<T, TStub, null | undefined | string | string[]>,
|
||||
keyof TVirtualFields<T>
|
||||
>;
|
||||
};
|
||||
|
||||
//populate types
|
||||
|
Loading…
Reference in New Issue
Block a user