diff --git a/apps/meteor/app/api/server/helpers/addUserToFileObj.ts b/apps/meteor/app/api/server/helpers/addUserToFileObj.ts index 4c46192b53224..1a9471e6d550d 100644 --- a/apps/meteor/app/api/server/helpers/addUserToFileObj.ts +++ b/apps/meteor/app/api/server/helpers/addUserToFileObj.ts @@ -1,8 +1,10 @@ import type { IUpload, IUser } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; +const isString = (value: unknown): value is string => typeof value === 'string'; + export async function addUserToFileObj(files: IUpload[]): Promise<(IUpload & { user?: Pick })[]> { - const uids = files.map(({ userId }) => userId).filter(Boolean); + const uids = files.map(({ userId }) => userId).filter(isString); const users = await Users.findByIds(uids, { projection: { name: 1, username: 1 } }).toArray(); diff --git a/apps/meteor/app/api/server/lib/users.ts b/apps/meteor/app/api/server/lib/users.ts index aa99d92166678..c95338d8a4ae3 100644 --- a/apps/meteor/app/api/server/lib/users.ts +++ b/apps/meteor/app/api/server/lib/users.ts @@ -2,7 +2,7 @@ import type { IUser } from '@rocket.chat/core-typings'; import { Users, Subscriptions } from '@rocket.chat/models'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import type { Mongo } from 'meteor/mongo'; -import type { Filter, RootFilterOperators } from 'mongodb'; +import type { Filter, FindOptions, RootFilterOperators } from 'mongodb'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { settings } from '../../../settings/server'; @@ -21,7 +21,7 @@ export async function findUsersToAutocomplete({ const searchFields = settings.get('Accounts_SearchFields').trim().split(','); const exceptions = selector.exceptions || []; const conditions = selector.conditions || {}; - const options = { + const options: FindOptions & { limit: number } = { projection: { name: 1, username: 1, diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts index 24d0bbac7d033..7824ac0c2070e 100644 --- a/apps/meteor/app/api/server/v1/channels.ts +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -1,5 +1,5 @@ import { Team, Room } from '@rocket.chat/core-services'; -import { TEAM_TYPE, type IRoom, type ISubscription, type IUser, type RoomType } from '@rocket.chat/core-typings'; +import { TEAM_TYPE, type IRoom, type ISubscription, type IUser, type RoomType, type UserStatus } from '@rocket.chat/core-typings'; import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models'; import { isChannelsAddAllProps, @@ -1013,7 +1013,7 @@ API.v1.addRoute( }, ]; - const { cursor, totalCount } = await Rooms.findPaginated(ourQuery, { + const { cursor, totalCount } = Rooms.findPaginated(ourQuery, { sort: sort || { name: 1 }, skip: offset, limit: count, @@ -1052,7 +1052,7 @@ API.v1.addRoute( }); } - const { cursor, totalCount } = await Rooms.findPaginatedByTypeAndIds('c', rids, { + const { cursor, totalCount } = Rooms.findPaginatedByTypeAndIds('c', rids, { sort: sort || { name: 1 }, skip: offset, limit: count, @@ -1103,7 +1103,7 @@ API.v1.addRoute( const { cursor, totalCount } = await findUsersOfRoom({ rid: findResult._id, - ...(status && { status: { $in: status } }), + ...(status && { status: { $in: status as UserStatus[] } }), skip, limit, filter, diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index b77c498875f1e..38d1b6a2ddaa4 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -1,5 +1,5 @@ import { Team, isMeteorError } from '@rocket.chat/core-services'; -import type { IIntegration, IUser, IRoom, RoomType } from '@rocket.chat/core-typings'; +import type { IIntegration, IUser, IRoom, RoomType, UserStatus } from '@rocket.chat/core-typings'; import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models'; import { isGroupsOnlineProps, isGroupsMessagesProps } from '@rocket.chat/rest-typings'; import { check, Match } from 'meteor/check'; @@ -740,7 +740,7 @@ API.v1.addRoute( const { cursor, totalCount } = await findUsersOfRoom({ rid: findResult.rid, - ...(status && { status: { $in: status } }), + ...(status && { status: { $in: status as UserStatus[] } }), skip, limit, filter, diff --git a/apps/meteor/app/api/server/v1/im.ts b/apps/meteor/app/api/server/v1/im.ts index f345bad4118c2..5f196263e1dfe 100644 --- a/apps/meteor/app/api/server/v1/im.ts +++ b/apps/meteor/app/api/server/v1/im.ts @@ -1,7 +1,7 @@ /** * Docs: https://github.com/RocketChat/developer-docs/blob/master/reference/api/rest-api/endpoints/team-collaboration-endpoints/im-endpoints */ -import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings'; +import type { IMessage, IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; import { Subscriptions, Uploads, Messages, Rooms, Users } from '@rocket.chat/models'; import { isDmDeleteProps, @@ -13,6 +13,7 @@ import { } from '@rocket.chat/rest-typings'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import type { FindOptions } from 'mongodb'; import { eraseRoom } from '../../../../server/lib/eraseRoom'; import { openRoom } from '../../../../server/lib/openRoom'; @@ -338,7 +339,7 @@ API.v1.addRoute( room._id, ); - const options = { + const options: FindOptions = { projection: { _id: 1, username: 1, diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index d3c05a5589e22..c645535ea40cd 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -233,9 +233,7 @@ API.v1.addRoute( }); } - let user = await (async (): Promise< - Pick | undefined | null - > => { + let user = await (async (): Promise | undefined | null> => { if (isUserFromParams(this.bodyParams, this.userId, this.user)) { return Users.findOneById(this.userId); } @@ -269,9 +267,11 @@ API.v1.addRoute( const sentTheUserByFormData = fields.userId || fields.username; if (sentTheUserByFormData) { if (fields.userId) { - user = await Users.findOneById(fields.userId, { projection: { username: 1 } }); + user = await Users.findOneById>(fields.userId, { projection: { username: 1 } }); } else if (fields.username) { - user = await Users.findOneByUsernameIgnoringCase(fields.username, { projection: { username: 1 } }); + user = await Users.findOneByUsernameIgnoringCase>(fields.username, { + projection: { username: 1 }, + }); } if (!user) { diff --git a/apps/meteor/app/api/server/v1/voip/rooms.ts b/apps/meteor/app/api/server/v1/voip/rooms.ts index 2e4c7cea7c1b0..13d2e2c31a054 100644 --- a/apps/meteor/app/api/server/v1/voip/rooms.ts +++ b/apps/meteor/app/api/server/v1/voip/rooms.ts @@ -1,5 +1,5 @@ import { LivechatVoip } from '@rocket.chat/core-services'; -import type { ILivechatAgent, IVoipRoom } from '@rocket.chat/core-typings'; +import type { IVoipRoom } from '@rocket.chat/core-typings'; import { VoipRoom, LivechatVisitors, Users } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; import { isVoipRoomProps, isVoipRoomsProps, isVoipRoomCloseProps } from '@rocket.chat/rest-typings'; @@ -128,7 +128,7 @@ API.v1.addRoute( return API.v1.failure('agent-not-found'); } - const agentObj: ILivechatAgent = await Users.findOneAgentById(agentId, { + const agentObj = await Users.findOneAgentById(agentId, { projection: { username: 1 }, }); if (!agentObj?.username) { diff --git a/apps/meteor/app/apps/server/bridges/activation.ts b/apps/meteor/app/apps/server/bridges/activation.ts index dc5a0f57e0035..399dfd285e65e 100644 --- a/apps/meteor/app/apps/server/bridges/activation.ts +++ b/apps/meteor/app/apps/server/bridges/activation.ts @@ -1,6 +1,7 @@ import type { IAppServerOrchestrator, AppStatus } from '@rocket.chat/apps'; import type { ProxiedApp } from '@rocket.chat/apps-engine/server/ProxiedApp'; import { AppActivationBridge as ActivationBridge } from '@rocket.chat/apps-engine/server/bridges/AppActivationBridge'; +import { UserStatus } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; export class AppActivationBridge extends ActivationBridge { @@ -29,7 +30,7 @@ export class AppActivationBridge extends ActivationBridge { } protected async appStatusChanged(app: ProxiedApp, status: AppStatus): Promise { - const userStatus = ['auto_enabled', 'manually_enabled'].includes(status) ? 'online' : 'offline'; + const userStatus = ['auto_enabled', 'manually_enabled'].includes(status) ? UserStatus.ONLINE : UserStatus.OFFLINE; await Users.updateStatusByAppId(app.getID(), userStatus); diff --git a/apps/meteor/app/e2e/server/methods/setUserPublicAndPrivateKeys.ts b/apps/meteor/app/e2e/server/methods/setUserPublicAndPrivateKeys.ts index 45a00886af1e1..2477d1547ca25 100644 --- a/apps/meteor/app/e2e/server/methods/setUserPublicAndPrivateKeys.ts +++ b/apps/meteor/app/e2e/server/methods/setUserPublicAndPrivateKeys.ts @@ -11,6 +11,10 @@ declare module '@rocket.chat/ddp-client' { } } +const isKeysResult = (result: any): result is { public_key: string; private_key: string } => { + return result.private_key && result.public_key; +}; + Meteor.methods({ async 'e2e.setUserPublicAndPrivateKeys'(keyPair) { const userId = Meteor.userId(); @@ -24,7 +28,7 @@ Meteor.methods({ if (!keyPair.force) { const keys = await Users.fetchKeysByUserId(userId); - if (keys.private_key && keys.public_key) { + if (isKeysResult(keys)) { throw new Meteor.Error('error-keys-already-set', 'Keys already set', { method: 'e2e.setUserPublicAndPrivateKeys', }); diff --git a/apps/meteor/app/importer/server/classes/converters/ConverterCache.ts b/apps/meteor/app/importer/server/classes/converters/ConverterCache.ts index 284e51dddcd57..3dec0ab78d624 100644 --- a/apps/meteor/app/importer/server/classes/converters/ConverterCache.ts +++ b/apps/meteor/app/importer/server/classes/converters/ConverterCache.ts @@ -215,7 +215,9 @@ export class ConverterCache { } const user = await Users.findOneByUsername>(username, { projection: { _id: 1 } }); - this.addUsernameToId(username, user?._id); + if (user) { + this.addUsernameToId(username, user._id); + } return user?._id; } diff --git a/apps/meteor/app/importer/server/classes/converters/UserConverter.ts b/apps/meteor/app/importer/server/classes/converters/UserConverter.ts index 1b411100e13c1..88ea742de7bc7 100644 --- a/apps/meteor/app/importer/server/classes/converters/UserConverter.ts +++ b/apps/meteor/app/importer/server/classes/converters/UserConverter.ts @@ -127,7 +127,7 @@ export class UserConverter extends RecordConverter { + async findExistingUser(data: IImportUser): Promise { if (data.emails.length) { const emailUser = await Users.findOneByEmailAddress(data.emails[0], {}); @@ -138,7 +138,7 @@ export class UserConverter extends RecordConverter(data.username, {}); } } @@ -221,7 +221,7 @@ export class UserConverter extends RecordConverter { + async insertOrUpdateUser(existingUser: IUser | null | undefined, data: IImportUser): Promise { if (!data.username && !existingUser?.username) { const emails = data.emails.filter(Boolean).map((email) => ({ address: email })); data.username = await generateUsernameSuggestion({ diff --git a/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts b/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts index 0f83cfe1c0880..6e0eb970b9bc5 100644 --- a/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts +++ b/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts @@ -1,4 +1,5 @@ import type { IUser } from '@rocket.chat/core-typings'; +import { isNotUndefined } from '@rocket.chat/core-typings'; import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; import type { ClientSession } from 'mongodb'; @@ -13,8 +14,8 @@ async function getUsersWhoAreInTheSameGroupDMsAs(user: IUser) { return; } - const userIds = new Set(); - const users = new Map(); + const userIds = new Set(); + const users = new Map(); const rooms = Rooms.findGroupDMsByUids([user._id], { projection: { uids: 1 } }); await rooms.forEach((room) => { @@ -25,9 +26,7 @@ async function getUsersWhoAreInTheSameGroupDMsAs(user: IUser) { room.uids.forEach((uid) => uid !== user._id && userIds.add(uid)); }); - (await Users.findByIds([...userIds], { projection: { username: 1, name: 1 } }).toArray()).forEach((user: IUser) => - users.set(user._id, user), - ); + (await Users.findByIds([...userIds], { projection: { username: 1, name: 1 } }).toArray()).forEach((user) => users.set(user._id, user)); return users; } @@ -59,7 +58,7 @@ export const updateGroupDMsName = async ( const rooms = Rooms.findGroupDMsByUids([userThatChangedName._id], { projection: { uids: 1 }, session }); // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - const getMembers = (uids: string[]) => uids.map((uid) => users.get(uid)).filter(Boolean); + const getMembers = (uids: string[]) => uids.map((uid) => users.get(uid)).filter(isNotUndefined); // loop rooms to update the subscriptions from them all for await (const room of rooms) { diff --git a/apps/meteor/app/livechat/server/api/lib/users.ts b/apps/meteor/app/livechat/server/api/lib/users.ts index 49ac5682a6c04..62663603768fe 100644 --- a/apps/meteor/app/livechat/server/api/lib/users.ts +++ b/apps/meteor/app/livechat/server/api/lib/users.ts @@ -51,7 +51,7 @@ async function findUsers({ sortedResults, totalCount: [{ total } = { total: 0 }], }, - ] = await Users.findAgentsWithDepartments(role, query, { + ] = await Users.findAgentsWithDepartments(role, query, { sort: sort || { name: 1 }, skip: offset, limit: count, diff --git a/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts b/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts index 940954dfa2a79..1a61cca494935 100644 --- a/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts +++ b/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts @@ -1,4 +1,5 @@ import type { AtLeast, ILivechatAgentStatus, ILivechatBusinessHour, ILivechatDepartment } from '@rocket.chat/core-typings'; +import { UserStatus } from '@rocket.chat/core-typings'; import type { ILivechatBusinessHoursModel, IUsersModel } from '@rocket.chat/model-typings'; import { LivechatBusinessHours, Users } from '@rocket.chat/models'; import type { IWorkHoursCronJobsWrapper } from '@rocket.chat/models'; @@ -55,7 +56,7 @@ export abstract class AbstractBusinessHourBehavior { status, // Why this works: statusDefault is the property set when a user manually changes their status // So if it's set to offline, we can be sure the user will be offline after login and we can skip the update - { livechatStatusSystemModified: true, statusDefault: { $ne: 'offline' } }, + { livechatStatusSystemModified: true, statusDefault: { $ne: UserStatus.OFFLINE } }, { livechatStatusSystemModified: true }, ); diff --git a/apps/meteor/app/livechat/server/business-hour/Helper.ts b/apps/meteor/app/livechat/server/business-hour/Helper.ts index d21b51ce0184d..e5b16865d8f91 100644 --- a/apps/meteor/app/livechat/server/business-hour/Helper.ts +++ b/apps/meteor/app/livechat/server/business-hour/Helper.ts @@ -49,7 +49,7 @@ export const createDefaultBusinessHourIfNotExists = async (): Promise => { } }; -export async function makeAgentsUnavailableBasedOnBusinessHour(agentIds: string[] | null = null) { +export async function makeAgentsUnavailableBasedOnBusinessHour(agentIds?: string[]) { const results = await Users.findAgentsAvailableWithoutBusinessHours(agentIds).toArray(); const update = await Users.updateLivechatStatusByAgentIds( @@ -75,7 +75,7 @@ export async function makeAgentsUnavailableBasedOnBusinessHour(agentIds: string[ ); } -export async function makeOnlineAgentsAvailable(agentIds: string[] | null = null) { +export async function makeOnlineAgentsAvailable(agentIds?: string[]) { const results = await Users.findOnlineButNotAvailableAgents(agentIds).toArray(); const update = await Users.updateLivechatStatusByAgentIds( diff --git a/apps/meteor/app/livechat/server/lib/contacts/getContacts.ts b/apps/meteor/app/livechat/server/lib/contacts/getContacts.ts index 09b7a2545a1cc..ad9a4cbadf174 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/getContacts.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/getContacts.ts @@ -1,4 +1,5 @@ import type { IUser } from '@rocket.chat/core-typings'; +import { isNotUndefined } from '@rocket.chat/core-typings'; import { LivechatContacts, Users } from '@rocket.chat/models'; import type { PaginatedResult, ILivechatContactWithManagerData } from '@rocket.chat/rest-typings'; import type { FindCursor, Sort } from 'mongodb'; @@ -25,7 +26,7 @@ export async function getContacts(params: GetContactsParams): Promise contactManager))]; + const managerIds = [...new Set(rawContacts.map(({ contactManager }) => contactManager))].filter(isNotUndefined); const managersCursor: FindCursor<[string, Pick]> = Users.findByIds(managerIds, { projection: { name: 1, username: 1 }, }).map((manager) => [manager._id, manager]); diff --git a/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts b/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts index 8c53bf7c555d0..e394366cba7ad 100644 --- a/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts +++ b/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts @@ -1,6 +1,6 @@ -import type { IMessage } from '@rocket.chat/core-typings'; +import type { IMessage, IUser } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; -export async function isMessageFromBot(message: IMessage): Promise { +export async function isMessageFromBot(message: IMessage): Promise | null> { return Users.isUserInRole(message.u._id, 'bot'); } diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/routing/LoadRotation.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/routing/LoadRotation.ts index b520a1b2a2a35..7c915c65b8812 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/routing/LoadRotation.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/routing/LoadRotation.ts @@ -33,7 +33,7 @@ class LoadRotation { ignoreAgentId, settings.get('Livechat_enabled_when_agent_idle'), ); - if (!nextAgent) { + if (!nextAgent?.username) { return; } diff --git a/apps/meteor/ee/server/lib/ldap/Manager.ts b/apps/meteor/ee/server/lib/ldap/Manager.ts index 61e0ba990082b..f74e5e43ab415 100644 --- a/apps/meteor/ee/server/lib/ldap/Manager.ts +++ b/apps/meteor/ee/server/lib/ldap/Manager.ts @@ -291,6 +291,10 @@ export class LDAPEEManager extends LDAPManager { const roomOwner = settings.get('LDAP_Sync_User_Data_Channels_Admin') || ''; const user = await Users.findOneByUsernameIgnoringCase(roomOwner); + if (!user) { + logger.error(`Unable to find user '${roomOwner}' to be the owner of the channel '${channel}'.`); + return; + } const room = await createRoom('c', channel, user, [], false, false, { customFields: { ldap: true }, diff --git a/apps/meteor/imports/personal-access-tokens/server/api/methods/regenerateToken.ts b/apps/meteor/imports/personal-access-tokens/server/api/methods/regenerateToken.ts index 0ee4cf018f38e..281ed55dc013c 100644 --- a/apps/meteor/imports/personal-access-tokens/server/api/methods/regenerateToken.ts +++ b/apps/meteor/imports/personal-access-tokens/server/api/methods/regenerateToken.ts @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; +import { isPersonalAccessToken } from '@rocket.chat/core-typings'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; import { twoFactorRequired } from '../../../../../app/2fa/server/twoFactorRequired'; @@ -33,8 +34,14 @@ export const regeneratePersonalAccessTokenOfUser = async (tokenName: string, use await removePersonalAccessTokenOfUser(tokenName, userId); - return generatePersonalAccessTokenOfUser({ tokenName, userId, bypassTwoFactor: tokenExist.bypassTwoFactor || false }); -} + const tokenObject = tokenExist.services?.resume?.loginTokens?.find((token) => isPersonalAccessToken(token) && token.name === tokenName); + + return generatePersonalAccessTokenOfUser({ + tokenName, + userId, + bypassTwoFactor: (tokenObject && isPersonalAccessToken(tokenObject) && tokenObject.bypassTwoFactor) || false, + }); +}; Meteor.methods({ 'personalAccessTokens:regenerateToken': twoFactorRequired(async function ({ tokenName }) { @@ -44,7 +51,7 @@ Meteor.methods({ method: 'personalAccessTokens:regenerateToken', }); } - + return regeneratePersonalAccessTokenOfUser(tokenName, uid); }), }); diff --git a/apps/meteor/server/lib/findUsersOfRoom.ts b/apps/meteor/server/lib/findUsersOfRoom.ts index 6441d5265d114..25c0a34f0198a 100644 --- a/apps/meteor/server/lib/findUsersOfRoom.ts +++ b/apps/meteor/server/lib/findUsersOfRoom.ts @@ -1,13 +1,13 @@ import type { IUser } from '@rocket.chat/core-typings'; import type { FindPaginated } from '@rocket.chat/model-typings'; import { Users } from '@rocket.chat/models'; -import type { FilterOperators, FindCursor } from 'mongodb'; +import type { FindCursor, FindOptions, Filter } from 'mongodb'; import { settings } from '../../app/settings/server'; type FindUsersParam = { rid: string; - status?: FilterOperators; + status?: Filter['status']; skip?: number; limit?: number; filter?: string; @@ -15,7 +15,7 @@ type FindUsersParam = { }; export function findUsersOfRoom({ rid, status, skip = 0, limit = 0, filter = '', sort }: FindUsersParam): FindPaginated> { - const options = { + const options: FindOptions = { projection: { name: 1, username: 1, diff --git a/apps/meteor/server/lib/ldap/Manager.ts b/apps/meteor/server/lib/ldap/Manager.ts index a0f474cfe5d89..4f34acc61de1c 100644 --- a/apps/meteor/server/lib/ldap/Manager.ts +++ b/apps/meteor/server/lib/ldap/Manager.ts @@ -140,12 +140,12 @@ export class LDAPManager { } // This method will only find existing users that are already linked to LDAP - protected static async findExistingLDAPUser(ldapUser: ILDAPEntry): Promise { + protected static async findExistingLDAPUser(ldapUser: ILDAPEntry): Promise { const uniqueIdentifierField = this.getLdapUserUniqueID(ldapUser); if (uniqueIdentifierField) { logger.debug({ msg: 'Querying user', uniqueId: uniqueIdentifierField.value }); - return UsersRaw.findOneByLDAPId(uniqueIdentifierField.value, uniqueIdentifierField.attribute); + return UsersRaw.findOneByLDAPId(uniqueIdentifierField.value, uniqueIdentifierField.attribute); } } @@ -341,7 +341,7 @@ export class LDAPManager { ldapUser: ILDAPEntry, existingUser?: IUser, usedUsername?: string | undefined, - ): Promise { + ): Promise { logger.debug({ msg: 'Syncing user data', ldapUser: omit(ldapUser, '_raw'), @@ -501,14 +501,14 @@ export class LDAPManager { } // This method will find existing users by LDAP id or by username. - private static async findExistingUser(ldapUser: ILDAPEntry, slugifiedUsername: string): Promise { + private static async findExistingUser(ldapUser: ILDAPEntry, slugifiedUsername: string): Promise { const user = await this.findExistingLDAPUser(ldapUser); if (user) { return user; } // If we don't have that ldap user linked yet, check if there's any non-ldap user with the same username - return UsersRaw.findOneWithoutLDAPByUsernameIgnoringCase(slugifiedUsername); + return UsersRaw.findOneWithoutLDAPByUsernameIgnoringCase(slugifiedUsername); } private static fallbackToDefaultLogin(username: LoginUsername, password: string): LDAPLoginResult { diff --git a/apps/meteor/server/lib/ldap/UserConverter.ts b/apps/meteor/server/lib/ldap/UserConverter.ts index 1d94db88db3c7..4f76c61cefe62 100644 --- a/apps/meteor/server/lib/ldap/UserConverter.ts +++ b/apps/meteor/server/lib/ldap/UserConverter.ts @@ -20,7 +20,7 @@ export class LDAPUserConverter extends UserConverter { this.mergeExistingUsers = settings.get('LDAP_Merge_Existing_Users') ?? true; } - async findExistingUser(data: IImportUser): Promise { + async findExistingUser(data: IImportUser): Promise { if (data.services?.ldap?.id) { const importedUser = await Users.findOneByLDAPId(data.services.ldap.id, data.services.ldap.idAttribute); if (importedUser) { @@ -41,7 +41,7 @@ export class LDAPUserConverter extends UserConverter { } if (data.username) { - return Users.findOneWithoutLDAPByUsernameIgnoringCase(data.username); + return Users.findOneWithoutLDAPByUsernameIgnoringCase(data.username); } } diff --git a/apps/meteor/server/methods/browseChannels.ts b/apps/meteor/server/methods/browseChannels.ts index 5956fa4eaa8c3..bba6c6a173c22 100644 --- a/apps/meteor/server/methods/browseChannels.ts +++ b/apps/meteor/server/methods/browseChannels.ts @@ -6,6 +6,7 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; import mem from 'mem'; import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; import { Meteor } from 'meteor/meteor'; +import type { FindOptions, SortDirection } from 'mongodb'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; import { federationSearchUsers } from '../../app/federation/server/handler'; @@ -32,7 +33,7 @@ const sortChannels = (field: string, direction: 'asc' | 'desc'): Record { +const sortUsers = (field: string, direction: 'asc' | 'desc'): Record => { switch (field) { case 'email': return { @@ -183,7 +184,7 @@ const findUsers = async ({ viewFullOtherUserInfo, }: { text: string; - sort: Record; + sort: Record; pagination: { skip: number; limit: number; @@ -194,7 +195,7 @@ const findUsers = async ({ const searchFields = workspace === 'all' ? ['username', 'name', 'emails.address'] : settings.get('Accounts_SearchFields').trim().split(','); - const options = { + const options: FindOptions = { ...pagination, sort, projection: { @@ -264,7 +265,7 @@ const getUsers = async ( user: IUser | undefined, text: string, workspace: string, - sort: Record, + sort: Record, pagination: { skip: number; limit: number; @@ -289,6 +290,7 @@ const getUsers = async ( // Add the federated user to the results results.unshift({ + _id: user._id, username: user.username, name: user.name, bio: user.bio, diff --git a/apps/meteor/server/methods/getUsersOfRoom.ts b/apps/meteor/server/methods/getUsersOfRoom.ts index d3d69ce937542..915cfad4fe01f 100644 --- a/apps/meteor/server/methods/getUsersOfRoom.ts +++ b/apps/meteor/server/methods/getUsersOfRoom.ts @@ -1,4 +1,5 @@ import type { IRoom, IUser } from '@rocket.chat/core-typings'; +import { UserStatus } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Subscriptions, Rooms } from '@rocket.chat/models'; import { check } from 'meteor/check'; @@ -54,7 +55,7 @@ Meteor.methods({ const { cursor } = findUsersOfRoom({ rid, - status: !showAll ? { $ne: 'offline' } : undefined, + status: !showAll ? { $ne: UserStatus.OFFLINE } : undefined, limit, skip, filter, diff --git a/apps/meteor/server/methods/removeUserFromRoom.ts b/apps/meteor/server/methods/removeUserFromRoom.ts index 8d1cb2e2ba2be..28e900667204d 100644 --- a/apps/meteor/server/methods/removeUserFromRoom.ts +++ b/apps/meteor/server/methods/removeUserFromRoom.ts @@ -55,6 +55,11 @@ export const removeUserFromRoomMethod = async (fromId: string, data: { rid: stri } const removedUser = await Users.findOneByUsernameIgnoringCase(data.username); + if (!removedUser) { + throw new Meteor.Error('error-user-not-in-room', 'User is not in this room', { + method: 'removeUserFromRoom', + }); + } await Room.beforeUserRemoved(room); diff --git a/apps/meteor/server/services/authorization/service.ts b/apps/meteor/server/services/authorization/service.ts index d970068e23ba4..af9801c1da08e 100644 --- a/apps/meteor/server/services/authorization/service.ts +++ b/apps/meteor/server/services/authorization/service.ts @@ -126,7 +126,7 @@ export class Authorization extends ServiceClass implements IAuthorization { private getUserFromRoles = mem( async (roleIds: string[]) => { - const options = { + const users = await Users.findUsersInRoles(roleIds, null, { sort: { username: 1, }, @@ -134,9 +134,7 @@ export class Authorization extends ServiceClass implements IAuthorization { username: 1, roles: 1, }, - }; - - const users = await Users.findUsersInRoles(roleIds, null, options).toArray(); + }).toArray(); return users.map((user) => ({ ...user, diff --git a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/User.ts b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/User.ts index 73bbdcadc77bf..14f938c91e4c3 100644 --- a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/User.ts +++ b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/User.ts @@ -93,8 +93,8 @@ export class RocketChatUserAdapter { return user; } - public async getInternalUserByUsername(username: string): Promise { - return Users.findOneByUsername(username); + public async getInternalUserByUsername(username: string): Promise { + return Users.findOneByUsername(username); } public async createFederatedUser(federatedUser: FederatedUser): Promise { diff --git a/apps/meteor/server/services/omnichannel-voip/service.ts b/apps/meteor/server/services/omnichannel-voip/service.ts index 9c30202045cf6..d6d199ca47b38 100644 --- a/apps/meteor/server/services/omnichannel-voip/service.ts +++ b/apps/meteor/server/services/omnichannel-voip/service.ts @@ -15,7 +15,7 @@ import { isILivechatVisitor, OmnichannelSourceType, isVoipRoom, VoipClientEvents import { Logger } from '@rocket.chat/logger'; import { Users, VoipRoom, PbxEvents } from '@rocket.chat/models'; import type { PaginatedResult } from '@rocket.chat/rest-typings'; -import type { FindOptions } from 'mongodb'; +import type { FindOptions, SortDirection } from 'mongodb'; import _ from 'underscore'; import type { IOmniRoomClosingMessage } from './internalTypes'; @@ -183,7 +183,7 @@ export class OmnichannelVoipService extends ServiceClassInternal implements IOmn private async getAllocatedExtesionAllocationData(projection: Partial<{ [P in keyof IUser]: number }>): Promise { const roles: string[] = ['livechat-agent', 'livechat-manager', 'admin']; - const options = { + const options: FindOptions = { sort: { username: 1, }, @@ -458,9 +458,9 @@ export class OmnichannelVoipService extends ServiceClassInternal implements IOmn text?: string, count?: number, offset?: number, - sort?: Record, + sort?: Record, ): Promise<{ agents: ILivechatAgent[]; total: number }> { - const { cursor, totalCount } = Users.getAvailableAgentsIncludingExt(includeExtension, text, { count, skip: offset, sort }); + const { cursor, totalCount } = Users.getAvailableAgentsIncludingExt(includeExtension, text, { limit: count, skip: offset, sort }); const [agents, total] = await Promise.all([cursor.toArray(), totalCount]); diff --git a/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts b/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts index 8f4d0e44b5544..20288a4427ca3 100644 --- a/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts +++ b/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts @@ -17,6 +17,7 @@ import type { ILivechatVisitor, ILivechatAgent, IOmnichannelSystemMessage, + AtLeast, } from '@rocket.chat/core-typings'; import { isQuoteAttachment, isFileAttachment, isFileImageAttachment } from '@rocket.chat/core-typings'; import type { Logger } from '@rocket.chat/logger'; @@ -64,7 +65,7 @@ export type MessageData = Pick< type WorkerData = { siteName: string; visitor: Pick | null; - agent: ILivechatAgent | undefined; + agent: ILivechatAgent | undefined | null; closedAt?: Date; messages: MessageData[]; timezone: string; @@ -101,7 +102,7 @@ export class OmnichannelTranscript extends ServiceClass implements IOmnichannelT this.log = new loggerClass('OmnichannelTranscript'); } - async getTimezone(user?: { utcOffset?: string | number }): Promise { + async getTimezone(user?: AtLeast | null): Promise { const reportingTimezone = await settingsService.get('Default_Timezone_For_Reporting'); switch (reportingTimezone) { diff --git a/packages/core-typings/src/IUser.ts b/packages/core-typings/src/IUser.ts index c1ba49b556e13..510d6d4ceed22 100644 --- a/packages/core-typings/src/IUser.ts +++ b/packages/core-typings/src/IUser.ts @@ -21,6 +21,9 @@ export interface IPersonalAccessToken extends ILoginToken { bypassTwoFactor?: boolean; } +export const isPersonalAccessToken = (token: LoginToken): token is IPersonalAccessToken => + 'type' in token && token.type === 'personalAccessToken'; + export interface IUserEmailVerificationToken { token: string; address: string; @@ -218,6 +221,7 @@ export interface IUser extends IRocketChatRecord { requirePasswordChangeReason?: string; roomRolePriorities?: Record; isOAuthUser?: boolean; // client only field + __rooms?: string[]; } export interface IRegisterUser extends IUser { diff --git a/packages/core-typings/src/utils.ts b/packages/core-typings/src/utils.ts index 2e20ebc48c842..3e8f4869ce3d2 100644 --- a/packages/core-typings/src/utils.ts +++ b/packages/core-typings/src/utils.ts @@ -48,3 +48,5 @@ export type DeepPartial = { ? DeepPartial : T[P]; }; + +export const isNotUndefined = (value: T | undefined): value is T => value !== undefined; diff --git a/packages/model-typings/src/models/IBaseModel.ts b/packages/model-typings/src/models/IBaseModel.ts index c3a541a1d1a75..e293dfa52d750 100644 --- a/packages/model-typings/src/models/IBaseModel.ts +++ b/packages/model-typings/src/models/IBaseModel.ts @@ -25,7 +25,7 @@ import type { import type { Updater } from '../updater'; -export type DefaultFields = Record | Record | void; +export type DefaultFields = Partial> | Partial> | void; export type ResultFields = Defaults extends void ? Base : Defaults[keyof Defaults] extends 1 diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index 75f4cdf67e8bf..73bcbb5c02812 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -1,13 +1,13 @@ import type { IUser, IRole, - IRoom, ILivechatAgent, UserStatus, ILoginToken, IPersonalAccessToken, AtLeast, ILivechatAgentStatus, + IMeteorLoginToken, } from '@rocket.chat/core-typings'; import type { Document, @@ -19,131 +19,136 @@ import type { DeleteResult, WithId, UpdateOptions, - ClientSession, + UpdateFilter, } from 'mongodb'; import type { FindPaginated, IBaseModel } from './IBaseModel'; export interface IUsersModel extends IBaseModel { addRolesByUserId(uid: IUser['_id'], roles: IRole['_id'][]): Promise; - findUsersInRoles(roles: IRole['_id'][], scope?: null, options?: any): FindCursor; - findPaginatedUsersInRoles(roles: IRole['_id'][], options?: any): FindPaginated>; + findUsersInRoles(roles: IRole['_id'][] | IRole['_id'], _scope?: null, options?: FindOptions): FindCursor; + findPaginatedUsersInRoles(roles: IRole['_id'][] | IRole['_id'], options?: FindOptions): FindPaginated>; findOneByIdWithEmailAddress(uid: IUser['_id'], options?: FindOptions): Promise; - findOneByUsername(username: string, options?: any): Promise; - findOneAgentById(_id: string, options: any): Promise; - findUsersInRolesWithQuery(roles: IRole['_id'] | IRole['_id'][], query: any, options: any): FindCursor; - findPaginatedUsersInRolesWithQuery( - roles: IRole['_id'] | IRole['_id'][], - query: any, - options: any, - ): FindPaginated>; - findOneByUsernameAndRoomIgnoringCase(username: string, rid: IRoom['_id'], options: any): FindCursor; - findOneByIdAndLoginHashedToken(_id: string, token: any, options?: any): FindCursor; - findByActiveUsersExcept( - searchTerm: any, - exceptions: any, - options: any, - searchFields: any, - extraQuery?: any, - params?: { startsWith?: boolean; endsWith?: boolean }, - ): FindCursor; - findPaginatedByActiveUsersExcept( - searchTerm: any, - exceptions: any, - options: any, - searchFields: any, - extraQuery?: any, - params?: { startsWith?: boolean; endsWith?: boolean }, - ): FindPaginated>; - - findPaginatedByActiveLocalUsersExcept( - searchTerm: any, - exceptions: any, - options: any, - forcedSearchFields: any, - localDomain: any, - ): FindPaginated>; + findOneByUsername(username: string, options?: FindOptions): Promise; + findOneAgentById(_id: IUser['_id'], options?: FindOptions): Promise; + findUsersInRolesWithQuery(roles: IRole['_id'][] | IRole['_id'], query: Filter, options?: FindOptions): FindCursor; + findPaginatedUsersInRolesWithQuery( + roles: IRole['_id'][] | IRole['_id'], + query: Filter, + options?: FindOptions, + ): FindPaginated>>; + findOneByUsernameAndRoomIgnoringCase(username: string | RegExp, rid: string, options?: FindOptions): Promise; + findOneByIdAndLoginHashedToken(_id: IUser['_id'], token: string, options?: FindOptions): Promise; + findByActiveUsersExcept( + searchTerm: string, + exceptions: string[], + options?: FindOptions, + searchFields?: string[], + extraQuery?: Filter[], + extra?: { startsWith: boolean; endsWith: boolean }, + ): FindCursor; + findPaginatedByActiveUsersExcept( + searchTerm: string, + exceptions?: string[], + options?: FindOptions, + searchFields?: string[], + extraQuery?: Filter[], + extra?: { startsWith?: boolean; endsWith?: boolean }, + ): FindPaginated>>; + + findPaginatedByActiveLocalUsersExcept( + searchTerm: string, + exceptions?: string[], + options?: FindOptions, + forcedSearchFields?: string[], + localDomain?: string, + ): FindPaginated>>; - findPaginatedByActiveExternalUsersExcept( - searchTerm: any, - exceptions: any, - options: any, - forcedSearchFields: any, - localDomain: any, - ): FindPaginated>; + findPaginatedByActiveExternalUsersExcept( + searchTerm: string, + exceptions?: string[], + options?: FindOptions, + forcedSearchFields?: string[], + localDomain?: string, + ): FindPaginated>>; - findActive(options?: any): FindCursor; + findActive(query: Filter, options?: FindOptions): FindCursor; - findActiveByIds(userIds: any, options?: any): FindCursor; + findActiveByIds(userIds: IUser['_id'][], options?: FindOptions): FindCursor; - findByIds(userIds: any, options?: any): FindCursor; + findByIds(userIds: IUser['_id'][], options?: FindOptions): FindCursor; - findOneByUsernameIgnoringCase(username: any, options?: any): Promise; + findOneByUsernameIgnoringCase(username: IUser['username'], options?: FindOptions): Promise; - findOneWithoutLDAPByUsernameIgnoringCase(username: string, options?: any): Promise; + findOneWithoutLDAPByUsernameIgnoringCase(username: string, options?: FindOptions): Promise; - findOneByLDAPId(id: any, attribute?: any): Promise; + findOneByLDAPId(id: string, attribute?: string): Promise; - findOneByAppId(appId: string, options?: FindOptions): Promise; + findOneByAppId(appId: string, options?: FindOptions): Promise; - findLDAPUsers(options?: any): FindCursor; + findLDAPUsers(options?: FindOptions): FindCursor; - findLDAPUsersExceptIds(userIds: IUser['_id'][], options?: FindOptions): FindCursor; + findLDAPUsersExceptIds(userIds: IUser['_id'][], options?: FindOptions): FindCursor; - findConnectedLDAPUsers(options?: any): FindCursor; + findConnectedLDAPUsers(options?: FindOptions): FindCursor; - isUserInRole(userId: IUser['_id'], roleId: IRole['_id']): Promise; + isUserInRole(userId: IUser['_id'], roleId: IRole['_id']): Promise | null>; - getDistinctFederationDomains(): any; + getDistinctFederationDomains(): Promise; getNextLeastBusyAgent( - department: any, - ignoreAgentId: any, + department?: string, + ignoreAgentId?: string, isEnabledWhenAgentIdle?: boolean, - ): Promise<{ agentId: string; username: string; lastRoutingTime: Date; departments: any[]; count: number }>; + ): Promise<{ agentId: string; username?: string; lastRoutingTime?: Date; count: number; departments?: any[] }>; getLastAvailableAgentRouted( - department: any, - ignoreAgentId: any, + department?: string, + ignoreAgentId?: string, isEnabledWhenAgentIdle?: boolean, - ): Promise<{ agentId: string; username: string; lastRoutingTime: Date; departments: any[] }>; + ): Promise<{ agentId: string; username?: string; lastRoutingTime?: Date; departments?: any[] }>; - setLastRoutingTime(userId: any): Promise; + setLastRoutingTime(userId: IUser['_id']): Promise | null>; - setLivechatStatusIf(userId: string, status: ILivechatAgentStatus, conditions?: any, extraFields?: any): Promise; + setLivechatStatusIf( + userId: IUser['_id'], + status: ILivechatAgentStatus, + conditions?: Filter, + extraFields?: UpdateFilter['$set'], + ): Promise; getAgentAndAmountOngoingChats( - userId: any, - ): Promise<{ agentId: string; username: string; lastAssignTime: Date; lastRoutingTime: Date; queueInfo: { chats: number } }>; + userId: IUser['_id'], + ): Promise<{ agentId: string; username?: string; lastAssignTime?: Date; lastRoutingTime?: Date; queueInfo: { chats: number } }>; - findAllResumeTokensByUserId(userId: any): any; + findAllResumeTokensByUserId(userId: IUser['_id']): Promise<{ tokens: IMeteorLoginToken[] }[]>; - findActiveByUsernameOrNameRegexWithExceptionsAndConditions( - termRegex: any, - exceptions: any, - conditions: any, - options: any, + findActiveByUsernameOrNameRegexWithExceptionsAndConditions( + termRegex: { $regex: string; $options: string } | RegExp, + exceptions?: string[], + conditions?: Filter, + options?: FindOptions, ): FindCursor; - countAllAgentsStatus({ departmentId }: { departmentId?: any }): any; + countAllAgentsStatus({ + departmentId, + }: { + departmentId?: string; + }): Promise<{ offline: number; away: number; busy: number; available: number }[]>; - getTotalOfRegisteredUsersByDate({ start, end, options }: { start: any; end: any; options?: any }): Promise; - // TODO change back to below when convert model to TS - // Promise< - // { - // date: string; - // users: number; - // type: 'users'; - // }[] - // >; + getTotalOfRegisteredUsersByDate(params: { + start: Date; + end: Date; + options?: { count?: number; sort?: Record }; + }): Promise<{ date: string; users: number; type: 'users' }[]>; - getUserLanguages(): any; + getUserLanguages(): Promise<{ _id: string; total: number }[]>; - updateStatusText(_id: any, statusText: any, options?: { session?: ClientSession }): any; + updateStatusText(_id: IUser['_id'], statusText: string, options?: UpdateOptions): Promise; - updateStatusByAppId(appId: any, status: any): any; + updateStatusByAppId(appId: string, status: UserStatus): Promise; - openAgentsBusinessHoursByBusinessHourId(businessHourIds: any): any; + openAgentsBusinessHoursByBusinessHourId(businessHourIds: string[]): Promise; - openAgentBusinessHoursByBusinessHourIdsAndAgentId(businessHourIds: string[], agentId: string): Promise; + openAgentBusinessHoursByBusinessHourIdsAndAgentId(businessHourIds: string[], agentId: IUser['_id']): Promise; addBusinessHourByAgentIds(agentIds: string[], businessHourId: string): any; @@ -189,7 +194,11 @@ export interface IUsersModel extends IBaseModel { unsetExtension(userId: any): any; - getAvailableAgentsIncludingExt(includeExt: any, text: any, options: any): FindPaginated>; + getAvailableAgentsIncludingExt( + includeExt?: string, + text?: string, + options?: FindOptions, + ): FindPaginated>>; findActiveUsersTOTPEnable(options: any): any; @@ -199,7 +208,7 @@ export interface IUsersModel extends IBaseModel { countActiveUsersEmail2faEnable(options: any): Promise; - findActiveByIdsOrUsernames(userIds: string[], options?: any): FindCursor; + findActiveByIdsOrUsernames(userIds: IUser['_id'][], options?: FindOptions): FindCursor; setAsFederated(userId: string): any; @@ -208,26 +217,29 @@ export interface IUsersModel extends IBaseModel { findOneByResetToken(token: string, options: FindOptions): Promise; updateStatusById( - userId: string, + userId: IUser['_id'], { statusDefault, status, statusConnection, statusText, - }: { statusDefault?: string; status: UserStatus; statusConnection: UserStatus; statusText?: string }, + }: { statusDefault?: UserStatus; status: UserStatus; statusConnection: UserStatus; statusText?: string }, ): Promise; - setFederationAvatarUrlById(userId: string, federationAvatarUrl: string): Promise; + setFederationAvatarUrlById(userId: IUser['_id'], federationAvatarUrl: string): Promise; - findSearchedServerNamesByUserId(userId: string): Promise; + findSearchedServerNamesByUserId(userId: IUser['_id']): Promise; addServerNameToSearchedServerNamesList(userId: string, serverName: string): Promise; removeServerNameFromSearchedServerNamesList(userId: string, serverName: string): Promise; countFederatedExternalUsers(): Promise; - findOnlineUserFromList(userList: string[], isLivechatEnabledWhenAgentIdle?: boolean): FindCursor; - countOnlineUserFromList(userList: string[], isLivechatEnabledWhenAgentIdle?: boolean): Promise; + findOnlineUserFromList( + userList: string | string[], + isLivechatEnabledWhenAgentIdle?: boolean, + ): FindCursor; + countOnlineUserFromList(userList: string | string[], isLivechatEnabledWhenAgentIdle?: boolean): Promise; getUnavailableAgents( departmentId?: string, extraQuery?: Document, @@ -247,28 +259,29 @@ export interface IUsersModel extends IBaseModel { isLivechatEnabledWhenAgentIdle?: boolean, ): Promise; - findBotAgents(usernameList?: string[]): FindCursor; - countBotAgents(usernameList?: string[]): Promise; + findBotAgents(usernameList?: string | string[]): FindCursor; + countBotAgents(usernameList?: string | string[]): Promise; removeAllRoomsByUserId(userId: string): Promise; removeRoomByUserId(userId: string, rid: string): Promise; addRoomByUserId(userId: string, rid: string): Promise; - addRoomByUserIds(uids: string[], rid: string): Promise; + addRoomByUserIds(uids: string[], rid: string): Promise; removeRoomByRoomIds(rids: string[]): Promise; addRoomRolePriorityByUserId(userId: string, rid: string, rolePriority: number): Promise; removeRoomRolePriorityByUserId(userId: string, rid: string): Promise; assignRoomRolePrioritiesByUserIdPriorityMap(rolePrioritiesMap: Record, rid: string): Promise; - unassignRoomRolePrioritiesByRoomId(rid: string): Promise; + unassignRoomRolePrioritiesByRoomId(rid: string): Promise; getLoginTokensByUserId(userId: string): FindCursor; addPersonalAccessTokenToUser(data: { userId: string; loginTokenObject: IPersonalAccessToken }): Promise; removePersonalAccessTokenOfUser(data: { userId: string; loginTokenObject: AtLeast; }): Promise; - findPersonalAccessTokenByTokenNameAndUserId(data: { userId: string; tokenName: string }): Promise; + findPersonalAccessTokenByTokenNameAndUserId({ userId, tokenName }: { userId: IUser['_id']; tokenName: string }): Promise; setOperator(userId: string, operator: boolean): Promise; checkOnlineAgents(agentId?: string, isLivechatEnabledWhenIdle?: boolean): Promise; - findOnlineAgents(agentId?: string, isLivechatEnabledWhenIdle?: boolean): FindCursor; - findOneBotAgent(): Promise; + findOnlineAgents(agentId?: IUser['_id'], isLivechatEnabledWhenIdle?: boolean): FindCursor; + countOnlineAgents(agentId: string): Promise; + findOneBotAgent(): Promise; findOneOnlineAgentById( agentId: string, isLivechatEnabledWhenAgentIdle?: boolean, @@ -280,21 +293,21 @@ export interface IUsersModel extends IBaseModel { ignoreAgentId?: string, extraQuery?: Filter, enabledWhenAgentIdle?: boolean, - ): Promise<{ agentId: string; username: string } | null>; - getNextBotAgent(ignoreAgentId?: string): Promise<{ agentId: string; username: string } | null>; + ): Promise<{ agentId: string; username?: string } | null>; + getNextBotAgent(ignoreAgentId?: string): Promise<{ agentId: string; username?: string } | null>; setLivechatStatus(userId: string, status: ILivechatAgentStatus): Promise; makeAgentUnavailableAndUnsetExtension(userId: string): Promise; setLivechatData(userId: string, data?: Record): Promise; closeOffice(): Promise; openOffice(): Promise; getAgentInfo( - agentId: string, + agentId: IUser['_id'], showAgentEmail?: boolean, - ): Promise | null>; + ): Promise | null>; roleBaseQuery(userId: string): { _id: string }; setE2EPublicAndPrivateKeysByUserId(userId: string, e2e: { public_key: string; private_key: string }): Promise; rocketMailUnsubscribe(userId: string, createdAt: string): Promise; - fetchKeysByUserId(userId: string): Promise<{ public_key: string; private_key: string } | Record>; + fetchKeysByUserId(userId: string): Promise<{ public_key: string; private_key: string } | object>; disable2FAAndSetTempSecretByUserId(userId: string, tempSecret: string): Promise; enable2FAAndSetSecretAndCodesByUserId(userId: string, secret: string, codes: string[]): Promise; disable2FAByUserId(userId: string): Promise; @@ -304,8 +317,6 @@ export interface IUsersModel extends IBaseModel { findByIdsWithPublicE2EKey(userIds: string[], options?: FindOptions): FindCursor; resetE2EKey(userId: string): Promise; removeExpiredEmailCodeOfUserId(userId: string): Promise; - removeEmailCodeByUserId(userId: string): Promise; - increaseInvalidEmailCodeAttempt(userId: string): Promise; maxInvalidEmailCodeAttemptsReached(userId: string, maxAttemtps: number): Promise; addEmailCodeByUserId(userId: string, code: string, expire: Date): Promise; findActiveUsersInRoles(roles: string[], options?: FindOptions): FindCursor; @@ -328,12 +339,12 @@ export interface IUsersModel extends IBaseModel { findOneByIdAndLoginToken(userId: string, loginToken: string, options?: FindOptions): Promise; findOneActiveById(userId: string, options?: FindOptions): Promise; findOneByIdOrUsername(userId: string, options?: FindOptions): Promise; - findOneByRolesAndType(roles: string[], type: string, options?: FindOptions): Promise; + findOneByRolesAndType(roles: IRole['_id'][], type: string, options?: FindOptions): Promise; findNotOfflineByIds(userIds: string[], options?: FindOptions): FindCursor; findUsersNotOffline(options?: FindOptions): FindCursor; countUsersNotOffline(options?: FindOptions): Promise; findNotIdUpdatedFrom(userId: string, updatedFrom: Date, options?: FindOptions): FindCursor; - findByRoomId(roomId: string, options?: FindOptions): FindCursor; + findByRoomId(roomId: string, options?: FindOptions): Promise>; findByUsername(username: string, options?: FindOptions): FindCursor; findByUsernames(usernames: string[], options?: FindOptions): FindCursor; findByUsernamesIgnoringCase(usernames: string[], options?: FindOptions): FindCursor; @@ -344,28 +355,27 @@ export interface IUsersModel extends IBaseModel { findByUsernameNameOrEmailAddress(nameOrUsernameOrEmail: string, options?: FindOptions): FindCursor; findCrowdUsers(options?: FindOptions): FindCursor; getLastLogin(options?: FindOptions): Promise; - findUsersByUsernames(usernames: string[], options?: FindOptions): FindCursor; + findUsersByUsernames(usernames: string[], options?: FindOptions): FindCursor; findUsersByIds(userIds: string[], options?: FindOptions): FindCursor; findUsersWithUsernameByIds(userIds: string[], options?: FindOptions): FindCursor; findUsersWithUsernameByIdsNotOffline(userIds: string[], options?: FindOptions): FindCursor; getOldest(options?: FindOptions): Promise; - findActiveRemoteUsers(options?: FindOptions): FindCursor; findActiveFederated(options?: FindOptions): FindCursor; getSAMLByIdAndSAMLProvider(userId: string, samlProvider: string): Promise; - findBySAMLNameIdOrIdpSession(samlNameId: string, idpSession: string): FindCursor; - findBySAMLInResponseTo(inResponseTo: string): FindCursor; + findBySAMLNameIdOrIdpSession(samlNameId: string, idpSession: string, options?: FindOptions): FindCursor; + findBySAMLInResponseTo(inResponseTo: string, options?: FindOptions): FindCursor; addImportIds(userId: string, importIds: string | string[]): Promise; updateInviteToken(userId: string, token: string): Promise; updateLastLoginById(userId: string): Promise; addPasswordToHistory(userId: string, password: string, passwordHistoryAmount: number): Promise; setServiceId(userId: string, serviceName: string, serviceId: string): Promise; - setUsername(userId: string, username: string, options?: { session?: ClientSession }): Promise; - setEmail(userId: string, email: string, verified?: boolean, options?: { session?: ClientSession }): Promise; + setUsername(userId: string, username: string, options?: UpdateOptions): Promise; + setEmail(userId: string, email: string, verified?: boolean, options?: UpdateOptions): Promise; setEmailVerified(userId: string, email: string): Promise; - setName(userId: string, name: string, options?: { session?: ClientSession }): Promise; - unsetName(userId: string, options?: { session?: ClientSession }): Promise; + setName(userId: string, name: string, options?: UpdateOptions): Promise; + unsetName(userId: string, options?: UpdateOptions): Promise; setCustomFields(userId: string, customFields: Record): Promise; - setAvatarData(userId: string, origin: string, etag?: Date | null | string, options?: { session?: ClientSession }): Promise; + setAvatarData(userId: string, origin: string, etag?: Date | null | string, options?: UpdateOptions): Promise; unsetAvatarData(userId: string): Promise; setUserActive(userId: string, active: boolean): Promise; setAllUsersActive(active: boolean): Promise; @@ -384,7 +394,6 @@ export interface IUsersModel extends IBaseModel { setPreferences(userId: string, preferences: Record): Promise; setTwoFactorAuthorizationHashAndUntilForUserIdAndToken(userId: string, token: string, hash: string, until: Date): Promise; setUtcOffset(userId: string, utcOffset: number): Promise; - saveUserById(userId: string, user: Partial): Promise; setReason(userId: string, reason: string): Promise; unsetReason(userId: string): Promise; bannerExistsById(userId: string, bannerId: string): Promise; @@ -405,24 +414,27 @@ export interface IUsersModel extends IBaseModel { updateCustomFieldsById(userId: string, customFields: Record): Promise; countRoomMembers(roomId: string): Promise; countRemote(options?: FindOptions): Promise; - findOneByImportId(importId: string, options?: FindOptions): Promise; + findOneByImportId(_id: IUser['_id'], options?: FindOptions): Promise; removeAgent(_id: string): Promise; - findAgentsWithDepartments( - role: string, + findAgentsWithDepartments( + role: IRole['_id'][] | IRole['_id'], query: Filter, - options: FindOptions, + options?: FindOptions, ): Promise<{ sortedResults: (T & { departments: string[] })[]; totalCount: { total: number }[] }[]>; countByRole(roleName: string): Promise; removeEmailCodeOfUserId(userId: string): Promise; incrementInvalidEmailCodeAttempt(userId: string): Promise | null>; - findOnlineButNotAvailableAgents(userIds: string[] | null): FindCursor>; - findAgentsAvailableWithoutBusinessHours(userIds: string[] | null): FindCursor>; - updateLivechatStatusByAgentIds(userIds: string[], status: ILivechatAgentStatus): Promise; - findOneByFreeSwitchExtension(extension: string, options?: FindOptions): Promise; - findOneByFreeSwitchExtensions(extensions: string[], options?: FindOptions): Promise; + findOnlineButNotAvailableAgents(userIds?: IUser['_id'][]): FindCursor; + findAgentsAvailableWithoutBusinessHours(userIds?: IUser['_id'][]): FindCursor>; + updateLivechatStatusByAgentIds(userIds: string[], status: ILivechatAgentStatus): Promise; + findOneByFreeSwitchExtension(freeSwitchExtension: string, options?: FindOptions): Promise; + findOneByFreeSwitchExtensions( + freeSwitchExtensions: string[], + options?: FindOptions, + ): Promise; setFreeSwitchExtension(userId: string, extension: string | undefined): Promise; - findAssignedFreeSwitchExtensions(): FindCursor; - findUsersWithAssignedFreeSwitchExtensions(options?: FindOptions): FindCursor; + findAssignedFreeSwitchExtensions(): FindCursor; + findUsersWithAssignedFreeSwitchExtensions(options?: FindOptions): FindCursor; countUsersInRoles(roles: IRole['_id'][]): Promise; countAllUsersWithPendingAvatar(): Promise; } diff --git a/packages/models/src/models/BaseRaw.ts b/packages/models/src/models/BaseRaw.ts index 047c889ed1611..0c28c17913e3a 100644 --- a/packages/models/src/models/BaseRaw.ts +++ b/packages/models/src/models/BaseRaw.ts @@ -53,7 +53,7 @@ export abstract class BaseRaw< TDeleted extends RocketChatRecordDeleted = RocketChatRecordDeleted, > implements IBaseModel { - public readonly defaultFields: C | undefined; + protected defaultFields: C | undefined; public readonly col: Collection; diff --git a/packages/models/src/models/Users.js b/packages/models/src/models/Users.ts similarity index 62% rename from packages/models/src/models/Users.js rename to packages/models/src/models/Users.ts index 108963273ee7f..9f9cb54886e32 100644 --- a/packages/models/src/models/Users.js +++ b/packages/models/src/models/Users.ts @@ -1,10 +1,37 @@ -import { ILivechatAgentStatus } from '@rocket.chat/core-typings'; -import { Subscriptions } from '@rocket.chat/models'; +import type { + AtLeast, + DeepWritable, + ILivechatAgent, + ILoginToken, + IMeteorLoginToken, + IPersonalAccessToken, + IRole, + IRoom, + IUser, + RocketChatRecordDeleted, +} from '@rocket.chat/core-typings'; +import { ILivechatAgentStatus, UserStatus } from '@rocket.chat/core-typings'; +import type { DefaultFields, InsertionModel, IUsersModel } from '@rocket.chat/model-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; - +import type { + Collection, + Db, + Filter, + FindOptions, + IndexDescription, + Document, + UpdateFilter, + UpdateOptions, + FindCursor, + SortDirection, + UpdateResult, + FindOneAndUpdateOptions, +} from 'mongodb'; + +import { Subscriptions } from '../index'; import { BaseRaw } from './BaseRaw'; -const queryStatusAgentOnline = (extraFilters = {}, isLivechatEnabledWhenAgentIdle) => ({ +const queryStatusAgentOnline = (extraFilters = {}, isLivechatEnabledWhenAgentIdle?: boolean): Filter => ({ statusLivechat: 'available', roles: 'livechat-agent', // ignore deactivated users @@ -14,7 +41,7 @@ const queryStatusAgentOnline = (extraFilters = {}, isLivechatEnabledWhenAgentIdl { status: { $exists: true, - $ne: 'offline', + $ne: UserStatus.OFFLINE, }, roles: { $ne: 'bot', @@ -31,8 +58,8 @@ const queryStatusAgentOnline = (extraFilters = {}, isLivechatEnabledWhenAgentIdl }), }); -export class UsersRaw extends BaseRaw { - constructor(db, trash) { +export class UsersRaw extends BaseRaw> implements IUsersModel { + constructor(db: Db, trash?: Collection>) { super(db, 'users', trash, { collectionNameResolver(name) { return name; @@ -45,19 +72,19 @@ export class UsersRaw extends BaseRaw { } // Move index from constructor to here - modelIndexes() { + modelIndexes(): IndexDescription[] { return [ - { key: { __rooms: 1 }, sparse: 1 }, - { key: { roles: 1 }, sparse: 1 }, + { key: { __rooms: 1 }, sparse: true }, + { key: { roles: 1 }, sparse: true }, { key: { name: 1 } }, - { key: { bio: 1 }, sparse: 1 }, - { key: { nickname: 1 }, sparse: 1 }, + { key: { bio: 1 }, sparse: true }, + { key: { nickname: 1 }, sparse: true }, { key: { createdAt: 1 } }, { key: { lastLogin: 1 } }, { key: { status: 1 } }, { key: { statusText: 1 } }, - { key: { statusConnection: 1 }, sparse: 1 }, - { key: { appId: 1 }, sparse: 1 }, + { key: { statusConnection: 1 }, sparse: true }, + { key: { appId: 1 }, sparse: true }, { key: { type: 1 } }, { key: { federated: 1 }, sparse: true }, { key: { federation: 1 }, sparse: true }, @@ -106,7 +133,7 @@ export class UsersRaw extends BaseRaw { * @param {string} uid * @param {IRole['_id'][]} roles list of role ids */ - addRolesByUserId(uid, roles) { + addRolesByUserId(uid: string, roles: string | string[]) { if (!Array.isArray(roles)) { roles = [roles]; process.env.NODE_ENV === 'development' && console.warn('[WARN] Users.addRolesByUserId: roles should be an array'); @@ -129,8 +156,8 @@ export class UsersRaw extends BaseRaw { * @param {null} scope the value for the role scope (room id) - not used in the users collection * @param {any} options */ - findUsersInRoles(roles, scope, options) { - roles = [].concat(roles); + findUsersInRoles(roles: IRole['_id'][] | IRole['_id'], _scope?: null, options?: FindOptions) { + roles = ([] as string[]).concat(roles); const query = { roles: { $in: roles }, @@ -139,8 +166,8 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - countUsersInRoles(roles) { - roles = [].concat(roles); + countUsersInRoles(roles: IRole['_id'][] | IRole['_id']) { + roles = ([] as string[]).concat(roles); const query = { roles: { $in: roles }, @@ -149,8 +176,8 @@ export class UsersRaw extends BaseRaw { return this.countDocuments(query); } - findPaginatedUsersInRoles(roles, options) { - roles = [].concat(roles); + findPaginatedUsersInRoles(roles: IRole['_id'][] | IRole['_id'], options?: FindOptions) { + roles = ([] as string[]).concat(roles); const query = { roles: { $in: roles }, @@ -159,19 +186,19 @@ export class UsersRaw extends BaseRaw { return this.findPaginated(query, options); } - findOneByUsername(username, options = null) { + findOneByUsername(username: string, options?: FindOptions) { const query = { username }; - return this.findOne(query, options); + return this.findOne(query, options); } - findOneAgentById(_id, options) { + findOneAgentById(_id: IUser['_id'], options?: FindOptions) { const query = { _id, roles: 'livechat-agent', }; - return this.findOne(query, options); + return this.findOne(query, options); } /** @@ -179,8 +206,8 @@ export class UsersRaw extends BaseRaw { * @param {any} query * @param {any} options */ - findUsersInRolesWithQuery(roles, query, options) { - roles = [].concat(roles); + findUsersInRolesWithQuery(roles: IRole['_id'][] | IRole['_id'], query: Filter, options?: FindOptions) { + roles = ([] as string[]).concat(roles); Object.assign(query, { roles: { $in: roles } }); @@ -192,16 +219,24 @@ export class UsersRaw extends BaseRaw { * @param {any} query * @param {any} options */ - findPaginatedUsersInRolesWithQuery(roles, query, options) { - roles = [].concat(roles); + findPaginatedUsersInRolesWithQuery( + roles: IRole['_id'][] | IRole['_id'], + query: Filter, + options?: FindOptions, + ) { + roles = ([] as string[]).concat(roles); Object.assign(query, { roles: { $in: roles } }); - return this.findPaginated(query, options); + return this.findPaginated(query, options); } - findAgentsWithDepartments(role, query, options) { - const roles = [].concat(role); + findAgentsWithDepartments( + role: IRole['_id'][] | IRole['_id'], + query: Filter, + options?: FindOptions, + ): Promise<{ sortedResults: (T & { departments: string[] })[]; totalCount: { total: number }[] }[]> { + const roles = ([] as string[]).concat(role); Object.assign(query, { roles: { $in: roles } }); @@ -237,16 +272,16 @@ export class UsersRaw extends BaseRaw { }, { $facet: { - sortedResults: [{ $sort: options.sort }, { $skip: options.skip }, options.limit && { $limit: options.limit }], + sortedResults: [{ $sort: options?.sort }, { $skip: options?.skip }, options?.limit && { $limit: options.limit }], totalCount: [{ $group: { _id: null, total: { $sum: 1 } } }], }, }, ]; - return this.col.aggregate(aggregate).toArray(); + return this.col.aggregate<{ sortedResults: (T & { departments: string[] })[]; totalCount: { total: number }[] }>(aggregate).toArray(); } - findOneByUsernameAndRoomIgnoringCase(username, rid, options) { + findOneByUsernameAndRoomIgnoringCase(username: string | RegExp, rid: string, options?: FindOptions) { if (typeof username === 'string') { username = new RegExp(`^${escapeRegExp(username)}$`, 'i'); } @@ -259,7 +294,7 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - findOneByIdAndLoginHashedToken(_id, token, options = {}) { + findOneByIdAndLoginHashedToken(_id: IUser['_id'], token: string, options: FindOptions = {}) { const query = { _id, 'services.resume.loginTokens.hashedToken': token, @@ -268,7 +303,14 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - findByActiveUsersExcept(searchTerm, exceptions, options, searchFields, extraQuery = [], { startsWith = false, endsWith = false } = {}) { + findByActiveUsersExcept( + searchTerm: string, + exceptions: string[], + options?: FindOptions, + searchFields?: string[], + extraQuery: Filter[] = [], + { startsWith = false, endsWith = false } = {}, + ) { if (exceptions == null) { exceptions = []; } @@ -281,10 +323,13 @@ export class UsersRaw extends BaseRaw { const termRegex = new RegExp((startsWith ? '^' : '') + escapeRegExp(searchTerm) + (endsWith ? '$' : ''), 'i'); - const orStmt = (searchFields || []).reduce((acc, el) => { - acc.push({ [el.trim()]: termRegex }); - return acc; - }, []); + const orStmt = (searchFields || []).reduce( + (acc, el) => { + acc.push({ [el.trim()]: termRegex }); + return acc; + }, + [] as Record[], + ); const query = { $and: [ @@ -304,12 +349,12 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - findPaginatedByActiveUsersExcept( - searchTerm, - exceptions, - options, - searchFields, - extraQuery = [], + findPaginatedByActiveUsersExcept( + searchTerm: string, + exceptions?: string[], + options?: FindOptions, + searchFields: string[] = [], + extraQuery: Filter[] = [], { startsWith = false, endsWith = false } = {}, ) { if (exceptions == null) { @@ -324,10 +369,13 @@ export class UsersRaw extends BaseRaw { const termRegex = new RegExp((startsWith ? '^' : '') + escapeRegExp(searchTerm) + (endsWith ? '$' : ''), 'i'); - const orStmt = (searchFields || []).reduce((acc, el) => { - acc.push({ [el.trim()]: termRegex }); - return acc; - }, []); + const orStmt = (searchFields || []).reduce( + (acc, el) => { + acc.push({ [el.trim()]: termRegex }); + return acc; + }, + [] as Record[], + ); const query = { $and: [ @@ -344,30 +392,42 @@ export class UsersRaw extends BaseRaw { ], }; - return this.findPaginated(query, options); + return this.findPaginated(query, options); } - findPaginatedByActiveLocalUsersExcept(searchTerm, exceptions, options, forcedSearchFields, localDomain) { + findPaginatedByActiveLocalUsersExcept( + searchTerm: string, + exceptions?: string[], + options?: FindOptions, + forcedSearchFields?: string[], + localDomain?: string, + ) { const extraQuery = [ { $or: [{ federation: { $exists: false } }, { 'federation.origin': localDomain }], }, ]; - return this.findPaginatedByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery); + return this.findPaginatedByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery); } - findPaginatedByActiveExternalUsersExcept(searchTerm, exceptions, options, forcedSearchFields, localDomain) { + findPaginatedByActiveExternalUsersExcept( + searchTerm: string, + exceptions?: string[], + options?: FindOptions, + forcedSearchFields?: string[], + localDomain?: string, + ) { const extraQuery = [{ federation: { $exists: true } }, { 'federation.origin': { $ne: localDomain } }]; - return this.findPaginatedByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery); + return this.findPaginatedByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery); } - findActive(query, options = {}) { + findActive(query: Filter, options: FindOptions = {}) { Object.assign(query, { active: true }); return this.find(query, options); } - findActiveByIds(userIds, options = {}) { + findActiveByIds(userIds: IUser['_id'][], options: FindOptions = {}) { const query = { _id: { $in: userIds }, active: true, @@ -376,7 +436,7 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - findActiveByIdsOrUsernames(userIds, options = {}) { + findActiveByIdsOrUsernames(userIds: IUser['_id'][], options: FindOptions = {}) { const query = { $or: [{ _id: { $in: userIds } }, { username: { $in: userIds } }], active: true, @@ -385,32 +445,32 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - findByIds(userIds, options = {}) { + findByIds(userIds: IUser['_id'][], options: FindOptions = {}) { const query = { _id: { $in: userIds }, }; - return this.find(query, options); + return this.find(query, options); } - findOneByImportId(_id, options) { - return this.findOne({ importIds: _id }, options); + findOneByImportId(_id: IUser['_id'], options?: FindOptions) { + return this.findOne({ importIds: _id }, options); } - findOneByUsernameIgnoringCase(username, options) { + findOneByUsernameIgnoringCase(username: IUser['username'], options?: FindOptions) { if (!username) { throw new Error('invalid username'); } const query = { username }; - return this.findOne(query, { + return this.findOne(query, { collation: { locale: 'en', strength: 2 }, // Case insensitive ...options, }); } - findOneWithoutLDAPByUsernameIgnoringCase(username, options) { + findOneWithoutLDAPByUsernameIgnoringCase(username: string, options?: FindOptions) { const expression = new RegExp(`^${escapeRegExp(username)}$`, 'i'); const query = { @@ -420,34 +480,31 @@ export class UsersRaw extends BaseRaw { }, }; - return this.findOne(query, options); + return this.findOne(query, options); } - async findOneByLDAPId(id, attribute = undefined) { + async findOneByLDAPId(id: string, attribute?: string) { const query = { 'services.ldap.id': id, + ...(attribute && { 'services.ldap.idAttribute': attribute }), }; - if (attribute) { - query['services.ldap.idAttribute'] = attribute; - } - - return this.findOne(query); + return this.findOne(query); } - async findOneByAppId(appId, options) { + async findOneByAppId(appId: string, options?: FindOptions) { const query = { appId }; - return this.findOne(query, options); + return this.findOne(query, options); } - findLDAPUsers(options) { + findLDAPUsers(options?: FindOptions) { const query = { ldap: true }; - return this.find(query, options); + return this.find(query, options); } - findLDAPUsersExceptIds(userIds, options = {}) { + findLDAPUsersExceptIds(userIds: IUser['_id'][], options: FindOptions = {}) { const query = { ldap: true, _id: { @@ -455,10 +512,10 @@ export class UsersRaw extends BaseRaw { }, }; - return this.find(query, options); + return this.find(query, options); } - findConnectedLDAPUsers(options) { + findConnectedLDAPUsers(options?: FindOptions) { const query = { 'ldap': true, 'services.resume.loginTokens': { @@ -467,25 +524,29 @@ export class UsersRaw extends BaseRaw { }, }; - return this.find(query, options); + return this.find(query, options); } - isUserInRole(userId, roleId) { + isUserInRole(userId: IUser['_id'], roleId: IRole['_id']) { const query = { _id: userId, roles: roleId, }; - return this.findOne(query, { projection: { roles: 1 } }); + return this.findOne>(query, { projection: { roles: 1 } }); } getDistinctFederationDomains() { return this.col.distinct('federation.origin', { federation: { $exists: true } }); } - async getNextLeastBusyAgent(department, ignoreAgentId, isEnabledWhenAgentIdle) { + async getNextLeastBusyAgent( + department?: string, + ignoreAgentId?: string, + isEnabledWhenAgentIdle?: boolean, + ): Promise<{ agentId: string; username?: string; lastRoutingTime?: Date; count: number; departments?: any[] }> { const match = queryStatusAgentOnline({ ...(ignoreAgentId && { _id: { $ne: ignoreAgentId } }) }, isEnabledWhenAgentIdle); - const aggregate = [ + const aggregate: Document[] = [ { $match: match }, { $lookup: { @@ -535,7 +596,9 @@ export class UsersRaw extends BaseRaw { aggregate.push({ $limit: 1 }); - const [agent] = await this.col.aggregate(aggregate).toArray(); + const [agent] = await this.col + .aggregate<{ agentId: string; username?: string; lastRoutingTime?: Date; count: number; departments?: any[] }>(aggregate) + .toArray(); if (agent) { await this.setLastRoutingTime(agent.agentId); } @@ -543,9 +606,13 @@ export class UsersRaw extends BaseRaw { return agent; } - async getLastAvailableAgentRouted(department, ignoreAgentId, isEnabledWhenAgentIdle) { + async getLastAvailableAgentRouted( + department?: string, + ignoreAgentId?: string, + isEnabledWhenAgentIdle?: boolean, + ): Promise<{ agentId: string; username?: string; lastRoutingTime?: Date; departments?: any[] }> { const match = queryStatusAgentOnline({ ...(ignoreAgentId && { _id: { $ne: ignoreAgentId } }) }, isEnabledWhenAgentIdle); - const aggregate = [ + const aggregate: Document[] = [ { $match: match }, { $lookup: { @@ -566,7 +633,9 @@ export class UsersRaw extends BaseRaw { aggregate.push({ $limit: 1 }); - const [agent] = await this.col.aggregate(aggregate).toArray(); + const [agent] = await this.col + .aggregate<{ agentId: string; username?: string; lastRoutingTime?: Date; departments?: any[] }>(aggregate) + .toArray(); if (agent) { await this.setLastRoutingTime(agent.agentId); } @@ -574,7 +643,7 @@ export class UsersRaw extends BaseRaw { return agent; } - async setLastRoutingTime(userId) { + async setLastRoutingTime(userId: IUser['_id']) { const result = await this.findOneAndUpdate( { _id: userId }, { @@ -584,12 +653,17 @@ export class UsersRaw extends BaseRaw { }, { returnDocument: 'after' }, ); - return result.value; + return result; } - setLivechatStatusIf(userId, status, conditions = {}, extraFields = {}) { + setLivechatStatusIf( + userId: IUser['_id'], + status: ILivechatAgentStatus, + conditions: Filter = {}, + extraFields: UpdateFilter['$set'] = {}, + ) { // TODO: Create class Agent - const query = { + const query: Filter = { _id: userId, ...conditions, }; @@ -604,7 +678,13 @@ export class UsersRaw extends BaseRaw { return this.updateOne(query, update); } - async getAgentAndAmountOngoingChats(userId) { + async getAgentAndAmountOngoingChats(userId: IUser['_id']): Promise<{ + agentId: string; + username?: string; + lastAssignTime?: Date; + lastRoutingTime?: Date; + queueInfo: { chats: number }; + }> { const aggregate = [ { $match: { @@ -643,13 +723,21 @@ export class UsersRaw extends BaseRaw { { $sort: { 'queueInfo.chats': 1, 'lastAssignTime': 1, 'lastRoutingTime': 1, 'username': 1 } }, ]; - const [agent] = await this.col.aggregate(aggregate).toArray(); + const [agent] = await this.col + .aggregate<{ + agentId: string; + username?: string; + lastAssignTime?: Date; + lastRoutingTime?: Date; + queueInfo: { chats: number }; + }>(aggregate) + .toArray(); return agent; } - findAllResumeTokensByUserId(userId) { + findAllResumeTokensByUserId(userId: IUser['_id']): Promise<{ tokens: IMeteorLoginToken[] }[]> { return this.col - .aggregate([ + .aggregate<{ tokens: IMeteorLoginToken[] }>([ { $match: { _id: userId, @@ -675,7 +763,12 @@ export class UsersRaw extends BaseRaw { .toArray(); } - findActiveByUsernameOrNameRegexWithExceptionsAndConditions(termRegex, exceptions, conditions, options) { + findActiveByUsernameOrNameRegexWithExceptionsAndConditions( + termRegex: { $regex: string; $options: string } | RegExp, + exceptions?: string[], + conditions?: Filter, + options?: FindOptions, + ) { if (exceptions == null) { exceptions = []; } @@ -722,16 +815,20 @@ export class UsersRaw extends BaseRaw { ], }; - return this.find(query, options); + return this.find(query, options); } - countAllAgentsStatus({ departmentId = undefined }) { - const match = { + countAllAgentsStatus({ + departmentId, + }: { + departmentId?: string; + }): Promise<{ offline: number; away: number; busy: number; available: number }[]> { + const match: Document = { $match: { roles: { $in: ['livechat-agent'] }, }, }; - const group = { + const group: Document = { $group: { _id: null, offline: { @@ -785,7 +882,7 @@ export class UsersRaw extends BaseRaw { }, }, }; - const lookup = { + const lookup: Document = { $lookup: { from: 'rocketchat_livechat_department_agents', localField: '_id', @@ -793,13 +890,13 @@ export class UsersRaw extends BaseRaw { as: 'departments', }, }; - const unwind = { + const unwind: Document = { $unwind: { path: '$departments', preserveNullAndEmptyArrays: true, }, }; - const departmentsMatch = { + const departmentsMatch: Document = { $match: { 'departments.departmentId': departmentId, }, @@ -811,11 +908,19 @@ export class UsersRaw extends BaseRaw { params.push(departmentsMatch); } params.push(group); - return this.col.aggregate(params).toArray(); + return this.col.aggregate<{ offline: number; away: number; busy: number; available: number }>(params).toArray(); } - getTotalOfRegisteredUsersByDate({ start, end, options = {} }) { - const params = [ + getTotalOfRegisteredUsersByDate({ + start, + end, + options = {}, + }: { + start: Date; + end: Date; + options?: { count?: number; sort?: Record }; + }): Promise<{ date: string; users: number; type: 'users' }[]> { + const params: Document[] = [ { $match: { createdAt: { $gte: start, $lte: end }, @@ -851,10 +956,10 @@ export class UsersRaw extends BaseRaw { if (options.count) { params.push({ $limit: options.count }); } - return this.col.aggregate(params).toArray(); + return this.col.aggregate<{ date: string; users: number; type: 'users' }>(params).toArray(); } - getUserLanguages() { + getUserLanguages(): Promise<{ _id: string; total: number }[]> { const pipeline = [ { $match: { @@ -872,18 +977,10 @@ export class UsersRaw extends BaseRaw { }, ]; - return this.col.aggregate(pipeline).toArray(); + return this.col.aggregate<{ _id: string; total: number }>(pipeline).toArray(); } - /** - * - * @param {string} _id - * @param {string} statusText - * @param {Object} options - * @param {ClientSession} options.session - * @returns {Promise} - */ - updateStatusText(_id, statusText, options) { + updateStatusText(_id: IUser['_id'], statusText: string, options?: UpdateOptions) { const update = { $set: { statusText, @@ -893,7 +990,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update, { session: options?.session }); } - updateStatusByAppId(appId, status) { + updateStatusByAppId(appId: string, status: UserStatus) { const query = { appId, status: { $ne: status }, @@ -916,7 +1013,15 @@ export class UsersRaw extends BaseRaw { * @param {string} [status.statusDefault] * @param {string} [status.statusText] */ - updateStatusById(userId, { statusDefault, status, statusConnection, statusText }) { + updateStatusById( + userId: IUser['_id'], + { + statusDefault, + status, + statusConnection, + statusText, + }: { statusDefault?: UserStatus; status: UserStatus; statusConnection: UserStatus; statusText?: string }, + ) { const query = { _id: userId, }; @@ -937,7 +1042,7 @@ export class UsersRaw extends BaseRaw { return this.col.updateOne(query, update); } - openAgentsBusinessHoursByBusinessHourId(businessHourIds) { + openAgentsBusinessHoursByBusinessHourId(businessHourIds: string[]) { const query = { roles: 'livechat-agent', }; @@ -951,7 +1056,7 @@ export class UsersRaw extends BaseRaw { return this.updateMany(query, update); } - openAgentBusinessHoursByBusinessHourIdsAndAgentId(businessHourIds, agentId) { + openAgentBusinessHoursByBusinessHourIdsAndAgentId(businessHourIds: string[], agentId: IUser['_id']) { const query = { _id: agentId, roles: 'livechat-agent', @@ -966,7 +1071,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne(query, update); } - addBusinessHourByAgentIds(agentIds = [], businessHourId) { + addBusinessHourByAgentIds(agentIds: IUser['_id'][] = [], businessHourId: string) { const query = { _id: { $in: agentIds }, roles: 'livechat-agent', @@ -981,20 +1086,20 @@ export class UsersRaw extends BaseRaw { return this.updateMany(query, update); } - findOnlineButNotAvailableAgents(userIds) { + findOnlineButNotAvailableAgents(userIds?: IUser['_id'][]) { const query = { ...(userIds && { _id: { $in: userIds } }), roles: 'livechat-agent', // Exclude away users - status: 'online', + status: UserStatus.ONLINE, // Exclude users that are already available, maybe due to other business hour - statusLivechat: 'not-available', + statusLivechat: ILivechatAgentStatus.NOT_AVAILABLE, }; - return this.find(query); + return this.find(query); } - removeBusinessHourByAgentIds(agentIds = [], businessHourId) { + removeBusinessHourByAgentIds(agentIds: IUser['_id'][] = [], businessHourId: string) { const query = { _id: { $in: agentIds }, roles: 'livechat-agent', @@ -1009,7 +1114,7 @@ export class UsersRaw extends BaseRaw { return this.updateMany(query, update); } - openBusinessHourToAgentsWithoutDepartment(agentIdsWithDepartment = [], businessHourId) { + openBusinessHourToAgentsWithoutDepartment(agentIdsWithDepartment: IUser['_id'][] = [], businessHourId: string) { const query = { _id: { $nin: agentIdsWithDepartment }, }; @@ -1023,7 +1128,7 @@ export class UsersRaw extends BaseRaw { return this.updateMany(query, update); } - closeBusinessHourToAgentsWithoutDepartment(agentIdsWithDepartment = [], businessHourId) { + closeBusinessHourToAgentsWithoutDepartment(agentIdsWithDepartment: IUser['_id'][] = [], businessHourId: string) { const query = { _id: { $nin: agentIdsWithDepartment }, }; @@ -1037,7 +1142,7 @@ export class UsersRaw extends BaseRaw { return this.updateMany(query, update); } - closeAgentsBusinessHoursByBusinessHourIds(businessHourIds) { + closeAgentsBusinessHoursByBusinessHourIds(businessHourIds: string[]) { const query = { roles: 'livechat-agent', }; @@ -1051,15 +1156,15 @@ export class UsersRaw extends BaseRaw { return this.updateMany(query, update); } - findAgentsAvailableWithoutBusinessHours(userIds = []) { - return this.find( + findAgentsAvailableWithoutBusinessHours(userIds: IUser['_id'][] = []) { + return this.find>( { $or: [{ openBusinessHours: { $exists: false } }, { openBusinessHours: { $size: 0 } }], $and: [{ roles: 'livechat-agent' }, { roles: { $ne: 'bot' } }], // exclude deactivated users active: true, // Avoid unnecessary updates - statusLivechat: 'available', + statusLivechat: ILivechatAgentStatus.AVAILABLE, ...(Array.isArray(userIds) && userIds.length > 0 && { _id: { $in: userIds } }), }, { @@ -1068,10 +1173,10 @@ export class UsersRaw extends BaseRaw { ); } - setLivechatStatusActiveBasedOnBusinessHours(userId) { + setLivechatStatusActiveBasedOnBusinessHours(userId: IUser['_id']) { const query = { _id: userId, - statusDefault: { $ne: 'offline' }, + statusDefault: { $ne: UserStatus.OFFLINE }, openBusinessHours: { $exists: true, $not: { $size: 0 }, @@ -1080,14 +1185,14 @@ export class UsersRaw extends BaseRaw { const update = { $set: { - statusLivechat: 'available', + statusLivechat: ILivechatAgentStatus.AVAILABLE, }, }; return this.updateOne(query, update); } - async isAgentWithinBusinessHours(agentId) { + async isAgentWithinBusinessHours(agentId: IUser['_id']) { const query = { _id: agentId, $or: [ @@ -1116,14 +1221,14 @@ export class UsersRaw extends BaseRaw { const update = { $unset: { - openBusinessHours: 1, + openBusinessHours: 1 as const, }, }; return this.updateMany(query, update); } - resetTOTPById(userId) { + resetTOTPById(userId: IUser['_id']) { return this.col.updateOne( { _id: userId, @@ -1136,7 +1241,7 @@ export class UsersRaw extends BaseRaw { ); } - unsetOneLoginToken(_id, token) { + unsetOneLoginToken(_id: IUser['_id'], token: string) { const update = { $pull: { 'services.resume.loginTokens': { hashedToken: token }, @@ -1146,7 +1251,7 @@ export class UsersRaw extends BaseRaw { return this.col.updateOne({ _id }, update); } - unsetLoginTokens(userId) { + unsetLoginTokens(userId: IUser['_id']) { return this.col.updateOne( { _id: userId, @@ -1159,7 +1264,7 @@ export class UsersRaw extends BaseRaw { ); } - removeNonPATLoginTokensExcept(userId, authToken) { + removeNonPATLoginTokensExcept(userId: IUser['_id'], authToken: string) { return this.col.updateOne( { _id: userId, @@ -1175,7 +1280,7 @@ export class UsersRaw extends BaseRaw { ); } - removeRoomsByRoomIdsAndUserId(rids, userId) { + removeRoomsByRoomIdsAndUserId(rids: IRoom['_id'][], userId: IUser['_id']) { return this.updateMany( { _id: userId, @@ -1183,10 +1288,13 @@ export class UsersRaw extends BaseRaw { }, { $pullAll: { __rooms: rids }, - $unset: rids.reduce((acc, rid) => { - acc[`roomRolePriorities.${rid}`] = ''; - return acc; - }, {}), + $unset: rids.reduce( + (acc, rid) => { + acc[`roomRolePriorities.${rid}`] = ''; + return acc; + }, + {} as Record, + ), }, ); } @@ -1195,7 +1303,7 @@ export class UsersRaw extends BaseRaw { * @param {string} uid * @param {IRole['_id']} roles the list of role ids to remove */ - removeRolesByUserId(uid, roles) { + removeRolesByUserId(uid: IUser['_id'], roles: IRole['_id'][]) { const query = { _id: uid, }; @@ -1209,7 +1317,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne(query, update); } - async isUserInRoleScope(uid) { + async isUserInRoleScope(uid: IUser['_id']) { const query = { _id: uid, }; @@ -1222,7 +1330,7 @@ export class UsersRaw extends BaseRaw { return !!found; } - addBannerById(_id, banner) { + addBannerById(_id: IUser['_id'], banner: { id: string }) { const query = { _id, [`banners.${banner.id}.read`]: { @@ -1240,13 +1348,13 @@ export class UsersRaw extends BaseRaw { } // Voip functions - findOneByAgentUsername(username, options) { + findOneByAgentUsername(username: string, options?: FindOptions) { const query = { username, roles: 'livechat-agent' }; return this.findOne(query, options); } - findOneByExtension(extension, options) { + findOneByExtension(extension: string, options?: FindOptions) { const query = { extension, }; @@ -1254,7 +1362,7 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - findByExtensions(extensions, options) { + findByExtensions(extensions: string[], options?: FindOptions) { const query = { extension: { $in: extensions, @@ -1264,7 +1372,7 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - getVoipExtensionByUserId(userId, options) { + getVoipExtensionByUserId(userId: IUser['_id'], options?: FindOptions) { const query = { _id: userId, extension: { $exists: true }, @@ -1272,7 +1380,7 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - setExtension(userId, extension) { + setExtension(userId: IUser['_id'], extension: string) { const query = { _id: userId, }; @@ -1285,33 +1393,33 @@ export class UsersRaw extends BaseRaw { return this.updateOne(query, update); } - unsetExtension(userId) { + unsetExtension(userId: IUser['_id']) { const query = { _id: userId, }; const update = { $unset: { - extension: true, + extension: 1 as const, }, }; return this.updateOne(query, update); } - getAvailableAgentsIncludingExt(includeExt, text, options) { + getAvailableAgentsIncludingExt(includeExt?: string, text?: string, options?: FindOptions) { const query = { roles: { $in: ['livechat-agent'] }, $and: [ - ...(text && text.trim() + ...(text?.trim() ? [{ $or: [{ username: new RegExp(escapeRegExp(text), 'i') }, { name: new RegExp(escapeRegExp(text), 'i') }] }] : []), { $or: [{ extension: { $exists: false } }, ...(includeExt ? [{ extension: includeExt }] : [])] }, ], }; - return this.findPaginated(query, options); + return this.findPaginated(query, options); } - findActiveUsersTOTPEnable(options) { + findActiveUsersTOTPEnable(options?: FindOptions) { const query = { 'active': true, 'services.totp.enabled': true, @@ -1319,7 +1427,7 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - countActiveUsersTOTPEnable(options) { + countActiveUsersTOTPEnable(options?: FindOptions) { const query = { 'active': true, 'services.totp.enabled': true, @@ -1327,7 +1435,7 @@ export class UsersRaw extends BaseRaw { return this.countDocuments(query, options); } - findActiveUsersEmail2faEnable(options) { + findActiveUsersEmail2faEnable(options?: FindOptions) { const query = { 'active': true, 'services.email2fa.enabled': true, @@ -1335,7 +1443,7 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - countActiveUsersEmail2faEnable(options) { + countActiveUsersEmail2faEnable(options?: FindOptions) { const query = { 'active': true, 'services.email2fa.enabled': true, @@ -1343,7 +1451,7 @@ export class UsersRaw extends BaseRaw { return this.countDocuments(query, options); } - setAsFederated(uid) { + setAsFederated(uid: IUser['_id']) { const query = { _id: uid, }; @@ -1356,7 +1464,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne(query, update); } - removeRoomByRoomId(rid, options) { + removeRoomByRoomId(rid: IRoom['_id'], options?: UpdateOptions) { return this.updateMany( { __rooms: rid, @@ -1369,11 +1477,11 @@ export class UsersRaw extends BaseRaw { ); } - findOneByResetToken(token, options) { + findOneByResetToken(token: string, options?: FindOptions) { return this.findOne({ 'services.password.reset.token': token }, options); } - findOneByIdWithEmailAddress(userId, options) { + findOneByIdWithEmailAddress(userId: IUser['_id'], options?: FindOptions) { return this.findOne( { _id: userId, @@ -1383,7 +1491,7 @@ export class UsersRaw extends BaseRaw { ); } - setFederationAvatarUrlById(userId, federationAvatarUrl) { + setFederationAvatarUrlById(userId: IUser['_id'], federationAvatarUrl: string) { return this.updateOne( { _id: userId, @@ -1396,8 +1504,8 @@ export class UsersRaw extends BaseRaw { ); } - async findSearchedServerNamesByUserId(userId) { - const user = await this.findOne( + async findSearchedServerNamesByUserId(userId: IUser['_id']): Promise { + const user = await this.findOne>( { _id: userId, }, @@ -1408,10 +1516,10 @@ export class UsersRaw extends BaseRaw { }, ); - return user.federation?.searchedServerNames || []; + return user?.federation?.searchedServerNames || []; } - addServerNameToSearchedServerNamesList(userId, serverName) { + addServerNameToSearchedServerNamesList(userId: IUser['_id'], serverName: string) { return this.updateOne( { _id: userId, @@ -1424,7 +1532,7 @@ export class UsersRaw extends BaseRaw { ); } - removeServerNameFromSearchedServerNamesList(userId, serverName) { + removeServerNameFromSearchedServerNamesList(userId: IUser['_id'], serverName: string) { return this.updateOne( { _id: userId, @@ -1443,21 +1551,21 @@ export class UsersRaw extends BaseRaw { }); } - findOnlineUserFromList(userList, isLivechatEnabledWhenAgentIdle) { + findOnlineUserFromList(userList: string | string[], isLivechatEnabledWhenAgentIdle?: boolean) { // TODO: Create class Agent const username = { - $in: [].concat(userList), + $in: ([] as string[]).concat(userList), }; const query = queryStatusAgentOnline({ username }, isLivechatEnabledWhenAgentIdle); - return this.find(query); + return this.find(query); } - countOnlineUserFromList(userList, isLivechatEnabledWhenAgentIdle) { + countOnlineUserFromList(userList: string | string[], isLivechatEnabledWhenAgentIdle?: boolean) { // TODO: Create class Agent const username = { - $in: [].concat(userList), + $in: ([] as string[]).concat(userList), }; const query = queryStatusAgentOnline({ username }, isLivechatEnabledWhenAgentIdle); @@ -1465,10 +1573,10 @@ export class UsersRaw extends BaseRaw { return this.countDocuments(query); } - findOneOnlineAgentByUserList(userList, options, isLivechatEnabledWhenAgentIdle) { + findOneOnlineAgentByUserList(userList: string | string[], options?: FindOptions, isLivechatEnabledWhenAgentIdle?: boolean) { // TODO:: Create class Agent const username = { - $in: [].concat(userList), + $in: ([] as string[]).concat(userList), }; const query = queryStatusAgentOnline({ username }, isLivechatEnabledWhenAgentIdle); @@ -1476,11 +1584,30 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - getUnavailableAgents() { - return []; - } - - findBotAgents(usernameList) { + async getUnavailableAgents( + _departmentId?: string | undefined, + _extraQuery?: Document | undefined, + ): Promise< + { + agentId: string; + username: string; + lastAssignTime: string; + lastRoutingTime: string; + livechat: { maxNumberSimultaneousChat: number }; + queueInfo: { chats: number }; + }[] + > { + return [] as { + agentId: string; + username: string; + lastAssignTime: string; + lastRoutingTime: string; + livechat: { maxNumberSimultaneousChat: number }; + queueInfo: { chats: number }; + }[]; + } + + findBotAgents(usernameList?: string | string[]): FindCursor { // TODO:: Create class Agent const query = { roles: { @@ -1488,15 +1615,15 @@ export class UsersRaw extends BaseRaw { }, ...(usernameList && { username: { - $in: [].concat(usernameList), + $in: ([] as string[]).concat(usernameList), }, }), }; - return this.find(query); + return this.find(query); } - countBotAgents(usernameList) { + countBotAgents(usernameList?: string | string[]) { // TODO:: Create class Agent const query = { roles: { @@ -1504,7 +1631,7 @@ export class UsersRaw extends BaseRaw { }, ...(usernameList && { username: { - $in: [].concat(usernameList), + $in: ([] as string[]).concat(usernameList), }, }), }; @@ -1512,7 +1639,7 @@ export class UsersRaw extends BaseRaw { return this.countDocuments(query); } - removeAllRoomsByUserId(_id) { + removeAllRoomsByUserId(_id: IUser['_id']) { return this.updateOne( { _id, @@ -1524,7 +1651,7 @@ export class UsersRaw extends BaseRaw { ); } - removeRoomByUserId(_id, rid) { + removeRoomByUserId(_id: IUser['_id'], rid: IRoom['_id']) { return this.updateOne( { _id, @@ -1537,7 +1664,7 @@ export class UsersRaw extends BaseRaw { ); } - addRoomByUserId(_id, rid) { + addRoomByUserId(_id: IUser['_id'], rid: IRoom['_id']) { return this.updateOne( { _id, @@ -1549,7 +1676,7 @@ export class UsersRaw extends BaseRaw { ); } - addRoomByUserIds(uids, rid) { + addRoomByUserIds(uids: IUser['_id'][], rid: IRoom['_id']) { return this.updateMany( { _id: { $in: uids }, @@ -1561,22 +1688,25 @@ export class UsersRaw extends BaseRaw { ); } - removeRoomByRoomIds(rids) { + removeRoomByRoomIds(rids: IRoom['_id'][]) { return this.updateMany( { __rooms: { $in: rids }, }, { $pullAll: { __rooms: rids }, - $unset: rids.reduce((acc, rid) => { - acc[`roomRolePriorities.${rid}`] = ''; - return acc; - }, {}), + $unset: rids.reduce( + (acc, rid) => { + acc[`roomRolePriorities.${rid}`] = ''; + return acc; + }, + {} as Record, + ), }, ); } - addRoomRolePriorityByUserId(userId, rid, priority) { + addRoomRolePriorityByUserId(userId: IUser['_id'], rid: IRoom['_id'], priority: number) { return this.updateOne( { _id: userId, @@ -1589,7 +1719,7 @@ export class UsersRaw extends BaseRaw { ); } - removeRoomRolePriorityByUserId(userId, rid) { + removeRoomRolePriorityByUserId(userId: IUser['_id'], rid: IRoom['_id']) { return this.updateOne( { _id: userId, @@ -1602,7 +1732,7 @@ export class UsersRaw extends BaseRaw { ); } - async assignRoomRolePrioritiesByUserIdPriorityMap(userIdAndrolePriorityMap, rid) { + async assignRoomRolePrioritiesByUserIdPriorityMap(userIdAndrolePriorityMap: Record, rid: IRoom['_id']) { const bulk = this.col.initializeUnorderedBulkOp(); for (const [userId, priority] of Object.entries(userIdAndrolePriorityMap)) { @@ -1617,7 +1747,7 @@ export class UsersRaw extends BaseRaw { return 0; } - unassignRoomRolePrioritiesByRoomId(rid) { + unassignRoomRolePrioritiesByRoomId(rid: IRoom['_id']) { return this.updateMany( { __rooms: rid, @@ -1630,7 +1760,7 @@ export class UsersRaw extends BaseRaw { ); } - getLoginTokensByUserId(userId) { + getLoginTokensByUserId(userId: IUser['_id']) { const query = { 'services.resume.loginTokens.type': { $exists: true, @@ -1639,10 +1769,10 @@ export class UsersRaw extends BaseRaw { '_id': userId, }; - return this.find(query, { projection: { 'services.resume.loginTokens': 1 } }); + return this.find(query, { projection: { 'services.resume.loginTokens': 1 } }); } - addPersonalAccessTokenToUser({ userId, loginTokenObject }) { + addPersonalAccessTokenToUser({ userId, loginTokenObject }: { userId: IUser['_id']; loginTokenObject: ILoginToken }) { return this.updateOne( { _id: userId }, { @@ -1653,7 +1783,13 @@ export class UsersRaw extends BaseRaw { ); } - removePersonalAccessTokenOfUser({ userId, loginTokenObject }) { + removePersonalAccessTokenOfUser({ + userId, + loginTokenObject, + }: { + userId: IUser['_id']; + loginTokenObject: AtLeast; + }) { return this.updateOne( { _id: userId }, { @@ -1664,7 +1800,7 @@ export class UsersRaw extends BaseRaw { ); } - findPersonalAccessTokenByTokenNameAndUserId({ userId, tokenName }) { + findPersonalAccessTokenByTokenNameAndUserId({ userId, tokenName }: { userId: IUser['_id']; tokenName: string }) { const query = { 'services.resume.loginTokens': { $elemMatch: { name: tokenName, type: 'personalAccessToken' }, @@ -1675,7 +1811,8 @@ export class UsersRaw extends BaseRaw { return this.findOne(query); } - setOperator(_id, operator) { + // TODO: check if this is still valid/used for something + setOperator(_id: IUser['_id'], operator: boolean) { // TODO:: Create class Agent const update = { $set: { @@ -1686,21 +1823,28 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - async checkOnlineAgents(agentId, isLivechatEnabledWhenAgentIdle) { + async checkOnlineAgents(agentId: IUser['_id'], isLivechatEnabledWhenAgentIdle?: boolean) { // TODO:: Create class Agent const query = queryStatusAgentOnline(agentId && { _id: agentId }, isLivechatEnabledWhenAgentIdle); return !!(await this.findOne(query)); } - findOnlineAgents(agentId, isLivechatEnabledWhenAgentIdle) { + findOnlineAgents(agentId?: IUser['_id'], isLivechatEnabledWhenAgentIdle?: boolean) { // TODO:: Create class Agent const query = queryStatusAgentOnline(agentId && { _id: agentId }, isLivechatEnabledWhenAgentIdle); - return this.find(query); + return this.find(query); } - findOneBotAgent() { + countOnlineAgents(agentId: IUser['_id']) { + // TODO:: Create class Agent + const query = queryStatusAgentOnline(agentId && { _id: agentId }); + + return this.col.countDocuments(query); + } + + findOneBotAgent() { // TODO:: Create class Agent const query = { roles: { @@ -1708,23 +1852,27 @@ export class UsersRaw extends BaseRaw { }, }; - return this.findOne(query); + return this.findOne(query); } - findOneOnlineAgentById(_id, isLivechatEnabledWhenAgentIdle, options) { + findOneOnlineAgentById( + _id: IUser['_id'], + isLivechatEnabledWhenAgentIdle?: boolean, + options?: FindOptions, + ) { // TODO: Create class Agent const query = queryStatusAgentOnline({ _id }, isLivechatEnabledWhenAgentIdle); - return this.findOne(query, options); + return this.findOne(query, options); } - findAgents() { + findAgents() { // TODO: Create class Agent const query = { roles: 'livechat-agent', }; - return this.find(query); + return this.find(query); } countAgents() { @@ -1737,10 +1885,10 @@ export class UsersRaw extends BaseRaw { } // 2 - async getNextAgent(ignoreAgentId, extraQuery, enabledWhenAgentIdle) { + async getNextAgent(ignoreAgentId?: string, extraQuery?: Filter, enabledWhenAgentIdle?: boolean) { // TODO: Create class Agent // fetch all unavailable agents, and exclude them from the selection - const unavailableAgents = (await this.getUnavailableAgents(null, extraQuery)).map((u) => u.username); + const unavailableAgents = (await this.getUnavailableAgents(undefined, extraQuery)).map((u) => u.username); const extraFilters = { ...(ignoreAgentId && { _id: { $ne: ignoreAgentId } }), // limit query to remove booked agents @@ -1749,7 +1897,7 @@ export class UsersRaw extends BaseRaw { const query = queryStatusAgentOnline(extraFilters, enabledWhenAgentIdle); - const sort = { + const sort: Record = { livechatCount: 1, username: 1, }; @@ -1770,7 +1918,7 @@ export class UsersRaw extends BaseRaw { return null; } - async getNextBotAgent(ignoreAgentId) { + async getNextBotAgent(ignoreAgentId?: string) { // TODO: Create class Agent const query = { roles: { @@ -1790,7 +1938,7 @@ export class UsersRaw extends BaseRaw { }, }; - const user = await this.findOneAndUpdate(query, update, { sort, returnDocument: 'after' }); + const user = await this.findOneAndUpdate(query, update, { sort, returnDocument: 'after' } as FindOneAndUpdateOptions); if (user) { return { agentId: user._id, @@ -1800,7 +1948,7 @@ export class UsersRaw extends BaseRaw { return null; } - setLivechatStatus(userId, status) { + setLivechatStatus(userId: IUser['_id'], status: ILivechatAgentStatus) { // TODO: Create class Agent const query = { _id: userId, @@ -1816,7 +1964,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne(query, update); } - makeAgentUnavailableAndUnsetExtension(userId) { + makeAgentUnavailableAndUnsetExtension(userId: IUser['_id']) { const query = { _id: userId, roles: 'livechat-agent', @@ -1827,14 +1975,15 @@ export class UsersRaw extends BaseRaw { statusLivechat: ILivechatAgentStatus.NOT_AVAILABLE, }, $unset: { - extension: 1, + extension: 1 as const, }, }; return this.updateOne(query, update); } - setLivechatData(userId, data = {}) { + // TODO: improve type of livechatData + setLivechatData(userId: IUser['_id'], data: Record = {}) { // TODO: Create class Agent const query = { _id: userId, @@ -1849,21 +1998,31 @@ export class UsersRaw extends BaseRaw { 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 = []; - await this.findAgents().forEach((agent) => promises.push(this.setLivechatStatus(agent._id, 'not-available'))); + 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 = []; - await this.findAgents().forEach((agent) => promises.push(this.setLivechatStatus(agent._id, 'available'))); + const promises: Promise>[] = []; + await this.findAgents().forEach((agent) => { + promises.push(this.setLivechatStatus(agent._id, ILivechatAgentStatus.AVAILABLE)); + }); await Promise.all(promises); } - getAgentInfo(agentId, showAgentEmail = false) { + getAgentInfo( + agentId: IUser['_id'], + showAgentEmail = false, + ): Promise | null> { // TODO: Create class Agent const query = { _id: agentId, @@ -1884,11 +2043,12 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - roleBaseQuery(userId) { + roleBaseQuery(userId: IUser['_id']) { return { _id: userId }; } - setE2EPublicAndPrivateKeysByUserId(userId, { public_key, private_key }) { + // eslint-disable-next-line @typescript-eslint/naming-convention + setE2EPublicAndPrivateKeysByUserId(userId: IUser['_id'], { public_key, private_key }: { public_key: string; private_key: string }) { return this.updateOne( { _id: userId }, { @@ -1900,7 +2060,7 @@ export class UsersRaw extends BaseRaw { ); } - async rocketMailUnsubscribe(_id, createdAt) { + async rocketMailUnsubscribe(_id: IUser['_id'], createdAt: string) { const query = { _id, createdAt: new Date(parseInt(createdAt)), @@ -1910,11 +2070,11 @@ export class UsersRaw extends BaseRaw { 'mailer.unsubscribed': true, }, }; - const affectedRows = (await this.updateOne(query, update)).updatedCount; + const affectedRows = (await this.updateOne(query, update)).modifiedCount; return affectedRows; } - async fetchKeysByUserId(userId) { + async fetchKeysByUserId(userId: IUser['_id']) { const user = await this.findOne({ _id: userId }, { projection: { e2e: 1 } }); if (!user?.e2e?.public_key) { @@ -1927,7 +2087,7 @@ export class UsersRaw extends BaseRaw { }; } - disable2FAAndSetTempSecretByUserId(userId, tempToken) { + disable2FAAndSetTempSecretByUserId(userId: IUser['_id'], tempToken: string) { return this.updateOne( { _id: userId, @@ -1943,7 +2103,7 @@ export class UsersRaw extends BaseRaw { ); } - enable2FAAndSetSecretAndCodesByUserId(userId, secret, backupCodes) { + enable2FAAndSetSecretAndCodesByUserId(userId: IUser['_id'], secret: string, backupCodes: string[]) { return this.updateOne( { _id: userId, @@ -1961,7 +2121,7 @@ export class UsersRaw extends BaseRaw { ); } - disable2FAByUserId(userId) { + disable2FAByUserId(userId: IUser['_id']) { return this.updateOne( { _id: userId, @@ -1976,7 +2136,7 @@ export class UsersRaw extends BaseRaw { ); } - update2FABackupCodesByUserId(userId, backupCodes) { + update2FABackupCodesByUserId(userId: IUser['_id'], backupCodes: string[]) { return this.updateOne( { _id: userId, @@ -1989,7 +2149,7 @@ export class UsersRaw extends BaseRaw { ); } - enableEmail2FAByUserId(userId) { + enableEmail2FAByUserId(userId: IUser['_id']) { return this.updateOne( { _id: userId, @@ -2005,7 +2165,7 @@ export class UsersRaw extends BaseRaw { ); } - disableEmail2FAByUserId(userId) { + disableEmail2FAByUserId(userId: IUser['_id']) { return this.updateOne( { _id: userId, @@ -2021,7 +2181,7 @@ export class UsersRaw extends BaseRaw { ); } - findByIdsWithPublicE2EKey(ids, options) { + findByIdsWithPublicE2EKey(ids: IUser['_id'][], options?: FindOptions) { const query = { '_id': { $in: ids, @@ -2034,7 +2194,7 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - resetE2EKey(userId) { + resetE2EKey(userId: IUser['_id']) { return this.updateOne( { _id: userId }, { @@ -2045,7 +2205,7 @@ export class UsersRaw extends BaseRaw { ); } - removeExpiredEmailCodeOfUserId(userId) { + removeExpiredEmailCodeOfUserId(userId: IUser['_id']) { return this.updateOne( { '_id': userId, 'services.emailCode.expire': { $lt: new Date() } }, { @@ -2054,7 +2214,7 @@ export class UsersRaw extends BaseRaw { ); } - removeEmailCodeOfUserId(userId) { + removeEmailCodeOfUserId(userId: IUser['_id']) { return this.updateOne( { _id: userId }, { @@ -2063,7 +2223,7 @@ export class UsersRaw extends BaseRaw { ); } - incrementInvalidEmailCodeAttempt(userId) { + incrementInvalidEmailCodeAttempt(userId: IUser['_id']) { return this.findOneAndUpdate( { _id: userId }, { @@ -2078,7 +2238,7 @@ export class UsersRaw extends BaseRaw { ); } - async maxInvalidEmailCodeAttemptsReached(userId, maxAttempts) { + async maxInvalidEmailCodeAttemptsReached(userId: IUser['_id'], maxAttempts: number) { const result = await this.findOne( { '_id': userId, @@ -2093,7 +2253,7 @@ export class UsersRaw extends BaseRaw { return !!result?._id; } - addEmailCodeByUserId(userId, code, expire) { + addEmailCodeByUserId(userId: IUser['_id'], code: string, expire: Date) { return this.updateOne( { _id: userId }, { @@ -2112,8 +2272,8 @@ export class UsersRaw extends BaseRaw { * @param {IRole['_id'][]} roles the list of role ids * @param {any} options */ - findActiveUsersInRoles(roles, options) { - roles = [].concat(roles); + findActiveUsersInRoles(roles: IRole['_id'][], options?: FindOptions) { + roles = ([] as string[]).concat(roles); const query = { roles: { $in: roles }, @@ -2123,8 +2283,8 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - countActiveUsersInRoles(roles, options) { - roles = [].concat(roles); + countActiveUsersInRoles(roles: IRole['_id'][], options?: FindOptions) { + roles = ([] as string[]).concat(roles); const query = { roles: { $in: roles }, @@ -2134,7 +2294,12 @@ export class UsersRaw extends BaseRaw { return this.countDocuments(query, options); } - findOneByUsernameAndServiceNameIgnoringCase(username, userId, serviceName, options) { + findOneByUsernameAndServiceNameIgnoringCase( + username: string | RegExp, + userId: IUser['_id'], + serviceName: string, + options?: FindOptions, + ) { if (typeof username === 'string') { username = new RegExp(`^${escapeRegExp(username)}$`, 'i'); } @@ -2144,7 +2309,12 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - findOneByEmailAddressAndServiceNameIgnoringCase(emailAddress, userId, serviceName, options) { + findOneByEmailAddressAndServiceNameIgnoringCase( + emailAddress: string, + userId: IUser['_id'], + serviceName: string, + options?: FindOptions, + ) { const query = { 'emails.address': String(emailAddress).trim(), [`services.${serviceName}.id`]: userId, @@ -2156,7 +2326,7 @@ export class UsersRaw extends BaseRaw { }); } - findOneByEmailAddress(emailAddress, options) { + findOneByEmailAddress(emailAddress: string, options?: FindOptions) { const query = { 'emails.address': String(emailAddress).trim() }; return this.findOne(query, { @@ -2165,7 +2335,7 @@ export class UsersRaw extends BaseRaw { }); } - findOneWithoutLDAPByEmailAddress(emailAddress, options) { + findOneWithoutLDAPByEmailAddress(emailAddress: string, options?: FindOptions) { const query = { 'email.address': emailAddress.trim().toLowerCase(), 'services.ldap': { @@ -2176,13 +2346,13 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - findOneAdmin(userId, options) { + findOneAdmin(userId: IUser['_id'], options?: FindOptions) { const query = { roles: { $in: ['admin'] }, _id: userId }; return this.findOne(query, options); } - findOneByIdAndLoginToken(_id, token, options) { + findOneByIdAndLoginToken(_id: IUser['_id'], token: string, options?: FindOptions) { const query = { _id, 'services.resume.loginTokens.hashedToken': token, @@ -2191,13 +2361,13 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - findOneById(userId, options = {}) { + findOneById(userId: IUser['_id'], options: FindOptions = {}) { const query = { _id: userId }; return this.findOne(query, options); } - findOneActiveById(userId, options) { + findOneActiveById(userId?: IUser['_id'], options?: FindOptions) { const query = { _id: userId, active: true, @@ -2206,7 +2376,7 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - findOneByIdOrUsername(idOrUsername, options) { + findOneByIdOrUsername(idOrUsername: IUser['_id'] | IUser['username'], options?: FindOptions) { const query = { $or: [ { @@ -2221,53 +2391,53 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - findOneByRolesAndType(roles, type, options) { + findOneByRolesAndType(roles: IRole['_id'][], type: string, options?: FindOptions) { const query = { roles, type }; - return this.findOne(query, options); + return this.findOne(query, options); } - findNotOfflineByIds(users, options) { + findNotOfflineByIds(users?: IUser['_id'][], options?: FindOptions) { const query = { _id: { $in: users }, status: { - $in: ['online', 'away', 'busy'], + $in: [UserStatus.ONLINE, UserStatus.AWAY, UserStatus.BUSY], }, }; return this.find(query, options); } - findUsersNotOffline(options) { + findUsersNotOffline(options?: FindOptions) { const query = { username: { - $exists: 1, + $exists: true, }, status: { - $in: ['online', 'away', 'busy'], + $in: [UserStatus.ONLINE, UserStatus.AWAY, UserStatus.BUSY], }, }; return this.find(query, options); } - countUsersNotOffline(options) { + countUsersNotOffline(options?: FindOptions) { const query = { username: { - $exists: 1, + $exists: true, }, status: { - $in: ['online', 'away', 'busy'], + $in: [UserStatus.ONLINE, UserStatus.AWAY, UserStatus.BUSY], }, }; return this.col.countDocuments(query, options); } - findNotIdUpdatedFrom(uid, from, options) { - const query = { + findNotIdUpdatedFrom(uid: IUser['_id'], from: Date, options?: FindOptions) { + const query: Filter = { _id: { $ne: uid }, username: { - $exists: 1, + $exists: true, }, _updatedAt: { $gte: from }, }; @@ -2275,7 +2445,7 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - async findByRoomId(rid, options) { + async findByRoomId(rid: IRoom['_id'], options?: FindOptions) { const data = (await Subscriptions.findByRoomId(rid).toArray()).map((item) => item.u._id); const query = { _id: { @@ -2286,19 +2456,19 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - findByUsername(username, options) { + findByUsername(username: string, options?: FindOptions) { const query = { username }; return this.find(query, options); } - findByUsernames(usernames, options) { + findByUsernames(usernames: string[], options?: FindOptions) { const query = { username: { $in: usernames } }; return this.find(query, options); } - findByUsernamesIgnoringCase(usernames, options) { + findByUsernamesIgnoringCase(usernames: string[], options?: FindOptions) { const query = { username: { $in: usernames.filter(Boolean).map((u) => new RegExp(`^${escapeRegExp(u)}$`, 'i')), @@ -2308,7 +2478,7 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - findActiveByUserIds(ids, options = {}) { + findActiveByUserIds(ids: IUser['_id'][], options: FindOptions = {}) { return this.find( { active: true, @@ -2319,8 +2489,8 @@ export class UsersRaw extends BaseRaw { ); } - findActiveLocalGuests(idExceptions = [], options = {}) { - const query = { + findActiveLocalGuests(idExceptions: IUser['_id'] | IUser['_id'][] = [], options: FindOptions = {}) { + const query: Filter = { active: true, type: { $nin: ['app'] }, roles: { @@ -2341,8 +2511,8 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - countActiveLocalGuests(idExceptions = []) { - const query = { + countActiveLocalGuests(idExceptions: IUser['_id'] | IUser['_id'][] = []) { + const query: Filter = { active: true, type: { $nin: ['app'] }, roles: { @@ -2364,10 +2534,10 @@ export class UsersRaw extends BaseRaw { } // 4 - findUsersByNameOrUsername(nameOrUsername, options) { + findUsersByNameOrUsername(nameOrUsername: string, options?: FindOptions) { const query = { username: { - $exists: 1, + $exists: true, }, $or: [{ name: nameOrUsername }, { username: nameOrUsername }], @@ -2380,7 +2550,7 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - findByUsernameNameOrEmailAddress(usernameNameOrEmailAddress, options) { + findByUsernameNameOrEmailAddress(usernameNameOrEmailAddress: string, options?: FindOptions) { const query = { $or: [ { name: usernameNameOrEmailAddress }, @@ -2395,29 +2565,29 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - findCrowdUsers(options) { + findCrowdUsers(options?: FindOptions) { const query = { crowd: true }; return this.find(query, options); } - async getLastLogin(options = { projection: { _id: 0, lastLogin: 1 } }) { + async getLastLogin(options: FindOptions = { projection: { _id: 0, lastLogin: 1 } }) { options.sort = { lastLogin: -1 }; const user = await this.findOne({}, options); return user?.lastLogin; } - findUsersByUsernames(usernames, options) { + findUsersByUsernames(usernames: string[], options?: FindOptions) { const query = { username: { $in: usernames, }, }; - return this.find(query, options); + return this.find(query, options); } - findUsersByIds(ids, options) { + findUsersByIds(ids: IUser['_id'][], options?: FindOptions) { const query = { _id: { $in: ids, @@ -2426,29 +2596,29 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - findUsersWithUsernameByIds(ids, options) { + findUsersWithUsernameByIds(ids: IUser['_id'][], options?: FindOptions) { const query = { _id: { $in: ids, }, username: { - $exists: 1, + $exists: true, }, }; return this.find(query, options); } - findUsersWithUsernameByIdsNotOffline(ids, options) { + findUsersWithUsernameByIdsNotOffline(ids: IUser['_id'][], options?: FindOptions) { const query = { _id: { $in: ids, }, username: { - $exists: 1, + $exists: true, }, status: { - $in: ['online', 'away', 'busy'], + $in: [UserStatus.ONLINE, UserStatus.AWAY, UserStatus.BUSY], }, }; @@ -2458,14 +2628,14 @@ export class UsersRaw extends BaseRaw { /** * @param {import('mongodb').Filter} projection */ - getOldest(optionsParams) { + getOldest(optionsParams?: FindOptions) { const query = { _id: { $ne: 'rocket.cat', }, }; - const options = { + const options: FindOptions = { ...optionsParams, sort: { createdAt: 1, @@ -2475,11 +2645,11 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - countRemote(options = {}) { + countRemote(options: FindOptions = {}) { return this.countDocuments({ isRemote: true }, options); } - findActiveRemote(options = {}) { + findActiveRemote(options: FindOptions = {}) { return this.find( { active: true, @@ -2490,7 +2660,7 @@ export class UsersRaw extends BaseRaw { ); } - findActiveFederated(options = {}) { + findActiveFederated(options: FindOptions = {}) { return this.find( { active: true, @@ -2500,38 +2670,44 @@ export class UsersRaw extends BaseRaw { ); } - getSAMLByIdAndSAMLProvider(_id, provider) { + getSAMLByIdAndSAMLProvider(_id: IUser['_id'], provider: string) { return this.findOne( { _id, 'services.saml.provider': provider, }, { - 'services.saml': 1, + projection: { 'services.saml': 1 }, }, ); } - findBySAMLNameIdOrIdpSession(nameID, idpSession) { - return this.find({ - $or: [{ 'services.saml.nameID': nameID }, { 'services.saml.idpSession': idpSession }], - }); + findBySAMLNameIdOrIdpSession(nameID: string, idpSession: string, options?: FindOptions) { + return this.find( + { + $or: [{ 'services.saml.nameID': nameID }, { 'services.saml.idpSession': idpSession }], + }, + options, + ); } - countBySAMLNameIdOrIdpSession(nameID, idpSession) { + countBySAMLNameIdOrIdpSession(nameID: string, idpSession: string) { return this.countDocuments({ $or: [{ 'services.saml.nameID': nameID }, { 'services.saml.idpSession': idpSession }], }); } - findBySAMLInResponseTo(inResponseTo) { - return this.find({ - 'services.saml.inResponseTo': inResponseTo, - }); + findBySAMLInResponseTo(inResponseTo: string, options?: FindOptions) { + return this.find( + { + 'services.saml.inResponseTo': inResponseTo, + }, + options, + ); } - findOneByFreeSwitchExtension(freeSwitchExtension, options = {}) { - return this.findOne( + findOneByFreeSwitchExtension(freeSwitchExtension: string, options: FindOptions = {}) { + return this.findOne( { freeSwitchExtension, }, @@ -2539,8 +2715,8 @@ export class UsersRaw extends BaseRaw { ); } - findOneByFreeSwitchExtensions(freeSwitchExtensions, options = {}) { - return this.findOne( + findOneByFreeSwitchExtensions(freeSwitchExtensions: string[], options: FindOptions = {}) { + return this.findOne( { freeSwitchExtension: { $in: freeSwitchExtensions }, }, @@ -2556,11 +2732,11 @@ export class UsersRaw extends BaseRaw { }).map(({ freeSwitchExtension }) => freeSwitchExtension); } - findUsersWithAssignedFreeSwitchExtensions(options = {}) { - return this.find( + findUsersWithAssignedFreeSwitchExtensions(options: FindOptions = {}) { + return this.find( { freeSwitchExtension: { - $exists: 1, + $exists: true, }, }, options, @@ -2568,8 +2744,8 @@ export class UsersRaw extends BaseRaw { } // UPDATE - addImportIds(_id, importIds) { - importIds = [].concat(importIds); + addImportIds(_id: IUser['_id'], importIds: string[]) { + importIds = ([] as string[]).concat(importIds); const query = { _id }; @@ -2584,7 +2760,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne(query, update); } - updateInviteToken(_id, inviteToken) { + updateInviteToken(_id: IUser['_id'], inviteToken: string) { const update = { $set: { inviteToken, @@ -2594,7 +2770,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - updateLastLoginById(_id) { + updateLastLoginById(_id: IUser['_id']) { const update = { $set: { lastLogin: new Date(), @@ -2604,7 +2780,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - addPasswordToHistory(_id, password, passwordHistoryAmount) { + addPasswordToHistory(_id: IUser['_id'], password: string, passwordHistoryAmount: number) { const update = { $push: { 'services.passwordHistory': { @@ -2616,39 +2792,24 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - setServiceId(_id, serviceName, serviceId) { - const update = { $set: {} }; + setServiceId(_id: IUser['_id'], serviceName: string, serviceId: string) { + const update: UpdateFilter = { $set: {} }; const serviceIdKey = `services.${serviceName}.id`; - update.$set[serviceIdKey] = serviceId; + if (update.$set) { + update.$set[serviceIdKey] = serviceId; + } return this.updateOne({ _id }, update); } - /** - * - * @param {string} _id - * @param {string} username - * @param {Object} options - * @param {ClientSession} options.session - * @returns {Promise} - */ - setUsername(_id, username, options) { + setUsername(_id: IUser['_id'], username: string, options?: UpdateOptions) { const update = { $set: { username } }; return this.updateOne({ _id }, update, { session: options?.session }); } - /** - * - * @param {string} _id - * @param {string} email - * @param {boolean} verified - * @param {Object} options - * @param {ClientSession} options.session - * @returns {Promise} - */ - setEmail(_id, email, verified = false, options) { + setEmail(_id: IUser['_id'], email: string, verified = false, options?: UpdateOptions) { const update = { $set: { emails: [ @@ -2664,7 +2825,7 @@ export class UsersRaw extends BaseRaw { } // 5 - setEmailVerified(_id, email) { + setEmailVerified(_id: IUser['_id'], email: string) { const query = { _id, emails: { @@ -2684,15 +2845,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne(query, update); } - /** - * - * @param {string} _id - * @param {string} name - * @param {Object} options - * @param {ClientSession} options.session - * @returns {Promise} - */ - setName(_id, name, options) { + setName(_id: IUser['_id'], name: string, options?: UpdateOptions) { const update = { $set: { name, @@ -2702,25 +2855,18 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update, { session: options?.session }); } - /** - * - * @param {string} _id - * @param {Object} options - * @param {ClientSession} options.session - * @returns {Promise} - */ - unsetName(_id, options) { + unsetName(_id: IUser['_id'], options?: UpdateOptions) { const update = { $unset: { - name, + name: 1 as const, }, }; return this.updateOne({ _id }, update, { session: options?.session }); } - setCustomFields(_id, fields) { - const values = {}; + setCustomFields(_id: IUser['_id'], fields: Record) { + const values: Record = {}; Object.keys(fields).forEach((key) => { values[`customFields.${key}`] = fields[key]; }); @@ -2730,16 +2876,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - /** - * - * @param {string} _id - * @param {string} origin - * @param {string} etag - * @param {Object} options - * @param {ClientSession} options.session - * @returns {Promise} - */ - setAvatarData(_id, origin, etag, options) { + setAvatarData(_id: IUser['_id'], origin: string, etag: string, options?: UpdateOptions) { const update = { $set: { avatarOrigin: origin, @@ -2750,8 +2887,8 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update, { session: options?.session }); } - unsetAvatarData(_id) { - const update = { + unsetAvatarData(_id: IUser['_id']) { + const update: UpdateFilter = { $unset: { avatarOrigin: 1, avatarETag: 1, @@ -2761,7 +2898,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - setUserActive(_id, active) { + setUserActive(_id: IUser['_id'], active: boolean | null) { if (active == null) { active = true; } @@ -2774,7 +2911,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - setAllUsersActive(active) { + setAllUsersActive(active: boolean) { const update = { $set: { active, @@ -2789,8 +2926,8 @@ export class UsersRaw extends BaseRaw { * @param {IRole['_id']} role the role id * @param {boolean} active */ - setActiveNotLoggedInAfterWithRole(latestLastLoginDate, role = 'user', active = false) { - const neverActive = { lastLogin: { $exists: 0 }, createdAt: { $lte: latestLastLoginDate } }; + setActiveNotLoggedInAfterWithRole(latestLastLoginDate: Date, role: IRole['_id'] = 'user', active = false) { + const neverActive = { lastLogin: { $exists: false }, createdAt: { $lte: latestLastLoginDate } }; const idleTooLong = { lastLogin: { $lte: latestLastLoginDate } }; const query = { @@ -2808,8 +2945,8 @@ export class UsersRaw extends BaseRaw { return this.updateMany(query, update); } - unsetRequirePasswordChange(_id) { - const update = { + unsetRequirePasswordChange(_id: IUser['_id']) { + const update: UpdateFilter = { $unset: { requirePasswordChange: true, requirePasswordChangeReason: true, @@ -2819,10 +2956,10 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - resetPasswordAndSetRequirePasswordChange(_id, requirePasswordChange, requirePasswordChangeReason) { + resetPasswordAndSetRequirePasswordChange(_id: IUser['_id'], requirePasswordChange: boolean, requirePasswordChangeReason: string) { const update = { $unset: { - 'services.password': 1, + 'services.password': 1 as const, }, $set: { requirePasswordChange, @@ -2833,7 +2970,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - setLanguage(_id, language) { + setLanguage(_id: IUser['_id'], language: string) { const update = { $set: { language, @@ -2843,7 +2980,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - setProfile(_id, profile) { + setProfile(_id: IUser['_id'], profile: Record) { const update = { $set: { 'settings.profile': profile, @@ -2853,8 +2990,8 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - setBio(_id, bio = '') { - const update = { + setBio(_id: IUser['_id'], bio = '') { + const update: UpdateFilter = { ...(bio.trim() ? { $set: { @@ -2870,8 +3007,8 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - setNickname(_id, nickname = '') { - const update = { + setNickname(_id: IUser['_id'], nickname = '') { + const update: UpdateFilter = { ...(nickname.trim() ? { $set: { @@ -2887,7 +3024,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - clearSettings(_id) { + clearSettings(_id: IUser['_id']) { const update = { $set: { settings: {}, @@ -2897,7 +3034,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - setPreferences(_id, preferences) { + setPreferences(_id: IUser['_id'], preferences: Record) { const settingsObject = Object.assign( {}, ...Object.keys(preferences).map((key) => ({ @@ -2905,18 +3042,18 @@ export class UsersRaw extends BaseRaw { })), ); - const update = { + const update: DeepWritable> = { $set: settingsObject, }; if (parseInt(preferences.clockMode) === 0) { - delete update.$set['settings.preferences.clockMode']; + delete update.$set?.['settings.preferences.clockMode']; update.$unset = { 'settings.preferences.clockMode': 1 }; } return this.updateOne({ _id }, update); } - setTwoFactorAuthorizationHashAndUntilForUserIdAndToken(_id, token, hash, until) { + setTwoFactorAuthorizationHashAndUntilForUserIdAndToken(_id: IUser['_id'], token: string, hash: string, until: Date) { return this.updateOne( { _id, @@ -2931,7 +3068,7 @@ export class UsersRaw extends BaseRaw { ); } - setUtcOffset(_id, utcOffset) { + setUtcOffset(_id: IUser['_id'], utcOffset: number) { const query = { _id, utcOffset: { @@ -2948,52 +3085,7 @@ export class UsersRaw extends BaseRaw { return this.updateOne(query, update); } - saveUserById(_id, data) { - const setData = {}; - const unsetData = {}; - - if (data.name != null) { - if (data.name.trim()) { - setData.name = data.name.trim(); - } else { - unsetData.name = 1; - } - } - - if (data.email != null) { - if (data.email.trim()) { - setData.emails = [{ address: data.email.trim() }]; - } else { - unsetData.emails = 1; - } - } - - if (data.phone != null) { - if (data.phone.trim()) { - setData.phone = [{ phoneNumber: data.phone.trim() }]; - } else { - unsetData.phone = 1; - } - } - - const update = {}; - - if (setData) { - update.$set = setData; - } - - if (unsetData) { - update.$unset = unsetData; - } - - if (update) { - return true; - } - - return this.updateOne({ _id }, update); - } - - setReason(_id, reason) { + setReason(_id: IUser['_id'], reason: string) { const update = { $set: { reason, @@ -3003,17 +3095,17 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - unsetReason(_id) { + unsetReason(_id: IUser['_id']) { const update = { $unset: { - reason: true, + reason: true as const, }, }; return this.updateOne({ _id }, update); } - async bannerExistsById(_id, bannerId) { + async bannerExistsById(_id: IUser['_id'], bannerId: string) { const query = { _id, [`banners.${bannerId}`]: { @@ -3024,7 +3116,7 @@ export class UsersRaw extends BaseRaw { return (await this.countDocuments(query)) !== 0; } - setBannerReadById(_id, bannerId) { + setBannerReadById(_id: IUser['_id'], bannerId: string) { const update = { $set: { [`banners.${bannerId}.read`]: true, @@ -3034,27 +3126,27 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - removeBannerById(_id, bannerId) { + removeBannerById(_id: IUser['_id'], bannerId: string) { const update = { $unset: { - [`banners.${bannerId}`]: true, + [`banners.${bannerId}`]: true as const, }, }; return this.updateOne({ _id }, update); } - removeSamlServiceSession(_id) { + removeSamlServiceSession(_id: IUser['_id']) { const update = { $unset: { - 'services.saml.idpSession': '', + 'services.saml.idpSession': 1 as const, }, }; return this.updateOne({ _id }, update); } - updateDefaultStatus(_id, statusDefault) { + updateDefaultStatus(_id: IUser['_id'], statusDefault: UserStatus) { return this.updateOne( { _id, @@ -3068,8 +3160,8 @@ export class UsersRaw extends BaseRaw { ); } - setSamlInResponseTo(_id, inResponseTo) { - this.updateOne( + setSamlInResponseTo(_id: IUser['_id'], inResponseTo: string) { + return this.updateOne( { _id, }, @@ -3081,7 +3173,7 @@ export class UsersRaw extends BaseRaw { ); } - async setFreeSwitchExtension(_id, extension) { + async setFreeSwitchExtension(_id: IUser['_id'], extension?: string) { return this.updateOne( { _id, @@ -3093,11 +3185,11 @@ export class UsersRaw extends BaseRaw { } // INSERT - create(data) { + create(data: InsertionModel) { const user = { createdAt: new Date(), avatarOrigin: 'none', - }; + } as InsertionModel; Object.assign(user, data); @@ -3105,18 +3197,18 @@ export class UsersRaw extends BaseRaw { } // REMOVE - removeById(_id) { + removeById(_id: IUser['_id']) { return this.deleteOne({ _id }); } - removeLivechatData(userId) { + removeLivechatData(userId: IUser['_id']) { const query = { _id: userId, }; const update = { $unset: { - livechat: true, + livechat: 1 as const, }, }; @@ -3130,15 +3222,15 @@ export class UsersRaw extends BaseRaw { - has not disabled email notifications - `active` is equal to true (false means they were deactivated and can't login) */ - getUsersToSendOfflineEmail(usersIds) { + getUsersToSendOfflineEmail(usersIds: IUser['_id'][]) { const query = { '_id': { $in: usersIds, }, 'active': true, - 'status': 'offline', + 'status': UserStatus.OFFLINE, 'statusConnection': { - $ne: 'online', + $ne: UserStatus.ONLINE, }, 'emails.verified': true, }; @@ -3156,7 +3248,7 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - countActiveUsersByService(serviceName, options) { + countActiveUsersByService(serviceName: string, options?: FindOptions) { const query = { active: true, type: { $nin: ['app'] }, @@ -3168,7 +3260,7 @@ export class UsersRaw extends BaseRaw { } // here - getActiveLocalUserCount() { + async getActiveLocalUserCount() { return Promise.all([ // Count all active users (fast based on index) this.countDocuments({ @@ -3188,8 +3280,8 @@ export class UsersRaw extends BaseRaw { return this.countActiveLocalGuests(idExceptions); } - removeOlderResumeTokensByUserId(userId, fromDate) { - this.updateOne( + removeOlderResumeTokensByUserId(userId: IUser['_id'], fromDate: Date) { + return this.updateOne( { _id: userId }, { $pull: { @@ -3229,7 +3321,7 @@ export class UsersRaw extends BaseRaw { return this.countDocuments(query); } - updateCustomFieldsById(userId, customFields) { + updateCustomFieldsById(userId: IUser['_id'], customFields: Record) { return this.updateOne( { _id: userId }, { @@ -3240,12 +3332,12 @@ export class UsersRaw extends BaseRaw { ); } - countRoomMembers(roomId) { + countRoomMembers(roomId: IRoom['_id']) { return this.countDocuments({ __rooms: roomId, active: true }); } - removeAgent(_id) { - const update = { + removeAgent(_id: IUser['_id']) { + const update: UpdateFilter = { $set: { operator: false, }, @@ -3260,11 +3352,11 @@ export class UsersRaw extends BaseRaw { return this.updateOne({ _id }, update); } - countByRole(role) { + countByRole(role: IRole['_id']) { return this.countDocuments({ roles: role }); } - updateLivechatStatusByAgentIds(userIds, status) { + updateLivechatStatusByAgentIds(userIds: IUser['_id'][], status: ILivechatAgentStatus) { return this.updateMany( { _id: { $in: userIds }, diff --git a/packages/models/tsconfig.json b/packages/models/tsconfig.json index e28418fe4a248..52e9dd8c4976b 100644 --- a/packages/models/tsconfig.json +++ b/packages/models/tsconfig.json @@ -1,11 +1,9 @@ { "extends": "../../tsconfig.base.server.json", "compilerOptions": { - // TODO migrate Users to TS - "allowJs": true, "declaration": true, "rootDir": "./src", - "outDir": "./dist", + "outDir": "./dist" }, "include": ["./src/**/*"] }