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/schemas/subscriber.schema.ts b/api/src/chat/schemas/subscriber.schema.ts index f2a8e1cf..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 | null; + 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 | null; + @Transform(({ obj }) => obj.assignedTo?.toString() || null) + assignedTo: string | null; @Transform(({ obj }) => obj.avatar?.toString() || null) - avatar?: string | null; + 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/services/bot.service.spec.ts b/api/src/chat/services/bot.service.spec.ts index dcebfa35..c4bf517a 100644 --- a/api/src/chat/services/bot.service.spec.ts +++ b/api/src/chat/services/bot.service.spec.ts @@ -193,11 +193,11 @@ 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!); + event.setSender(webSubscriber); let hasBotSpoken = false; const clearMock = jest @@ -210,15 +210,15 @@ describe('BlockService', () => { isFallback: boolean, ) => { expect(actualConversation).toEqualPayload({ - sender: webSubscriber!.id, + sender: webSubscriber.id, active: true, next: [], context: { user: { - first_name: webSubscriber!.first_name, - last_name: webSubscriber!.last_name, + first_name: webSubscriber.first_name, + last_name: webSubscriber.last_name, language: 'en', - id: webSubscriber!.id, + id: webSubscriber.id, }, user_location: { lat: 0, @@ -260,10 +260,10 @@ 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!); + }))!; + event.setSender(webSubscriber); const clearMock = jest .spyOn(botService, 'handleIncomingMessage') @@ -278,10 +278,10 @@ describe('BlockService', () => { active: true, context: { user: { - first_name: webSubscriber!.first_name, - last_name: webSubscriber!.last_name, + first_name: webSubscriber.first_name, + last_name: webSubscriber.last_name, language: 'en', - id: webSubscriber!.id, + id: webSubscriber.id, }, user_location: { lat: 0, lon: 0 }, vars: {}, @@ -314,10 +314,10 @@ 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!); + }))!; + event.setSender(webSubscriber); const captured = await botService.processConversationMessage(event); expect(captured).toBe(false); diff --git a/api/src/extensions/channels/web/base-web-channel.ts b/api/src/extensions/channels/web/base-web-channel.ts index 28c49bff..99d2992f 100644 --- a/api/src/extensions/channels/web/base-web-channel.ts +++ b/api/src/extensions/channels/web/base-web-channel.ts @@ -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 || 'DEFAULT_MID', + mid: anyMessage.mid || this.generateId(), handover: !!anyMessage.handover, createdAt: anyMessage.createdAt, }); @@ -621,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', @@ -639,7 +644,7 @@ export default abstract class BaseWebChannelHandler< async handleWebUpload( req: Request, res: Response, - ): Promise { + ): Promise { try { const upload = multer({ limits: { @@ -665,7 +670,9 @@ export default abstract class BaseWebChannelHandler< reject(new Error('Unable to upload file!')); } - resolve(req.file); + if (req.file) { + resolve(req.file); + } }); }, ); @@ -678,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', @@ -703,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'); 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/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 6cd91662..28215170 100644 --- a/api/src/utils/test/sort.ts +++ b/api/src/utils/test/sort.ts @@ -13,16 +13,20 @@ type TSortProps = { order?: 'desc' | 'asc'; }; -type TCreateAt = { createdAt?: string | Date }; +type TCreatedAt = { createdAt?: string | Date }; -const sort = ({ +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/types/event-emitter.d.ts b/api/types/event-emitter.d.ts index fadb4d6e..a39d15cf 100644 --- a/api/types/event-emitter.d.ts +++ b/api/types/event-emitter.d.ts @@ -93,7 +93,7 @@ declare module '@nestjs/event-emitter' { object, { block: BlockFull; - passation: Subscriber | null; + passation: Subscriber; 'fallback-local': BlockFull; 'fallback-global': EventWrapper; intervention: Subscriber;