diff --git a/public/language/en_GB/global.json b/public/language/en_GB/global.json index e6f4a35ee6..0a79ed2c1e 100644 --- a/public/language/en_GB/global.json +++ b/public/language/en_GB/global.json @@ -62,6 +62,9 @@ "users": "Users", "topics": "Topics", "posts": "Posts", + "best": "Best", + "upvoted": "Upvoted", + "downvoted": "Downvoted", "views": "Views", "reputation": "Reputation", diff --git a/public/language/en_GB/pages.json b/public/language/en_GB/pages.json index b5dbc7140a..5946b3886a 100644 --- a/public/language/en_GB/pages.json +++ b/public/language/en_GB/pages.json @@ -40,6 +40,9 @@ "account/favourites": "%1's Favourite Posts", "account/settings": "User Settings", "account/watched": "Topics watched by %1", + "account/upvoted": "Posts upvoted by %1", + "account/downvoted": "Posts downvoted by %1", + "account/best": "Best posts made by %1", "maintenance.text": "%1 is currently undergoing maintenance. Please come back another time.", "maintenance.messageIntro": "Additionally, the administrator has left this message:", diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index c789570359..4c44138524 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -83,6 +83,9 @@ "has_no_posts": "This user hasn't posted anything yet.", "has_no_topics": "This user hasn't posted any topics yet.", "has_no_watched_topics": "This user hasn't watched any topics yet.", + "has_no_upvoted_posts": "This user hasn't upvoted any posts yet.", + "has_no_downvoted_posts": "This user hasn't downvoted any posts yet.", + "has_no_voted_posts": "This user has no voted posts", "email_hidden": "Email Hidden", "hidden": "hidden", diff --git a/public/src/client/account/best.js b/public/src/client/account/best.js new file mode 100644 index 0000000000..27384f9638 --- /dev/null +++ b/public/src/client/account/best.js @@ -0,0 +1,17 @@ +'use strict'; + +/* globals define */ + +define('forum/account/best', ['forum/account/header', 'forum/account/posts'], function(header, posts) { + var Best = {}; + + Best.init = function() { + header.init(); + + $('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive'); + + posts.handleInfiniteScroll('posts.loadMoreBestPosts', 'account/best'); + }; + + return Best; +}); diff --git a/public/src/client/account/downvoted.js b/public/src/client/account/downvoted.js new file mode 100644 index 0000000000..1a3758e215 --- /dev/null +++ b/public/src/client/account/downvoted.js @@ -0,0 +1,17 @@ +'use strict'; + +/* globals define */ + +define('forum/account/downvoted', ['forum/account/header', 'forum/account/posts'], function(header, posts) { + var Downvoted = {}; + + Downvoted.init = function() { + header.init(); + + $('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive'); + + posts.handleInfiniteScroll('posts.loadMoreDownVotedPosts', 'account/downvoted'); + }; + + return Downvoted; +}); diff --git a/public/src/client/account/upvoted.js b/public/src/client/account/upvoted.js new file mode 100644 index 0000000000..1d0ef86294 --- /dev/null +++ b/public/src/client/account/upvoted.js @@ -0,0 +1,17 @@ +'use strict'; + +/* globals define */ + +define('forum/account/upvoted', ['forum/account/header', 'forum/account/posts'], function(header, posts) { + var Upvoted = {}; + + Upvoted.init = function() { + header.init(); + + $('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive'); + + posts.handleInfiniteScroll('posts.loadMoreUpVotedPosts', 'account/upvoted'); + }; + + return Upvoted; +}); diff --git a/src/controllers/accounts/posts.js b/src/controllers/accounts/posts.js index f17bb675ce..7e1e67a8bd 100644 --- a/src/controllers/accounts/posts.js +++ b/src/controllers/accounts/posts.js @@ -14,22 +14,90 @@ var async = require('async'), var postsController = {}; postsController.getFavourites = function(req, res, next) { - getFromUserSet('account/favourites', 'favourites', '[[user:favourites]]', posts.getPostSummariesFromSet, 'posts', req, res, next); + var data = { + template: 'account/favourites', + set: 'favourites', + type: 'posts', + noItemsFoundKey: '[[topic:favourites.has_no_favourites]]', + method: posts.getPostSummariesFromSet, + crumb: '[[user:favourites]]' + }; + getFromUserSet(data, req, res, next); }; postsController.getPosts = function(req, res, next) { - getFromUserSet('account/posts', 'posts', '[[global:posts]]', posts.getPostSummariesFromSet, 'posts', req, res, next); + var data = { + template: 'account/posts', + set: 'posts', + type: 'posts', + noItemsFoundKey: '[[user:has_no_posts]]', + method: posts.getPostSummariesFromSet, + crumb: '[[global:posts]]' + }; + getFromUserSet(data, req, res, next); +}; + +postsController.getUpVotedPosts = function(req, res, next) { + var data = { + template: 'account/upvoted', + set: 'upvote', + type: 'posts', + noItemsFoundKey: '[[user:has_no_upvoted_posts]]', + method: posts.getPostSummariesFromSet, + crumb: '[[global:upvoted]]' + }; + getFromUserSet(data, req, res, next); +}; + +postsController.getDownVotedPosts = function(req, res, next) { + var data = { + template: 'account/downvoted', + set: 'downvote', + type: 'posts', + noItemsFoundKey: '[[user:has_no_downvoted_posts]]', + method: posts.getPostSummariesFromSet, + crumb: '[[global:downvoted]]' + }; + getFromUserSet(data, req, res, next); +}; + +postsController.getBestPosts = function(req, res, next) { + var data = { + template: 'account/best', + set: 'posts:votes', + type: 'posts', + noItemsFoundKey: '[[user:has_no_voted_posts]]', + method: posts.getPostSummariesFromSet, + crumb: '[[global:best]]' + }; + getFromUserSet(data, req, res, next); }; postsController.getWatchedTopics = function(req, res, next) { - getFromUserSet('account/watched', 'followed_tids', '[[user:watched]]',topics.getTopicsFromSet, 'topics', req, res, next); + var data = { + template: 'account/watched', + set: 'followed_tids', + type: 'topics', + noItemsFoundKey: '[[user:has_no_watched_topics]]', + method: topics.getTopicsFromSet, + crumb: '[[user:watched]]' + }; + getFromUserSet(data, req, res, next); }; postsController.getTopics = function(req, res, next) { - getFromUserSet('account/topics', 'topics', '[[global:topics]]', topics.getTopicsFromSet, 'topics', req, res, next); + var data = { + template: 'account/topics', + set: 'topics', + type: 'topics', + noItemsFoundKey: '[[user:has_no_topics]]', + method: topics.getTopicsFromSet, + crumb: '[[global:topics]]' + }; + getFromUserSet(data, req, res, next); }; -function getFromUserSet(tpl, set, crumb, method, type, req, res, next) { +function getFromUserSet(data, req, res, next) { async.parallel({ settings: function(next) { user.getSettings(req.uid, next); @@ -44,10 +112,10 @@ function getFromUserSet(tpl, set, crumb, method, type, req, res, next) { var userData = results.userData; - var setName = 'uid:' + userData.uid + ':' + set; + var setName = 'uid:' + userData.uid + ':' + data.set; var page = Math.max(1, parseInt(req.query.page, 10) || 1); - var itemsPerPage = (tpl === 'account/topics' || tpl === 'account/watched') ? results.settings.topicsPerPage : results.settings.postsPerPage; + var itemsPerPage = (data.template === 'account/topics' || data.template === 'account/watched') ? results.settings.topicsPerPage : results.settings.postsPerPage; async.parallel({ itemCount: function(next) { @@ -60,23 +128,24 @@ function getFromUserSet(tpl, set, crumb, method, type, req, res, next) { data: function(next) { var start = (page - 1) * itemsPerPage; var stop = start + itemsPerPage - 1; - method(setName, req.uid, start, stop, next); + data.method(setName, req.uid, start, stop, next); } }, function(err, results) { if (err) { return next(err); } - userData[type] = results.data[type]; + userData[data.type] = results.data[data.type]; userData.nextStart = results.data.nextStart; var pageCount = Math.ceil(results.itemCount / itemsPerPage); userData.pagination = pagination.create(page, pageCount); - userData.title = '[[pages:' + tpl + ', ' + userData.username + ']]'; - userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: crumb}]); + userData.noItemsFoundKey = data.noItemsFoundKey; + userData.title = '[[pages:' + data.template + ', ' + userData.username + ']]'; + userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: data.crumb}]); - res.render(tpl, userData); + res.render(data.template, userData); }); }); } diff --git a/src/favourites.js b/src/favourites.js index 5af0a7f051..2aa694faad 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -1,12 +1,11 @@ "use strict"; -var async = require('async'), - winston = require('winston'), - db = require('./database'), - posts = require('./posts'), - user = require('./user'), - plugins = require('./plugins'), - meta = require('./meta'); +var async = require('async'); +var db = require('./database'); +var posts = require('./posts'); +var user = require('./user'); +var plugins = require('./plugins'); +var meta = require('./meta'); (function (Favourites) { @@ -19,7 +18,7 @@ var async = require('async'), return callback(new Error('[[error:not-logged-in]]')); } - posts.getPostFields(pid, ['pid', 'uid'], function (err, postData) { + posts.getPostFields(pid, ['pid', 'uid', 'tid'], function (err, postData) { if (err) { return callback(err); } @@ -47,7 +46,7 @@ var async = require('async'), db.sortedSetAdd('users:reputation', newreputation, postData.uid); } - adjustPostVotes(pid, uid, type, unvote, function(err, votes) { + adjustPostVotes(postData, uid, type, unvote, function(err, votes) { postData.votes = votes; callback(err, { user: { @@ -62,19 +61,19 @@ var async = require('async'), }); } - function adjustPostVotes(pid, uid, type, unvote, callback) { + function adjustPostVotes(postData, uid, type, unvote, callback) { var notType = (type === 'upvote' ? 'downvote' : 'upvote'); async.series([ function(next) { if (unvote) { - db.setRemove('pid:' + pid + ':' + type, uid, next); + db.setRemove('pid:' + postData.pid + ':' + type, uid, next); } else { - db.setAdd('pid:' + pid + ':' + type, uid, next); + db.setAdd('pid:' + postData.pid + ':' + type, uid, next); } }, function(next) { - db.setRemove('pid:' + pid + ':' + notType, uid, next); + db.setRemove('pid:' + postData.pid + ':' + notType, uid, next); } ], function(err) { if (err) { @@ -83,18 +82,19 @@ var async = require('async'), async.parallel({ upvotes: function(next) { - db.setCount('pid:' + pid + ':upvote', next); + db.setCount('pid:' + postData.pid + ':upvote', next); }, downvotes: function(next) { - db.setCount('pid:' + pid + ':downvote', next); + db.setCount('pid:' + postData.pid + ':downvote', next); } }, function(err, results) { if (err) { return callback(err); } var voteCount = parseInt(results.upvotes, 10) - parseInt(results.downvotes, 10); - - posts.updatePostVoteCount(pid, voteCount, function(err) { +console.log("WE ARE HERE") + posts.updatePostVoteCount(postData, voteCount, function(err) { + console.log("NOT HERE") callback(err, voteCount); }); }); diff --git a/src/posts.js b/src/posts.js index bb6ff499b7..0f3cad00f9 100644 --- a/src/posts.js +++ b/src/posts.js @@ -219,28 +219,37 @@ var async = require('async'), }); }; - Posts.updatePostVoteCount = function(pid, voteCount, callback) { + Posts.updatePostVoteCount = function(postData, voteCount, callback) { + if (!postData || !postData.pid || !postData.tid) { + return callback(); + } async.parallel([ - function(next) { - Posts.getPostField(pid, 'tid', function(err, tid) { - if (err) { - return next(err); - } - topics.getTopicField(tid, 'mainPid', function(err, mainPid) { - if (err) { - return next(err); - } - if (parseInt(mainPid, 10) === parseInt(pid, 10)) { + function (next) { + if (postData.uid) { + db.sortedSetAdd('uid:' + postData.uid + ':posts:votes', voteCount, postData.pid, next); + } else { + next(); + } + }, + function (next) { + async.waterfall([ + function (next) { + topics.getTopicField(postData.tid, 'mainPid', next); + }, + function (mainPid, next) { + if (parseInt(mainPid, 10) === parseInt(postData.pid, 10)) { return next(); } - db.sortedSetAdd('tid:' + tid + ':posts:votes', voteCount, pid, next); - }); - }); + db.sortedSetAdd('tid:' + postData.tid + ':posts:votes', voteCount, postData.pid, next); + } + ], next); }, - function(next) { - Posts.setPostField(pid, 'votes', voteCount, next); + function (next) { + Posts.setPostField(postData.pid, 'votes', voteCount, next); } - ], callback); + ], function(err) { + callback(err); + }); }; }(exports)); diff --git a/src/routes/accounts.js b/src/routes/accounts.js index aa6f62bb89..8c7a505d0b 100644 --- a/src/routes/accounts.js +++ b/src/routes/accounts.js @@ -12,10 +12,13 @@ module.exports = function (app, middleware, controllers) { setupPageRoute(app, '/user/:userslug/followers', middleware, middlewares, controllers.accounts.follow.getFollowers); setupPageRoute(app, '/user/:userslug/posts', middleware, middlewares, controllers.accounts.posts.getPosts); setupPageRoute(app, '/user/:userslug/topics', middleware, middlewares, controllers.accounts.posts.getTopics); + setupPageRoute(app, '/user/:userslug/best', middleware, middlewares, controllers.accounts.posts.getBestPosts); setupPageRoute(app, '/user/:userslug/groups', middleware, middlewares, controllers.accounts.groups.get); setupPageRoute(app, '/user/:userslug/favourites', middleware, accountMiddlewares, controllers.accounts.posts.getFavourites); setupPageRoute(app, '/user/:userslug/watched', middleware, accountMiddlewares, controllers.accounts.posts.getWatchedTopics); + setupPageRoute(app, '/user/:userslug/upvoted', middleware, accountMiddlewares, controllers.accounts.posts.getUpVotedPosts); + setupPageRoute(app, '/user/:userslug/downvoted', middleware, accountMiddlewares, controllers.accounts.posts.getDownVotedPosts); setupPageRoute(app, '/user/:userslug/edit', middleware, accountMiddlewares, controllers.accounts.edit.get); setupPageRoute(app, '/user/:userslug/edit/username', middleware, accountMiddlewares, controllers.accounts.edit.username); setupPageRoute(app, '/user/:userslug/edit/email', middleware, accountMiddlewares, controllers.accounts.edit.email); diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 4135668cb0..64b5b75076 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -85,6 +85,18 @@ SocketPosts.loadMoreUserPosts = function(socket, data, callback) { loadMorePosts('uid:' + data.uid + ':posts', socket.uid, data, callback); }; +SocketPosts.loadMoreBestPosts = function(socket, data, callback) { + loadMorePosts('uid:' + data.uid + ':posts:votes', socket.uid, data, callback); +}; + +SocketPosts.loadMoreUpVotedPosts = function(socket, data, callback) { + loadMorePosts('uid:' + data.uid + ':upvote', socket.uid, data, callback); +}; + +SocketPosts.loadMoreDownVotedPosts = function(socket, data, callback) { + loadMorePosts('uid:' + data.uid + ':downvote', socket.uid, data, callback); +}; + function loadMorePosts(set, uid, data, callback) { if (!data || !utils.isNumber(data.uid) || !utils.isNumber(data.after)) { return callback(new Error('[[error:invalid-data]]')); diff --git a/src/upgrade.js b/src/upgrade.js index c82db21a06..ae8a6ab690 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -10,7 +10,7 @@ var db = require('./database'), schemaDate, thisSchemaDate, // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema - latestSchema = Date.UTC(2016, 0, 11); + latestSchema = Date.UTC(2016, 0, 14); Upgrade.check = function(callback) { db.get('schemaDate', function(err, value) { @@ -296,11 +296,45 @@ Upgrade.upgrade = function(callback) { winston.info('[2015/12/23] Adding theme to active plugins sorted set done!'); Upgrade.update(thisSchemaDate, next); - }) + }); } else { winston.info('[2015/12/23] Adding theme to active plugins sorted set skipped!'); next(); } + }, + function(next) { + thisSchemaDate = Date.UTC(2016, 0, 14); + + if (schemaDate < thisSchemaDate || 1) { + updatesMade = true; + winston.info('[2016/01/14] Creating user best post sorted sets'); + + var batch = require('./batch'); + + batch.processSortedSet('posts:pid', function(ids, next) { + async.eachSeries(ids, function(id, next) { + db.getObjectFields('post:' + id, ['pid', 'uid', 'votes'], function(err, postData) { + if (err) { + return next(err); + } + if (!postData || !parseInt(postData.votes, 10) || !parseInt(postData.uid, 10)) { + return next(); + } + winston.info('processing pid: ' + postData.pid + ' uid: ' + postData.uid + ' votes: ' + postData.votes); + db.sortedSetAdd('uid:' + postData.uid + ':posts:votes', postData.votes, postData.pid, next); + }); + }, next); + }, {}, function(err) { + if (err) { + return next(err); + } + winston.info('[2016/01/14] Creating user best post sorted sets done!'); + Upgrade.update(thisSchemaDate, next); + }); + } else { + winston.info('[2016/01/14] Creating user best post sorted sets skipped!'); + next(); + } } // Add new schema updates here // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 24!!!