From 82520b54beb50ce231348949510bd5f6cc75d634 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 08:39:28 -0600 Subject: [PATCH 01/17] todo-1 --- .../imports/server/rest/departments.ts | 1 + .../app/livechat/server/lib/departmentsLib.ts | 46 ++++++++----------- apps/meteor/app/livechat/server/lib/hooks.ts | 32 +++++++++---- .../server/hooks/beforeRoutingChat.ts | 19 ++++---- .../EmailInbox/EmailInbox_Incoming.ts | 2 +- .../model-typings/src/models/IUsersModel.ts | 1 - packages/models/src/models/Users.ts | 15 ------ 7 files changed, 54 insertions(+), 62 deletions(-) diff --git a/apps/meteor/app/livechat/imports/server/rest/departments.ts b/apps/meteor/app/livechat/imports/server/rest/departments.ts index 1be4c54e74971..0da9d6b8e1bac 100644 --- a/apps/meteor/app/livechat/imports/server/rest/departments.ts +++ b/apps/meteor/app/livechat/imports/server/rest/departments.ts @@ -266,6 +266,7 @@ API.v1.addRoute( 'livechat/department/:_id/agents', { authRequired: true, + // TODO: Use AJV permissionsRequired: { GET: { permissions: ['view-livechat-departments', 'view-l-room'], operation: 'hasAny' }, POST: { permissions: ['manage-livechat-departments', 'add-livechat-department-agents'], operation: 'hasAny' }, diff --git a/apps/meteor/app/livechat/server/lib/departmentsLib.ts b/apps/meteor/app/livechat/server/lib/departmentsLib.ts index 2a9a505064b25..7d5eebc9a6633 100644 --- a/apps/meteor/app/livechat/server/lib/departmentsLib.ts +++ b/apps/meteor/app/livechat/server/lib/departmentsLib.ts @@ -4,6 +4,7 @@ import { LivechatDepartment, LivechatDepartmentAgents, LivechatVisitors, Livecha import { Meteor } from 'meteor/meteor'; import { updateDepartmentAgents } from './Helper'; +import { afterDepartmentArchived, afterDepartmentUnarchived } from './hooks'; import { isDepartmentCreationAvailable } from './isDepartmentCreationAvailable'; import { livechatLogger } from './logger'; import { callbacks } from '../../../../lib/callbacks'; @@ -27,7 +28,6 @@ export async function saveDepartment( }, departmentUnit?: { _id?: string }, ) { - check(_id, Match.Maybe(String)); if (departmentUnit?._id !== undefined && typeof departmentUnit._id !== 'string') { throw new Meteor.Error('error-invalid-department-unit', 'Invalid department unit id provided', { method: 'livechat:saveDepartment', @@ -35,7 +35,9 @@ export async function saveDepartment( } const department = _id - ? await LivechatDepartment.findOneById(_id, { projection: { _id: 1, archived: 1, enabled: 1, parentId: 1 } }) + ? await LivechatDepartment.findOneById>(_id, { + projection: { _id: 1, archived: 1, enabled: 1, parentId: 1 }, + }) : null; if (departmentUnit && !departmentUnit._id && department && department.parentId) { @@ -59,6 +61,7 @@ export async function saveDepartment( }); } + // TODO: Use AJV or Zod for validation (or the lib we are using rn) const defaultValidations: Record | BooleanConstructor | StringConstructor> = { enabled: Boolean, name: String, @@ -112,8 +115,8 @@ export async function saveDepartment( } if (fallbackForwardDepartment) { - const fallbackDep = await LivechatDepartment.findOneById(fallbackForwardDepartment, { - projection: { _id: 1, fallbackForwardDepartment: 1 }, + const fallbackDep = await LivechatDepartment.findOneById>(fallbackForwardDepartment, { + projection: { _id: 1 }, }); if (!fallbackDep) { throw new Meteor.Error('error-fallback-department-not-found', 'Fallback department not found', { @@ -156,11 +159,7 @@ export async function archiveDepartment(_id: string) { throw new Error('department-not-found'); } - await Promise.all([LivechatDepartmentAgents.disableAgentsByDepartmentId(_id), LivechatDepartment.archiveDepartment(_id)]); - - void notifyOnLivechatDepartmentAgentChangedByDepartmentId(_id); - - await callbacks.run('livechat.afterDepartmentArchived', department); + await afterDepartmentArchived(department); } export async function unarchiveDepartment(_id: string) { @@ -170,12 +169,7 @@ export async function unarchiveDepartment(_id: string) { throw new Meteor.Error('department-not-found'); } - // TODO: these kind of actions should be on events instead of here - await Promise.all([LivechatDepartmentAgents.enableAgentsByDepartmentId(_id), LivechatDepartment.unarchiveDepartment(_id)]); - - void notifyOnLivechatDepartmentAgentChangedByDepartmentId(_id); - - return true; + await afterDepartmentUnarchived(department); } export async function saveDepartmentAgents( @@ -185,6 +179,7 @@ export async function saveDepartmentAgents( remove?: Pick[]; }, ) { + // TODO: remove when endpoint is validated check(_id, String); check(departmentAgents, { upsert: Match.Maybe([ @@ -213,13 +208,15 @@ export async function saveDepartmentAgents( return updateDepartmentAgents(_id, departmentAgents, department.enabled); } -export async function setDepartmentForGuest({ token, department }: { token: string; department: string }) { - check(token, String); - check(department, String); - - livechatLogger.debug(`Switching departments for user with token ${token} (to ${department})`); +export async function setDepartmentForGuest({ visitorId, department }: { visitorId: string; department: string }) { + livechatLogger.debug({ + msg: 'Switching departments for visitor', + visitorId, + department, + }); - const updateUser = { + // TODO: move to model + const visitorUpdate = { $set: { department, }, @@ -230,11 +227,8 @@ export async function setDepartmentForGuest({ token, department }: { token: stri throw new Meteor.Error('invalid-department', 'Provided department does not exists'); } - const visitor = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1 } }); - if (!visitor) { - throw new Meteor.Error('invalid-token', 'Provided token is invalid'); - } - await LivechatVisitors.updateById(visitor._id, updateUser); + // Visitor is already validated at this point + await LivechatVisitors.updateById(visitorId, visitorUpdate); } export async function removeDepartment(departmentId: string) { diff --git a/apps/meteor/app/livechat/server/lib/hooks.ts b/apps/meteor/app/livechat/server/lib/hooks.ts index d877ce13b8f6e..f2d1c1128e437 100644 --- a/apps/meteor/app/livechat/server/lib/hooks.ts +++ b/apps/meteor/app/livechat/server/lib/hooks.ts @@ -1,24 +1,20 @@ import { ILivechatAgentStatus } from '@rocket.chat/core-typings'; -import type { AtLeast, IUser } from '@rocket.chat/core-typings'; -import { Users } from '@rocket.chat/models'; +import type { AtLeast, ILivechatDepartment, IUser } from '@rocket.chat/core-typings'; +import { LivechatDepartmentAgents, LivechatDepartment } from '@rocket.chat/models'; import { setUserStatusLivechat } from './utils'; import { callbacks } from '../../../../lib/callbacks'; +import { notifyOnLivechatDepartmentAgentChangedByDepartmentId } from '../../../lib/server/lib/notifyListener'; export async function afterAgentUserActivated(user: IUser) { if (!user.roles.includes('livechat-agent')) { throw new Error('invalid-user-role'); } - // TODO: deprecate this `operator` property - await Users.setOperator(user._id, true); callbacks.runAsync('livechat.onNewAgentCreated', user._id); } export async function afterAgentAdded(user: IUser) { - await Promise.all([ - Users.setOperator(user._id, true), - setUserStatusLivechat(user._id, user.status !== 'offline' ? ILivechatAgentStatus.AVAILABLE : ILivechatAgentStatus.NOT_AVAILABLE), - ]); + await setUserStatusLivechat(user._id, user.status !== 'offline' ? ILivechatAgentStatus.AVAILABLE : ILivechatAgentStatus.NOT_AVAILABLE); callbacks.runAsync('livechat.onNewAgentCreated', user._id); return user; @@ -28,3 +24,23 @@ export async function afterRemoveAgent(user: AtLeast) await callbacks.run('livechat.afterAgentRemoved', { agent: user }); return true; } + +export async function afterDepartmentArchived(department: AtLeast) { + await Promise.all([ + LivechatDepartmentAgents.disableAgentsByDepartmentId(department._id), + LivechatDepartment.archiveDepartment(department._id), + ]); + + void notifyOnLivechatDepartmentAgentChangedByDepartmentId(department._id); + + await callbacks.run('livechat.afterDepartmentArchived', department); +} + +export async function afterDepartmentUnarchived(department: AtLeast) { + await Promise.all([ + LivechatDepartmentAgents.enableAgentsByDepartmentId(department._id), + LivechatDepartment.unarchiveDepartment(department._id), + ]); + + void notifyOnLivechatDepartmentAgentChangedByDepartmentId(department._id); +} diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.ts index d2782ddee00e9..3d3bab70ec03b 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.ts @@ -34,23 +34,20 @@ callbacks.add( `Inquiry ${inquiry._id} will be moved from department ${department._id} to fallback department ${department.fallbackForwardDepartment}`, ); - // update visitor - await setDepartmentForGuest({ - token: inquiry?.v?.token, - department: department.fallbackForwardDepartment, - }); - - // update inquiry - const updatedLivechatInquiry = await LivechatInquiry.setDepartmentByInquiryId(inquiry._id, department.fallbackForwardDepartment); + const [, updatedLivechatInquiry] = await Promise.all([ + setDepartmentForGuest({ + visitorId: inquiry.v._id, + department: department.fallbackForwardDepartment, + }), + LivechatInquiry.setDepartmentByInquiryId(inquiry._id, department.fallbackForwardDepartment), + LivechatRooms.setDepartmentByRoomId(inquiry.rid, department.fallbackForwardDepartment), + ]); if (updatedLivechatInquiry) { void notifyOnLivechatInquiryChanged(updatedLivechatInquiry, 'updated', { department: updatedLivechatInquiry.department }); } inquiry = updatedLivechatInquiry ?? inquiry; - - // update room - await LivechatRooms.setDepartmentByRoomId(inquiry.rid, department.fallbackForwardDepartment); } } diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts index 2fc4f164f9ed5..1f114ac909af9 100644 --- a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts +++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts @@ -36,7 +36,7 @@ async function getGuestByEmail(email: string, name: string, department = ''): Pr delete guest.department; return guest; } - await setDepartmentForGuest({ token: guest.token, department }); + await setDepartmentForGuest({ visitorId: guest._id, department }); return LivechatVisitors.findOneEnabledById(guest._id, {}); } return guest; diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index e5b3c53845393..a85c6f91f3511 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -281,7 +281,6 @@ export interface IUsersModel extends IBaseModel { loginTokenObject: AtLeast; }): Promise; findPersonalAccessTokenByTokenNameAndUserId({ userId, tokenName }: { userId: IUser['_id']; tokenName: string }): Promise; - setOperator(userId: string, operator: boolean): Promise; checkOnlineAgents(agentId?: string, isLivechatEnabledWhenIdle?: boolean): Promise; findOnlineAgents(agentId?: IUser['_id'], isLivechatEnabledWhenIdle?: boolean): FindCursor; countOnlineAgents(agentId: string): Promise; diff --git a/packages/models/src/models/Users.ts b/packages/models/src/models/Users.ts index 0c86e727a187f..ad55ec5c84050 100644 --- a/packages/models/src/models/Users.ts +++ b/packages/models/src/models/Users.ts @@ -1832,18 +1832,6 @@ export class UsersRaw extends BaseRaw> implements IU return this.findOne(query); } - // TODO: check if this is still valid/used for something - setOperator(_id: IUser['_id'], operator: boolean) { - // TODO:: Create class Agent - const update = { - $set: { - operator, - }, - }; - - return this.updateOne({ _id }, update); - } - async checkOnlineAgents(agentId: IUser['_id'], isLivechatEnabledWhenAgentIdle?: boolean) { // TODO:: Create class Agent const query = queryStatusAgentOnline(agentId && { _id: agentId }, isLivechatEnabledWhenAgentIdle); @@ -3359,9 +3347,6 @@ export class UsersRaw extends BaseRaw> implements IU removeAgent(_id: IUser['_id']) { const update: UpdateFilter = { - $set: { - operator: false, - }, $unset: { livechat: 1, statusLivechat: 1, From 5daf86f5b200e52659ad474ddaa50e8d32375180 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 09:04:29 -0600 Subject: [PATCH 02/17] optimize a bit getRequiredDep --- .../app/livechat/server/lib/departmentsLib.ts | 17 +++++--------- .../src/models/ILivechatDepartmentModel.ts | 4 ++++ .../models/src/models/LivechatDepartment.ts | 22 +++++++++++++++++++ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/departmentsLib.ts b/apps/meteor/app/livechat/server/lib/departmentsLib.ts index 7d5eebc9a6633..71f1df5a62583 100644 --- a/apps/meteor/app/livechat/server/lib/departmentsLib.ts +++ b/apps/meteor/app/livechat/server/lib/departmentsLib.ts @@ -254,10 +254,8 @@ export async function removeDepartment(departmentId: string) { `Performing post-department-removal actions: ${_id}. Removing department agents, unsetting fallback department and removing department from rooms`, ); - const removeByDept = LivechatDepartmentAgents.removeByDepartmentId(_id); - const promiseResponses = await Promise.allSettled([ - removeByDept, + LivechatDepartmentAgents.removeByDepartmentId(_id), LivechatDepartment.unsetFallbackDepartmentByDepartmentId(_id), LivechatRooms.bulkRemoveDepartmentAndUnitsFromRooms(_id), ]); @@ -290,16 +288,13 @@ export async function removeDepartment(departmentId: string) { } export async function getRequiredDepartment(onlineRequired = true) { - const departments = LivechatDepartment.findEnabledWithAgents(); + if (!onlineRequired) { + return LivechatDepartment.findOneEnabledWithAgentsAndRegistration(); + } - for await (const dept of departments) { - if (!dept.showOnRegistration) { - continue; - } - if (!onlineRequired) { - return dept; - } + const departments = LivechatDepartment.findEnabledWithAgentsAndRegistration(); + for await (const dept of departments) { const onlineAgents = await LivechatDepartmentAgents.countOnlineForDepartment(dept._id); if (onlineAgents) { return dept; diff --git a/packages/model-typings/src/models/ILivechatDepartmentModel.ts b/packages/model-typings/src/models/ILivechatDepartmentModel.ts index a38eac14e85e8..5b7291c0fc26c 100644 --- a/packages/model-typings/src/models/ILivechatDepartmentModel.ts +++ b/packages/model-typings/src/models/ILivechatDepartmentModel.ts @@ -64,4 +64,8 @@ export interface ILivechatDepartmentModel extends IBaseModel; addDepartmentToUnit(_id: string, unitId: string, ancestors: string[]): Promise; removeDepartmentFromUnit(_id: string): Promise; + findEnabledWithAgentsAndRegistration(projection?: FindOptions['projection']): FindCursor; + findOneEnabledWithAgentsAndRegistration( + projection?: FindOptions['projection'], + ): Promise; } diff --git a/packages/models/src/models/LivechatDepartment.ts b/packages/models/src/models/LivechatDepartment.ts index b4929890eb239..63ef6a745a78b 100644 --- a/packages/models/src/models/LivechatDepartment.ts +++ b/packages/models/src/models/LivechatDepartment.ts @@ -282,6 +282,28 @@ export class LivechatDepartmentRaw extends BaseRaw implemen return this.find(query, projection && { projection }); } + findEnabledWithAgentsAndRegistration( + projection: FindOptions['projection'] = {}, + ): FindCursor { + const query = { + numAgents: { $gt: 0 }, + enabled: true, + showOnRegistration: true, + }; + return this.find(query, projection && { projection }); + } + + findOneEnabledWithAgentsAndRegistration( + projection: FindOptions['projection'] = {}, + ): Promise { + const query = { + numAgents: { $gt: 0 }, + enabled: true, + showOnRegistration: true, + }; + return this.findOne(query, projection && { projection }); + } + async findEnabledWithAgentsAndBusinessUnit( _: any, projection: FindOptions['projection'] = {}, From f5b7c9aca58b5a97fb688ae53e703fec9c11ce4f Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 09:18:21 -0600 Subject: [PATCH 03/17] projections --- apps/meteor/app/livechat/server/lib/guests.ts | 44 ++++++++----------- .../src/models/ILivechatRoomsModel.ts | 6 ++- packages/models/src/models/LivechatRooms.ts | 4 +- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/guests.ts b/apps/meteor/app/livechat/server/lib/guests.ts index 92a6dbf9b7c15..97769b0397803 100644 --- a/apps/meteor/app/livechat/server/lib/guests.ts +++ b/apps/meteor/app/livechat/server/lib/guests.ts @@ -1,5 +1,5 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; -import type { ILivechatVisitor, IOmnichannelRoom, UserStatus } from '@rocket.chat/core-typings'; +import type { ILivechatVisitor, IOmnichannelRoom, IUser, UserStatus } from '@rocket.chat/core-typings'; import { LivechatVisitors, LivechatCustomField, @@ -42,25 +42,12 @@ export async function saveGuest( } livechatLogger.debug({ msg: 'Saving guest', guestData }); - const updateData: { - name?: string | undefined; - username?: string | undefined; - email?: string | undefined; - phone?: string | undefined; - livechatData: { - [k: string]: any; - }; - } = { livechatData: {} }; - - if (name) { - updateData.name = name; - } - if (email) { - updateData.email = email; - } - if (phone) { - updateData.phone = phone; - } + const updateData = { + ...(name && { name }), + ...(email && { email }), + ...(phone && { phone }), + ...(livechatData && { livechatData: {} }), + }; const customFields: Record = {}; @@ -147,7 +134,8 @@ async function cleanGuestHistory(token: string) { throw new Error('error-invalid-guest'); } - const cursor = LivechatRooms.findByVisitorToken(token); + // TODO: optimize function => instead of removing one by one, fetch the _ids of the rooms and then remove them in bulk + const cursor = LivechatRooms.findByVisitorToken(token, { projection: { _id: 1 } }); for await (const room of cursor) { await Promise.all([ Subscriptions.removeByRoomId(room._id, { @@ -174,7 +162,11 @@ export async function getLivechatRoomGuestInfo(room: IOmnichannelRoom) { throw new Error('error-invalid-visitor'); } - const agent = room.servedBy?._id ? await Users.findOneById(room.servedBy?._id) : null; + const agent = room.servedBy?._id + ? await Users.findOneById>(room.servedBy?._id, { + projection: { _id: 1, customFields: 1, name: 1, username: 1, emails: 1 }, + }) + : null; const ua = new UAParser(); ua.setUA(visitor.userAgent || ''); @@ -230,10 +222,10 @@ export async function getLivechatRoomGuestInfo(room: IOmnichannelRoom) { } export async function notifyGuestStatusChanged(token: string, status: UserStatus) { - // TODO: a promise.all maybe? - await LivechatRooms.updateVisitorStatus(token, status); - - const inquiryVisitorStatus = await LivechatInquiry.updateVisitorStatus(token, status); + const [, inquiryVisitorStatus] = await Promise.all([ + LivechatRooms.updateVisitorStatus(token, status), + LivechatInquiry.updateVisitorStatus(token, status), + ]); if (inquiryVisitorStatus.modifiedCount) { void notifyOnLivechatInquiryChangedByToken(token, 'updated', { v: { status } }); diff --git a/packages/model-typings/src/models/ILivechatRoomsModel.ts b/packages/model-typings/src/models/ILivechatRoomsModel.ts index 1a7c541a24f30..b78baf040a893 100644 --- a/packages/model-typings/src/models/ILivechatRoomsModel.ts +++ b/packages/model-typings/src/models/ILivechatRoomsModel.ts @@ -200,7 +200,11 @@ export interface ILivechatRoomsModel extends IBaseModel { options?: FindOptions, extraQuery?: Filter, ): FindCursor; - findByVisitorToken(visitorToken: string, extraQuery?: Filter): FindCursor; + findByVisitorToken( + visitorToken: string, + extraQuery?: Filter, + options?: FindOptions, + ): FindCursor; findByVisitorIdAndAgentId( visitorId?: string, agentId?: string, diff --git a/packages/models/src/models/LivechatRooms.ts b/packages/models/src/models/LivechatRooms.ts index aef7c2e495082..46eeab337cc67 100644 --- a/packages/models/src/models/LivechatRooms.ts +++ b/packages/models/src/models/LivechatRooms.ts @@ -1976,14 +1976,14 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive return this.find(query, options); } - findByVisitorToken(visitorToken: string, extraQuery: Filter = {}) { + findByVisitorToken(visitorToken: string, extraQuery: Filter = {}, options?: FindOptions) { const query: Filter = { 't': 'l', 'v.token': visitorToken, ...extraQuery, }; - return this.find(query); + return this.find(query, options); } findByVisitorIdAndAgentId( From 61249229c7470edffe0eb0f1ba6f643d82e6bc5c Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 09:54:49 -0600 Subject: [PATCH 04/17] fixes --- .../app/livechat/imports/server/rest/users.ts | 40 +++++-------------- .../app/livechat/server/lib/messages.ts | 21 ++++------ .../app/livechat/server/lib/omni-users.ts | 26 +++++------- .../src/models/ILivechatRoomsModel.ts | 6 ++- .../model-typings/src/models/IUsersModel.ts | 1 + packages/models/src/models/LivechatRooms.ts | 4 +- packages/models/src/models/Users.ts | 6 +++ 7 files changed, 41 insertions(+), 63 deletions(-) diff --git a/apps/meteor/app/livechat/imports/server/rest/users.ts b/apps/meteor/app/livechat/imports/server/rest/users.ts index b8d5088d80418..672434d3a1ba1 100644 --- a/apps/meteor/app/livechat/imports/server/rest/users.ts +++ b/apps/meteor/app/livechat/imports/server/rest/users.ts @@ -1,7 +1,6 @@ import { Users } from '@rocket.chat/models'; import { isLivechatUsersManagerGETProps, isPOSTLivechatUsersTypeProps } from '@rocket.chat/rest-typings'; import { check } from 'meteor/check'; -import _ from 'underscore'; import { API } from '../../../../api/server'; import { getPaginationItems } from '../../../../api/server/helpers/getPaginationItems'; @@ -96,45 +95,28 @@ API.v1.addRoute( { authRequired: true, permissionsRequired: ['view-livechat-manager'] }, { async get() { - const user = await Users.findOneById(this.urlParams._id); - - if (!user) { - return API.v1.failure('User not found'); - } - - let role; - - if (this.urlParams.type === 'agent') { - role = 'livechat-agent'; - } else if (this.urlParams.type === 'manager') { - role = 'livechat-manager'; - } else { + if (!['agent', 'manager'].includes(this.urlParams.type)) { throw new Error('Invalid type'); } + const role = this.urlParams.type === 'agent' ? 'livechat-agent' : 'livechat-manager'; - if (user.roles.indexOf(role) !== -1) { - return API.v1.success({ - user: _.pick(user, '_id', 'username', 'name', 'status', 'statusLivechat', 'emails', 'livechat'), - }); - } - - return API.v1.success({ - user: null, + const user = await Users.findOneByIdAndRole(this.urlParams._id, role, { + projection: { _id: 1, username: 1, name: 1, status: 1, statusLivechat: 1, emails: 1, livechat: 1 }, }); - }, - async delete() { - const user = await Users.findOneById(this.urlParams._id); - if (!user?.username) { - return API.v1.failure(); + if (!user) { + return API.v1.failure('User not found'); } + return API.v1.success({ user }); + }, + async delete() { if (this.urlParams.type === 'agent') { - if (await removeAgent(user.username)) { + if (await removeAgent(this.urlParams._id)) { return API.v1.success(); } } else if (this.urlParams.type === 'manager') { - if (await removeManager(user.username)) { + if (await removeManager(this.urlParams._id)) { return API.v1.success(); } } else { diff --git a/apps/meteor/app/livechat/server/lib/messages.ts b/apps/meteor/app/livechat/server/lib/messages.ts index b37624dd723ba..b04749a2053b8 100644 --- a/apps/meteor/app/livechat/server/lib/messages.ts +++ b/apps/meteor/app/livechat/server/lib/messages.ts @@ -80,21 +80,17 @@ export async function sendOfflineMessage(data: OfflineMessageData) { const fromText = `${name} - ${email} <${from}>`; const replyTo = `${name} <${email}>`; const subject = `Livechat offline message from ${name}: ${`${emailMessage}`.substring(0, 20)}`; - await sendEmail(fromText, emailTo, replyTo, subject, html); - - setImmediate(() => { - void callbacks.run('livechat.offlineMessage', data); - }); -} - -async function sendEmail(from: string, to: string, replyTo: string, subject: string, html: string): Promise { await Mailer.send({ - to, - from, + to: emailTo, + from: fromText, replyTo, subject, html, }); + + setImmediate(() => { + void callbacks.run('livechat.offlineMessage', data); + }); } export async function updateMessage({ guest, message }: { guest: ILivechatVisitor; message: AtLeast }) { @@ -147,10 +143,7 @@ export async function sendMessage({ agent?: SelectedAgent; }) { const { room, newRoom } = await getRoom(guest, message, roomInfo, agent); - if (guest.name) { - message.alias = guest.name; - } - return Object.assign(await sendMessageFunc(guest, { ...message, token: guest.token }, room), { + return Object.assign(await sendMessageFunc(guest, { ...message, token: guest.token, ...(guest.name && { alias: guest.name }) }, room), { newRoom, showConnecting: showConnecting(), }); diff --git a/apps/meteor/app/livechat/server/lib/omni-users.ts b/apps/meteor/app/livechat/server/lib/omni-users.ts index 3b144196b7b1e..6925a4de1b7ef 100644 --- a/apps/meteor/app/livechat/server/lib/omni-users.ts +++ b/apps/meteor/app/livechat/server/lib/omni-users.ts @@ -1,5 +1,5 @@ import { api } from '@rocket.chat/core-services'; -import type { UserStatus } from '@rocket.chat/core-typings'; +import type { UserStatus, IUser } from '@rocket.chat/core-typings'; import { LivechatDepartment, LivechatDepartmentAgents, LivechatRooms, Users } from '@rocket.chat/models'; import { removeEmpty } from '@rocket.chat/tools'; @@ -21,18 +21,15 @@ export async function notifyAgentStatusChanged(userId: string, status?: UserStat return; } - await LivechatRooms.findOpenByAgent(userId).forEach((room) => { + for await (const room of LivechatRooms.findOpenByAgent(userId, { projection: { _id: 1 } })) { void api.broadcast('omnichannel.room', room._id, { type: 'agentStatus', status, }); - }); + } } export async function addManager(username: string) { - // TODO: remove 'check' function call - check(username, String); - const user = await Users.findOneByUsername(username, { projection: { _id: 1, username: 1 } }); if (!user) { @@ -47,8 +44,6 @@ export async function addManager(username: string) { } export async function addAgent(username: string) { - check(username, String); - const user = await Users.findOneByUsername(username, { projection: { _id: 1, username: 1 } }); if (!user) { @@ -62,14 +57,12 @@ export async function addAgent(username: string) { return false; } -export async function removeAgent(username: string) { - // TODO: we already validated user exists at this point, remove this check - const user = await Users.findOneByUsername(username, { projection: { _id: 1, username: 1 } }); +export async function removeAgent(id: IUser['_id']) { + const user = await Users.findOneByUsername(id, { projection: { _id: 1, username: 1 } }); if (!user) { - throw new Error('error-invalid-user'); + throw new Meteor.Error('error-invalid-user'); } - const { _id } = user; if (await removeUserFromRolesAsync(_id, ['livechat-agent'])) { @@ -79,12 +72,11 @@ export async function removeAgent(username: string) { return false; } -export async function removeManager(username: string) { - // TODO: we already validated user exists at this point, remove this check - const user = await Users.findOneByUsername(username, { projection: { _id: 1 } }); +export async function removeManager(id: IUser['_id']) { + const user = await Users.findOneByUsername(id, { projection: { _id: 1, username: 1 } }); if (!user) { - throw new Error('error-invalid-user'); + throw new Meteor.Error('error-invalid-user'); } return removeUserFromRolesAsync(user._id, ['livechat-manager']); diff --git a/packages/model-typings/src/models/ILivechatRoomsModel.ts b/packages/model-typings/src/models/ILivechatRoomsModel.ts index b78baf040a893..d8cf90e346b67 100644 --- a/packages/model-typings/src/models/ILivechatRoomsModel.ts +++ b/packages/model-typings/src/models/ILivechatRoomsModel.ts @@ -256,7 +256,11 @@ export interface ILivechatRoomsModel extends IBaseModel { date: { gte: Date; lte: Date }, data?: { departmentId: string }, ): AggregationCursor>; - findOpenByAgent(userId: string, extraQuery?: Filter): FindCursor; + findOpenByAgent( + userId: string, + extraQuery?: Filter, + options?: FindOptions, + ): FindCursor; countOpenByAgent(userId: string, extraQuery?: Filter): Promise; changeAgentByRoomId(roomId: string, newAgent: { agentId: string; username: string }): Promise; changeDepartmentIdByRoomId(roomId: string, departmentId: string): Promise; diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index a85c6f91f3511..1bbdca75707b1 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -440,4 +440,5 @@ export interface IUsersModel extends IBaseModel { findUsersWithAssignedFreeSwitchExtensions(options?: FindOptions): FindCursor; countUsersInRoles(roles: IRole['_id'][]): Promise; countAllUsersWithPendingAvatar(): Promise; + findOneByIdAndRole(userId: IUser['_id'], role: string, options: FindOptions): Promise; } diff --git a/packages/models/src/models/LivechatRooms.ts b/packages/models/src/models/LivechatRooms.ts index 46eeab337cc67..de58f52fb8adb 100644 --- a/packages/models/src/models/LivechatRooms.ts +++ b/packages/models/src/models/LivechatRooms.ts @@ -2320,7 +2320,7 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive return this.countDocuments(query); } - findOpenByAgent(userId: string, extraQuery: Filter = {}) { + findOpenByAgent(userId: string, extraQuery: Filter = {}, options: FindOptions = {}) { const query: Filter = { 't': 'l', 'open': true, @@ -2328,7 +2328,7 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive ...extraQuery, }; - return this.find(query); + return this.find(query, options); } changeAgentByRoomId(roomId: string, newAgent: { agentId: string; username: string; ts?: Date }) { diff --git a/packages/models/src/models/Users.ts b/packages/models/src/models/Users.ts index ad55ec5c84050..8611dd9573ee1 100644 --- a/packages/models/src/models/Users.ts +++ b/packages/models/src/models/Users.ts @@ -2454,6 +2454,12 @@ export class UsersRaw extends BaseRaw> implements IU return this.find(query, options); } + findOneByIdAndRole(userId: IUser['_id'], role: string, options: FindOptions = {}) { + const query = { _id: userId, roles: role }; + + return this.findOne(query, options); + } + async findByRoomId(rid: IRoom['_id'], options?: FindOptions) { const data = (await Subscriptions.findByRoomId(rid).toArray()).map((item) => item.u._id); const query = { From 8f840754fa717b74a8018b0c359eab80360ffc27 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 10:30:12 -0600 Subject: [PATCH 05/17] optimize findonline --- .../app/livechat/server/api/v1/visitor.ts | 4 +-- apps/meteor/app/livechat/server/lib/guests.ts | 4 +-- .../app/livechat/server/lib/sendTranscript.ts | 20 +++++--------- .../app/livechat/server/lib/service-status.ts | 13 +++------ .../models/ILivechatDepartmentAgentsModel.ts | 4 --- .../src/models/LivechatDepartmentAgents.ts | 27 ------------------- 6 files changed, 13 insertions(+), 59 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/v1/visitor.ts b/apps/meteor/app/livechat/server/api/v1/visitor.ts index d685f3e59757a..9a75d78d8c552 100644 --- a/apps/meteor/app/livechat/server/api/v1/visitor.ts +++ b/apps/meteor/app/livechat/server/api/v1/visitor.ts @@ -184,8 +184,8 @@ API.v1.addRoute('livechat/visitor/:token', { throw new Meteor.Error('visitor-has-open-rooms', 'Cannot remove visitors with opened rooms'); } - const { _id } = visitor; - const result = await removeGuest(_id); + const { _id, token } = visitor; + const result = await removeGuest({ _id, token }); if (!result.modifiedCount) { throw new Meteor.Error('error-removing-visitor', 'An error ocurred while deleting visitor'); } diff --git a/apps/meteor/app/livechat/server/lib/guests.ts b/apps/meteor/app/livechat/server/lib/guests.ts index 97769b0397803..95904ada1d4b6 100644 --- a/apps/meteor/app/livechat/server/lib/guests.ts +++ b/apps/meteor/app/livechat/server/lib/guests.ts @@ -78,13 +78,13 @@ export async function saveGuest( return ret; } -export async function removeGuest(_id: string) { +export async function removeGuest({ _id, token }: { _id: string; token: string }) { const guest = await LivechatVisitors.findOneEnabledById(_id, { projection: { _id: 1, token: 1 } }); if (!guest) { throw new Error('error-invalid-guest'); } - await cleanGuestHistory(guest.token); + await cleanGuestHistory(token); return LivechatVisitors.disableById(_id); } diff --git a/apps/meteor/app/livechat/server/lib/sendTranscript.ts b/apps/meteor/app/livechat/server/lib/sendTranscript.ts index 9d4af9f809730..32a3deb3973a2 100644 --- a/apps/meteor/app/livechat/server/lib/sendTranscript.ts +++ b/apps/meteor/app/livechat/server/lib/sendTranscript.ts @@ -4,6 +4,7 @@ import { type MessageTypesValues, type IOmnichannelSystemMessage, type ILivechatVisitor, + type IOmnichannelRoom, isFileAttachment, isFileImageAttachment, type AtLeast, @@ -11,7 +12,6 @@ import { import colors from '@rocket.chat/fuselage-tokens/colors'; import { Logger } from '@rocket.chat/logger'; import { LivechatRooms, Messages, Uploads, Users } from '@rocket.chat/models'; -import { check } from 'meteor/check'; import moment from 'moment-timezone'; import { callbacks } from '../../../../lib/callbacks'; @@ -37,11 +37,12 @@ export async function sendTranscript({ subject?: string; user?: Pick | null; }): Promise { - check(rid, String); - check(email, String); logger.debug(`Sending conversation transcript of room ${rid} to user with token ${token}`); - const room = await LivechatRooms.findOneById(rid); + const room = await LivechatRooms.findOneById>(rid, { projection: { _id: 1, v: 1 } }); + if (!room) { + throw new Error('error-invalid-room'); + } const visitor = room?.v as ILivechatVisitor; if (token !== visitor?.token) { @@ -52,15 +53,6 @@ export async function sendTranscript({ const timezone = getTimezone(user); logger.debug(`Transcript will be sent using ${timezone} as timezone`); - if (!room) { - throw new Error('error-invalid-room'); - } - - // allow to only user to send transcripts from their own chats - if (room.t !== 'l') { - throw new Error('error-invalid-room'); - } - const showAgentInfo = settings.get('Livechat_show_agent_info'); const showSystemMessages = settings.get('Livechat_transcript_show_system_messages'); const closingMessage = await Messages.findLivechatClosingMessage(rid, { projection: { ts: 1 } }); @@ -74,7 +66,7 @@ export async function sendTranscript({ 'omnichannel_priority_change_history', ]; const acceptableImageMimeTypes = ['image/jpeg', 'image/png', 'image/jpg']; - const messages = await Messages.findVisibleByRoomIdNotContainingTypesBeforeTs( + const messages = Messages.findVisibleByRoomIdNotContainingTypesBeforeTs( rid, ignoredMessageTypes, closingMessage?.ts ? new Date(closingMessage.ts) : new Date(), diff --git a/apps/meteor/app/livechat/server/lib/service-status.ts b/apps/meteor/app/livechat/server/lib/service-status.ts index 65b9b606d6713..d877da785c55b 100644 --- a/apps/meteor/app/livechat/server/lib/service-status.ts +++ b/apps/meteor/app/livechat/server/lib/service-status.ts @@ -11,17 +11,10 @@ export async function getOnlineAgents(department?: string, agent?: SelectedAgent } if (department) { - const departmentAgents = await LivechatDepartmentAgents.getOnlineForDepartment(department); - if (!departmentAgents) { - return; - } - - const agentIds = await departmentAgents.map(({ agentId }) => agentId).toArray(); - if (!agentIds.length) { - return; - } + const agents = await LivechatDepartmentAgents.findByDepartmentId(department, { projection: { agentId: 1 } }).toArray(); + const agentIds = agents.map(({ agentId }) => agentId); - return Users.findByIds([...new Set(agentIds)]); + return Users.findOnlineUserFromList([...new Set(agentIds)], settings.get('Livechat_enabled_when_agent_idle')); } return Users.findOnlineAgents(undefined, settings.get('Livechat_enabled_when_agent_idle')); } diff --git a/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts b/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts index 827b266421b32..c0b5958a85bfb 100644 --- a/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts +++ b/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts @@ -60,10 +60,6 @@ export interface ILivechatDepartmentAgentsModel extends IBaseModel, ): Promise | null | undefined>; checkOnlineForDepartment(departmentId: string): Promise; - getOnlineForDepartment( - departmentId: string, - isLivechatEnabledWhenAgentIdle?: boolean, - ): Promise | undefined>; countOnlineForDepartment(departmentId: string, isLivechatEnabledWhenAgentIdle?: boolean): Promise; getBotsForDepartment(departmentId: string): Promise>; countBotsForDepartment(departmentId: string): Promise; diff --git a/packages/models/src/models/LivechatDepartmentAgents.ts b/packages/models/src/models/LivechatDepartmentAgents.ts index 064a59704154c..fb5eed1bd1270 100644 --- a/packages/models/src/models/LivechatDepartmentAgents.ts +++ b/packages/models/src/models/LivechatDepartmentAgents.ts @@ -238,33 +238,6 @@ export class LivechatDepartmentAgentsRaw extends BaseRaw | undefined> { - const agents = await this.findByDepartmentId(departmentId).toArray(); - - if (agents.length === 0) { - return; - } - - const onlineUsers = await Users.findOnlineUserFromList( - agents.map((a) => a.username), - isLivechatEnabledWhenAgentIdle, - ).toArray(); - - const onlineUsernames = onlineUsers.map((user) => user.username).filter(isStringValue); - - const query = { - departmentId, - username: { - $in: onlineUsernames, - }, - }; - - return this.find(query); - } - async countOnlineForDepartment(departmentId: string, isLivechatEnabledWhenAgentIdle?: boolean): Promise { const agents = await this.findByDepartmentId(departmentId, { projection: { username: 1 } }).toArray(); From b680a77bdc9c1b79a54f232d92c983d5e0f2c14b Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 10:56:51 -0600 Subject: [PATCH 06/17] improve --- .../app/livechat/server/lib/QueueManager.ts | 5 ++-- .../app/livechat/server/lib/departmentsLib.ts | 29 +++++++++++++++++-- .../app/livechat/server/lib/service-status.ts | 12 ++++---- .../models/ILivechatDepartmentAgentsModel.ts | 2 -- .../src/models/LivechatDepartmentAgents.ts | 25 ---------------- 5 files changed, 34 insertions(+), 39 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index 7460bc6bc8247..cfbf74cee86c2 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -14,7 +14,7 @@ import type { import { LivechatInquiryStatus } from '@rocket.chat/core-typings'; import { Logger } from '@rocket.chat/logger'; import type { InsertionModel } from '@rocket.chat/model-typings'; -import { LivechatContacts, LivechatDepartment, LivechatDepartmentAgents, LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models'; +import { LivechatContacts, LivechatDepartment, LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -22,6 +22,7 @@ import { Meteor } from 'meteor/meteor'; import { createLivechatRoom, createLivechatInquiry, allowAgentSkipQueue, prepareLivechatRoom } from './Helper'; import { RoutingManager } from './RoutingManager'; import { isVerifiedChannelInSource } from './contacts/isVerifiedChannelInSource'; +import { checkOnlineForDepartment } from './departmentsLib'; import { checkOnlineAgents, getOnlineAgents } from './service-status'; import { getInquirySortMechanismSetting } from './settings'; import { dispatchInquiryPosition } from '../../../../ee/app/livechat-enterprise/server/lib/Helper'; @@ -69,7 +70,7 @@ const getDepartment = async (department: string): Promise => return; } - if (await LivechatDepartmentAgents.checkOnlineForDepartment(department)) { + if (await checkOnlineForDepartment(department)) { return department; } diff --git a/apps/meteor/app/livechat/server/lib/departmentsLib.ts b/apps/meteor/app/livechat/server/lib/departmentsLib.ts index 71f1df5a62583..219d6253ac650 100644 --- a/apps/meteor/app/livechat/server/lib/departmentsLib.ts +++ b/apps/meteor/app/livechat/server/lib/departmentsLib.ts @@ -1,6 +1,6 @@ import { AppEvents, Apps } from '@rocket.chat/apps'; -import type { LivechatDepartmentDTO, ILivechatDepartment, ILivechatDepartmentAgents } from '@rocket.chat/core-typings'; -import { LivechatDepartment, LivechatDepartmentAgents, LivechatVisitors, LivechatRooms } from '@rocket.chat/models'; +import type { LivechatDepartmentDTO, ILivechatDepartment, ILivechatDepartmentAgents, ILivechatAgent } from '@rocket.chat/core-typings'; +import { LivechatDepartment, LivechatDepartmentAgents, LivechatVisitors, LivechatRooms, Users } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { updateDepartmentAgents } from './Helper'; @@ -12,6 +12,7 @@ import { notifyOnLivechatDepartmentAgentChangedByDepartmentId, notifyOnLivechatDepartmentAgentChanged, } from '../../../lib/server/lib/notifyListener'; +import { settings } from '../../../settings/server'; /** * @param {string|null} _id - The department id * @param {Partial} departmentData @@ -295,9 +296,31 @@ export async function getRequiredDepartment(onlineRequired = true) { const departments = LivechatDepartment.findEnabledWithAgentsAndRegistration(); for await (const dept of departments) { - const onlineAgents = await LivechatDepartmentAgents.countOnlineForDepartment(dept._id); + const departmentAgents = await LivechatDepartmentAgents.findByDepartmentId(dept._id, { projection: { username: 1 } }).toArray(); + const onlineAgents = await Users.countOnlineUserFromList( + departmentAgents.map((a) => a.username), + settings.get('Livechat_enabled_when_agent_idle'), + ); if (onlineAgents) { return dept; } } } + +export async function checkOnlineForDepartment(departmentId: string) { + const depUsers = await LivechatDepartmentAgents.findByDepartmentId(departmentId, { projection: { username: 1 } }).toArray(); + const onlineForDep = await Users.findOneOnlineAgentByUserList( + depUsers.map((agent) => agent.username), + { projection: { _id: 1 } }, + settings.get('Livechat_enabled_when_agent_idle'), + ); + + return !!onlineForDep; +} + +export async function getOnlineForDepartment(departmentId: string) { + const agents = await LivechatDepartmentAgents.findByDepartmentId(departmentId, { projection: { username: 1 } }).toArray(); + const usernames = agents.map(({ username }) => username); + + return Users.findOnlineUserFromList([...new Set(usernames)], settings.get('Livechat_enabled_when_agent_idle')); +} diff --git a/apps/meteor/app/livechat/server/lib/service-status.ts b/apps/meteor/app/livechat/server/lib/service-status.ts index d877da785c55b..fc57ff7817323 100644 --- a/apps/meteor/app/livechat/server/lib/service-status.ts +++ b/apps/meteor/app/livechat/server/lib/service-status.ts @@ -2,6 +2,7 @@ import type { ILivechatAgent, ILivechatDepartment, SelectedAgent } from '@rocket import { Users, LivechatDepartmentAgents, LivechatDepartment } from '@rocket.chat/models'; import type { FindCursor } from 'mongodb'; +import { checkOnlineForDepartment, getOnlineForDepartment } from './departmentsLib'; import { livechatLogger } from './logger'; import { settings } from '../../../settings/server'; @@ -11,10 +12,7 @@ export async function getOnlineAgents(department?: string, agent?: SelectedAgent } if (department) { - const agents = await LivechatDepartmentAgents.findByDepartmentId(department, { projection: { agentId: 1 } }).toArray(); - const agentIds = agents.map(({ agentId }) => agentId); - - return Users.findOnlineUserFromList([...new Set(agentIds)], settings.get('Livechat_enabled_when_agent_idle')); + return getOnlineForDepartment(department); } return Users.findOnlineAgents(undefined, settings.get('Livechat_enabled_when_agent_idle')); } @@ -49,16 +47,16 @@ export async function checkOnlineAgents(department?: string, agent?: { agentId: } if (department) { - const onlineForDep = await LivechatDepartmentAgents.checkOnlineForDepartment(department); + const onlineForDep = await checkOnlineForDepartment(department); if (onlineForDep || skipFallbackCheck) { - return onlineForDep; + return !!onlineForDep; } const dep = await LivechatDepartment.findOneById>(department, { projection: { fallbackForwardDepartment: 1 }, }); if (!dep?.fallbackForwardDepartment) { - return onlineForDep; + return !!onlineForDep; } return checkOnlineAgents(dep?.fallbackForwardDepartment); diff --git a/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts b/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts index c0b5958a85bfb..2ad9ce6f20459 100644 --- a/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts +++ b/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts @@ -59,8 +59,6 @@ export interface ILivechatDepartmentAgentsModel extends IBaseModel, ): Promise | null | undefined>; - checkOnlineForDepartment(departmentId: string): Promise; - countOnlineForDepartment(departmentId: string, isLivechatEnabledWhenAgentIdle?: boolean): Promise; getBotsForDepartment(departmentId: string): Promise>; countBotsForDepartment(departmentId: string): Promise; getNextBotForDepartment( diff --git a/packages/models/src/models/LivechatDepartmentAgents.ts b/packages/models/src/models/LivechatDepartmentAgents.ts index fb5eed1bd1270..6f4187d39b6fa 100644 --- a/packages/models/src/models/LivechatDepartmentAgents.ts +++ b/packages/models/src/models/LivechatDepartmentAgents.ts @@ -226,31 +226,6 @@ export class LivechatDepartmentAgentsRaw extends BaseRaw { - const agents = await this.findByDepartmentId(departmentId).toArray(); - - if (agents.length === 0) { - return false; - } - - const onlineUser = await Users.findOneOnlineAgentByUserList(agents.map((agent) => agent.username)); - - return Boolean(onlineUser); - } - - async countOnlineForDepartment(departmentId: string, isLivechatEnabledWhenAgentIdle?: boolean): Promise { - const agents = await this.findByDepartmentId(departmentId, { projection: { username: 1 } }).toArray(); - - if (agents.length === 0) { - return 0; - } - - return Users.countOnlineUserFromList( - agents.map((a) => a.username), - isLivechatEnabledWhenAgentIdle, - ); - } - async getBotsForDepartment(departmentId: string): Promise> { const agents = await this.findByDepartmentId(departmentId).toArray(); From 11e4e771d89ea14dd40104f4c56875f88c31f528 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 11:03:33 -0600 Subject: [PATCH 07/17] fix test --- .../unit/app/livechat/server/lib/sendTranscript.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/meteor/tests/unit/app/livechat/server/lib/sendTranscript.spec.ts b/apps/meteor/tests/unit/app/livechat/server/lib/sendTranscript.spec.ts index ca39a64c21a9a..5ac938b798b45 100644 --- a/apps/meteor/tests/unit/app/livechat/server/lib/sendTranscript.spec.ts +++ b/apps/meteor/tests/unit/app/livechat/server/lib/sendTranscript.spec.ts @@ -87,7 +87,7 @@ describe('Send transcript', () => { }); it('should attempt to send an email when params are valid using default subject', async () => { modelsMock.LivechatRooms.findOneById.resolves({ t: 'l', v: { token: 'token' } }); - modelsMock.Messages.findVisibleByRoomIdNotContainingTypesBeforeTs.resolves([]); + modelsMock.Messages.findVisibleByRoomIdNotContainingTypesBeforeTs.returns([]); tStub.returns('Conversation Transcript'); await sendTranscript({ @@ -112,7 +112,7 @@ describe('Send transcript', () => { }); it('should use provided subject', async () => { modelsMock.LivechatRooms.findOneById.resolves({ t: 'l', v: { token: 'token' } }); - modelsMock.Messages.findVisibleByRoomIdNotContainingTypesBeforeTs.resolves([]); + modelsMock.Messages.findVisibleByRoomIdNotContainingTypesBeforeTs.returns([]); await sendTranscript({ rid: 'rid', @@ -137,7 +137,7 @@ describe('Send transcript', () => { }); it('should use subject from setting (when configured) when no subject provided', async () => { modelsMock.LivechatRooms.findOneById.resolves({ t: 'l', v: { token: 'token' } }); - modelsMock.Messages.findVisibleByRoomIdNotContainingTypesBeforeTs.resolves([]); + modelsMock.Messages.findVisibleByRoomIdNotContainingTypesBeforeTs.returns([]); mockSettingValues.Livechat_transcript_email_subject = 'A custom subject obtained from setting.get'; await sendTranscript({ @@ -197,7 +197,7 @@ describe('Send transcript', () => { }); it('should work when token matches room.v', async () => { modelsMock.LivechatRooms.findOneById.resolves({ t: 'l', v: { token: 'token-123' } }); - modelsMock.Messages.findVisibleByRoomIdNotContainingTypesBeforeTs.resolves([]); + modelsMock.Messages.findVisibleByRoomIdNotContainingTypesBeforeTs.returns([]); delete mockSettingValues.Livechat_transcript_email_subject; tStub.returns('Conversation Transcript'); From eb9812b752422a86f19b1c61615d0f754aa9da04 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 11:38:36 -0600 Subject: [PATCH 08/17] fix test --- apps/meteor/app/livechat/imports/server/rest/users.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/meteor/app/livechat/imports/server/rest/users.ts b/apps/meteor/app/livechat/imports/server/rest/users.ts index 672434d3a1ba1..2bcf648cf8a17 100644 --- a/apps/meteor/app/livechat/imports/server/rest/users.ts +++ b/apps/meteor/app/livechat/imports/server/rest/users.ts @@ -104,10 +104,6 @@ API.v1.addRoute( projection: { _id: 1, username: 1, name: 1, status: 1, statusLivechat: 1, emails: 1, livechat: 1 }, }); - if (!user) { - return API.v1.failure('User not found'); - } - return API.v1.success({ user }); }, async delete() { From bf2416d06645c0bb9d72eb67deaa971f96f4b701 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 12:08:04 -0600 Subject: [PATCH 09/17] improve --- apps/meteor/app/livechat/server/lib/departmentsLib.ts | 9 +-------- apps/meteor/app/livechat/server/lib/guests.ts | 5 ----- .../server/features/EmailInbox/EmailInbox_Incoming.ts | 3 +-- .../model-typings/src/models/ILivechatVisitorsModel.ts | 1 + packages/models/src/models/LivechatVisitors.ts | 4 ++++ 5 files changed, 7 insertions(+), 15 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/departmentsLib.ts b/apps/meteor/app/livechat/server/lib/departmentsLib.ts index 219d6253ac650..ae769120b1601 100644 --- a/apps/meteor/app/livechat/server/lib/departmentsLib.ts +++ b/apps/meteor/app/livechat/server/lib/departmentsLib.ts @@ -216,20 +216,13 @@ export async function setDepartmentForGuest({ visitorId, department }: { visitor department, }); - // TODO: move to model - const visitorUpdate = { - $set: { - department, - }, - }; - const dep = await LivechatDepartment.findOneById>(department, { projection: { _id: 1 } }); if (!dep) { throw new Meteor.Error('invalid-department', 'Provided department does not exists'); } // Visitor is already validated at this point - await LivechatVisitors.updateById(visitorId, visitorUpdate); + return LivechatVisitors.updateDepartmentById(visitorId, department); } export async function removeDepartment(departmentId: string) { diff --git a/apps/meteor/app/livechat/server/lib/guests.ts b/apps/meteor/app/livechat/server/lib/guests.ts index 95904ada1d4b6..8ff573e522cd3 100644 --- a/apps/meteor/app/livechat/server/lib/guests.ts +++ b/apps/meteor/app/livechat/server/lib/guests.ts @@ -79,11 +79,6 @@ export async function saveGuest( } export async function removeGuest({ _id, token }: { _id: string; token: string }) { - const guest = await LivechatVisitors.findOneEnabledById(_id, { projection: { _id: 1, token: 1 } }); - if (!guest) { - throw new Error('error-invalid-guest'); - } - await cleanGuestHistory(token); return LivechatVisitors.disableById(_id); } diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts index 1f114ac909af9..d7a6ce004fc62 100644 --- a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts +++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts @@ -36,8 +36,7 @@ async function getGuestByEmail(email: string, name: string, department = ''): Pr delete guest.department; return guest; } - await setDepartmentForGuest({ visitorId: guest._id, department }); - return LivechatVisitors.findOneEnabledById(guest._id, {}); + return setDepartmentForGuest({ visitorId: guest._id, department }); } return guest; } diff --git a/packages/model-typings/src/models/ILivechatVisitorsModel.ts b/packages/model-typings/src/models/ILivechatVisitorsModel.ts index a4b79fc1f8041..a29f59d39e9a6 100644 --- a/packages/model-typings/src/models/ILivechatVisitorsModel.ts +++ b/packages/model-typings/src/models/ILivechatVisitorsModel.ts @@ -72,4 +72,5 @@ export interface ILivechatVisitorsModel extends IBaseModel { ): Promise; setLastChatById(_id: string, lastChat: Required): Promise; countVisitorsBetweenDate({ start, end, department }: { start: Date; end: Date; department?: string }): Promise; + updateDepartmentById(_id: string, department: string): Promise>; } diff --git a/packages/models/src/models/LivechatVisitors.ts b/packages/models/src/models/LivechatVisitors.ts index ad34bef963ee9..d30155112b578 100644 --- a/packages/models/src/models/LivechatVisitors.ts +++ b/packages/models/src/models/LivechatVisitors.ts @@ -440,6 +440,10 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL }, ); } + + updateDepartmentById(_id: string, department: string) { + return this.findOneAndUpdate({ _id }, { $set: { department } }, { returnDocument: 'after' }); + } } type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; From 7f2211bd7273d9e023791207150ed8cc0969e44f Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 12:10:14 -0600 Subject: [PATCH 10/17] remove meteor.error usage --- apps/meteor/app/livechat/server/lib/omni-users.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/omni-users.ts b/apps/meteor/app/livechat/server/lib/omni-users.ts index 6925a4de1b7ef..0d7c4b555b6ff 100644 --- a/apps/meteor/app/livechat/server/lib/omni-users.ts +++ b/apps/meteor/app/livechat/server/lib/omni-users.ts @@ -33,7 +33,7 @@ export async function addManager(username: string) { const user = await Users.findOneByUsername(username, { projection: { _id: 1, username: 1 } }); if (!user) { - throw new Meteor.Error('error-invalid-user'); + throw new Error('error-invalid-user'); } if (await addUserRolesAsync(user._id, ['livechat-manager'])) { @@ -47,7 +47,7 @@ export async function addAgent(username: string) { const user = await Users.findOneByUsername(username, { projection: { _id: 1, username: 1 } }); if (!user) { - throw new Meteor.Error('error-invalid-user'); + throw new Error('error-invalid-user'); } if (await addUserRolesAsync(user._id, ['livechat-agent'])) { @@ -61,7 +61,7 @@ export async function removeAgent(id: IUser['_id']) { const user = await Users.findOneByUsername(id, { projection: { _id: 1, username: 1 } }); if (!user) { - throw new Meteor.Error('error-invalid-user'); + throw new Error('error-invalid-user'); } const { _id } = user; @@ -76,7 +76,7 @@ export async function removeManager(id: IUser['_id']) { const user = await Users.findOneByUsername(id, { projection: { _id: 1, username: 1 } }); if (!user) { - throw new Meteor.Error('error-invalid-user'); + throw new Error('error-invalid-user'); } return removeUserFromRolesAsync(user._id, ['livechat-manager']); @@ -90,7 +90,7 @@ export async function saveAgentInfo(_id: string, agentData: any, agentDepartment const user = await Users.findOneById(_id); if (!user || !(await hasRoleAsync(_id, 'livechat-agent'))) { - throw new Meteor.Error('error-user-is-not-agent', 'User is not a livechat agent'); + throw new Error('error-user-is-not-agent'); } await Users.setLivechatData(_id, removeEmpty(agentData)); From d2351351575469baa1d99c4f5d5dbafab50f3839 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 12:23:39 -0600 Subject: [PATCH 11/17] test --- .../app/livechat/imports/server/rest/users.ts | 1 + .../end-to-end/api/livechat/01-agents.ts | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/apps/meteor/app/livechat/imports/server/rest/users.ts b/apps/meteor/app/livechat/imports/server/rest/users.ts index 2bcf648cf8a17..53039e8836c4e 100644 --- a/apps/meteor/app/livechat/imports/server/rest/users.ts +++ b/apps/meteor/app/livechat/imports/server/rest/users.ts @@ -104,6 +104,7 @@ API.v1.addRoute( projection: { _id: 1, username: 1, name: 1, status: 1, statusLivechat: 1, emails: 1, livechat: 1 }, }); + // TODO: throw error instead of returning null return API.v1.success({ user }); }, async delete() { diff --git a/apps/meteor/tests/end-to-end/api/livechat/01-agents.ts b/apps/meteor/tests/end-to-end/api/livechat/01-agents.ts index b9c143c4b1c5c..24648f03749f1 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/01-agents.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/01-agents.ts @@ -305,18 +305,12 @@ describe('LIVECHAT - Agents', () => { await updatePermission('view-livechat-manager', ['admin']); await request - .get(api(`livechat/users/invalid-type/invalid-id${agent._id}`)) + .get(api(`livechat/users/invalid-type/${agent._id}`)) .set(credentials) .expect('Content-Type', 'application/json') .expect(400); }).timeout(5000); - it('should return an error when _id is invalid', async () => { - await updatePermission('view-livechat-manager', ['admin']); - - await request.get(api('livechat/users/agent/invalid-id')).set(credentials).expect('Content-Type', 'application/json').expect(400); - }).timeout(5000); - it('should return a valid user when all goes fine', async () => { await updatePermission('view-livechat-manager', ['admin']); const agent = await createAgent(); @@ -351,6 +345,24 @@ describe('LIVECHAT - Agents', () => { // cleanup await deleteUser(user); }); + + it('should return { user: null } when user is not a manager', async () => { + await updatePermission('view-livechat-manager', ['admin']); + const user = await createUser(); + await request + .get(api(`livechat/users/manager/${user._id}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('user'); + expect(res.body.user).to.be.null; + }); + + // cleanup + await deleteUser(user); + }); }); describe('DELETE livechat/users/:type/:_id', () => { From febc64dee05c12cf414c07eef195634b99403aae Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 12:37:21 -0600 Subject: [PATCH 12/17] fix --- apps/meteor/app/livechat/server/lib/omni-users.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/omni-users.ts b/apps/meteor/app/livechat/server/lib/omni-users.ts index 0d7c4b555b6ff..a348419960f5b 100644 --- a/apps/meteor/app/livechat/server/lib/omni-users.ts +++ b/apps/meteor/app/livechat/server/lib/omni-users.ts @@ -58,7 +58,7 @@ export async function addAgent(username: string) { } export async function removeAgent(id: IUser['_id']) { - const user = await Users.findOneByUsername(id, { projection: { _id: 1, username: 1 } }); + const user = await Users.findOneById(id, { projection: { _id: 1, username: 1 } }); if (!user) { throw new Error('error-invalid-user'); @@ -73,7 +73,7 @@ export async function removeAgent(id: IUser['_id']) { } export async function removeManager(id: IUser['_id']) { - const user = await Users.findOneByUsername(id, { projection: { _id: 1, username: 1 } }); + const user = await Users.findOneById(id, { projection: { _id: 1, username: 1 } }); if (!user) { throw new Error('error-invalid-user'); From 0a4ff80acdb6352787b7d2831d2078cb106cb071 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 14 Apr 2025 14:10:29 -0600 Subject: [PATCH 13/17] Use func --- .../app/livechat/server/lib/departmentsLib.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/departmentsLib.ts b/apps/meteor/app/livechat/server/lib/departmentsLib.ts index ae769120b1601..378e690b94013 100644 --- a/apps/meteor/app/livechat/server/lib/departmentsLib.ts +++ b/apps/meteor/app/livechat/server/lib/departmentsLib.ts @@ -244,9 +244,12 @@ export async function removeDepartment(departmentId: string) { const removedAgents = await LivechatDepartmentAgents.findByDepartmentId(department._id, { projection: { agentId: 1 } }).toArray(); - livechatLogger.debug( - `Performing post-department-removal actions: ${_id}. Removing department agents, unsetting fallback department and removing department from rooms`, - ); + const actions = ['Removing department agents', 'Unsetting fallback department', 'Removing department from rooms']; + livechatLogger.debug({ + msg: 'Post department removal actions', + departmentId: _id, + actions, + }); const promiseResponses = await Promise.allSettled([ LivechatDepartmentAgents.removeByDepartmentId(_id), @@ -256,7 +259,11 @@ export async function removeDepartment(departmentId: string) { promiseResponses.forEach((response, index) => { if (response.status === 'rejected') { - livechatLogger.error(`Error while performing post-department-removal actions: ${_id}. Action No: ${index}. Error:`, response.reason); + livechatLogger.error({ + msg: 'Post removal action failed', + actionId: actions[index], + error: response.reason, + }); } }); @@ -289,11 +296,7 @@ export async function getRequiredDepartment(onlineRequired = true) { const departments = LivechatDepartment.findEnabledWithAgentsAndRegistration(); for await (const dept of departments) { - const departmentAgents = await LivechatDepartmentAgents.findByDepartmentId(dept._id, { projection: { username: 1 } }).toArray(); - const onlineAgents = await Users.countOnlineUserFromList( - departmentAgents.map((a) => a.username), - settings.get('Livechat_enabled_when_agent_idle'), - ); + const onlineAgents = await checkOnlineForDepartment(dept._id); if (onlineAgents) { return dept; } From fa06aa479b81554d38b5e7a79144eacda9aec05a Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 23 Apr 2025 12:59:17 -0600 Subject: [PATCH 14/17] Apply suggestions from code review --- apps/meteor/app/livechat/imports/server/rest/departments.ts | 1 - apps/meteor/app/livechat/server/lib/departmentsLib.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/apps/meteor/app/livechat/imports/server/rest/departments.ts b/apps/meteor/app/livechat/imports/server/rest/departments.ts index 0da9d6b8e1bac..1be4c54e74971 100644 --- a/apps/meteor/app/livechat/imports/server/rest/departments.ts +++ b/apps/meteor/app/livechat/imports/server/rest/departments.ts @@ -266,7 +266,6 @@ API.v1.addRoute( 'livechat/department/:_id/agents', { authRequired: true, - // TODO: Use AJV permissionsRequired: { GET: { permissions: ['view-livechat-departments', 'view-l-room'], operation: 'hasAny' }, POST: { permissions: ['manage-livechat-departments', 'add-livechat-department-agents'], operation: 'hasAny' }, diff --git a/apps/meteor/app/livechat/server/lib/departmentsLib.ts b/apps/meteor/app/livechat/server/lib/departmentsLib.ts index 378e690b94013..2ebe8a4549a5b 100644 --- a/apps/meteor/app/livechat/server/lib/departmentsLib.ts +++ b/apps/meteor/app/livechat/server/lib/departmentsLib.ts @@ -180,7 +180,6 @@ export async function saveDepartmentAgents( remove?: Pick[]; }, ) { - // TODO: remove when endpoint is validated check(_id, String); check(departmentAgents, { upsert: Match.Maybe([ From 9f98af6f5e239210f85d936ae02cf61be2014e68 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 23 Apr 2025 13:34:06 -0600 Subject: [PATCH 15/17] review --- .../meteor/app/livechat/server/lib/departmentsLib.ts | 12 +++++++++--- apps/meteor/app/livechat/server/lib/guests.ts | 2 +- apps/meteor/app/livechat/server/lib/hooks.ts | 10 ++-------- apps/meteor/app/livechat/server/lib/messages.ts | 5 +++-- apps/meteor/app/livechat/server/lib/omni-users.ts | 4 ++-- .../src/models/ILivechatDepartmentModel.ts | 4 ++-- packages/models/src/models/LivechatDepartment.ts | 4 ++-- 7 files changed, 21 insertions(+), 20 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/departmentsLib.ts b/apps/meteor/app/livechat/server/lib/departmentsLib.ts index 2ebe8a4549a5b..ab1897fed8439 100644 --- a/apps/meteor/app/livechat/server/lib/departmentsLib.ts +++ b/apps/meteor/app/livechat/server/lib/departmentsLib.ts @@ -160,17 +160,23 @@ export async function archiveDepartment(_id: string) { throw new Error('department-not-found'); } - await afterDepartmentArchived(department); + const status = await LivechatDepartment.archiveDepartment(department._id); + if (status.modifiedCount) { + await afterDepartmentArchived(department); + } } export async function unarchiveDepartment(_id: string) { - const department = await LivechatDepartment.findOneById(_id, { projection: { _id: 1 } }); + const department = await LivechatDepartment.findOneById>(_id, { projection: { _id: 1 } }); if (!department) { throw new Meteor.Error('department-not-found'); } - await afterDepartmentUnarchived(department); + const status = await LivechatDepartment.unarchiveDepartment(department._id); + if (status.modifiedCount) { + await afterDepartmentUnarchived(department); + } } export async function saveDepartmentAgents( diff --git a/apps/meteor/app/livechat/server/lib/guests.ts b/apps/meteor/app/livechat/server/lib/guests.ts index 8ff573e522cd3..86ddf28fd2ce1 100644 --- a/apps/meteor/app/livechat/server/lib/guests.ts +++ b/apps/meteor/app/livechat/server/lib/guests.ts @@ -46,7 +46,7 @@ export async function saveGuest( ...(name && { name }), ...(email && { email }), ...(phone && { phone }), - ...(livechatData && { livechatData: {} }), + livechatData: {}, }; const customFields: Record = {}; diff --git a/apps/meteor/app/livechat/server/lib/hooks.ts b/apps/meteor/app/livechat/server/lib/hooks.ts index f2d1c1128e437..f97b1b8ed1369 100644 --- a/apps/meteor/app/livechat/server/lib/hooks.ts +++ b/apps/meteor/app/livechat/server/lib/hooks.ts @@ -26,10 +26,7 @@ export async function afterRemoveAgent(user: AtLeast) } export async function afterDepartmentArchived(department: AtLeast) { - await Promise.all([ - LivechatDepartmentAgents.disableAgentsByDepartmentId(department._id), - LivechatDepartment.archiveDepartment(department._id), - ]); + await LivechatDepartmentAgents.disableAgentsByDepartmentId(department._id); void notifyOnLivechatDepartmentAgentChangedByDepartmentId(department._id); @@ -37,10 +34,7 @@ export async function afterDepartmentArchived(department: AtLeast) { - await Promise.all([ - LivechatDepartmentAgents.enableAgentsByDepartmentId(department._id), - LivechatDepartment.unarchiveDepartment(department._id), - ]); + await LivechatDepartmentAgents.enableAgentsByDepartmentId(department._id); void notifyOnLivechatDepartmentAgentChangedByDepartmentId(department._id); } diff --git a/apps/meteor/app/livechat/server/lib/messages.ts b/apps/meteor/app/livechat/server/lib/messages.ts index b04749a2053b8..6db7855953d76 100644 --- a/apps/meteor/app/livechat/server/lib/messages.ts +++ b/apps/meteor/app/livechat/server/lib/messages.ts @@ -143,8 +143,9 @@ export async function sendMessage({ agent?: SelectedAgent; }) { const { room, newRoom } = await getRoom(guest, message, roomInfo, agent); - return Object.assign(await sendMessageFunc(guest, { ...message, token: guest.token, ...(guest.name && { alias: guest.name }) }, room), { + return { + ...(await sendMessageFunc(guest, { ...message, token: guest.token, ...(guest.name && { alias: guest.name }) }, room)), newRoom, showConnecting: showConnecting(), - }); + }; } diff --git a/apps/meteor/app/livechat/server/lib/omni-users.ts b/apps/meteor/app/livechat/server/lib/omni-users.ts index a348419960f5b..166832d033761 100644 --- a/apps/meteor/app/livechat/server/lib/omni-users.ts +++ b/apps/meteor/app/livechat/server/lib/omni-users.ts @@ -58,7 +58,7 @@ export async function addAgent(username: string) { } export async function removeAgent(id: IUser['_id']) { - const user = await Users.findOneById(id, { projection: { _id: 1, username: 1 } }); + const user = await Users.findOneById>(id, { projection: { _id: 1, username: 1 } }); if (!user) { throw new Error('error-invalid-user'); @@ -73,7 +73,7 @@ export async function removeAgent(id: IUser['_id']) { } export async function removeManager(id: IUser['_id']) { - const user = await Users.findOneById(id, { projection: { _id: 1, username: 1 } }); + const user = await Users.findOneById>(id, { projection: { _id: 1 } }); if (!user) { throw new Error('error-invalid-user'); diff --git a/packages/model-typings/src/models/ILivechatDepartmentModel.ts b/packages/model-typings/src/models/ILivechatDepartmentModel.ts index 5b7291c0fc26c..720af8e4517b0 100644 --- a/packages/model-typings/src/models/ILivechatDepartmentModel.ts +++ b/packages/model-typings/src/models/ILivechatDepartmentModel.ts @@ -60,8 +60,8 @@ export interface ILivechatDepartmentModel extends IBaseModel; countArchived(): Promise; findEnabledInIds(departmentsIds: string[], options?: FindOptions): FindCursor; - archiveDepartment(_id: string): Promise; - unarchiveDepartment(_id: string): Promise; + archiveDepartment(_id: string): Promise; + unarchiveDepartment(_id: string): Promise; addDepartmentToUnit(_id: string, unitId: string, ancestors: string[]): Promise; removeDepartmentFromUnit(_id: string): Promise; findEnabledWithAgentsAndRegistration(projection?: FindOptions['projection']): FindCursor; diff --git a/packages/models/src/models/LivechatDepartment.ts b/packages/models/src/models/LivechatDepartment.ts index 63ef6a745a78b..e42c9edac9ae5 100644 --- a/packages/models/src/models/LivechatDepartment.ts +++ b/packages/models/src/models/LivechatDepartment.ts @@ -213,11 +213,11 @@ export class LivechatDepartmentRaw extends BaseRaw implemen return this.updateMany(query, update); } - unarchiveDepartment(_id: string): Promise { + unarchiveDepartment(_id: string): Promise { return this.updateOne({ _id }, { $set: { archived: false } }); } - archiveDepartment(_id: string): Promise { + archiveDepartment(_id: string): Promise { return this.updateOne({ _id }, { $set: { archived: true, enabled: false } }); } From 762626e90cce6051caad907ef70d988256c06e67 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 23 Apr 2025 13:45:47 -0600 Subject: [PATCH 16/17] ts --- apps/meteor/app/livechat/server/lib/hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/lib/hooks.ts b/apps/meteor/app/livechat/server/lib/hooks.ts index f97b1b8ed1369..4543b86f3882f 100644 --- a/apps/meteor/app/livechat/server/lib/hooks.ts +++ b/apps/meteor/app/livechat/server/lib/hooks.ts @@ -1,6 +1,6 @@ import { ILivechatAgentStatus } from '@rocket.chat/core-typings'; import type { AtLeast, ILivechatDepartment, IUser } from '@rocket.chat/core-typings'; -import { LivechatDepartmentAgents, LivechatDepartment } from '@rocket.chat/models'; +import { LivechatDepartmentAgents } from '@rocket.chat/models'; import { setUserStatusLivechat } from './utils'; import { callbacks } from '../../../../lib/callbacks'; From 9d15b0846a66b1c64bc0f820227a00b04ae80bc3 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 23 Apr 2025 14:21:38 -0600 Subject: [PATCH 17/17] room --- packages/models/src/models/LivechatRooms.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/models/src/models/LivechatRooms.ts b/packages/models/src/models/LivechatRooms.ts index 5f262308d2084..51a3a1336591b 100644 --- a/packages/models/src/models/LivechatRooms.ts +++ b/packages/models/src/models/LivechatRooms.ts @@ -82,6 +82,18 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive ]; } + async findOneById(_id: IOmnichannelRoom['_id'], options?: FindOptions): Promise; + + async findOneById

(_id: IOmnichannelRoom['_id'], options?: FindOptions

): Promise

; + + async findOneById(_id: IOmnichannelRoom['_id'], options?: any): Promise { + const query: Filter = { _id, t: 'l' } as Filter; + if (options) { + return this.findOne(query, options); + } + return this.findOne(query); + } + getQueueMetrics({ departmentId, agentId,