diff --git a/public/openapi/write/groups/slug/members.yaml b/public/openapi/write/groups/slug/members.yaml index 706efceecf..324738b5fa 100644 --- a/public/openapi/write/groups/slug/members.yaml +++ b/public/openapi/write/groups/slug/members.yaml @@ -9,18 +9,31 @@ get: schema: type: string required: true - description: group slug (that also acts as its identifier) to check + description: a group slug (that also acts as its identifier) example: administrators - in: query name: 'query' schema: type: string required: false - description: A keyword search query + description: > + A keyword search query. + + This parameter conflicts with `after`. If both are present, `after` is ignored. example: 'a' + - in: query + name: 'after' + schema: + type: string + required: false + description: > + Offset returned results. + + This parameter conflicts with `query`. If both are present, this parameter is ignored. + example: '0' responses: '200': - description: matching user group members successfully listed + description: user group members successfully listed content: application/json: schema: @@ -42,5 +55,10 @@ get: type: boolean matchCount: type: number + nullable: true timing: - type: string \ No newline at end of file + type: string + nullable: true + nextStart: + type: number + nullable: true \ No newline at end of file diff --git a/public/src/client/groups/list.js b/public/src/client/groups/list.js index cef4dfc660..aa270bf7f4 100644 --- a/public/src/client/groups/list.js +++ b/public/src/client/groups/list.js @@ -36,7 +36,7 @@ define('forum/groups/list', [ return; } - infinitescroll.loadMore('groups.loadMore', { + infinitescroll.loadMore('/groups', { sort: $('#search-sort').val(), after: $('[component="groups/container"]').attr('data-nextstart'), }, function (data, done) { diff --git a/public/src/client/groups/memberlist.js b/public/src/client/groups/memberlist.js index 986257bf9a..3a2839cab3 100644 --- a/public/src/client/groups/memberlist.js +++ b/public/src/client/groups/memberlist.js @@ -92,10 +92,7 @@ define('forum/groups/memberlist', ['api', 'bootbox', 'alerts'], function (api, b const searchEl = $('[component="groups/members/search"]'); searchEl.on('keyup', utils.debounce(function () { const query = searchEl.val(); - socket.emit('groups.searchMembers', { - groupName: groupName, - query: query, - }, function (err, results) { + api.get(`/groups/${groupName}/members`, { query }, function (err, results) { if (err) { return alerts.error(err); } @@ -125,8 +122,7 @@ define('forum/groups/memberlist', ['api', 'bootbox', 'alerts'], function (api, b } members.attr('loading', 1); - socket.emit('groups.loadMoreMembers', { - groupName: groupName, + api.get(`/groups/${groupName}/members`, { after: members.attr('data-nextstart'), }, function (err, data) { if (err) { diff --git a/src/api/groups.js b/src/api/groups.js index 7c0186ac31..5372f386be 100644 --- a/src/api/groups.js +++ b/src/api/groups.js @@ -74,6 +74,7 @@ groupsAPI.delete = async function (caller, data) { }; groupsAPI.listMembers = async (caller, data) => { + // v4 wishlist — search should paginate (with lru caching I guess) to match index listing behaviour const groupName = await groups.getGroupNameByGroupSlug(data.slug); await canSearchMembers(caller.uid, groupName); @@ -81,11 +82,26 @@ groupsAPI.listMembers = async (caller, data) => { throw new Error('[[error:no-privileges]]'); } - return await groups.searchMembers({ - uid: caller.uid, - query: data.query, - groupName, - }); + const { query } = data; + const after = parseInt(data.after || 0, 10); + let response; + if (query) { + response = await groups.searchMembers({ + uid: caller.uid, + query, + groupName, + }); + response.nextStart = null; + } else { + response = { + users: await groups.getOwnersAndMembers(groupName, caller.uid, after, after + 19), + nextStart: after + 20, + matchCount: null, + timing: null, + }; + } + + return response; }; async function canSearchMembers(uid, groupName) { diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 508078d4d3..760c6e7e14 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -53,16 +53,15 @@ SocketGroups.searchMembers = async (socket, data) => { }; SocketGroups.loadMoreMembers = async (socket, data) => { + sockets.warnDeprecated(socket, 'GET /api/v3/groups/:groupName/members'); + if (!data.groupName || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { throw new Error('[[error:invalid-data]]'); } - await canSearchMembers(socket.uid, data.groupName); - data.after = parseInt(data.after, 10); - const users = await groups.getOwnersAndMembers(data.groupName, socket.uid, data.after, data.after + 9); - return { - users: users, - nextStart: data.after + 10, - }; + data.slug = slugify(data.groupName); + delete data.groupName; + + return api.groups.listMembers(socket, data); }; SocketGroups.getChatGroups = async (socket) => { @@ -76,20 +75,6 @@ SocketGroups.getChatGroups = async (socket) => { return groupsList.map(g => ({ name: g.name, displayName: g.displayName })); }; -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]]'); - } -} - SocketGroups.cover = {}; SocketGroups.cover.update = async (socket, data) => {