You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2897 lines
53 KiB
2897 lines
53 KiB
import { escapeRegExp } from '@rocket.chat/string-helpers';
|
|
import { Subscriptions } from '@rocket.chat/models';
|
|
|
|
import { BaseRaw } from './BaseRaw';
|
|
|
|
const queryStatusAgentOnline = (extraFilters = {}, isLivechatEnabledWhenAgentIdle) => ({
|
|
statusLivechat: 'available',
|
|
roles: 'livechat-agent',
|
|
...(!isLivechatEnabledWhenAgentIdle && {
|
|
$or: [
|
|
{
|
|
status: {
|
|
$exists: true,
|
|
$ne: 'offline',
|
|
},
|
|
roles: {
|
|
$ne: 'bot',
|
|
},
|
|
},
|
|
{
|
|
roles: 'bot',
|
|
},
|
|
],
|
|
}),
|
|
...extraFilters,
|
|
...(isLivechatEnabledWhenAgentIdle === false && {
|
|
statusConnection: { $ne: 'away' },
|
|
}),
|
|
});
|
|
|
|
export class UsersRaw extends BaseRaw {
|
|
constructor(db, trash) {
|
|
super(db, 'users', trash, {
|
|
collectionNameResolver(name) {
|
|
return name;
|
|
},
|
|
});
|
|
|
|
this.defaultFields = {
|
|
__rooms: 0,
|
|
};
|
|
}
|
|
|
|
// Move index from constructor to here
|
|
modelIndexes() {
|
|
return [
|
|
{ key: { __rooms: 1 }, sparse: 1 },
|
|
{ key: { roles: 1 }, sparse: 1 },
|
|
{ key: { name: 1 } },
|
|
{ key: { bio: 1 }, sparse: 1 },
|
|
{ key: { nickname: 1 }, sparse: 1 },
|
|
{ key: { createdAt: 1 } },
|
|
{ key: { lastLogin: 1 } },
|
|
{ key: { status: 1 } },
|
|
{ key: { statusText: 1 } },
|
|
{ key: { active: 1 }, sparse: 1 },
|
|
{ key: { statusConnection: 1 }, sparse: 1 },
|
|
{ key: { appId: 1 }, sparse: 1 },
|
|
{ key: { type: 1 } },
|
|
{ key: { federation: 1 }, sparse: true },
|
|
{ key: { isRemote: 1 }, sparse: true },
|
|
{ key: { 'services.saml.inResponseTo': 1 } },
|
|
{ key: { openBusinessHours: 1 }, sparse: true },
|
|
{ key: { statusLivechat: 1 }, sparse: true },
|
|
{ key: { extension: 1 }, sparse: true, unique: true },
|
|
{ key: { language: 1 }, sparse: true },
|
|
{ key: { 'active': 1, 'services.email2fa.enabled': 1 }, sparse: true }, // used by statistics
|
|
{ key: { 'active': 1, 'services.totp.enabled': 1 }, sparse: true }, // used by statistics
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param {string} uid
|
|
* @param {IRole['_id'][]} roles list of role ids
|
|
*/
|
|
addRolesByUserId(uid, roles) {
|
|
if (!Array.isArray(roles)) {
|
|
roles = [roles];
|
|
process.env.NODE_ENV === 'development' && console.warn('[WARN] Users.addRolesByUserId: roles should be an array');
|
|
}
|
|
|
|
const query = {
|
|
_id: uid,
|
|
};
|
|
|
|
const update = {
|
|
$addToSet: {
|
|
roles: { $each: roles },
|
|
},
|
|
};
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
/**
|
|
* @param {IRole['_id'][]} roles list of role ids
|
|
* @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);
|
|
|
|
const query = {
|
|
roles: { $in: roles },
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findPaginatedUsersInRoles(roles, options) {
|
|
roles = [].concat(roles);
|
|
|
|
const query = {
|
|
roles: { $in: roles },
|
|
};
|
|
|
|
return this.findPaginated(query, options);
|
|
}
|
|
|
|
findOneByUsername(username, options = null) {
|
|
const query = { username };
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneAgentById(_id, options) {
|
|
const query = {
|
|
_id,
|
|
roles: 'livechat-agent',
|
|
};
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
/**
|
|
* @param {IRole['_id'][] | IRole['_id']} roles the list of role ids
|
|
* @param {any} query
|
|
* @param {any} options
|
|
*/
|
|
findUsersInRolesWithQuery(roles, query, options) {
|
|
roles = [].concat(roles);
|
|
|
|
Object.assign(query, { roles: { $in: roles } });
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
/**
|
|
* @param {IRole['_id'][] | IRole['_id']} roles the list of role ids
|
|
* @param {any} query
|
|
* @param {any} options
|
|
*/
|
|
findPaginatedUsersInRolesWithQuery(roles, query, options) {
|
|
roles = [].concat(roles);
|
|
|
|
Object.assign(query, { roles: { $in: roles } });
|
|
|
|
return this.findPaginated(query, options);
|
|
}
|
|
|
|
findOneByUsernameAndRoomIgnoringCase(username, rid, options) {
|
|
if (typeof username === 'string') {
|
|
username = new RegExp(`^${escapeRegExp(username)}$`, 'i');
|
|
}
|
|
|
|
const query = {
|
|
__rooms: rid,
|
|
username,
|
|
};
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneByIdAndLoginHashedToken(_id, token, options = {}) {
|
|
const query = {
|
|
_id,
|
|
'services.resume.loginTokens.hashedToken': token,
|
|
};
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findByActiveUsersExcept(searchTerm, exceptions, options, searchFields, extraQuery = [], { startsWith = false, endsWith = false } = {}) {
|
|
if (exceptions == null) {
|
|
exceptions = [];
|
|
}
|
|
if (options == null) {
|
|
options = {};
|
|
}
|
|
if (!Array.isArray(exceptions)) {
|
|
exceptions = [exceptions];
|
|
}
|
|
|
|
const termRegex = new RegExp((startsWith ? '^' : '') + escapeRegExp(searchTerm) + (endsWith ? '$' : ''), 'i');
|
|
|
|
const orStmt = (searchFields || []).reduce(function (acc, el) {
|
|
acc.push({ [el.trim()]: termRegex });
|
|
return acc;
|
|
}, []);
|
|
|
|
const query = {
|
|
$and: [
|
|
{
|
|
active: true,
|
|
username: {
|
|
$exists: true,
|
|
...(exceptions.length > 0 && { $nin: exceptions }),
|
|
},
|
|
// if the search term is empty, don't need to have the $or statement (because it would be an empty regex)
|
|
...(searchTerm && orStmt.length > 0 && { $or: orStmt }),
|
|
},
|
|
...extraQuery,
|
|
],
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findPaginatedByActiveUsersExcept(
|
|
searchTerm,
|
|
exceptions,
|
|
options,
|
|
searchFields,
|
|
extraQuery = [],
|
|
{ startsWith = false, endsWith = false } = {},
|
|
) {
|
|
if (exceptions == null) {
|
|
exceptions = [];
|
|
}
|
|
if (options == null) {
|
|
options = {};
|
|
}
|
|
if (!Array.isArray(exceptions)) {
|
|
exceptions = [exceptions];
|
|
}
|
|
|
|
const termRegex = new RegExp((startsWith ? '^' : '') + escapeRegExp(searchTerm) + (endsWith ? '$' : ''), 'i');
|
|
|
|
const orStmt = (searchFields || []).reduce(function (acc, el) {
|
|
acc.push({ [el.trim()]: termRegex });
|
|
return acc;
|
|
}, []);
|
|
|
|
const query = {
|
|
$and: [
|
|
{
|
|
active: true,
|
|
username: {
|
|
$exists: true,
|
|
...(exceptions.length > 0 && { $nin: exceptions }),
|
|
},
|
|
// if the search term is empty, don't need to have the $or statement (because it would be an empty regex)
|
|
...(searchTerm && orStmt.length > 0 && { $or: orStmt }),
|
|
},
|
|
...extraQuery,
|
|
],
|
|
};
|
|
|
|
return this.findPaginated(query, options);
|
|
}
|
|
|
|
findPaginatedByActiveLocalUsersExcept(searchTerm, exceptions, options, forcedSearchFields, localDomain) {
|
|
const extraQuery = [
|
|
{
|
|
$or: [{ federation: { $exists: false } }, { 'federation.origin': localDomain }],
|
|
},
|
|
];
|
|
return this.findPaginatedByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery);
|
|
}
|
|
|
|
findPaginatedByActiveExternalUsersExcept(searchTerm, exceptions, options, forcedSearchFields, localDomain) {
|
|
const extraQuery = [{ federation: { $exists: true } }, { 'federation.origin': { $ne: localDomain } }];
|
|
return this.findPaginatedByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery);
|
|
}
|
|
|
|
findActive(query, options = {}) {
|
|
Object.assign(query, { active: true });
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findActiveByIds(userIds, options = {}) {
|
|
const query = {
|
|
_id: { $in: userIds },
|
|
active: true,
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findActiveByIdsOrUsernames(userIds, options = {}) {
|
|
const query = {
|
|
$or: [{ _id: { $in: userIds } }, { username: { $in: userIds } }],
|
|
active: true,
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findByIds(userIds, options = {}) {
|
|
const query = {
|
|
_id: { $in: userIds },
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findOneByImportId(_id, options) {
|
|
return this.findOne({ importIds: _id }, options);
|
|
}
|
|
|
|
findOneByUsernameIgnoringCase(username, options) {
|
|
if (typeof username === 'string') {
|
|
username = new RegExp(`^${escapeRegExp(username)}$`, 'i');
|
|
}
|
|
|
|
const query = { username };
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneWithoutLDAPByUsernameIgnoringCase(username, options) {
|
|
const expression = new RegExp(`^${escapeRegExp(username)}$`, 'i');
|
|
|
|
const query = {
|
|
'username': expression,
|
|
'services.ldap': {
|
|
$exists: false,
|
|
},
|
|
};
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
async findOneByLDAPId(id, attribute = undefined) {
|
|
const query = {
|
|
'services.ldap.id': id,
|
|
};
|
|
|
|
if (attribute) {
|
|
query['services.ldap.idAttribute'] = attribute;
|
|
}
|
|
|
|
return this.findOne(query);
|
|
}
|
|
|
|
async findOneByAppId(appId, options) {
|
|
const query = { appId };
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findLDAPUsers(options) {
|
|
const query = { ldap: true };
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findConnectedLDAPUsers(options) {
|
|
const query = {
|
|
'ldap': true,
|
|
'services.resume.loginTokens': {
|
|
$exists: true,
|
|
$ne: [],
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
isUserInRole(userId, roleId) {
|
|
const query = {
|
|
_id: userId,
|
|
roles: roleId,
|
|
};
|
|
|
|
return this.findOne(query, { projection: { roles: 1 } });
|
|
}
|
|
|
|
getDistinctFederationDomains() {
|
|
return this.col.distinct('federation.origin', { federation: { $exists: true } });
|
|
}
|
|
|
|
async getNextLeastBusyAgent(department, ignoreAgentId) {
|
|
const aggregate = [
|
|
{
|
|
$match: {
|
|
status: { $exists: true, $ne: 'offline' },
|
|
statusLivechat: 'available',
|
|
roles: 'livechat-agent',
|
|
...(ignoreAgentId && { _id: { $ne: ignoreAgentId } }),
|
|
},
|
|
},
|
|
{
|
|
$lookup: {
|
|
from: 'rocketchat_subscription',
|
|
let: { id: '$_id' },
|
|
pipeline: [
|
|
{
|
|
$match: {
|
|
$expr: {
|
|
$and: [
|
|
{ $eq: ['$u._id', '$$id'] },
|
|
{ $eq: ['$open', true] },
|
|
{ $ne: ['$onHold', true] },
|
|
{ ...(department && { $eq: ['$department', department] }) },
|
|
],
|
|
},
|
|
},
|
|
},
|
|
],
|
|
as: 'subs',
|
|
},
|
|
},
|
|
{
|
|
$lookup: {
|
|
from: 'rocketchat_livechat_department_agents',
|
|
localField: '_id',
|
|
foreignField: 'agentId',
|
|
as: 'departments',
|
|
},
|
|
},
|
|
{
|
|
$project: {
|
|
agentId: '$_id',
|
|
username: 1,
|
|
lastRoutingTime: 1,
|
|
departments: 1,
|
|
count: { $size: '$subs' },
|
|
},
|
|
},
|
|
{ $sort: { count: 1, lastRoutingTime: 1, username: 1 } },
|
|
];
|
|
|
|
if (department) {
|
|
aggregate.push({ $unwind: '$departments' });
|
|
aggregate.push({ $match: { 'departments.departmentId': department } });
|
|
}
|
|
|
|
aggregate.push({ $limit: 1 });
|
|
|
|
const [agent] = await this.col.aggregate(aggregate).toArray();
|
|
if (agent) {
|
|
await this.setLastRoutingTime(agent.agentId);
|
|
}
|
|
|
|
return agent;
|
|
}
|
|
|
|
async getLastAvailableAgentRouted(department, ignoreAgentId) {
|
|
const aggregate = [
|
|
{
|
|
$match: {
|
|
status: { $exists: true, $ne: 'offline' },
|
|
statusLivechat: 'available',
|
|
roles: 'livechat-agent',
|
|
...(ignoreAgentId && { _id: { $ne: ignoreAgentId } }),
|
|
},
|
|
},
|
|
{
|
|
$lookup: {
|
|
from: 'rocketchat_livechat_department_agents',
|
|
localField: '_id',
|
|
foreignField: 'agentId',
|
|
as: 'departments',
|
|
},
|
|
},
|
|
{ $project: { agentId: '$_id', username: 1, lastRoutingTime: 1, departments: 1 } },
|
|
{ $sort: { lastRoutingTime: 1, username: 1 } },
|
|
];
|
|
|
|
if (department) {
|
|
aggregate.push({ $unwind: '$departments' });
|
|
aggregate.push({ $match: { 'departments.departmentId': department } });
|
|
}
|
|
|
|
aggregate.push({ $limit: 1 });
|
|
|
|
const [agent] = await this.col.aggregate(aggregate).toArray();
|
|
if (agent) {
|
|
await this.setLastRoutingTime(agent.agentId);
|
|
}
|
|
|
|
return agent;
|
|
}
|
|
|
|
async setLastRoutingTime(userId) {
|
|
const result = await this.findOneAndUpdate(
|
|
{ _id: userId },
|
|
{
|
|
$set: {
|
|
lastRoutingTime: new Date(),
|
|
},
|
|
},
|
|
{ returnDocument: 'after' },
|
|
);
|
|
return result.value;
|
|
}
|
|
|
|
setLivechatStatusIf(userId, status, conditions = {}, extraFields = {}) {
|
|
// TODO: Create class Agent
|
|
const query = {
|
|
_id: userId,
|
|
...conditions,
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
statusLivechat: status,
|
|
...extraFields,
|
|
},
|
|
};
|
|
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
async getAgentAndAmountOngoingChats(userId) {
|
|
const aggregate = [
|
|
{
|
|
$match: {
|
|
_id: userId,
|
|
statusLivechat: 'available',
|
|
roles: 'livechat-agent',
|
|
},
|
|
},
|
|
{
|
|
$lookup: {
|
|
from: 'rocketchat_subscription',
|
|
localField: '_id',
|
|
foreignField: 'u._id',
|
|
as: 'subs',
|
|
},
|
|
},
|
|
{
|
|
$project: {
|
|
'agentId': '$_id',
|
|
'username': 1,
|
|
'lastAssignTime': 1,
|
|
'lastRoutingTime': 1,
|
|
'queueInfo.chats': {
|
|
$size: {
|
|
$filter: {
|
|
input: '$subs',
|
|
as: 'sub',
|
|
cond: {
|
|
$and: [{ $eq: ['$$sub.t', 'l'] }, { $eq: ['$$sub.open', true] }, { $ne: ['$$sub.onHold', true] }],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{ $sort: { 'queueInfo.chats': 1, 'lastAssignTime': 1, 'lastRoutingTime': 1, 'username': 1 } },
|
|
];
|
|
|
|
const [agent] = await this.col.aggregate(aggregate).toArray();
|
|
return agent;
|
|
}
|
|
|
|
findAllResumeTokensByUserId(userId) {
|
|
return this.col
|
|
.aggregate([
|
|
{
|
|
$match: {
|
|
_id: userId,
|
|
},
|
|
},
|
|
{
|
|
$project: {
|
|
tokens: {
|
|
$filter: {
|
|
input: '$services.resume.loginTokens',
|
|
as: 'token',
|
|
cond: {
|
|
$ne: ['$$token.type', 'personalAccessToken'],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{ $unwind: '$tokens' },
|
|
{ $sort: { 'tokens.when': 1 } },
|
|
{ $group: { _id: '$_id', tokens: { $push: '$tokens' } } },
|
|
])
|
|
.toArray();
|
|
}
|
|
|
|
findActiveByUsernameOrNameRegexWithExceptionsAndConditions(termRegex, exceptions, conditions, options) {
|
|
if (exceptions == null) {
|
|
exceptions = [];
|
|
}
|
|
if (conditions == null) {
|
|
conditions = {};
|
|
}
|
|
if (options == null) {
|
|
options = {};
|
|
}
|
|
if (!Array.isArray(exceptions)) {
|
|
exceptions = [exceptions];
|
|
}
|
|
|
|
const query = {
|
|
$or: [
|
|
{
|
|
username: termRegex,
|
|
},
|
|
{
|
|
name: termRegex,
|
|
},
|
|
{
|
|
nickname: termRegex,
|
|
},
|
|
],
|
|
active: true,
|
|
type: {
|
|
$in: ['user', 'bot'],
|
|
},
|
|
$and: [
|
|
{
|
|
username: {
|
|
$exists: true,
|
|
},
|
|
},
|
|
{
|
|
username: {
|
|
$nin: exceptions,
|
|
},
|
|
},
|
|
],
|
|
...conditions,
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
countAllAgentsStatus({ departmentId = undefined }) {
|
|
const match = {
|
|
$match: {
|
|
roles: { $in: ['livechat-agent'] },
|
|
},
|
|
};
|
|
const group = {
|
|
$group: {
|
|
_id: null,
|
|
offline: {
|
|
$sum: {
|
|
$cond: [
|
|
{
|
|
$or: [
|
|
{
|
|
$and: [{ $eq: ['$status', 'offline'] }, { $eq: ['$statusLivechat', 'available'] }],
|
|
},
|
|
{ $eq: ['$statusLivechat', 'not-available'] },
|
|
],
|
|
},
|
|
1,
|
|
0,
|
|
],
|
|
},
|
|
},
|
|
away: {
|
|
$sum: {
|
|
$cond: [
|
|
{
|
|
$and: [{ $eq: ['$status', 'away'] }, { $eq: ['$statusLivechat', 'available'] }],
|
|
},
|
|
1,
|
|
0,
|
|
],
|
|
},
|
|
},
|
|
busy: {
|
|
$sum: {
|
|
$cond: [
|
|
{
|
|
$and: [{ $eq: ['$status', 'busy'] }, { $eq: ['$statusLivechat', 'available'] }],
|
|
},
|
|
1,
|
|
0,
|
|
],
|
|
},
|
|
},
|
|
available: {
|
|
$sum: {
|
|
$cond: [
|
|
{
|
|
$and: [{ $eq: ['$status', 'online'] }, { $eq: ['$statusLivechat', 'available'] }],
|
|
},
|
|
1,
|
|
0,
|
|
],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
const lookup = {
|
|
$lookup: {
|
|
from: 'rocketchat_livechat_department_agents',
|
|
localField: '_id',
|
|
foreignField: 'agentId',
|
|
as: 'departments',
|
|
},
|
|
};
|
|
const unwind = {
|
|
$unwind: {
|
|
path: '$departments',
|
|
preserveNullAndEmptyArrays: true,
|
|
},
|
|
};
|
|
const departmentsMatch = {
|
|
$match: {
|
|
'departments.departmentId': departmentId,
|
|
},
|
|
};
|
|
const params = [match];
|
|
if (departmentId && departmentId !== 'undefined') {
|
|
params.push(lookup);
|
|
params.push(unwind);
|
|
params.push(departmentsMatch);
|
|
}
|
|
params.push(group);
|
|
return this.col.aggregate(params).toArray();
|
|
}
|
|
|
|
getTotalOfRegisteredUsersByDate({ start, end, options = {} }) {
|
|
const params = [
|
|
{
|
|
$match: {
|
|
createdAt: { $gte: start, $lte: end },
|
|
roles: { $ne: 'anonymous' },
|
|
},
|
|
},
|
|
{
|
|
$group: {
|
|
_id: {
|
|
$concat: [{ $substr: ['$createdAt', 0, 4] }, { $substr: ['$createdAt', 5, 2] }, { $substr: ['$createdAt', 8, 2] }],
|
|
},
|
|
users: { $sum: 1 },
|
|
},
|
|
},
|
|
{
|
|
$group: {
|
|
_id: '$_id',
|
|
users: { $sum: '$users' },
|
|
},
|
|
},
|
|
{
|
|
$project: {
|
|
_id: 0,
|
|
date: '$_id',
|
|
users: 1,
|
|
type: 'users',
|
|
},
|
|
},
|
|
];
|
|
if (options.sort) {
|
|
params.push({ $sort: options.sort });
|
|
}
|
|
if (options.count) {
|
|
params.push({ $limit: options.count });
|
|
}
|
|
return this.col.aggregate(params).toArray();
|
|
}
|
|
|
|
getUserLanguages() {
|
|
const pipeline = [
|
|
{
|
|
$match: {
|
|
language: {
|
|
$exists: true,
|
|
$ne: '',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
$group: {
|
|
_id: '$language',
|
|
total: { $sum: 1 },
|
|
},
|
|
},
|
|
];
|
|
|
|
return this.col.aggregate(pipeline).toArray();
|
|
}
|
|
|
|
updateStatusText(_id, statusText) {
|
|
const update = {
|
|
$set: {
|
|
statusText,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
updateStatusByAppId(appId, status) {
|
|
const query = {
|
|
appId,
|
|
status: { $ne: status },
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
status,
|
|
},
|
|
};
|
|
|
|
return this.updateMany(query, update);
|
|
}
|
|
|
|
/**
|
|
* @param {string} userId
|
|
* @param {object} status
|
|
* @param {string} status.status
|
|
* @param {string} status.statusConnection
|
|
* @param {string} [status.statusDefault]
|
|
* @param {string} [status.statusText]
|
|
*/
|
|
updateStatusById(userId, { statusDefault, status, statusConnection, statusText }) {
|
|
const query = {
|
|
_id: userId,
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
status,
|
|
statusConnection,
|
|
...(statusDefault && { statusDefault }),
|
|
...(statusText && {
|
|
statusText: String(statusText).trim().substr(0, 120),
|
|
}),
|
|
},
|
|
};
|
|
|
|
// We don't want to update the _updatedAt field on this operation,
|
|
// so we can check if the status update triggered a change
|
|
return this.col.updateOne(query, update);
|
|
}
|
|
|
|
openAgentsBusinessHoursByBusinessHourId(businessHourIds) {
|
|
const query = {
|
|
roles: 'livechat-agent',
|
|
};
|
|
|
|
const update = {
|
|
$addToSet: {
|
|
openBusinessHours: { $each: businessHourIds },
|
|
},
|
|
};
|
|
|
|
return this.updateMany(query, update);
|
|
}
|
|
|
|
openAgentBusinessHoursByBusinessHourIdsAndAgentId(businessHourIds, agentId) {
|
|
const query = {
|
|
_id: agentId,
|
|
roles: 'livechat-agent',
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
statusLivechat: 'available',
|
|
},
|
|
$addToSet: {
|
|
openBusinessHours: { $each: businessHourIds },
|
|
},
|
|
};
|
|
|
|
return this.updateMany(query, update);
|
|
}
|
|
|
|
addBusinessHourByAgentIds(agentIds = [], businessHourId) {
|
|
const query = {
|
|
_id: { $in: agentIds },
|
|
roles: 'livechat-agent',
|
|
};
|
|
|
|
const update = {
|
|
$addToSet: {
|
|
openBusinessHours: businessHourId,
|
|
},
|
|
};
|
|
|
|
return this.updateMany(query, update);
|
|
}
|
|
|
|
removeBusinessHourByAgentIds(agentIds = [], businessHourId) {
|
|
const query = {
|
|
_id: { $in: agentIds },
|
|
roles: 'livechat-agent',
|
|
};
|
|
|
|
const update = {
|
|
$pull: {
|
|
openBusinessHours: businessHourId,
|
|
},
|
|
};
|
|
|
|
return this.updateMany(query, update);
|
|
}
|
|
|
|
openBusinessHourToAgentsWithoutDepartment(agentIdsWithDepartment = [], businessHourId) {
|
|
const query = {
|
|
_id: { $nin: agentIdsWithDepartment },
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
statusLivechat: 'available',
|
|
},
|
|
$addToSet: {
|
|
openBusinessHours: businessHourId,
|
|
},
|
|
};
|
|
|
|
return this.updateMany(query, update);
|
|
}
|
|
|
|
closeBusinessHourToAgentsWithoutDepartment(agentIdsWithDepartment = [], businessHourId) {
|
|
const query = {
|
|
_id: { $nin: agentIdsWithDepartment },
|
|
};
|
|
|
|
const update = {
|
|
$pull: {
|
|
openBusinessHours: businessHourId,
|
|
},
|
|
};
|
|
|
|
return this.updateMany(query, update);
|
|
}
|
|
|
|
closeAgentsBusinessHoursByBusinessHourIds(businessHourIds) {
|
|
const query = {
|
|
roles: 'livechat-agent',
|
|
};
|
|
|
|
const update = {
|
|
$pull: {
|
|
openBusinessHours: { $in: businessHourIds },
|
|
},
|
|
};
|
|
|
|
return this.updateMany(query, update);
|
|
}
|
|
|
|
updateLivechatStatusBasedOnBusinessHours(userIds = []) {
|
|
const query = {
|
|
$or: [{ openBusinessHours: { $exists: false } }, { openBusinessHours: { $size: 0 } }],
|
|
roles: 'livechat-agent',
|
|
...(Array.isArray(userIds) && userIds.length > 0 && { _id: { $in: userIds } }),
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
statusLivechat: 'not-available',
|
|
},
|
|
};
|
|
|
|
return this.updateMany(query, update);
|
|
}
|
|
|
|
setLivechatStatusActiveBasedOnBusinessHours(userId) {
|
|
const query = {
|
|
_id: userId,
|
|
statusDefault: { $ne: 'offline' },
|
|
openBusinessHours: {
|
|
$exists: true,
|
|
$not: { $size: 0 },
|
|
},
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
statusLivechat: 'available',
|
|
},
|
|
};
|
|
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
async isAgentWithinBusinessHours(agentId) {
|
|
const query = {
|
|
_id: agentId,
|
|
openBusinessHours: {
|
|
$exists: true,
|
|
$not: { $size: 0 },
|
|
},
|
|
};
|
|
return (await this.col.countDocuments(query)) > 0;
|
|
}
|
|
|
|
removeBusinessHoursFromAllUsers() {
|
|
const query = {
|
|
roles: 'livechat-agent',
|
|
openBusinessHours: {
|
|
$exists: true,
|
|
},
|
|
};
|
|
|
|
const update = {
|
|
$unset: {
|
|
openBusinessHours: 1,
|
|
},
|
|
};
|
|
|
|
return this.updateMany(query, update);
|
|
}
|
|
|
|
resetTOTPById(userId) {
|
|
return this.col.updateOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
$unset: {
|
|
'services.totp': 1,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
unsetOneLoginToken(_id, token) {
|
|
const update = {
|
|
$pull: {
|
|
'services.resume.loginTokens': { hashedToken: token },
|
|
},
|
|
};
|
|
|
|
return this.col.updateOne({ _id }, update);
|
|
}
|
|
|
|
unsetLoginTokens(userId) {
|
|
return this.col.updateOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
$set: {
|
|
'services.resume.loginTokens': [],
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
removeNonPATLoginTokensExcept(userId, authToken) {
|
|
return this.col.updateOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
$pull: {
|
|
'services.resume.loginTokens': {
|
|
when: { $exists: true },
|
|
hashedToken: { $ne: authToken },
|
|
},
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
removeRoomsByRoomIdsAndUserId(rids, userId) {
|
|
return this.updateMany(
|
|
{
|
|
_id: userId,
|
|
__rooms: { $in: rids },
|
|
},
|
|
{
|
|
$pullAll: { __rooms: rids },
|
|
},
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param {string} uid
|
|
* @param {IRole['_id']} roles the list of role ids to remove
|
|
*/
|
|
removeRolesByUserId(uid, roles) {
|
|
const query = {
|
|
_id: uid,
|
|
};
|
|
|
|
const update = {
|
|
$pullAll: {
|
|
roles,
|
|
},
|
|
};
|
|
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
async isUserInRoleScope(uid) {
|
|
const query = {
|
|
_id: uid,
|
|
};
|
|
|
|
const options = {
|
|
projection: { _id: 1 },
|
|
};
|
|
|
|
const found = await this.findOne(query, options);
|
|
return !!found;
|
|
}
|
|
|
|
addBannerById(_id, banner) {
|
|
const query = {
|
|
_id,
|
|
[`banners.${banner.id}.read`]: {
|
|
$ne: true,
|
|
},
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
[`banners.${banner.id}`]: banner,
|
|
},
|
|
};
|
|
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
// Voip functions
|
|
findOneByAgentUsername(username, options) {
|
|
const query = { username, roles: 'livechat-agent' };
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneByExtension(extension, options) {
|
|
const query = {
|
|
extension,
|
|
};
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findByExtensions(extensions, options) {
|
|
const query = {
|
|
extension: {
|
|
$in: extensions,
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
getVoipExtensionByUserId(userId, options) {
|
|
const query = {
|
|
_id: userId,
|
|
extension: { $exists: true },
|
|
};
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
setExtension(userId, extension) {
|
|
const query = {
|
|
_id: userId,
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
extension,
|
|
},
|
|
};
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
unsetExtension(userId) {
|
|
const query = {
|
|
_id: userId,
|
|
};
|
|
const update = {
|
|
$unset: {
|
|
extension: true,
|
|
},
|
|
};
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
getAvailableAgentsIncludingExt(includeExt, text, options) {
|
|
const query = {
|
|
roles: { $in: ['livechat-agent'] },
|
|
$and: [
|
|
...(text && 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);
|
|
}
|
|
|
|
findActiveUsersTOTPEnable(options) {
|
|
const query = {
|
|
'active': true,
|
|
'services.totp.enabled': true,
|
|
};
|
|
return this.find(query, options);
|
|
}
|
|
|
|
countActiveUsersTOTPEnable(options) {
|
|
const query = {
|
|
'active': true,
|
|
'services.totp.enabled': true,
|
|
};
|
|
return this.col.countDocuments(query, options);
|
|
}
|
|
|
|
findActiveUsersEmail2faEnable(options) {
|
|
const query = {
|
|
'active': true,
|
|
'services.email2fa.enabled': true,
|
|
};
|
|
return this.find(query, options);
|
|
}
|
|
|
|
countActiveUsersEmail2faEnable(options) {
|
|
const query = {
|
|
'active': true,
|
|
'services.email2fa.enabled': true,
|
|
};
|
|
return this.col.countDocuments(query, options);
|
|
}
|
|
|
|
setAsFederated(uid) {
|
|
const query = {
|
|
_id: uid,
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
federated: true,
|
|
},
|
|
};
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
removeRoomByRoomId(rid) {
|
|
return this.updateMany(
|
|
{
|
|
__rooms: rid,
|
|
},
|
|
{
|
|
$pull: { __rooms: rid },
|
|
},
|
|
);
|
|
}
|
|
|
|
findOneByResetToken(token, options) {
|
|
return this.findOne({ 'services.password.reset.token': token }, options);
|
|
}
|
|
|
|
setFederationAvatarUrlById(userId, federationAvatarUrl) {
|
|
return this.updateOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
$set: {
|
|
'federation.avatarUrl': federationAvatarUrl,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
async findSearchedServerNamesByUserId(userId) {
|
|
const user = await this.findOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
projection: {
|
|
'federation.searchedServerNames': 1,
|
|
},
|
|
},
|
|
);
|
|
|
|
return user.federation?.searchedServerNames || [];
|
|
}
|
|
|
|
addServerNameToSearchedServerNamesList(userId, serverName) {
|
|
return this.updateOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
$addToSet: {
|
|
'federation.searchedServerNames': serverName,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
removeServerNameFromSearchedServerNamesList(userId, serverName) {
|
|
return this.updateOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
$pull: {
|
|
'federation.searchedServerNames': serverName,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
countFederatedExternalUsers() {
|
|
return this.col.countDocuments({
|
|
federated: true,
|
|
});
|
|
}
|
|
|
|
findOnlineUserFromList(userList, isLivechatEnabledWhenAgentIdle) {
|
|
// TODO: Create class Agent
|
|
const username = {
|
|
$in: [].concat(userList),
|
|
};
|
|
|
|
const query = queryStatusAgentOnline({ username }, isLivechatEnabledWhenAgentIdle);
|
|
|
|
return this.find(query);
|
|
}
|
|
|
|
findOneOnlineAgentByUserList(userList, options, isLivechatEnabledWhenAgentIdle) {
|
|
// TODO:: Create class Agent
|
|
const username = {
|
|
$in: [].concat(userList),
|
|
};
|
|
|
|
const query = queryStatusAgentOnline({ username }, isLivechatEnabledWhenAgentIdle);
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
getUnavailableAgents() {
|
|
return [];
|
|
}
|
|
|
|
findBotAgents(usernameList) {
|
|
// TODO:: Create class Agent
|
|
const query = {
|
|
roles: {
|
|
$all: ['bot', 'livechat-agent'],
|
|
},
|
|
...(usernameList && {
|
|
username: {
|
|
$in: [].concat(usernameList),
|
|
},
|
|
}),
|
|
};
|
|
|
|
return this.find(query);
|
|
}
|
|
|
|
removeAllRoomsByUserId(_id) {
|
|
return this.updateOne(
|
|
{
|
|
_id,
|
|
},
|
|
{
|
|
$set: { __rooms: [] },
|
|
},
|
|
);
|
|
}
|
|
|
|
removeRoomByUserId(_id, rid) {
|
|
return this.updateOne(
|
|
{
|
|
_id,
|
|
__rooms: rid,
|
|
},
|
|
{
|
|
$pull: { __rooms: rid },
|
|
},
|
|
);
|
|
}
|
|
|
|
addRoomByUserId(_id, rid) {
|
|
return this.updateOne(
|
|
{
|
|
_id,
|
|
__rooms: { $ne: rid },
|
|
},
|
|
{
|
|
$addToSet: { __rooms: rid },
|
|
},
|
|
);
|
|
}
|
|
|
|
removeRoomByRoomIds(rids) {
|
|
return this.updateMany(
|
|
{
|
|
__rooms: { $in: rids },
|
|
},
|
|
{
|
|
$pullAll: { __rooms: rids },
|
|
},
|
|
);
|
|
}
|
|
|
|
getLoginTokensByUserId(userId) {
|
|
const query = {
|
|
'services.resume.loginTokens.type': {
|
|
$exists: true,
|
|
$eq: 'personalAccessToken',
|
|
},
|
|
'_id': userId,
|
|
};
|
|
|
|
return this.find(query, { projection: { 'services.resume.loginTokens': 1 } });
|
|
}
|
|
|
|
addPersonalAccessTokenToUser({ userId, loginTokenObject }) {
|
|
return this.updateOne(
|
|
{ _id: userId },
|
|
{
|
|
$push: {
|
|
'services.resume.loginTokens': loginTokenObject,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
removePersonalAccessTokenOfUser({ userId, loginTokenObject }) {
|
|
return this.updateOne(
|
|
{ _id: userId },
|
|
{
|
|
$pull: {
|
|
'services.resume.loginTokens': loginTokenObject,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
findPersonalAccessTokenByTokenNameAndUserId({ userId, tokenName }) {
|
|
const query = {
|
|
'services.resume.loginTokens': {
|
|
$elemMatch: { name: tokenName, type: 'personalAccessToken' },
|
|
},
|
|
'_id': userId,
|
|
};
|
|
|
|
return this.findOne(query);
|
|
}
|
|
|
|
setOperator(_id, operator) {
|
|
// TODO:: Create class Agent
|
|
const update = {
|
|
$set: {
|
|
operator,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
async checkOnlineAgents(agentId) {
|
|
// TODO:: Create class Agent
|
|
const query = queryStatusAgentOnline(agentId && { _id: agentId });
|
|
|
|
return !!(await this.findOne(query));
|
|
}
|
|
|
|
findOnlineAgents(agentId) {
|
|
// TODO:: Create class Agent
|
|
const query = queryStatusAgentOnline(agentId && { _id: agentId });
|
|
|
|
return this.find(query);
|
|
}
|
|
|
|
countOnlineAgents(agentId) {
|
|
// TODO:: Create class Agent
|
|
const query = queryStatusAgentOnline(agentId && { _id: agentId });
|
|
|
|
return this.col.countDocuments(query);
|
|
}
|
|
|
|
findOneBotAgent() {
|
|
// TODO:: Create class Agent
|
|
const query = {
|
|
roles: {
|
|
$all: ['bot', 'livechat-agent'],
|
|
},
|
|
};
|
|
|
|
return this.findOne(query);
|
|
}
|
|
|
|
findOneOnlineAgentById(_id, isLivechatEnabledWhenAgentIdle) {
|
|
// TODO: Create class Agent
|
|
const query = queryStatusAgentOnline({ _id }, isLivechatEnabledWhenAgentIdle);
|
|
|
|
return this.findOne(query);
|
|
}
|
|
|
|
findAgents() {
|
|
// TODO: Create class Agent
|
|
const query = {
|
|
roles: 'livechat-agent',
|
|
};
|
|
|
|
return this.find(query);
|
|
}
|
|
|
|
countAgents() {
|
|
// TODO: Create class Agent
|
|
const query = {
|
|
roles: 'livechat-agent',
|
|
};
|
|
|
|
return this.col.countDocuments(query);
|
|
}
|
|
|
|
// 2
|
|
async getNextAgent(ignoreAgentId, extraQuery) {
|
|
// 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 extraFilters = {
|
|
...(ignoreAgentId && { _id: { $ne: ignoreAgentId } }),
|
|
// limit query to remove booked agents
|
|
username: { $nin: unavailableAgents },
|
|
};
|
|
|
|
const query = queryStatusAgentOnline(extraFilters);
|
|
|
|
const sort = {
|
|
livechatCount: 1,
|
|
username: 1,
|
|
};
|
|
|
|
const update = {
|
|
$inc: {
|
|
livechatCount: 1,
|
|
},
|
|
};
|
|
|
|
const user = await this.col.findOneAndUpdate(query, update, { sort, returnDocument: 'after' });
|
|
if (user && user.value) {
|
|
return {
|
|
agentId: user.value._id,
|
|
username: user.value.username,
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async getNextBotAgent(ignoreAgentId) {
|
|
// TODO: Create class Agent
|
|
const query = {
|
|
roles: {
|
|
$all: ['bot', 'livechat-agent'],
|
|
},
|
|
...(ignoreAgentId && { _id: { $ne: ignoreAgentId } }),
|
|
};
|
|
|
|
const sort = {
|
|
livechatCount: 1,
|
|
username: 1,
|
|
};
|
|
|
|
const update = {
|
|
$inc: {
|
|
livechatCount: 1,
|
|
},
|
|
};
|
|
|
|
const user = await this.col.findOneAndUpdate(query, update, { sort, returnDocument: 'after' });
|
|
if (user?.value) {
|
|
return {
|
|
agentId: user.value._id,
|
|
username: user.value.username,
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
setLivechatStatus(userId, status) {
|
|
// TODO: Create class Agent
|
|
const query = {
|
|
_id: userId,
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
statusLivechat: status,
|
|
livechatStatusSystemModified: false,
|
|
},
|
|
};
|
|
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
setLivechatData(userId, data = {}) {
|
|
// TODO: Create class Agent
|
|
const query = {
|
|
_id: userId,
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
livechat: data,
|
|
},
|
|
};
|
|
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
async closeOffice() {
|
|
// TODO: Create class Agent
|
|
const promises = [];
|
|
await this.findAgents().forEach((agent) => promises.push(this.setLivechatStatus(agent._id, 'not-available')));
|
|
await Promise.all(promises);
|
|
}
|
|
|
|
async openOffice() {
|
|
// TODO: Create class Agent
|
|
const promises = [];
|
|
await this.findAgents().forEach((agent) => promises.push(this.setLivechatStatus(agent._id, 'available')));
|
|
await Promise.all(promises);
|
|
}
|
|
|
|
getAgentInfo(agentId, showAgentEmail = false) {
|
|
// TODO: Create class Agent
|
|
const query = {
|
|
_id: agentId,
|
|
};
|
|
|
|
const options = {
|
|
projection: {
|
|
name: 1,
|
|
username: 1,
|
|
phone: 1,
|
|
customFields: 1,
|
|
status: 1,
|
|
livechat: 1,
|
|
...(showAgentEmail && { emails: 1 }),
|
|
},
|
|
};
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
roleBaseQuery(userId) {
|
|
return { _id: userId };
|
|
}
|
|
|
|
setE2EPublicAndPrivateKeysByUserId(userId, { public_key, private_key }) {
|
|
return this.updateOne(
|
|
{ _id: userId },
|
|
{
|
|
$set: {
|
|
'e2e.public_key': public_key,
|
|
'e2e.private_key': private_key,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
async rocketMailUnsubscribe(_id, createdAt) {
|
|
const query = {
|
|
_id,
|
|
createdAt: new Date(parseInt(createdAt)),
|
|
};
|
|
const update = {
|
|
$set: {
|
|
'mailer.unsubscribed': true,
|
|
},
|
|
};
|
|
const affectedRows = (await this.updateOne(query, update)).updatedCount;
|
|
return affectedRows;
|
|
}
|
|
|
|
async fetchKeysByUserId(userId) {
|
|
const user = await this.findOne({ _id: userId }, { projection: { e2e: 1 } });
|
|
|
|
if (!user?.e2e?.public_key) {
|
|
return {};
|
|
}
|
|
|
|
return {
|
|
public_key: user.e2e.public_key,
|
|
private_key: user.e2e.private_key,
|
|
};
|
|
}
|
|
|
|
disable2FAAndSetTempSecretByUserId(userId, tempToken) {
|
|
return this.updateOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
$set: {
|
|
'services.totp': {
|
|
enabled: false,
|
|
tempSecret: tempToken,
|
|
},
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
enable2FAAndSetSecretAndCodesByUserId(userId, secret, backupCodes) {
|
|
return this.updateOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
$set: {
|
|
'services.totp.enabled': true,
|
|
'services.totp.secret': secret,
|
|
'services.totp.hashedBackup': backupCodes,
|
|
},
|
|
$unset: {
|
|
'services.totp.tempSecret': 1,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
disable2FAByUserId(userId) {
|
|
return this.updateOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
$set: {
|
|
'services.totp': {
|
|
enabled: false,
|
|
},
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
update2FABackupCodesByUserId(userId, backupCodes) {
|
|
return this.updateOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
$set: {
|
|
'services.totp.hashedBackup': backupCodes,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
enableEmail2FAByUserId(userId) {
|
|
return this.updateOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
$set: {
|
|
'services.email2fa': {
|
|
enabled: true,
|
|
changedAt: new Date(),
|
|
},
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
disableEmail2FAByUserId(userId) {
|
|
return this.updateOne(
|
|
{
|
|
_id: userId,
|
|
},
|
|
{
|
|
$set: {
|
|
'services.email2fa': {
|
|
enabled: false,
|
|
changedAt: new Date(),
|
|
},
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
findByIdsWithPublicE2EKey(ids, options) {
|
|
const query = {
|
|
'_id': {
|
|
$in: ids,
|
|
},
|
|
'e2e.public_key': {
|
|
$exists: 1,
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
resetE2EKey(userId) {
|
|
return this.updateOne(
|
|
{ _id: userId },
|
|
{
|
|
$unset: {
|
|
e2e: '',
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
removeExpiredEmailCodesOfUserId(userId) {
|
|
return this.updateOne(
|
|
{ _id: userId },
|
|
{
|
|
$pull: {
|
|
'services.emailCode': {
|
|
expire: { $lt: new Date() },
|
|
},
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
removeEmailCodeByUserIdAndCode(userId, code) {
|
|
return this.updateOne(
|
|
{ _id: userId },
|
|
{
|
|
$pull: {
|
|
'services.emailCode': {
|
|
code,
|
|
},
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
addEmailCodeByUserId(userId, code, expire) {
|
|
return this.updateOne(
|
|
{ _id: userId },
|
|
{
|
|
$push: {
|
|
'services.emailCode': {
|
|
$each: [
|
|
{
|
|
code,
|
|
expire,
|
|
},
|
|
],
|
|
$slice: -5,
|
|
},
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param {IRole['_id'][]} roles the list of role ids
|
|
* @param {any} options
|
|
*/
|
|
findActiveUsersInRoles(roles, options) {
|
|
roles = [].concat(roles);
|
|
|
|
const query = {
|
|
roles: { $in: roles },
|
|
active: true,
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
countActiveUsersInRoles(roles, options) {
|
|
roles = [].concat(roles);
|
|
|
|
const query = {
|
|
roles: { $in: roles },
|
|
active: true,
|
|
};
|
|
|
|
return this.col.countDocuments(query, options);
|
|
}
|
|
|
|
findOneByUsernameAndServiceNameIgnoringCase(username, userId, serviceName, options) {
|
|
if (typeof username === 'string') {
|
|
username = new RegExp(`^${escapeRegExp(username)}$`, 'i');
|
|
}
|
|
|
|
const query = { username, [`services.${serviceName}.id`]: userId };
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneByEmailAddressAndServiceNameIgnoringCase(emailAddress, userId, serviceName, options) {
|
|
const query = {
|
|
'emails.address': new RegExp(`^${escapeRegExp(String(emailAddress).trim())}$`, 'i'),
|
|
[`services.${serviceName}.id`]: userId,
|
|
};
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneByEmailAddress(emailAddress, options) {
|
|
const query = { 'emails.address': String(emailAddress).trim().toLowerCase() };
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneWithoutLDAPByEmailAddress(emailAddress, options) {
|
|
const query = {
|
|
'email.address': emailAddress.trim().toLowerCase(),
|
|
'services.ldap': {
|
|
$exists: false,
|
|
},
|
|
};
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneAdmin(userId, options) {
|
|
const query = { roles: { $in: ['admin'] }, _id: userId };
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneByIdAndLoginToken(_id, token, options) {
|
|
const query = {
|
|
_id,
|
|
'services.resume.loginTokens.hashedToken': token,
|
|
};
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneById(userId, options = {}) {
|
|
const query = { _id: userId };
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneActiveById(userId, options) {
|
|
const query = {
|
|
_id: userId,
|
|
active: true,
|
|
};
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneByIdOrUsername(idOrUsername, options) {
|
|
const query = {
|
|
$or: [
|
|
{
|
|
_id: idOrUsername,
|
|
},
|
|
{
|
|
username: idOrUsername,
|
|
},
|
|
],
|
|
};
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findOneByRolesAndType(roles, type, options) {
|
|
const query = { roles, type };
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
findNotOfflineByIds(users, options) {
|
|
const query = {
|
|
_id: { $in: users },
|
|
status: {
|
|
$in: ['online', 'away', 'busy'],
|
|
},
|
|
};
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findUsersNotOffline(options) {
|
|
const query = {
|
|
username: {
|
|
$exists: 1,
|
|
},
|
|
status: {
|
|
$in: ['online', 'away', 'busy'],
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
countUsersNotOffline(options) {
|
|
const query = {
|
|
username: {
|
|
$exists: 1,
|
|
},
|
|
status: {
|
|
$in: ['online', 'away', 'busy'],
|
|
},
|
|
};
|
|
|
|
return this.col.countDocuments(query, options);
|
|
}
|
|
|
|
findNotIdUpdatedFrom(uid, from, options) {
|
|
const query = {
|
|
_id: { $ne: uid },
|
|
username: {
|
|
$exists: 1,
|
|
},
|
|
_updatedAt: { $gte: from },
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
async findByRoomId(rid, options) {
|
|
const data = (await Subscriptions.findByRoomId(rid).toArray()).map((item) => item.u._id);
|
|
const query = {
|
|
_id: {
|
|
$in: data,
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findByUsername(username, options) {
|
|
const query = { username };
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findByUsernames(usernames, options) {
|
|
const query = { username: { $in: usernames } };
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findByUsernamesIgnoringCase(usernames, options) {
|
|
const query = {
|
|
username: {
|
|
$in: usernames.filter(Boolean).map((u) => new RegExp(`^${escapeRegExp(u)}$`, 'i')),
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findActiveByUserIds(ids, options = {}) {
|
|
return this.find(
|
|
{
|
|
active: true,
|
|
type: { $nin: ['app'] },
|
|
roles: { $ne: ['guest'] },
|
|
_id: { $in: ids },
|
|
},
|
|
options,
|
|
);
|
|
}
|
|
|
|
findActiveLocalGuests(idExceptions = [], options = {}) {
|
|
const query = {
|
|
active: true,
|
|
type: { $nin: ['app'] },
|
|
roles: {
|
|
$eq: 'guest',
|
|
$size: 1,
|
|
},
|
|
isRemote: { $ne: true },
|
|
};
|
|
|
|
if (idExceptions) {
|
|
if (!Array.isArray(idExceptions)) {
|
|
idExceptions = [idExceptions];
|
|
}
|
|
|
|
query._id = { $nin: idExceptions };
|
|
}
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
countActiveLocalGuests(idExceptions = []) {
|
|
const query = {
|
|
active: true,
|
|
type: { $nin: ['app'] },
|
|
roles: {
|
|
$eq: 'guest',
|
|
$size: 1,
|
|
},
|
|
isRemote: { $ne: true },
|
|
};
|
|
|
|
if (idExceptions) {
|
|
if (!Array.isArray(idExceptions)) {
|
|
idExceptions = [idExceptions];
|
|
}
|
|
|
|
query._id = { $nin: idExceptions };
|
|
}
|
|
|
|
return this.col.countDocuments(query);
|
|
}
|
|
|
|
// 4
|
|
findUsersByNameOrUsername(nameOrUsername, options) {
|
|
const query = {
|
|
username: {
|
|
$exists: 1,
|
|
},
|
|
|
|
$or: [{ name: nameOrUsername }, { username: nameOrUsername }],
|
|
|
|
type: {
|
|
$in: ['user'],
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findByUsernameNameOrEmailAddress(usernameNameOrEmailAddress, options) {
|
|
const query = {
|
|
$or: [
|
|
{ name: usernameNameOrEmailAddress },
|
|
{ username: usernameNameOrEmailAddress },
|
|
{ 'emails.address': usernameNameOrEmailAddress },
|
|
],
|
|
type: {
|
|
$in: ['user', 'bot'],
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findCrowdUsers(options) {
|
|
const query = { crowd: true };
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
async getLastLogin(options = { projection: { _id: 0, lastLogin: 1 } }) {
|
|
options.sort = { lastLogin: -1 };
|
|
const user = await this.findOne({}, options);
|
|
return user?.lastLogin;
|
|
}
|
|
|
|
findUsersByUsernames(usernames, options) {
|
|
const query = {
|
|
username: {
|
|
$in: usernames,
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findUsersByIds(ids, options) {
|
|
const query = {
|
|
_id: {
|
|
$in: ids,
|
|
},
|
|
};
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findUsersWithUsernameByIds(ids, options) {
|
|
const query = {
|
|
_id: {
|
|
$in: ids,
|
|
},
|
|
username: {
|
|
$exists: 1,
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
findUsersWithUsernameByIdsNotOffline(ids, options) {
|
|
const query = {
|
|
_id: {
|
|
$in: ids,
|
|
},
|
|
username: {
|
|
$exists: 1,
|
|
},
|
|
status: {
|
|
$in: ['online', 'away', 'busy'],
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
/**
|
|
* @param {import('mongodb').Filter<import('@rocket.chat/core-typings').IStats>} projection
|
|
*/
|
|
getOldest(optionsParams) {
|
|
const query = {
|
|
_id: {
|
|
$ne: 'rocket.cat',
|
|
},
|
|
};
|
|
|
|
const options = {
|
|
...optionsParams,
|
|
sort: {
|
|
createdAt: 1,
|
|
},
|
|
};
|
|
|
|
return this.findOne(query, options);
|
|
}
|
|
|
|
countRemote(options = {}) {
|
|
return this.col.countDocuments({ isRemote: true }, options);
|
|
}
|
|
|
|
findActiveRemote(options = {}) {
|
|
return this.find(
|
|
{
|
|
active: true,
|
|
isRemote: true,
|
|
roles: { $ne: ['guest'] },
|
|
},
|
|
options,
|
|
);
|
|
}
|
|
|
|
findActiveFederated(options = {}) {
|
|
return this.find(
|
|
{
|
|
active: true,
|
|
federated: true,
|
|
},
|
|
options,
|
|
);
|
|
}
|
|
|
|
getSAMLByIdAndSAMLProvider(_id, provider) {
|
|
return this.findOne(
|
|
{
|
|
_id,
|
|
'services.saml.provider': provider,
|
|
},
|
|
{
|
|
'services.saml': 1,
|
|
},
|
|
);
|
|
}
|
|
|
|
findBySAMLNameIdOrIdpSession(nameID, idpSession) {
|
|
return this.find({
|
|
$or: [{ 'services.saml.nameID': nameID }, { 'services.saml.idpSession': idpSession }],
|
|
});
|
|
}
|
|
|
|
countBySAMLNameIdOrIdpSession(nameID, idpSession) {
|
|
return this.col.countDocuments({
|
|
$or: [{ 'services.saml.nameID': nameID }, { 'services.saml.idpSession': idpSession }],
|
|
});
|
|
}
|
|
|
|
findBySAMLInResponseTo(inResponseTo) {
|
|
return this.find({
|
|
'services.saml.inResponseTo': inResponseTo,
|
|
});
|
|
}
|
|
|
|
// UPDATE
|
|
addImportIds(_id, importIds) {
|
|
importIds = [].concat(importIds);
|
|
|
|
const query = { _id };
|
|
|
|
const update = {
|
|
$addToSet: {
|
|
importIds: {
|
|
$each: importIds,
|
|
},
|
|
},
|
|
};
|
|
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
updateInviteToken(_id, inviteToken) {
|
|
const update = {
|
|
$set: {
|
|
inviteToken,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
updateLastLoginById(_id) {
|
|
const update = {
|
|
$set: {
|
|
lastLogin: new Date(),
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
addPasswordToHistory(_id, password, passwordHistoryAmount) {
|
|
const update = {
|
|
$push: {
|
|
'services.passwordHistory': {
|
|
$each: [password],
|
|
$slice: -Number(passwordHistoryAmount),
|
|
},
|
|
},
|
|
};
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setServiceId(_id, serviceName, serviceId) {
|
|
const update = { $set: {} };
|
|
|
|
const serviceIdKey = `services.${serviceName}.id`;
|
|
update.$set[serviceIdKey] = serviceId;
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setUsername(_id, username) {
|
|
const update = { $set: { username } };
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setEmail(_id, email) {
|
|
const update = {
|
|
$set: {
|
|
emails: [
|
|
{
|
|
address: email,
|
|
verified: false,
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
// 5
|
|
setEmailVerified(_id, email) {
|
|
const query = {
|
|
_id,
|
|
emails: {
|
|
$elemMatch: {
|
|
address: email,
|
|
verified: false,
|
|
},
|
|
},
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
'emails.$.verified': true,
|
|
},
|
|
};
|
|
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
setName(_id, name) {
|
|
const update = {
|
|
$set: {
|
|
name,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
unsetName(_id) {
|
|
const update = {
|
|
$unset: {
|
|
name,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setCustomFields(_id, fields) {
|
|
const values = {};
|
|
Object.keys(fields).forEach((key) => {
|
|
values[`customFields.${key}`] = fields[key];
|
|
});
|
|
|
|
const update = { $set: values };
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setAvatarData(_id, origin, etag) {
|
|
const update = {
|
|
$set: {
|
|
avatarOrigin: origin,
|
|
avatarETag: etag,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
unsetAvatarData(_id) {
|
|
const update = {
|
|
$unset: {
|
|
avatarOrigin: 1,
|
|
avatarETag: 1,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setUserActive(_id, active) {
|
|
if (active == null) {
|
|
active = true;
|
|
}
|
|
const update = {
|
|
$set: {
|
|
active,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setAllUsersActive(active) {
|
|
const update = {
|
|
$set: {
|
|
active,
|
|
},
|
|
};
|
|
|
|
return this.updateMany({}, update);
|
|
}
|
|
|
|
/**
|
|
* @param latestLastLoginDate
|
|
* @param {IRole['_id']} role the role id
|
|
* @param {boolean} active
|
|
*/
|
|
setActiveNotLoggedInAfterWithRole(latestLastLoginDate, role = 'user', active = false) {
|
|
const neverActive = { lastLogin: { $exists: 0 }, createdAt: { $lte: latestLastLoginDate } };
|
|
const idleTooLong = { lastLogin: { $lte: latestLastLoginDate } };
|
|
|
|
const query = {
|
|
$or: [neverActive, idleTooLong],
|
|
active: true,
|
|
roles: role,
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
active,
|
|
},
|
|
};
|
|
|
|
return this.updateMany(query, update);
|
|
}
|
|
|
|
unsetRequirePasswordChange(_id) {
|
|
const update = {
|
|
$unset: {
|
|
requirePasswordChange: true,
|
|
requirePasswordChangeReason: true,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
resetPasswordAndSetRequirePasswordChange(_id, requirePasswordChange, requirePasswordChangeReason) {
|
|
const update = {
|
|
$unset: {
|
|
'services.password': 1,
|
|
},
|
|
$set: {
|
|
requirePasswordChange,
|
|
requirePasswordChangeReason,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setLanguage(_id, language) {
|
|
const update = {
|
|
$set: {
|
|
language,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setProfile(_id, profile) {
|
|
const update = {
|
|
$set: {
|
|
'settings.profile': profile,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setBio(_id, bio = '') {
|
|
const update = {
|
|
...(bio.trim()
|
|
? {
|
|
$set: {
|
|
bio,
|
|
},
|
|
}
|
|
: {
|
|
$unset: {
|
|
bio: 1,
|
|
},
|
|
}),
|
|
};
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setNickname(_id, nickname = '') {
|
|
const update = {
|
|
...(nickname.trim()
|
|
? {
|
|
$set: {
|
|
nickname,
|
|
},
|
|
}
|
|
: {
|
|
$unset: {
|
|
nickname: 1,
|
|
},
|
|
}),
|
|
};
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
clearSettings(_id) {
|
|
const update = {
|
|
$set: {
|
|
settings: {},
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setPreferences(_id, preferences) {
|
|
const settingsObject = Object.assign(
|
|
{},
|
|
...Object.keys(preferences).map((key) => ({
|
|
[`settings.preferences.${key}`]: preferences[key],
|
|
})),
|
|
);
|
|
|
|
const update = {
|
|
$set: settingsObject,
|
|
};
|
|
if (parseInt(preferences.clockMode) === 0) {
|
|
delete update.$set['settings.preferences.clockMode'];
|
|
update.$unset = { 'settings.preferences.clockMode': 1 };
|
|
}
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
setTwoFactorAuthorizationHashAndUntilForUserIdAndToken(_id, token, hash, until) {
|
|
return this.updateOne(
|
|
{
|
|
_id,
|
|
'services.resume.loginTokens.hashedToken': token,
|
|
},
|
|
{
|
|
$set: {
|
|
'services.resume.loginTokens.$.twoFactorAuthorizedHash': hash,
|
|
'services.resume.loginTokens.$.twoFactorAuthorizedUntil': until,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
setUtcOffset(_id, utcOffset) {
|
|
const query = {
|
|
_id,
|
|
utcOffset: {
|
|
$ne: utcOffset,
|
|
},
|
|
};
|
|
|
|
const update = {
|
|
$set: {
|
|
utcOffset,
|
|
},
|
|
};
|
|
|
|
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) {
|
|
const update = {
|
|
$set: {
|
|
reason,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
unsetReason(_id) {
|
|
const update = {
|
|
$unset: {
|
|
reason: true,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
async bannerExistsById(_id, bannerId) {
|
|
const query = {
|
|
_id,
|
|
[`banners.${bannerId}`]: {
|
|
$exists: true,
|
|
},
|
|
};
|
|
|
|
return (await this.col.countDocuments(query)) !== 0;
|
|
}
|
|
|
|
setBannerReadById(_id, bannerId) {
|
|
const update = {
|
|
$set: {
|
|
[`banners.${bannerId}.read`]: true,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
removeBannerById(_id, bannerId) {
|
|
const update = {
|
|
$unset: {
|
|
[`banners.${bannerId}`]: true,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
removeSamlServiceSession(_id) {
|
|
const update = {
|
|
$unset: {
|
|
'services.saml.idpSession': '',
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
|
|
updateDefaultStatus(_id, statusDefault) {
|
|
return this.updateOne(
|
|
{
|
|
_id,
|
|
statusDefault: { $ne: statusDefault },
|
|
},
|
|
{
|
|
$set: {
|
|
statusDefault,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
setSamlInResponseTo(_id, inResponseTo) {
|
|
this.updateOne(
|
|
{
|
|
_id,
|
|
},
|
|
{
|
|
$set: {
|
|
'services.saml.inResponseTo': inResponseTo,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
// INSERT
|
|
create(data) {
|
|
const user = {
|
|
createdAt: new Date(),
|
|
avatarOrigin: 'none',
|
|
};
|
|
|
|
Object.assign(user, data);
|
|
|
|
return this.insertOne(user);
|
|
}
|
|
|
|
// REMOVE
|
|
removeById(_id) {
|
|
return this.deleteOne({ _id });
|
|
}
|
|
|
|
removeLivechatData(userId) {
|
|
const query = {
|
|
_id: userId,
|
|
};
|
|
|
|
const update = {
|
|
$unset: {
|
|
livechat: true,
|
|
},
|
|
};
|
|
|
|
return this.updateOne(query, update);
|
|
}
|
|
|
|
/*
|
|
Find users to send a message by email if:
|
|
- he is not online
|
|
- has a verified email
|
|
- has not disabled email notifications
|
|
- `active` is equal to true (false means they were deactivated and can't login)
|
|
*/
|
|
getUsersToSendOfflineEmail(usersIds) {
|
|
const query = {
|
|
'_id': {
|
|
$in: usersIds,
|
|
},
|
|
'active': true,
|
|
'status': 'offline',
|
|
'statusConnection': {
|
|
$ne: 'online',
|
|
},
|
|
'emails.verified': true,
|
|
};
|
|
|
|
const options = {
|
|
projection: {
|
|
'name': 1,
|
|
'username': 1,
|
|
'emails': 1,
|
|
'settings.preferences.emailNotificationMode': 1,
|
|
'language': 1,
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
countActiveUsersByService(serviceName, options) {
|
|
const query = {
|
|
active: true,
|
|
type: { $nin: ['app'] },
|
|
roles: { $ne: ['guest'] },
|
|
[`services.${serviceName}`]: { $exists: true },
|
|
};
|
|
|
|
return this.col.countDocuments(query, options);
|
|
}
|
|
|
|
// here
|
|
getActiveLocalUserCount() {
|
|
return Promise.all([
|
|
// Count all active users (fast based on index)
|
|
this.col.countDocuments({
|
|
active: true,
|
|
}),
|
|
// Count all active that are guests, apps or federated
|
|
// Fast based on indexes, usually based on guest index as is usually small
|
|
this.col.countDocuments({
|
|
active: true,
|
|
$or: [{ roles: ['guest'] }, { type: 'app' }, { federated: true }, { isRemote: true }],
|
|
}),
|
|
// Get all active and remove the guests, apps, federated, etc
|
|
]).then((results) => results.reduce((a, b) => a - b));
|
|
}
|
|
|
|
getActiveLocalGuestCount(idExceptions = []) {
|
|
return this.countActiveLocalGuests(idExceptions);
|
|
}
|
|
|
|
removeOlderResumeTokensByUserId(userId, fromDate) {
|
|
this.updateOne(
|
|
{ _id: userId },
|
|
{
|
|
$pull: {
|
|
'services.resume.loginTokens': {
|
|
when: { $lt: fromDate },
|
|
},
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
findAllUsersWithPendingAvatar() {
|
|
const query = {
|
|
_pendingAvatarUrl: {
|
|
$exists: true,
|
|
},
|
|
};
|
|
|
|
const options = {
|
|
projection: {
|
|
_id: 1,
|
|
name: 1,
|
|
_pendingAvatarUrl: 1,
|
|
},
|
|
};
|
|
|
|
return this.find(query, options);
|
|
}
|
|
|
|
updateCustomFieldsById(userId, customFields) {
|
|
return this.updateOne(
|
|
{ _id: userId },
|
|
{
|
|
$set: {
|
|
customFields,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
countRoomMembers(roomId) {
|
|
return this.col.countDocuments({ __rooms: roomId, active: true });
|
|
}
|
|
|
|
removeAgent(_id) {
|
|
const update = {
|
|
$set: {
|
|
operator: false,
|
|
},
|
|
$unset: {
|
|
livechat: 1,
|
|
statusLivechat: 1,
|
|
extension: 1,
|
|
openBusinessHours: 1,
|
|
},
|
|
};
|
|
|
|
return this.updateOne({ _id }, update);
|
|
}
|
|
}
|
|
|