From 5392f0957dce00c3307e5ae797d82da743b2b960 Mon Sep 17 00:00:00 2001 From: Aditya Mitra <55396651+aditya-mitra@users.noreply.github.com> Date: Tue, 23 Mar 2021 05:03:00 +0530 Subject: [PATCH] [IMPROVE] Sort Users List In Case Insensitive Manner (#20790) --- app/api/server/lib/users.js | 58 +++++++++++++++++++++++++++++++++++++ app/api/server/v1/users.js | 51 +++++++++++++++++++++++++++----- 2 files changed, 101 insertions(+), 8 deletions(-) diff --git a/app/api/server/lib/users.js b/app/api/server/lib/users.js index 78f5adb17ad..fcec008fcf8 100644 --- a/app/api/server/lib/users.js +++ b/app/api/server/lib/users.js @@ -28,3 +28,61 @@ export async function findUsersToAutocomplete({ uid, selector }) { items: users, }; } + +/** + * Returns a new query object with the inclusive fields only + * @param {Object} query search query for matching rows + */ +export function getInclusiveFields(query) { + const newQuery = {}; + + for (const [key, value] of Object.entries(query)) { + if (value === 1) { + newQuery[key] = value; + } + } + + return newQuery; +} + +/** + * get the default fields if **fields** are empty (`{}`) or `undefined`/`null` + * @param {Object|null|undefined} fields the fields from parsed jsonQuery + */ +export function getNonEmptyFields(fields) { + const defaultFields = { + name: 1, + username: 1, + emails: 1, + roles: 1, + status: 1, + active: 1, + avatarETag: 1, + }; + + if (!fields || Object.keys(fields).length === 0) { + return defaultFields; + } + + return { ...defaultFields, ...fields }; +} + +/** + * get the default query if **query** is empty (`{}`) or `undefined`/`null` + * @param {Object|null|undefined} query the query from parsed jsonQuery + */ +export function getNonEmptyQuery(query) { + const defaultQuery = { + $or: [ + { 'emails.address': { $regex: '', $options: 'i' } }, + { username: { $regex: '', $options: 'i' } }, + { name: { $regex: '', $options: 'i' } }, + ], + }; + + if (!query || Object.keys(query).length === 0) { + return defaultQuery; + } + + return { ...defaultQuery, ...query }; +} diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index fe2d6f08b63..27512e9bfad 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -5,6 +5,7 @@ import _ from 'underscore'; import Busboy from 'busboy'; import { Users, Subscriptions } from '../../../models/server'; +import { Users as UsersRaw } from '../../../models/server/raw'; import { hasPermission } from '../../../authorization'; import { settings } from '../../../settings'; import { getURL } from '../../../utils'; @@ -19,7 +20,7 @@ import { import { getFullUserDataByIdOrUsername } from '../../../lib/server/functions/getFullUserData'; import { API } from '../api'; import { setStatusText } from '../../../lib/server'; -import { findUsersToAutocomplete } from '../lib/users'; +import { findUsersToAutocomplete, getInclusiveFields, getNonEmptyFields, getNonEmptyQuery } from '../lib/users'; import { getUserForCheck, emailCheck } from '../../../2fa/server/code'; import { resetUserE2EEncriptionKey } from '../../../../server/lib/resetUserE2EKey'; import { setUserStatus } from '../../../../imports/users-presence/server/activeUsers'; @@ -229,18 +230,52 @@ API.v1.addRoute('users.list', { authRequired: true }, { const { offset, count } = this.getPaginationItems(); const { sort, fields, query } = this.parseJsonQuery(); - const users = Users.find(query, { - sort: sort || { username: 1 }, - skip: offset, - limit: count, - fields, - }).fetch(); + const nonEmptyQuery = getNonEmptyQuery(query); + const nonEmptyFields = getNonEmptyFields(fields); + + const inclusiveFields = getInclusiveFields(nonEmptyFields); + + const actualSort = sort && sort.name ? { nameInsensitive: sort.name, ...sort } : sort || { username: 1 }; + + const result = Promise.await( + UsersRaw.col + .aggregate([ + { + $match: nonEmptyQuery, + }, + { + $project: inclusiveFields, + }, + { + $addFields: { + nameInsensitive: { + $toLower: '$name', + }, + }, + }, + { + $skip: offset, + }, + { + $limit: count, + }, + { + $facet: { + sortedResults: [{ $sort: actualSort }], + totalCount: [{ $count: 'value' }], + }, + }, + ]) + .toArray(), + ); + + const { sortedResults: users, totalCount } = result[0]; return API.v1.success({ users, count: users.length, offset, - total: Users.find(query).count(), + total: totalCount[0].value, }); }, });