You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
7.9 KiB
JavaScript
327 lines
7.9 KiB
JavaScript
'use strict';
|
|
|
|
var async = require('async'),
|
|
winston = require('winston'),
|
|
cron = require('cron').CronJob,
|
|
nconf = require('nconf'),
|
|
S = require('string'),
|
|
_ = require('underscore'),
|
|
|
|
db = require('./database'),
|
|
utils = require('../public/src/utils'),
|
|
events = require('./events'),
|
|
User = require('./user'),
|
|
groups = require('./groups'),
|
|
meta = require('./meta'),
|
|
plugins = require('./plugins');
|
|
|
|
(function(Notifications) {
|
|
|
|
Notifications.init = function() {
|
|
winston.verbose('[notifications.init] Registering jobs.');
|
|
new cron('*/30 * * * *', Notifications.prune, null, true);
|
|
};
|
|
|
|
Notifications.get = function(nid, callback) {
|
|
Notifications.getMultiple([nid], function(err, notifications) {
|
|
callback(err, Array.isArray(notifications) && notifications.length ? notifications[0] : null);
|
|
});
|
|
};
|
|
|
|
Notifications.getMultiple = function(nids, callback) {
|
|
var keys = nids.map(function(nid) {
|
|
return 'notifications:' + nid;
|
|
});
|
|
|
|
db.getObjects(keys, function(err, notifications) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
if (!Array.isArray(notifications) || !notifications.length) {
|
|
return callback(null, []);
|
|
}
|
|
|
|
async.map(notifications, function(notification, next) {
|
|
if (!notification) {
|
|
return next(null, null);
|
|
}
|
|
|
|
if (notification.bodyShort) {
|
|
notification.bodyShort = S(notification.bodyShort).escapeHTML().s;
|
|
}
|
|
if (notification.bodyLong) {
|
|
notification.bodyLong = S(notification.bodyLong).escapeHTML().s;
|
|
}
|
|
|
|
if (notification.from && !notification.image) {
|
|
User.getUserFields(notification.from, ['username', 'userslug', 'picture'], function(err, userData) {
|
|
if (err) {
|
|
return next(err);
|
|
}
|
|
notification.image = userData.picture;
|
|
notification.user = userData;
|
|
next(null, notification);
|
|
});
|
|
return;
|
|
} else if (notification.image) {
|
|
switch(notification.image) {
|
|
case 'brand:logo':
|
|
notification.image = meta.config['brand:logo'] || nconf.get('relative_path') + '/logo.png';
|
|
break;
|
|
}
|
|
|
|
return next(null, notification);
|
|
} else {
|
|
notification.image = meta.config['brand:logo'] || nconf.get('relative_path') + '/logo.png';
|
|
return next(null, notification);
|
|
}
|
|
|
|
}, callback);
|
|
});
|
|
};
|
|
|
|
Notifications.create = function(data, callback) {
|
|
if (!data.nid) {
|
|
return callback(new Error('no-notification-id'));
|
|
}
|
|
data.importance = data.importance || 5;
|
|
db.getObject('notifications:' + data.nid, function(err, oldNotification) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
if (oldNotification) {
|
|
if (parseInt(oldNotification.pid, 10) === parseInt(data.pid, 10) && parseInt(oldNotification.importance, 10) > parseInt(data.importance, 10)) {
|
|
return callback();
|
|
}
|
|
}
|
|
|
|
var now = Date.now();
|
|
data.datetime = now;
|
|
async.parallel([
|
|
function(next) {
|
|
db.sortedSetAdd('notifications', now, data.nid, next);
|
|
},
|
|
function(next) {
|
|
db.setObject('notifications:' + data.nid, data, next);
|
|
}
|
|
], function(err) {
|
|
callback(err, data);
|
|
});
|
|
});
|
|
};
|
|
|
|
Notifications.push = function(notification, uids, callback) {
|
|
callback = callback || function() {};
|
|
|
|
if (!notification.nid) {
|
|
return callback();
|
|
}
|
|
|
|
if (!Array.isArray(uids)) {
|
|
uids = [uids];
|
|
}
|
|
|
|
uids = uids.filter(function(uid) {
|
|
return parseInt(uid, 10);
|
|
});
|
|
|
|
if (!uids.length) {
|
|
return callback();
|
|
}
|
|
|
|
var done = false;
|
|
var start = 0;
|
|
var batchSize = 50;
|
|
|
|
setTimeout(function() {
|
|
async.whilst(
|
|
function() {
|
|
return !done;
|
|
},
|
|
function(next) {
|
|
var currentUids = uids.slice(start, start + batchSize);
|
|
if (!currentUids.length) {
|
|
done = true;
|
|
return next();
|
|
}
|
|
pushToUids(currentUids, notification, function(err) {
|
|
if (err) {
|
|
return next(err);
|
|
}
|
|
start = start + batchSize;
|
|
|
|
setTimeout(next, 1000);
|
|
});
|
|
},
|
|
function(err) {
|
|
if (err) {
|
|
winston.error(err.stack);
|
|
}
|
|
}
|
|
);
|
|
}, 1000);
|
|
|
|
callback();
|
|
};
|
|
|
|
function pushToUids(uids, notification, callback) {
|
|
var unreadKeys = [];
|
|
var readKeys = [];
|
|
|
|
uids.forEach(function(uid) {
|
|
unreadKeys.push('uid:' + uid + ':notifications:unread');
|
|
readKeys.push('uid:' + uid + ':notifications:read');
|
|
});
|
|
|
|
var oneWeekAgo = Date.now() - 604800000;
|
|
async.series([
|
|
function(next) {
|
|
db.sortedSetsAdd(unreadKeys, notification.datetime, notification.nid, next);
|
|
},
|
|
function(next) {
|
|
db.sortedSetsRemove(readKeys, notification.nid, next);
|
|
},
|
|
function(next) {
|
|
db.sortedSetsRemoveRangeByScore(unreadKeys, 0, oneWeekAgo, next);
|
|
},
|
|
function(next) {
|
|
db.sortedSetsRemoveRangeByScore(readKeys, 0, oneWeekAgo, next);
|
|
}
|
|
], function(err) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
plugins.fireHook('action:notification.pushed', {notification: notification, uids: uids});
|
|
|
|
var websockets = require('./socket.io');
|
|
if (websockets.server) {
|
|
for(var i=0; i<uids.length; ++i) {
|
|
websockets.in('uid_' + uids[i]).emit('event:new_notification', notification);
|
|
}
|
|
}
|
|
|
|
callback();
|
|
});
|
|
}
|
|
|
|
Notifications.pushGroup = function(notification, groupName, callback) {
|
|
callback = callback || function() {};
|
|
groups.getMembers(groupName, 0, -1, function(err, members) {
|
|
if (err || !Array.isArray(members) || !members.length) {
|
|
return callback(err);
|
|
}
|
|
|
|
Notifications.push(notification, members, callback);
|
|
});
|
|
};
|
|
|
|
Notifications.markRead = function(nid, uid, callback) {
|
|
callback = callback || function() {};
|
|
if (!parseInt(uid, 10) || !nid) {
|
|
return callback();
|
|
}
|
|
Notifications.markReadMultiple([nid], uid, callback);
|
|
};
|
|
|
|
Notifications.markUnread = function(nid, uid, callback) {
|
|
callback = callback || function() {};
|
|
if (!parseInt(uid, 10) || !nid) {
|
|
return callback();
|
|
}
|
|
|
|
db.getObjectField(nid, 'datetime', function(err, datetime) {
|
|
datetime = datetime || Date.now();
|
|
|
|
async.parallel([
|
|
async.apply(db.sortedSetRemove, 'uid:' + uid + ':notifications:read', nid),
|
|
async.apply(db.sortedSetAdd, 'uid:' + uid + ':notifications:unread', datetime, nid)
|
|
], callback);
|
|
});
|
|
};
|
|
|
|
Notifications.markReadMultiple = function(nids, uid, callback) {
|
|
callback = callback || function() {};
|
|
if (!Array.isArray(nids) || !nids.length) {
|
|
return callback();
|
|
}
|
|
|
|
var notificationKeys = nids.filter(Boolean).map(function(nid) {
|
|
return 'notifications:' + nid;
|
|
});
|
|
|
|
db.getObjectsFields(notificationKeys, ['datetime'], function(err, notificationData) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
var datetimes = notificationData.map(function(notification) {
|
|
return (notification && notification.datetime) || Date.now();
|
|
});
|
|
|
|
async.parallel([
|
|
function(next) {
|
|
db.sortedSetRemove('uid:' + uid + ':notifications:unread', nids, next);
|
|
},
|
|
function(next) {
|
|
db.sortedSetAdd('uid:' + uid + ':notifications:read', datetimes, nids, next);
|
|
}
|
|
], callback);
|
|
});
|
|
};
|
|
|
|
Notifications.markAllRead = function(uid, callback) {
|
|
db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99, function(err, nids) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
if (!Array.isArray(nids) || !nids.length) {
|
|
return callback();
|
|
}
|
|
|
|
Notifications.markReadMultiple(nids, uid, callback);
|
|
});
|
|
};
|
|
|
|
Notifications.prune = function() {
|
|
var week = 604800000,
|
|
numPruned = 0;
|
|
|
|
var cutoffTime = Date.now() - week;
|
|
|
|
db.getSortedSetRangeByScore('notifications', 0, 500, 0, cutoffTime, function(err, nids) {
|
|
if (err) {
|
|
return winston.error(err.message);
|
|
}
|
|
|
|
if (!Array.isArray(nids) || !nids.length) {
|
|
return;
|
|
}
|
|
|
|
var keys = nids.map(function(nid) {
|
|
return 'notifications:' + nid;
|
|
});
|
|
|
|
numPruned = nids.length;
|
|
|
|
async.parallel([
|
|
function(next) {
|
|
db.sortedSetRemove('notifications', nids, next);
|
|
},
|
|
function(next) {
|
|
db.deleteAll(keys, next);
|
|
}
|
|
], function(err) {
|
|
if (err) {
|
|
return winston.error('Encountered error pruning notifications: ' + err.message);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
}(exports));
|
|
|