mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +01:00 
			
		
		
		
	feat: #11420, add new GET routes to retrieve pending and invited members of a group, plus accept/reject pending
This commit is contained in:
		| @@ -154,6 +154,8 @@ | |||||||
| 	"group-already-requested": "Your membership request has already been submitted", | 	"group-already-requested": "Your membership request has already been submitted", | ||||||
| 	"group-join-disabled": "You are not able to join this group at this time", | 	"group-join-disabled": "You are not able to join this group at this time", | ||||||
| 	"group-leave-disabled": "You are not able to leave this group at this time", | 	"group-leave-disabled": "You are not able to leave this group at this time", | ||||||
|  | 	"group-user-not-pending": "User does not have a pending request to join this group.", | ||||||
|  | 	"gorup-user-not-invited": "User has not been invited to join this group.", | ||||||
|  |  | ||||||
| 	"post-already-deleted": "This post has already been deleted", | 	"post-already-deleted": "This post has already been deleted", | ||||||
| 	"post-already-restored": "This post has already been restored", | 	"post-already-restored": "This post has already been restored", | ||||||
|   | |||||||
| @@ -2,7 +2,10 @@ put: | |||||||
|   tags: |   tags: | ||||||
|     - groups |     - groups | ||||||
|   summary: join a group |   summary: join a group | ||||||
|   description: This operation joins an existing group, or causes another user to join a group. If the group is private and you are not an administrator, this method will cause that user to request membership, instead. For user _invitations_, you'll want to call `PUT /groups/{slug}/invites/{uid}`. |   description: | | ||||||
|  |     This operation joins an existing group, or causes another user to join a group. | ||||||
|  |     If the group is private and you are not an administrator, this method will cause that user to request membership, instead. | ||||||
|  |     For user _invitations_, you'll want to call `POST /groups/{slug}/invites/{uid}`. | ||||||
|   parameters: |   parameters: | ||||||
|     - in: path |     - in: path | ||||||
|       name: slug |       name: slug | ||||||
|   | |||||||
| @@ -206,11 +206,57 @@ groupsAPI.rescind = async (caller, data) => { | |||||||
|  |  | ||||||
| 	await groups.ownership.rescind(data.uid, groupName); | 	await groups.ownership.rescind(data.uid, groupName); | ||||||
| 	logGroupEvent(caller, 'group-owner-rescind', { | 	logGroupEvent(caller, 'group-owner-rescind', { | ||||||
| 		groupName: groupName, | 		groupName, | ||||||
| 		targetUid: data.uid, | 		targetUid: data.uid, | ||||||
| 	}); | 	}); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | groupsAPI.getPending = async (caller, { slug }) => { | ||||||
|  | 	const groupName = await groups.getGroupNameByGroupSlug(slug); | ||||||
|  | 	await isOwner(caller, groupName); | ||||||
|  |  | ||||||
|  | 	return await groups.getPending(groupName); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | groupsAPI.accept = async (caller, { slug, uid }) => { | ||||||
|  | 	const groupName = await groups.getGroupNameByGroupSlug(slug); | ||||||
|  |  | ||||||
|  | 	await isOwner(caller, groupName); | ||||||
|  | 	const isPending = await groups.isPending(uid, groupName); | ||||||
|  | 	if (!isPending) { | ||||||
|  | 		throw new Error('[[error:group-user-not-pending]]'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	await groups.acceptMembership(groupName, uid); | ||||||
|  | 	logGroupEvent(caller, 'group-accept-membership', { | ||||||
|  | 		groupName, | ||||||
|  | 		targetUid: uid, | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | groupsAPI.reject = async (caller, { slug, uid }) => { | ||||||
|  | 	const groupName = await groups.getGroupNameByGroupSlug(slug); | ||||||
|  |  | ||||||
|  | 	await isOwner(caller, groupName); | ||||||
|  | 	const isPending = await groups.isPending(uid, groupName); | ||||||
|  | 	if (!isPending) { | ||||||
|  | 		throw new Error('[[error:group-user-not-pending]]'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	await groups.rejectMembership(groupName, uid); | ||||||
|  | 	logGroupEvent(caller, 'group-reject-membership', { | ||||||
|  | 		groupName, | ||||||
|  | 		targetUid: uid, | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | groupsAPI.getInvites = async (caller, { slug }) => { | ||||||
|  | 	const groupName = await groups.getGroupNameByGroupSlug(slug); | ||||||
|  | 	await isOwner(caller, groupName); | ||||||
|  |  | ||||||
|  | 	return await groups.getInvites(groupName); | ||||||
|  | }; | ||||||
|  |  | ||||||
| async function isOwner(caller, groupName) { | async function isOwner(caller, groupName) { | ||||||
| 	if (typeof groupName !== 'string') { | 	if (typeof groupName !== 'string') { | ||||||
| 		throw new Error('[[error:invalid-group-name]]'); | 		throw new Error('[[error:invalid-group-name]]'); | ||||||
|   | |||||||
| @@ -47,3 +47,23 @@ Groups.rescind = async (req, res) => { | |||||||
| 	await api.groups.rescind(req, req.params); | 	await api.groups.rescind(req, req.params); | ||||||
| 	helpers.formatApiResponse(200, res); | 	helpers.formatApiResponse(200, res); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | Groups.getPending = async (req, res) => { | ||||||
|  | 	const pending = await api.groups.getPending(req, req.params); | ||||||
|  | 	helpers.formatApiResponse(200, res, { pending }); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Groups.accept = async (req, res) => { | ||||||
|  | 	await api.groups.accept(req, req.params); | ||||||
|  | 	helpers.formatApiResponse(200, res); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Groups.reject = async (req, res) => { | ||||||
|  | 	await api.groups.reject(req, req.params); | ||||||
|  | 	helpers.formatApiResponse(200, res); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Groups.getInvites = async (req, res) => { | ||||||
|  | 	const invites = await api.groups.getInvites(req, req.params); | ||||||
|  | 	helpers.formatApiResponse(200, res, { invites }); | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -123,8 +123,8 @@ Groups.get = async function (groupName, options) { | |||||||
| 	const [groupData, members, pending, invited, isMember, isPending, isInvited, isOwner] = await Promise.all([ | 	const [groupData, members, pending, invited, isMember, isPending, isInvited, isOwner] = await Promise.all([ | ||||||
| 		Groups.getGroupData(groupName), | 		Groups.getGroupData(groupName), | ||||||
| 		Groups.getOwnersAndMembers(groupName, options.uid, 0, stop), | 		Groups.getOwnersAndMembers(groupName, options.uid, 0, stop), | ||||||
| 		Groups.getUsersFromSet(`group:${groupName}:pending`, ['username', 'userslug', 'picture']), | 		Groups.getPending(groupName), | ||||||
| 		Groups.getUsersFromSet(`group:${groupName}:invited`, ['username', 'userslug', 'picture']), | 		Groups.getInvites(groupName), | ||||||
| 		Groups.isMember(options.uid, groupName), | 		Groups.isMember(options.uid, groupName), | ||||||
| 		Groups.isPending(options.uid, groupName), | 		Groups.isPending(options.uid, groupName), | ||||||
| 		Groups.isInvited(options.uid, groupName), | 		Groups.isInvited(options.uid, groupName), | ||||||
|   | |||||||
| @@ -9,6 +9,14 @@ const plugins = require('../plugins'); | |||||||
| const notifications = require('../notifications'); | const notifications = require('../notifications'); | ||||||
|  |  | ||||||
| module.exports = function (Groups) { | module.exports = function (Groups) { | ||||||
|  | 	Groups.getPending = async function (groupName) { | ||||||
|  | 		return await Groups.getUsersFromSet(`group:${groupName}:pending`, ['username', 'userslug', 'picture']); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Groups.getInvites = async function (groupName) { | ||||||
|  | 		return await Groups.getUsersFromSet(`group:${groupName}:invited`, ['username', 'userslug', 'picture']); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
| 	Groups.requestMembership = async function (groupName, uid) { | 	Groups.requestMembership = async function (groupName, uid) { | ||||||
| 		await inviteOrRequestMembership(groupName, uid, 'request'); | 		await inviteOrRequestMembership(groupName, uid, 'request'); | ||||||
| 		const { displayname } = await user.getUserFields(uid, ['username']); | 		const { displayname } = await user.getUserFields(uid, ['username']); | ||||||
| @@ -107,11 +115,4 @@ module.exports = function (Groups) { | |||||||
| 		const map = _.zipObject(checkUids, isMembers); | 		const map = _.zipObject(checkUids, isMembers); | ||||||
| 		return isArray ? uids.map(uid => !!map[uid]) : !!map[uids[0]]; | 		return isArray ? uids.map(uid => !!map[uid]) : !!map[uids[0]]; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	Groups.getPending = async function (groupName) { |  | ||||||
| 		if (!groupName) { |  | ||||||
| 			return []; |  | ||||||
| 		} |  | ||||||
| 		return await db.getSetMembers(`group:${groupName}:pending`); |  | ||||||
| 	}; |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -14,10 +14,21 @@ module.exports = function () { | |||||||
| 	setupApiRoute(router, 'head', '/:slug', [middleware.assert.group], controllers.write.groups.exists); | 	setupApiRoute(router, 'head', '/:slug', [middleware.assert.group], controllers.write.groups.exists); | ||||||
| 	setupApiRoute(router, 'put', '/:slug', [...middlewares, middleware.assert.group], controllers.write.groups.update); | 	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, 'delete', '/:slug', [...middlewares, middleware.assert.group], controllers.write.groups.delete); | ||||||
|  |  | ||||||
| 	setupApiRoute(router, 'put', '/:slug/membership/:uid', [...middlewares, middleware.assert.group], controllers.write.groups.join); | 	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); | 	setupApiRoute(router, 'delete', '/:slug/membership/:uid', [...middlewares, middleware.assert.group], controllers.write.groups.leave); | ||||||
|  |  | ||||||
| 	setupApiRoute(router, 'put', '/:slug/ownership/:uid', [...middlewares, middleware.assert.group], controllers.write.groups.grant); | 	setupApiRoute(router, 'put', '/:slug/ownership/:uid', [...middlewares, middleware.assert.group], controllers.write.groups.grant); | ||||||
| 	setupApiRoute(router, 'delete', '/:slug/ownership/:uid', [...middlewares, middleware.assert.group], controllers.write.groups.rescind); | 	setupApiRoute(router, 'delete', '/:slug/ownership/:uid', [...middlewares, middleware.assert.group], controllers.write.groups.rescind); | ||||||
|  |  | ||||||
|  | 	setupApiRoute(router, 'get', '/:slug/pending', [...middlewares, middleware.assert.group], controllers.write.groups.getPending); | ||||||
|  | 	setupApiRoute(router, 'put', '/:slug/pending/:uid', [...middlewares, middleware.assert.group], controllers.write.groups.accept); | ||||||
|  | 	setupApiRoute(router, 'delete', '/:slug/pending/:uid', [...middlewares, middleware.assert.group], controllers.write.groups.reject); | ||||||
|  |  | ||||||
|  | 	setupApiRoute(router, 'get', '/:slug/invites', [...middlewares, middleware.assert.group], controllers.write.groups.getInvites); | ||||||
|  | 	// setupApiRoute(router, 'post', '/:slug/invites', [...middlewares, middleware.assert.group], controllers.write.groups.issueInvite); | ||||||
|  | 	// setupApiRoute(router, 'put', '/:slug/invites/:uid', [...middlewares, middleware.assert.group], controllers.write.groups.acceptInvite); | ||||||
|  | 	// setupApiRoute(router, 'delete', '/:slug/invites/:uid', [...middlewares, middleware.assert.group], controllers.write.groups.rejectInvite); | ||||||
|  |  | ||||||
| 	return router; | 	return router; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -66,24 +66,6 @@ async function isInvited(socket, data) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| SocketGroups.accept = async (socket, data) => { |  | ||||||
| 	await isOwner(socket, data); |  | ||||||
| 	await groups.acceptMembership(data.groupName, data.toUid); |  | ||||||
| 	logGroupEvent(socket, 'group-accept-membership', { |  | ||||||
| 		groupName: data.groupName, |  | ||||||
| 		targetUid: data.toUid, |  | ||||||
| 	}); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| SocketGroups.reject = async (socket, data) => { |  | ||||||
| 	await isOwner(socket, data); |  | ||||||
| 	await groups.rejectMembership(data.groupName, data.toUid); |  | ||||||
| 	logGroupEvent(socket, 'group-reject-membership', { |  | ||||||
| 		groupName: data.groupName, |  | ||||||
| 		targetUid: data.toUid, |  | ||||||
| 	}); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| SocketGroups.acceptAll = async (socket, data) => { | SocketGroups.acceptAll = async (socket, data) => { | ||||||
| 	await isOwner(socket, data); | 	await isOwner(socket, data); | ||||||
| 	await acceptRejectAll(SocketGroups.accept, socket, data); | 	await acceptRejectAll(SocketGroups.accept, socket, data); | ||||||
| @@ -98,7 +80,8 @@ async function acceptRejectAll(method, socket, data) { | |||||||
| 	if (typeof data.groupName !== 'string') { | 	if (typeof data.groupName !== 'string') { | ||||||
| 		throw new Error('[[error:invalid-group-name]]'); | 		throw new Error('[[error:invalid-group-name]]'); | ||||||
| 	} | 	} | ||||||
| 	const uids = await groups.getPending(data.groupName); | 	const users = await groups.getPending(data.groupName); | ||||||
|  | 	const uids = users.map(u => u.uid); | ||||||
| 	await Promise.all(uids.map(async (uid) => { | 	await Promise.all(uids.map(async (uid) => { | ||||||
| 		await method(socket, { groupName: data.groupName, toUid: uid }); | 		await method(socket, { groupName: data.groupName, toUid: uid }); | ||||||
| 	})); | 	})); | ||||||
|   | |||||||
| @@ -944,7 +944,8 @@ describe('Groups', () => { | |||||||
| 			]); | 			]); | ||||||
| 			await requestMembership(uid1, uid2); | 			await requestMembership(uid1, uid2); | ||||||
| 			await socketGroups.rejectAll({ uid: adminUid }, { groupName: 'PrivateCanJoin' }); | 			await socketGroups.rejectAll({ uid: adminUid }, { groupName: 'PrivateCanJoin' }); | ||||||
| 			const pending = await Groups.getPending('PrivateCanJoin'); | 			let pending = await Groups.getPending('PrivateCanJoin'); | ||||||
|  | 			pending = pending.map(u => u.uid); | ||||||
| 			assert.equal(pending.length, 0); | 			assert.equal(pending.length, 0); | ||||||
| 			await requestMembership(uid1, uid2); | 			await requestMembership(uid1, uid2); | ||||||
| 			await socketGroups.acceptAll({ uid: adminUid }, { groupName: 'PrivateCanJoin' }); | 			await socketGroups.acceptAll({ uid: adminUid }, { groupName: 'PrivateCanJoin' }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user