mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +01:00 
			
		
		
		
	feat(writeapi): added group joining and deletion
This commit is contained in:
		| @@ -17,7 +17,7 @@ | ||||
| 		"jquery": true, | ||||
| 		"amd": true, | ||||
| 		"browser": true, | ||||
| 		"es6": false | ||||
| 		"es6": true | ||||
| 	}, | ||||
| 	"rules": { | ||||
| 		"block-scoped-var": "off", | ||||
| @@ -31,9 +31,9 @@ | ||||
| 		"prefer-template": "off" | ||||
| 	}, | ||||
| 	"parserOptions": { | ||||
| 		"ecmaVersion": 5, | ||||
| 		"ecmaVersion": 6, | ||||
| 		"ecmaFeatures": { | ||||
| 			"arrowFunctions": false, | ||||
| 			"arrowFunctions": true, | ||||
| 			"classes": false, | ||||
| 			"defaultParams": false, | ||||
| 			"destructuring": false, | ||||
|   | ||||
| @@ -419,6 +419,43 @@ paths: | ||||
|                     $ref: '#/components/schemas/Status' | ||||
|                   response: | ||||
|                     $ref: components/schemas/GroupObject.yaml#/GroupDataObject | ||||
|     delete: | ||||
|       tags: | ||||
|         - groups | ||||
|       summary: Delete an existing group | ||||
|       description: This operation deletes an existing group, all members within this group will cease to be members after the group is deleted. | ||||
|       responses: | ||||
|         '200': | ||||
|           description: group successfully deleted | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: object | ||||
|                 properties: | ||||
|                   status: | ||||
|                     $ref: '#/components/schemas/Status' | ||||
|                   response: | ||||
|                     type: object | ||||
|                     properties: {} | ||||
|   /groups/{slug}/membership/{uid}: | ||||
|     put: | ||||
|       tags: | ||||
|         - groups | ||||
|       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}`. | ||||
|       responses: | ||||
|         '200': | ||||
|           description: group successfully joined, or membership requested | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: object | ||||
|                 properties: | ||||
|                   status: | ||||
|                     $ref: '#/components/schemas/Status' | ||||
|                   response: | ||||
|                     type: object | ||||
|                     properties: {} | ||||
| components: | ||||
|   schemas: | ||||
|     Status: | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| define('admin/manage/admins-mods', ['translator', 'benchpress', 'autocomplete'], function (translator, Benchpress, autocomplete) { | ||||
| define('admin/manage/admins-mods', ['translator', 'benchpress', 'autocomplete', 'api'], function (translator, Benchpress, autocomplete, api) { | ||||
| 	var AdminsMods = {}; | ||||
|  | ||||
| 	AdminsMods.init = function () { | ||||
| @@ -42,13 +42,7 @@ define('admin/manage/admins-mods', ['translator', 'benchpress', 'autocomplete'], | ||||
| 		}); | ||||
|  | ||||
| 		autocomplete.user($('#global-mod-search'), function (ev, ui) { | ||||
| 			socket.emit('admin.groups.join', { | ||||
| 				groupName: 'Global Moderators', | ||||
| 				uid: ui.item.user.uid, | ||||
| 			}, function (err) { | ||||
| 				if (err) { | ||||
| 					return app.alertError(err.message); | ||||
| 				} | ||||
| 			api.put('/groups/global-moderators/membership/' + ui.item.user.uid, undefined, () => { | ||||
| 				app.alertSuccess('[[admin/manage/users:alerts.make-global-mod-success]]'); | ||||
| 				$('#global-mod-search').val(''); | ||||
|  | ||||
| @@ -60,7 +54,7 @@ define('admin/manage/admins-mods', ['translator', 'benchpress', 'autocomplete'], | ||||
| 					$('.global-moderator-area').prepend(html); | ||||
| 					$('#no-global-mods-warning').addClass('hidden'); | ||||
| 				}); | ||||
| 			}); | ||||
| 			}, err => app.alertError(err.status.message)); | ||||
| 		}); | ||||
|  | ||||
| 		$('.global-moderator-area').on('click', '.remove-user-icon', function () { | ||||
|   | ||||
| @@ -93,15 +93,12 @@ define('admin/manage/users', ['translator', 'benchpress', 'autocomplete', 'api'] | ||||
| 					modal.on('shown.bs.modal', function () { | ||||
| 						autocomplete.group(modal.find('.group-search'), function (ev, ui) { | ||||
| 							var uid = $(ev.target).attr('data-uid'); | ||||
| 							socket.emit('admin.groups.join', { uid: uid, groupName: ui.item.value }, function (err) { | ||||
| 								if (err) { | ||||
| 									return app.alertError(err); | ||||
| 								} | ||||
| 							api.put('/groups/' + ui.item.group.slug + '/membership/' + uid, undefined, () => { | ||||
| 								ui.item.group.nameEscaped = translator.escape(ui.item.group.displayName); | ||||
| 								app.parseAndTranslate('admin/partials/manage_user_groups', { users: [{ groups: [ui.item.group] }] }, function (html) { | ||||
| 									$('[data-uid=' + uid + '] .group-area').append(html.find('.group-area').html()); | ||||
| 								}); | ||||
| 							}); | ||||
| 							}, err => app.alertError(err.status.message)); | ||||
| 						}); | ||||
| 					}); | ||||
| 					modal.on('click', '.group-area a', function () { | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const user = require('../../user'); | ||||
| const groups = require('../../groups'); | ||||
| const events = require('../../events'); | ||||
| const meta = require('../../meta'); | ||||
|  | ||||
| const helpers = require('../helpers'); | ||||
|  | ||||
| @@ -26,6 +28,66 @@ Groups.create = async (req, res) => { | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| Groups.delete = async (req, res) => { | ||||
| 	const group = await groups.getByGroupslug(req.params.slug, { | ||||
| 		uid: req.user.uid, | ||||
| 	}); | ||||
|  | ||||
| 	if (groups.ephemeralGroups.includes(group.slug)) { | ||||
| 		throw new Error('[[error:not-allowed]]'); | ||||
| 	} | ||||
|  | ||||
| 	if (group.system || (!group.isOwner && !res.locals.privileges.isAdmin && !res.locals.privileges.isGmod)) { | ||||
| 		throw new Error('[[error:no-privileges]]'); | ||||
| 	} | ||||
|  | ||||
| 	await groups.destroy(group.name); | ||||
| 	helpers.formatApiResponse(200, res); | ||||
| 	logGroupEvent(req, 'group-delete', { | ||||
| 		groupName: group.name, | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| Groups.join = async (req, res) => { | ||||
| 	const group = await groups.getByGroupslug(req.params.slug, { | ||||
| 		uid: req.params.uid, | ||||
| 	}); | ||||
| 	const [isCallerOwner, userExists] = await Promise.all([ | ||||
| 		groups.ownership.isOwner(req.user.uid, group.name), | ||||
| 		user.exists(req.user.uid), | ||||
| 	]); | ||||
|  | ||||
| 	if (group.isMember) { | ||||
| 		// No change | ||||
| 		return helpers.formatApiResponse(200, res); | ||||
| 	} else if (!userExists) { | ||||
| 		throw new Error('[[error:invalid-uid]]'); | ||||
| 	} | ||||
|  | ||||
| 	// console.log(res.locals.privileges); | ||||
| 	// return res.sendStatus(200); | ||||
|  | ||||
| 	if (!res.locals.privileges.isAdmin) { | ||||
| 		// Admin and privilege groups unjoinable client-side | ||||
| 		if (group.name === 'administrators' || groups.isPrivilegeGroup(group.name)) { | ||||
| 			throw new Error('[[error:not-allowed]]'); | ||||
| 		} | ||||
|  | ||||
| 		if (!isCallerOwner && parseInt(meta.config.allowPrivateGroups, 10) !== 0 && group.private) { | ||||
| 			await groups.requestMembership(group.name, req.params.uid); | ||||
| 		} else { | ||||
| 			await groups.join(group.name, req.params.uid); | ||||
| 		} | ||||
| 	} else { | ||||
| 		await groups.join(group.name, req.params.uid); | ||||
| 	} | ||||
|  | ||||
| 	helpers.formatApiResponse(200, res); | ||||
| 	logGroupEvent(req, 'group-join', { | ||||
| 		groupName: group.name, | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| function logGroupEvent(req, event, additional) { | ||||
| 	events.log({ | ||||
| 		type: event, | ||||
|   | ||||
| @@ -190,6 +190,7 @@ Groups.getOwnersAndMembers = async function (groupName, uid, start, stop) { | ||||
| }; | ||||
|  | ||||
| Groups.getByGroupslug = async function (slug, options) { | ||||
| 	options = options || {}; | ||||
| 	const groupName = await db.getObjectField('groupslug:groupname', slug); | ||||
| 	if (!groupName) { | ||||
| 		throw new Error('[[error:no-group]]'); | ||||
|   | ||||
							
								
								
									
										20
									
								
								src/middleware/assert.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/middleware/assert.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| /** | ||||
|  * The middlewares here strictly act to "assert" validity of the incoming | ||||
|  * payload and throw an error otherwise. | ||||
|  */ | ||||
|  | ||||
| const groups = require('../groups'); | ||||
|  | ||||
| module.exports = function (middleware) { | ||||
| 	middleware.assertGroup = async (req, res, next) => { | ||||
| 		const name = await groups.getGroupNameByGroupSlug(req.params.slug); | ||||
| 		const exists = await groups.exists(name); | ||||
| 		if (!exists) { | ||||
| 			throw new Error('[[error:no-group]]'); | ||||
| 		} | ||||
|  | ||||
| 		next(); | ||||
| 	}; | ||||
| }; | ||||
| @@ -60,6 +60,7 @@ require('./maintenance')(middleware); | ||||
| require('./user')(middleware); | ||||
| require('./headers')(middleware); | ||||
| require('./expose')(middleware); | ||||
| require('./assert')(middleware); | ||||
|  | ||||
| middleware.stripLeadingSlashes = function stripLeadingSlashes(req, res, next) { | ||||
| 	var target = req.originalUrl.replace(nconf.get('relative_path'), ''); | ||||
|   | ||||
| @@ -11,33 +11,8 @@ module.exports = function () { | ||||
| 	const middlewares = [middleware.authenticate]; | ||||
|  | ||||
| 	setupApiRoute(router, '/', middleware, [...middlewares, middleware.checkRequired.bind(null, ['name']), middleware.exposePrivilegeSet], 'post', controllers.write.groups.create); | ||||
| 	// setupApiRoute(router, '/:slug', middleware, [...middlewares, middleware.exposePrivilegeSet], 'delete', controllers.write.groups.delete); | ||||
|  | ||||
| 	// app.delete('/:slug', apiMiddleware.requireUser, middleware.exposeGroupName, apiMiddleware.validateGroup, apiMiddleware.requireGroupOwner, function(req, res) { | ||||
| 	// 	Groups.destroy(res.locals.groupName, function(err) { | ||||
| 	// 		errorHandler.handle(err, res); | ||||
| 	// 	}); | ||||
| 	// }); | ||||
|  | ||||
| 	// app.put('/:slug/membership', apiMiddleware.requireUser, middleware.exposeGroupName, apiMiddleware.validateGroup, function(req, res) { | ||||
| 	// 	if (Meta.config.allowPrivateGroups !== '0') { | ||||
| 	// 		Groups.isPrivate(res.locals.groupName, function(err, isPrivate) { | ||||
| 	// 			if (isPrivate) { | ||||
| 	// 				Groups.requestMembership(res.locals.groupName, req.user.uid, function(err) { | ||||
| 	// 					errorHandler.handle(err, res); | ||||
| 	// 				}); | ||||
| 	// 			} else { | ||||
| 	// 				Groups.join(res.locals.groupName, req.user.uid, function(err) { | ||||
| 	// 					errorHandler.handle(err, res); | ||||
| 	// 				}); | ||||
| 	// 			} | ||||
| 	// 		}); | ||||
| 	// 	} else { | ||||
| 	// 		Groups.join(res.locals.groupName, req.user.uid, function(err) { | ||||
| 	// 			errorHandler.handle(err, res); | ||||
| 	// 		}); | ||||
| 	// 	} | ||||
| 	// }); | ||||
| 	setupApiRoute(router, '/:slug', middleware, [...middlewares, middleware.assertGroup, middleware.exposePrivileges], 'delete', controllers.write.groups.delete); | ||||
| 	setupApiRoute(router, '/:slug/membership/:uid', middleware, [...middlewares, middleware.assertGroup, middleware.exposePrivileges], 'put', controllers.write.groups.join); | ||||
|  | ||||
| 	// app.put('/:slug/membership/:uid', middleware.exposeGroupName, apiMiddleware.validateGroup, apiMiddleware.requireUser, apiMiddleware.requireAdmin, function(req, res) { | ||||
| 	// 	Groups.join(res.locals.groupName, req.params.uid, function(err) { | ||||
|   | ||||
| @@ -24,6 +24,8 @@ Groups.create = async function (socket, data) { | ||||
| }; | ||||
|  | ||||
| Groups.join = async (socket, data) => { | ||||
| 	sockets.warnDeprecated(socket, 'PUT /api/v1/groups/:slug/membership/:uid'); | ||||
|  | ||||
| 	if (!data) { | ||||
| 		throw new Error('[[error:invalid-data]]'); | ||||
| 	} | ||||
|   | ||||
| @@ -19,6 +19,8 @@ SocketGroups.before = async (socket, method, data) => { | ||||
| }; | ||||
|  | ||||
| SocketGroups.join = async (socket, data) => { | ||||
| 	sockets.warnDeprecated(socket, 'PUT /api/v1/groups/:slug/membership/:uid'); | ||||
|  | ||||
| 	if (socket.uid <= 0) { | ||||
| 		throw new Error('[[error:invalid-uid]]'); | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user