mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +01:00 
			
		
		
		
	feat(writeapi): added group joining and deletion
This commit is contained in:
		| @@ -17,7 +17,7 @@ | |||||||
| 		"jquery": true, | 		"jquery": true, | ||||||
| 		"amd": true, | 		"amd": true, | ||||||
| 		"browser": true, | 		"browser": true, | ||||||
| 		"es6": false | 		"es6": true | ||||||
| 	}, | 	}, | ||||||
| 	"rules": { | 	"rules": { | ||||||
| 		"block-scoped-var": "off", | 		"block-scoped-var": "off", | ||||||
| @@ -31,9 +31,9 @@ | |||||||
| 		"prefer-template": "off" | 		"prefer-template": "off" | ||||||
| 	}, | 	}, | ||||||
| 	"parserOptions": { | 	"parserOptions": { | ||||||
| 		"ecmaVersion": 5, | 		"ecmaVersion": 6, | ||||||
| 		"ecmaFeatures": { | 		"ecmaFeatures": { | ||||||
| 			"arrowFunctions": false, | 			"arrowFunctions": true, | ||||||
| 			"classes": false, | 			"classes": false, | ||||||
| 			"defaultParams": false, | 			"defaultParams": false, | ||||||
| 			"destructuring": false, | 			"destructuring": false, | ||||||
|   | |||||||
| @@ -419,6 +419,43 @@ paths: | |||||||
|                     $ref: '#/components/schemas/Status' |                     $ref: '#/components/schemas/Status' | ||||||
|                   response: |                   response: | ||||||
|                     $ref: components/schemas/GroupObject.yaml#/GroupDataObject |                     $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: | components: | ||||||
|   schemas: |   schemas: | ||||||
|     Status: |     Status: | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| 'use strict'; | '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 = {}; | 	var AdminsMods = {}; | ||||||
|  |  | ||||||
| 	AdminsMods.init = function () { | 	AdminsMods.init = function () { | ||||||
| @@ -42,13 +42,7 @@ define('admin/manage/admins-mods', ['translator', 'benchpress', 'autocomplete'], | |||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		autocomplete.user($('#global-mod-search'), function (ev, ui) { | 		autocomplete.user($('#global-mod-search'), function (ev, ui) { | ||||||
| 			socket.emit('admin.groups.join', { | 			api.put('/groups/global-moderators/membership/' + ui.item.user.uid, undefined, () => { | ||||||
| 				groupName: 'Global Moderators', |  | ||||||
| 				uid: ui.item.user.uid, |  | ||||||
| 			}, function (err) { |  | ||||||
| 				if (err) { |  | ||||||
| 					return app.alertError(err.message); |  | ||||||
| 				} |  | ||||||
| 				app.alertSuccess('[[admin/manage/users:alerts.make-global-mod-success]]'); | 				app.alertSuccess('[[admin/manage/users:alerts.make-global-mod-success]]'); | ||||||
| 				$('#global-mod-search').val(''); | 				$('#global-mod-search').val(''); | ||||||
|  |  | ||||||
| @@ -60,7 +54,7 @@ define('admin/manage/admins-mods', ['translator', 'benchpress', 'autocomplete'], | |||||||
| 					$('.global-moderator-area').prepend(html); | 					$('.global-moderator-area').prepend(html); | ||||||
| 					$('#no-global-mods-warning').addClass('hidden'); | 					$('#no-global-mods-warning').addClass('hidden'); | ||||||
| 				}); | 				}); | ||||||
| 			}); | 			}, err => app.alertError(err.status.message)); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		$('.global-moderator-area').on('click', '.remove-user-icon', function () { | 		$('.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 () { | 					modal.on('shown.bs.modal', function () { | ||||||
| 						autocomplete.group(modal.find('.group-search'), function (ev, ui) { | 						autocomplete.group(modal.find('.group-search'), function (ev, ui) { | ||||||
| 							var uid = $(ev.target).attr('data-uid'); | 							var uid = $(ev.target).attr('data-uid'); | ||||||
| 							socket.emit('admin.groups.join', { uid: uid, groupName: ui.item.value }, function (err) { | 							api.put('/groups/' + ui.item.group.slug + '/membership/' + uid, undefined, () => { | ||||||
| 								if (err) { |  | ||||||
| 									return app.alertError(err); |  | ||||||
| 								} |  | ||||||
| 								ui.item.group.nameEscaped = translator.escape(ui.item.group.displayName); | 								ui.item.group.nameEscaped = translator.escape(ui.item.group.displayName); | ||||||
| 								app.parseAndTranslate('admin/partials/manage_user_groups', { users: [{ groups: [ui.item.group] }] }, function (html) { | 								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()); | 									$('[data-uid=' + uid + '] .group-area').append(html.find('.group-area').html()); | ||||||
| 								}); | 								}); | ||||||
| 							}); | 							}, err => app.alertError(err.status.message)); | ||||||
| 						}); | 						}); | ||||||
| 					}); | 					}); | ||||||
| 					modal.on('click', '.group-area a', function () { | 					modal.on('click', '.group-area a', function () { | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
|  | const user = require('../../user'); | ||||||
| const groups = require('../../groups'); | const groups = require('../../groups'); | ||||||
| const events = require('../../events'); | const events = require('../../events'); | ||||||
|  | const meta = require('../../meta'); | ||||||
|  |  | ||||||
| const helpers = require('../helpers'); | 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) { | function logGroupEvent(req, event, additional) { | ||||||
| 	events.log({ | 	events.log({ | ||||||
| 		type: event, | 		type: event, | ||||||
|   | |||||||
| @@ -190,6 +190,7 @@ Groups.getOwnersAndMembers = async function (groupName, uid, start, stop) { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| Groups.getByGroupslug = async function (slug, options) { | Groups.getByGroupslug = async function (slug, options) { | ||||||
|  | 	options = options || {}; | ||||||
| 	const groupName = await db.getObjectField('groupslug:groupname', slug); | 	const groupName = await db.getObjectField('groupslug:groupname', slug); | ||||||
| 	if (!groupName) { | 	if (!groupName) { | ||||||
| 		throw new Error('[[error:no-group]]'); | 		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('./user')(middleware); | ||||||
| require('./headers')(middleware); | require('./headers')(middleware); | ||||||
| require('./expose')(middleware); | require('./expose')(middleware); | ||||||
|  | require('./assert')(middleware); | ||||||
|  |  | ||||||
| middleware.stripLeadingSlashes = function stripLeadingSlashes(req, res, next) { | middleware.stripLeadingSlashes = function stripLeadingSlashes(req, res, next) { | ||||||
| 	var target = req.originalUrl.replace(nconf.get('relative_path'), ''); | 	var target = req.originalUrl.replace(nconf.get('relative_path'), ''); | ||||||
|   | |||||||
| @@ -11,33 +11,8 @@ module.exports = function () { | |||||||
| 	const middlewares = [middleware.authenticate]; | 	const middlewares = [middleware.authenticate]; | ||||||
|  |  | ||||||
| 	setupApiRoute(router, '/', middleware, [...middlewares, middleware.checkRequired.bind(null, ['name']), middleware.exposePrivilegeSet], 'post', controllers.write.groups.create); | 	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); | 	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.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); |  | ||||||
| 	// 		}); |  | ||||||
| 	// 	} |  | ||||||
| 	// }); |  | ||||||
|  |  | ||||||
| 	// app.put('/:slug/membership/:uid', middleware.exposeGroupName, apiMiddleware.validateGroup, apiMiddleware.requireUser, apiMiddleware.requireAdmin, function(req, res) { | 	// 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) { | 	// 	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) => { | Groups.join = async (socket, data) => { | ||||||
|  | 	sockets.warnDeprecated(socket, 'PUT /api/v1/groups/:slug/membership/:uid'); | ||||||
|  |  | ||||||
| 	if (!data) { | 	if (!data) { | ||||||
| 		throw new Error('[[error:invalid-data]]'); | 		throw new Error('[[error:invalid-data]]'); | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -19,6 +19,8 @@ SocketGroups.before = async (socket, method, data) => { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| SocketGroups.join = async (socket, data) => { | SocketGroups.join = async (socket, data) => { | ||||||
|  | 	sockets.warnDeprecated(socket, 'PUT /api/v1/groups/:slug/membership/:uid'); | ||||||
|  |  | ||||||
| 	if (socket.uid <= 0) { | 	if (socket.uid <= 0) { | ||||||
| 		throw new Error('[[error:invalid-uid]]'); | 		throw new Error('[[error:invalid-uid]]'); | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user