Async refactor in place (#7736)

* feat: allow both callback&and await

* feat: ignore async key

* feat: callbackify and promisify in same file

* Revert "feat: callbackify and promisify in same file"

This reverts commit cea206a9b8.

* feat: no need to store .callbackify

* feat: change getTopics to async

* feat: remove .async

* fix: byScore

* feat: rewrite topics/index and social with async/await

* fix: rewrite topics/data.js

fix issue with async.waterfall, only pass result if its not undefined

* feat: add callbackify to redis/psql

* feat: psql use await

* fix: redis 🌋

* feat: less returns

* feat: more await rewrite

* fix: redis tests

* feat: convert sortedSetAdd

rewrite psql transaction to async/await

* feat: 🐶

* feat: test

* feat: log client and query

* feat: log bind

* feat: more logs

* feat: more logs

* feat: check perform

* feat: dont callbackify transaction

* feat: remove logs

* fix: main functions

* feat: more logs

* fix: increment

* fix: rename

* feat: remove cls

* fix: remove console.log

* feat: add deprecation message to .async usage

* feat: update more dbal methods

* fix: redis :voodoo:

* feat:  fix redis zrem, convert setObject

* feat: upgrade getObject methods

* fix: psql getObjectField

* fix: redis tests

* feat: getObjectKeys

* feat: getObjectValues

* feat: isObjectField

* fix: add missing return

* feat: delObjectField

* feat: incrObjectField

* fix: add missing await

* feat: remove exposed helpers

* feat: list methods

* feat: flush/empty

* feat: delete

* fix: redis delete all

* feat: get/set

* feat: incr/rename

* feat: type

* feat: expire

* feat: setAdd

* feat: setRemove

* feat: isSetMember

* feat: getSetMembers

* feat: setCount, setRemoveRandom

* feat: zcard,zcount

* feat: sortedSetRank

* feat: isSortedSetMember

* feat: zincrby

* feat: sortedSetLex

* feat: processSortedSet

* fix: add mising await

* feat: debug psql

* fix: psql test

* fix: test

* fix: another test

* fix: test fix

* fix: psql tests

* feat: remove logs

* feat: user arrow func

use builtin async promises

* feat: topic bookmarks

* feat: topic.delete

* feat: topic.restore

* feat: topics.purge

* feat: merge

* feat: suggested

* feat: topics/user.js

* feat: topics modules

* feat: topics/follow

* fix: deprecation msg

* feat: fork

* feat: topics/posts

* feat: sorted/recent

* feat: topic/teaser

* feat: topics/tools

* feat: topics/unread

* feat: add back node versions

disable deprecation notice
wrap async controllers in try/catch

* feat: use db directly

* feat: promisify in place

* fix: redis/psql

* feat: deprecation message

logs for psql

* feat: more logs

* feat: more logs

* feat: logs again

* feat: more logs

* fix: call release

* feat: restore travis, remove logs

* fix: loops

* feat: remove .async. usage
This commit is contained in:
Barış Soner Uşaklı
2019-07-09 12:46:49 -04:00
committed by GitHub
parent 43ce5f8af3
commit 805dcd7ca2
73 changed files with 4030 additions and 6110 deletions

View File

@@ -15,66 +15,47 @@ var utils = require('../utils');
var plugins = require('../plugins');
module.exports = function (Topics) {
Topics.getTotalUnread = function (uid, filter, callback) {
if (!callback) {
callback = filter;
filter = '';
}
Topics.getUnreadTids({ cid: 0, uid: uid, count: true }, function (err, counts) {
callback(err, counts && counts[filter]);
});
Topics.getTotalUnread = async function (uid, filter) {
filter = filter || '';
const counts = await Topics.getUnreadTids({ cid: 0, uid: uid, count: true });
return counts && counts[filter];
};
Topics.getUnreadTopics = function (params, callback) {
Topics.getUnreadTopics = async function (params) {
var unreadTopics = {
showSelect: true,
nextStart: 0,
topics: [],
};
let tids = await Topics.getUnreadTids(params);
unreadTopics.topicCount = tids.length;
async.waterfall([
function (next) {
Topics.getUnreadTids(params, next);
},
function (tids, next) {
unreadTopics.topicCount = tids.length;
if (!tids.length) {
return unreadTopics;
}
if (!tids.length) {
return next(null, []);
}
tids = tids.slice(params.start, params.stop !== -1 ? params.stop + 1 : undefined);
tids = tids.slice(params.start, params.stop !== -1 ? params.stop + 1 : undefined);
Topics.getTopicsByTids(tids, params.uid, next);
},
function (topicData, next) {
if (!topicData.length) {
return next(null, unreadTopics);
}
Topics.calculateTopicIndices(topicData, params.start);
unreadTopics.topics = topicData;
unreadTopics.nextStart = params.stop + 1;
next(null, unreadTopics);
},
], callback);
const topicData = await Topics.getTopicsByTids(tids, params.uid);
if (!topicData.length) {
return unreadTopics;
}
Topics.calculateTopicIndices(topicData, params.start);
unreadTopics.topics = topicData;
unreadTopics.nextStart = params.stop + 1;
return unreadTopics;
};
Topics.unreadCutoff = function () {
return Date.now() - (meta.config.unreadCutoff * 86400000);
};
Topics.getUnreadTids = function (params, callback) {
async.waterfall([
function (next) {
Topics.getUnreadData(params, next);
},
function (results, next) {
next(null, params.count ? results.counts : results.tids);
},
], callback);
Topics.getUnreadTids = async function (params) {
const results = await Topics.getUnreadData(params);
return params.count ? results.counts : results.tids;
};
Topics.getUnreadData = function (params, callback) {
Topics.getUnreadData = async function (params) {
const uid = parseInt(params.uid, 10);
const counts = {
'': 0,
@@ -94,7 +75,7 @@ module.exports = function (Topics) {
};
if (uid <= 0) {
return setImmediate(callback, null, noUnreadData);
return noUnreadData;
}
params.filter = params.filter || '';
@@ -104,45 +85,35 @@ module.exports = function (Topics) {
if (params.cid && !Array.isArray(params.cid)) {
params.cid = [params.cid];
}
const [ignoredTids, recentTids, userScores, tids_unread] = await Promise.all([
user.getIgnoredTids(uid, 0, -1),
db.getSortedSetRevRangeByScoreWithScores('topics:recent', 0, -1, '+inf', cutoff),
db.getSortedSetRevRangeByScoreWithScores('uid:' + uid + ':tids_read', 0, -1, '+inf', cutoff),
db.getSortedSetRevRangeWithScores('uid:' + uid + ':tids_unread', 0, -1),
]);
async.waterfall([
function (next) {
async.parallel({
ignoredTids: function (next) {
user.getIgnoredTids(uid, 0, -1, next);
},
recentTids: function (next) {
db.getSortedSetRevRangeByScoreWithScores('topics:recent', 0, -1, '+inf', cutoff, next);
},
userScores: function (next) {
db.getSortedSetRevRangeByScoreWithScores('uid:' + uid + ':tids_read', 0, -1, '+inf', cutoff, next);
},
tids_unread: function (next) {
db.getSortedSetRevRangeWithScores('uid:' + uid + ':tids_unread', 0, -1, next);
},
}, next);
},
function (results, next) {
if (results.recentTids && !results.recentTids.length && !results.tids_unread.length) {
return callback(null, noUnreadData);
}
if (recentTids && !recentTids.length && !tids_unread.length) {
return noUnreadData;
}
filterTopics(params, results, next);
},
function (data, next) {
plugins.fireHook('filter:topics.getUnreadTids', {
uid: uid,
tids: data.tids,
counts: data.counts,
tidsByFilter: data.tidsByFilter,
cid: params.cid,
filter: params.filter,
}, next);
},
], callback);
const data = await filterTopics(params, {
ignoredTids: ignoredTids,
recentTids: recentTids,
userScores: userScores,
tids_unread: tids_unread,
});
const result = await plugins.fireHook('filter:topics.getUnreadTids', {
uid: uid,
tids: data.tids,
counts: data.counts,
tidsByFilter: data.tidsByFilter,
cid: params.cid,
filter: params.filter,
});
return result;
};
function filterTopics(params, results, callback) {
async function filterTopics(params, results) {
const counts = {
'': 0,
new: 0,
@@ -180,385 +151,253 @@ module.exports = function (Topics) {
var uid = params.uid;
var cids;
var topicData;
var blockedUids;
tids = tids.slice(0, 200);
if (!tids.length) {
return callback(null, { counts: counts, tids: tids, tidsByFilter: tidsByFilter });
return { counts: counts, tids: tids, tidsByFilter: tidsByFilter };
}
const blockedUids = await user.blocks.list(uid);
async.waterfall([
function (next) {
user.blocks.list(uid, next);
},
function (_blockedUids, next) {
blockedUids = _blockedUids;
filterTidsThatHaveBlockedPosts({
uid: uid,
tids: tids,
blockedUids: blockedUids,
recentTids: results.recentTids,
}, next);
},
function (_tids, next) {
tids = _tids;
Topics.getTopicsFields(tids, ['tid', 'cid', 'uid', 'postcount'], next);
},
function (_topicData, next) {
topicData = _topicData;
cids = _.uniq(topicData.map(topic => topic.cid)).filter(Boolean);
tids = await filterTidsThatHaveBlockedPosts({
uid: uid,
tids: tids,
blockedUids: blockedUids,
recentTids: results.recentTids,
});
async.parallel({
isTopicsFollowed: function (next) {
db.sortedSetScores('uid:' + uid + ':followed_tids', tids, next);
},
categoryWatchState: function (next) {
categories.getWatchState(cids, uid, next);
},
readableCids: function (next) {
privileges.categories.filterCids('read', cids, uid, next);
},
}, next);
},
function (results, next) {
cid = cid && cid.map(String);
results.readableCids = results.readableCids.map(String);
const userCidState = _.zipObject(cids, results.categoryWatchState);
topicData = await Topics.getTopicsFields(tids, ['tid', 'cid', 'uid', 'postcount']);
cids = _.uniq(topicData.map(topic => topic.cid)).filter(Boolean);
topicData.forEach(function (topic, index) {
function cidMatch(topicCid) {
return (!cid || (cid.length && cid.includes(String(topicCid)))) && results.readableCids.includes(String(topicCid));
}
const [isTopicsFollowed, categoryWatchState, readCids] = await Promise.all([
db.sortedSetScores('uid:' + uid + ':followed_tids', tids),
categories.getWatchState(cids, uid),
privileges.categories.filterCids('read', cids, uid),
]);
cid = cid && cid.map(String);
const readableCids = readCids.map(String);
const userCidState = _.zipObject(cids, categoryWatchState);
if (topic && topic.cid && cidMatch(topic.cid) && !blockedUids.includes(parseInt(topic.uid, 10))) {
topic.tid = parseInt(topic.tid, 10);
if ((results.isTopicsFollowed[index] || userCidState[topic.cid] === categories.watchStates.watching)) {
tidsByFilter[''].push(topic.tid);
}
topicData.forEach(function (topic, index) {
function cidMatch(topicCid) {
return (!cid || (cid.length && cid.includes(String(topicCid)))) && readableCids.includes(String(topicCid));
}
if (results.isTopicsFollowed[index]) {
tidsByFilter.watched.push(topic.tid);
}
if (topic && topic.cid && cidMatch(topic.cid) && !blockedUids.includes(parseInt(topic.uid, 10))) {
topic.tid = parseInt(topic.tid, 10);
if ((isTopicsFollowed[index] || userCidState[topic.cid] === categories.watchStates.watching)) {
tidsByFilter[''].push(topic.tid);
}
if (topic.postcount <= 1) {
tidsByFilter.unreplied.push(topic.tid);
}
if (isTopicsFollowed[index]) {
tidsByFilter.watched.push(topic.tid);
}
if (!userRead[topic.tid]) {
tidsByFilter.new.push(topic.tid);
}
}
});
counts[''] = tidsByFilter[''].length;
counts.watched = tidsByFilter.watched.length;
counts.unreplied = tidsByFilter.unreplied.length;
counts.new = tidsByFilter.new.length;
if (topic.postcount <= 1) {
tidsByFilter.unreplied.push(topic.tid);
}
next(null, {
counts: counts,
tids: tidsByFilter[params.filter],
tidsByFilter: tidsByFilter,
});
},
], callback);
if (!userRead[topic.tid]) {
tidsByFilter.new.push(topic.tid);
}
}
});
counts[''] = tidsByFilter[''].length;
counts.watched = tidsByFilter.watched.length;
counts.unreplied = tidsByFilter.unreplied.length;
counts.new = tidsByFilter.new.length;
return {
counts: counts,
tids: tidsByFilter[params.filter],
tidsByFilter: tidsByFilter,
};
}
function filterTidsThatHaveBlockedPosts(params, callback) {
async function filterTidsThatHaveBlockedPosts(params) {
if (!params.blockedUids.length) {
return setImmediate(callback, null, params.tids);
return params.tids;
}
const topicScores = _.mapValues(_.keyBy(params.recentTids, 'value'), 'score');
db.sortedSetScores('uid:' + params.uid + ':tids_read', params.tids, function (err, results) {
if (err) {
return callback(err);
}
const userScores = _.zipObject(params.tids, results);
const results = await db.sortedSetScores('uid:' + params.uid + ':tids_read', params.tids);
async.filter(params.tids, function (tid, next) {
doesTidHaveUnblockedUnreadPosts(tid, {
blockedUids: params.blockedUids,
topicTimestamp: topicScores[tid],
userLastReadTimestamp: userScores[tid],
}, next);
}, callback);
const userScores = _.zipObject(params.tids, results);
return await async.filter(params.tids, async function (tid) {
return await doesTidHaveUnblockedUnreadPosts(tid, {
blockedUids: params.blockedUids,
topicTimestamp: topicScores[tid],
userLastReadTimestamp: userScores[tid],
});
});
}
function doesTidHaveUnblockedUnreadPosts(tid, params, callback) {
async function doesTidHaveUnblockedUnreadPosts(tid, params) {
var userLastReadTimestamp = params.userLastReadTimestamp;
if (!userLastReadTimestamp) {
return setImmediate(callback, null, true);
return true;
}
var start = 0;
var count = 3;
var done = false;
var hasUnblockedUnread = params.topicTimestamp > userLastReadTimestamp;
if (!params.blockedUids.length) {
return setImmediate(callback, null, hasUnblockedUnread);
return hasUnblockedUnread;
}
async.whilst(function (next) {
next(null, !done);
}, function (_next) {
async.waterfall([
function (next) {
db.getSortedSetRangeByScore('tid:' + tid + ':posts', start, count, userLastReadTimestamp, '+inf', next);
},
function (pidsSinceLastVisit, next) {
if (!pidsSinceLastVisit.length) {
done = true;
return _next();
}
while (!done) {
/* eslint-disable no-await-in-loop */
const pidsSinceLastVisit = await db.getSortedSetRangeByScore('tid:' + tid + ':posts', start, count, userLastReadTimestamp, '+inf');
if (!pidsSinceLastVisit.length) {
return hasUnblockedUnread;
}
let postData = await posts.getPostsFields(pidsSinceLastVisit, ['pid', 'uid']);
postData = postData.filter(function (post) {
return !params.blockedUids.includes(parseInt(post.uid, 10));
});
posts.getPostsFields(pidsSinceLastVisit, ['pid', 'uid'], next);
},
function (postData, next) {
postData = postData.filter(function (post) {
return !params.blockedUids.includes(parseInt(post.uid, 10));
});
done = postData.length > 0;
hasUnblockedUnread = postData.length > 0;
start += count;
next();
},
], _next);
}, function (err) {
callback(err, hasUnblockedUnread);
});
done = postData.length > 0;
hasUnblockedUnread = postData.length > 0;
start += count;
}
return hasUnblockedUnread;
}
Topics.pushUnreadCount = function (uid, callback) {
callback = callback || function () {};
Topics.pushUnreadCount = async function (uid) {
if (!uid || parseInt(uid, 10) <= 0) {
return setImmediate(callback);
return;
}
async.waterfall([
function (next) {
Topics.getUnreadTids({ uid: uid, count: true }, next);
},
function (results, next) {
require('../socket.io').in('uid_' + uid).emit('event:unread.updateCount', {
unreadTopicCount: results[''],
unreadNewTopicCount: results.new,
unreadWatchedTopicCount: results.watched,
unreadUnrepliedTopicCount: results.unreplied,
});
setImmediate(next);
},
], callback);
const results = await Topics.getUnreadTids({ uid: uid, count: true });
require('../socket.io').in('uid_' + uid).emit('event:unread.updateCount', {
unreadTopicCount: results[''],
unreadNewTopicCount: results.new,
unreadWatchedTopicCount: results.watched,
unreadUnrepliedTopicCount: results.unreplied,
});
};
Topics.markAsUnreadForAll = function (tid, callback) {
Topics.markCategoryUnreadForAll(tid, callback);
Topics.markAsUnreadForAll = async function (tid) {
await Topics.markCategoryUnreadForAll(tid);
};
Topics.markAsRead = function (tids, uid, callback) {
callback = callback || function () {};
Topics.markAsRead = async function (tids, uid) {
if (!Array.isArray(tids) || !tids.length) {
return setImmediate(callback, null, false);
return false;
}
tids = _.uniq(tids).filter(function (tid) {
return tid && utils.isNumber(tid);
tids = _.uniq(tids).filter(tid => tid && utils.isNumber(tid));
if (!tids.length) {
return false;
}
const [topicScores, userScores] = await Promise.all([
db.sortedSetScores('topics:recent', tids),
db.sortedSetScores('uid:' + uid + ':tids_read', tids),
]);
tids = tids.filter(function (tid, index) {
return topicScores[index] && (!userScores[index] || userScores[index] < topicScores[index]);
});
if (!tids.length) {
return setImmediate(callback, null, false);
return false;
}
async.waterfall([
function (next) {
async.parallel({
topicScores: async.apply(db.sortedSetScores, 'topics:recent', tids),
userScores: async.apply(db.sortedSetScores, 'uid:' + uid + ':tids_read', tids),
}, next);
},
function (results, next) {
tids = tids.filter(function (tid, index) {
return results.topicScores[index] && (!results.userScores[index] || results.userScores[index] < results.topicScores[index]);
});
var now = Date.now();
var scores = tids.map(() => now);
const [topicData] = await Promise.all([
Topics.getTopicsFields(tids, ['cid']),
db.sortedSetAdd('uid:' + uid + ':tids_read', scores, tids),
db.sortedSetRemove('uid:' + uid + ':tids_unread', tids),
]);
if (!tids.length) {
return callback(null, false);
}
var cids = _.uniq(topicData.map(t => t && t.cid).filter(Boolean));
await categories.markAsRead(cids, uid);
var now = Date.now();
var scores = tids.map(function () {
return now;
});
async.parallel({
markRead: async.apply(db.sortedSetAdd, 'uid:' + uid + ':tids_read', scores, tids),
markUnread: async.apply(db.sortedSetRemove, 'uid:' + uid + ':tids_unread', tids),
topicData: async.apply(Topics.getTopicsFields, tids, ['cid']),
}, next);
},
function (results, next) {
var cids = results.topicData.map(function (topic) {
return topic && topic.cid;
}).filter(Boolean);
cids = _.uniq(cids);
categories.markAsRead(cids, uid, next);
},
function (next) {
plugins.fireHook('action:topics.markAsRead', { uid: uid, tids: tids });
next(null, true);
},
], callback);
plugins.fireHook('action:topics.markAsRead', { uid: uid, tids: tids });
return true;
};
Topics.markAllRead = function (uid, callback) {
async.waterfall([
function (next) {
db.getSortedSetRevRangeByScore('topics:recent', 0, -1, '+inf', Topics.unreadCutoff(), next);
},
function (tids, next) {
Topics.markTopicNotificationsRead(tids, uid);
Topics.markAsRead(tids, uid, next);
},
function (markedRead, next) {
db.delete('uid:' + uid + ':tids_unread', next);
},
], callback);
Topics.markAllRead = async function (uid) {
const tids = await db.getSortedSetRevRangeByScore('topics:recent', 0, -1, '+inf', Topics.unreadCutoff());
Topics.markTopicNotificationsRead(tids, uid);
await Topics.markAsRead(tids, uid);
await db.delete('uid:' + uid + ':tids_unread');
};
Topics.markTopicNotificationsRead = function (tids, uid, callback) {
callback = callback || function () {};
Topics.markTopicNotificationsRead = async function (tids, uid) {
if (!Array.isArray(tids) || !tids.length) {
return callback();
return;
}
async.waterfall([
function (next) {
user.notifications.getUnreadByField(uid, 'tid', tids, next);
},
function (nids, next) {
notifications.markReadMultiple(nids, uid, next);
},
function (next) {
user.notifications.pushCount(uid);
next();
},
], callback);
const nids = await user.notifications.getUnreadByField(uid, 'tid', tids);
await notifications.markReadMultiple(nids, uid);
user.notifications.pushCount(uid);
};
Topics.markCategoryUnreadForAll = function (tid, callback) {
async.waterfall([
function (next) {
Topics.getTopicField(tid, 'cid', next);
},
function (cid, next) {
categories.markAsUnreadForAll(cid, next);
},
], callback);
Topics.markCategoryUnreadForAll = async function (tid) {
const cid = await Topics.getTopicField(tid, 'cid');
await categories.markAsUnreadForAll(cid);
};
Topics.hasReadTopics = function (tids, uid, callback) {
Topics.hasReadTopics = async function (tids, uid) {
if (!(parseInt(uid, 10) > 0)) {
return setImmediate(callback, null, tids.map(() => false));
return tids.map(() => false);
}
const [topicScores, userScores, tids_unread, blockedUids] = await Promise.all([
db.sortedSetScores('topics:recent', tids),
db.sortedSetScores('uid:' + uid + ':tids_read', tids),
db.sortedSetScores('uid:' + uid + ':tids_unread', tids),
user.blocks.list(uid),
]);
async.waterfall([
function (next) {
async.parallel({
topicScores: function (next) {
db.sortedSetScores('topics:recent', tids, next);
},
userScores: function (next) {
db.sortedSetScores('uid:' + uid + ':tids_read', tids, next);
},
tids_unread: function (next) {
db.sortedSetScores('uid:' + uid + ':tids_unread', tids, next);
},
blockedUids: function (next) {
user.blocks.list(uid, next);
},
}, next);
},
function (results, next) {
var cutoff = Topics.unreadCutoff();
var result = tids.map(function (tid, index) {
var read = !results.tids_unread[index] &&
(results.topicScores[index] < cutoff ||
!!(results.userScores[index] && results.userScores[index] >= results.topicScores[index]));
return { tid: tid, read: read, index: index };
});
var cutoff = Topics.unreadCutoff();
var result = tids.map(function (tid, index) {
var read = !tids_unread[index] &&
(topicScores[index] < cutoff ||
!!(userScores[index] && userScores[index] >= topicScores[index]));
return { tid: tid, read: read, index: index };
});
async.map(result, function (data, next) {
if (data.read) {
return next(null, true);
}
doesTidHaveUnblockedUnreadPosts(data.tid, {
topicTimestamp: results.topicScores[data.index],
userLastReadTimestamp: results.userScores[data.index],
blockedUids: results.blockedUids,
}, function (err, hasUnblockedUnread) {
if (err) {
return next(err);
}
if (!hasUnblockedUnread) {
data.read = true;
}
next(null, data.read);
});
}, next);
},
], callback);
};
Topics.hasReadTopic = function (tid, uid, callback) {
Topics.hasReadTopics([tid], uid, function (err, hasRead) {
callback(err, Array.isArray(hasRead) && hasRead.length ? hasRead[0] : false);
return await async.map(result, async function (data) {
if (data.read) {
return true;
}
const hasUnblockedUnread = await doesTidHaveUnblockedUnreadPosts(data.tid, {
topicTimestamp: topicScores[data.index],
userLastReadTimestamp: userScores[data.index],
blockedUids: blockedUids,
});
if (!hasUnblockedUnread) {
data.read = true;
}
return data.read;
});
};
Topics.markUnread = function (tid, uid, callback) {
async.waterfall([
function (next) {
Topics.exists(tid, next);
},
function (exists, next) {
if (!exists) {
return next(new Error('[[error:no-topic]]'));
}
db.sortedSetRemove('uid:' + uid + ':tids_read', tid, next);
},
function (next) {
db.sortedSetAdd('uid:' + uid + ':tids_unread', Date.now(), tid, next);
},
], callback);
Topics.hasReadTopic = async function (tid, uid) {
const hasRead = await Topics.hasReadTopics([tid], uid);
return Array.isArray(hasRead) && hasRead.length ? hasRead[0] : false;
};
Topics.filterNewTids = function (tids, uid, callback) {
if (parseInt(uid, 10) <= 0) {
return setImmediate(callback, null, []);
Topics.markUnread = async function (tid, uid) {
const exists = await Topics.exists(tid);
if (!exists) {
throw new Error('[[error:no-topic]]');
}
async.waterfall([
function (next) {
db.sortedSetScores('uid:' + uid + ':tids_read', tids, next);
},
function (scores, next) {
tids = tids.filter((tid, index) => tid && !scores[index]);
next(null, tids);
},
], callback);
await db.sortedSetRemove('uid:' + uid + ':tids_read', tid);
await db.sortedSetAdd('uid:' + uid + ':tids_unread', Date.now(), tid);
};
Topics.filterUnrepliedTids = function (tids, callback) {
async.waterfall([
function (next) {
db.sortedSetScores('topics:posts', tids, next);
},
function (scores, next) {
tids = tids.filter((tid, index) => tid && scores[index] <= 1);
next(null, tids);
},
], callback);
Topics.filterNewTids = async function (tids, uid) {
if (parseInt(uid, 10) <= 0) {
return [];
}
const scores = await db.sortedSetScores('uid:' + uid + ':tids_read', tids);
return tids.filter((tid, index) => tid && !scores[index]);
};
Topics.filterUnrepliedTids = async function (tids) {
const scores = await db.sortedSetScores('topics:posts', tids);
return tids.filter((tid, index) => tid && scores[index] <= 1);
};
};