diff --git a/apps/meteor/app/livechat/imports/server/rest/departments.ts b/apps/meteor/app/livechat/imports/server/rest/departments.ts index 1be4c54e74971..9a744e5e00a23 100644 --- a/apps/meteor/app/livechat/imports/server/rest/departments.ts +++ b/apps/meteor/app/livechat/imports/server/rest/departments.ts @@ -1,6 +1,10 @@ import type { ILivechatDepartment } from '@rocket.chat/core-typings'; import { LivechatDepartment, LivechatDepartmentAgents } from '@rocket.chat/models'; import { isGETLivechatDepartmentProps, isPOSTLivechatDepartmentProps } from '@rocket.chat/rest-typings'; +import { + isLivechatDepartmentDepartmentIdAgentsGETProps, + isLivechatDepartmentDepartmentIdAgentsPOSTProps, +} from '@rocket.chat/rest-typings/src/v1/omnichannel'; import { Match, check } from 'meteor/check'; import { API } from '../../../../api/server'; @@ -270,12 +274,10 @@ API.v1.addRoute( GET: { permissions: ['view-livechat-departments', 'view-l-room'], operation: 'hasAny' }, POST: { permissions: ['manage-livechat-departments', 'add-livechat-department-agents'], operation: 'hasAny' }, }, + validateParams: { GET: isLivechatDepartmentDepartmentIdAgentsGETProps, POST: isLivechatDepartmentDepartmentIdAgentsPOSTProps }, }, { async get() { - check(this.urlParams, { - _id: String, - }); const { offset, count } = await getPaginationItems(this.queryParams); const { sort } = await this.parseJsonQuery(); @@ -292,13 +294,6 @@ API.v1.addRoute( return API.v1.success(agents); }, async post() { - check( - this.bodyParams, - Match.ObjectIncluding({ - upsert: Array, - remove: Array, - }), - ); await saveDepartmentAgents(this.urlParams._id, this.bodyParams); return API.v1.success(); diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index df43140b28f5b..226359b848a5f 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -1,6 +1,7 @@ import type { ILivechatAgent, ILivechatDepartment, ILivechatTrigger, ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings'; import { License } from '@rocket.chat/license'; import { EmojiCustom, LivechatTrigger, LivechatVisitors, LivechatRooms, LivechatDepartment } from '@rocket.chat/models'; +import { makeFunction } from '@rocket.chat/patch-injection'; import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../../lib/callbacks'; @@ -26,18 +27,15 @@ async function findTriggers(): Promise[]> { - // TODO: check this function usage - return ( - await LivechatDepartment.findEnabledWithAgentsAndBusinessUnit< - Pick - >(businessUnit, { - _id: 1, - name: 1, - showOnRegistration: 1, - showOnOfflineForm: 1, - departmentsAllowedToForward: 1, - }) - ).toArray(); + return LivechatDepartment.findEnabledWithAgentsAndBusinessUnit< + Pick + >(businessUnit, { + _id: 1, + name: 1, + showOnRegistration: 1, + showOnOfflineForm: 1, + departmentsAllowedToForward: 1, + }).toArray(); } export function findGuest(token: string): Promise { @@ -96,12 +94,13 @@ export function normalizeHttpHeaderData(headers: Headers = new Headers()): { } export async function settings({ businessUnit = '' }: { businessUnit?: string } = {}): Promise> { - // Putting this ugly conversion while we type the livechat service - const initSettings = await getInitSettings(); - const triggers = await findTriggers(); - const departments = await findDepartments(businessUnit); + const [initSettings, triggers, departments, emojis] = await Promise.all([ + getInitSettings(), + findTriggers(), + findDepartments(businessUnit), + EmojiCustom.find().toArray(), + ]); const sound = `${Meteor.absoluteUrl()}sounds/chime.mp3`; - const emojis = await EmojiCustom.find().toArray(); return { enabled: initSettings.Livechat_enabled, settings: { @@ -178,11 +177,22 @@ export async function settings({ businessUnit = '' }: { businessUnit?: string } }; } -export async function getExtraConfigInfo(room?: IOmnichannelRoom): Promise { - return callbacks.run('livechat.onLoadConfigApi', { room }); -} - -// TODO: please forgive me for this. Still finding the good types for these callbacks -export function onCheckRoomParams(params: any): Promise { - return callbacks.run('livechat.onCheckRoomApiParams', params); -} +export const getExtraConfigInfo = makeFunction( + async (options: { + room?: IOmnichannelRoom; + }): Promise<{ + queueInfo?: unknown; + customFields?: { + options?: string[] | undefined; + _id: string; + label: string; + regexp: string | undefined; + required: boolean; + type: string | undefined; + defaultValue: string | null; + }[]; + room?: IOmnichannelRoom; + }> => options, +); + +export const onCheckRoomParams = makeFunction((params: any) => params); diff --git a/apps/meteor/app/livechat/server/api/v1/config.ts b/apps/meteor/app/livechat/server/api/v1/config.ts index 55afdfc00e6ad..3c799513cd468 100644 --- a/apps/meteor/app/livechat/server/api/v1/config.ts +++ b/apps/meteor/app/livechat/server/api/v1/config.ts @@ -20,18 +20,20 @@ API.v1.addRoute( } const { token, department, businessUnit } = this.queryParams; - - const config = await cachedSettings({ businessUnit }); - - const status = await online(department); - const guest = token ? await findGuestWithoutActivity(token) : null; + const [config, status, guest] = await Promise.all([ + cachedSettings({ businessUnit }), + online(department), + token ? findGuestWithoutActivity(token) : null, + ]); const room = guest ? await findOpenRoom(guest.token) : undefined; - const agent = guest && room && room.servedBy && (await findAgent(room.servedBy._id)); + const agentPromise = room?.servedBy ? findAgent(room.servedBy._id) : null; + const extraInfoPromise = getExtraConfigInfo({ room }); + + const [agent, extraInfo] = await Promise.all([agentPromise, extraInfoPromise]); - const extra = await getExtraConfigInfo(room); return API.v1.success({ - config: { ...config, online: status, ...extra, ...(guest && { guest }), ...(room && { room }), ...(agent && { agent }) }, + config: { ...config, online: status, ...extraInfo, ...(guest && { guest }), ...(room && { room }), ...(agent && { agent }) }, }); }, }, diff --git a/apps/meteor/app/livechat/server/api/v1/room.ts b/apps/meteor/app/livechat/server/api/v1/room.ts index faec79b0208de..1c592d459b5e2 100644 --- a/apps/meteor/app/livechat/server/api/v1/room.ts +++ b/apps/meteor/app/livechat/server/api/v1/room.ts @@ -44,7 +44,7 @@ API.v1.addRoute( { async get() { // I'll temporary use check for validation, as validateParams doesnt support what's being done here - const extraCheckParams = await onCheckRoomParams({ + const extraCheckParams = onCheckRoomParams({ token: String, rid: Match.Maybe(String), agentId: Match.Maybe(String), diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 6db7408aeef0d..9e195d3b6e999 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -749,7 +749,7 @@ const parseFromIntOrStr = (value: string | number) => { export const updateDepartmentAgents = async ( departmentId: string, agents: { - upsert?: Pick[]; + upsert?: (Pick & { count?: number; order?: number })[]; remove?: Pick[]; }, departmentEnabled: boolean, diff --git a/apps/meteor/app/livechat/server/lib/departmentsLib.ts b/apps/meteor/app/livechat/server/lib/departmentsLib.ts index ab1897fed8439..d0fe436a5231e 100644 --- a/apps/meteor/app/livechat/server/lib/departmentsLib.ts +++ b/apps/meteor/app/livechat/server/lib/departmentsLib.ts @@ -1,6 +1,7 @@ import { AppEvents, Apps } from '@rocket.chat/apps'; import type { LivechatDepartmentDTO, ILivechatDepartment, ILivechatDepartmentAgents, ILivechatAgent } from '@rocket.chat/core-typings'; import { LivechatDepartment, LivechatDepartmentAgents, LivechatVisitors, LivechatRooms, Users } from '@rocket.chat/models'; +import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { updateDepartmentAgents } from './Helper'; @@ -25,7 +26,7 @@ export async function saveDepartment( departmentData: LivechatDepartmentDTO, departmentAgents?: { upsert?: { agentId: string; count?: number; order?: number }[]; - remove?: { agentId: string; count?: number; order?: number }; + remove?: { agentId: string; count?: number; order?: number }[]; }, departmentUnit?: { _id?: string }, ) { @@ -182,30 +183,13 @@ export async function unarchiveDepartment(_id: string) { export async function saveDepartmentAgents( _id: string, departmentAgents: { - upsert?: Pick[]; - remove?: Pick[]; + upsert?: (Pick & { + count?: number; + sort?: number; + })[]; + remove?: Pick[]; }, ) { - check(_id, String); - check(departmentAgents, { - upsert: Match.Maybe([ - Match.ObjectIncluding({ - agentId: String, - username: String, - count: Match.Maybe(Match.Integer), - order: Match.Maybe(Match.Integer), - }), - ]), - remove: Match.Maybe([ - Match.ObjectIncluding({ - agentId: String, - username: Match.Maybe(String), - count: Match.Maybe(Match.Integer), - order: Match.Maybe(Match.Integer), - }), - ]), - }); - const department = await LivechatDepartment.findOneById>(_id, { projection: { enabled: 1 } }); if (!department) { throw new Meteor.Error('error-department-not-found', 'Department not found'); diff --git a/apps/meteor/app/livechat/server/lib/omni-users.ts b/apps/meteor/app/livechat/server/lib/omni-users.ts index 166832d033761..0d8357e426dbf 100644 --- a/apps/meteor/app/livechat/server/lib/omni-users.ts +++ b/apps/meteor/app/livechat/server/lib/omni-users.ts @@ -8,7 +8,6 @@ import { afterAgentAdded, afterRemoveAgent } from './hooks'; import { callbacks } from '../../../../lib/callbacks'; import { addUserRolesAsync } from '../../../../server/lib/roles/addUserRoles'; import { removeUserFromRolesAsync } from '../../../../server/lib/roles/removeUserFromRoles'; -import { hasRoleAsync } from '../../../authorization/server/functions/hasRole'; import { settings } from '../../../settings/server'; export async function notifyAgentStatusChanged(userId: string, status?: UserStatus) { @@ -83,16 +82,6 @@ export async function removeManager(id: IUser['_id']) { } export async function saveAgentInfo(_id: string, agentData: any, agentDepartments: string[]) { - // TODO: check if these 'check' functions are necessary - check(_id, String); - check(agentData, Object); - check(agentDepartments, [String]); - - const user = await Users.findOneById(_id); - if (!user || !(await hasRoleAsync(_id, 'livechat-agent'))) { - throw new Error('error-user-is-not-agent'); - } - await Users.setLivechatData(_id, removeEmpty(agentData)); const currentDepartmentsForAgent = await LivechatDepartmentAgents.findByAgentId(_id).toArray(); diff --git a/apps/meteor/app/livechat/server/methods/saveAgentInfo.ts b/apps/meteor/app/livechat/server/methods/saveAgentInfo.ts index 215cb2b891a45..e48f396d5c300 100644 --- a/apps/meteor/app/livechat/server/methods/saveAgentInfo.ts +++ b/apps/meteor/app/livechat/server/methods/saveAgentInfo.ts @@ -1,5 +1,4 @@ import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Users } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; @@ -15,15 +14,18 @@ declare module '@rocket.chat/ddp-client' { Meteor.methods({ async 'livechat:saveAgentInfo'(_id, agentData, agentDepartments) { - const uid = Meteor.userId(); - if (!uid || !(await hasPermissionAsync(uid, 'manage-livechat-agents'))) { + check(_id, String); + check(agentData, Object); + check(agentDepartments, [String]); + + const user = await Meteor.userAsync(); + if (!user || !(await hasPermissionAsync(user._id, 'manage-livechat-agents'))) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:saveAgentInfo', }); } - const user = await Users.findOneById(_id); - if (!user || !(await hasRoleAsync(_id, 'livechat-agent'))) { + if (!(await hasRoleAsync(_id, 'livechat-agent'))) { throw new Meteor.Error('error-user-is-not-agent', 'User is not a livechat agent', { method: 'livechat:saveAgentInfo', }); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/index.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/index.ts index 8d59c834e2b5b..4dbbb00ee1b97 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/index.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/index.ts @@ -11,8 +11,6 @@ import './beforeNewInquiry'; import './beforeNewRoom'; import './beforeRoutingChat'; import './checkAgentBeforeTakeInquiry'; -import './onCheckRoomParamsApi'; -import './onLoadConfigApi'; import './onCloseLivechat'; import './onSaveVisitorInfo'; import './onBusinessHourStart'; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCheckRoomParamsApi.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCheckRoomParamsApi.ts index b072e0d4fa988..2bf4e92e035e8 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCheckRoomParamsApi.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCheckRoomParamsApi.ts @@ -1,10 +1,5 @@ import { Match } from 'meteor/check'; -import { callbacks } from '../../../../../lib/callbacks'; +import { onCheckRoomParams } from '../../../../../app/livechat/server/api/lib/livechat'; -callbacks.add( - 'livechat.onCheckRoomApiParams', - (params) => ({ ...params, sla: Match.Maybe(String), priority: Match.Maybe(String) }), - callbacks.priority.MEDIUM, - 'livechat-on-check-room-params-api', -); +onCheckRoomParams.patch((_: any, params) => ({ ...params, sla: Match.Maybe(String), priority: Match.Maybe(String) })); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.ts index 01b4600ca954a..3d30fec1eec94 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.ts @@ -1,9 +1,27 @@ -import { callbacks } from '../../../../../lib/callbacks'; +import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; + +import { getExtraConfigInfo } from '../../../../../app/livechat/server/api/lib/livechat'; import { getLivechatQueueInfo, getLivechatCustomFields } from '../lib/Helper'; -callbacks.add( - 'livechat.onLoadConfigApi', - async (options) => { +getExtraConfigInfo.patch( + async ( + _: any, + options: { + room?: IOmnichannelRoom; + }, + ): Promise<{ + queueInfo?: unknown; + customFields?: { + options?: string[] | undefined; + _id: string; + label: string; + regexp: string | undefined; + required: boolean; + type: string | undefined; + defaultValue: string | null; + }[]; + room?: IOmnichannelRoom; + }> => { const { room } = options; const [queueInfo, customFields] = await Promise.all([getLivechatQueueInfo(room), getLivechatCustomFields()]); @@ -13,6 +31,4 @@ callbacks.add( ...options, }; }, - callbacks.priority.MEDIUM, - 'livechat-on-load-config-api', ); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts index 821d42b9db7d0..f90951905a219 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts @@ -1,4 +1,4 @@ -import type { IOmnichannelBusinessUnit, IOmnichannelServiceLevelAgreements } from '@rocket.chat/core-typings'; +import type { IOmnichannelBusinessUnit, IOmnichannelServiceLevelAgreements, IUser, ILivechatTag } from '@rocket.chat/core-typings'; import { Users, OmnichannelServiceLevelAgreements, LivechatTag, LivechatUnitMonitors, LivechatUnit } from '@rocket.chat/models'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -11,9 +11,9 @@ import { removeUserFromRolesAsync } from '../../../../../server/lib/roles/remove export const LivechatEnterprise = { async addMonitor(username: string) { - check(username, String); - - const user = await Users.findOneByUsername(username, { projection: { _id: 1, username: 1 } }); + const user = await Users.findOneByUsername>(username, { + projection: { _id: 1, username: 1, roles: 1 }, + }); if (!user) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { @@ -29,9 +29,9 @@ export const LivechatEnterprise = { }, async removeMonitor(username: string) { - check(username, String); - - const user = await Users.findOneByUsername(username, { projection: { _id: 1 } }); + const user = await Users.findOneByUsername>(username, { + projection: { _id: 1 }, + }); if (!user) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { @@ -39,8 +39,7 @@ export const LivechatEnterprise = { }); } - const removeRoleResult = await removeUserFromRolesAsync(user._id, ['livechat-monitor']); - if (!removeRoleResult) { + if (!(await removeUserFromRolesAsync(user._id, ['livechat-monitor']))) { return false; } @@ -51,15 +50,12 @@ export const LivechatEnterprise = { }, async removeUnit(_id: string) { - check(_id, String); - - const unit = await LivechatUnit.findOneById(_id, { projection: { _id: 1 } }); - - if (!unit) { + const result = await LivechatUnit.removeById(_id); + if (!result.deletedCount) { throw new Meteor.Error('unit-not-found', 'Unit not found', { method: 'livechat:removeUnit' }); } - return LivechatUnit.removeById(_id); + return result; }, async saveUnit( @@ -94,7 +90,9 @@ export const LivechatEnterprise = { let ancestors: string[] = []; if (_id) { - const unit = await LivechatUnit.findOneById(_id); + const unit = await LivechatUnit.findOneById>(_id, { + projection: { _id: 1, ancestors: 1 }, + }); if (!unit) { throw new Meteor.Error('error-unit-not-found', 'Unit not found', { method: 'livechat:saveUnit', @@ -112,16 +110,14 @@ export const LivechatEnterprise = { const monitors = validUserMonitors.map(({ _id: monitorId, username }) => ({ monitorId, - username, - })) as { monitorId: string; username: string }[]; + username: username!, + })); return LivechatUnit.createOrUpdateUnit(_id, unitData, ancestors, monitors, unitDepartments); }, async removeTag(_id: string) { - check(_id, String); - - const tag = await LivechatTag.findOneById(_id, { projection: { _id: 1, name: 1 } }); + const tag = await LivechatTag.findOneById>(_id, { projection: { _id: 1, name: 1 } }); if (!tag) { throw new Meteor.Error('tag-not-found', 'Tag not found', { method: 'livechat:removeTag' }); @@ -132,20 +128,15 @@ export const LivechatEnterprise = { }, async saveTag(_id: string | undefined, tagData: { name: string; description?: string }, tagDepartments: string[]) { - check(_id, Match.Maybe(String)); - - check(tagData, { - name: String, - description: Match.Optional(String), - }); - - check(tagDepartments, [String]); - return LivechatTag.createOrUpdateTag(_id, tagData, tagDepartments); }, async saveSLA(_id: string | null, slaData: Pick) { - const oldSLA = _id && (await OmnichannelServiceLevelAgreements.findOneById(_id, { projection: { dueTimeInMinutes: 1 } })); + const oldSLA = + _id && + (await OmnichannelServiceLevelAgreements.findOneById>(_id, { + projection: { dueTimeInMinutes: 1 }, + })); const exists = await OmnichannelServiceLevelAgreements.findDuplicate(_id, slaData.name, slaData.dueTimeInMinutes); if (exists) { throw new Error('error-duplicated-sla'); @@ -167,14 +158,9 @@ export const LivechatEnterprise = { }, async removeSLA(_id: string) { - const sla = await OmnichannelServiceLevelAgreements.findOneById(_id, { projection: { _id: 1 } }); - if (!sla) { - throw new Error(`SLA with id ${_id} not found`); - } - const removedResult = await OmnichannelServiceLevelAgreements.removeById(_id); if (!removedResult || removedResult.deletedCount !== 1) { - throw new Error(`Error removing SLA with id ${_id}`); + throw new Error(`SLA with id ${_id} not found`); } await removeSLAFromRooms(_id); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/query.helper.js b/apps/meteor/ee/app/livechat-enterprise/server/lib/query.helper.js deleted file mode 100644 index 0daaa9709d504..0000000000000 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/query.helper.js +++ /dev/null @@ -1,19 +0,0 @@ -import { getUnitsFromUser } from './units'; - -// TODO: We need to add a new index in the departmentAncestors field - -export const addQueryRestrictionsToRoomsModel = async (originalQuery = {}) => { - const query = { ...originalQuery }; - - const units = await getUnitsFromUser(); - if (!Array.isArray(units)) { - return query; - } - - const expressions = query.$and || []; - const condition = { - $or: [{ departmentAncestors: { $in: units } }, { departmentId: { $in: units } }], - }; - query.$and = [condition, ...expressions]; - return query; -}; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/methods/addMonitor.ts b/apps/meteor/ee/app/livechat-enterprise/server/methods/addMonitor.ts index 6e6e5a7423d12..4b420da34412b 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/methods/addMonitor.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/methods/addMonitor.ts @@ -1,5 +1,6 @@ import type { IUser } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; +import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; @@ -8,7 +9,7 @@ import { LivechatEnterprise } from '../lib/LivechatEnterprise'; declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { - 'livechat:addMonitor'(username: string): boolean | IUser; + 'livechat:addMonitor'(username: string): boolean | Pick; } } @@ -19,6 +20,7 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:addMonitor' }); } + check(username, String); return LivechatEnterprise.addMonitor(username); }, }); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/methods/removeMonitor.ts b/apps/meteor/ee/app/livechat-enterprise/server/methods/removeMonitor.ts index 12352a47d9cb2..f1d4f808cbcf6 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/methods/removeMonitor.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/methods/removeMonitor.ts @@ -1,4 +1,5 @@ import type { ServerMethods } from '@rocket.chat/ddp-client'; +import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; @@ -20,6 +21,7 @@ Meteor.methods({ }); } + check(username, String); return LivechatEnterprise.removeMonitor(username); }, }); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/methods/removeTag.ts b/apps/meteor/ee/app/livechat-enterprise/server/methods/removeTag.ts index ca9bc93e97eec..775194dc09c7a 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/methods/removeTag.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/methods/removeTag.ts @@ -1,4 +1,5 @@ import type { ServerMethods } from '@rocket.chat/ddp-client'; +import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; @@ -18,6 +19,7 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:removeTag' }); } + check(id, String); return (await LivechatEnterprise.removeTag(id)).deletedCount > 0; }, }); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/methods/removeUnit.ts b/apps/meteor/ee/app/livechat-enterprise/server/methods/removeUnit.ts index c4c193f4b105e..38de032ef4f4b 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/methods/removeUnit.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/methods/removeUnit.ts @@ -1,4 +1,5 @@ import type { ServerMethods } from '@rocket.chat/ddp-client'; +import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; @@ -18,6 +19,7 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:removeUnit' }); } + check(id, String); return (await LivechatEnterprise.removeUnit(id)).deletedCount > 0; }, }); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/methods/saveTag.ts b/apps/meteor/ee/app/livechat-enterprise/server/methods/saveTag.ts index 094c4a97285a4..e83e22c377896 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/methods/saveTag.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/methods/saveTag.ts @@ -1,5 +1,6 @@ import type { ILivechatTag } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; +import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; @@ -8,7 +9,7 @@ import { LivechatEnterprise } from '../lib/LivechatEnterprise'; declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { - 'livechat:saveTag'(id: string, tagData: any, tagDepartments: any): Promise; + 'livechat:saveTag'(id: string, tagData: { name: string; description?: string }, tagDepartments: string[]): Promise; } } @@ -19,6 +20,14 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:saveTags' }); } + check(_id, Match.Maybe(String)); + + check(tagData, { + name: String, + description: Match.Maybe(String), + }); + + check(tagDepartments, [String]); return LivechatEnterprise.saveTag(_id, tagData, tagDepartments); }, }); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/startup.ts b/apps/meteor/ee/app/livechat-enterprise/server/startup.ts index 1a0a5120002d4..984205ca75ee1 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/startup.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/startup.ts @@ -7,7 +7,6 @@ import { logger } from './lib/logger'; import { businessHourManager } from '../../../../app/livechat/server/business-hour'; import { SingleBusinessHourBehavior } from '../../../../app/livechat/server/business-hour/Single'; import { settings } from '../../../../app/settings/server'; -import './lib/query.helper'; const visitorActivityMonitor = new VisitorInactivityMonitor(); const businessHours = { diff --git a/apps/meteor/ee/server/models/raw/LivechatDepartment.ts b/apps/meteor/ee/server/models/raw/LivechatDepartment.ts index 50a17da13b65a..4c5850984f93e 100644 --- a/apps/meteor/ee/server/models/raw/LivechatDepartment.ts +++ b/apps/meteor/ee/server/models/raw/LivechatDepartment.ts @@ -1,6 +1,6 @@ import type { ILivechatDepartment, RocketChatRecordDeleted, LivechatDepartmentDTO } from '@rocket.chat/core-typings'; import type { ILivechatDepartmentModel } from '@rocket.chat/model-typings'; -import { LivechatUnit, LivechatDepartmentRaw } from '@rocket.chat/models'; +import { LivechatDepartmentRaw } from '@rocket.chat/models'; import type { Collection, DeleteResult, @@ -27,9 +27,9 @@ declare module '@rocket.chat/model-typings' { unfilteredRemove(query: Filter): Promise; removeParentAndAncestorById(id: string): Promise; findEnabledWithAgentsAndBusinessUnit( - businessUnit: string, - projection: FindOptions['projection'], - ): Promise>; + businessUnit?: string, + projection?: FindOptions['projection'], + ): FindCursor; findByParentId(parentId: string, options?: FindOptions): FindCursor; findAgentsByBusinessHourId(businessHourId: string): AggregationCursor<{ agentIds: string[] }>; } @@ -72,19 +72,28 @@ export class LivechatDepartmentEE extends LivechatDepartmentRaw implements ILive return this.updateMany({ parentId: id }, { $unset: { parentId: 1 }, $pull: { ancestors: id } }); } - async findEnabledWithAgentsAndBusinessUnit( - businessUnit: string, - projection: FindOptions['projection'], - ): Promise> { + findActiveByUnitIds(unitIds: string[], options: FindOptions = {}): FindCursor { + const query = { + enabled: true, + numAgents: { $gt: 0 }, + parentId: { + $exists: true, + $in: unitIds, + }, + }; + + return this.find(query, options); + } + + findEnabledWithAgentsAndBusinessUnit( + businessUnit?: string, + projection?: FindOptions['projection'], + ): FindCursor { if (!businessUnit) { return super.findEnabledWithAgents(projection); } - const unit = await LivechatUnit.findOneById(businessUnit, { projection: { _id: 1 } }); - if (!unit) { - throw new Meteor.Error('error-unit-not-found', `Error! No Active Business Unit found with id: ${businessUnit}`); - } - return super.findActiveByUnitIds([businessUnit], { projection }); + return this.findActiveByUnitIds([businessUnit], { projection }); } findByParentId(parentId: string, options?: FindOptions): FindCursor { diff --git a/apps/meteor/ee/server/models/raw/LivechatTag.ts b/apps/meteor/ee/server/models/raw/LivechatTag.ts index aec7f605b270b..f1997e95b713a 100644 --- a/apps/meteor/ee/server/models/raw/LivechatTag.ts +++ b/apps/meteor/ee/server/models/raw/LivechatTag.ts @@ -19,12 +19,6 @@ export class LivechatTagRaw extends BaseRaw implements ILivechatTa ]; } - findOneById(_id: string, options?: FindOptions): Promise { - const query = { _id }; - - return this.findOne(query, options); - } - findInIds(ids: string[], options?: FindOptions): FindCursor { const query = { _id: { $in: ids } }; diff --git a/apps/meteor/lib/callbacks.ts b/apps/meteor/lib/callbacks.ts index 7a6420da40dc2..c917d37c2f4bf 100644 --- a/apps/meteor/lib/callbacks.ts +++ b/apps/meteor/lib/callbacks.ts @@ -131,10 +131,6 @@ type ChainedCallbackSignatures = { props: { user: Required>; oldRoom: IOmnichannelRoom }, ) => IOmnichannelRoom; - 'livechat.onCheckRoomApiParams': (params: Record) => Record; - - 'livechat.onLoadConfigApi': (config: { room: IOmnichannelRoom }) => Record; - 'afterCreateUser': (user: AtLeast) => IUser; 'afterDeleteRoom': (rid: IRoom['_id']) => IRoom['_id']; 'livechat:afterOnHold': (room: Pick) => Pick; @@ -241,8 +237,6 @@ export type Hook = | 'livechat.sendTranscript' | 'livechat.closeRoom' | 'livechat.offlineMessage' - | 'livechat.onCheckRoomApiParams' - | 'livechat.onLoadConfigApi' | 'livechat.manageDepartmentUnit' | 'loginPageStateChange' | 'mapLDAPUserData' diff --git a/apps/meteor/server/services/omnichannel-analytics/AgentData.ts b/apps/meteor/server/services/omnichannel-analytics/AgentData.ts index e92f7c7b67165..49717f8e7b783 100644 --- a/apps/meteor/server/services/omnichannel-analytics/AgentData.ts +++ b/apps/meteor/server/services/omnichannel-analytics/AgentData.ts @@ -2,6 +2,7 @@ import type { ConversationData } from '@rocket.chat/core-services'; import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import type { ILivechatRoomsModel } from '@rocket.chat/model-typings'; +import type moment from 'moment'; import type { Filter } from 'mongodb'; import { secondsToHHMMSS } from '../../../lib/utils/secondsToHHMMSS'; diff --git a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts index 52ae177471c6a..3b4a191cd5120 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts @@ -850,7 +850,12 @@ import { IS_EE } from '../../../e2e/config/constants'; it('should throw an error if the user doesnt have the permission to manage the departments', async () => { await updatePermission('manage-livechat-departments', []); await updatePermission('add-livechat-department-agents', []); - await request.post(api('livechat/department/test/agents')).set(credentials).expect('Content-Type', 'application/json').expect(403); + await request + .post(api('livechat/department/test/agents')) + .set(credentials) + .send({ upsert: [], remove: [] }) + .expect('Content-Type', 'application/json') + .expect(403); }); it('should throw an error if the departmentId is not valid', async () => { @@ -877,7 +882,7 @@ import { IS_EE } from '../../../e2e/config/constants'; .set(credentials) .expect(400); expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', "Match error: Missing key 'upsert'"); + expect(res.body).to.have.property('error', "must have required property 'upsert' [invalid-params]"); await deleteDepartment(dep._id); }); @@ -891,7 +896,7 @@ import { IS_EE } from '../../../e2e/config/constants'; .send({ upsert: [{}], remove: [] }) .expect(400); expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', "Match error: Missing key 'agentId' in field upsert[0]"); + expect(res.body).to.have.property('error', "must have required property 'agentId' [invalid-params]"); await deleteDepartment(dep._id); }); diff --git a/packages/model-typings/src/models/ILivechatDepartmentModel.ts b/packages/model-typings/src/models/ILivechatDepartmentModel.ts index 720af8e4517b0..fe0ca6c650483 100644 --- a/packages/model-typings/src/models/ILivechatDepartmentModel.ts +++ b/packages/model-typings/src/models/ILivechatDepartmentModel.ts @@ -44,7 +44,7 @@ export interface ILivechatDepartmentModel extends IBaseModel( _: any, projection: FindOptions['projection'], - ): Promise>; + ): FindCursor; findOneByIdOrName(_idOrName: string, options?: FindOptions): Promise; findByUnitIds(unitIds: string[], options?: FindOptions): FindCursor; countDepartmentsInUnit(unitId: string): Promise; diff --git a/packages/model-typings/src/models/ILivechatTagModel.ts b/packages/model-typings/src/models/ILivechatTagModel.ts index 270e9ba99e095..f32e4f3fd79b8 100644 --- a/packages/model-typings/src/models/ILivechatTagModel.ts +++ b/packages/model-typings/src/models/ILivechatTagModel.ts @@ -4,7 +4,6 @@ import type { FindOptions, DeleteResult, FindCursor } from 'mongodb'; import type { IBaseModel } from './IBaseModel'; export interface ILivechatTagModel extends IBaseModel { - findOneById(_id: string, options?: FindOptions): Promise; findInIds(ids: string[], options?: FindOptions): FindCursor; createOrUpdateTag( _id: string | undefined, diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index a018b94854746..37495f68bdc53 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -304,8 +304,6 @@ export interface IUsersModel extends IBaseModel { setLivechatStatus(userId: string, status: ILivechatAgentStatus): Promise; makeAgentUnavailableAndUnsetExtension(userId: string): Promise; setLivechatData(userId: string, data?: Record): Promise; - closeOffice(): Promise; - openOffice(): Promise; getAgentInfo( agentId: IUser['_id'], showAgentEmail?: boolean, diff --git a/packages/models/src/models/LivechatDepartment.ts b/packages/models/src/models/LivechatDepartment.ts index e42c9edac9ae5..02500815961bc 100644 --- a/packages/models/src/models/LivechatDepartment.ts +++ b/packages/models/src/models/LivechatDepartment.ts @@ -304,15 +304,11 @@ export class LivechatDepartmentRaw extends BaseRaw implemen return this.findOne(query, projection && { projection }); } - async findEnabledWithAgentsAndBusinessUnit( + findEnabledWithAgentsAndBusinessUnit( _: any, - projection: FindOptions['projection'] = {}, - ): Promise> { - const query = { - numAgents: { $gt: 0 }, - enabled: true, - }; - return this.find(query, projection && { projection }); + projection?: FindOptions['projection'], + ): FindCursor { + return this.findEnabledWithAgents(projection); } findOneByIdOrName(_idOrName: string, options: FindOptions = {}): Promise { @@ -345,17 +341,8 @@ export class LivechatDepartmentRaw extends BaseRaw implemen return this.countDocuments({ parentId: unitId }); } - findActiveByUnitIds(unitIds: string[], options: FindOptions = {}): FindCursor { - const query = { - enabled: true, - numAgents: { $gt: 0 }, - parentId: { - $exists: true, - $in: unitIds, - }, - }; - - return this.find(query, options); + findActiveByUnitIds(_unitIds: string[], _options: FindOptions = {}): FindCursor { + throw new Error('not-implemented'); } findNotArchived(options: FindOptions = {}): FindCursor { diff --git a/packages/models/src/models/Users.ts b/packages/models/src/models/Users.ts index 19eaa1f892930..0a65f2473033d 100644 --- a/packages/models/src/models/Users.ts +++ b/packages/models/src/models/Users.ts @@ -25,7 +25,6 @@ import type { UpdateOptions, FindCursor, SortDirection, - UpdateResult, FindOneAndUpdateOptions, } from 'mongodb'; @@ -2059,27 +2058,6 @@ export class UsersRaw extends BaseRaw> implements IU return this.updateOne(query, update); } - // TODO: why this needs to be one by one instead of an updateMany? - async closeOffice() { - // TODO: Create class Agent - const promises: Promise>[] = []; - // TODO: limit the data returned by findAgents - await this.findAgents().forEach((agent) => { - promises.push(this.setLivechatStatus(agent._id, ILivechatAgentStatus.NOT_AVAILABLE)); - }); - await Promise.all(promises); - } - - // Same todo's as the above - async openOffice() { - // TODO: Create class Agent - const promises: Promise>[] = []; - await this.findAgents().forEach((agent) => { - promises.push(this.setLivechatStatus(agent._id, ILivechatAgentStatus.AVAILABLE)); - }); - await Promise.all(promises); - } - getAgentInfo( agentId: IUser['_id'], showAgentEmail = false, diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 51e5934864c0f..0d7302b5fc0a6 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -141,18 +141,22 @@ const LivechatDepartmentAutocompleteSchema = { export const isLivechatDepartmentAutocompleteProps = ajv.compile(LivechatDepartmentAutocompleteSchema); -type LivechatDepartmentDepartmentIdAgentsGET = { - sort: string; -}; +type LivechatDepartmentDepartmentIdAgentsGET = PaginatedRequest; const LivechatDepartmentDepartmentIdAgentsGETSchema = { type: 'object', properties: { + count: { + type: 'number', + }, + offset: { + type: 'number', + }, sort: { type: 'string', }, }, - required: ['sort'], + required: [], additionalProperties: false, }; @@ -161,8 +165,8 @@ export const isLivechatDepartmentDepartmentIdAgentsGETProps = ajv.compile