diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index d08236d274..6e5904cbf2 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -226,6 +226,8 @@ paths: $ref: 'write/flags/flagId/notes/datetime.yaml' /search/categories: $ref: 'write/search/categories.yaml' + /search/chats/{roomId}/users: + $ref: 'write/search/chats/roomId/users.yaml' /admin/settings/{setting}: $ref: 'write/admin/settings/setting.yaml' /admin/analytics: diff --git a/public/openapi/write/search/chats/roomId/users.yaml b/public/openapi/write/search/chats/roomId/users.yaml new file mode 100644 index 0000000000..dd94a88d02 --- /dev/null +++ b/public/openapi/write/search/chats/roomId/users.yaml @@ -0,0 +1,44 @@ +get: + tags: + - search + summary: find room users by keyword + description: This operation returns a set of users in a chat room matching the keyword search. + parameters: + - in: path + name: roomId + schema: + type: number + required: true + description: room ID to check + example: 1 + - in: query + name: 'query' + schema: + type: string + required: false + description: The keyword used in the category search + example: 'admin' + responses: + '200': + description: matching users successfully retrieved + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../../components/schemas/Status.yaml#/Status + response: + type: object + properties: + users: + type: array + items: + allOf: + - $ref: ../../../../components/schemas/UserObject.yaml#/UserObjectSlim + - type: object + properties: + isOwner: + type: boolean + canKick: + type: boolean \ No newline at end of file diff --git a/public/src/client/chats/user-list.js b/public/src/client/chats/user-list.js index a4bd77cf65..36c6267cea 100644 --- a/public/src/client/chats/user-list.js +++ b/public/src/client/chats/user-list.js @@ -72,11 +72,8 @@ define('forum/chats/user-list', ['api'], function (api) { userList.addSearchHandler = function (roomId, inputEl, callback) { inputEl.on('keyup', utils.debounce(async () => { - const username = inputEl.val(); - const data = await socket.emit('modules.chats.searchMembers', { - username: username, - roomId: roomId, - }); + const query = inputEl.val(); + const data = await api.get(`/search/chats/${roomId}/users`, { query }); callback(data); }, 200)); }; diff --git a/src/api/search.js b/src/api/search.js index ac77347c26..043c97176e 100644 --- a/src/api/search.js +++ b/src/api/search.js @@ -2,7 +2,9 @@ const _ = require('lodash'); +const user = require('../user'); const categories = require('../categories'); +const messaging = require('../messaging'); const privileges = require('../privileges'); const meta = require('../meta'); const plugins = require('../plugins'); @@ -103,3 +105,50 @@ async function loadCids(uid, parentCid) { await getCidsRecursive(pageCids); return resultCids; } + +searchApi.roomUsers = async (caller, { query, roomId }) => { + const [isAdmin, inRoom, isRoomOwner] = await Promise.all([ + user.isAdministrator(caller.uid), + messaging.isUserInRoom(caller.uid, roomId), + messaging.isRoomOwner(caller.uid, roomId), + ]); + + if (!isAdmin && !inRoom) { + throw new Error('[[error:no-privileges]]'); + } + + const results = await user.search({ + query, + paginate: false, + hardCap: -1, + uid: caller.uid, + }); + + const { users } = results; + const foundUids = users.map(user => user && user.uid); + const isUidInRoom = _.zipObject( + foundUids, + await messaging.isUsersInRoom(foundUids, roomId) + ); + + const roomUsers = users.filter(user => isUidInRoom[user.uid]); + const isOwners = await messaging.isRoomOwner(roomUsers.map(u => u.uid), roomId); + + roomUsers.forEach((user, index) => { + if (user) { + user.isOwner = isOwners[index]; + user.canKick = isRoomOwner && (parseInt(user.uid, 10) !== parseInt(caller.uid, 10)); + } + }); + + roomUsers.sort((a, b) => { + if (a.isOwner && !b.isOwner) { + return -1; + } else if (!a.isOwner && b.isOwner) { + return 1; + } + return 0; + }); + + return { users: roomUsers }; +}; diff --git a/src/controllers/write/search.js b/src/controllers/write/search.js index a6acd0a59a..cfdaad44a8 100644 --- a/src/controllers/write/search.js +++ b/src/controllers/write/search.js @@ -8,3 +8,8 @@ const Search = module.exports; Search.categories = async (req, res) => { helpers.formatApiResponse(200, res, await api.search.categories(req, req.query)); }; + +Search.roomUsers = async (req, res) => { + const { query } = req.query; + helpers.formatApiResponse(200, res, await api.search.roomUsers(req, { query, ...req.params })); +}; diff --git a/src/routes/write/search.js b/src/routes/write/search.js index 01b98cdeed..4d4108a05b 100644 --- a/src/routes/write/search.js +++ b/src/routes/write/search.js @@ -1,19 +1,20 @@ 'use strict'; const router = require('express').Router(); -// const middleware = require('../../middleware'); +const middleware = require('../../middleware'); const controllers = require('../../controllers'); const routeHelpers = require('../helpers'); const { setupApiRoute } = routeHelpers; module.exports = function () { - // const middlewares = []; + const middlewares = [middleware.ensureLoggedIn]; // maybe redirect to /search/posts? // setupApiRoute(router, 'post', '/', [...middlewares], controllers.write.search.TBD); setupApiRoute(router, 'get', '/categories', [], controllers.write.search.categories); + setupApiRoute(router, 'get', '/chats/:roomId/users', [...middlewares, middleware.checkRequired.bind(null, ['query']), middleware.canChat, middleware.assert.room], controllers.write.search.roomUsers); return router; }; diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 9901b25c82..910713d9cf 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -157,51 +157,16 @@ SocketModules.chats.sortPublicRooms = async function (socket, data) { }; SocketModules.chats.searchMembers = async function (socket, data) { + sockets.warnDeprecated(socket, 'GET /api/v3/search/chats/:roomId/users?query='); + if (!data || !data.roomId) { throw new Error('[[error:invalid-data]]'); } - const [isAdmin, inRoom, isRoomOwner] = await Promise.all([ - user.isAdministrator(socket.uid), - Messaging.isUserInRoom(socket.uid, data.roomId), - Messaging.isRoomOwner(socket.uid, data.roomId), - ]); - if (!isAdmin && !inRoom) { - throw new Error('[[error:no-privileges]]'); - } - - const results = await user.search({ - query: data.username, - paginate: false, - hardCap: -1, - }); - - const { users } = results; - const foundUids = users.map(user => user && user.uid); - const isUidInRoom = _.zipObject( - foundUids, - await Messaging.isUsersInRoom(foundUids, data.roomId) - ); - - const roomUsers = users.filter(user => isUidInRoom[user.uid]); - const isOwners = await Messaging.isRoomOwner(roomUsers.map(u => u.uid), data.roomId); - - roomUsers.forEach((user, index) => { - if (user) { - user.isOwner = isOwners[index]; - user.canKick = isRoomOwner && (parseInt(user.uid, 10) !== parseInt(socket.uid, 10)); - } - }); - - roomUsers.sort((a, b) => { - if (a.isOwner && !b.isOwner) { - return -1; - } else if (!a.isOwner && b.isOwner) { - return 1; - } - return 0; - }); - return { users: roomUsers }; + // parameter renamed; backwards compatibility + data.query = data.username; + delete data.username; + return await api.search.roomUsers(socket, data); }; SocketModules.chats.toggleOwner = async (socket, data) => {