diff --git a/public/src/admin/manage/admins-mods.js b/public/src/admin/manage/admins-mods.js index 4aa3bfceee8d..f2b3cea6e69f 100644 --- a/public/src/admin/manage/admins-mods.js +++ b/public/src/admin/manage/admins-mods.js @@ -63,16 +63,13 @@ define('admin/manage/admins-mods', ['translator', 'benchpress', 'autocomplete', bootbox.confirm('[[admin/manage/users:alerts.confirm-remove-global-mod]]', function (confirm) { if (confirm) { - socket.emit('admin.groups.leave', { uid: uid, groupName: 'Global Moderators' }, function (err) { - if (err) { - return app.alertError(err.message); - } + api.del('/groups/global-moderators/membership/' + uid, undefined, () => { app.alertSuccess('[[admin/manage/users:alerts.remove-global-mod-success]]'); userCard.remove(); if (!$('.global-moderator-area').children().length) { $('#no-global-mods-warning').removeClass('hidden'); } - }); + }, err => app.alertError(err.status.message)); } }); }); diff --git a/public/src/admin/manage/group.js b/public/src/admin/manage/group.js index 3a31f89c6318..4cfef7eeaa72 100644 --- a/public/src/admin/manage/group.js +++ b/public/src/admin/manage/group.js @@ -1,6 +1,5 @@ 'use strict'; - define('admin/manage/group', [ 'forum/groups/memberlist', 'iconSelect', @@ -8,7 +7,8 @@ define('admin/manage/group', [ 'translator', 'categorySelector', 'groupSearch', -], function (memberList, iconSelect, colorpicker, translator, categorySelector, groupSearch) { + 'api', +], function (memberList, iconSelect, colorpicker, translator, categorySelector, groupSearch, api) { var Groups = {}; Groups.init = function () { @@ -137,15 +137,9 @@ define('admin/manage/group', [ if (!confirm) { return; } - socket.emit('admin.groups.leave', { - uid: uid, - groupName: groupName, - }, function (err) { - if (err) { - return app.alertError(err.message); - } + api.del('/groups/' + ajaxify.data.group.slug + '/membership/' + uid, undefined, () => { userRow.slideUp().remove(); - }); + }, err => app.alertError(err.status.message)); }); break; default: diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index 64a1ee8c96e6..858ffdadab5f 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -108,12 +108,9 @@ define('admin/manage/users', ['translator', 'benchpress', 'autocomplete', 'api'] var groupCard = $(this).parents('[data-group-name]'); var groupName = groupCard.attr('data-group-name'); var uid = $(this).parents('[data-uid]').attr('data-uid'); - socket.emit('admin.groups.leave', { uid: uid, groupName: groupName }, function (err) { - if (err) { - return app.alertError(err); - } + api.del('/groups/' + utils.slugify(groupName) + '/membership/' + uid, undefined, () => { groupCard.remove(); - }); + }, err => app.alertError(err.status.message)); return false; }); }); diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index ba2a0cb3e912..03db28e96f67 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -109,8 +109,11 @@ define('forum/groups/details', [ api.put('/groups/' + ajaxify.data.group.slug + '/membership/' + (uid || app.user.uid), undefined, () => ajaxify.refresh(), err => app.alertError(err.status.message)); break; - case 'leave': // intentional fall-throughs! - case 'accept': + case 'leave': + api.del('/groups/' + ajaxify.data.group.slug + '/membership/' + (uid || app.user.uid), undefined, () => ajaxify.refresh(), err => app.alertError(err.status.message)); + break; + + case 'accept': // intentional fall-throughs! case 'reject': case 'issueInvite': case 'rescindInvite': diff --git a/public/src/client/groups/memberlist.js b/public/src/client/groups/memberlist.js index 615a3e8fce51..e0a3db3dd802 100644 --- a/public/src/client/groups/memberlist.js +++ b/public/src/client/groups/memberlist.js @@ -1,7 +1,6 @@ 'use strict'; - -define('forum/groups/memberlist', function () { +define('forum/groups/memberlist', ['api'], function (api) { var MemberList = {}; var searchInterval; var groupName; @@ -83,7 +82,13 @@ define('forum/groups/memberlist', function () { if (groupName === 'administrators') { socket.emit('admin.user.makeAdmins', uids, done); } else { - socket.emit('groups.addMember', { groupName: groupName, uid: uids }, done); + var requests = uids.map(function (uid) { + return api.put('/groups/' + ajaxify.data.group.slug + '/membership/' + uid); + }); + + $.when(requests) + .done(done) + .fail(err => app.alertError(err.status.message)); } } diff --git a/src/controllers/write/groups.js b/src/controllers/write/groups.js index f04d17a3ce71..db48853bd6a8 100644 --- a/src/controllers/write/groups.js +++ b/src/controllers/write/groups.js @@ -1,9 +1,13 @@ 'use strict'; +const validator = require('validator'); + const user = require('../../user'); const groups = require('../../groups'); const events = require('../../events'); const meta = require('../../meta'); +const utils = require('../../utils'); +const notifications = require('../../notifications'); const helpers = require('../helpers'); @@ -85,6 +89,42 @@ Groups.join = async (req, res) => { }); }; +Groups.leave = async (req, res) => { + const [group, userExists] = await Promise.all([ + groups.getByGroupslug(req.params.slug, { + uid: req.params.uid, + }), + user.exists(req.params.uid), + ]); + + if (!userExists) { + throw new Error('[[error:invalid-uid]]'); + } else if (group.disableLeave) { + throw new Error('[[error:group-leave-disabled]]'); + } else if (!group.isMember) { + // No change + return helpers.formatApiResponse(200, res); + } + + await groups.leave(group.name, req.params.uid); + + // Notify owners of user having left + const username = await user.getUserField(req.params.uid, 'username'); + const notification = await notifications.create({ + type: 'group-leave', + bodyShort: '[[groups:membership.leave.notification-title, ' + username + ', ' + group.name + ']]', + nid: 'group:' + validator.escape(group.name) + ':uid:' + req.params.uid + ':group-leave', + path: '/groups/' + utils.slugify(group.name), + }); + const uids = await groups.getOwners(group.name); + await notifications.push(notification, uids); + + helpers.formatApiResponse(200, res); + logGroupEvent(req, 'group-leave', { + groupName: group.name, + }); +}; + function logGroupEvent(req, event, additional) { events.log({ type: event, diff --git a/src/routes/write/groups.js b/src/routes/write/groups.js index 474bc60f0952..275e0ec30283 100644 --- a/src/routes/write/groups.js +++ b/src/routes/write/groups.js @@ -13,36 +13,7 @@ module.exports = function () { setupApiRoute(router, '/', middleware, [...middlewares, middleware.checkRequired.bind(null, ['name']), middleware.exposePrivilegeSet], 'post', controllers.write.groups.create); 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) { - // errorHandler.handle(err, res); - // }); - // }); - - // app.delete('/:slug/membership', apiMiddleware.requireUser, middleware.exposeGroupName, apiMiddleware.validateGroup, function(req, res) { - // Groups.isMember(req.user.uid, res.locals.groupName, function(err, isMember) { - // if (isMember) { - // Groups.leave(res.locals.groupName, req.user.uid, function(err) { - // errorHandler.handle(err, res); - // }); - // } else { - // errorHandler.respond(400, res); - // } - // }); - // }); - - // app.delete('/:slug/membership/:uid', middleware.exposeGroupName, apiMiddleware.validateGroup, apiMiddleware.requireUser, apiMiddleware.requireAdmin, function(req, res) { - // Groups.isMember(req.params.uid, res.locals.groupName, function(err, isMember) { - // if (isMember) { - // Groups.leave(res.locals.groupName, req.params.uid, function(err) { - // errorHandler.handle(err, res); - // }); - // } else { - // errorHandler.respond(400, res); - // } - // }); - // }); + setupApiRoute(router, '/:slug/membership/:uid', middleware, [...middlewares, middleware.assertGroup, middleware.exposePrivileges], 'delete', controllers.write.groups.leave); return router; }; diff --git a/src/socket.io/admin/groups.js b/src/socket.io/admin/groups.js index e98bdf66a1ae..dc875ccfe81b 100644 --- a/src/socket.io/admin/groups.js +++ b/src/socket.io/admin/groups.js @@ -39,6 +39,8 @@ Groups.join = async (socket, data) => { }; Groups.leave = async function (socket, data) { + sockets.warnDeprecated(socket, 'DELETE /api/v1/groups/:slug/membership/:uid'); + if (!data) { throw new Error('[[error:invalid-data]]'); } diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 850e18435123..706b2b6b7fe9 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -69,6 +69,8 @@ SocketGroups.join = async (socket, data) => { }; SocketGroups.leave = async (socket, data) => { + sockets.warnDeprecated(socket, 'DELETE /api/v1/groups/:slug/membership/:uid'); + if (socket.uid <= 0) { throw new Error('[[error:invalid-uid]]'); }