diff --git a/public/src/client/account/followers.js b/public/src/client/account/followers.js index 71b3dd38da..8c98a6bacd 100644 --- a/public/src/client/account/followers.js +++ b/public/src/client/account/followers.js @@ -1,9 +1,43 @@ -define('forum/account/followers', ['forum/account/header'], function(header) { +'use strict'; + +/* globals define, socket, utils */ + +define('forum/account/followers', ['forum/account/header', 'forum/infinitescroll'], function(header, infinitescroll) { var Followers = {}; Followers.init = function() { header.init(); + + infinitescroll.init(function(direction) { + Followers.loadMore(direction, 'account/followers', 'followers:' + $('.account-username-box').attr('data-uid')); + }); }; + Followers.loadMore = function(direction, tpl, set) { + if (direction < 0) { + return; + } + + infinitescroll.loadMore('user.loadMore', { + set: set, + after: $('#users-container').attr('data-nextstart') + }, function(data, done) { + if (data.users && data.users.length) { + onUsersLoaded(tpl, data.users, done); + $('#users-container').attr('data-nextstart', data.nextStart); + } else { + done(); + } + }); + }; + + function onUsersLoaded(tpl, users, callback) { + infinitescroll.parseAndTranslate(tpl, 'users', {users: users}, function(html) { + $('#users-container').append(html); + utils.addCommasToNumbers(html.find('.formatted-number')); + callback(); + }); + } + return Followers; }); diff --git a/public/src/client/account/following.js b/public/src/client/account/following.js index 4d31a2cbb6..3b7ba980b7 100644 --- a/public/src/client/account/following.js +++ b/public/src/client/account/following.js @@ -1,8 +1,16 @@ -define('forum/account/following', ['forum/account/header'], function(header) { +'use strict'; + +/* globals define */ + +define('forum/account/following', ['forum/account/header', 'forum/infinitescroll', 'forum/account/followers'], function(header, infinitescroll, followers) { var Following = {}; Following.init = function() { header.init(); + + infinitescroll.init(function(direction) { + followers.loadMore(direction, 'account/following', 'following:' + $('.account-username-box').attr('data-uid')); + }); }; return Following; diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js index 2732c593dc..63a2d91000 100644 --- a/src/controllers/accounts.js +++ b/src/controllers/accounts.js @@ -45,9 +45,6 @@ function getUserDataByUserSlug(userslug, callerUID, callback) { isAdmin : function(next) { user.isAdministrator(callerUID, next); }, - followStats: function(next) { - user.getFollowStats(uid, next); - }, ips: function(next) { user.getIPs(uid, 4, next); }, @@ -97,8 +94,8 @@ function getUserDataByUserSlug(userslug, callerUID, callback) { userData.status = websockets.isUserOnline(userData.uid) ? (userData.status || 'online') : 'offline'; userData.banned = parseInt(userData.banned, 10) === 1; userData.websiteName = userData.website.replace(validator.escape('http://'), '').replace(validator.escape('https://'), ''); - userData.followingCount = results.followStats.followingCount; - userData.followerCount = results.followStats.followerCount; + userData.followingCount = parseInt(userData.followingCount, 10) || 0; + userData.followerCount = parseInt(userData.followerCount, 10) || 0; callback(null, userData); }); @@ -206,14 +203,15 @@ function getFollow(tpl, name, req, res, next) { return helpers.notFound(req, res); } var method = name === 'following' ? 'getFollowing' : 'getFollowers'; - user[method](userData.uid, next); + user[method](userData.uid, 0, 49, next); } ], function(err, users) { if(err) { return next(err); } - userData[name] = users; - userData[name + 'Count'] = users.length; + + userData.users = users; + userData.nextStart = 50; res.render(tpl, userData); }); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 023a823f88..d28421c383 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -329,27 +329,28 @@ SocketUser.loadMore = function(socket, data, callback) { return callback(new Error('[[error:no-privileges]]')); } - var start = data.after, + var start = parseInt(data.after, 10), end = start + 19; user.getUsersFromSet(data.set, start, end, function(err, userData) { - if(err) { + if (err) { return callback(err); } user.isAdministrator(socket.uid, function (err, isAdministrator) { - if(err) { + if (err) { return callback(err); } - if(!isAdministrator && data.set === 'users:online') { + if (!isAdministrator && data.set === 'users:online') { userData = userData.filter(function(item) { return item.status !== 'offline'; }); } callback(null, { - users: userData + users: userData, + nextStart: end + 1 }); }); }); diff --git a/src/upgrade.js b/src/upgrade.js index 23194cddf9..10ca9fef14 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -21,7 +21,7 @@ var db = require('./database'), schemaDate, thisSchemaDate, // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema - latestSchema = Date.UTC(2015, 0, 13); + latestSchema = Date.UTC(2015, 0, 14); Upgrade.check = function(callback) { db.get('schemaDate', function(err, value) { @@ -566,6 +566,70 @@ Upgrade.upgrade = function(callback) { winston.info('[2015/01/13] Creating uid:followed_tids sorted set skipped'); next(); } + }, + function(next) { + thisSchemaDate = Date.UTC(2015, 0, 14); + if (schemaDate < thisSchemaDate) { + winston.info('[2015/01/14] Upgrading follow sets to sorted sets'); + + db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) { + if (err) { + winston.error('[2014/01/14] Error encountered while Upgrading follow sets to sorted sets'); + return next(err); + } + + var now = Date.now(); + + async.eachLimit(uids, 50, function(uid, next) { + async.parallel({ + following: function(next) { + db.getSetMembers('following:' + uid, next); + }, + followers: function(next) { + db.getSetMembers('followers:' + uid, next); + } + }, function(err, results) { + function updateToSortedSet(set, uids, callback) { + async.eachLimit(uids, 50, function(uid, next) { + if (parseInt(uid, 10)) { + db.sortedSetAdd(set, now, uid, next); + } else { + next(); + } + }, callback); + } + if (err) { + return next(err); + } + + async.parallel([ + async.apply(db.delete, 'following:' + uid), + async.apply(db.delete, 'followers:' + uid) + ], function(err) { + if (err) { + return next(err); + } + async.parallel([ + async.apply(updateToSortedSet, 'following:' + uid, results.following), + async.apply(updateToSortedSet, 'followers:' + uid, results.followers), + async.apply(db.setObjectField, 'user:' + uid, 'followingCount', results.following.length), + async.apply(db.setObjectField, 'user:' + uid, 'followerCount', results.followers.length), + ], next); + }); + }); + }, function(err) { + if (err) { + winston.error('[2015/01/14] Error encountered while Upgrading follow sets to sorted sets'); + return next(err); + } + winston.info('[2015/01/14] Upgrading follow sets to sorted sets done'); + Upgrade.update(thisSchemaDate, next); + }); + }); + } else { + winston.info('[2015/01/14] Upgrading follow sets to sorted sets skipped'); + next(); + } } // Add new schema updates here diff --git a/src/user/follow.js b/src/user/follow.js index 94b9d555f8..4c4dfc5223 100644 --- a/src/user/follow.js +++ b/src/user/follow.js @@ -23,29 +23,40 @@ module.exports = function(User) { return callback(new Error('[[error:you-cant-follow-yourself]]')); } - var command = type === 'follow' ? 'setAdd' : 'setRemove'; - db[command]('following:' + uid, theiruid, function(err) { - if(err) { - return callback(err); - } - db[command]('followers:' + theiruid, uid, callback); - }); + var now = Date.now(); + + if (type === 'follow') { + async.parallel([ + async.apply(db.sortedSetAdd, 'following:' + uid, now, theiruid), + async.apply(db.sortedSetAdd, 'followers:' + theiruid, now, uid), + async.apply(User.incrementUserFieldBy, uid, 'followingCount', 1), + async.apply(User.incrementUserFieldBy, theiruid, 'followerCount', 1) + ], callback); + + } else { + async.parallel([ + async.apply(db.sortedSetRemove, 'following:' + uid, theiruid), + async.apply(db.sortedSetRemove, 'followers:' + theiruid, uid), + async.apply(User.decrementUserFieldBy, uid, 'followingCount', 1), + async.apply(User.decrementUserFieldBy, theiruid, 'followerCount', 1) + ], callback); + } } - User.getFollowing = function(uid, callback) { - getFollow(uid, 'following:' + uid, callback); + User.getFollowing = function(uid, start, end, callback) { + getFollow(uid, 'following:' + uid, start, end, callback); }; - User.getFollowers = function(uid, callback) { - getFollow(uid, 'followers:' + uid, callback); + User.getFollowers = function(uid, start, end, callback) { + getFollow(uid, 'followers:' + uid, start, end, callback); }; - function getFollow(uid, set, callback) { + function getFollow(uid, set, start, end, callback) { if (!parseInt(uid, 10)) { return callback(null, []); } - db.getSetMembers(set, function(err, uids) { + db.getSortedSetRevRange(set, start, end, function(err, uids) { if (err) { return callback(err); } @@ -54,36 +65,11 @@ module.exports = function(User) { }); } - User.getFollowingCount = function(uid, callback) { - if (!parseInt(uid, 10)) { - return callback(null, 0); - } - db.setCount('following:' + uid, callback); - }; - - User.getFollowerCount = function(uid, callback) { - if (!parseInt(uid, 10)) { - return callback(null, 0); - } - db.setCount('followers:' + uid, callback); - }; - - User.getFollowStats = function (uid, callback) { - async.parallel({ - followingCount: function(next) { - User.getFollowingCount(uid, next); - }, - followerCount : function(next) { - User.getFollowerCount(uid, next); - } - }, callback); - }; - User.isFollowing = function(uid, theirid, callback) { if (!parseInt(uid, 10) || !parseInt(theirid, 10)) { return callback(null, false); } - db.isSetMember('following:' + uid, theirid, callback); + db.isSortedSetMember('following:' + uid, theirid, callback); }; };