Merge pull request #4662 from NodeBB/topic-watching

topic watching
This commit is contained in:
Barış Soner Uşaklı
2016-05-19 14:34:42 +03:00
19 changed files with 404 additions and 59 deletions

View File

@@ -305,4 +305,23 @@ var privileges = require('./privileges');
return tree;
};
Categories.getIgnorers = function(cid, start, stop, callback) {
db.getSortedSetRevRange('cid:' + cid + ':ignorers', start, stop, callback);
};
Categories.filterIgnoringUids = function(cid, uids, callback) {
async.waterfall([
function (next){
db.sortedSetScores('cid:' + cid + ':ignorers', uids, next);
},
function (scores, next) {
var readingUids = uids.filter(function(uid, index) {
return uid && !!scores[index];
});
next(null, readingUids);
}
], callback);
};
}(exports));

View File

@@ -38,6 +38,7 @@ module.exports = function(Categories) {
'cid:' + cid + ':tids:posts',
'cid:' + cid + ':pids',
'cid:' + cid + ':read_by_uid',
'cid:' + cid + ':ignorers',
'cid:' + cid + ':children',
'category:' + cid
], next);

View File

@@ -4,6 +4,7 @@ var async = require('async');
var winston = require('winston');
var S = require('string');
var db = require('../database');
var websockets = require('./index');
var user = require('../user');
var posts = require('../posts');
@@ -27,6 +28,9 @@ SocketHelpers.notifyNew = function(uid, type, result) {
function(uids, next) {
privileges.topics.filterUids('read', result.posts[0].topic.tid, uids, next);
},
function(uids, next) {
filterTidCidIgnorers(uids, result.posts[0].topic.tid, result.posts[0].topic.cid, next);
},
function(uids, next) {
plugins.fireHook('filter:sockets.sendNewPostToUids', {uidsTo: uids, uidFrom: uid, type: type}, next);
}
@@ -48,6 +52,31 @@ SocketHelpers.notifyNew = function(uid, type, result) {
});
};
function filterTidCidIgnorers(uids, tid, cid, callback) {
async.waterfall([
function (next) {
async.parallel({
topicFollowed: function(next) {
db.isSetMembers('tid:' + tid + ':followers', uids, next);
},
topicIgnored: function(next) {
db.isSetMembers('tid:' + tid + ':ignorers', uids, next);
},
categoryIgnored: function(next) {
db.sortedSetScores('cid:' + cid + ':ignorers', uids, next);
}
}, next);
},
function (results, next) {
uids = uids.filter(function(uid, index) {
return results.topicFollowed[index] ||
(!results.topicFollowed[index] && !results.topicIgnored[index] && !results.categoryIgnored[index]);
});
next(null, uids);
}
], callback);
}
SocketHelpers.sendNotificationToPostOwner = function(pid, fromuid, notification) {
if (!pid || !fromuid || !notification) {
return;

View File

@@ -72,8 +72,15 @@ SocketTopics.createTopicFromPosts = function(socket, data, callback) {
topics.createTopicFromPosts(socket.uid, data.title, data.pids, data.fromTid, callback);
};
SocketTopics.toggleFollow = function(socket, tid, callback) {
followCommand(topics.toggleFollow, socket, tid, callback);
SocketTopics.changeWatching = function(socket, data, callback) {
if (!data.tid || !data.type) {
return callback(new Error('[[error:invalid-data]]'));
}
var commands = ['follow', 'unfollow', 'ignore'];
if (commands.indexOf(data.type) === -1) {
return callback(new Error('[[error:invalid-command]]'));
}
followCommand(topics[data.type], socket, data.tid, callback);
};
SocketTopics.follow = function(socket, tid, callback) {

View File

@@ -131,6 +131,9 @@ var social = require('./social');
hasRead: function(next) {
Topics.hasReadTopics(tids, uid, next);
},
isIgnored: function(next) {
Topics.isIgnoring(tids, uid, next);
},
bookmarks: function(next) {
Topics.getUserBookmarks(tids, uid, next);
},
@@ -157,7 +160,8 @@ var social = require('./social');
topics[i].pinned = parseInt(topics[i].pinned, 10) === 1;
topics[i].locked = parseInt(topics[i].locked, 10) === 1;
topics[i].deleted = parseInt(topics[i].deleted, 10) === 1;
topics[i].unread = !results.hasRead[i];
topics[i].ignored = results.isIgnored[i];
topics[i].unread = !results.hasRead[i] && !results.isIgnored[i];
topics[i].bookmark = results.bookmarks[i];
topics[i].unreplied = !topics[i].teaser;
}
@@ -184,6 +188,7 @@ var social = require('./social');
threadTools: async.apply(plugins.fireHook, 'filter:topic.thread_tools', {topic: topicData, uid: uid, tools: []}),
tags: async.apply(Topics.getTopicTagsObjects, topicData.tid),
isFollowing: async.apply(Topics.isFollowing, [topicData.tid], uid),
isIgnoring: async.apply(Topics.isIgnoring, [topicData.tid], uid),
bookmark: async.apply(Topics.getUserBookmark, topicData.tid, uid),
postSharing: async.apply(social.getActivePostSharing)
}, next);
@@ -194,6 +199,8 @@ var social = require('./social');
topicData.thread_tools = results.threadTools.tools;
topicData.tags = results.tags;
topicData.isFollowing = results.isFollowing[0];
topicData.isNotFollowing = !results.isFollowing[0] && !results.isIgnoring[0];
topicData.isIgnoring = results.isIgnoring[0];
topicData.bookmark = results.bookmark;
topicData.postSharing = results.postSharing;

View File

@@ -113,6 +113,7 @@ module.exports = function(Topics) {
function(next) {
db.deleteAll([
'tid:' + tid + ':followers',
'tid:' + tid + ':ignorers',
'tid:' + tid + ':posts',
'tid:' + tid + ':posts:votes',
'tid:' + tid + ':bookmarks',

View File

@@ -56,12 +56,12 @@ module.exports = function(Topics) {
if (!exists) {
return next(new Error('[[error:no-topic]]'));
}
db.setAdd('tid:' + tid + ':followers', uid, next);
follow(tid, uid, next);
},
async.apply(plugins.fireHook, 'action:topic.follow', { uid: uid, tid: tid }),
function(next) {
db.sortedSetAdd('uid:' + uid + ':followed_tids', Date.now(), tid, next);
}
function (next) {
unignore(tid, uid, next);
},
async.apply(plugins.fireHook, 'action:topic.follow', {uid: uid, tid: tid})
], callback);
};
@@ -75,14 +75,77 @@ module.exports = function(Topics) {
if (!exists) {
return next(new Error('[[error:no-topic]]'));
}
unfollow(tid, uid, next);
},
function (next) {
unignore(tid, uid, next);
},
async.apply(plugins.fireHook, 'action:topic.unfollow', {uid: uid, tid: tid}),
], callback);
};
Topics.ignore = function(tid, uid, callback) {
callback = callback || function() {};
async.waterfall([
function (next) {
Topics.exists(tid, next);
},
function (exists, next) {
if (!exists) {
return next(new Error('[[error:no-topic]]'));
}
ignore(tid, uid, next);
},
function (next) {
unfollow(tid, uid, next);
},
async.apply(plugins.fireHook, 'action:topic.ignore', {uid: uid, tid: tid})
], callback);
};
function follow(tid, uid, callback) {
async.waterfall([
function (next) {
db.setAdd('tid:' + tid + ':followers', uid, next);
},
function (next) {
db.sortedSetAdd('uid:' + uid + ':followed_tids', Date.now(), tid, next);
}
], callback);
}
function unfollow(tid, uid, callback) {
async.waterfall([
function (next) {
db.setRemove('tid:' + tid + ':followers', uid, next);
},
async.apply(plugins.fireHook, 'action:topic.unfollow', { uid: uid, tid: tid }),
function(next) {
function (next) {
db.sortedSetRemove('uid:' + uid + ':followed_tids', tid, next);
}
], callback);
};
}
function ignore(tid, uid, callback) {
async.waterfall([
function (next) {
db.setAdd('tid:' + tid + ':ignorers', uid, next);
},
function(next) {
db.sortedSetAdd('uid:' + uid + ':ignored_tids', Date.now(), tid, next);
}
], callback);
}
function unignore(tid, uid, callback) {
async.waterfall([
function (next) {
db.setRemove('tid:' + tid + ':ignorers', uid, next);
},
function(next) {
db.sortedSetRemove('uid:' + uid + ':ignored_tids', tid, next);
}
], callback);
}
Topics.isFollowing = function(tids, uid, callback) {
if (!Array.isArray(tids)) {
@@ -97,10 +160,41 @@ module.exports = function(Topics) {
db.isMemberOfSets(keys, uid, callback);
};
Topics.isIgnoring = function(tids, uid, callback) {
if (!Array.isArray(tids)) {
return callback();
}
if (!parseInt(uid, 10)) {
return callback(null, tids.map(function() { return false; }));
}
var keys = tids.map(function(tid) {
return 'tid:' + tid + ':ignorers';
});
db.isMemberOfSets(keys, uid, callback);
};
Topics.getFollowers = function(tid, callback) {
db.getSetMembers('tid:' + tid + ':followers', callback);
};
Topics.getIgnorers = function(tid, callback) {
db.getSetMembers('tid:' + tid + ':ignorers', callback);
};
Topics.filterIgnoringUids = function(tid, uids, callback) {
async.waterfall([
function (next){
db.isSetMembers('tid:' + tid + ':ignorers', uids, next);
},
function (isMembers, next){
var readingUids = uids.filter(function(uid, index) {
return uid && isMembers[index];
});
next(null, readingUids);
}
], callback);
};
Topics.notifyFollowers = function(postData, exceptUid, callback) {
callback = callback || function() {};
var followers;

View File

@@ -87,6 +87,9 @@ module.exports = function(Topics) {
}
user.getIgnoredCategories(uid, next);
},
ignoredTids: function(next) {
user.getIgnoredTids(uid, 0, -1, next);
},
recentTids: function(next) {
db.getSortedSetRevRangeByScoreWithScores('topics:recent', 0, -1, '+inf', cutoff, next);
},
@@ -116,6 +119,9 @@ module.exports = function(Topics) {
});
var tids = results.recentTids.filter(function(recentTopic) {
if (results.ignoredTids.indexOf(recentTopic.value.toString()) !== -1) {
return false;
}
switch (filter) {
case 'new':
return !userRead[recentTopic.value];
@@ -138,7 +144,7 @@ module.exports = function(Topics) {
tids = tids.slice(0, 200);
filterTopics(uid, tids, cid, ignoredCids, next);
filterTopics(uid, tids, cid, ignoredCids, filter, next);
}
], callback);
};
@@ -155,7 +161,7 @@ module.exports = function(Topics) {
});
}
function filterTopics(uid, tids, cid, ignoredCids, callback) {
function filterTopics(uid, tids, cid, ignoredCids, filter, callback) {
if (!Array.isArray(ignoredCids) || !tids.length) {
return callback(null, tids);
}
@@ -165,11 +171,24 @@ module.exports = function(Topics) {
privileges.topics.filterTids('read', tids, uid, next);
},
function(tids, next) {
Topics.getTopicsFields(tids, ['tid', 'cid'], next);
async.parallel({
topics: function(next) {
Topics.getTopicsFields(tids, ['tid', 'cid'], next);
},
isTopicsFollowed: function(next) {
if (filter === 'watched' || filter === 'new') {
return next(null, []);
}
db.sortedSetScores('uid:' + uid + ':followed_tids', tids, next);
}
}, next);
},
function(topics, next) {
tids = topics.filter(function(topic) {
return topic && topic.cid && ignoredCids.indexOf(topic.cid.toString()) === -1 && (!cid || parseInt(cid, 10) === parseInt(topic.cid, 10));
function(results, next) {
var topics = results.topics;
tids = topics.filter(function(topic, index) {
return topic && topic.cid &&
(!!results.isTopicsFollowed[index] || ignoredCids.indexOf(topic.cid.toString()) === -1) &&
(!cid || parseInt(cid, 10) === parseInt(topic.cid, 10));
}).map(function(topic) {
return topic.tid;
});

View File

@@ -1,12 +1,12 @@
'use strict';
var async = require('async'),
var async = require('async');
plugins = require('./plugins'),
db = require('./database'),
topics = require('./topics'),
privileges = require('./privileges'),
utils = require('../public/src/utils');
var plugins = require('./plugins');
var db = require('./database');
var topics = require('./topics');
var privileges = require('./privileges');
var utils = require('../public/src/utils');
(function(User) {
@@ -19,6 +19,7 @@ var async = require('async'),
require('./user/auth')(User);
require('./user/create')(User);
require('./user/posts')(User);
require('./user/topics')(User);
require('./user/categories')(User);
require('./user/follow')(User);
require('./user/profile')(User);

View File

@@ -45,6 +45,9 @@ module.exports = function(User) {
return next(new Error('[[error:no-category]]'));
}
db.sortedSetAdd('uid:' + uid + ':ignored:cids', Date.now(), cid, next);
},
function (next) {
db.sortedSetAdd('cid:' + cid + ':ignorers', Date.now(), uid, next);
}
], callback);
};
@@ -63,6 +66,9 @@ module.exports = function(User) {
return next(new Error('[[error:no-category]]'));
}
db.sortedSetRemove('uid:' + uid + ':ignored:cids', cid, next);
},
function (next) {
db.sortedSetRemove('cid:' + cid + ':ignorers', uid, next);
}
], callback);
};

