From 1be203cbe947621c397ac57c3c788d5703004ad4 Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Thu, 2 Jan 2025 10:03:04 +0100 Subject: [PATCH 1/4] fix: channel ts strict null check --- api/src/channel/channel.service.ts | 50 +++++++++++-------- .../channel/lib/__test__/subscriber.mock.ts | 1 + api/src/chat/dto/subscriber.dto.ts | 3 ++ api/src/chat/schemas/subscriber.schema.ts | 14 +++--- api/src/chat/services/chat.service.ts | 18 ++++++- api/src/chat/services/subscriber.service.ts | 13 +++++ .../channels/web/base-web-channel.ts | 26 +++++++--- api/src/user/schemas/user.schema.ts | 4 +- api/src/utils/test/fixtures/conversation.ts | 8 +++ api/src/utils/test/fixtures/subscriber.ts | 28 ++++++----- api/src/utils/test/fixtures/user.ts | 13 +++-- api/src/utils/test/mocks/subscriber.ts | 1 + 12 files changed, 125 insertions(+), 54 deletions(-) diff --git a/api/src/channel/channel.service.ts b/api/src/channel/channel.service.ts index 66745c977..ed249772d 100644 --- a/api/src/channel/channel.service.ts +++ b/api/src/channel/channel.service.ts @@ -9,6 +9,7 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Request, Response } from 'express'; +import { ChannelName } from '@/channel/types'; import { SubscriberService } from '@/chat/services/subscriber.service'; import { CONSOLE_CHANNEL_NAME } from '@/extensions/channels/console/settings'; import { WEB_CHANNEL_NAME } from '@/extensions/channels/web/settings'; @@ -23,7 +24,6 @@ import { SocketRequest } from '@/websocket/utils/socket-request'; import { SocketResponse } from '@/websocket/utils/socket-response'; import ChannelHandler from './lib/Handler'; -import { ChannelName } from './types'; @Injectable() export class ChannelService { @@ -156,26 +156,36 @@ export class ChannelService { } // Create test subscriber for the current user - const testSubscriber = await this.subscriberService.findOneOrCreate( - { - foreign_id: req.session.passport.user.id, - }, - { - id: req.session.passport.user.id, - foreign_id: req.session.passport.user.id, - first_name: req.session.passport.user.first_name, - last_name: req.session.passport.user.last_name, - locale: '', - language: '', - gender: '', - country: '', - labels: [], - channel: { - name: CONSOLE_CHANNEL_NAME, - isSocket: true, + // TODO: check if req.session.passport.user.id can be undefined could cause bugs + const testSubscriber = + await this.subscriberService.findByIdOrCreateTestSubscriber( + { + foreign_id: req.session.passport.user.id, }, - }, - ); + { + id: req.session.passport.user.id, + foreign_id: req.session.passport.user.id, + first_name: req.session.passport.user.first_name, + last_name: req.session.passport.user.last_name, + locale: '', + language: '', + gender: '', + country: '', + labels: [], + channel: { + name: CONSOLE_CHANNEL_NAME, + isSocket: true, + } as { + name: ChannelName; + isSocket: boolean; + }, + assignedTo: null, + avatar: null, + assignedAt: null, + lastvisit: null, + retainedFrom: null, + }, + ); // Update session (end user is both a user + subscriber) req.session.web = { diff --git a/api/src/channel/lib/__test__/subscriber.mock.ts b/api/src/channel/lib/__test__/subscriber.mock.ts index 5f2bda7e7..dc402aa50 100644 --- a/api/src/channel/lib/__test__/subscriber.mock.ts +++ b/api/src/channel/lib/__test__/subscriber.mock.ts @@ -29,6 +29,7 @@ export const subscriberInstance: Subscriber = { }, labels: [], ...modelInstance, + avatar: null, }; export const subscriberWithoutLabels: Subscriber = { diff --git a/api/src/chat/dto/subscriber.dto.ts b/api/src/chat/dto/subscriber.dto.ts index 82148f96b..05dca17d0 100644 --- a/api/src/chat/dto/subscriber.dto.ts +++ b/api/src/chat/dto/subscriber.dto.ts @@ -111,6 +111,9 @@ export class SubscriberCreateDto { @IsNotEmpty() @IsChannelData() channel: SubscriberChannelData; + + @IsOptional() + avatar?: string | null = null; } export class SubscriberUpdateDto extends PartialType(SubscriberCreateDto) {} diff --git a/api/src/chat/schemas/subscriber.schema.ts b/api/src/chat/schemas/subscriber.schema.ts index 5302df41c..159399e55 100644 --- a/api/src/chat/schemas/subscriber.schema.ts +++ b/api/src/chat/schemas/subscriber.schema.ts @@ -86,19 +86,19 @@ export class SubscriberStub extends BaseSchema { type: Date, default: null, }) - assignedAt?: Date; + assignedAt: Date | null; @Prop({ type: Date, default: () => Date.now() + 7 * 24 * 60 * 60 * 1000, }) - lastvisit?: Date; + lastvisit: Date; @Prop({ type: Date, default: () => Date.now() + 7 * 24 * 60 * 60 * 1000, }) - retainedFrom?: Date; + retainedFrom: Date; @Prop({ type: Object, @@ -110,7 +110,7 @@ export class SubscriberStub extends BaseSchema { ref: 'Attachment', default: null, }) - avatar?: unknown; + avatar?: unknown | null; @Prop({ type: Object, @@ -132,10 +132,10 @@ export class Subscriber extends SubscriberStub { labels: string[]; @Transform(({ obj }) => (obj.assignedTo ? obj.assignedTo.toString() : null)) - assignedTo?: string; + 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/services/chat.service.ts b/api/src/chat/services/chat.service.ts index 89db5fd21..7a7ffca79 100644 --- a/api/src/chat/services/chat.service.ts +++ b/api/src/chat/services/chat.service.ts @@ -244,8 +244,24 @@ export class ChatService { if (!subscriber) { const subscriberData = await handler.getUserData(event); this.eventEmitter.emit('hook:stats:entry', 'new_users', 'New users'); + const currentDate = new Date(); subscriberData.channel = event.getChannelData(); - subscriber = await this.subscriberService.create(subscriberData); + subscriber = await this.subscriberService.create({ + country: subscriberData.country, + first_name: subscriberData.first_name, + last_name: subscriberData.last_name, + gender: subscriberData.gender, + language: subscriberData.language, + locale: subscriberData.locale, + channel: subscriberData.channel, + foreign_id: subscriberData.foreign_id, + labels: subscriberData.labels, + lastvisit: subscriberData.lastvisit || currentDate, + retainedFrom: subscriberData.retainedFrom || currentDate, + assignedAt: subscriberData.assignedAt || null, + assignedTo: subscriberData.assignedTo || null, + avatar: subscriberData.avatar || null, + }); } else { // Already existing user profile // Exec lastvisit hook diff --git a/api/src/chat/services/subscriber.service.ts b/api/src/chat/services/subscriber.service.ts index 54f942280..5943e58df 100644 --- a/api/src/chat/services/subscriber.service.ts +++ b/api/src/chat/services/subscriber.service.ts @@ -19,6 +19,7 @@ import { AttachmentService } from '@/attachment/services/attachment.service'; import { config } from '@/config'; import { LoggerService } from '@/logger/logger.service'; import { BaseService } from '@/utils/generics/base-service'; +import { TFilterQuery } from '@/utils/types/filter.types'; import { SocketGet, SocketPost, @@ -91,6 +92,18 @@ export class SubscriberService extends BaseService< return await this.repository.findOneByForeignId(id); } + // TODO: add tests + async findByIdOrCreateTestSubscriber( + criteria: TFilterQuery, // TODO: change to { id : string} + subscriber: Omit, + ) { + const testSubscriber = await this.findOne(criteria); + if (!testSubscriber) { + return this.repository.create(subscriber); + } + return testSubscriber; + } + /** * Finds and returns a single subscriber based on a foreign ID, * and populates the result with related data. diff --git a/api/src/extensions/channels/web/base-web-channel.ts b/api/src/extensions/channels/web/base-web-channel.ts index 9bc8274f1..552fed985 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'; @@ -49,6 +49,7 @@ import { config } from '@/config'; import { I18nService } from '@/i18n/services/i18n.service'; import { LoggerService } from '@/logger/logger.service'; import { SettingService } from '@/setting/services/setting.service'; +import { BaseSchema } from '@/utils/generics/base-schema'; import { SocketRequest } from '@/websocket/utils/socket-request'; import { SocketResponse } from '@/websocket/utils/socket-response'; import { WebsocketGateway } from '@/websocket/websocket.gateway'; @@ -227,15 +228,17 @@ export default abstract class BaseWebChannelHandler< createdAt: anyMessage.createdAt, }); } else { - const message = this.formatOutgoingHistoryMessage(anyMessage); - formattedMessages.push({ - ...message, + const baseMessage = this.formatOutgoingHistoryMessage(anyMessage); + const outgoingMessage = { + ...baseMessage, author: 'chatbot', read: true, // Temporary fix as read is false in the bd mid: anyMessage.mid, handover: !!anyMessage.handover, createdAt: anyMessage.createdAt, - }); + } as Web.OutgoingMessage; + + formattedMessages.push(outgoingMessage); } } @@ -443,7 +446,7 @@ export default abstract class BaseWebChannelHandler< return subscriber; } - const newProfile: SubscriberCreateDto = { + const newProfile: Omit = { foreign_id: this.generateId(), first_name: data.first_name ? data.first_name.toString() : 'Anon.', last_name: data.last_name ? data.last_name.toString() : 'Web User', @@ -461,7 +464,9 @@ export default abstract class BaseWebChannelHandler< gender: 'male', country: '', labels: [], + avatar: null, }; + const subscriber = await this.subscriberService.create(newProfile); // Init session const profile: SubscriberFull = { @@ -520,6 +525,11 @@ 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: Unable to fetchMessages()`, + ); + } const since = new Date(req.query.since.toString()); const messages = await this.pollMessages(req, since); if (messages.length === 0 && retrials <= 5) { @@ -722,7 +732,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']!, }; } @@ -966,7 +976,7 @@ export default abstract class BaseWebChannelHandler< type: Web.OutgoingMessageType.file, data: { type: message.attachment.type, - url: message.attachment.payload.url, + url: message.attachment.payload.url!, }, }; if (message.quickReplies && message.quickReplies.length > 0) { diff --git a/api/src/user/schemas/user.schema.ts b/api/src/user/schemas/user.schema.ts index 21a4a19dd..adde442e6 100644 --- a/api/src/user/schemas/user.schema.ts +++ b/api/src/user/schemas/user.schema.ts @@ -91,7 +91,7 @@ export class UserStub extends BaseSchema { ref: 'Attachment', default: null, }) - avatar?: unknown; + avatar?: unknown | null; @Prop({ type: String, @@ -112,7 +112,7 @@ export class User extends UserStub { roles: string[]; @Transform(({ obj }) => obj.avatar?.toString() || null) - avatar?: string; + avatar?: string | null; } @Schema({ timestamps: true }) diff --git a/api/src/utils/test/fixtures/conversation.ts b/api/src/utils/test/fixtures/conversation.ts index 548a84b20..5e257ecd9 100644 --- a/api/src/utils/test/fixtures/conversation.ts +++ b/api/src/utils/test/fixtures/conversation.ts @@ -61,6 +61,10 @@ const conversations: ConversationCreateDto[] = [ labels: [], assignedTo: null, channel: { name: 'messenger-channel' }, + avatar: null, + assignedAt: null, + lastvisit: null, + retainedFrom: null, }, skip: {}, attempt: 0, @@ -107,6 +111,10 @@ const conversations: ConversationCreateDto[] = [ labels: [], assignedTo: null, channel: { name: 'web-channel' }, + avatar: null, + assignedAt: null, + lastvisit: null, + retainedFrom: null, }, skip: {}, attempt: 0, diff --git a/api/src/utils/test/fixtures/subscriber.ts b/api/src/utils/test/fixtures/subscriber.ts index 605d75608..fba8ec822 100644 --- a/api/src/utils/test/fixtures/subscriber.ts +++ b/api/src/utils/test/fixtures/subscriber.ts @@ -9,7 +9,7 @@ import mongoose from 'mongoose'; import { SubscriberCreateDto } from '@/chat/dto/subscriber.dto'; -import { Subscriber, SubscriberModel } from '@/chat/schemas/subscriber.schema'; +import { SubscriberModel } from '@/chat/schemas/subscriber.schema'; import { getFixturesWithDefaultValues } from '../defaultValues'; import { TFixturesDefaultValues } from '../types'; @@ -84,19 +84,21 @@ const subscribers: SubscriberCreateDto[] = [ }, ]; -export const subscriberDefaultValues: TFixturesDefaultValues = { - timezone: 0, - assignedTo: null, - assignedAt: null, - lastvisit: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), - retainedFrom: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), - avatar: null, -}; +export const subscriberDefaultValues: TFixturesDefaultValues = + { + timezone: 0, + assignedTo: null, + assignedAt: null, + lastvisit: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), + retainedFrom: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), + avatar: null, + }; -export const subscriberFixtures = getFixturesWithDefaultValues({ - fixtures: subscribers, - defaultValues: subscriberDefaultValues, -}); +export const subscriberFixtures = + getFixturesWithDefaultValues({ + fixtures: subscribers, + defaultValues: subscriberDefaultValues, + }); export const installSubscriberFixtures = async () => { const Subscriber = mongoose.model( diff --git a/api/src/utils/test/fixtures/user.ts b/api/src/utils/test/fixtures/user.ts index c5f9d5d9b..3511a37e7 100644 --- a/api/src/utils/test/fixtures/user.ts +++ b/api/src/utils/test/fixtures/user.ts @@ -9,7 +9,7 @@ import mongoose from 'mongoose'; import { UserCreateDto } from '@/user/dto/user.dto'; -import { UserModel, User } from '@/user/schemas/user.schema'; +import { User, UserModel } from '@/user/schemas/user.schema'; import { hash } from '@/user/utilities/bcryptjs'; import { getFixturesWithDefaultValues } from '../defaultValues'; @@ -17,6 +17,13 @@ import { TFixturesDefaultValues } from '../types'; import { installRoleFixtures } from './role'; +interface UserFxiture extends User { + state?: boolean; + language?: string; + timezone?: string; + avatar?: string | null; +} + export const users: UserCreateDto[] = [ { username: 'admin', @@ -28,7 +35,7 @@ export const users: UserCreateDto[] = [ }, ]; -export const userDefaultValues: TFixturesDefaultValues = { +export const userDefaultValues: TFixturesDefaultValues = { state: true, language: 'en', timezone: 'Europe/Berlin', @@ -38,7 +45,7 @@ export const userDefaultValues: TFixturesDefaultValues = { }; export const getUserFixtures = (users: UserCreateDto[]) => - getFixturesWithDefaultValues({ + getFixturesWithDefaultValues({ fixtures: users, defaultValues: userDefaultValues, }); diff --git a/api/src/utils/test/mocks/subscriber.ts b/api/src/utils/test/mocks/subscriber.ts index 8ec8ee83b..4a47fb723 100644 --- a/api/src/utils/test/mocks/subscriber.ts +++ b/api/src/utils/test/mocks/subscriber.ts @@ -29,6 +29,7 @@ export const subscriberInstance: Subscriber = { }, labels: [], ...modelInstance, + avatar: null, }; export const subscriberWithoutLabels: Subscriber = { From f825ff8b01958a43a5edcad9d188851c88b4d85f Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Fri, 3 Jan 2025 09:55:46 +0100 Subject: [PATCH 2/4] fix: address comments --- api/src/channel/channel.service.ts | 55 +++++++++---------- api/src/chat/schemas/message.schema.ts | 4 +- api/src/chat/services/subscriber.service.ts | 13 ----- .../channels/web/base-web-channel.ts | 4 +- 4 files changed, 30 insertions(+), 46 deletions(-) diff --git a/api/src/channel/channel.service.ts b/api/src/channel/channel.service.ts index ed249772d..a284033d1 100644 --- a/api/src/channel/channel.service.ts +++ b/api/src/channel/channel.service.ts @@ -156,36 +156,33 @@ export class ChannelService { } // Create test subscriber for the current user - // TODO: check if req.session.passport.user.id can be undefined could cause bugs - const testSubscriber = - await this.subscriberService.findByIdOrCreateTestSubscriber( - { - foreign_id: req.session.passport.user.id, + const testSubscriber = await this.subscriberService.findOneOrCreate( + { + foreign_id: req.session.passport.user.id, + }, + { + foreign_id: req.session.passport.user.id, + first_name: req.session.passport.user.first_name || 'Anonymous', + last_name: req.session.passport.user.last_name || 'Anonymous', + locale: '', + language: '', + gender: '', + country: '', + labels: [], + channel: { + name: CONSOLE_CHANNEL_NAME, + isSocket: true, + } as { + name: ChannelName; + isSocket: boolean; }, - { - id: req.session.passport.user.id, - foreign_id: req.session.passport.user.id, - first_name: req.session.passport.user.first_name, - last_name: req.session.passport.user.last_name, - locale: '', - language: '', - gender: '', - country: '', - labels: [], - channel: { - name: CONSOLE_CHANNEL_NAME, - isSocket: true, - } as { - name: ChannelName; - isSocket: boolean; - }, - assignedTo: null, - avatar: null, - assignedAt: null, - lastvisit: null, - retainedFrom: null, - }, - ); + assignedTo: null, + avatar: null, + assignedAt: null, + lastvisit: new Date(), + retainedFrom: new Date(), + }, + ); // Update session (end user is both a user + subscriber) req.session.web = { diff --git a/api/src/chat/schemas/message.schema.ts b/api/src/chat/schemas/message.schema.ts index 302db5080..5631c1470 100644 --- a/api/src/chat/schemas/message.schema.ts +++ b/api/src/chat/schemas/message.schema.ts @@ -21,10 +21,10 @@ import { StdIncomingMessage, StdOutgoingMessage } from './types/message'; export class MessageStub extends BaseSchema { @Prop({ type: String, - required: false, + required: true, //TODO : add default value for mid }) - mid?: string; + mid: string; @Prop({ type: MongooseSchema.Types.ObjectId, diff --git a/api/src/chat/services/subscriber.service.ts b/api/src/chat/services/subscriber.service.ts index 5943e58df..54f942280 100644 --- a/api/src/chat/services/subscriber.service.ts +++ b/api/src/chat/services/subscriber.service.ts @@ -19,7 +19,6 @@ import { AttachmentService } from '@/attachment/services/attachment.service'; import { config } from '@/config'; import { LoggerService } from '@/logger/logger.service'; import { BaseService } from '@/utils/generics/base-service'; -import { TFilterQuery } from '@/utils/types/filter.types'; import { SocketGet, SocketPost, @@ -92,18 +91,6 @@ export class SubscriberService extends BaseService< return await this.repository.findOneByForeignId(id); } - // TODO: add tests - async findByIdOrCreateTestSubscriber( - criteria: TFilterQuery, // TODO: change to { id : string} - subscriber: Omit, - ) { - const testSubscriber = await this.findOne(criteria); - if (!testSubscriber) { - return this.repository.create(subscriber); - } - return testSubscriber; - } - /** * Finds and returns a single subscriber based on a foreign ID, * and populates the result with related data. diff --git a/api/src/extensions/channels/web/base-web-channel.ts b/api/src/extensions/channels/web/base-web-channel.ts index 552fed985..e92d87d3e 100644 --- a/api/src/extensions/channels/web/base-web-channel.ts +++ b/api/src/extensions/channels/web/base-web-channel.ts @@ -229,14 +229,14 @@ export default abstract class BaseWebChannelHandler< }); } else { const baseMessage = this.formatOutgoingHistoryMessage(anyMessage); - const outgoingMessage = { + const outgoingMessage: Web.OutgoingMessage = { ...baseMessage, author: 'chatbot', read: true, // Temporary fix as read is false in the bd mid: anyMessage.mid, handover: !!anyMessage.handover, createdAt: anyMessage.createdAt, - } as Web.OutgoingMessage; + }; formattedMessages.push(outgoingMessage); } From f25f5763e14ffd8c39f8761ab9adc3bcdc24f232 Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Fri, 3 Jan 2025 10:14:09 +0100 Subject: [PATCH 3/4] fix: make mid required in createMessageDto & fix subscriberChannelData type --- api/src/chat/dto/message.dto.ts | 5 ++--- api/src/chat/schemas/types/channel.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/api/src/chat/dto/message.dto.ts b/api/src/chat/dto/message.dto.ts index a5855bde4..710d10aad 100644 --- a/api/src/chat/dto/message.dto.ts +++ b/api/src/chat/dto/message.dto.ts @@ -11,8 +11,8 @@ import { IsBoolean, IsNotEmpty, IsObject, - IsString, IsOptional, + IsString, } from 'class-validator'; import { IsObjectId } from '@/utils/validation-rules/is-object-id'; @@ -25,9 +25,8 @@ import { IsValidMessageText } from '../validation-rules/is-valid-message-text'; export class MessageCreateDto { @ApiProperty({ description: 'Message id', type: String }) - @IsOptional() @IsString() - mid?: string; + mid: string; @ApiProperty({ description: 'Reply to Message id', type: String }) @IsOptional() diff --git a/api/src/chat/schemas/types/channel.ts b/api/src/chat/schemas/types/channel.ts index d53a3c60f..c9129e3eb 100644 --- a/api/src/chat/schemas/types/channel.ts +++ b/api/src/chat/schemas/types/channel.ts @@ -9,9 +9,9 @@ import { ChannelName } from '@/channel/types'; export type SubscriberChannelData< - C extends ChannelName = null, + C extends ChannelName = 'undefined-name-channel', // K extends keyof SubscriberChannelDict[C] = keyof SubscriberChannelDict[C], -> = C extends null +> = C extends 'undefined-name-channel' ? { name: ChannelName } : { name: C; From 2469cbdea86c98170d286462a79113af9775d480 Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Fri, 3 Jan 2025 11:23:14 +0100 Subject: [PATCH 4/4] fix: address comments --- api/src/chat/dto/subscriber.dto.ts | 12 ++++----- api/src/chat/schemas/types/channel.ts | 4 +-- api/src/chat/services/chat.service.ts | 35 +++++++++++++-------------- api/src/utils/test/fixtures/user.ts | 11 ++------- 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/api/src/chat/dto/subscriber.dto.ts b/api/src/chat/dto/subscriber.dto.ts index 05dca17d0..2fdee16b7 100644 --- a/api/src/chat/dto/subscriber.dto.ts +++ b/api/src/chat/dto/subscriber.dto.ts @@ -77,7 +77,7 @@ export class SubscriberCreateDto { @IsOptional() @IsString() @IsObjectId({ message: 'AssignedTo must be a valid ObjectId' }) - assignedTo?: string | null; + assignedTo: string | null = null; @ApiPropertyOptional({ description: 'Subscriber assigned at', @@ -86,23 +86,21 @@ export class SubscriberCreateDto { }) @IsOptional() @IsDate() - assignedAt?: Date | null; + assignedAt: Date | null = null; @ApiPropertyOptional({ description: 'Subscriber last visit', type: Date, }) - @IsOptional() @IsDate() - lastvisit?: Date; + lastvisit: Date; @ApiPropertyOptional({ description: 'Subscriber retained from', type: Date, }) - @IsOptional() @IsDate() - retainedFrom?: Date; + retainedFrom: Date; @ApiProperty({ description: 'Subscriber channel', @@ -113,7 +111,7 @@ export class SubscriberCreateDto { channel: SubscriberChannelData; @IsOptional() - avatar?: string | null = null; + avatar: string | null = null; } export class SubscriberUpdateDto extends PartialType(SubscriberCreateDto) {} diff --git a/api/src/chat/schemas/types/channel.ts b/api/src/chat/schemas/types/channel.ts index c9129e3eb..b8afb60f0 100644 --- a/api/src/chat/schemas/types/channel.ts +++ b/api/src/chat/schemas/types/channel.ts @@ -9,9 +9,9 @@ import { ChannelName } from '@/channel/types'; export type SubscriberChannelData< - C extends ChannelName = 'undefined-name-channel', + C extends ChannelName = 'unknown-channel', // K extends keyof SubscriberChannelDict[C] = keyof SubscriberChannelDict[C], -> = C extends 'undefined-name-channel' +> = C extends 'unknown-channel' ? { name: ChannelName } : { name: C; diff --git a/api/src/chat/services/chat.service.ts b/api/src/chat/services/chat.service.ts index 7a7ffca79..5052e67ac 100644 --- a/api/src/chat/services/chat.service.ts +++ b/api/src/chat/services/chat.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 { EventEmitter2, OnEvent } from '@nestjs/event-emitter'; import EventWrapper from '@/channel/lib/EventWrapper'; @@ -117,6 +117,11 @@ export class ChatService { try { const msg = await this.messageService.create(received); const populatedMsg = await this.messageService.findOneAndPopulate(msg.id); + if (!populatedMsg) { + throw new NotFoundException( + `Unable to find message ${msg.id} hook:chatbot:received`, + ); + } this.websocketGateway.broadcastMessageReceived(populatedMsg, subscriber); this.eventEmitter.emit('hook:stats:entry', 'incoming', 'Incoming'); this.eventEmitter.emit( @@ -244,24 +249,8 @@ export class ChatService { if (!subscriber) { const subscriberData = await handler.getUserData(event); this.eventEmitter.emit('hook:stats:entry', 'new_users', 'New users'); - const currentDate = new Date(); subscriberData.channel = event.getChannelData(); - subscriber = await this.subscriberService.create({ - country: subscriberData.country, - first_name: subscriberData.first_name, - last_name: subscriberData.last_name, - gender: subscriberData.gender, - language: subscriberData.language, - locale: subscriberData.locale, - channel: subscriberData.channel, - foreign_id: subscriberData.foreign_id, - labels: subscriberData.labels, - lastvisit: subscriberData.lastvisit || currentDate, - retainedFrom: subscriberData.retainedFrom || currentDate, - assignedAt: subscriberData.assignedAt || null, - assignedTo: subscriberData.assignedTo || null, - avatar: subscriberData.avatar || null, - }); + subscriber = await this.subscriberService.create(subscriberData); } else { // Already existing user profile // Exec lastvisit hook @@ -304,6 +293,11 @@ export class ChatService { @OnEvent('hook:subscriber:postCreate') async onSubscriberCreate({ _id }: SubscriberDocument) { const subscriber = await this.subscriberService.findOne(_id); + if (!subscriber) { + throw new NotFoundException( + `Unable to find subscriber ${_id} hook:subscriber:postCreate`, + ); + } this.websocketGateway.broadcastSubscriberNew(subscriber); } @@ -315,6 +309,11 @@ export class ChatService { @OnEvent('hook:subscriber:postUpdate') async onSubscriberUpdate({ _id }: SubscriberDocument) { const subscriber = await this.subscriberService.findOne(_id); + if (!subscriber) { + throw new NotFoundException( + `Unable to find subscriber ${_id} hook:subscriber:postUpdate`, + ); + } this.websocketGateway.broadcastSubscriberUpdate(subscriber); } } diff --git a/api/src/utils/test/fixtures/user.ts b/api/src/utils/test/fixtures/user.ts index 3511a37e7..e7d1be642 100644 --- a/api/src/utils/test/fixtures/user.ts +++ b/api/src/utils/test/fixtures/user.ts @@ -17,13 +17,6 @@ import { TFixturesDefaultValues } from '../types'; import { installRoleFixtures } from './role'; -interface UserFxiture extends User { - state?: boolean; - language?: string; - timezone?: string; - avatar?: string | null; -} - export const users: UserCreateDto[] = [ { username: 'admin', @@ -35,7 +28,7 @@ export const users: UserCreateDto[] = [ }, ]; -export const userDefaultValues: TFixturesDefaultValues = { +export const userDefaultValues: TFixturesDefaultValues = { state: true, language: 'en', timezone: 'Europe/Berlin', @@ -45,7 +38,7 @@ export const userDefaultValues: TFixturesDefaultValues = { }; export const getUserFixtures = (users: UserCreateDto[]) => - getFixturesWithDefaultValues({ + getFixturesWithDefaultValues({ fixtures: users, defaultValues: userDefaultValues, });