From a9672ab9d837f93d98605e72124994473aa2a040 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Thu, 23 May 2013 12:52:16 -0400 Subject: [PATCH] begin refactor of posts/topics + pagination + cleanup + async --- src/categories.js | 1 - src/postTools.js | 92 +++++++++ src/posts.js | 165 +++++++++-------- src/threadTools.js | 157 ++++++++++++++++ src/topics.js | 453 ++++++++++++++------------------------------- src/websockets.js | 28 +-- 6 files changed, 492 insertions(+), 404 deletions(-) create mode 100644 src/postTools.js create mode 100644 src/threadTools.js diff --git a/src/categories.js b/src/categories.js index ec1e2a0ea1..7ec41b2292 100644 --- a/src/categories.js +++ b/src/categories.js @@ -61,7 +61,6 @@ var RDB = require('./redis.js'), if (start == null) start = 0; if (end == null) end = start + 10; - //build a proper wrapper for this and move it into above function later var range_var = (category_id) ? 'categories:' + category_id + ':tid' : 'topics:tid'; RDB.smembers(range_var, function(err, tids) { diff --git a/src/postTools.js b/src/postTools.js new file mode 100644 index 0000000000..cc8d703bdf --- /dev/null +++ b/src/postTools.js @@ -0,0 +1,92 @@ +var RDB = require('./redis.js'), + posts = require('./posts.js'), + threadTools = require('./threadTools.js'), + user = require('./user.js'), + config = require('../config.js'), + async = require('async'), + marked = require('marked'); + +marked.setOptions({ + breaks: true +}); + +(function(PostTools) { + + PostTools.privileges = function(pid, uid, callback) { + //todo: break early if one condition is true + + function getThreadPrivileges(next) { + posts.get_tid_by_pid(pid, function(tid) { + threadTools.privileges(tid, uid, function(privileges) { + next(null, privileges); + }); + }); + } + + function isOwnPost(next) { + RDB.get('pid:' + pid + ':uid', function(err, author) { + if (author && parseInt(author) > 0) next(null, author === uid); + }); + } + + function hasEnoughRep(next) { + // DRY fail in threadTools. + + user.getUserField(uid, 'reputation', function(reputation) { + next(null, reputation >= config.privilege_thresholds.manage_content); + }); + } + + async.parallel([getThreadPrivileges, isOwnPost, hasEnoughRep], function(err, results) { + callback({ + editable: results[0].editable || (results.slice(1).indexOf(true) !== -1 ? true : false), + view_deleted: results[0].view_deleted || (results.slice(1).indexOf(true) !== -1 ? true : false) + }); + }); + } + + PostTools.edit = function(uid, pid, content) { + var success = function() { + RDB.set('pid:' + pid + ':content', content); + RDB.set('pid:' + pid + ':edited', new Date().getTime()); + RDB.set('pid:' + pid + ':editor', uid); + + posts.get_tid_by_pid(pid, function(tid) { + io.sockets.in('topic_' + tid).emit('event:post_edited', { pid: pid, content: marked(content || '') }); + }); + }; + + PostTools.privileges(pid, uid, function(privileges) { + if (privileges.editable) success(); + }); + } + + PostTools.delete = function(uid, pid) { + var success = function() { + RDB.set('pid:' + pid + ':deleted', 1); + + posts.get_tid_by_pid(pid, function(tid) { + io.sockets.in('topic_' + tid).emit('event:post_deleted', { pid: pid }); + }); + }; + + PostTools.privileges(pid, uid, function(privileges) { + if (privileges.editable) success(); + }); + } + + PostTools.restore = function(uid, pid) { + var success = function() { + RDB.del('pid:' + pid + ':deleted'); + + posts.get_tid_by_pid(pid, function(tid) { + io.sockets.in('topic_' + tid).emit('event:post_restored', { pid: pid }); + }); + }; + + PostTools.privileges(pid, uid, function(privileges) { + if (privileges.editable) success(); + }); + } + +}(exports)); \ No newline at end of file diff --git a/src/posts.js b/src/posts.js index 9f2a9e7694..38d269ccc5 100644 --- a/src/posts.js +++ b/src/posts.js @@ -4,6 +4,7 @@ var RDB = require('./redis.js'), user = require('./user.js'), topics = require('./topics.js'), config = require('../config.js'), + threadTools = require('./threadTools.js'), async = require('async'); marked.setOptions({ @@ -12,49 +13,108 @@ marked.setOptions({ (function(Posts) { - Posts.get = function(callback, pid, current_user) { - // Not used... although Topics.get could be refactored to call Posts.get for every post - } + Posts.get = function(callback, tid, current_user, start, end) { + if (start == null) start = 0; + if (end == null) end = start + 10; + + RDB.lrange('tid:' + tid + ':posts', start, end, function(err, pids) { + RDB.handle(err); + + if (pids.length === 0 ) { + callback(false); + return; + } + + topics.markAsRead(tid, current_user); + + var content = [], uid = [], timestamp = [], pid = [], post_rep = [], editor = [], editTime = [], deleted = []; + + for (var i=0, ii=pids.length; i 0) next(null, author === uid); - }); - }, - function(next) { - user.getUserField(uid, 'reputation', function(reputation) { - next(null, reputation >= config.privilege_thresholds.manage_content); - }); } - ], function(err, results) { - callback({ - editable: results[0].editable || (results.slice(1).indexOf(true) !== -1 ? true : false), - view_deleted: results[0].view_deleted || (results.slice(1).indexOf(true) !== -1 ? true : false) + + async.parallel([getFavouritesData, getPostData], function(err, results) { + callback({ + 'voteData' : results[0], // to be moved + 'userData' : results[1].users, // to be moved + 'postData' : results[1].posts + }); }); + }); } Posts.get_tid_by_pid = function(pid, callback) { RDB.get('pid:' + pid + ':tid', function(err, tid) { - if (tid && parseInt(tid) > 0) callback(tid); - else callback(false); + if (tid && parseInt(tid) > 0) { + callback(tid); + } else { + callback(false); + } }); } Posts.get_cid_by_pid = function(pid, callback) { Posts.get_tid_by_pid(pid, function(tid) { if (tid) topics.get_cid_by_tid(tid, function(cid) { - if (cid) callback(cid); - else callback(false); + if (cid) { + callback(cid); + } else { + callback(false); + } }); }) } @@ -81,6 +141,7 @@ marked.setOptions({ // Re-add the poster, so he/she does not get an "unread" flag on this topic topics.markAsRead(tid, uid); + // this will duplicate once we enter the thread, which is where we should be going socket.emit('event:alert', { title: 'Reply Successful', @@ -89,7 +150,7 @@ marked.setOptions({ timeout: 2000 }); - user.getUserFields(uid, ['username','reputation','picture','signature'], function(data){ + user.getUserFields(uid, ['username','reputation','picture','signature'], function(data) { var timestamp = new Date().getTime(); @@ -144,7 +205,7 @@ marked.setOptions({ RDB.incr('tid:' + tid + ':postcount'); - user.getUserFields(uid, ['username'], function(data){ + user.getUserFields(uid, ['username'], function(data) { //add active users to this category RDB.get('tid:' + tid + ':cid', function(err, cid) { RDB.handle(err); @@ -275,48 +336,4 @@ marked.setOptions({ socket.emit('api:posts.getRawPost', { post: raw }); }); } - - Posts.edit = function(uid, pid, content) { - var success = function() { - RDB.set('pid:' + pid + ':content', content); - RDB.set('pid:' + pid + ':edited', new Date().getTime()); - RDB.set('pid:' + pid + ':editor', uid); - - Posts.get_tid_by_pid(pid, function(tid) { - io.sockets.in('topic_' + tid).emit('event:post_edited', { pid: pid, content: marked(content || '') }); - }); - }; - - Posts.privileges(pid, uid, function(privileges) { - if (privileges.editable) success(); - }); - } - - Posts.delete = function(uid, pid) { - var success = function() { - RDB.set('pid:' + pid + ':deleted', 1); - - Posts.get_tid_by_pid(pid, function(tid) { - io.sockets.in('topic_' + tid).emit('event:post_deleted', { pid: pid }); - }); - }; - - Posts.privileges(pid, uid, function(privileges) { - if (privileges.editable) success(); - }); - } - - Posts.restore = function(uid, pid) { - var success = function() { - RDB.del('pid:' + pid + ':deleted'); - - Posts.get_tid_by_pid(pid, function(tid) { - io.sockets.in('topic_' + tid).emit('event:post_restored', { pid: pid }); - }); - }; - - Posts.privileges(pid, uid, function(privileges) { - if (privileges.editable) success(); - }); - } }(exports)); \ No newline at end of file diff --git a/src/threadTools.js b/src/threadTools.js new file mode 100644 index 0000000000..06850a2b05 --- /dev/null +++ b/src/threadTools.js @@ -0,0 +1,157 @@ +var RDB = require('./redis.js'), + topics = require('./topics.js'), + categories = require('./categories.js'), + user = require('./user.js'), + config = require('../config.js'), + async = require('async'); + + +(function(ThreadTools) { + + ThreadTools.privileges = function(tid, uid, callback) { + //todo: break early if one condition is true + + function getCategoryPrivileges(next) { + topics.get_cid_by_tid(tid, function(cid) { + categories.privileges(cid, uid, function(privileges) { + next(null, privileges); + }); + }); + } + + function hasEnoughRep(next) { + // DRY fail in postTools + + user.getUserField(uid, 'reputation', function(reputation) { + next(null, reputation >= config.privilege_thresholds.manage_thread); + }); + } + + + async.parallel([getCategoryPrivileges, hasEnoughRep], function(err, results) { + callback({ + editable: results[0].editable || (results.slice(1).indexOf(true) !== -1 ? true : false), + view_deleted: results[0].view_deleted || (results.slice(1).indexOf(true) !== -1 ? true : false) + }); + }); + } + + ThreadTools.lock = function(tid, uid, socket) { + ThreadTools.privileges(tid, uid, function(privileges) { + if (privileges.editable) { + // Mark thread as locked + RDB.set('tid:' + tid + ':locked', 1); + + if (socket) { + io.sockets.in('topic_' + tid).emit('event:topic_locked', { + tid: tid, + status: 'ok' + }); + } + } + }); + } + + ThreadTools.unlock = function(tid, uid, socket) { + ThreadTools.privileges(tid, uid, function(privileges) { + if (privileges.editable) { + // Mark thread as unlocked + RDB.del('tid:' + tid + ':locked'); + + if (socket) { + io.sockets.in('topic_' + tid).emit('event:topic_unlocked', { + tid: tid, + status: 'ok' + }); + } + } + }); + } + + ThreadTools.delete = function(tid, uid, socket) { + ThreadTools.privileges(tid, uid, function(privileges) { + if (privileges.editable) { + // Mark thread as deleted + RDB.set('tid:' + tid + ':deleted', 1); + ThreadTools.lock(tid, uid); + + if (socket) { + io.sockets.in('topic_' + tid).emit('event:topic_deleted', { + tid: tid, + status: 'ok' + }); + } + } + }); + } + + ThreadTools.restore = function(tid, uid, socket) { + ThreadTools.privileges(tid, uid, function(privileges) { + if (privileges.editable) { + // Mark thread as restored + RDB.del('tid:' + tid + ':deleted'); + ThreadTools.unlock(tid, uid); + + if (socket) { + io.sockets.in('topic_' + tid).emit('event:topic_restored', { + tid: tid, + status: 'ok' + }); + } + } + }); + } + + ThreadTools.pin = function(tid, uid, socket) { + ThreadTools.privileges(tid, uid, function(privileges) { + if (privileges.editable) { + // Mark thread as pinned + RDB.set('tid:' + tid + ':pinned', 1); + + if (socket) { + io.sockets.in('topic_' + tid).emit('event:topic_pinned', { + tid: tid, + status: 'ok' + }); + } + } + }); + } + + ThreadTools.unpin = function(tid, uid, socket) { + ThreadTools.privileges(tid, uid, function(privileges) { + if (privileges.editable) { + // Mark thread as unpinned + RDB.del('tid:' + tid + ':pinned'); + + if (socket) { + io.sockets.in('topic_' + tid).emit('event:topic_unpinned', { + tid: tid, + status: 'ok' + }); + } + } + }); + } + + ThreadTools.move = function(tid, cid, socket) { + RDB.get('tid:' + tid + ':cid', function(err, oldCid) { + RDB.handle(err); + + RDB.smove('categories:' + oldCid + ':tid', 'categories:' + cid + ':tid', tid, function(err, result) { + if (!err && result === 1) { + RDB.set('tid:' + tid + ':cid', cid); + categories.get_category([cid], function(data) { + RDB.set('tid:' + tid + ':category_name', data.categories[0].name); + RDB.set('tid:' + tid + ':category_slug', data.categories[0].slug); + }); + socket.emit('api:topic.move', { status: 'ok' }); + io.sockets.in('topic_' + tid).emit('event:topic_moved', { tid: tid }); + } else { + socket.emit('api:topic.move', { status: 'error' }); + } + }); + }); + } + +}(exports)); \ No newline at end of file diff --git a/src/topics.js b/src/topics.js index a5b8389ba8..a139797869 100644 --- a/src/topics.js +++ b/src/topics.js @@ -6,6 +6,7 @@ var RDB = require('./redis.js'), categories = require('./categories.js'), posts = require('./posts.js'), marked = require('marked'), + threadTools = require('./threadTools.js'), async = require('async'); marked.setOptions({ @@ -13,240 +14,175 @@ marked.setOptions({ }); (function(Topics) { + Topics.get = function(callback, tid, current_user) { + + function getTopicData(next) { + RDB.multi() + .get('tid:' + tid + ':title') + .get('tid:' + tid + ':locked') + .get('tid:' + tid + ':category_name') + .get('tid:' + tid + ':category_slug') + .get('tid:' + tid + ':deleted') + .get('tid:' + tid + ':pinned') + .exec(function(err, replies) { + next(null, { + topic_name: replies[0], + locked: replies[1] || 0, + category_name: replies[2], + category_slug: replies[3], + deleted: replies[4] || 0, + pinned: replies[5] || 0 + }); + }); + } - Topics.get_by_category = function(callback, category, start, end) { - - } - - - Topics.get = function(callback, tid, current_user, start, end) { - if (start == null) start = 0; - if (end == null) end = -1;//start + 10; - - var post_data, user_data, thread_data, vote_data, privileges; + function getTopicPosts(next) { + posts.get(function(postData) { + next(null, postData); + }, tid, current_user, 0, 10); + } - getTopicPosts(); + function getPrivileges(next) { + threadTools.privileges(tid, current_user, function(privData) { + next(null, privData); + }); + } - getPrivileges(); - + async.parallel([getTopicData, getTopicPosts, getPrivileges], function(err, results) { + var retrieved_posts = [], + main_posts = []; - //compile thread after all data is asynchronously called - function generateThread() { - if (!post_data || !user_data || !thread_data || !vote_data || !privileges) return; + var topicData = results[0], + postData = results[1].postData, + userData = results[1].userData, + voteData = results[1].voteData, + privileges = results[2]; - var retrieved_posts = [], - main_posts = []; + for (var i=0, ii= postData.pid.length; i 0) { + RDB.sismember('tid:' + tid + ':read_by_uid', uid, function(err, read) { + topicData.badgeclass = read ? '' : 'badge-important'; - Topics.privileges = function(tid, uid, callback) { - async.parallel([ - function(next) { - Topics.get_cid_by_tid(tid, function(cid) { - categories.privileges(cid, uid, function(privileges) { - next(null, privileges); - }); - }); - }, - function(next) { - user.getUserField(uid, 'reputation', function(reputation) { - next(null, reputation >= config.privilege_thresholds.manage_thread); + next(); }); + } else { + next(); } - ], function(err, results) { - callback({ - editable: results[0].editable || (results.slice(1).indexOf(true) !== -1 ? true : false), - view_deleted: results[0].view_deleted || (results.slice(1).indexOf(true) !== -1 ? true : false) - }); - }); - } + } - Topics.get_topic = function(tid, uid, callback) { - var topicData = {}; + function get_teaser(next) { + Topics.get_teaser(tid, function(teaser) { + topicData.teaser_text = teaser.text; + topicData.teaser_username = teaser.username; - async.parallel([ - function(next) { - RDB.mget([ - 'tid:' + tid + ':title', - 'tid:' + tid + ':uid', - 'tid:' + tid + ':timestamp', - 'tid:' + tid + ':slug', - 'tid:' + tid + ':postcount', - 'tid:' + tid + ':locked', - 'tid:' + tid + ':pinned', - 'tid:' + tid + ':deleted' - ], function(err, topic) { - topicData.title = topic[0]; - topicData.uid = topic[1]; - topicData.timestamp = topic[2]; - topicData.relativeTime = utils.relativeTime(topic[2]), - topicData.slug = topic[3]; - topicData.post_count = topic[4]; - topicData.locked = topic[5]; - topicData.pinned = topic[6]; - topicData.deleted = topic[7]; - - user.getUserField(topic[1], 'username', function(username) { - topicData.username = username; - next(null); - }) - }); - }, - function(next) { - if (uid && parseInt(uid) > 0) { - RDB.sismember('tid:' + tid + ':read_by_uid', uid, function(err, read) { - topicData.badgeclass = read ? '' : 'badge-important'; - next(null); - }); - } else next(null); - }, - function(next) { - Topics.get_teaser(tid, function(teaser) { - topicData.teaser_text = teaser.text; - topicData.teaser_username = teaser.username; - next(null); - }); - } - ], function(err) { - if (!err) { - callback(topicData); + next(); + }); + } + + async.parallel([get_topic_data, get_read_status, get_teaser], function(err) { + if (err) { + throw new Error(err); } + + callback(topicData); }); } Topics.get_cid_by_tid = function(tid, callback) { RDB.get('tid:' + tid + ':cid', function(err, cid) { - if (cid && parseInt(cid) > 0) callback(cid); - else callback(false); + if (cid && parseInt(cid) > 0) { + callback(cid); + } else { + callback(false); + } }); } @@ -274,18 +210,20 @@ marked.setOptions({ var requests = []; if (Array.isArray(tids)) { for(x=0,numTids=tids.length;x