View File

@@ -102,8 +102,12 @@ module.exports = function(User) {
},
function(next) {
var keys = [
'uid:' + uid + ':notifications:read', 'uid:' + uid + ':notifications:unread',
'uid:' + uid + ':favourites', 'uid:' + uid + ':followed_tids', 'user:' + uid + ':settings',
'uid:' + uid + ':notifications:read',
'uid:' + uid + ':notifications:unread',
'uid:' + uid + ':favourites',
'uid:' + uid + ':followed_tids',
'uid:' + uid + ':ignored_tids',
'user:' + uid + ':settings',
'uid:' + uid + ':topics', 'uid:' + uid + ':posts',
'uid:' + uid + ':chats', 'uid:' + uid + ':chats:unread',
'uid:' + uid + ':chat:rooms', 'uid:' + uid + ':chat:rooms:unread',

View File

@@ -1,9 +1,9 @@
'use strict';
var async = require('async'),
db = require('../database'),
meta = require('../meta'),
privileges = require('../privileges');
var async = require('async');
var db = require('../database');
var meta = require('../meta');
var privileges = require('../privileges');
module.exports = function(User) {
@@ -83,13 +83,6 @@ module.exports = function(User) {
db.sortedSetAdd('uid:' + uid + ':posts', timestamp, pid, callback);
};
User.addTopicIdToUser = function(uid, tid, timestamp, callback) {
async.parallel([
async.apply(db.sortedSetAdd, 'uid:' + uid + ':topics', timestamp, tid),
async.apply(User.incrementUserFieldBy, uid, 'topiccount', 1)
], callback);
};
User.incrementUserPostCountBy = function(uid, value, callback) {
callback = callback || function() {};
User.incrementUserFieldBy(uid, 'postcount', value, function(err, newpostcount) {

19
src/user/topics.js Normal file
View File

@@ -0,0 +1,19 @@
'use strict';
var async = require('async');
var db = require('../database');
module.exports = function(User) {
User.getIgnoredTids = function(uid, start, stop, callback) {
db.getSortedSetRevRange('uid:' + uid + ':ignored_tids', start, stop, callback);
};
User.addTopicIdToUser = function(uid, tid, timestamp, callback) {
async.parallel([
async.apply(db.sortedSetAdd, 'uid:' + uid + ':topics', timestamp, tid),
async.apply(User.incrementUserFieldBy, uid, 'topiccount', 1)
], callback);
};
};