mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-06-23 09:30:51 +02:00
Merge commit 'bc41848adb6c2a84c74a88598b87334a179ecabf' into v1.11.x
This commit is contained in:
@@ -26,7 +26,7 @@ var uniquevisitors = 0;
|
||||
* the cache could be exhausted continuously if there are more than 500 concurrently
|
||||
* active users
|
||||
*/
|
||||
var ipCache = LRU({
|
||||
var ipCache = new LRU({
|
||||
max: 500,
|
||||
length: function () { return 1; },
|
||||
maxAge: 0,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
var LRU = require('lru-cache');
|
||||
var pubsub = require('./pubsub');
|
||||
|
||||
var cache = LRU({
|
||||
var cache = new LRU({
|
||||
max: 1000,
|
||||
maxAge: 0,
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ module.exports = function (Categories) {
|
||||
'cid:' + cid + ':tids:posts',
|
||||
'cid:' + cid + ':pids',
|
||||
'cid:' + cid + ':read_by_uid',
|
||||
'cid:' + cid + ':ignorers',
|
||||
'cid:' + cid + ':uid:watch:state',
|
||||
'cid:' + cid + ':children',
|
||||
'cid:' + cid + ':tag:whitelist',
|
||||
'category:' + cid,
|
||||
|
||||
@@ -6,7 +6,7 @@ var _ = require('lodash');
|
||||
|
||||
var db = require('../database');
|
||||
var user = require('../user');
|
||||
var Groups = require('../groups');
|
||||
var groups = require('../groups');
|
||||
var plugins = require('../plugins');
|
||||
var privileges = require('../privileges');
|
||||
const cache = require('../cache');
|
||||
@@ -21,6 +21,7 @@ require('./unread')(Categories);
|
||||
require('./activeusers')(Categories);
|
||||
require('./recentreplies')(Categories);
|
||||
require('./update')(Categories);
|
||||
require('./watch')(Categories);
|
||||
|
||||
Categories.exists = function (cid, callback) {
|
||||
db.exists('category:' + cid, callback);
|
||||
@@ -45,8 +46,8 @@ Categories.getCategoryById = function (data, callback) {
|
||||
topicCount: function (next) {
|
||||
Categories.getTopicCount(data, next);
|
||||
},
|
||||
isIgnored: function (next) {
|
||||
Categories.isIgnored([data.cid], data.uid, next);
|
||||
watchState: function (next) {
|
||||
Categories.getWatchState([data.cid], data.uid, next);
|
||||
},
|
||||
parent: function (next) {
|
||||
if (category.parentCid) {
|
||||
@@ -64,7 +65,9 @@ Categories.getCategoryById = function (data, callback) {
|
||||
category.topics = results.topics.topics;
|
||||
category.nextStart = results.topics.nextStart;
|
||||
category.topic_count = results.topicCount;
|
||||
category.isIgnored = results.isIgnored[0];
|
||||
category.isWatched = results.watchState[0] === Categories.watchStates.watching;
|
||||
category.isNotWatched = results.watchState[0] === Categories.watchStates.notwatching;
|
||||
category.isIgnored = results.watchState[0] === Categories.watchStates.ignoring;
|
||||
category.parent = results.parent;
|
||||
|
||||
calculateTopicPostCount(category);
|
||||
@@ -76,14 +79,6 @@ Categories.getCategoryById = function (data, callback) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
Categories.isIgnored = function (cids, uid, callback) {
|
||||
if (parseInt(uid, 10) <= 0) {
|
||||
return setImmediate(callback, null, cids.map(() => false));
|
||||
}
|
||||
const keys = cids.map(cid => 'cid:' + cid + ':ignorers');
|
||||
db.isMemberOfSortedSets(keys, uid, callback);
|
||||
};
|
||||
|
||||
Categories.getAllCidsFromSet = function (key, callback) {
|
||||
const cids = cache.get(key);
|
||||
if (cids) {
|
||||
@@ -135,10 +130,45 @@ Categories.getCategoriesByPrivilege = function (set, uid, privilege, callback) {
|
||||
Categories.getModerators = function (cid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Groups.getMembers('cid:' + cid + ':privileges:moderate', 0, -1, next);
|
||||
Categories.getModeratorUids([cid], next);
|
||||
},
|
||||
function (uids, next) {
|
||||
user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture'], next);
|
||||
user.getUsersFields(uids[0], ['uid', 'username', 'userslug', 'picture'], next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Categories.getModeratorUids = function (cids, callback) {
|
||||
var sets;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
var groupNames = cids.reduce(function (memo, cid) {
|
||||
memo.push('cid:' + cid + ':privileges:moderate');
|
||||
memo.push('cid:' + cid + ':privileges:groups:moderate');
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
groups.getMembersOfGroups(groupNames, next);
|
||||
},
|
||||
function (memberSets, next) {
|
||||
// Every other set is actually a list of user groups, not uids, so convert those to members
|
||||
sets = memberSets.reduce(function (memo, set, idx) {
|
||||
if (idx % 2) {
|
||||
memo.groupNames.push(set);
|
||||
} else {
|
||||
memo.uids.push(set);
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, { groupNames: [], uids: [] });
|
||||
|
||||
groups.getMembersOfGroups(sets.groupNames, next);
|
||||
},
|
||||
function (groupUids, next) {
|
||||
const moderatorUids = cids.map(function (cid, index) {
|
||||
return _.union(sets.uids[index].concat(groupUids[index]));
|
||||
});
|
||||
next(null, moderatorUids);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
@@ -443,20 +473,4 @@ Categories.buildForSelectCategories = function (categories, callback) {
|
||||
callback(null, categoriesData);
|
||||
};
|
||||
|
||||
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.isSortedSetMembers('cid:' + cid + ':ignorers', uids, next);
|
||||
},
|
||||
function (isIgnoring, next) {
|
||||
const readingUids = uids.filter((uid, index) => uid && !isIgnoring[index]);
|
||||
next(null, readingUids);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Categories.async = require('../promisify')(Categories);
|
||||
|
||||
80
src/categories/watch.js
Normal file
80
src/categories/watch.js
Normal file
@@ -0,0 +1,80 @@
|
||||
'use strict';
|
||||
|
||||
const async = require('async');
|
||||
|
||||
const db = require('../database');
|
||||
const user = require('../user');
|
||||
|
||||
module.exports = function (Categories) {
|
||||
Categories.watchStates = {
|
||||
ignoring: 1,
|
||||
notwatching: 2,
|
||||
watching: 3,
|
||||
};
|
||||
|
||||
Categories.isIgnored = function (cids, uid, callback) {
|
||||
if (!(parseInt(uid, 10) > 0)) {
|
||||
return setImmediate(callback, null, cids.map(() => false));
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getWatchState(cids, uid, next);
|
||||
},
|
||||
function (states, next) {
|
||||
next(null, states.map(state => state === Categories.watchStates.ignoring));
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Categories.getWatchState = function (cids, uid, callback) {
|
||||
if (!(parseInt(uid, 10) > 0)) {
|
||||
return setImmediate(callback, null, cids.map(() => Categories.watchStates.notwatching));
|
||||
}
|
||||
if (!Array.isArray(cids) || !cids.length) {
|
||||
return setImmediate(callback, null, []);
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
const keys = cids.map(cid => 'cid:' + cid + ':uid:watch:state');
|
||||
async.parallel({
|
||||
userSettings: async.apply(user.getSettings, uid),
|
||||
states: async.apply(db.sortedSetsScore, keys, uid),
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
next(null, results.states.map(state => state || Categories.watchStates[results.userSettings.categoryWatchState]));
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Categories.getIgnorers = function (cid, start, stop, callback) {
|
||||
const count = (stop === -1) ? -1 : (stop - start + 1);
|
||||
db.getSortedSetRevRangeByScore('cid:' + cid + ':uid:watch:state', start, count, Categories.watchStates.ignoring, Categories.watchStates.ignoring, callback);
|
||||
};
|
||||
|
||||
Categories.filterIgnoringUids = function (cid, uids, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getUidsWatchStates(cid, uids, next);
|
||||
},
|
||||
function (states, next) {
|
||||
const readingUids = uids.filter((uid, index) => uid && states[index] !== Categories.watchStates.ignoring);
|
||||
next(null, readingUids);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Categories.getUidsWatchStates = function (cid, uids, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
userSettings: async.apply(user.getMultipleUserSettings, uids),
|
||||
states: async.apply(db.sortedSetScores, 'cid:' + cid + ':uid:watch:state', uids),
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
next(null, results.states.map((state, index) => state || Categories.watchStates[results.userSettings[index].categoryWatchState]));
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
};
|
||||
@@ -3,6 +3,8 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
require('../../require-main');
|
||||
|
||||
var packageInstall = require('./package-install');
|
||||
var dirname = require('./paths').baseDir;
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ var validator = require('validator');
|
||||
var meta = require('../meta');
|
||||
var plugins = require('../plugins');
|
||||
|
||||
exports.handle404 = function (req, res) {
|
||||
exports.handle404 = function handle404(req, res) {
|
||||
var relativePath = nconf.get('relative_path');
|
||||
var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js');
|
||||
var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js(\\?v=\\w+)?$');
|
||||
|
||||
if (plugins.hasListeners('action:meta.override404')) {
|
||||
return plugins.fireHook('action:meta.override404', {
|
||||
|
||||
@@ -21,8 +21,8 @@ categoriesController.get = function (req, res, callback) {
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
ignored: function (next) {
|
||||
user.getIgnoredCategories(userData.uid, next);
|
||||
states: function (next) {
|
||||
user.getCategoryWatchState(userData.uid, next);
|
||||
},
|
||||
categories: function (next) {
|
||||
categories.buildForSelect(userData.uid, 'find', next);
|
||||
@@ -32,7 +32,9 @@ categoriesController.get = function (req, res, callback) {
|
||||
function (results) {
|
||||
results.categories.forEach(function (category) {
|
||||
if (category) {
|
||||
category.isIgnored = results.ignored.includes(String(category.cid));
|
||||
category.isIgnored = results.states[category.cid] === categories.watchStates.ignoring;
|
||||
category.isWatched = results.states[category.cid] === categories.watchStates.watching;
|
||||
category.isNotWatched = results.states[category.cid] === categories.watchStates.notwatching;
|
||||
}
|
||||
});
|
||||
userData.categories = results.categories;
|
||||
|
||||
@@ -41,11 +41,13 @@ profileController.get = function (req, res, callback) {
|
||||
}
|
||||
userData = _userData;
|
||||
|
||||
req.session.uids_viewed = req.session.uids_viewed || {};
|
||||
if (req.uid >= 0) {
|
||||
req.session.uids_viewed = req.session.uids_viewed || {};
|
||||
|
||||
if (req.uid !== userData.uid && (!req.session.uids_viewed[userData.uid] || req.session.uids_viewed[userData.uid] < Date.now() - 3600000)) {
|
||||
user.incrementUserFieldBy(userData.uid, 'profileviews', 1);
|
||||
req.session.uids_viewed[userData.uid] = Date.now();
|
||||
if (req.uid !== userData.uid && (!req.session.uids_viewed[userData.uid] || req.session.uids_viewed[userData.uid] < Date.now() - 3600000)) {
|
||||
user.incrementUserFieldBy(userData.uid, 'profileviews', 1);
|
||||
req.session.uids_viewed[userData.uid] = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var nconf = require('nconf');
|
||||
var winston = require('winston');
|
||||
var _ = require('lodash');
|
||||
var jwt = require('jsonwebtoken');
|
||||
|
||||
var user = require('../../user');
|
||||
var languages = require('../../languages');
|
||||
@@ -161,6 +164,8 @@ settingsController.get = function (req, res, callback) {
|
||||
};
|
||||
});
|
||||
|
||||
userData.categoryWatchState = { [userData.settings.categoryWatchState]: true };
|
||||
|
||||
userData.disableCustomUserSkins = meta.config.disableCustomUserSkins;
|
||||
|
||||
userData.allowUserHomePage = meta.config.allowUserHomePage;
|
||||
@@ -181,6 +186,52 @@ settingsController.get = function (req, res, callback) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
settingsController.unsubscribe = function (req, res) {
|
||||
if (!req.params.token) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
|
||||
jwt.verify(req.params.token, nconf.get('secret'), function (err, payload) {
|
||||
if (err) {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
|
||||
switch (payload.template) {
|
||||
case 'digest':
|
||||
async.parallel([
|
||||
async.apply(user.setSetting, payload.uid, 'dailyDigestFreq', 'off'),
|
||||
async.apply(user.updateDigestSetting, payload.uid, 'off'),
|
||||
], function (err) {
|
||||
if (err) {
|
||||
winston.error('[settings/unsubscribe] One-click unsubscribe failed with error: ' + err.message);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
||||
return res.sendStatus(200);
|
||||
});
|
||||
break;
|
||||
case 'notification':
|
||||
async.waterfall([
|
||||
async.apply(db.getObjectField, 'user:' + payload.uid + ':settings', 'notificationType_' + payload.type),
|
||||
(current, next) => {
|
||||
user.setSetting(payload.uid, 'notificationType_' + payload.type, (current === 'notificationemail' ? 'notification' : 'none'), next);
|
||||
},
|
||||
], function (err) {
|
||||
if (err) {
|
||||
winston.error('[settings/unsubscribe] One-click unsubscribe failed with error: ' + err.message);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
||||
return res.sendStatus(200);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
res.sendStatus(404);
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function getNotificationSettings(userData, callback) {
|
||||
var privilegedTypes = [];
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const validator = require('validator');
|
||||
var plugins = require('../../plugins');
|
||||
|
||||
var hooksController = module.exports;
|
||||
@@ -18,7 +19,7 @@ hooksController.get = function (req, res) {
|
||||
current.methods.push({
|
||||
id: hookData.id,
|
||||
priority: hookData.priority,
|
||||
method: hookData.method ? hookData.method.toString() : 'No plugin function!',
|
||||
method: hookData.method ? validator.escape(hookData.method.toString()) : 'No plugin function!',
|
||||
index: hookIndex + '-code-' + methodIndex,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ var categories = require('../categories');
|
||||
var privileges = require('../privileges');
|
||||
var plugins = require('../plugins');
|
||||
var translator = require('../translator');
|
||||
var languages = require('../languages');
|
||||
|
||||
var apiController = module.exports;
|
||||
|
||||
@@ -57,11 +58,12 @@ apiController.loadConfig = function (req, callback) {
|
||||
config.requireEmailConfirmation = meta.config.requireEmailConfirmation === 1;
|
||||
config.topicPostSort = meta.config.topicPostSort || 'oldest_to_newest';
|
||||
config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest';
|
||||
config.csrf_token = req.csrfToken && req.csrfToken();
|
||||
config.csrf_token = req.uid >= 0 && req.csrfToken && req.csrfToken();
|
||||
config.searchEnabled = plugins.hasListeners('filter:search.query');
|
||||
config.bootswatchSkin = meta.config.bootswatchSkin || '';
|
||||
config.enablePostHistory = (meta.config.enablePostHistory || 1) === 1;
|
||||
config.notificationAlertTimeout = meta.config.notificationAlertTimeout || 5000;
|
||||
config.timeagoCodes = languages.timeagoCodes;
|
||||
|
||||
if (config.useOutgoingLinksPage) {
|
||||
config.outgoingLinksWhitelist = meta.config['outgoingLinks:whitelist'];
|
||||
@@ -101,6 +103,10 @@ apiController.loadConfig = function (req, callback) {
|
||||
config.bootswatchSkin = (meta.config.disableCustomUserSkins !== 1 && settings.bootswatchSkin && settings.bootswatchSkin !== '') ? settings.bootswatchSkin : '';
|
||||
plugins.fireHook('filter:config.get', config, next);
|
||||
},
|
||||
function (config, next) {
|
||||
req.res.locals.config = config;
|
||||
process.nextTick(next, null, config);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ var privileges = require('../privileges');
|
||||
var sockets = require('../socket.io');
|
||||
|
||||
var authenticationController = module.exports;
|
||||
var apiController = require('./api');
|
||||
|
||||
authenticationController.register = function (req, res) {
|
||||
var registrationType = meta.config.registrationType || 'normal';
|
||||
@@ -286,10 +285,10 @@ function continueLogin(req, res, next) {
|
||||
} else {
|
||||
delete req.query.lang;
|
||||
|
||||
async.parallel({
|
||||
async.series({
|
||||
doLogin: async.apply(authenticationController.doLogin, req, userData.uid),
|
||||
buildHeader: async.apply(middleware.buildHeader, req, res),
|
||||
header: async.apply(middleware.generateHeader, req, res, {}),
|
||||
config: async.apply(apiController.loadConfig, req),
|
||||
}, function (err, payload) {
|
||||
if (err) {
|
||||
return helpers.noScriptErrors(req, res, err.message, 403);
|
||||
@@ -309,7 +308,7 @@ function continueLogin(req, res, next) {
|
||||
res.status(200).send({
|
||||
next: destination,
|
||||
header: payload.header,
|
||||
config: payload.config,
|
||||
config: res.locals.config,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -474,33 +473,40 @@ authenticationController.logout = function (req, res, next) {
|
||||
req.logout();
|
||||
req.session.regenerate(function (err) {
|
||||
req.uid = 0;
|
||||
req.headers['x-csrf-token'] = req.csrfToken();
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
function (next) {
|
||||
user.setUserField(req.uid, 'lastonline', Date.now() - 300000, next);
|
||||
user.setUserField(req.uid, 'lastonline', Date.now() - (meta.config.onlineCutoff * 60000), next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetRemove('users:online', req.uid, next);
|
||||
},
|
||||
function (next) {
|
||||
plugins.fireHook('static:user.loggedOut', { req: req, res: res, uid: req.uid }, next);
|
||||
},
|
||||
async.apply(middleware.autoLocale, req, res),
|
||||
function () {
|
||||
// Force session check for all connected socket.io clients with the same session id
|
||||
sockets.in('sess_' + req.sessionID).emit('checkSession', 0);
|
||||
if (req.body.noscript === 'true') {
|
||||
res.redirect(nconf.get('relative_path') + '/');
|
||||
} else {
|
||||
async.parallel({
|
||||
async.series({
|
||||
buildHeader: async.apply(middleware.buildHeader, req, res),
|
||||
header: async.apply(middleware.generateHeader, req, res, {}),
|
||||
config: async.apply(apiController.loadConfig, req),
|
||||
}, function (err, payload) {
|
||||
if (err) {
|
||||
return res.status(500);
|
||||
}
|
||||
|
||||
res.status(200).send({
|
||||
payload = {
|
||||
header: payload.header,
|
||||
config: payload.config,
|
||||
});
|
||||
config: res.locals.config,
|
||||
};
|
||||
plugins.fireHook('filter:user.logout', payload);
|
||||
res.status(200).send(payload);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ var winston = require('winston');
|
||||
var validator = require('validator');
|
||||
var plugins = require('../plugins');
|
||||
|
||||
exports.handleURIErrors = function (err, req, res, next) {
|
||||
exports.handleURIErrors = function handleURIErrors(err, req, res, next) {
|
||||
// Handle cases where malformed URIs are passed in
|
||||
if (err instanceof URIError) {
|
||||
const cleanPath = req.path.replace(new RegExp('^' + nconf.get('relative_path')), '');
|
||||
@@ -36,7 +36,7 @@ exports.handleURIErrors = function (err, req, res, next) {
|
||||
|
||||
// this needs to have four arguments or express treats it as `(req, res, next)`
|
||||
// don't remove `next`!
|
||||
exports.handleErrors = function (err, req, res, next) { // eslint-disable-line no-unused-vars
|
||||
exports.handleErrors = function handleErrors(err, req, res, next) { // eslint-disable-line no-unused-vars
|
||||
var cases = {
|
||||
EBADCSRFTOKEN: function () {
|
||||
winston.error(req.path + '\n', err.message);
|
||||
|
||||
@@ -239,6 +239,20 @@ helpers.getCategories = function (set, uid, privilege, selectedCid, callback) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
helpers.getCategoriesByStates = function (uid, selectedCid, states, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.getCategoriesByStates(uid, states, next);
|
||||
},
|
||||
function (cids, next) {
|
||||
privileges.categories.filterCids('read', cids, uid, next);
|
||||
},
|
||||
function (cids, next) {
|
||||
getCategoryData(cids, uid, selectedCid, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
helpers.getWatchedCategories = function (uid, selectedCid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
|
||||
@@ -92,7 +92,7 @@ Controllers.login = function (req, res, next) {
|
||||
var registrationType = meta.config.registrationType || 'normal';
|
||||
|
||||
var allowLoginWith = (meta.config.allowLoginWith || 'username-email');
|
||||
var returnTo = (req.headers['x-return-to'] || '').replace(nconf.get('base_url'), '');
|
||||
var returnTo = (req.headers['x-return-to'] || '').replace(nconf.get('base_url') + nconf.get('relative_path'), '');
|
||||
|
||||
var errorText;
|
||||
if (req.query.error === 'csrf-invalid') {
|
||||
@@ -214,7 +214,7 @@ Controllers.registerInterstitial = function (req, res, next) {
|
||||
// No interstitials, redirect to home
|
||||
const returnTo = req.session.returnTo || req.session.registration.returnTo;
|
||||
delete req.session.registration;
|
||||
return helpers.redirect(res, returnTo || nconf.get('relative_path') + '/');
|
||||
return helpers.redirect(res, returnTo || '/');
|
||||
}
|
||||
var renders = data.interstitials.map(function (interstitial) {
|
||||
return async.apply(req.app.render.bind(req.app), interstitial.template, interstitial.data || {});
|
||||
@@ -252,6 +252,7 @@ Controllers.robots = function (req, res) {
|
||||
res.send('User-agent: *\n' +
|
||||
'Disallow: ' + nconf.get('relative_path') + '/admin/\n' +
|
||||
'Disallow: ' + nconf.get('relative_path') + '/reset/\n' +
|
||||
'Disallow: ' + nconf.get('relative_path') + '/compose\n' +
|
||||
'Sitemap: ' + nconf.get('url') + '/sitemap.xml');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ var async = require('async');
|
||||
var nconf = require('nconf');
|
||||
|
||||
var user = require('../user');
|
||||
var categories = require('../categories');
|
||||
var topics = require('../topics');
|
||||
var meta = require('../meta');
|
||||
var helpers = require('./helpers');
|
||||
@@ -47,8 +48,8 @@ recentController.getData = function (req, url, sort, callback) {
|
||||
settings: function (next) {
|
||||
user.getSettings(req.uid, next);
|
||||
},
|
||||
watchedCategories: function (next) {
|
||||
helpers.getWatchedCategories(req.uid, cid, next);
|
||||
categories: function (next) {
|
||||
helpers.getCategoriesByStates(req.uid, cid, [categories.watchStates.watching, categories.watchStates.notwatching], next);
|
||||
},
|
||||
rssToken: function (next) {
|
||||
user.auth.getFeedToken(req.uid, next);
|
||||
@@ -58,7 +59,7 @@ recentController.getData = function (req, url, sort, callback) {
|
||||
function (results, next) {
|
||||
rssToken = results.rssToken;
|
||||
settings = results.settings;
|
||||
categoryData = results.watchedCategories;
|
||||
categoryData = results.categories;
|
||||
|
||||
var start = Math.max(0, (page - 1) * settings.topicsPerPage);
|
||||
stop = start + settings.topicsPerPage - 1;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
var async = require('async');
|
||||
var nconf = require('nconf');
|
||||
var winston = require('winston');
|
||||
|
||||
var user = require('../user');
|
||||
var meta = require('../meta');
|
||||
@@ -17,7 +18,7 @@ var analytics = require('../analytics');
|
||||
|
||||
var topicsController = module.exports;
|
||||
|
||||
topicsController.get = function (req, res, callback) {
|
||||
topicsController.get = function getTopic(req, res, callback) {
|
||||
var tid = req.params.topic_id;
|
||||
var currentPage = parseInt(req.query.page, 10) || 1;
|
||||
var pageCount = 1;
|
||||
@@ -161,16 +162,18 @@ topicsController.get = function (req, res, callback) {
|
||||
res.locals.linkTags.push(rel);
|
||||
});
|
||||
|
||||
req.session.tids_viewed = req.session.tids_viewed || {};
|
||||
if (!req.session.tids_viewed[tid] || req.session.tids_viewed[tid] < Date.now() - 3600000) {
|
||||
topics.increaseViewCount(tid);
|
||||
req.session.tids_viewed[tid] = Date.now();
|
||||
if (req.uid >= 0) {
|
||||
req.session.tids_viewed = req.session.tids_viewed || {};
|
||||
if (!req.session.tids_viewed[tid] || req.session.tids_viewed[tid] < Date.now() - 3600000) {
|
||||
topics.increaseViewCount(tid);
|
||||
req.session.tids_viewed[tid] = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
if (req.loggedIn) {
|
||||
topics.markAsRead([tid], req.uid, function (err, markedRead) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
return winston.error(err);
|
||||
}
|
||||
if (markedRead) {
|
||||
topics.pushUnreadCount(req.uid);
|
||||
|
||||
@@ -8,6 +8,7 @@ var querystring = require('querystring');
|
||||
var meta = require('../meta');
|
||||
var pagination = require('../pagination');
|
||||
var user = require('../user');
|
||||
var categories = require('../categories');
|
||||
var topics = require('../topics');
|
||||
var plugins = require('../plugins');
|
||||
var helpers = require('./helpers');
|
||||
@@ -35,7 +36,7 @@ unreadController.get = function (req, res, next) {
|
||||
if (plugins.hasListeners('filter:unread.categories')) {
|
||||
plugins.fireHook('filter:unread.categories', { uid: req.uid, cid: cid }, next);
|
||||
} else {
|
||||
helpers.getWatchedCategories(req.uid, cid, next);
|
||||
helpers.getCategoriesByStates(req.uid, cid, [categories.watchStates.watching], next);
|
||||
}
|
||||
},
|
||||
settings: function (next) {
|
||||
|
||||
@@ -168,31 +168,6 @@ uploadsController.uploadThumb = function (req, res, next) {
|
||||
}, next);
|
||||
};
|
||||
|
||||
uploadsController.uploadGroupCover = function (uid, uploadedFile, callback) {
|
||||
if (plugins.hasListeners('filter:uploadImage')) {
|
||||
return plugins.fireHook('filter:uploadImage', {
|
||||
image: uploadedFile,
|
||||
uid: uid,
|
||||
}, callback);
|
||||
}
|
||||
|
||||
if (plugins.hasListeners('filter:uploadFile')) {
|
||||
return plugins.fireHook('filter:uploadFile', {
|
||||
file: uploadedFile,
|
||||
uid: uid,
|
||||
}, callback);
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
file.isFileTypeAllowed(uploadedFile.path, next);
|
||||
},
|
||||
function (next) {
|
||||
saveFileToLocal(uid, uploadedFile, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
uploadsController.uploadFile = function (uid, uploadedFile, callback) {
|
||||
if (plugins.hasListeners('filter:uploadFile')) {
|
||||
return plugins.fireHook('filter:uploadFile', {
|
||||
|
||||
@@ -15,17 +15,24 @@ coverPhoto.getDefaultProfileCover = function (uid) {
|
||||
};
|
||||
|
||||
function getCover(type, id) {
|
||||
const defaultCover = nconf.get('relative_path') + '/assets/images/cover-default.png';
|
||||
if (meta.config[type + ':defaultCovers']) {
|
||||
var covers = meta.config[type + ':defaultCovers'].trim().split(/[\s,]+/g);
|
||||
var covers = String(meta.config[type + ':defaultCovers']).trim().split(/[\s,]+/g);
|
||||
let coverPhoto = defaultCover;
|
||||
if (!covers.length) {
|
||||
return coverPhoto;
|
||||
}
|
||||
|
||||
if (typeof id === 'string') {
|
||||
id = (id.charCodeAt(0) + id.charCodeAt(1)) % covers.length;
|
||||
} else {
|
||||
id %= covers.length;
|
||||
}
|
||||
|
||||
return covers[id];
|
||||
if (covers[id] && !covers[id].startsWith('http')) {
|
||||
coverPhoto = nconf.get('relative_path') + covers[id];
|
||||
}
|
||||
return coverPhoto;
|
||||
}
|
||||
|
||||
return nconf.get('relative_path') + '/assets/images/cover-default.png';
|
||||
return defaultCover;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ module.exports.create = function (name) {
|
||||
var LRU = require('lru-cache');
|
||||
var pubsub = require('../pubsub');
|
||||
|
||||
var cache = LRU({
|
||||
var cache = new LRU({
|
||||
max: 20000,
|
||||
length: function () { return 1; },
|
||||
maxAge: 0,
|
||||
|
||||
@@ -16,10 +16,9 @@ module.exports = function (db, module) {
|
||||
if (!key || !data) {
|
||||
return callback();
|
||||
}
|
||||
if (data.hasOwnProperty('')) {
|
||||
delete data[''];
|
||||
}
|
||||
db.collection('objects').updateOne({ _key: key }, { $set: data }, { upsert: true, w: 1 }, function (err) {
|
||||
|
||||
const writeData = helpers.serializeData(data);
|
||||
db.collection('objects').updateOne({ _key: key }, { $set: writeData }, { upsert: true, w: 1 }, function (err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -34,7 +33,6 @@ module.exports = function (db, module) {
|
||||
return callback();
|
||||
}
|
||||
var data = {};
|
||||
field = helpers.fieldToString(field);
|
||||
data[field] = value;
|
||||
module.setObject(key, data, callback);
|
||||
};
|
||||
@@ -76,7 +74,7 @@ module.exports = function (db, module) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
data = data.map(helpers.deserializeData);
|
||||
var map = helpers.toMap(data);
|
||||
unCachedKeys.forEach(function (key) {
|
||||
cachedData[key] = map[key] || null;
|
||||
|
||||
@@ -22,8 +22,27 @@ helpers.fieldToString = function (field) {
|
||||
field = field.toString();
|
||||
}
|
||||
// if there is a '.' in the field name it inserts subdocument in mongo, replace '.'s with \uff0E
|
||||
field = field.replace(/\./g, '\uff0E');
|
||||
return field;
|
||||
return field.replace(/\./g, '\uff0E');
|
||||
};
|
||||
|
||||
helpers.serializeData = function (data) {
|
||||
const serialized = {};
|
||||
for (const field in data) {
|
||||
if (data.hasOwnProperty(field) && field !== '') {
|
||||
serialized[helpers.fieldToString(field)] = data[field];
|
||||
}
|
||||
}
|
||||
return serialized;
|
||||
};
|
||||
|
||||
helpers.deserializeData = function (data) {
|
||||
const deserialized = {};
|
||||
for (const field in data) {
|
||||
if (data.hasOwnProperty(field)) {
|
||||
deserialized[field.replace(/\uff0E/g, '.')] = data[field];
|
||||
}
|
||||
}
|
||||
return deserialized;
|
||||
};
|
||||
|
||||
helpers.valueToString = function (value) {
|
||||
|
||||
@@ -55,6 +55,9 @@ module.exports = function (db, module) {
|
||||
}
|
||||
|
||||
bulk.execute(function (err) {
|
||||
if (err && err.message.startsWith('E11000 duplicate key error')) {
|
||||
return process.nextTick(module.setsAdd, keys, value, callback);
|
||||
}
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -56,6 +56,10 @@ module.exports = function (db, module) {
|
||||
query.score.$lte = max;
|
||||
}
|
||||
|
||||
if (max === min) {
|
||||
query.score = max;
|
||||
}
|
||||
|
||||
const fields = { _id: 0, _key: 0 };
|
||||
if (!withScores) {
|
||||
fields.score = 0;
|
||||
@@ -115,10 +119,10 @@ module.exports = function (db, module) {
|
||||
};
|
||||
|
||||
function getSortedSetRangeByScore(key, start, count, min, max, sort, withScores, callback) {
|
||||
if (parseInt(count, 10) === -1) {
|
||||
count = 0;
|
||||
if (parseInt(count, 10) === 0) {
|
||||
return setImmediate(callback, null, []);
|
||||
}
|
||||
var stop = start + count - 1;
|
||||
const stop = (parseInt(count, 10) === -1) ? -1 : (start + count - 1);
|
||||
getSortedSetRange(key, start, stop, min, max, sort, withScores, callback);
|
||||
}
|
||||
|
||||
@@ -261,7 +265,7 @@ module.exports = function (db, module) {
|
||||
|
||||
module.sortedSetsScore = function (keys, value, callback) {
|
||||
if (!Array.isArray(keys) || !keys.length) {
|
||||
return callback();
|
||||
return callback(null, []);
|
||||
}
|
||||
value = helpers.valueToString(value);
|
||||
db.collection('objects').find({ _key: { $in: keys }, value: value }, { projection: { _id: 0, value: 0 } }).toArray(function (err, result) {
|
||||
@@ -269,22 +273,27 @@ module.exports = function (db, module) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var map = helpers.toMap(result);
|
||||
var returnData = [];
|
||||
var item;
|
||||
var map = {};
|
||||
result.forEach(function (item) {
|
||||
if (item) {
|
||||
map[item._key] = item;
|
||||
}
|
||||
});
|
||||
|
||||
for (var i = 0; i < keys.length; i += 1) {
|
||||
item = map[keys[i]];
|
||||
returnData.push(item ? item.score : null);
|
||||
}
|
||||
result = keys.map(function (key) {
|
||||
return map[key] ? map[key].score : null;
|
||||
});
|
||||
|
||||
callback(null, returnData);
|
||||
callback(null, result);
|
||||
});
|
||||
};
|
||||
|
||||
module.sortedSetScores = function (key, values, callback) {
|
||||
if (!key) {
|
||||
return callback(null, null);
|
||||
return setImmediate(callback, null, null);
|
||||
}
|
||||
if (!values.length) {
|
||||
return setImmediate(callback, null, []);
|
||||
}
|
||||
values = values.map(helpers.valueToString);
|
||||
db.collection('objects').find({ _key: key, value: { $in: values } }, { projection: { _id: 0, _key: 0 } }).toArray(function (err, result) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function (db, module) {
|
||||
var helpers = module.helpers.postgres;
|
||||
@@ -44,9 +45,7 @@ SELECT $1::TEXT, m
|
||||
value = [value];
|
||||
}
|
||||
|
||||
keys = keys.filter(function (k, i, a) {
|
||||
return a.indexOf(k) === i;
|
||||
});
|
||||
keys = _.uniq(keys);
|
||||
|
||||
module.transaction(function (tx, done) {
|
||||
var query = tx.client.query.bind(tx.client);
|
||||
|
||||
@@ -348,7 +348,7 @@ SELECT z."score" s
|
||||
|
||||
module.sortedSetsScore = function (keys, value, callback) {
|
||||
if (!Array.isArray(keys) || !keys.length) {
|
||||
return callback();
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
value = helpers.valueToString(value);
|
||||
@@ -382,9 +382,11 @@ SELECT o."_key" k,
|
||||
|
||||
module.sortedSetScores = function (key, values, callback) {
|
||||
if (!key) {
|
||||
return callback(null, null);
|
||||
return setImmediate(callback, null, null);
|
||||
}
|
||||
if (!values.length) {
|
||||
return setImmediate(callback, null, []);
|
||||
}
|
||||
|
||||
values = values.map(helpers.valueToString);
|
||||
|
||||
query({
|
||||
|
||||
@@ -170,6 +170,9 @@ module.exports = function (redisClient, module) {
|
||||
};
|
||||
|
||||
module.sortedSetsScore = function (keys, value, callback) {
|
||||
if (!Array.isArray(keys) || !keys.length) {
|
||||
return callback(null, []);
|
||||
}
|
||||
helpers.execKeysValue(redisClient, 'batch', 'zscore', keys, value, function (err, scores) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
@@ -182,6 +185,9 @@ module.exports = function (redisClient, module) {
|
||||
};
|
||||
|
||||
module.sortedSetScores = function (key, values, callback) {
|
||||
if (!values.length) {
|
||||
return setImmediate(callback, null, []);
|
||||
}
|
||||
helpers.execKeyValues(redisClient, 'batch', 'zscore', key, values, function (err, scores) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
|
||||
@@ -11,6 +11,7 @@ var url = require('url');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var _ = require('lodash');
|
||||
var jwt = require('jsonwebtoken');
|
||||
|
||||
var User = require('./user');
|
||||
var Plugins = require('./plugins');
|
||||
@@ -110,7 +111,7 @@ Emailer.setupFallbackTransport = function (config) {
|
||||
smtpOptions.ignoreTLS = false;
|
||||
}
|
||||
} else {
|
||||
smtpOptions.service = config['email:smtpTransport:service'];
|
||||
smtpOptions.service = String(config['email:smtpTransport:service']);
|
||||
}
|
||||
|
||||
Emailer.transports.smtp = nodemailer.createTransport(smtpOptions);
|
||||
@@ -216,6 +217,31 @@ Emailer.sendToEmail = function (template, email, language, params, callback) {
|
||||
'List-Unsubscribe': '<' + [nconf.get('url'), 'uid', params.uid, 'settings'].join('/') + '>',
|
||||
}, params.headers);
|
||||
|
||||
// Digests and notifications can be one-click unsubbed
|
||||
let payload = {
|
||||
template: template,
|
||||
uid: params.uid,
|
||||
};
|
||||
|
||||
switch (template) {
|
||||
case 'digest':
|
||||
payload = jwt.sign(payload, nconf.get('secret'), {
|
||||
expiresIn: '30d',
|
||||
});
|
||||
params.headers['List-Unsubscribe'] = '<' + [nconf.get('url'), 'email', 'unsubscribe', payload].join('/') + '>';
|
||||
params.headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click';
|
||||
break;
|
||||
|
||||
case 'notification':
|
||||
payload.type = params.notification.type;
|
||||
payload = jwt.sign(payload, nconf.get('secret'), {
|
||||
expiresIn: '30d',
|
||||
});
|
||||
params.headers['List-Unsubscribe'] = '<' + [nconf.get('url'), 'email', 'unsubscribe', payload].join('/') + '>';
|
||||
params.headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click';
|
||||
break;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Plugins.fireHook('filter:email.params', {
|
||||
|
||||
@@ -158,9 +158,7 @@ events.deleteEvents = function (eids, callback) {
|
||||
var keys;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
keys = eids.map(function (eid) {
|
||||
return 'event:' + eid;
|
||||
});
|
||||
keys = eids.map(eid => 'event:' + eid);
|
||||
db.getObjectsFields(keys, ['type'], next);
|
||||
},
|
||||
function (eventData, next) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
var LRU = require('lru-cache');
|
||||
var pubsub = require('../pubsub');
|
||||
|
||||
var cache = LRU({
|
||||
var cache = new LRU({
|
||||
max: 40000,
|
||||
maxAge: 0,
|
||||
});
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
var async = require('async');
|
||||
var path = require('path');
|
||||
var mime = require('mime');
|
||||
|
||||
var db = require('../database');
|
||||
var image = require('../image');
|
||||
var file = require('../file');
|
||||
var uploadsController = require('../controllers/uploads');
|
||||
|
||||
module.exports = function (Groups) {
|
||||
Groups.updateCoverPosition = function (groupName, position, callback) {
|
||||
@@ -25,7 +23,7 @@ module.exports = function (Groups) {
|
||||
|
||||
var tempPath = data.file ? data.file : '';
|
||||
var url;
|
||||
var type = data.file ? mime.getType(data.file) : 'image/png';
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
if (tempPath) {
|
||||
@@ -36,10 +34,10 @@ module.exports = function (Groups) {
|
||||
function (_tempPath, next) {
|
||||
tempPath = _tempPath;
|
||||
|
||||
uploadsController.uploadGroupCover(uid, {
|
||||
name: 'groupCover' + path.extname(tempPath),
|
||||
const filename = 'groupCover-' + data.groupName + path.extname(tempPath);
|
||||
image.uploadImage(filename, 'files', {
|
||||
path: tempPath,
|
||||
type: type,
|
||||
uid: uid,
|
||||
}, next);
|
||||
},
|
||||
function (uploadData, next) {
|
||||
@@ -53,10 +51,9 @@ module.exports = function (Groups) {
|
||||
}, next);
|
||||
},
|
||||
function (next) {
|
||||
uploadsController.uploadGroupCover(uid, {
|
||||
name: 'groupCoverThumb' + path.extname(tempPath),
|
||||
image.uploadImage('groupCoverThumb-' + data.groupName + path.extname(tempPath), 'files', {
|
||||
path: tempPath,
|
||||
type: type,
|
||||
uid: uid,
|
||||
}, next);
|
||||
},
|
||||
function (uploadData, next) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
var async = require('async');
|
||||
var validator = require('validator');
|
||||
var nconf = require('nconf');
|
||||
|
||||
var db = require('../database');
|
||||
var plugins = require('../plugins');
|
||||
@@ -91,8 +92,20 @@ function modifyGroup(group, fields) {
|
||||
group.createtimeISO = utils.toISOString(group.createtime);
|
||||
group.private = ([null, undefined].includes(group.private)) ? 1 : group.private;
|
||||
|
||||
group['cover:url'] = group['cover:url'] || require('../coverPhoto').getDefaultGroupCover(group.name);
|
||||
group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url'];
|
||||
|
||||
if (group['cover:url']) {
|
||||
group['cover:url'] = group['cover:url'].startsWith('http') ? group['cover:url'] : (nconf.get('relative_path') + group['cover:url']);
|
||||
} else {
|
||||
group['cover:url'] = require('../coverPhoto').getDefaultGroupCover(group.name);
|
||||
}
|
||||
|
||||
if (group['cover:thumb:url']) {
|
||||
group['cover:thumb:url'] = group['cover:thumb:url'].startsWith('http') ? group['cover:thumb:url'] : (nconf.get('relative_path') + group['cover:thumb:url']);
|
||||
} else {
|
||||
group['cover:thumb:url'] = require('../coverPhoto').getDefaultGroupCover(group.name);
|
||||
}
|
||||
|
||||
group['cover:position'] = validator.escape(String(group['cover:position'] || '50% 50%'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ Groups.getGroupsAndMembers = function (groupNames, callback) {
|
||||
data.groups.forEach(function (group, index) {
|
||||
if (group) {
|
||||
group.members = data.members[index] || [];
|
||||
group.truncated = group.memberCount > data.members.length;
|
||||
group.truncated = group.memberCount > group.members.length;
|
||||
}
|
||||
});
|
||||
next(null, data.groups);
|
||||
|
||||
@@ -20,6 +20,7 @@ module.exports = function (Groups) {
|
||||
async.parallel({
|
||||
notification: function (next) {
|
||||
notifications.create({
|
||||
type: 'group-request-membership',
|
||||
bodyShort: '[[groups:request.notification_title, ' + username + ']]',
|
||||
bodyLong: '[[groups:request.notification_text, ' + username + ', ' + groupName + ']]',
|
||||
nid: 'group:' + groupName + ':uid:' + uid + ':request',
|
||||
@@ -133,9 +134,7 @@ module.exports = function (Groups) {
|
||||
};
|
||||
|
||||
Groups.getMembersOfGroups = function (groupNames, callback) {
|
||||
db.getSortedSetsMembers(groupNames.map(function (name) {
|
||||
return 'group:' + name + ':members';
|
||||
}), callback);
|
||||
db.getSortedSetsMembers(groupNames.map(name => 'group:' + name + ':members'), callback);
|
||||
};
|
||||
|
||||
Groups.isMember = function (uid, groupName, callback) {
|
||||
|
||||
@@ -15,20 +15,23 @@ module.exports = function (Groups) {
|
||||
async.waterfall([
|
||||
async.apply(db.getSortedSetRange, 'groups:createtime', 0, -1),
|
||||
function (groupNames, next) {
|
||||
// Ephemeral groups and the registered-users groups are searchable
|
||||
groupNames = Groups.ephemeralGroups.concat(groupNames);
|
||||
if (!options.hideEphemeralGroups) {
|
||||
groupNames = Groups.ephemeralGroups.concat(groupNames);
|
||||
}
|
||||
groupNames = groupNames.filter(function (name) {
|
||||
return name.toLowerCase().includes(query) && name !== 'administrators' && !Groups.isPrivilegeGroup(name);
|
||||
});
|
||||
groupNames = groupNames.slice(0, 100);
|
||||
Groups.getGroupsData(groupNames, next);
|
||||
if (options.showMembers) {
|
||||
Groups.getGroupsAndMembers(groupNames, next);
|
||||
} else {
|
||||
Groups.getGroupsData(groupNames, next);
|
||||
}
|
||||
},
|
||||
function (groupsData, next) {
|
||||
groupsData = groupsData.filter(Boolean);
|
||||
if (options.filterHidden) {
|
||||
groupsData = groupsData.filter(function (group) {
|
||||
return !group.hidden;
|
||||
});
|
||||
groupsData = groupsData.filter(group => !group.hidden);
|
||||
}
|
||||
|
||||
Groups.sort(options.sort, groupsData, next);
|
||||
|
||||
25
src/image.js
25
src/image.js
@@ -139,3 +139,28 @@ image.writeImageDataToTempFile = function (imageData, callback) {
|
||||
image.sizeFromBase64 = function (imageData) {
|
||||
return Buffer.from(imageData.slice(imageData.indexOf('base64') + 7), 'base64').length;
|
||||
};
|
||||
|
||||
image.uploadImage = function (filename, folder, image, callback) {
|
||||
if (plugins.hasListeners('filter:uploadImage')) {
|
||||
return plugins.fireHook('filter:uploadImage', {
|
||||
image: image,
|
||||
uid: image.uid,
|
||||
}, callback);
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
file.isFileTypeAllowed(image.path, next);
|
||||
},
|
||||
function (next) {
|
||||
file.saveFileToLocal(filename, folder, image.path, next);
|
||||
},
|
||||
function (upload, next) {
|
||||
next(null, {
|
||||
url: upload.url,
|
||||
path: upload.path,
|
||||
name: image.name,
|
||||
});
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,9 @@ var async = require('async');
|
||||
var Languages = module.exports;
|
||||
var languagesPath = path.join(__dirname, '../build/public/language');
|
||||
|
||||
const files = fs.readdirSync(path.join(__dirname, '../public/vendor/jquery/timeago/locales'));
|
||||
Languages.timeagoCodes = files.filter(f => f.startsWith('jquery.timeago')).map(f => f.split('.')[2]);
|
||||
|
||||
Languages.get = function (language, namespace, callback) {
|
||||
fs.readFile(path.join(languagesPath, language, namespace + '.json'), { encoding: 'utf-8' }, function (err, data) {
|
||||
if (err) {
|
||||
|
||||
@@ -167,19 +167,14 @@ function build(targets, options, callback) {
|
||||
return aliases[target];
|
||||
})
|
||||
// filter nonexistent targets
|
||||
.filter(Boolean)
|
||||
// map multitargets to their sets
|
||||
.reduce(function (prev, target) {
|
||||
if (Array.isArray(targetHandlers[target])) {
|
||||
return prev.concat(targetHandlers[target]);
|
||||
}
|
||||
.filter(Boolean);
|
||||
|
||||
return prev.concat(target);
|
||||
}, [])
|
||||
// unique
|
||||
.filter(function (target, i, arr) {
|
||||
return arr.indexOf(target) === i;
|
||||
});
|
||||
// map multitargets to their sets
|
||||
targets = _.uniq(_.flatMap(targets, target => (
|
||||
Array.isArray(targetHandlers[target]) ?
|
||||
targetHandlers[target] :
|
||||
target
|
||||
)));
|
||||
|
||||
winston.verbose('[build] building the following targets: ' + targets.join(', '));
|
||||
|
||||
|
||||
@@ -232,7 +232,7 @@ function minifyAndSave(data, callback) {
|
||||
var minified = uglify.minify(scripts, {
|
||||
sourceMap: {
|
||||
filename: data.filename,
|
||||
url: data.filename + '.map',
|
||||
url: String(data.filename).split(/[/\\]/).pop() + '.map',
|
||||
includeSources: true,
|
||||
},
|
||||
compress: false,
|
||||
|
||||
@@ -4,6 +4,7 @@ var path = require('path');
|
||||
var async = require('async');
|
||||
var nconf = require('nconf');
|
||||
var jsesc = require('jsesc');
|
||||
var _ = require('lodash');
|
||||
|
||||
var db = require('../database');
|
||||
var user = require('../user');
|
||||
@@ -24,12 +25,16 @@ var controllers = {
|
||||
};
|
||||
|
||||
module.exports = function (middleware) {
|
||||
middleware.buildHeader = function (req, res, next) {
|
||||
middleware.buildHeader = function buildHeader(req, res, next) {
|
||||
res.locals.renderHeader = true;
|
||||
res.locals.isAPI = false;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
middleware.applyCSRF(req, res, next);
|
||||
if (req.uid >= 0) {
|
||||
middleware.applyCSRF(req, res, next);
|
||||
} else {
|
||||
setImmediate(next);
|
||||
}
|
||||
},
|
||||
function (next) {
|
||||
async.parallel({
|
||||
@@ -42,13 +47,13 @@ module.exports = function (middleware) {
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
res.locals.config = results.config;
|
||||
next();
|
||||
// Return no arguments
|
||||
setImmediate(next);
|
||||
},
|
||||
], next);
|
||||
};
|
||||
|
||||
middleware.generateHeader = function (req, res, data, callback) {
|
||||
middleware.generateHeader = function generateHeader(req, res, data, callback) {
|
||||
var registrationType = meta.config.registrationType || 'normal';
|
||||
res.locals.config = res.locals.config || {};
|
||||
var templateValues = {
|
||||
@@ -108,7 +113,7 @@ module.exports = function (middleware) {
|
||||
banned: async.apply(user.isBanned, req.uid),
|
||||
banReason: async.apply(user.getBannedReason, req.uid),
|
||||
|
||||
unreadCounts: async.apply(topics.getUnreadTids, { uid: req.uid, count: true }),
|
||||
unreadData: async.apply(topics.getUnreadData, { uid: req.uid }),
|
||||
unreadChatCount: async.apply(messaging.getUnreadCount, req.uid),
|
||||
unreadNotificationCount: async.apply(user.notifications.getUnreadCount, req.uid),
|
||||
}, next);
|
||||
@@ -119,6 +124,14 @@ module.exports = function (middleware) {
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
const unreadData = {
|
||||
'': {},
|
||||
new: {},
|
||||
watched: {},
|
||||
unreplied: {},
|
||||
};
|
||||
|
||||
results.user.unreadData = unreadData;
|
||||
results.user.isAdmin = results.isAdmin;
|
||||
results.user.isGlobalMod = results.isGlobalMod;
|
||||
results.user.isMod = !!results.isModerator;
|
||||
@@ -129,13 +142,15 @@ module.exports = function (middleware) {
|
||||
results.user['email:confirmed'] = results.user['email:confirmed'] === 1;
|
||||
results.user.isEmailConfirmSent = !!results.isEmailConfirmSent;
|
||||
|
||||
templateValues.bootswatchSkin = parseInt(meta.config.disableCustomUserSkins, 10) !== 1 ? res.locals.config.bootswatchSkin || '' : '';
|
||||
templateValues.bootswatchSkin = (parseInt(meta.config.disableCustomUserSkins, 10) !== 1 ? res.locals.config.bootswatchSkin : '') || meta.config.bootswatchSkin || '';
|
||||
templateValues.config.bootswatchSkin = templateValues.bootswatchSkin || 'noskin'; // TODO remove in v1.12.0+
|
||||
|
||||
const unreadCounts = results.unreadData.counts;
|
||||
var unreadCount = {
|
||||
topic: results.unreadCounts[''] || 0,
|
||||
newTopic: results.unreadCounts.new || 0,
|
||||
watchedTopic: results.unreadCounts.watched || 0,
|
||||
unrepliedTopic: results.unreadCounts.unreplied || 0,
|
||||
topic: unreadCounts[''] || 0,
|
||||
newTopic: unreadCounts.new || 0,
|
||||
watchedTopic: unreadCounts.watched || 0,
|
||||
unrepliedTopic: unreadCounts.unreplied || 0,
|
||||
chat: results.unreadChatCount || 0,
|
||||
notification: results.unreadNotificationCount || 0,
|
||||
};
|
||||
@@ -146,19 +161,21 @@ module.exports = function (middleware) {
|
||||
}
|
||||
});
|
||||
|
||||
const tidsByFilter = results.unreadData.tidsByFilter;
|
||||
results.navigation = results.navigation.map(function (item) {
|
||||
function modifyNavItem(item, route, count, content) {
|
||||
function modifyNavItem(item, route, filter, content) {
|
||||
if (item && item.originalRoute === route) {
|
||||
unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true));
|
||||
item.content = content;
|
||||
if (count > 0) {
|
||||
if (unreadCounts[filter] > 0) {
|
||||
item.iconClass += ' unread-count';
|
||||
}
|
||||
}
|
||||
}
|
||||
modifyNavItem(item, '/unread', results.unreadCounts[''], unreadCount.topic);
|
||||
modifyNavItem(item, '/unread?filter=new', results.unreadCounts.new, unreadCount.newTopic);
|
||||
modifyNavItem(item, '/unread?filter=watched', results.unreadCounts.watched, unreadCount.watchedTopic);
|
||||
modifyNavItem(item, '/unread?filter=unreplied', results.unreadCounts.unreplied, unreadCount.unrepliedTopic);
|
||||
modifyNavItem(item, '/unread', '', unreadCount.topic);
|
||||
modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic);
|
||||
modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic);
|
||||
modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic);
|
||||
return item;
|
||||
});
|
||||
|
||||
@@ -202,7 +219,7 @@ module.exports = function (middleware) {
|
||||
});
|
||||
};
|
||||
|
||||
middleware.renderHeader = function (req, res, data, callback) {
|
||||
middleware.renderHeader = function renderHeader(req, res, data, callback) {
|
||||
async.waterfall([
|
||||
async.apply(middleware.generateHeader, req, res, data),
|
||||
function (templateValues, next) {
|
||||
@@ -211,7 +228,7 @@ module.exports = function (middleware) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
middleware.renderFooter = function (req, res, data, callback) {
|
||||
middleware.renderFooter = function renderFooter(req, res, data, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
plugins.fireHook('filter:middleware.renderFooter', {
|
||||
@@ -224,20 +241,18 @@ module.exports = function (middleware) {
|
||||
async.parallel({
|
||||
scripts: async.apply(plugins.fireHook, 'filter:scripts.get', []),
|
||||
timeagoLocale: (next) => {
|
||||
const userLang = res.locals.config.userLang;
|
||||
const pathToLocaleFile = '/vendor/jquery/timeago/locales/jquery.timeago.' + utils.userLangToTimeagoCode(userLang) + '.js';
|
||||
|
||||
async.waterfall([
|
||||
async.apply(languages.list),
|
||||
(languages, next) => {
|
||||
if (!languages.some(obj => obj.code === userLang)) {
|
||||
return next(null, false);
|
||||
}
|
||||
async.apply(languages.listCodes),
|
||||
(languageCodes, next) => {
|
||||
const userLang = res.locals.config.userLang;
|
||||
const timeagoCode = utils.userLangToTimeagoCode(userLang);
|
||||
|
||||
file.exists(path.join(__dirname, '../../public', pathToLocaleFile), next);
|
||||
},
|
||||
(exists, next) => {
|
||||
next(null, exists ? (nconf.get('relative_path') + '/assets' + pathToLocaleFile) : null);
|
||||
if (languageCodes.includes(userLang) && languages.timeagoCodes.includes(timeagoCode)) {
|
||||
const pathToLocaleFile = '/vendor/jquery/timeago/locales/jquery.timeago.' + timeagoCode + '.js';
|
||||
next(null, (nconf.get('relative_path') + '/assets' + pathToLocaleFile));
|
||||
} else {
|
||||
next(null, false);
|
||||
}
|
||||
},
|
||||
], next);
|
||||
},
|
||||
@@ -255,7 +270,7 @@ module.exports = function (middleware) {
|
||||
|
||||
data.templateValues.useCustomJS = meta.config.useCustomJS;
|
||||
data.templateValues.customJS = data.templateValues.useCustomJS ? meta.config.customJS : '';
|
||||
data.templateValues.isSpider = req.isSpider();
|
||||
data.templateValues.isSpider = req.uid === -1;
|
||||
req.app.render('footer', data.templateValues, next);
|
||||
},
|
||||
], callback);
|
||||
|
||||
@@ -8,7 +8,7 @@ var meta = require('../meta');
|
||||
var languages = require('../languages');
|
||||
|
||||
module.exports = function (middleware) {
|
||||
middleware.addHeaders = function (req, res, next) {
|
||||
middleware.addHeaders = function addHeaders(req, res, next) {
|
||||
var headers = {
|
||||
'X-Powered-By': encodeURI(meta.config['powered-by'] || 'NodeBB'),
|
||||
'X-Frame-Options': meta.config['allow-from-uri'] ? 'ALLOW-FROM ' + encodeURI(meta.config['allow-from-uri']) : 'SAMEORIGIN',
|
||||
@@ -64,7 +64,7 @@ module.exports = function (middleware) {
|
||||
};
|
||||
|
||||
let langs = [];
|
||||
middleware.autoLocale = function (req, res, next) {
|
||||
middleware.autoLocale = function autoLocale(req, res, next) {
|
||||
if (parseInt(req.uid, 10) > 0 || !meta.config.autoDetectLang) {
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ var controllers = {
|
||||
helpers: require('../controllers/helpers'),
|
||||
};
|
||||
|
||||
var delayCache = LRU({
|
||||
var delayCache = new LRU({
|
||||
maxAge: 1000 * 60,
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ require('./maintenance')(middleware);
|
||||
require('./user')(middleware);
|
||||
require('./headers')(middleware);
|
||||
|
||||
middleware.stripLeadingSlashes = function (req, res, next) {
|
||||
middleware.stripLeadingSlashes = function stripLeadingSlashes(req, res, next) {
|
||||
var target = req.originalUrl.replace(nconf.get('relative_path'), '');
|
||||
if (target.startsWith('//')) {
|
||||
res.redirect(nconf.get('relative_path') + target.replace(/^\/+/, '/'));
|
||||
@@ -51,7 +51,7 @@ middleware.stripLeadingSlashes = function (req, res, next) {
|
||||
}
|
||||
};
|
||||
|
||||
middleware.pageView = function (req, res, next) {
|
||||
middleware.pageView = function pageView(req, res, next) {
|
||||
analytics.pageView({
|
||||
ip: req.ip,
|
||||
uid: req.uid,
|
||||
@@ -73,7 +73,7 @@ middleware.pageView = function (req, res, next) {
|
||||
};
|
||||
|
||||
|
||||
middleware.pluginHooks = function (req, res, next) {
|
||||
middleware.pluginHooks = function pluginHooks(req, res, next) {
|
||||
async.each(plugins.loadedHooks['filter:router.page'] || [], function (hookObj, next) {
|
||||
hookObj.method(req, res, next);
|
||||
}, function (err) {
|
||||
@@ -82,7 +82,7 @@ middleware.pluginHooks = function (req, res, next) {
|
||||
});
|
||||
};
|
||||
|
||||
middleware.validateFiles = function (req, res, next) {
|
||||
middleware.validateFiles = function validateFiles(req, res, next) {
|
||||
if (!Array.isArray(req.files.files) || !req.files.files.length) {
|
||||
return next(new Error(['[[error:invalid-files]]']));
|
||||
}
|
||||
@@ -90,12 +90,12 @@ middleware.validateFiles = function (req, res, next) {
|
||||
next();
|
||||
};
|
||||
|
||||
middleware.prepareAPI = function (req, res, next) {
|
||||
middleware.prepareAPI = function prepareAPI(req, res, next) {
|
||||
res.locals.isAPI = true;
|
||||
next();
|
||||
};
|
||||
|
||||
middleware.routeTouchIcon = function (req, res) {
|
||||
middleware.routeTouchIcon = function routeTouchIcon(req, res) {
|
||||
if (meta.config['brand:touchIcon'] && validator.isURL(meta.config['brand:touchIcon'])) {
|
||||
return res.redirect(meta.config['brand:touchIcon']);
|
||||
}
|
||||
@@ -111,7 +111,7 @@ middleware.routeTouchIcon = function (req, res) {
|
||||
});
|
||||
};
|
||||
|
||||
middleware.privateTagListing = function (req, res, next) {
|
||||
middleware.privateTagListing = function privateTagListing(req, res, next) {
|
||||
if (!req.loggedIn && meta.config.privateTagListing) {
|
||||
controllers.helpers.notAllowed(req, res);
|
||||
} else {
|
||||
@@ -119,11 +119,11 @@ middleware.privateTagListing = function (req, res, next) {
|
||||
}
|
||||
};
|
||||
|
||||
middleware.exposeGroupName = function (req, res, next) {
|
||||
middleware.exposeGroupName = function exposeGroupName(req, res, next) {
|
||||
expose('groupName', groups.getGroupNameByGroupSlug, 'slug', req, res, next);
|
||||
};
|
||||
|
||||
middleware.exposeUid = function (req, res, next) {
|
||||
middleware.exposeUid = function exposeUid(req, res, next) {
|
||||
expose('uid', user.getUidByUserslug, 'userslug', req, res, next);
|
||||
};
|
||||
|
||||
@@ -142,7 +142,7 @@ function expose(exposedField, method, field, req, res, next) {
|
||||
], next);
|
||||
}
|
||||
|
||||
middleware.privateUploads = function (req, res, next) {
|
||||
middleware.privateUploads = function privateUploads(req, res, next) {
|
||||
if (req.loggedIn || !meta.config.privateUploads) {
|
||||
return next();
|
||||
}
|
||||
@@ -158,7 +158,7 @@ middleware.privateUploads = function (req, res, next) {
|
||||
next();
|
||||
};
|
||||
|
||||
middleware.busyCheck = function (req, res, next) {
|
||||
middleware.busyCheck = function busyCheck(req, res, next) {
|
||||
if (global.env === 'production' && meta.config.eventLoopCheckEnabled && toobusy()) {
|
||||
analytics.increment('errors:503');
|
||||
res.status(503).type('text/html').sendFile(path.join(__dirname, '../../public/503.html'));
|
||||
@@ -167,13 +167,13 @@ middleware.busyCheck = function (req, res, next) {
|
||||
}
|
||||
};
|
||||
|
||||
middleware.applyBlacklist = function (req, res, next) {
|
||||
middleware.applyBlacklist = function applyBlacklist(req, res, next) {
|
||||
meta.blacklist.test(req.ip, function (err) {
|
||||
next(err);
|
||||
});
|
||||
};
|
||||
|
||||
middleware.delayLoading = function (req, res, next) {
|
||||
middleware.delayLoading = function delayLoading(req, res, next) {
|
||||
// Introduces an artificial delay during load so that brute force attacks are effectively mitigated
|
||||
|
||||
// Add IP to cache so if too many requests are made, subsequent requests are blocked for a minute
|
||||
@@ -186,7 +186,7 @@ middleware.delayLoading = function (req, res, next) {
|
||||
setTimeout(next, 1000);
|
||||
};
|
||||
|
||||
middleware.buildSkinAsset = function (req, res, next) {
|
||||
middleware.buildSkinAsset = function buildSkinAsset(req, res, next) {
|
||||
// If this middleware is reached, a skin was requested, so it is built on-demand
|
||||
var target = path.basename(req.originalUrl).match(/(client-[a-z]+)/);
|
||||
if (target) {
|
||||
@@ -206,7 +206,7 @@ middleware.buildSkinAsset = function (req, res, next) {
|
||||
}
|
||||
};
|
||||
|
||||
middleware.trimUploadTimestamps = (req, res, next) => {
|
||||
middleware.trimUploadTimestamps = function trimUploadTimestamps(req, res, next) {
|
||||
// Check match
|
||||
let basename = path.basename(req.path);
|
||||
if (req.path.startsWith('/uploads/files/') && middleware.regexes.timestampedUpload.test(basename)) {
|
||||
@@ -214,5 +214,5 @@ middleware.trimUploadTimestamps = (req, res, next) => {
|
||||
res.header('Content-Disposition', 'inline; filename="' + basename + '"');
|
||||
}
|
||||
|
||||
return next();
|
||||
next();
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ var meta = require('../meta');
|
||||
var user = require('../user');
|
||||
|
||||
module.exports = function (middleware) {
|
||||
middleware.maintenanceMode = function (req, res, callback) {
|
||||
middleware.maintenanceMode = function maintenanceMode(req, res, callback) {
|
||||
if (!meta.config.maintenanceMode) {
|
||||
return setImmediate(callback);
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ var widgets = require('../widgets');
|
||||
var utils = require('../utils');
|
||||
|
||||
module.exports = function (middleware) {
|
||||
middleware.processRender = function (req, res, next) {
|
||||
middleware.processRender = function processRender(req, res, next) {
|
||||
// res.render post-processing, modified from here: https://gist.github.com/mrlannigan/5051687
|
||||
var render = res.render;
|
||||
res.render = function (template, options, fn) {
|
||||
res.render = function renderOverride(template, options, fn) {
|
||||
var self = this;
|
||||
var req = this.req;
|
||||
var defaultFn = function (err, str) {
|
||||
@@ -37,7 +37,7 @@ module.exports = function (middleware) {
|
||||
var templateToRender;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
options.loggedIn = !!req.uid;
|
||||
options.loggedIn = req.uid > 0;
|
||||
options.relative_path = nconf.get('relative_path');
|
||||
options.template = { name: template };
|
||||
options.template[template] = true;
|
||||
|
||||
@@ -43,21 +43,21 @@ module.exports = function (middleware) {
|
||||
callback();
|
||||
}
|
||||
|
||||
middleware.authenticate = function (req, res, next) {
|
||||
middleware.authenticate = function middlewareAuthenticate(req, res, next) {
|
||||
authenticate(req, res, next, function () {
|
||||
controllers.helpers.notAllowed(req, res, next);
|
||||
});
|
||||
};
|
||||
|
||||
middleware.authenticateOrGuest = function (req, res, next) {
|
||||
middleware.authenticateOrGuest = function authenticateOrGuest(req, res, next) {
|
||||
authenticate(req, res, next, next);
|
||||
};
|
||||
|
||||
middleware.ensureSelfOrGlobalPrivilege = function (req, res, next) {
|
||||
middleware.ensureSelfOrGlobalPrivilege = function ensureSelfOrGlobalPrivilege(req, res, next) {
|
||||
ensureSelfOrMethod(user.isAdminOrGlobalMod, req, res, next);
|
||||
};
|
||||
|
||||
middleware.ensureSelfOrPrivileged = function (req, res, next) {
|
||||
middleware.ensureSelfOrPrivileged = function ensureSelfOrPrivileged(req, res, next) {
|
||||
ensureSelfOrMethod(user.isPrivileged, req, res, next);
|
||||
};
|
||||
|
||||
@@ -87,7 +87,7 @@ module.exports = function (middleware) {
|
||||
], next);
|
||||
}
|
||||
|
||||
middleware.checkGlobalPrivacySettings = function (req, res, next) {
|
||||
middleware.checkGlobalPrivacySettings = function checkGlobalPrivacySettings(req, res, next) {
|
||||
if (!req.loggedIn && meta.config.privateUserInfo) {
|
||||
return middleware.authenticate(req, res, next);
|
||||
}
|
||||
@@ -95,7 +95,7 @@ module.exports = function (middleware) {
|
||||
next();
|
||||
};
|
||||
|
||||
middleware.checkAccountPermissions = function (req, res, next) {
|
||||
middleware.checkAccountPermissions = function checkAccountPermissions(req, res, next) {
|
||||
// This middleware ensures that only the requested user and admins can pass
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
@@ -128,8 +128,8 @@ module.exports = function (middleware) {
|
||||
], next);
|
||||
};
|
||||
|
||||
middleware.redirectToAccountIfLoggedIn = function (req, res, next) {
|
||||
if (req.session.forceLogin || !req.uid) {
|
||||
middleware.redirectToAccountIfLoggedIn = function redirectToAccountIfLoggedIn(req, res, next) {
|
||||
if (req.session.forceLogin || req.uid <= 0) {
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ module.exports = function (middleware) {
|
||||
], next);
|
||||
};
|
||||
|
||||
middleware.redirectUidToUserslug = function (req, res, next) {
|
||||
middleware.redirectUidToUserslug = function redirectUidToUserslug(req, res, next) {
|
||||
var uid = parseInt(req.params.uid, 10);
|
||||
if (uid <= 0) {
|
||||
return next();
|
||||
@@ -164,7 +164,7 @@ module.exports = function (middleware) {
|
||||
], next);
|
||||
};
|
||||
|
||||
middleware.redirectMeToUserslug = function (req, res, next) {
|
||||
middleware.redirectMeToUserslug = function redirectMeToUserslug(req, res, next) {
|
||||
var uid = req.uid;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
@@ -180,7 +180,7 @@ module.exports = function (middleware) {
|
||||
], next);
|
||||
};
|
||||
|
||||
middleware.isAdmin = function (req, res, next) {
|
||||
middleware.isAdmin = function isAdmin(req, res, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.isAdministrator(req.uid, next);
|
||||
@@ -233,7 +233,7 @@ module.exports = function (middleware) {
|
||||
res.status(403).render('403', { title: '[[global:403.title]]' });
|
||||
};
|
||||
|
||||
middleware.registrationComplete = function (req, res, next) {
|
||||
middleware.registrationComplete = function registrationComplete(req, res, next) {
|
||||
// If the user's session contains registration data, redirect the user to complete registration
|
||||
if (!req.session.hasOwnProperty('registration')) {
|
||||
return setImmediate(next);
|
||||
@@ -244,7 +244,7 @@ module.exports = function (middleware) {
|
||||
|
||||
controllers.helpers.redirect(res, '/register/complete');
|
||||
} else {
|
||||
return setImmediate(next);
|
||||
setImmediate(next);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ Notifications.baseTypes = [
|
||||
'notificationType_follow',
|
||||
'notificationType_new-chat',
|
||||
'notificationType_group-invite',
|
||||
'notificationType_group-request-membership',
|
||||
];
|
||||
|
||||
Notifications.privilegedTypes = [
|
||||
@@ -557,11 +558,9 @@ Notifications.merge = function (notifications, callback) {
|
||||
case 'notifications:user_posted_to':
|
||||
case 'notifications:user_flagged_post_in':
|
||||
case 'notifications:user_flagged_user':
|
||||
var usernames = set.map(function (notifObj) {
|
||||
var usernames = _.uniq(set.map(function (notifObj) {
|
||||
return notifObj && notifObj.user && notifObj.user.username;
|
||||
}).filter(function (username, idx, array) {
|
||||
return array.indexOf(username) === idx;
|
||||
});
|
||||
}));
|
||||
var numUsers = usernames.length;
|
||||
|
||||
var title = utils.decodeHTMLEntities(notifications[modifyIndex].topicTitle || '');
|
||||
|
||||
@@ -84,19 +84,31 @@ module.exports = function (Plugins) {
|
||||
|
||||
Plugins.fireHook = function (hook, params, callback) {
|
||||
callback = typeof callback === 'function' ? callback : function () {};
|
||||
|
||||
function done(err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (hook !== 'action:plugins.firehook') {
|
||||
Plugins.fireHook('action:plugins.firehook', { hook: hook, params: params });
|
||||
}
|
||||
if (result !== undefined) {
|
||||
callback(null, result);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
var hookList = Plugins.loadedHooks[hook];
|
||||
var hookType = hook.split(':')[0];
|
||||
winston.verbose('[plugins/fireHook]', hook);
|
||||
switch (hookType) {
|
||||
case 'filter':
|
||||
fireFilterHook(hook, hookList, params, callback);
|
||||
fireFilterHook(hook, hookList, params, done);
|
||||
break;
|
||||
case 'action':
|
||||
fireActionHook(hook, hookList, params, callback);
|
||||
fireActionHook(hook, hookList, params, done);
|
||||
break;
|
||||
case 'static':
|
||||
fireStaticHook(hook, hookList, params, callback);
|
||||
fireStaticHook(hook, hookList, params, done);
|
||||
break;
|
||||
default:
|
||||
winston.warn('[plugins] Unknown hookType: ' + hookType + ', hook : ' + hook);
|
||||
|
||||
@@ -81,15 +81,6 @@ module.exports = function (Plugins) {
|
||||
}
|
||||
|
||||
Plugins.prepareForBuild = function (targets, callback) {
|
||||
Plugins.cssFiles.length = 0;
|
||||
Plugins.lessFiles.length = 0;
|
||||
Plugins.acpLessFiles.length = 0;
|
||||
Plugins.clientScripts.length = 0;
|
||||
Plugins.acpScripts.length = 0;
|
||||
Plugins.soundpacks.length = 0;
|
||||
Plugins.languageData.languages = [];
|
||||
Plugins.languageData.namespaces = [];
|
||||
|
||||
var map = {
|
||||
'plugin static dirs': ['staticDirs'],
|
||||
'requirejs modules': ['modules'],
|
||||
@@ -101,13 +92,27 @@ module.exports = function (Plugins) {
|
||||
languages: ['languageData'],
|
||||
};
|
||||
|
||||
var fields = targets.reduce(function (prev, target) {
|
||||
if (!map[target]) {
|
||||
return prev;
|
||||
var fields = _.uniq(_.flatMap(targets, target => map[target] || []));
|
||||
|
||||
// clear old data before build
|
||||
fields.forEach((field) => {
|
||||
switch (field) {
|
||||
case 'clientScripts':
|
||||
case 'acpScripts':
|
||||
case 'cssFiles':
|
||||
case 'lessFiles':
|
||||
case 'acpLessFiles':
|
||||
Plugins[field].length = 0;
|
||||
break;
|
||||
case 'soundpack':
|
||||
Plugins.soundpacks.length = 0;
|
||||
break;
|
||||
case 'languageData':
|
||||
Plugins.languageData.languages = [];
|
||||
Plugins.languageData.namespaces = [];
|
||||
break;
|
||||
// do nothing for modules and staticDirs
|
||||
}
|
||||
return prev.concat(map[target]);
|
||||
}, []).filter(function (field, i, arr) {
|
||||
return arr.indexOf(field) === i;
|
||||
});
|
||||
|
||||
winston.verbose('[plugins] loading the following fields from plugin data: ' + fields.join(', '));
|
||||
|
||||
@@ -86,12 +86,7 @@ module.exports = function (Posts) {
|
||||
|
||||
Posts.hasBookmarked = function (pid, uid, callback) {
|
||||
if (parseInt(uid, 10) <= 0) {
|
||||
if (Array.isArray(pid)) {
|
||||
callback(null, pid.map(() => false));
|
||||
} else {
|
||||
callback(null, false);
|
||||
}
|
||||
return;
|
||||
return callback(null, Array.isArray(pid) ? pid.map(() => false) : false);
|
||||
}
|
||||
|
||||
if (Array.isArray(pid)) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
var LRU = require('lru-cache');
|
||||
var meta = require('../meta');
|
||||
|
||||
var cache = LRU({
|
||||
var cache = new LRU({
|
||||
max: meta.config.postCacheSize,
|
||||
length: function (n) { return n.length; },
|
||||
maxAge: 0,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
const _ = require('lodash');
|
||||
|
||||
var db = require('../database');
|
||||
var user = require('../user');
|
||||
var meta = require('../meta');
|
||||
var groups = require('../groups');
|
||||
var topics = require('../topics');
|
||||
var categories = require('../categories');
|
||||
var notifications = require('../notifications');
|
||||
var privileges = require('../privileges');
|
||||
var plugins = require('../plugins');
|
||||
@@ -18,7 +21,7 @@ module.exports = function (Posts) {
|
||||
user.getUserFields(uid, ['uid', 'reputation', 'postcount'], next);
|
||||
},
|
||||
function (userData, next) {
|
||||
var shouldQueue = meta.config.postQueue && (!userData.uid || userData.reputation < 0 || userData.postcount <= 0);
|
||||
const shouldQueue = meta.config.postQueue && (!userData.uid || userData.reputation < 0 || userData.postcount <= 0);
|
||||
plugins.fireHook('filter:post.shouldQueue', {
|
||||
shouldQueue: shouldQueue,
|
||||
uid: uid,
|
||||
@@ -31,6 +34,44 @@ module.exports = function (Posts) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
function removeQueueNotification(id, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
notifications.rescind('post-queue-' + id, next);
|
||||
},
|
||||
function (next) {
|
||||
getParsedObject(id, next);
|
||||
},
|
||||
function (data, next) {
|
||||
if (!data) {
|
||||
return callback();
|
||||
}
|
||||
getCid(data.type, data, next);
|
||||
},
|
||||
function (cid, next) {
|
||||
getNotificationUids(cid, next);
|
||||
},
|
||||
function (uids, next) {
|
||||
uids.forEach(uid => user.notifications.pushCount(uid));
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function getNotificationUids(cid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel([
|
||||
async.apply(groups.getMembersOfGroups, ['administrators', 'Global Moderators']),
|
||||
async.apply(categories.getModeratorUids, [cid]),
|
||||
], next);
|
||||
},
|
||||
function (results, next) {
|
||||
next(null, _.uniq(_.flattenDeep(results)));
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
Posts.addToQueue = function (data, callback) {
|
||||
var type = data.title ? 'topic' : 'reply';
|
||||
var id = type + '-' + Date.now();
|
||||
@@ -64,14 +105,21 @@ module.exports = function (Posts) {
|
||||
path: '/post-queue',
|
||||
}, next);
|
||||
},
|
||||
cid: function (next) {
|
||||
getCid(type, data, next);
|
||||
uids: function (next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
getCid(type, data, next);
|
||||
},
|
||||
function (cid, next) {
|
||||
getNotificationUids(cid, next);
|
||||
},
|
||||
], next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
if (results.notification) {
|
||||
notifications.pushGroups(results.notification, ['administrators', 'Global Moderators', 'cid:' + results.cid + ':privileges:moderate'], next);
|
||||
notifications.push(results.notification, results.uids, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
@@ -127,15 +175,15 @@ module.exports = function (Posts) {
|
||||
|
||||
Posts.removeFromQueue = function (id, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
removeQueueNotification(id, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetRemove('post:queue', id, next);
|
||||
},
|
||||
function (next) {
|
||||
db.delete('post:queue:' + id, next);
|
||||
},
|
||||
function (next) {
|
||||
notifications.rescind('post-queue-' + id, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,13 @@ module.exports = function (app, middleware, controllers) {
|
||||
var router = express.Router();
|
||||
app.use('/api', router);
|
||||
|
||||
router.get('/config', middleware.applyCSRF, controllers.api.getConfig);
|
||||
router.get('/config', function (req, res, next) {
|
||||
if (req.uid >= 0) {
|
||||
middleware.applyCSRF(req, res, next);
|
||||
} else {
|
||||
setImmediate(next);
|
||||
}
|
||||
}, controllers.api.getConfig);
|
||||
|
||||
router.get('/me', middleware.checkGlobalPrivacySettings, controllers.user.getCurrentUser);
|
||||
router.get('/user/uid/:uid', middleware.checkGlobalPrivacySettings, controllers.user.getUserByUID);
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
var async = require('async');
|
||||
var passport = require('passport');
|
||||
var passportLocal = require('passport-local').Strategy;
|
||||
var nconf = require('nconf');
|
||||
var winston = require('winston');
|
||||
|
||||
var controllers = require('../controllers');
|
||||
var helpers = require('../controllers/helpers');
|
||||
var plugins = require('../plugins');
|
||||
|
||||
var loginStrategies = [];
|
||||
@@ -14,8 +14,14 @@ var loginStrategies = [];
|
||||
var Auth = module.exports;
|
||||
|
||||
Auth.initialize = function (app, middleware) {
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
const passwortInitMiddleware = passport.initialize();
|
||||
app.use(function passportInitialize(req, res, next) {
|
||||
passwortInitMiddleware(req, res, next);
|
||||
});
|
||||
const passportSessionMiddleware = passport.session();
|
||||
app.use(function passportSession(req, res, next) {
|
||||
passportSessionMiddleware(req, res, next);
|
||||
});
|
||||
|
||||
app.use(Auth.setAuthVars);
|
||||
|
||||
@@ -23,7 +29,7 @@ Auth.initialize = function (app, middleware) {
|
||||
Auth.middleware = middleware;
|
||||
};
|
||||
|
||||
Auth.setAuthVars = function (req, res, next) {
|
||||
Auth.setAuthVars = function setAuthVars(req, res, next) {
|
||||
var isSpider = req.isSpider();
|
||||
req.loggedIn = !isSpider && !!req.user;
|
||||
if (req.user) {
|
||||
@@ -68,8 +74,12 @@ Auth.reloadRoutes = function (router, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
router.get(strategy.callbackURL, function (req, res, next) {
|
||||
// Ensure the passed-back state value is identical to the saved ssoState
|
||||
router[strategy.callbackMethod || 'get'](strategy.callbackURL, function (req, res, next) {
|
||||
// Ensure the passed-back state value is identical to the saved ssoState (unless explicitly skipped)
|
||||
if (strategy.checkState === false) {
|
||||
return next();
|
||||
}
|
||||
|
||||
next(req.query.state !== req.session.ssoState ? new Error('[[error:csrf-invalid]]') : null);
|
||||
}, function (req, res, next) {
|
||||
// Trigger registration interstitial checks
|
||||
@@ -78,10 +88,27 @@ Auth.reloadRoutes = function (router, callback) {
|
||||
// passport seems to remove `req.session.returnTo` after it redirects
|
||||
req.session.registration.returnTo = req.session.returnTo;
|
||||
next();
|
||||
}, passport.authenticate(strategy.name, {
|
||||
successReturnToOrRedirect: nconf.get('relative_path') + (strategy.successUrl !== undefined ? strategy.successUrl : '/'),
|
||||
failureRedirect: nconf.get('relative_path') + (strategy.failureUrl !== undefined ? strategy.failureUrl : '/login'),
|
||||
}));
|
||||
}, function (req, res, next) {
|
||||
passport.authenticate(strategy.name, function (err, user) {
|
||||
if (err) {
|
||||
delete req.session.registration;
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
delete req.session.registration;
|
||||
return helpers.redirect(res, strategy.failureUrl !== undefined ? strategy.failureUrl : '/login');
|
||||
}
|
||||
|
||||
req.login(user, function (err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
helpers.redirect(res, strategy.successUrl !== undefined ? strategy.successUrl : '/');
|
||||
});
|
||||
})(req, res, next);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/register', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.register);
|
||||
|
||||
@@ -35,6 +35,7 @@ function mainRoutes(app, middleware, controllers) {
|
||||
setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse);
|
||||
|
||||
app.post('/compose', middleware.applyCSRF, controllers.composer.post);
|
||||
app.post('/email/unsubscribe/:token', controllers.accounts.settings.unsubscribe);
|
||||
}
|
||||
|
||||
function modRoutes(app, middleware, controllers) {
|
||||
@@ -53,7 +54,9 @@ function topicRoutes(app, middleware, controllers) {
|
||||
}
|
||||
|
||||
function postRoutes(app, middleware, controllers) {
|
||||
setupPageRoute(app, '/post/:pid', middleware, [], controllers.posts.redirectToPost);
|
||||
const middlewares = [middleware.maintenanceMode, middleware.registrationComplete, middleware.pluginHooks];
|
||||
app.get('/post/:pid', middleware.busyCheck, middleware.buildHeader, middlewares, controllers.posts.redirectToPost);
|
||||
app.get('/api/post/:pid', middlewares, controllers.posts.redirectToPost);
|
||||
}
|
||||
|
||||
function tagRoutes(app, middleware, controllers) {
|
||||
|
||||
@@ -330,7 +330,7 @@ function getSearchCids(data, callback) {
|
||||
async.parallel({
|
||||
watchedCids: function (next) {
|
||||
if (data.categories.includes('watched')) {
|
||||
user.getWatchedCategories(data.uid, next);
|
||||
user.getCategoriesByStates(data.uid, [categories.watchStates.watching], next);
|
||||
} else {
|
||||
setImmediate(next, null, []);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ var userDigest = require('../user/digest');
|
||||
var userEmail = require('../user/email');
|
||||
var logger = require('../logger');
|
||||
var events = require('../events');
|
||||
var notifications = require('../notifications');
|
||||
var emailer = require('../emailer');
|
||||
var db = require('../database');
|
||||
var analytics = require('../analytics');
|
||||
@@ -273,6 +274,30 @@ SocketAdmin.email.test = function (socket, data, callback) {
|
||||
}, callback);
|
||||
break;
|
||||
|
||||
case 'notification':
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
notifications.create({
|
||||
type: 'test',
|
||||
bodyShort: '[[admin-settings-email:testing]]',
|
||||
bodyLong: '[[admin-settings-email:testing.send-help]]',
|
||||
nid: 'uid:' + socket.uid + ':test',
|
||||
path: '/',
|
||||
from: socket.uid,
|
||||
}, next);
|
||||
},
|
||||
function (notifObj, next) {
|
||||
emailer.send('notification', socket.uid, {
|
||||
path: notifObj.path,
|
||||
subject: utils.stripHTMLTags(notifObj.subject || '[[notifications:new_notification]]'),
|
||||
intro: utils.stripHTMLTags(notifObj.bodyShort),
|
||||
body: notifObj.bodyLong || '',
|
||||
notification: notifObj,
|
||||
showUnsubscribe: true,
|
||||
}, next);
|
||||
},
|
||||
]);
|
||||
break;
|
||||
default:
|
||||
emailer.send(data.template, socket.uid, payload, callback);
|
||||
break;
|
||||
@@ -333,6 +358,10 @@ SocketAdmin.errors.clear = function (socket, data, callback) {
|
||||
meta.errors.clear(callback);
|
||||
};
|
||||
|
||||
SocketAdmin.deleteEvents = function (socket, eids, callback) {
|
||||
events.deleteEvents(eids, callback);
|
||||
};
|
||||
|
||||
SocketAdmin.deleteAllEvents = function (socket, data, callback) {
|
||||
events.deleteAll(callback);
|
||||
};
|
||||
|
||||
@@ -155,20 +155,28 @@ SocketCategories.getSelectCategories = function (socket, data, callback) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
SocketCategories.watch = function (socket, cid, callback) {
|
||||
ignoreOrWatch(user.watchCategory, socket, cid, callback);
|
||||
SocketCategories.setWatchState = function (socket, data, callback) {
|
||||
if (!data || !data.cid || !data.state) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
ignoreOrWatch(function (uid, cid, next) {
|
||||
user.setCategoryWatchState(uid, cid, categories.watchStates[data.state], next);
|
||||
}, socket, data, callback);
|
||||
};
|
||||
|
||||
SocketCategories.ignore = function (socket, cid, callback) {
|
||||
ignoreOrWatch(user.ignoreCategory, socket, cid, callback);
|
||||
SocketCategories.watch = function (socket, data, callback) {
|
||||
ignoreOrWatch(user.watchCategory, socket, data, callback);
|
||||
};
|
||||
|
||||
function ignoreOrWatch(fn, socket, cid, callback) {
|
||||
SocketCategories.ignore = function (socket, data, callback) {
|
||||
ignoreOrWatch(user.ignoreCategory, socket, data, callback);
|
||||
};
|
||||
|
||||
function ignoreOrWatch(fn, socket, data, callback) {
|
||||
var targetUid = socket.uid;
|
||||
var cids = [parseInt(cid, 10)];
|
||||
if (typeof cid === 'object') {
|
||||
targetUid = cid.uid;
|
||||
cids = [parseInt(cid.cid, 10)];
|
||||
var cids = [parseInt(data.cid, 10)];
|
||||
if (data.hasOwnProperty('uid')) {
|
||||
targetUid = data.uid;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
var _ = require('lodash');
|
||||
|
||||
var db = require('../database');
|
||||
var websockets = require('./index');
|
||||
var user = require('../user');
|
||||
var posts = require('../posts');
|
||||
var topics = require('../topics');
|
||||
var categories = require('../categories');
|
||||
var privileges = require('../privileges');
|
||||
var notifications = require('../notifications');
|
||||
var plugins = require('../plugins');
|
||||
@@ -16,21 +18,32 @@ var utils = require('../utils');
|
||||
var SocketHelpers = module.exports;
|
||||
|
||||
SocketHelpers.notifyNew = function (uid, type, result) {
|
||||
let watchStateUids;
|
||||
let categoryWatchStates;
|
||||
let topicFollowState;
|
||||
const post = result.posts[0];
|
||||
const tid = post.topic.tid;
|
||||
const cid = post.topic.cid;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.getUidsFromSet('users:online', 0, -1, next);
|
||||
},
|
||||
function (uids, next) {
|
||||
privileges.topics.filterUids('read', result.posts[0].topic.tid, uids, next);
|
||||
uids = uids.filter(toUid => parseInt(toUid, 10) !== uid);
|
||||
privileges.topics.filterUids('read', tid, uids, next);
|
||||
},
|
||||
function (uids, next) {
|
||||
filterTidCidIgnorers(uids, result.posts[0].topic.tid, result.posts[0].topic.cid, next);
|
||||
watchStateUids = uids;
|
||||
getWatchStates(watchStateUids, tid, cid, next);
|
||||
},
|
||||
function (uids, next) {
|
||||
function (watchStates, next) {
|
||||
categoryWatchStates = _.zipObject(watchStateUids, watchStates.categoryWatchStates);
|
||||
topicFollowState = _.zipObject(watchStateUids, watchStates.topicFollowed);
|
||||
const uids = filterTidCidIgnorers(watchStateUids, watchStates);
|
||||
user.blocks.filterUids(uid, uids, next);
|
||||
},
|
||||
function (uids, next) {
|
||||
user.blocks.filterUids(result.posts[0].topic.uid, uids, next);
|
||||
user.blocks.filterUids(post.topic.uid, uids, next);
|
||||
},
|
||||
function (uids, next) {
|
||||
plugins.fireHook('filter:sockets.sendNewPostToUids', { uidsTo: uids, uidFrom: uid, type: type }, next);
|
||||
@@ -40,42 +53,38 @@ SocketHelpers.notifyNew = function (uid, type, result) {
|
||||
return winston.error(err.stack);
|
||||
}
|
||||
|
||||
result.posts[0].ip = undefined;
|
||||
post.ip = undefined;
|
||||
|
||||
data.uidsTo.forEach(function (toUid) {
|
||||
if (parseInt(toUid, 10) !== uid) {
|
||||
websockets.in('uid_' + toUid).emit('event:new_post', result);
|
||||
if (result.topic && type === 'newTopic') {
|
||||
websockets.in('uid_' + toUid).emit('event:new_topic', result.topic);
|
||||
}
|
||||
post.categoryWatchState = categoryWatchStates[toUid];
|
||||
post.topic.isFollowing = topicFollowState[toUid];
|
||||
websockets.in('uid_' + toUid).emit('event:new_post', result);
|
||||
if (result.topic && type === 'newTopic') {
|
||||
websockets.in('uid_' + toUid).emit('event:new_topic', result.topic);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
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 getWatchStates(uids, tid, cid, callback) {
|
||||
async.parallel({
|
||||
topicFollowed: function (next) {
|
||||
db.isSetMembers('tid:' + tid + ':followers', uids, 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);
|
||||
topicIgnored: function (next) {
|
||||
db.isSetMembers('tid:' + tid + ':ignorers', uids, next);
|
||||
},
|
||||
], callback);
|
||||
categoryWatchStates: function (next) {
|
||||
categories.getUidsWatchStates(cid, uids, next);
|
||||
},
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function filterTidCidIgnorers(uids, watchStates) {
|
||||
return uids.filter(function (uid, index) {
|
||||
return watchStates.topicFollowed[index] ||
|
||||
(!watchStates.topicIgnored[index] && watchStates.categoryWatchStates[index] !== categories.watchStates.ignoring);
|
||||
});
|
||||
}
|
||||
|
||||
SocketHelpers.sendNotificationToPostOwner = function (pid, fromuid, command, notification) {
|
||||
|
||||
@@ -10,6 +10,7 @@ var cookieParser = require('cookie-parser')(nconf.get('secret'));
|
||||
var db = require('../database');
|
||||
var user = require('../user');
|
||||
var logger = require('../logger');
|
||||
var plugins = require('../plugins');
|
||||
var ratelimit = require('../middleware/ratelimit');
|
||||
|
||||
|
||||
@@ -179,12 +180,17 @@ function validateSession(socket, callback) {
|
||||
if (!req.signedCookies || !req.signedCookies[nconf.get('sessionKey')]) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
db.sessionStore.get(req.signedCookies[nconf.get('sessionKey')], function (err, sessionData) {
|
||||
if (err || !sessionData) {
|
||||
return callback(err || new Error('[[error:invalid-session]]'));
|
||||
}
|
||||
|
||||
callback();
|
||||
plugins.fireHook('static:sockets.validateSession', {
|
||||
req: req,
|
||||
socket: socket,
|
||||
session: sessionData,
|
||||
}, callback);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -188,13 +188,15 @@ Topics.getTopicWithPosts = function (topicData, set, uid, start, stop, reverse,
|
||||
topicData.bookmark = results.bookmark;
|
||||
topicData.postSharing = results.postSharing;
|
||||
topicData.deleter = results.deleter;
|
||||
topicData.deletedTimestampISO = utils.toISOString(topicData.deletedTimestamp);
|
||||
if (results.deleter) {
|
||||
topicData.deletedTimestampISO = utils.toISOString(topicData.deletedTimestamp);
|
||||
}
|
||||
topicData.merger = results.merger;
|
||||
topicData.mergedTimestampISO = utils.toISOString(topicData.mergedTimestamp);
|
||||
if (results.merger) {
|
||||
topicData.mergedTimestampISO = utils.toISOString(topicData.mergedTimestamp);
|
||||
}
|
||||
topicData.related = results.related || [];
|
||||
|
||||
topicData.unreplied = topicData.postcount === 1;
|
||||
|
||||
topicData.icons = [];
|
||||
|
||||
plugins.fireHook('filter:topic.get', { topic: topicData, uid: uid }, next);
|
||||
@@ -245,14 +247,14 @@ function getMainPostAndReplies(topic, set, uid, start, stop, reverse, callback)
|
||||
}
|
||||
|
||||
function getDeleter(topicData, callback) {
|
||||
if (!topicData.deleterUid) {
|
||||
if (!parseInt(topicData.deleterUid, 10)) {
|
||||
return setImmediate(callback, null, null);
|
||||
}
|
||||
user.getUserFields(topicData.deleterUid, ['username', 'userslug', 'picture'], callback);
|
||||
}
|
||||
|
||||
function getMerger(topicData, callback) {
|
||||
if (!topicData.mergerUid) {
|
||||
if (!parseInt(topicData.mergerUid, 10)) {
|
||||
return setImmediate(callback, null, null);
|
||||
}
|
||||
async.waterfall([
|
||||
|
||||
@@ -154,7 +154,7 @@ module.exports = function (Topics) {
|
||||
if (!parentPids.length) {
|
||||
return setImmediate(callback);
|
||||
}
|
||||
|
||||
parentPids = _.uniq(parentPids);
|
||||
var parentPosts;
|
||||
async.waterfall([
|
||||
async.apply(posts.getPostsFields, parentPids, ['uid']),
|
||||
|
||||
@@ -232,13 +232,13 @@ module.exports = function (Topics) {
|
||||
};
|
||||
|
||||
Topics.getTagData = function (tags, callback) {
|
||||
var keys = tags.map(function (tag) {
|
||||
return 'tag:' + tag.value;
|
||||
});
|
||||
if (!tags.length) {
|
||||
return setImmediate(callback, null, []);
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getObjects(keys, next);
|
||||
db.getObjects(tags.map(tag => 'tag:' + tag.value), next);
|
||||
},
|
||||
function (tagData, next) {
|
||||
tags.forEach(function (tag, index) {
|
||||
|
||||
@@ -64,15 +64,37 @@ module.exports = function (Topics) {
|
||||
};
|
||||
|
||||
Topics.getUnreadTids = function (params, callback) {
|
||||
var uid = parseInt(params.uid, 10);
|
||||
var counts = {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Topics.getUnreadData(params, next);
|
||||
},
|
||||
function (results, next) {
|
||||
next(null, params.count ? results.counts : results.tids);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Topics.getUnreadData = function (params, callback) {
|
||||
const uid = parseInt(params.uid, 10);
|
||||
const counts = {
|
||||
'': 0,
|
||||
new: 0,
|
||||
watched: 0,
|
||||
unreplied: 0,
|
||||
};
|
||||
const noUnreadData = {
|
||||
tids: [],
|
||||
counts: counts,
|
||||
tidsByFilter: {
|
||||
'': [],
|
||||
new: [],
|
||||
watched: [],
|
||||
unreplied: [],
|
||||
},
|
||||
};
|
||||
|
||||
if (uid <= 0) {
|
||||
return callback(null, params.count ? counts : []);
|
||||
return setImmediate(callback, null, noUnreadData);
|
||||
}
|
||||
|
||||
params.filter = params.filter || '';
|
||||
@@ -102,7 +124,7 @@ module.exports = function (Topics) {
|
||||
},
|
||||
function (results, next) {
|
||||
if (results.recentTids && !results.recentTids.length && !results.tids_unread.length) {
|
||||
return callback(null, params.count ? counts : []);
|
||||
return callback(null, noUnreadData);
|
||||
}
|
||||
|
||||
filterTopics(params, results, next);
|
||||
@@ -117,9 +139,6 @@ module.exports = function (Topics) {
|
||||
filter: params.filter,
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
next(null, params.count ? results.counts : results.tids);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -166,7 +185,7 @@ module.exports = function (Topics) {
|
||||
tids = tids.slice(0, 200);
|
||||
|
||||
if (!tids.length) {
|
||||
return callback(null, { counts: counts, tids: tids });
|
||||
return callback(null, { counts: counts, tids: tids, tidsByFilter: tidsByFilter });
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
@@ -194,8 +213,8 @@ module.exports = function (Topics) {
|
||||
isTopicsFollowed: function (next) {
|
||||
db.sortedSetScores('uid:' + uid + ':followed_tids', tids, next);
|
||||
},
|
||||
ignoredCids: function (next) {
|
||||
categories.isIgnored(cids, uid, next);
|
||||
categoryWatchState: function (next) {
|
||||
categories.getWatchState(cids, uid, next);
|
||||
},
|
||||
readableCids: function (next) {
|
||||
privileges.categories.filterCids('read', cids, uid, next);
|
||||
@@ -205,7 +224,7 @@ module.exports = function (Topics) {
|
||||
function (results, next) {
|
||||
cid = cid && cid.map(String);
|
||||
results.readableCids = results.readableCids.map(String);
|
||||
const isCidIgnored = _.zipObject(cids, results.ignoredCids);
|
||||
const userCidState = _.zipObject(cids, results.categoryWatchState);
|
||||
|
||||
topicData.forEach(function (topic, index) {
|
||||
function cidMatch(topicCid) {
|
||||
@@ -214,7 +233,7 @@ module.exports = function (Topics) {
|
||||
|
||||
if (topic && topic.cid && cidMatch(topic.cid) && !blockedUids.includes(parseInt(topic.uid, 10))) {
|
||||
topic.tid = parseInt(topic.tid, 10);
|
||||
if ((results.isTopicsFollowed[index] || !isCidIgnored[topic.cid])) {
|
||||
if ((results.isTopicsFollowed[index] || userCidState[topic.cid] === categories.watchStates.watching)) {
|
||||
tidsByFilter[''].push(topic.tid);
|
||||
}
|
||||
|
||||
|
||||
46
src/upgrades/1.12.0/category_watch_state.js
Normal file
46
src/upgrades/1.12.0/category_watch_state.js
Normal file
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
|
||||
var db = require('../../database');
|
||||
var batch = require('../../batch');
|
||||
var categories = require('../../categories');
|
||||
|
||||
module.exports = {
|
||||
name: 'Update category watch data',
|
||||
timestamp: Date.UTC(2018, 11, 13),
|
||||
method: function (callback) {
|
||||
const progress = this.progress;
|
||||
let keys;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getSortedSetRange('categories:cid', 0, -1, next);
|
||||
},
|
||||
function (cids, next) {
|
||||
keys = cids.map(cid => 'cid:' + cid + ':ignorers');
|
||||
batch.processSortedSet('users:joindate', function (uids, next) {
|
||||
progress.incr(uids.length);
|
||||
|
||||
async.eachSeries(cids, function (cid, next) {
|
||||
db.isSortedSetMembers('cid:' + cid + ':ignorers', uids, function (err, isMembers) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
uids = uids.filter((uid, index) => isMembers[index]);
|
||||
if (!uids.length) {
|
||||
return setImmediate(next);
|
||||
}
|
||||
const states = uids.map(() => categories.watchStates.ignoring);
|
||||
db.sortedSetAdd('cid:' + cid + ':uid:watch:state', states, uids, next);
|
||||
});
|
||||
}, next);
|
||||
}, {
|
||||
progress: progress,
|
||||
}, next);
|
||||
},
|
||||
function (next) {
|
||||
db.deleteAll(keys, next);
|
||||
},
|
||||
], callback);
|
||||
},
|
||||
};
|
||||
@@ -9,7 +9,7 @@ var pubsub = require('../pubsub');
|
||||
|
||||
module.exports = function (User) {
|
||||
User.blocks = {
|
||||
_cache: LRU({
|
||||
_cache: new LRU({
|
||||
max: 100,
|
||||
length: function () { return 1; },
|
||||
maxAge: 0,
|
||||
|
||||
@@ -1,15 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
const async = require('async');
|
||||
const _ = require('lodash');
|
||||
|
||||
var db = require('../database');
|
||||
var categories = require('../categories');
|
||||
const db = require('../database');
|
||||
const categories = require('../categories');
|
||||
|
||||
module.exports = function (User) {
|
||||
User.getIgnoredCategories = function (uid, callback) {
|
||||
if (parseInt(uid, 10) <= 0) {
|
||||
return setImmediate(callback, null, []);
|
||||
User.setCategoryWatchState = function (uid, cid, state, callback) {
|
||||
if (!(parseInt(uid, 10) > 0)) {
|
||||
return setImmediate(callback);
|
||||
}
|
||||
const isStateValid = Object.keys(categories.watchStates).some(key => categories.watchStates[key] === parseInt(state, 10));
|
||||
if (!isStateValid) {
|
||||
return setImmediate(callback, new Error('[[error:invalid-watch-state]]'));
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
categories.exists(cid, next);
|
||||
},
|
||||
function (exists, next) {
|
||||
if (!exists) {
|
||||
return next(new Error('[[error:no-category]]'));
|
||||
}
|
||||
|
||||
db.sortedSetAdd('cid:' + cid + ':uid:watch:state', state, uid, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
User.getCategoryWatchState = function (uid, callback) {
|
||||
if (parseInt(uid, 10) <= 0) {
|
||||
return setImmediate(callback, null, {});
|
||||
}
|
||||
|
||||
let cids;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
@@ -17,69 +41,49 @@ module.exports = function (User) {
|
||||
},
|
||||
function (_cids, next) {
|
||||
cids = _cids;
|
||||
db.isMemberOfSortedSets(cids.map(cid => 'cid:' + cid + ':ignorers'), uid, next);
|
||||
categories.getWatchState(cids, uid, next);
|
||||
},
|
||||
function (isMembers, next) {
|
||||
next(null, cids.filter((cid, index) => isMembers[index]));
|
||||
function (states, next) {
|
||||
next(null, _.zipObject(cids, states));
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
User.getIgnoredCategories = function (uid, callback) {
|
||||
if (parseInt(uid, 10) <= 0) {
|
||||
return setImmediate(callback, null, []);
|
||||
}
|
||||
User.getCategoriesByStates(uid, [categories.watchStates.ignoring], callback);
|
||||
};
|
||||
|
||||
User.getWatchedCategories = function (uid, callback) {
|
||||
if (parseInt(uid, 10) <= 0) {
|
||||
return setImmediate(callback, null, []);
|
||||
}
|
||||
User.getCategoriesByStates(uid, [categories.watchStates.watching], callback);
|
||||
};
|
||||
|
||||
User.getCategoriesByStates = function (uid, states, callback) {
|
||||
if (!(parseInt(uid, 10) > 0)) {
|
||||
return categories.getAllCidsFromSet('categories:cid', callback);
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
ignored: function (next) {
|
||||
User.getIgnoredCategories(uid, next);
|
||||
},
|
||||
all: function (next) {
|
||||
categories.getAllCidsFromSet('categories:cid', next);
|
||||
},
|
||||
}, next);
|
||||
User.getCategoryWatchState(uid, next);
|
||||
},
|
||||
function (results, next) {
|
||||
const ignored = new Set(results.ignored);
|
||||
const watched = results.all.filter(cid => cid && !ignored.has(String(cid)));
|
||||
next(null, watched);
|
||||
function (userState, next) {
|
||||
const cids = Object.keys(userState);
|
||||
next(null, cids.filter(cid => states.includes(userState[cid])));
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
User.ignoreCategory = function (uid, cid, callback) {
|
||||
if (uid <= 0) {
|
||||
return setImmediate(callback);
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
categories.exists(cid, next);
|
||||
},
|
||||
function (exists, next) {
|
||||
if (!exists) {
|
||||
return next(new Error('[[error:no-category]]'));
|
||||
}
|
||||
|
||||
db.sortedSetAdd('cid:' + cid + ':ignorers', Date.now(), uid, next);
|
||||
},
|
||||
], callback);
|
||||
User.setCategoryWatchState(uid, cid, categories.watchStates.ignoring, callback);
|
||||
};
|
||||
|
||||
User.watchCategory = function (uid, cid, callback) {
|
||||
if (uid <= 0) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
categories.exists(cid, next);
|
||||
},
|
||||
function (exists, next) {
|
||||
if (!exists) {
|
||||
return next(new Error('[[error:no-category]]'));
|
||||
}
|
||||
|
||||
db.sortedSetRemove('cid:' + cid + ':ignorers', uid, next);
|
||||
},
|
||||
], callback);
|
||||
User.setCategoryWatchState(uid, cid, categories.watchStates.watching, callback);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -291,32 +291,10 @@ User.getModeratorUids = function (callback) {
|
||||
async.waterfall([
|
||||
async.apply(categories.getAllCidsFromSet, 'categories:cid'),
|
||||
function (cids, next) {
|
||||
var groupNames = cids.reduce(function (memo, cid) {
|
||||
memo.push('cid:' + cid + ':privileges:moderate');
|
||||
memo.push('cid:' + cid + ':privileges:groups:moderate');
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
groups.getMembersOfGroups(groupNames, next);
|
||||
categories.getModeratorUids(cids, next);
|
||||
},
|
||||
function (memberSets, next) {
|
||||
// Every other set is actually a list of user groups, not uids, so convert those to members
|
||||
var sets = memberSets.reduce(function (memo, set, idx) {
|
||||
if (idx % 2) {
|
||||
memo.working.push(set);
|
||||
} else {
|
||||
memo.regular.push(set);
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, { working: [], regular: [] });
|
||||
|
||||
groups.getMembersOfGroups(sets.working, function (err, memberSets) {
|
||||
next(err, sets.regular.concat(memberSets || []));
|
||||
});
|
||||
},
|
||||
function (memberSets, next) {
|
||||
next(null, _.union.apply(_, memberSets));
|
||||
function (uids, next) {
|
||||
next(null, _.union(uids));
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ var async = require('async');
|
||||
var db = require('../database');
|
||||
var topics = require('../topics');
|
||||
var plugins = require('../plugins');
|
||||
var meta = require('../meta');
|
||||
|
||||
module.exports = function (User) {
|
||||
User.updateLastOnlineTime = function (uid, callback) {
|
||||
@@ -52,7 +53,7 @@ module.exports = function (User) {
|
||||
},
|
||||
function (lastonline, next) {
|
||||
function checkOnline(lastonline) {
|
||||
return now - lastonline < 300000;
|
||||
return (now - lastonline) < (meta.config.onlineCutoff * 60000);
|
||||
}
|
||||
|
||||
var isOnline;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
|
||||
var plugins = require('../plugins');
|
||||
var file = require('../file');
|
||||
var image = require('../image');
|
||||
var meta = require('../meta');
|
||||
@@ -58,7 +57,7 @@ module.exports = function (User) {
|
||||
|
||||
var extension = file.typeToExtension(type);
|
||||
var filename = generateProfileImageFilename(data.uid, 'profilecover', extension);
|
||||
uploadProfileOrCover(filename, picture, next);
|
||||
image.uploadImage(filename, 'profile', picture, next);
|
||||
},
|
||||
function (uploadData, next) {
|
||||
url = uploadData.url;
|
||||
@@ -130,7 +129,7 @@ module.exports = function (User) {
|
||||
},
|
||||
function (next) {
|
||||
var filename = generateProfileImageFilename(data.uid, 'profileavatar', extension);
|
||||
uploadProfileOrCover(filename, picture, next);
|
||||
image.uploadImage(filename, 'profile', picture, next);
|
||||
},
|
||||
function (_uploadedImage, next) {
|
||||
uploadedImage = _uploadedImage;
|
||||
@@ -162,41 +161,12 @@ module.exports = function (User) {
|
||||
], callback);
|
||||
}
|
||||
|
||||
function uploadProfileOrCover(filename, image, callback) {
|
||||
if (plugins.hasListeners('filter:uploadImage')) {
|
||||
return plugins.fireHook('filter:uploadImage', {
|
||||
image: image,
|
||||
uid: image.uid,
|
||||
}, callback);
|
||||
}
|
||||
|
||||
saveFileToLocal(filename, image, callback);
|
||||
}
|
||||
|
||||
function generateProfileImageFilename(uid, type, extension) {
|
||||
var keepAllVersions = meta.config['profile:keepAllUserImages'] === 1;
|
||||
var convertToPNG = meta.config['profile:convertProfileImageToPNG'] === 1;
|
||||
return uid + '-' + type + (keepAllVersions ? '-' + Date.now() : '') + (convertToPNG ? '.png' : extension);
|
||||
}
|
||||
|
||||
function saveFileToLocal(filename, image, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
file.isFileTypeAllowed(image.path, next);
|
||||
},
|
||||
function (next) {
|
||||
file.saveFileToLocal(filename, 'profile', image.path, next);
|
||||
},
|
||||
function (upload, next) {
|
||||
next(null, {
|
||||
url: upload.url,
|
||||
path: upload.path,
|
||||
name: image.name,
|
||||
});
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
User.removeCoverPicture = function (data, callback) {
|
||||
db.deleteObjectFields('user:' + data.uid, ['cover:url', 'cover:position'], callback);
|
||||
};
|
||||
|
||||
@@ -79,6 +79,7 @@ module.exports = function (User) {
|
||||
settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1;
|
||||
settings.bootswatchSkin = settings.bootswatchSkin || '';
|
||||
settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1;
|
||||
settings.categoryWatchState = getSetting(settings, 'categoryWatchState', 'notwatching');
|
||||
|
||||
notifications.getAllNotificationTypes(next);
|
||||
},
|
||||
@@ -137,6 +138,7 @@ module.exports = function (User) {
|
||||
outgoingChatSound: data.outgoingChatSound,
|
||||
upvoteNotifFreq: data.upvoteNotifFreq,
|
||||
bootswatchSkin: data.bootswatchSkin,
|
||||
categoryWatchState: data.categoryWatchState,
|
||||
};
|
||||
|
||||
async.waterfall([
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<!-- ENDIF !events.length -->
|
||||
<div class="events-list">
|
||||
<!-- BEGIN events -->
|
||||
<div>
|
||||
<div data-eid="{events.eid}">
|
||||
<span>#{events.eid} </span><span class="label label-info">{events.type}</span>
|
||||
<a href="{config.relative_path}/user/{events.user.userslug}" target="_blank">
|
||||
<!-- IF events.user.picture -->
|
||||
@@ -23,6 +23,7 @@
|
||||
<!-- ENDIF events.user.picture -->
|
||||
</a>
|
||||
<a href="{config.relative_path}/user/{events.user.userslug}" target="_blank">{events.user.username}</a> (uid {events.uid}) (IP {events.ip})
|
||||
<span class="pull-right delete-event"><i class="fa fa-trash-o"></i></span>
|
||||
<span class="pull-right">{events.timestampISO}</span>
|
||||
<br /><br />
|
||||
<pre>{events.jsonString}</pre>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<form role="form" class="category" data-cid="{category.cid}">
|
||||
<div class="category" data-cid="{category.cid}">
|
||||
<div class="row">
|
||||
<div class="col-md-3 pull-right">
|
||||
<select id="category-selector" class="form-control">
|
||||
@@ -163,7 +163,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<button id="save" class="floating-button mdl-button mdl-js-button mdl-button--fab mdl-js-ripple-effect mdl-button--colored">
|
||||
<i class="material-icons">save</i>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<p class="help-block">
|
||||
[[admin/settings/group:default-cover-help]]
|
||||
</p>
|
||||
<input type="text" class="form-control input-lg" id="groups:defaultCovers" data-field="groups:defaultCovers" data-field-type="tagsinput" value="{config.relative_path}/assets/images/cover-default.png" placeholder="https://example.com/group1.png, https://example.com/group2.png" /><br />
|
||||
<input type="text" class="form-control input-lg" id="groups:defaultCovers" data-field="groups:defaultCovers" data-field-type="tagsinput" value="/assets/images/cover-default.png" placeholder="https://example.com/group1.png, https://example.com/group2.png" /><br />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
<p class="help-block">
|
||||
[[admin/settings/uploads:default-covers-help]]
|
||||
</p>
|
||||
<input type="text" class="form-control input-lg" id="profile:defaultCovers" data-field="profile:defaultCovers" data-field-type="tagsinput" value="{config.relative_path}/assets/images/cover-default.png" placeholder="https://example.com/group1.png, https://example.com/group2.png" />
|
||||
<input type="text" class="form-control input-lg" id="profile:defaultCovers" data-field="profile:defaultCovers" data-field-type="tagsinput" value="/assets/images/cover-default.png" placeholder="https://example.com/group1.png, https://example.com/group2.png" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -307,6 +307,15 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>[[admin/settings/user:categoryWatchState]]</label>
|
||||
<select class="form-control" data-field="categoryWatchState">
|
||||
<option value="watching">[[admin/settings/user:categoryWatchState.watching]]</option>
|
||||
<option value="notwatching">[[admin/settings/user:categoryWatchState.notwatching]]</option>
|
||||
<option value="ignoring">[[admin/settings/user:categoryWatchState.ignoring]]</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<label>[[admin/settings/user:default-notification-settings]]</label>
|
||||
|
||||
<!-- BEGIN notificationSettings -->
|
||||
|
||||
@@ -166,16 +166,22 @@ function setupExpressApp(app, callback) {
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
app.use(cookieParser());
|
||||
app.use(useragent.express());
|
||||
app.use(detector.middleware());
|
||||
const userAgentMiddleware = useragent.express();
|
||||
app.use(function userAgent(req, res, next) {
|
||||
userAgentMiddleware(req, res, next);
|
||||
});
|
||||
const spiderDetectorMiddleware = detector.middleware();
|
||||
app.use(function spiderDetector(req, res, next) {
|
||||
spiderDetectorMiddleware(req, res, next);
|
||||
});
|
||||
|
||||
app.use(session({
|
||||
store: db.sessionStore,
|
||||
secret: nconf.get('secret'),
|
||||
key: nconf.get('sessionKey'),
|
||||
cookie: setupCookie(),
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
resave: nconf.get('sessionResave') || false,
|
||||
saveUninitialized: nconf.get('sessionSaveUninitialized') || false,
|
||||
}));
|
||||
|
||||
var hsts_option = {
|
||||
|
||||
Reference in New Issue
Block a user