From d2f3333af017e810f0a532497b612be011d6f3de Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 1 Nov 2023 15:06:35 -0400 Subject: [PATCH] refactor(socket.io): deprecate socketGroups.searchMembers in favour of api.groups.listMembers --- public/openapi/write.yaml | 2 + public/openapi/write/groups/slug/members.yaml | 46 +++++++++++++++++++ src/api/groups.js | 29 ++++++++++++ src/controllers/write/groups.js | 5 ++ src/groups/search.js | 4 +- src/routes/write/groups.js | 2 + src/socket.io/groups.js | 16 +++---- 7 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 public/openapi/write/groups/slug/members.yaml diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index 1c4dfd44a4..70462881aa 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -96,6 +96,8 @@ paths: $ref: 'write/groups.yaml' /groups/{slug}: $ref: 'write/groups/slug.yaml' + /groups/{slug}/members: + $ref: 'write/groups/slug/members.yaml' /groups/{slug}/membership/{uid}: $ref: 'write/groups/slug/membership/uid.yaml' /groups/{slug}/ownership/{uid}: diff --git a/public/openapi/write/groups/slug/members.yaml b/public/openapi/write/groups/slug/members.yaml new file mode 100644 index 0000000000..706efceecf --- /dev/null +++ b/public/openapi/write/groups/slug/members.yaml @@ -0,0 +1,46 @@ +get: + tags: + - groups + summary: list group members + description: This operation returns a list of members in a user groups. Group owners (if any) are floated to the top of the returned users. + parameters: + - in: path + name: slug + schema: + type: string + required: true + description: group slug (that also acts as its identifier) to check + example: administrators + - in: query + name: 'query' + schema: + type: string + required: false + description: A keyword search query + example: 'a' + responses: + '200': + description: matching user group members successfully listed + 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 + matchCount: + type: number + timing: + type: string \ No newline at end of file diff --git a/src/api/groups.js b/src/api/groups.js index 3b0ecb7b10..7c0186ac31 100644 --- a/src/api/groups.js +++ b/src/api/groups.js @@ -73,6 +73,35 @@ groupsAPI.delete = async function (caller, data) { }); }; +groupsAPI.listMembers = async (caller, data) => { + const groupName = await groups.getGroupNameByGroupSlug(data.slug); + + await canSearchMembers(caller.uid, groupName); + if (!await privileges.global.can('search:users', caller.uid)) { + throw new Error('[[error:no-privileges]]'); + } + + return await groups.searchMembers({ + uid: caller.uid, + query: data.query, + groupName, + }); +}; + +async function canSearchMembers(uid, groupName) { + const [isHidden, isMember, hasAdminPrivilege, isGlobalMod, viewGroups] = await Promise.all([ + groups.isHidden(groupName), + groups.isMember(uid, groupName), + privileges.admin.can('admin:groups', uid), + user.isGlobalModerator(uid), + privileges.global.can('view:groups', uid), + ]); + + if (!viewGroups || (isHidden && !isMember && !hasAdminPrivilege && !isGlobalMod)) { + throw new Error('[[error:no-privileges]]'); + } +} + groupsAPI.join = async function (caller, data) { if (!data) { throw new Error('[[error:invalid-data]]'); diff --git a/src/controllers/write/groups.js b/src/controllers/write/groups.js index 8e18450139..8e261c1b95 100644 --- a/src/controllers/write/groups.js +++ b/src/controllers/write/groups.js @@ -32,6 +32,11 @@ Groups.delete = async (req, res) => { helpers.formatApiResponse(200, res); }; +Groups.listMembers = async (req, res) => { + const { slug } = req.params; + helpers.formatApiResponse(200, res, await api.groups.listMembers(req, { ...req.query, slug })); +}; + Groups.join = async (req, res) => { await api.groups.join(req, req.params); helpers.formatApiResponse(200, res); diff --git a/src/groups/search.js b/src/groups/search.js index 9b6b15d5a4..d1fd8892be 100644 --- a/src/groups/search.js +++ b/src/groups/search.js @@ -53,7 +53,9 @@ module.exports = function (Groups) { Groups.searchMembers = async function (data) { if (!data.query) { const users = await Groups.getOwnersAndMembers(data.groupName, data.uid, 0, 19); - return { users: users }; + const matchCount = users.length; + const timing = '0.00'; + return { users, matchCount, timing }; } const results = await user.search({ diff --git a/src/routes/write/groups.js b/src/routes/write/groups.js index d6f78217c9..6452e9c4cb 100644 --- a/src/routes/write/groups.js +++ b/src/routes/write/groups.js @@ -16,6 +16,8 @@ module.exports = function () { setupApiRoute(router, 'put', '/:slug', [...middlewares, middleware.assert.group], controllers.write.groups.update); setupApiRoute(router, 'delete', '/:slug', [...middlewares, middleware.assert.group], controllers.write.groups.delete); + setupApiRoute(router, 'get', '/:slug/members', [...middlewares, middleware.assert.group], controllers.write.groups.listMembers); + setupApiRoute(router, 'put', '/:slug/membership/:uid', [...middlewares, middleware.assert.group], controllers.write.groups.join); setupApiRoute(router, 'delete', '/:slug/membership/:uid', [...middlewares, middleware.assert.group], controllers.write.groups.leave); diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 2f3d45dc7a..508078d4d3 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -5,6 +5,7 @@ const user = require('../user'); const utils = require('../utils'); const privileges = require('../privileges'); const api = require('../api'); +const slugify = require('../slugify'); const sockets = require('.'); @@ -40,18 +41,15 @@ SocketGroups.loadMore = async (socket, data) => { }; SocketGroups.searchMembers = async (socket, data) => { + sockets.warnDeprecated(socket, 'GET /api/v3/groups/:groupName/members'); + if (!data.groupName) { throw new Error('[[error:invalid-data]]'); } - await canSearchMembers(socket.uid, data.groupName); - if (!await privileges.global.can('search:users', socket.uid)) { - throw new Error('[[error:no-privileges]]'); - } - return await groups.searchMembers({ - uid: socket.uid, - query: data.query, - groupName: data.groupName, - }); + data.slug = slugify(data.groupName); + delete data.groupName; + + return api.groups.listMembers(socket, data); }; SocketGroups.loadMoreMembers = async (socket, data) => {