From 22c541d95f405e85238cafcf5316e83fb4cb79f4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 22 Jan 2015 14:13:39 -0500 Subject: [PATCH 1/6] closes #2643 --- src/user/delete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/delete.js b/src/user/delete.js index b13fa1aa97..e6cdcebb19 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -85,7 +85,7 @@ module.exports = function(User) { db.deleteAll(keys, next); }, function(next) { - deleteUserIps(uids, next); + deleteUserIps(uid, next); }, function(next) { deleteUserFromFollowers(uid, next); From 46034845583119ac0732e2b639878f5a18339b4e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 22 Jan 2015 14:18:19 -0500 Subject: [PATCH 2/6] user.delete test --- tests/user.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/user.js b/tests/user.js index 3ef541623b..381c6f0a44 100644 --- a/tests/user.js +++ b/tests/user.js @@ -171,6 +171,28 @@ describe('User', function() { }); }); + describe('.delete()', function() { + var uid; + before(function(done) { + User.create({username: 'userToDelete', password: '123456', email: 'delete@me.com'}, function(err, newUid) { + assert.ifError(err); + uid = newUid; + done(); + }); + }); + + it('should delete a user account', function(done) { + User.delete(uid, function(err) { + assert.ifError(err); + User.exists('userToDelete', function(err, exists) { + assert.ifError(err); + assert.equal(exists, false); + done(); + }); + }); + }); + }); + after(function() { db.flushdb(); }); From 740c52fc4f55ad65c799542a5114b083c2c10832 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 22 Jan 2015 14:19:23 -0500 Subject: [PATCH 3/6] missing semicolon --- src/user/delete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/delete.js b/src/user/delete.js index e6cdcebb19..3440e810a4 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -127,7 +127,7 @@ module.exports = function(User) { } db.delete('uid:' + uid + ':ip', callback); }); - }) + }); } function deleteUserFromFollowers(uid, callback) { From d2217b813740cc4eda6eb0af846f5f1c3e67cf84 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 22 Jan 2015 14:19:59 -0500 Subject: [PATCH 4/6] closed #2641 --- public/language/en_GB/error.json | 1 + public/src/client/groups/details.js | 2 +- src/groups.js | 21 +++++++++++++++++---- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index fed0c807c9..7e21a1290a 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -66,6 +66,7 @@ "group-already-exists": "Group already exists", "group-name-change-not-allowed": "Group name change not allowed", "group-already-member": "You are already part of this group", + "group-needs-owner": "This group requires at least one owner", "post-already-deleted": "This post has already been deleted", "post-already-restored": "This post has already been restored", diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index b667176ae9..09826fb673 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -34,7 +34,7 @@ define('forum/groups/details', ['iconSelect', 'vendor/colorpicker/colorpicker', if (!err) { ownerFlagEl.toggleClass('invisible'); } else { - app.alertError(err); + app.alertError(err.message); } }); break; diff --git a/src/groups.js b/src/groups.js index fbb138dce5..beeddd19a9 100644 --- a/src/groups.js +++ b/src/groups.js @@ -659,8 +659,8 @@ var async = require('async'), Groups.requestMembership = function(groupName, uid, callback) { async.parallel({ - exists: async.apply(Groups.isMember, uid, groupName), - isMember: async.apply(Groups.exists, groupName) + exists: async.apply(Groups.exists, groupName), + isMember: async.apply(Groups.isMember, uid, groupName) }, function(err, checks) { if (!checks.exists) { return callback(new Error('[[error:no-group]]')); @@ -694,7 +694,12 @@ var async = require('async'), Groups.leave = function(groupName, uid, callback) { callback = callback || function() {}; - db.sortedSetRemove('group:' + groupName + ':members', uid, function(err) { + var tasks = [ + async.apply(db.sortedSetRemove, 'group:' + groupName + ':members', uid), + async.apply(db.setRemove, 'group:' + groupName + ':owners', uid) + ]; + + async.parallel(tasks, function(err) { if (err) { return callback(err); } @@ -891,7 +896,15 @@ var async = require('async'), Groups.ownership.rescind = function(toUid, groupName, callback) { // Note: No ownership checking is done here on purpose! - db.setRemove('group:' + groupName + ':owners', toUid, callback); + + // If the owners set only contains one member, error out! + db.setCount('group:' + groupName + ':owners', function(err, numOwners) { + if (numOwners <= 1) { + return callback(new Error('[[error:group-needs-owner]]')); + } + + db.setRemove('group:' + groupName + ':owners', toUid, callback); + }); }; Groups.search = function(query, options, callback) { From a5265779275ae51727beba2870c8f99a64e91687 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 22 Jan 2015 15:08:23 -0500 Subject: [PATCH 5/6] only attempt to remove address bar if current position is at the top of screen --- public/src/app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/src/app.js b/public/src/app.js index d7097f91e7..e24e2500dd 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -254,7 +254,9 @@ app.uid = null; app.replaceSelfLinks(); setTimeout(function () { - window.scrollTo(0, 1); // rehide address bar on mobile after page load completes. + if (window.scrollY === 0) { + window.scrollTo(0, 1); // rehide address bar on mobile after page load completes. + } }, 100); }; From 0c2d2a28ba8c8a678d4818d2da2595c07bca51b6 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 22 Jan 2015 16:01:17 -0500 Subject: [PATCH 6/6] admins can bypass the request step of a group and just join it, and they also become owners when they join a group --- src/groups.js | 21 +++++++++++++++++---- src/socket.io/groups.js | 10 ++++++++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/groups.js b/src/groups.js index beeddd19a9..e7c1eed77b 100644 --- a/src/groups.js +++ b/src/groups.js @@ -631,10 +631,23 @@ var async = require('async'), Groups.exists(groupName, function(err, exists) { if (exists) { - db.sortedSetAdd('group:' + groupName + ':members', Date.now(), uid, callback); - plugins.fireHook('action:group.join', { - groupName: groupName, - uid: uid + var tasks = [ + async.apply(db.sortedSetAdd, 'group:' + groupName + ':members', Date.now(), uid) + ]; + + user.isAdministrator(uid, function(err, isAdmin) { + if (isAdmin) { + tasks.push(async.apply(db.setAdd, 'group:' + groupName + ':owners', uid)); + } + + async.parallel(tasks, function(err) { + plugins.fireHook('action:group.join', { + groupName: groupName, + uid: uid + }); + + callback(); + }); }); } else { Groups.create({ diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index e51c2f6062..d85640e9fa 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -2,6 +2,9 @@ var groups = require('../groups'), meta = require('../meta'), + user = require('../user'), + + async = require('async'), SocketGroups = {}; @@ -11,8 +14,11 @@ SocketGroups.join = function(socket, data, callback) { } if (meta.config.allowPrivateGroups !== '0') { - groups.isPrivate(data.groupName, function(err, isPrivate) { - if (isPrivate) { + async.parallel({ + isAdmin: async.apply(user.isAdministrator, socket.uid), + isPrivate: async.apply(groups.isPrivate, data.groupName) + }, function(err, checks) { + if (checks.isPrivate && !checks.isAdmin) { groups.requestMembership(data.groupName, socket.uid, callback); } else { groups.join(data.groupName, socket.uid, callback);