Merge branch 'master' into develop

This commit is contained in:
Julian Lam
2017-03-06 09:51:11 -05:00
57 changed files with 2020 additions and 1693 deletions

47
src/controllers/404.js Normal file
View File

@@ -0,0 +1,47 @@
'use strict';
var nconf = require('nconf');
var winston = require('winston');
var validator = require('validator');
var meta = require('../meta');
var plugins = require('../plugins');
exports.handle404 = function (req, res) {
var relativePath = nconf.get('relative_path');
var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js');
if (plugins.hasListeners('action:meta.override404')) {
return plugins.fireHook('action:meta.override404', {
req: req,
res: res,
error: {},
});
}
if (isClientScript.test(req.url)) {
res.type('text/javascript').status(200).send('');
} else if (req.path.startsWith(relativePath + '/assets/uploads') || (req.get('accept') && req.get('accept').indexOf('text/html') === -1) || req.path === '/favicon.ico') {
meta.errors.log404(req.path || '');
res.sendStatus(404);
} else if (req.accepts('html')) {
if (process.env.NODE_ENV === 'development') {
winston.warn('Route requested but not found: ' + req.url);
}
meta.errors.log404(req.path.replace(/^\/api/, '') || '');
res.status(404);
var path = String(req.path || '');
if (res.locals.isAPI) {
return res.json({ path: validator.escape(path.replace(/^\/api/, '')), title: '[[global:404.title]]' });
}
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('404', { path: validator.escape(path), title: '[[global:404.title]]' });
});
} else {
res.status(404).type('txt').send('Not found');
}
};

View File

@@ -13,9 +13,8 @@ var privileges = require('../privileges');
var plugins = require('../plugins');
var widgets = require('../widgets');
var translator = require('../../public/src/modules/translator');
var accountHelpers = require('../controllers/accounts/helpers');
var apiController = {};
var apiController = module.exports;
apiController.getConfig = function (req, res, next) {
var config = {};
@@ -62,7 +61,7 @@ apiController.getConfig = function (req, res, next) {
config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest';
config.csrf_token = req.csrfToken();
config.searchEnabled = plugins.hasListeners('filter:search.query');
config.bootswatchSkin = 'default';
config.bootswatchSkin = meta.config.bootswatchSkin || 'default';
var timeagoCutoff = meta.config.timeagoCutoff === undefined ? 30 : meta.config.timeagoCutoff;
config.timeagoCutoff = timeagoCutoff !== '' ? Math.max(0, parseInt(timeagoCutoff, 10)) : timeagoCutoff;
@@ -220,92 +219,6 @@ apiController.getObject = function (req, res, next) {
});
};
apiController.getCurrentUser = function (req, res, next) {
if (!req.uid) {
return res.status(401).json('not-authorized');
}
async.waterfall([
function (next) {
user.getUserField(req.uid, 'userslug', next);
},
function (userslug, next) {
accountHelpers.getUserDataByUserSlug(userslug, req.uid, next);
},
], function (err, userData) {
if (err) {
return next(err);
}
res.json(userData);
});
};
apiController.getUserByUID = function (req, res, next) {
byType('uid', req, res, next);
};
apiController.getUserByUsername = function (req, res, next) {
byType('username', req, res, next);
};
apiController.getUserByEmail = function (req, res, next) {
byType('email', req, res, next);
};
function byType(type, req, res, next) {
apiController.getUserDataByField(req.uid, type, req.params[type], function (err, data) {
if (err || !data) {
return next(err);
}
res.json(data);
});
}
apiController.getUserDataByField = function (callerUid, field, fieldValue, callback) {
async.waterfall([
function (next) {
if (field === 'uid') {
next(null, fieldValue);
} else if (field === 'username') {
user.getUidByUsername(fieldValue, next);
} else if (field === 'email') {
user.getUidByEmail(fieldValue, next);
} else {
next();
}
},
function (uid, next) {
if (!uid) {
return next();
}
apiController.getUserDataByUID(callerUid, uid, next);
},
], callback);
};
apiController.getUserDataByUID = function (callerUid, uid, callback) {
if (!parseInt(callerUid, 10) && parseInt(meta.config.privateUserInfo, 10) === 1) {
return callback(new Error('[[error:no-privileges]]'));
}
if (!parseInt(uid, 10)) {
return callback(new Error('[[error:no-user]]'));
}
async.parallel({
userData: async.apply(user.getUserData, uid),
settings: async.apply(user.getSettings, uid),
}, function (err, results) {
if (err || !results.userData) {
return callback(err || new Error('[[error:no-user]]'));
}
results.userData.email = results.settings.showemail ? results.userData.email : undefined;
results.userData.fullname = results.settings.showfullname ? results.userData.fullname : undefined;
callback(null, results.userData);
});
};
apiController.getModerators = function (req, res, next) {
categories.getModerators(req.params.cid, function (err, moderators) {
if (err) {
@@ -314,16 +227,3 @@ apiController.getModerators = function (req, res, next) {
res.json({ moderators: moderators });
});
};
apiController.getRecentPosts = function (req, res, next) {
posts.getRecentPosts(req.uid, 0, 19, req.params.term, function (err, data) {
if (err) {
return next(err);
}
res.json(data);
});
};
module.exports = apiController;

View File

@@ -13,9 +13,6 @@ categoriesController.list = function (req, res, next) {
res.locals.metaTags = [{
name: 'title',
content: String(meta.config.title || 'NodeBB'),
}, {
name: 'description',
content: String(meta.config.description || ''),
}, {
property: 'og:title',
content: '[[pages:categories]]',

63
src/controllers/errors.js Normal file
View File

@@ -0,0 +1,63 @@
'use strict';
var nconf = require('nconf');
var winston = require('winston');
var validator = require('validator');
exports.handleURIErrors = function (err, req, res, next) {
// Handle cases where malformed URIs are passed in
if (err instanceof URIError) {
var tidMatch = req.path.match(/^\/topic\/(\d+)\//);
var cidMatch = req.path.match(/^\/category\/(\d+)\//);
if (tidMatch) {
res.redirect(nconf.get('relative_path') + tidMatch[0]);
} else if (cidMatch) {
res.redirect(nconf.get('relative_path') + cidMatch[0]);
} else {
winston.warn('[controller] Bad request: ' + req.path);
if (res.locals.isAPI) {
res.status(400).json({
error: '[[global:400.title]]',
});
} else {
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('400', { error: validator.escape(String(err.message)) });
});
}
}
} else {
next(err);
}
};
// 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
switch (err.code) {
case 'EBADCSRFTOKEN':
winston.error(req.path + '\n', err.message);
return res.sendStatus(403);
case 'blacklisted-ip':
return res.status(403).type('text/plain').send(err.message);
}
if (parseInt(err.status, 10) === 302 && err.path) {
return res.locals.isAPI ? res.status(302).json(err.path) : res.redirect(err.path);
}
winston.error(req.path + '\n', err.stack);
res.status(err.status || 500);
var path = String(req.path || '');
if (res.locals.isAPI) {
res.json({ path: validator.escape(path), error: err.message });
} else {
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('500', { path: validator.escape(path), error: validator.escape(String(err.message)) });
});
}
};

View File

@@ -3,34 +3,35 @@
var async = require('async');
var nconf = require('nconf');
var validator = require('validator');
var winston = require('winston');
var meta = require('../meta');
var user = require('../user');
var plugins = require('../plugins');
var helpers = require('./helpers');
var Controllers = {
topics: require('./topics'),
posts: require('./posts'),
categories: require('./categories'),
category: require('./category'),
unread: require('./unread'),
recent: require('./recent'),
popular: require('./popular'),
tags: require('./tags'),
search: require('./search'),
users: require('./users'),
groups: require('./groups'),
accounts: require('./accounts'),
authentication: require('./authentication'),
api: require('./api'),
admin: require('./admin'),
globalMods: require('./globalmods'),
mods: require('./mods'),
sitemap: require('./sitemap'),
};
var Controllers = module.exports;
Controllers.topics = require('./topics');
Controllers.posts = require('./posts');
Controllers.categories = require('./categories');
Controllers.category = require('./category');
Controllers.unread = require('./unread');
Controllers.recent = require('./recent');
Controllers.popular = require('./popular');
Controllers.tags = require('./tags');
Controllers.search = require('./search');
Controllers.user = require('./user');
Controllers.users = require('./users');
Controllers.groups = require('./groups');
Controllers.accounts = require('./accounts');
Controllers.authentication = require('./authentication');
Controllers.api = require('./api');
Controllers.admin = require('./admin');
Controllers.globalMods = require('./globalmods');
Controllers.mods = require('./mods');
Controllers.sitemap = require('./sitemap');
Controllers['404'] = require('./404');
Controllers.errors = require('./errors');
Controllers.home = function (req, res, next) {
var route = meta.config.homePageRoute || (meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories';
@@ -321,19 +322,18 @@ Controllers.manifest = function (req, res) {
res.status(200).json(manifest);
};
Controllers.outgoing = function (req, res) {
Controllers.outgoing = function (req, res, next) {
var url = req.query.url || '';
var data = {
if (!url) {
return next();
}
res.render('outgoing', {
outgoing: validator.escape(String(url)),
title: meta.config.title,
breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[notifications:outgoing_link]]' }]),
};
if (url) {
res.render('outgoing', data);
} else {
res.status(404).redirect(nconf.get('relative_path') + '/404');
}
});
};
Controllers.termsOfUse = function (req, res, next) {
@@ -346,102 +346,3 @@ Controllers.termsOfUse = function (req, res, next) {
Controllers.ping = function (req, res) {
res.status(200).send(req.path === '/sping' ? 'healthy' : '200');
};
Controllers.handle404 = function (req, res) {
var relativePath = nconf.get('relative_path');
var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js');
if (plugins.hasListeners('action:meta.override404')) {
return plugins.fireHook('action:meta.override404', {
req: req,
res: res,
error: {},
});
}
if (isClientScript.test(req.url)) {
res.type('text/javascript').status(200).send('');
} else if (req.path.startsWith(relativePath + '/assets/uploads') || (req.get('accept') && req.get('accept').indexOf('text/html') === -1) || req.path === '/favicon.ico') {
meta.errors.log404(req.path || '');
res.sendStatus(404);
} else if (req.accepts('html')) {
if (process.env.NODE_ENV === 'development') {
winston.warn('Route requested but not found: ' + req.url);
}
meta.errors.log404(req.path.replace(/^\/api/, '') || '');
res.status(404);
var path = String(req.path || '');
if (res.locals.isAPI) {
return res.json({ path: validator.escape(path.replace(/^\/api/, '')), title: '[[global:404.title]]' });
}
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('404', { path: validator.escape(path), title: '[[global:404.title]]' });
});
} else {
res.status(404).type('txt').send('Not found');
}
};
Controllers.handleURIErrors = function (err, req, res, next) {
// Handle cases where malformed URIs are passed in
if (err instanceof URIError) {
var tidMatch = req.path.match(/^\/topic\/(\d+)\//);
var cidMatch = req.path.match(/^\/category\/(\d+)\//);
if (tidMatch) {
res.redirect(nconf.get('relative_path') + tidMatch[0]);
} else if (cidMatch) {
res.redirect(nconf.get('relative_path') + cidMatch[0]);
} else {
winston.warn('[controller] Bad request: ' + req.path);
if (res.locals.isAPI) {
res.status(400).json({
error: '[[global:400.title]]',
});
} else {
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('400', { error: validator.escape(String(err.message)) });
});
}
}
} else {
next(err);
}
};
// this needs to have four arguments or express treats it as `(req, res, next)`
// don't remove `next`!
Controllers.handleErrors = function (err, req, res, next) { // eslint-disable-line no-unused-vars
switch (err.code) {
case 'EBADCSRFTOKEN':
winston.error(req.path + '\n', err.message);
return res.sendStatus(403);
case 'blacklisted-ip':
return res.status(403).type('text/plain').send(err.message);
}
if (parseInt(err.status, 10) === 302 && err.path) {
return res.locals.isAPI ? res.status(302).json(err.path) : res.redirect(err.path);
}
winston.error(req.path + '\n', err.stack);
res.status(err.status || 500);
var path = String(req.path || '');
if (res.locals.isAPI) {
res.json({ path: validator.escape(path), error: err.message });
} else {
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('500', { path: validator.escape(path), error: validator.escape(String(err.message)) });
});
}
};
module.exports = Controllers;

View File

@@ -1,24 +1,38 @@
'use strict';
var async = require('async');
var posts = require('../posts');
var helpers = require('./helpers');
var postsController = {};
var postsController = module.exports;
postsController.redirectToPost = function (req, res, callback) {
postsController.redirectToPost = function (req, res, next) {
var pid = parseInt(req.params.pid, 10);
if (!pid) {
return callback();
return next();
}
posts.generatePostPath(pid, req.uid, function (err, path) {
if (err || !path) {
return callback(err);
}
helpers.redirect(res, path);
});
async.waterfall([
function (next) {
posts.generatePostPath(pid, req.uid, next);
},
function (path, next) {
if (!path) {
return next();
}
helpers.redirect(res, path);
},
], next);
};
module.exports = postsController;
postsController.getRecentPosts = function (req, res, next) {
async.waterfall([
function (next) {
posts.getRecentPosts(req.uid, 0, 19, req.params.term, next);
},
function (data) {
res.json(data);
},
], next);
};

View File

@@ -1,68 +1,57 @@
'use strict';
var async = require('async');
var sitemap = require('../sitemap');
var meta = require('../meta');
var sitemapController = {};
sitemapController.render = function (req, res, next) {
sitemap.render(function (err, tplData) {
if (err) {
return next(err);
}
var sitemapController = module.exports;
req.app.render('sitemap', tplData, function (err, xml) {
if (err) {
return next(err);
}
sitemapController.render = function (req, res, next) {
async.waterfall([
function (next) {
sitemap.render(next);
},
function (tplData, next) {
req.app.render('sitemap', tplData, next);
},
function (xml) {
res.header('Content-Type', 'application/xml');
res.send(xml);
});
});
},
], next);
};
sitemapController.getPages = function (req, res, next) {
if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) {
return next();
}
sitemap.getPages(function (err, xml) {
if (err) {
return next(err);
}
res.header('Content-Type', 'application/xml');
res.send(xml);
});
sendSitemap(sitemap.getPages, res, next);
};
sitemapController.getCategories = function (req, res, next) {
if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) {
return next();
}
sitemap.getCategories(function (err, xml) {
if (err) {
return next(err);
}
res.header('Content-Type', 'application/xml');
res.send(xml);
});
sendSitemap(sitemap.getCategories, res, next);
};
sitemapController.getTopicPage = function (req, res, next) {
if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) {
return next();
}
sitemap.getTopicPage(parseInt(req.params[0], 10), function (err, xml) {
if (err) {
return next(err);
} else if (!xml) {
return next();
}
res.header('Content-Type', 'application/xml');
res.send(xml);
});
sendSitemap(function (callback) {
sitemap.getTopicPage(parseInt(req.params[0], 10), callback);
}, res, next);
};
module.exports = sitemapController;
function sendSitemap(method, res, callback) {
if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) {
return callback();
}
async.waterfall([
function (next) {
method(next);
},
function (xml) {
if (!xml) {
return callback();
}
res.header('Content-Type', 'application/xml');
res.send(xml);
},
], callback);
}

View File

@@ -66,7 +66,7 @@ function uploadAsImage(req, uploadedFile, callback) {
file.isFileTypeAllowed(uploadedFile.path, next);
},
function (next) {
uploadFile(req.uid, uploadedFile, next);
uploadsController.uploadFile(req.uid, uploadedFile, next);
},
function (fileObj, next) {
if (parseInt(meta.config.maximumImageWidth, 10) === 0) {
@@ -90,7 +90,7 @@ function uploadAsFile(req, uploadedFile, callback) {
if (parseInt(meta.config.allowFileUploads, 10) !== 1) {
return next(new Error('[[error:uploads-are-disabled]]'));
}
uploadFile(req.uid, uploadedFile, next);
uploadsController.uploadFile(req.uid, uploadedFile, next);
},
], callback);
}
@@ -161,7 +161,7 @@ uploadsController.uploadThumb = function (req, res, next) {
}, next);
}
uploadFile(req.uid, uploadedFile, next);
uploadsController.uploadFile(req.uid, uploadedFile, next);
},
], next);
}, next);
@@ -192,7 +192,7 @@ uploadsController.uploadGroupCover = function (uid, uploadedFile, callback) {
], callback);
};
function uploadFile(uid, uploadedFile, callback) {
uploadsController.uploadFile = function (uid, uploadedFile, callback) {
if (plugins.hasListeners('filter:uploadFile')) {
return plugins.fireHook('filter:uploadFile', {
file: uploadedFile,
@@ -217,7 +217,7 @@ function uploadFile(uid, uploadedFile, callback) {
}
saveFileToLocal(uploadedFile, callback);
}
};
function saveFileToLocal(uploadedFile, callback) {
var extension = file.typeToExtension(uploadedFile.type);

99
src/controllers/user.js Normal file
View File

@@ -0,0 +1,99 @@
'use strict';
var async = require('async');
var user = require('../user');
var meta = require('../meta');
var accountHelpers = require('./accounts/helpers');
var userController = module.exports;
userController.getCurrentUser = function (req, res, next) {
if (!req.uid) {
return res.status(401).json('not-authorized');
}
async.waterfall([
function (next) {
user.getUserField(req.uid, 'userslug', next);
},
function (userslug, next) {
accountHelpers.getUserDataByUserSlug(userslug, req.uid, next);
},
function (userData) {
res.json(userData);
},
], next);
};
userController.getUserByUID = function (req, res, next) {
byType('uid', req, res, next);
};
userController.getUserByUsername = function (req, res, next) {
byType('username', req, res, next);
};
userController.getUserByEmail = function (req, res, next) {
byType('email', req, res, next);
};
function byType(type, req, res, next) {
async.waterfall([
function (next) {
userController.getUserDataByField(req.uid, type, req.params[type], next);
},
function (data, next) {
if (!data) {
return next();
}
res.json(data);
},
], next);
}
userController.getUserDataByField = function (callerUid, field, fieldValue, callback) {
async.waterfall([
function (next) {
if (field === 'uid') {
next(null, fieldValue);
} else if (field === 'username') {
user.getUidByUsername(fieldValue, next);
} else if (field === 'email') {
user.getUidByEmail(fieldValue, next);
} else {
next(null, null);
}
},
function (uid, next) {
if (!uid) {
return next(null, null);
}
userController.getUserDataByUID(callerUid, uid, next);
},
], callback);
};
userController.getUserDataByUID = function (callerUid, uid, callback) {
if (!parseInt(callerUid, 10) && parseInt(meta.config.privateUserInfo, 10) === 1) {
return callback(new Error('[[error:no-privileges]]'));
}
if (!parseInt(uid, 10)) {
return callback(new Error('[[error:no-user]]'));
}
async.parallel({
userData: async.apply(user.getUserData, uid),
settings: async.apply(user.getSettings, uid),
}, function (err, results) {
if (err || !results.userData) {
return callback(err || new Error('[[error:no-user]]'));
}
results.userData.email = results.settings.showemail ? results.userData.email : undefined;
results.userData.fullname = results.settings.showfullname ? results.userData.fullname : undefined;
callback(null, results.userData);
});
};

View File

@@ -8,9 +8,7 @@ var pagination = require('../pagination');
var db = require('../database');
var helpers = require('./helpers');
var usersController = {};
var usersController = module.exports;
usersController.index = function (req, res, next) {
var section = req.query.section || 'joindate';
@@ -33,62 +31,65 @@ usersController.index = function (req, res, next) {
};
usersController.search = function (req, res, next) {
async.parallel({
search: function (next) {
user.search({
query: req.query.term,
searchBy: req.query.searchBy || 'username',
page: req.query.page || 1,
sortBy: req.query.sortBy,
onlineOnly: req.query.onlineOnly === 'true',
bannedOnly: req.query.bannedOnly === 'true',
flaggedOnly: req.query.flaggedOnly === 'true',
async.waterfall([
function (next) {
async.parallel({
search: function (next) {
user.search({
query: req.query.term,
searchBy: req.query.searchBy || 'username',
page: req.query.page || 1,
sortBy: req.query.sortBy,
onlineOnly: req.query.onlineOnly === 'true',
bannedOnly: req.query.bannedOnly === 'true',
flaggedOnly: req.query.flaggedOnly === 'true',
}, next);
},
isAdminOrGlobalMod: function (next) {
user.isAdminOrGlobalMod(req.uid, next);
},
}, next);
},
isAdminOrGlobalMod: function (next) {
user.isAdminOrGlobalMod(req.uid, next);
function (results, next) {
var section = req.query.section || 'joindate';
results.search.isAdminOrGlobalMod = results.isAdminOrGlobalMod;
results.search.pagination = pagination.create(req.query.page, results.search.pageCount, req.query);
results.search['section_' + section] = true;
render(req, res, results.search, next);
},
}, function (err, results) {
if (err) {
return next(err);
}
var section = req.query.section || 'joindate';
results.search.isAdminOrGlobalMod = results.isAdminOrGlobalMod;
results.search.pagination = pagination.create(req.query.page, results.search.pageCount, req.query);
results.search['section_' + section] = true;
render(req, res, results.search, next);
});
], next);
};
usersController.getOnlineUsers = function (req, res, next) {
async.parallel({
users: function (next) {
usersController.getUsers('users:online', req.uid, req.query, next);
async.waterfall([
function (next) {
async.parallel({
users: function (next) {
usersController.getUsers('users:online', req.uid, req.query, next);
},
guests: function (next) {
require('../socket.io/admin/rooms').getTotalGuestCount(next);
},
}, next);
},
guests: function (next) {
require('../socket.io/admin/rooms').getTotalGuestCount(next);
function (results, next) {
var userData = results.users;
var hiddenCount = 0;
if (!userData.isAdminOrGlobalMod) {
userData.users = userData.users.filter(function (user) {
if (user && user.status === 'offline') {
hiddenCount += 1;
}
return user && user.status !== 'offline';
});
}
userData.anonymousUserCount = results.guests + hiddenCount;
render(req, res, userData, next);
},
}, function (err, results) {
if (err) {
return next(err);
}
var userData = results.users;
var hiddenCount = 0;
if (!userData.isAdminOrGlobalMod) {
userData.users = userData.users.filter(function (user) {
if (user && user.status === 'offline') {
hiddenCount += 1;
}
return user && user.status !== 'offline';
});
}
userData.anonymousUserCount = results.guests + hiddenCount;
render(req, res, userData, next);
});
], next);
};
usersController.getUsersSortedByPosts = function (req, res, next) {
@@ -107,41 +108,36 @@ usersController.getUsersSortedByJoinDate = function (req, res, next) {
};
usersController.getBannedUsers = function (req, res, next) {
usersController.getUsers('users:banned', req.uid, req.query, function (err, userData) {
if (err) {
return next(err);
}
if (!userData.isAdminOrGlobalMod) {
return next();
}
render(req, res, userData, next);
});
renderIfAdminOrGlobalMod('users:banned', req, res, next);
};
usersController.getFlaggedUsers = function (req, res, next) {
usersController.getUsers('users:flags', req.uid, req.query, function (err, userData) {
if (err) {
return next(err);
}
if (!userData.isAdminOrGlobalMod) {
return next();
}
render(req, res, userData, next);
});
renderIfAdminOrGlobalMod('users:flags', req, res, next);
};
usersController.renderUsersPage = function (set, req, res, next) {
usersController.getUsers(set, req.uid, req.query, function (err, userData) {
if (err) {
return next(err);
}
function renderIfAdminOrGlobalMod(set, req, res, next) {
async.waterfall([
function (next) {
user.isAdminOrGlobalMod(req.uid, next);
},
function (isAdminOrGlobalMod, next) {
if (!isAdminOrGlobalMod) {
return helpers.notAllowed(req, res);
}
usersController.renderUsersPage(set, req, res, next);
},
], next);
}
render(req, res, userData, next);
});
usersController.renderUsersPage = function (set, req, res, next) {
async.waterfall([
function (next) {
usersController.getUsers(set, req.uid, req.query, next);
},
function (userData, next) {
render(req, res, userData, next);
},
], next);
};
usersController.getUsers = function (set, uid, query, callback) {
@@ -169,59 +165,62 @@ usersController.getUsers = function (set, uid, query, callback) {
var start = Math.max(0, page - 1) * resultsPerPage;
var stop = start + resultsPerPage - 1;
async.parallel({
isAdminOrGlobalMod: function (next) {
user.isAdminOrGlobalMod(uid, next);
async.waterfall([
function (next) {
async.parallel({
isAdminOrGlobalMod: function (next) {
user.isAdminOrGlobalMod(uid, next);
},
usersData: function (next) {
usersController.getUsersAndCount(set, uid, start, stop, next);
},
}, next);
},
usersData: function (next) {
usersController.getUsersAndCount(set, uid, start, stop, next);
function (results, next) {
var pageCount = Math.ceil(results.usersData.count / resultsPerPage);
var userData = {
users: results.usersData.users,
pagination: pagination.create(page, pageCount, query),
userCount: results.usersData.count,
title: setToData[set].title || '[[pages:users/latest]]',
breadcrumbs: helpers.buildBreadcrumbs(breadcrumbs),
isAdminOrGlobalMod: results.isAdminOrGlobalMod,
};
userData['section_' + (query.section || 'joindate')] = true;
next(null, userData);
},
}, function (err, results) {
if (err) {
return callback(err);
}
var pageCount = Math.ceil(results.usersData.count / resultsPerPage);
var userData = {
users: results.usersData.users,
pagination: pagination.create(page, pageCount, query),
userCount: results.usersData.count,
title: setToData[set].title || '[[pages:users/latest]]',
breadcrumbs: helpers.buildBreadcrumbs(breadcrumbs),
isAdminOrGlobalMod: results.isAdminOrGlobalMod,
};
userData['section_' + (query.section || 'joindate')] = true;
callback(null, userData);
});
], callback);
};
usersController.getUsersAndCount = function (set, uid, start, stop, callback) {
async.parallel({
users: function (next) {
user.getUsersFromSet(set, uid, start, stop, next);
async.waterfall([
function (next) {
async.parallel({
users: function (next) {
user.getUsersFromSet(set, uid, start, stop, next);
},
count: function (next) {
if (set === 'users:online') {
var now = Date.now();
db.sortedSetCount('users:online', now - 300000, '+inf', next);
} else if (set === 'users:banned') {
db.sortedSetCard('users:banned', next);
} else if (set === 'users:flags') {
db.sortedSetCard('users:flags', next);
} else {
db.getObjectField('global', 'userCount', next);
}
},
}, next);
},
count: function (next) {
if (set === 'users:online') {
var now = Date.now();
db.sortedSetCount('users:online', now - 300000, '+inf', next);
} else if (set === 'users:banned') {
db.sortedSetCard('users:banned', next);
} else if (set === 'users:flags') {
db.sortedSetCard('users:flags', next);
} else {
db.getObjectField('global', 'userCount', next);
}
},
}, function (err, results) {
if (err) {
return callback(err);
}
results.users = results.users.filter(function (user) {
return user && parseInt(user.uid, 10);
});
function (results, next) {
results.users = results.users.filter(function (user) {
return user && parseInt(user.uid, 10);
});
callback(null, results);
});
next(null, results);
},
], callback);
};
function render(req, res, data, next) {
@@ -232,16 +231,15 @@ function render(req, res, data, next) {
data.adminInviteOnly = registrationType === 'admin-invite-only';
data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
user.getInvitesNumber(req.uid, function (err, numInvites) {
if (err) {
return next(err);
}
async.waterfall([
function (next) {
user.getInvitesNumber(req.uid, next);
},
function (numInvites) {
res.append('X-Total-Count', data.userCount);
data.invites = numInvites;
res.append('X-Total-Count', data.userCount);
data.invites = numInvites;
res.render('users', data);
});
res.render('users', data);
},
], next);
}
module.exports = usersController;

View File

@@ -6,215 +6,197 @@ var validator = require('validator');
var user = require('./user');
var db = require('./database');
var plugins = require('./plugins');
var posts = require('./posts');
var privileges = require('./privileges');
var utils = require('../public/src/utils');
(function (Groups) {
require('./groups/create')(Groups);
require('./groups/delete')(Groups);
require('./groups/update')(Groups);
require('./groups/membership')(Groups);
require('./groups/ownership')(Groups);
require('./groups/search')(Groups);
require('./groups/cover')(Groups);
var Groups = module.exports;
var ephemeralGroups = ['guests'];
require('./groups/data')(Groups);
require('./groups/create')(Groups);
require('./groups/delete')(Groups);
require('./groups/update')(Groups);
require('./groups/membership')(Groups);
require('./groups/ownership')(Groups);
require('./groups/search')(Groups);
require('./groups/cover')(Groups);
require('./groups/posts')(Groups);
require('./groups/user')(Groups);
var internals = {
getEphemeralGroup: function (groupName) {
return {
name: groupName,
slug: utils.slugify(groupName),
description: '',
deleted: '0',
hidden: '0',
system: '1',
};
},
removeEphemeralGroups: function (groups) {
for (var x = groups.length; x >= 0; x -= 1) {
if (ephemeralGroups.indexOf(groups[x]) !== -1) {
groups.splice(x, 1);
}
}
return groups;
},
Groups.ephemeralGroups = ['guests'];
Groups.getEphemeralGroup = function (groupName) {
return {
name: groupName,
slug: utils.slugify(groupName),
description: '',
deleted: '0',
hidden: '0',
system: '1',
};
};
Groups.internals = internals;
var isPrivilegeGroupRegex = /^cid:\d+:privileges:[\w:]+$/;
Groups.isPrivilegeGroup = function (groupName) {
return isPrivilegeGroupRegex.test(groupName);
};
Groups.getEphemeralGroups = function () {
return ephemeralGroups;
};
Groups.getGroupsFromSet = function (set, uid, start, stop, callback) {
async.waterfall([
function (next) {
if (set === 'groups:visible:name') {
db.getSortedSetRangeByLex(set, '-', '+', start, stop - start + 1, next);
} else {
db.getSortedSetRevRange(set, start, stop, next);
}
},
function (groupNames, next) {
if (set === 'groups:visible:name') {
groupNames = groupNames.map(function (name) {
return name.split(':')[1];
});
}
Groups.getGroupsAndMembers(groupNames, next);
},
], callback);
};
Groups.getGroups = function (set, start, stop, callback) {
db.getSortedSetRevRange(set, start, stop, callback);
};
Groups.getGroupsAndMembers = function (groupNames, callback) {
async.parallel({
groups: function (next) {
Groups.getGroupsData(groupNames, next);
},
members: function (next) {
Groups.getMemberUsers(groupNames, 0, 3, next);
},
}, function (err, data) {
if (err) {
return callback(err);
}
data.groups.forEach(function (group, index) {
if (!group) {
return;
}
group.members = data.members[index] || [];
group.truncated = group.memberCount > data.members.length;
});
callback(null, data.groups);
});
};
Groups.get = function (groupName, options, callback) {
if (!groupName) {
return callback(new Error('[[error:invalid-group]]'));
Groups.removeEphemeralGroups = function (groups) {
for (var x = groups.length; x >= 0; x -= 1) {
if (Groups.ephemeralGroups.indexOf(groups[x]) !== -1) {
groups.splice(x, 1);
}
}
var stop = -1;
return groups;
};
async.parallel({
base: function (next) {
db.getObject('group:' + groupName, next);
},
members: function (next) {
if (options.truncateUserList) {
stop = (parseInt(options.userListCount, 10) || 4) - 1;
var isPrivilegeGroupRegex = /^cid:\d+:privileges:[\w:]+$/;
Groups.isPrivilegeGroup = function (groupName) {
return isPrivilegeGroupRegex.test(groupName);
};
Groups.getGroupsFromSet = function (set, uid, start, stop, callback) {
async.waterfall([
function (next) {
if (set === 'groups:visible:name') {
db.getSortedSetRangeByLex(set, '-', '+', start, stop - start + 1, next);
} else {
db.getSortedSetRevRange(set, start, stop, next);
}
},
function (groupNames, next) {
if (set === 'groups:visible:name') {
groupNames = groupNames.map(function (name) {
return name.split(':')[1];
});
}
Groups.getGroupsAndMembers(groupNames, next);
},
], callback);
};
Groups.getGroups = function (set, start, stop, callback) {
db.getSortedSetRevRange(set, start, stop, callback);
};
Groups.getGroupsAndMembers = function (groupNames, callback) {
async.waterfall([
function (next) {
async.parallel({
groups: function (next) {
Groups.getGroupsData(groupNames, next);
},
members: function (next) {
Groups.getMemberUsers(groupNames, 0, 3, next);
},
}, next);
},
function (data, next) {
data.groups.forEach(function (group, index) {
if (group) {
group.members = data.members[index] || [];
group.truncated = group.memberCount > data.members.length;
}
});
next(null, data.groups);
},
], callback);
};
Groups.getOwnersAndMembers(groupName, options.uid, 0, stop, next);
},
pending: function (next) {
async.waterfall([
function (next) {
db.getSetMembers('group:' + groupName + ':pending', next);
},
function (uids, next) {
user.getUsersData(uids, next);
},
], next);
},
invited: function (next) {
async.waterfall([
function (next) {
db.getSetMembers('group:' + groupName + ':invited', next);
},
function (uids, next) {
user.getUsersData(uids, next);
},
], next);
},
isMember: async.apply(Groups.isMember, options.uid, groupName),
isPending: async.apply(Groups.isPending, options.uid, groupName),
isInvited: async.apply(Groups.isInvited, options.uid, groupName),
isOwner: async.apply(Groups.ownership.isOwner, options.uid, groupName),
}, function (err, results) {
if (err) {
return callback(err);
} else if (!results.base) {
Groups.get = function (groupName, options, callback) {
if (!groupName) {
return callback(new Error('[[error:invalid-group]]'));
}
var stop = -1;
var results;
async.waterfall([
function (next) {
async.parallel({
base: function (next) {
db.getObject('group:' + groupName, next);
},
members: function (next) {
if (options.truncateUserList) {
stop = (parseInt(options.userListCount, 10) || 4) - 1;
}
Groups.getOwnersAndMembers(groupName, options.uid, 0, stop, next);
},
pending: function (next) {
Groups.getUsersFromSet('group:' + groupName + ':pending', next);
},
invited: function (next) {
Groups.getUsersFromSet('group:' + groupName + ':invited', next);
},
isMember: async.apply(Groups.isMember, options.uid, groupName),
isPending: async.apply(Groups.isPending, options.uid, groupName),
isInvited: async.apply(Groups.isInvited, options.uid, groupName),
isOwner: async.apply(Groups.ownership.isOwner, options.uid, groupName),
}, next);
},
function (_results, next) {
results = _results;
if (!results.base) {
return callback(new Error('[[error:no-group]]'));
}
plugins.fireHook('filter:parse.raw', results.base.description, next);
},
function (descriptionParsed, next) {
var groupData = results.base;
Groups.escapeGroupData(groupData);
results.base['cover:url'] = results.base['cover:url'] || require('./coverPhoto').getDefaultGroupCover(groupName);
results.base['cover:position'] = validator.escape(String(results.base['cover:position'] || '50% 50%'));
results.base.labelColor = validator.escape(String(results.base.labelColor || '#000000'));
results.base.icon = validator.escape(String(results.base.icon || ''));
groupData.descriptionParsed = descriptionParsed;
groupData.userTitleEnabled = groupData.userTitleEnabled ? !!parseInt(groupData.userTitleEnabled, 10) : true;
groupData.createtimeISO = utils.toISOString(groupData.createtime);
groupData.members = results.members;
groupData.membersNextStart = stop + 1;
groupData.pending = results.pending.filter(Boolean);
groupData.invited = results.invited.filter(Boolean);
groupData.deleted = !!parseInt(groupData.deleted, 10);
groupData.hidden = !!parseInt(groupData.hidden, 10);
groupData.system = !!parseInt(groupData.system, 10);
groupData.memberCount = parseInt(groupData.memberCount, 10);
groupData.private = (groupData.private === null || groupData.private === undefined) ? true : !!parseInt(groupData.private, 10);
groupData.disableJoinRequests = parseInt(groupData.disableJoinRequests, 10) === 1;
groupData.isMember = results.isMember;
groupData.isPending = results.isPending;
groupData.isInvited = results.isInvited;
groupData.isOwner = results.isOwner;
groupData['cover:url'] = groupData['cover:url'] || require('./coverPhoto').getDefaultGroupCover(groupName);
groupData['cover:position'] = validator.escape(String(groupData['cover:position'] || '50% 50%'));
groupData.labelColor = validator.escape(String(groupData.labelColor || '#000000'));
groupData.icon = validator.escape(String(groupData.icon || ''));
plugins.fireHook('filter:parse.raw', results.base.description, function (err, descriptionParsed) {
if (err) {
return callback(err);
}
plugins.fireHook('filter:group.get', { group: groupData }, next);
},
function (results, next) {
next(null, results.group);
},
], callback);
};
Groups.escapeGroupData(results.base);
results.base.descriptionParsed = descriptionParsed;
results.base.userTitleEnabled = results.base.userTitleEnabled ? !!parseInt(results.base.userTitleEnabled, 10) : true;
results.base.createtimeISO = utils.toISOString(results.base.createtime);
results.base.members = results.members;
results.base.membersNextStart = stop + 1;
results.base.pending = results.pending.filter(Boolean);
results.base.invited = results.invited.filter(Boolean);
results.base.deleted = !!parseInt(results.base.deleted, 10);
results.base.hidden = !!parseInt(results.base.hidden, 10);
results.base.system = !!parseInt(results.base.system, 10);
results.base.memberCount = parseInt(results.base.memberCount, 10);
results.base.private = (results.base.private === null || results.base.private === undefined) ? true : !!parseInt(results.base.private, 10);
results.base.disableJoinRequests = parseInt(results.base.disableJoinRequests, 10) === 1;
results.base.isMember = results.isMember;
results.base.isPending = results.isPending;
results.base.isInvited = results.isInvited;
results.base.isOwner = results.isOwner;
plugins.fireHook('filter:group.get', { group: results.base }, function (err, data) {
callback(err, data ? data.group : null);
});
});
});
};
Groups.getOwners = function (groupName, callback) {
db.getSetMembers('group:' + groupName + ':owners', callback);
};
Groups.getOwnersAndMembers = function (groupName, uid, start, stop, callback) {
async.parallel({
owners: function (next) {
async.waterfall([
function (next) {
db.getSetMembers('group:' + groupName + ':owners', next);
},
function (uids, next) {
user.getUsers(uids, uid, next);
},
], next);
},
members: function (next) {
user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop, next);
},
}, function (err, results) {
if (err) {
return callback(err);
}
Groups.getOwners = function (groupName, callback) {
db.getSetMembers('group:' + groupName + ':owners', callback);
};
Groups.getOwnersAndMembers = function (groupName, uid, start, stop, callback) {
async.waterfall([
function (next) {
async.parallel({
owners: function (next) {
async.waterfall([
function (next) {
db.getSetMembers('group:' + groupName + ':owners', next);
},
function (uids, next) {
user.getUsers(uids, uid, next);
},
], next);
},
members: function (next) {
user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop, next);
},
}, next);
},
function (results, next) {
var ownerUids = [];
results.owners.forEach(function (user) {
if (user) {
@@ -228,225 +210,94 @@ var utils = require('../public/src/utils');
});
results.members = results.owners.concat(results.members);
callback(null, results.members);
});
};
next(null, results.members);
},
], callback);
};
Groups.escapeGroupData = function (group) {
if (group) {
group.nameEncoded = encodeURIComponent(group.name);
group.displayName = validator.escape(String(group.name));
group.description = validator.escape(String(group.description || ''));
group.userTitle = validator.escape(String(group.userTitle || '')) || group.displayName;
}
};
Groups.escapeGroupData = function (group) {
if (group) {
group.nameEncoded = encodeURIComponent(group.name);
group.displayName = validator.escape(String(group.name));
group.description = validator.escape(String(group.description || ''));
group.userTitle = validator.escape(String(group.userTitle || '')) || group.displayName;
}
};
Groups.getByGroupslug = function (slug, options, callback) {
db.getObjectField('groupslug:groupname', slug, function (err, groupName) {
if (err) {
return callback(err);
} else if (!groupName) {
return callback(new Error('[[error:no-group]]'));
Groups.getByGroupslug = function (slug, options, callback) {
async.waterfall([
function (next) {
db.getObjectField('groupslug:groupname', slug, next);
},
function (groupName, next) {
if (!groupName) {
return next(new Error('[[error:no-group]]'));
}
Groups.get(groupName, options, next);
},
], callback);
};
Groups.get(groupName, options, callback);
Groups.getGroupNameByGroupSlug = function (slug, callback) {
db.getObjectField('groupslug:groupname', slug, callback);
};
Groups.isPrivate = function (groupName, callback) {
isFieldOn(groupName, 'private', callback);
};
Groups.isHidden = function (groupName, callback) {
isFieldOn(groupName, 'hidden', callback);
};
function isFieldOn(groupName, field, callback) {
async.waterfall([
function (next) {
db.getObjectField('group:' + groupName, field, next);
},
function (value, next) {
next(null, parseInt(value, 10) === 1);
},
], callback);
}
Groups.exists = function (name, callback) {
if (Array.isArray(name)) {
var slugs = name.map(function (groupName) {
return utils.slugify(groupName);
});
};
Groups.getGroupNameByGroupSlug = function (slug, callback) {
db.getObjectField('groupslug:groupname', slug, callback);
};
Groups.getGroupFields = function (groupName, fields, callback) {
Groups.getMultipleGroupFields([groupName], fields, function (err, groups) {
callback(err, groups ? groups[0] : null);
});
};
Groups.getMultipleGroupFields = function (groups, fields, callback) {
db.getObjectsFields(groups.map(function (group) {
return 'group:' + group;
}), fields, callback);
};
Groups.setGroupField = function (groupName, field, value, callback) {
db.setObjectField('group:' + groupName, field, value, function (err) {
if (err) {
return callback(err);
}
plugins.fireHook('action:group.set', { field: field, value: value, type: 'set' });
callback();
});
};
Groups.isPrivate = function (groupName, callback) {
db.getObjectField('group:' + groupName, 'private', function (err, isPrivate) {
if (err) {
return callback(err);
}
callback(null, parseInt(isPrivate, 10) !== 0);
});
};
Groups.isHidden = function (groupName, callback) {
db.getObjectField('group:' + groupName, 'hidden', function (err, isHidden) {
if (err) {
return callback(err);
}
callback(null, parseInt(isHidden, 10) === 1);
});
};
Groups.exists = function (name, callback) {
if (Array.isArray(name)) {
var slugs = name.map(function (groupName) {
return utils.slugify(groupName);
});
async.parallel([
function (next) {
next(null, slugs.map(function (slug) {
return ephemeralGroups.indexOf(slug) !== -1;
}));
},
async.apply(db.isSortedSetMembers, 'groups:createtime', name),
], function (err, results) {
if (err) {
return callback(err);
}
callback(null, name.map(function (n, index) {
return results[0][index] || results[1][index];
async.parallel([
function (next) {
next(null, slugs.map(function (slug) {
return Groups.ephemeralGroups.indexOf(slug) !== -1;
}));
});
} else {
var slug = utils.slugify(name);
async.parallel([
function (next) {
next(null, ephemeralGroups.indexOf(slug) !== -1);
},
async.apply(db.isSortedSetMember, 'groups:createtime', name),
], function (err, results) {
callback(err, !err ? (results[0] || results[1]) : null);
});
}
};
Groups.existsBySlug = function (slug, callback) {
if (Array.isArray(slug)) {
db.isObjectFields('groupslug:groupname', slug, callback);
} else {
db.isObjectField('groupslug:groupname', slug, callback);
}
};
Groups.getLatestMemberPosts = function (groupName, max, uid, callback) {
async.waterfall([
function (next) {
Groups.getMembers(groupName, 0, -1, next);
},
function (uids, next) {
if (!Array.isArray(uids) || !uids.length) {
return callback(null, []);
}
var keys = uids.map(function (uid) {
return 'uid:' + uid + ':posts';
});
db.getSortedSetRevRange(keys, 0, max - 1, next);
},
function (pids, next) {
privileges.posts.filter('read', pids, uid, next);
},
function (pids, next) {
posts.getPostSummaryByPids(pids, uid, { stripTags: false }, next);
},
], callback);
};
Groups.getGroupData = function (groupName, callback) {
Groups.getGroupsData([groupName], function (err, groupsData) {
callback(err, Array.isArray(groupsData) && groupsData[0] ? groupsData[0] : null);
});
};
Groups.getGroupsData = function (groupNames, callback) {
if (!Array.isArray(groupNames) || !groupNames.length) {
return callback(null, []);
}
var keys = groupNames.map(function (groupName) {
return 'group:' + groupName;
});
var ephemeralIdx = groupNames.reduce(function (memo, cur, idx) {
if (ephemeralGroups.indexOf(cur) !== -1) {
memo.push(idx);
}
return memo;
}, []);
db.getObjects(keys, function (err, groupData) {
async.apply(db.isSortedSetMembers, 'groups:createtime', name),
], function (err, results) {
if (err) {
return callback(err);
}
if (ephemeralIdx.length) {
ephemeralIdx.forEach(function (idx) {
groupData[idx] = internals.getEphemeralGroup(groupNames[idx]);
});
}
groupData.forEach(function (group) {
if (group) {
Groups.escapeGroupData(group);
group.userTitleEnabled = group.userTitleEnabled ? parseInt(group.userTitleEnabled, 10) === 1 : true;
group.labelColor = validator.escape(String(group.labelColor || '#000000'));
group.icon = validator.escape(String(group.icon || ''));
group.createtimeISO = utils.toISOString(group.createtime);
group.hidden = parseInt(group.hidden, 10) === 1;
group.system = parseInt(group.system, 10) === 1;
group.private = (group.private === null || group.private === undefined) ? true : !!parseInt(group.private, 10);
group.disableJoinRequests = parseInt(group.disableJoinRequests, 10) === 1;
group['cover:url'] = group['cover:url'] || require('./coverPhoto').getDefaultGroupCover(group.name);
group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url'];
group['cover:position'] = validator.escape(String(group['cover:position'] || '50% 50%'));
}
});
plugins.fireHook('filter:groups.get', { groups: groupData }, function (err, data) {
callback(err, data ? data.groups : null);
});
callback(null, name.map(function (n, index) {
return results[0][index] || results[1][index];
}));
});
};
Groups.getUserGroups = function (uids, callback) {
Groups.getUserGroupsFromSet('groups:visible:createtime', uids, callback);
};
Groups.getUserGroupsFromSet = function (set, uids, callback) {
async.waterfall([
} else {
var slug = utils.slugify(name);
async.parallel([
function (next) {
db.getSortedSetRevRange(set, 0, -1, next);
next(null, Groups.ephemeralGroups.indexOf(slug) !== -1);
},
function (groupNames, next) {
async.map(uids, function (uid, next) {
Groups.isMemberOfGroups(uid, groupNames, function (err, isMembers) {
if (err) {
return next(err);
}
async.apply(db.isSortedSetMember, 'groups:createtime', name),
], function (err, results) {
callback(err, !err ? (results[0] || results[1]) : null);
});
}
};
var memberOf = [];
isMembers.forEach(function (isMember, index) {
if (isMember) {
memberOf.push(groupNames[index]);
}
});
Groups.getGroupsData(memberOf, next);
});
}, next);
},
], callback);
};
}(module.exports));
Groups.existsBySlug = function (slug, callback) {
if (Array.isArray(slug)) {
db.isObjectFields('groupslug:groupname', slug, callback);
} else {
db.isObjectField('groupslug:groupname', slug, callback);
}
};

93
src/groups/data.js Normal file
View File

@@ -0,0 +1,93 @@
'use strict';
var async = require('async');
var validator = require('validator');
var db = require('../database');
var plugins = require('../plugins');
var utils = require('../../public/src/utils');
module.exports = function (Groups) {
Groups.getGroupsData = function (groupNames, callback) {
if (!Array.isArray(groupNames) || !groupNames.length) {
return callback(null, []);
}
var keys = groupNames.map(function (groupName) {
return 'group:' + groupName;
});
var ephemeralIdx = groupNames.reduce(function (memo, cur, idx) {
if (Groups.ephemeralGroups.indexOf(cur) !== -1) {
memo.push(idx);
}
return memo;
}, []);
async.waterfall([
function (next) {
db.getObjects(keys, next);
},
function (groupData, next) {
if (ephemeralIdx.length) {
ephemeralIdx.forEach(function (idx) {
groupData[idx] = Groups.getEphemeralGroup(groupNames[idx]);
});
}
groupData.forEach(function (group) {
if (group) {
Groups.escapeGroupData(group);
group.userTitleEnabled = group.userTitleEnabled ? parseInt(group.userTitleEnabled, 10) === 1 : true;
group.labelColor = validator.escape(String(group.labelColor || '#000000'));
group.icon = validator.escape(String(group.icon || ''));
group.createtimeISO = utils.toISOString(group.createtime);
group.hidden = parseInt(group.hidden, 10) === 1;
group.system = parseInt(group.system, 10) === 1;
group.private = (group.private === null || group.private === undefined) ? true : !!parseInt(group.private, 10);
group.disableJoinRequests = parseInt(group.disableJoinRequests, 10) === 1;
group['cover:url'] = group['cover:url'] || require('../coverPhoto').getDefaultGroupCover(group.name);
group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url'];
group['cover:position'] = validator.escape(String(group['cover:position'] || '50% 50%'));
}
});
plugins.fireHook('filter:groups.get', { groups: groupData }, next);
},
function (results, next) {
next(null, results.groups);
},
], callback);
};
Groups.getGroupData = function (groupName, callback) {
Groups.getGroupsData([groupName], function (err, groupsData) {
callback(err, Array.isArray(groupsData) && groupsData[0] ? groupsData[0] : null);
});
};
Groups.getGroupFields = function (groupName, fields, callback) {
Groups.getMultipleGroupFields([groupName], fields, function (err, groups) {
callback(err, groups ? groups[0] : null);
});
};
Groups.getMultipleGroupFields = function (groups, fields, callback) {
db.getObjectsFields(groups.map(function (group) {
return 'group:' + group;
}), fields, callback);
};
Groups.setGroupField = function (groupName, field, value, callback) {
async.waterfall([
function (next) {
db.setObjectField('group:' + groupName, field, value, next);
},
function (next) {
plugins.fireHook('action:group.set', { field: field, value: value, type: 'set' });
next();
},
], callback);
};
};

View File

@@ -413,32 +413,33 @@ module.exports = function (Groups) {
};
Groups.getMemberCount = function (groupName, callback) {
db.getObjectField('group:' + groupName, 'memberCount', function (err, count) {
if (err) {
return callback(err);
}
callback(null, parseInt(count, 10));
});
async.waterfall([
function (next) {
db.getObjectField('group:' + groupName, 'memberCount', next);
},
function (count, next) {
next(null, parseInt(count, 10));
},
], callback);
};
Groups.isMemberOfGroupList = function (uid, groupListKey, callback) {
db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, function (err, groupNames) {
if (err) {
return callback(err);
}
groupNames = Groups.internals.removeEphemeralGroups(groupNames);
if (groupNames.length === 0) {
return callback(null, false);
}
Groups.isMemberOfGroups(uid, groupNames, function (err, isMembers) {
if (err) {
return callback(err);
async.waterfall([
function (next) {
db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, next);
},
function (groupNames, next) {
groupNames = Groups.removeEphemeralGroups(groupNames);
if (groupNames.length === 0) {
return callback(null, false);
}
callback(null, isMembers.indexOf(true) !== -1);
});
});
Groups.isMemberOfGroups(uid, groupNames, next);
},
function (isMembers, next) {
next(null, isMembers.indexOf(true) !== -1);
},
], callback);
};
Groups.isMemberOfGroupsList = function (uid, groupListKeys, callback) {
@@ -446,19 +447,20 @@ module.exports = function (Groups) {
return 'group:' + groupName + ':members';
});
db.getSortedSetsMembers(sets, function (err, members) {
if (err) {
return callback(err);
}
var uniqueGroups = _.unique(_.flatten(members));
uniqueGroups = Groups.internals.removeEphemeralGroups(uniqueGroups);
Groups.isMemberOfGroups(uid, uniqueGroups, function (err, isMembers) {
if (err) {
return callback(err);
}
var uniqueGroups;
var members;
async.waterfall([
function (next) {
db.getSortedSetsMembers(sets, next);
},
function (_members, next) {
members = _members;
uniqueGroups = _.unique(_.flatten(members));
uniqueGroups = Groups.removeEphemeralGroups(uniqueGroups);
Groups.isMemberOfGroups(uid, uniqueGroups, next);
},
function (isMembers, next) {
var map = {};
uniqueGroups.forEach(function (groupName, index) {
@@ -474,62 +476,63 @@ module.exports = function (Groups) {
return false;
});
callback(null, result);
});
});
next(null, result);
},
], callback);
};
Groups.isMembersOfGroupList = function (uids, groupListKey, callback) {
db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, function (err, groupNames) {
if (err) {
return callback(err);
}
var groupNames;
var results = [];
uids.forEach(function () {
results.push(false);
});
var results = [];
uids.forEach(function () {
results.push(false);
});
async.waterfall([
function (next) {
db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, next);
},
function (_groupNames, next) {
groupNames = Groups.removeEphemeralGroups(_groupNames);
groupNames = Groups.internals.removeEphemeralGroups(groupNames);
if (groupNames.length === 0) {
return callback(null, results);
}
if (groupNames.length === 0) {
return callback(null, results);
}
async.each(groupNames, function (groupName, next) {
Groups.isMembers(uids, groupName, function (err, isMembers) {
if (err) {
return next(err);
}
async.map(groupNames, function (groupName, next) {
Groups.isMembers(uids, groupName, next);
}, next);
},
function (isGroupMembers, next) {
isGroupMembers.forEach(function (isMembers) {
results.forEach(function (isMember, index) {
if (!isMember && isMembers[index]) {
results[index] = true;
}
});
next();
});
}, function (err) {
callback(err, results);
});
});
next(null, results);
},
], callback);
};
Groups.isInvited = function (uid, groupName, callback) {
if (!uid) {
return callback(null, false);
return setImmediate(callback, null, false);
}
db.isSetMember('group:' + groupName + ':invited', uid, callback);
};
Groups.isPending = function (uid, groupName, callback) {
if (!uid) {
return callback(null, false);
return setImmediate(callback, null, false);
}
db.isSetMember('group:' + groupName + ':pending', uid, callback);
};
Groups.getPending = function (groupName, callback) {
if (!groupName) {
return callback(null, []);
return setImmediate(callback, null, []);
}
db.getSetMembers('group:' + groupName + ':pending', callback);
};

32
src/groups/posts.js Normal file
View File

@@ -0,0 +1,32 @@
'use strict';
var async = require('async');
var db = require('../database');
var privileges = require('../privileges');
var posts = require('../posts');
module.exports = function (Groups) {
Groups.getLatestMemberPosts = function (groupName, max, uid, callback) {
async.waterfall([
function (next) {
Groups.getMembers(groupName, 0, -1, next);
},
function (uids, next) {
if (!Array.isArray(uids) || !uids.length) {
return callback(null, []);
}
var keys = uids.map(function (uid) {
return 'uid:' + uid + ':posts';
});
db.getSortedSetRevRange(keys, 0, max - 1, next);
},
function (pids, next) {
privileges.posts.filter('read', pids, uid, next);
},
function (pids, next) {
posts.getPostSummaryByPids(pids, uid, { stripTags: false }, next);
},
], callback);
};
};

View File

@@ -16,7 +16,7 @@ module.exports = function (Groups) {
async.apply(db.getObjectValues, 'groupslug:groupname'),
function (groupNames, next) {
// Ephemeral groups and the registered-users groups are searchable
groupNames = Groups.getEphemeralGroups().concat(groupNames).concat('registered-users');
groupNames = Groups.ephemeralGroups.concat(groupNames).concat('registered-users');
groupNames = groupNames.filter(function (name) {
return name.toLowerCase().indexOf(query) !== -1 && name !== 'administrators' && !Groups.isPrivilegeGroup(name);
});

50
src/groups/user.js Normal file
View File

@@ -0,0 +1,50 @@
'use strict';
var async = require('async');
var db = require('../database');
var user = require('../user');
module.exports = function (Groups) {
Groups.getUsersFromSet = function (set, callback) {
async.waterfall([
function (next) {
db.getSetMembers(set, next);
},
function (uids, next) {
user.getUsersData(uids, next);
},
], callback);
};
Groups.getUserGroups = function (uids, callback) {
Groups.getUserGroupsFromSet('groups:visible:createtime', uids, callback);
};
Groups.getUserGroupsFromSet = function (set, uids, callback) {
async.waterfall([
function (next) {
db.getSortedSetRevRange(set, 0, -1, next);
},
function (groupNames, next) {
async.map(uids, function (uid, next) {
async.waterfall([
function (next) {
Groups.isMemberOfGroups(uid, groupNames, next);
},
function (isMembers, next) {
var memberOf = [];
isMembers.forEach(function (isMember, index) {
if (isMember) {
memberOf.push(groupNames[index]);
}
});
Groups.getGroupsData(memberOf, next);
},
], next);
}, next);
},
], callback);
};
};

View File

@@ -58,6 +58,7 @@ module.exports = function (Meta) {
'public/src/client/topic/fork.js',
'public/src/client/topic/move.js',
'public/src/client/topic/posts.js',
'public/src/client/topic/images.js',
'public/src/client/topic/postTools.js',
'public/src/client/topic/threadTools.js',
'public/src/client/categories.js',

View File

@@ -131,10 +131,10 @@ module.exports = function (Meta) {
}
});
if (!hasDescription) {
if (!hasDescription && Meta.config.description) {
meta.push({
name: 'description',
content: validator.escape(String(Meta.config.description || '')),
content: validator.escape(String(Meta.config.description)),
});
}
}

View File

@@ -114,7 +114,7 @@ module.exports = function (Meta) {
themeData['theme:templates'] = config.templates ? config.templates : '';
themeData['theme:src'] = '';
db.setObject('config', themeData, next);
Meta.configs.setMultiple(themeData, next);
// Re-set the themes path (for when NodeBB is reloaded)
Meta.themes.setPath(config);
@@ -125,7 +125,10 @@ module.exports = function (Meta) {
break;
case 'bootswatch':
Meta.configs.set('theme:src', data.src, callback);
Meta.configs.setMultiple({
'theme:src': data.src,
bootswatchSkin: data.id.toLowerCase(),
}, callback);
break;
}
};

View File

@@ -42,7 +42,6 @@ module.exports = function (middleware) {
middleware.renderHeader = function (req, res, data, callback) {
var registrationType = meta.config.registrationType || 'normal';
var templateValues = {
bootswatchCSS: meta.config['theme:src'],
title: meta.config.title || '',
description: meta.config.description || '',
'cache-buster': meta.config['cache-buster'] || '',
@@ -117,9 +116,7 @@ module.exports = function (middleware) {
results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1;
results.user.isEmailConfirmSent = !!results.isEmailConfirmSent;
if (res.locals.config && parseInt(meta.config.disableCustomUserSkins, 10) !== 1 && res.locals.config.bootswatchSkin !== 'default') {
templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + res.locals.config.bootswatchSkin + '/bootstrap.min.css';
}
setBootswatchCSS(templateValues, res.locals.config);
templateValues.browserTitle = controllers.helpers.buildTitle(data.title);
templateValues.navigation = results.navigation;
@@ -191,5 +188,21 @@ module.exports = function (middleware) {
return title;
}
function setBootswatchCSS(obj, config) {
if (config && config.bootswatchSkin !== 'default') {
var skinToUse = '';
if (parseInt(meta.config.disableCustomUserSkins, 10) !== 1) {
skinToUse = config.bootswatchSkin;
} else if (meta.config.bootswatchSkin !== 'default') {
skinToUse = meta.config.bootswatchSkin;
}
if (skinToUse) {
obj.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + skinToUse + '/bootstrap.min.css';
}
}
}
};

View File

@@ -53,22 +53,24 @@ middleware.ensureSelfOrGlobalPrivilege = function (req, res, next) {
The "self" part of this middleware hinges on you having used
middleware.exposeUid prior to invoking this middleware.
*/
if (req.user) {
if (req.user.uid === res.locals.uid) {
return next();
}
user.isAdminOrGlobalMod(req.uid, function (err, ok) {
if (err) {
return next(err);
} else if (ok) {
return next();
async.waterfall([
function (next) {
if (!req.uid) {
return setImmediate(next, null, false);
}
controllers.helpers.notAllowed(req, res);
});
} else {
controllers.helpers.notAllowed(req, res);
}
if (req.uid === parseInt(res.locals.uid, 10)) {
return setImmediate(next, null, true);
}
user.isAdminOrGlobalMod(req.uid, next);
},
function (isAdminOrGlobalMod, next) {
if (!isAdminOrGlobalMod) {
return controllers.helpers.notAllowed(req, res);
}
next();
},
], next);
};
middleware.ensureSelfOrPrivileged = function (req, res, next) {

View File

@@ -96,7 +96,7 @@ module.exports = function (privileges) {
return groupName.indexOf(':privileges:') === -1 && uniqueGroups.indexOf(groupName) !== -1;
});
groupNames = groups.getEphemeralGroups().concat(groupNames);
groupNames = groups.ephemeralGroups.concat(groupNames);
var registeredUsersIndex = groupNames.indexOf('registered-users');
if (registeredUsersIndex !== -1) {
groupNames.splice(0, 0, groupNames.splice(registeredUsersIndex, 1)[0]);
@@ -155,34 +155,36 @@ module.exports = function (privileges) {
privileges.categories.get = function (cid, uid, callback) {
var privs = ['topics:create', 'topics:read', 'read'];
async.parallel({
privileges: function (next) {
helpers.isUserAllowedTo(privs, uid, cid, next);
async.waterfall([
function (next) {
async.parallel({
privileges: function (next) {
helpers.isUserAllowedTo(privs, uid, cid, next);
},
isAdministrator: function (next) {
user.isAdministrator(uid, next);
},
isModerator: function (next) {
user.isModerator(uid, cid, next);
},
}, next);
},
isAdministrator: function (next) {
user.isAdministrator(uid, next);
},
isModerator: function (next) {
user.isModerator(uid, cid, next);
},
}, function (err, results) {
if (err) {
return callback(err);
}
var privData = _.object(privs, results.privileges);
var isAdminOrMod = results.isAdministrator || results.isModerator;
function (results, next) {
var privData = _.object(privs, results.privileges);
var isAdminOrMod = results.isAdministrator || results.isModerator;
plugins.fireHook('filter:privileges.categories.get', {
'topics:create': privData['topics:create'] || isAdminOrMod,
'topics:read': privData['topics:read'] || isAdminOrMod,
read: privData.read || isAdminOrMod,
cid: cid,
uid: uid,
editable: isAdminOrMod,
view_deleted: isAdminOrMod,
isAdminOrMod: isAdminOrMod,
}, callback);
});
plugins.fireHook('filter:privileges.categories.get', {
'topics:create': privData['topics:create'] || isAdminOrMod,
'topics:read': privData['topics:read'] || isAdminOrMod,
read: privData.read || isAdminOrMod,
cid: cid,
uid: uid,
editable: isAdminOrMod,
view_deleted: isAdminOrMod,
isAdminOrMod: isAdminOrMod,
}, next);
},
], callback);
};
privileges.categories.isAdminOrMod = function (cid, uid, callback) {
@@ -213,29 +215,29 @@ module.exports = function (privileges) {
return callback(null, false);
}
categories.getCategoryField(cid, 'disabled', function (err, disabled) {
if (err) {
return callback(err);
}
if (parseInt(disabled, 10) === 1) {
return callback(null, false);
}
helpers.some([
function (next) {
helpers.isUserAllowedTo(privilege, uid, [cid], function (err, results) {
next(err, Array.isArray(results) && results.length ? results[0] : false);
});
},
function (next) {
user.isModerator(uid, cid, next);
},
function (next) {
user.isAdministrator(uid, next);
},
], callback);
});
async.waterfall([
function (next) {
categories.getCategoryField(cid, 'disabled', next);
},
function (disabled, next) {
if (parseInt(disabled, 10) === 1) {
return callback(null, false);
}
helpers.some([
function (next) {
helpers.isUserAllowedTo(privilege, uid, [cid], function (err, results) {
next(err, Array.isArray(results) && results.length ? results[0] : false);
});
},
function (next) {
user.isModerator(uid, cid, next);
},
function (next) {
user.isAdministrator(uid, next);
},
], next);
},
], callback);
};
privileges.categories.filterCids = function (privilege, cids, uid, callback) {
@@ -247,18 +249,19 @@ module.exports = function (privileges) {
return array.indexOf(cid) === index;
});
privileges.categories.getBase(privilege, cids, uid, function (err, results) {
if (err) {
return callback(err);
}
async.waterfall([
function (next) {
privileges.categories.getBase(privilege, cids, uid, next);
},
function (results, next) {
cids = cids.filter(function (cid, index) {
return !results.categories[index].disabled &&
(results.allowedTo[index] || results.isAdmin || results.isModerators[index]);
});
cids = cids.filter(function (cid, index) {
return !results.categories[index].disabled &&
(results.allowedTo[index] || results.isAdmin || results.isModerators[index]);
});
callback(null, cids.filter(Boolean));
});
next(null, cids.filter(Boolean));
},
], callback);
};
privileges.categories.getBase = function (privilege, cids, uid, callback) {
@@ -287,26 +290,27 @@ module.exports = function (privileges) {
return array.indexOf(uid) === index;
});
async.parallel({
allowedTo: function (next) {
helpers.isUsersAllowedTo(privilege, uids, cid, next);
async.waterfall([
function (next) {
async.parallel({
allowedTo: function (next) {
helpers.isUsersAllowedTo(privilege, uids, cid, next);
},
isModerators: function (next) {
user.isModerator(uids, cid, next);
},
isAdmin: function (next) {
user.isAdministrator(uids, next);
},
}, next);
},
isModerators: function (next) {
user.isModerator(uids, cid, next);
function (results, next) {
uids = uids.filter(function (uid, index) {
return results.allowedTo[index] || results.isModerators[index] || results.isAdmin[index];
});
next(null, uids);
},
isAdmin: function (next) {
user.isAdministrator(uids, next);
},
}, function (err, results) {
if (err) {
return callback(err);
}
uids = uids.filter(function (uid, index) {
return results.allowedTo[index] || results.isModerators[index] || results.isAdmin[index];
});
callback(null, uids);
});
], callback);
};
privileges.categories.give = function (privileges, cid, groupName, callback) {
@@ -324,23 +328,24 @@ module.exports = function (privileges) {
}
privileges.categories.canMoveAllTopics = function (currentCid, targetCid, uid, callback) {
async.parallel({
isAdministrator: function (next) {
user.isAdministrator(uid, next);
async.waterfall([
function (next) {
async.parallel({
isAdministrator: function (next) {
user.isAdministrator(uid, next);
},
moderatorOfCurrent: function (next) {
user.isModerator(uid, currentCid, next);
},
moderatorOfTarget: function (next) {
user.isModerator(uid, targetCid, next);
},
}, next);
},
moderatorOfCurrent: function (next) {
user.isModerator(uid, currentCid, next);
function (results, next) {
next(null, results.isAdministrator || (results.moderatorOfCurrent && results.moderatorOfTarget));
},
moderatorOfTarget: function (next) {
user.isModerator(uid, targetCid, next);
},
}, function (err, results) {
if (err) {
return callback(err);
}
callback(null, results.isAdministrator || (results.moderatorOfCurrent && results.moderatorOfTarget));
});
], callback);
};
privileges.categories.userPrivileges = function (cid, uid, callback) {

View File

@@ -11,17 +11,17 @@ module.exports = function (app, middleware, controllers) {
router.get('/config', middleware.applyCSRF, controllers.api.getConfig);
router.get('/widgets/render', controllers.api.renderWidgets);
router.get('/me', middleware.checkGlobalPrivacySettings, controllers.api.getCurrentUser);
router.get('/user/uid/:uid', middleware.checkGlobalPrivacySettings, controllers.api.getUserByUID);
router.get('/user/username/:username', middleware.checkGlobalPrivacySettings, controllers.api.getUserByUsername);
router.get('/user/email/:email', middleware.checkGlobalPrivacySettings, controllers.api.getUserByEmail);
router.get('/me', middleware.checkGlobalPrivacySettings, controllers.user.getCurrentUser);
router.get('/user/uid/:uid', middleware.checkGlobalPrivacySettings, controllers.user.getUserByUID);
router.get('/user/username/:username', middleware.checkGlobalPrivacySettings, controllers.user.getUserByUsername);
router.get('/user/email/:email', middleware.checkGlobalPrivacySettings, controllers.user.getUserByEmail);
router.get('/:type/pid/:id', controllers.api.getObject);
router.get('/:type/tid/:id', controllers.api.getObject);
router.get('/:type/cid/:id', controllers.api.getObject);
router.get('/categories/:cid/moderators', controllers.api.getModerators);
router.get('/recent/posts/:term?', controllers.api.getRecentPosts);
router.get('/recent/posts/:term?', controllers.posts.getRecentPosts);
router.get('/unread/:filter?/total', middleware.authenticate, controllers.unread.unreadTotal);
router.get('/topic/teaser/:topic_id', controllers.topics.teaser);
router.get('/topic/pagination/:topic_id', controllers.topics.pagination);

View File

@@ -199,9 +199,9 @@ module.exports = function (app, middleware, hotswapIds) {
});
app.use(relativePath + '/assets/vendor/jquery/timeago/locales', middleware.processTimeagoLocales);
app.use(controllers.handle404);
app.use(controllers.handleURIErrors);
app.use(controllers.handleErrors);
app.use(controllers['404'].handle404);
app.use(controllers.errors.handleURIErrors);
app.use(controllers.errors.handleErrors);
// Add plugin routes
async.series([

View File

@@ -1,12 +1,14 @@
'use strict';
var async = require('async');
var topics = require('../topics');
var websockets = require('./index');
var user = require('../user');
var apiController = require('../controllers/api');
var socketHelpers = require('./helpers');
var SocketTopics = {};
var SocketTopics = module.exports;
require('./topics/unread')(SocketTopics);
require('./topics/move')(SocketTopics);
@@ -23,18 +25,19 @@ SocketTopics.post = function (socket, data, callback) {
data.req = websockets.reqFromSocket(socket);
data.timestamp = Date.now();
topics.post(data, function (err, result) {
if (err) {
return callback(err);
}
async.waterfall([
function (next) {
topics.post(data, next);
},
function (result, next) {
next(null, result.topicData);
callback(null, result.topicData);
socket.emit('event:new_post', { posts: [result.postData] });
socket.emit('event:new_topic', result.topicData);
socket.emit('event:new_post', { posts: [result.postData] });
socket.emit('event:new_topic', result.topicData);
socketHelpers.notifyNew(socket.uid, 'newTopic', { posts: [result.postData], topic: result.topicData });
});
socketHelpers.notifyNew(socket.uid, 'newTopic', { posts: [result.postData], topic: result.topicData });
},
], callback);
};
SocketTopics.postcount = function (socket, tid, callback) {
@@ -61,7 +64,7 @@ SocketTopics.createTopicFromPosts = function (socket, data, callback) {
};
SocketTopics.changeWatching = function (socket, data, callback) {
if (!data.tid || !data.type) {
if (!data || !data.tid || !data.type) {
return callback(new Error('[[error:invalid-data]]'));
}
var commands = ['follow', 'unfollow', 'ignore'];
@@ -90,20 +93,23 @@ SocketTopics.isFollowed = function (socket, tid, callback) {
};
SocketTopics.search = function (socket, data, callback) {
if (!data) {
return callback(new Error('[[error:invalid-data]]'));
}
topics.search(data.tid, data.term, callback);
};
SocketTopics.isModerator = function (socket, tid, callback) {
topics.getTopicField(tid, 'cid', function (err, cid) {
if (err) {
return callback(err);
}
user.isModerator(socket.uid, cid, callback);
});
async.waterfall([
function (next) {
topics.getTopicField(tid, 'cid', next);
},
function (cid, next) {
user.isModerator(socket.uid, cid, next);
},
], callback);
};
SocketTopics.getTopic = function (socket, tid, callback) {
apiController.getTopicData(tid, socket.uid, callback);
};
module.exports = SocketTopics;

View File

@@ -12,7 +12,7 @@ var meta = require('../meta');
var events = require('../events');
var emailer = require('../emailer');
var db = require('../database');
var apiController = require('../controllers/api');
var userController = require('../controllers/user');
var privileges = require('../privileges');
var SocketUser = {};
@@ -303,15 +303,15 @@ SocketUser.invite = function (socket, email, callback) {
};
SocketUser.getUserByUID = function (socket, uid, callback) {
apiController.getUserDataByField(socket.uid, 'uid', uid, callback);
userController.getUserDataByField(socket.uid, 'uid', uid, callback);
};
SocketUser.getUserByUsername = function (socket, username, callback) {
apiController.getUserDataByField(socket.uid, 'username', username, callback);
userController.getUserDataByField(socket.uid, 'username', username, callback);
};
SocketUser.getUserByEmail = function (socket, email, callback) {
apiController.getUserDataByField(socket.uid, 'email', email, callback);
userController.getUserDataByField(socket.uid, 'email', email, callback);
};
SocketUser.setModerationNote = function (socket, data, callback) {

View File

@@ -317,7 +317,7 @@ var social = require('./social');
term: term,
}, callback);
} else {
callback(new Error('no-plugins-available'), []);
callback(new Error('[[error:no-plugins-available]]'), []);
}
};
}(exports));

View File

@@ -330,7 +330,7 @@ module.exports = function (Topics) {
function check(item, min, max, minError, maxError, callback) {
// Trim and remove HTML (latter for composers that send in HTML, like redactor)
if (typeof item === 'string') {
item = S(item.trim()).stripTags().s;
item = S(item).stripTags().s.trim();
}
if (!item || item.length < parseInt(min, 10)) {

View File

@@ -6,388 +6,324 @@ var _ = require('underscore');
var groups = require('./groups');
var plugins = require('./plugins');
var db = require('./database');
var topics = require('./topics');
var privileges = require('./privileges');
var meta = require('./meta');
(function (User) {
User.email = require('./user/email');
User.notifications = require('./user/notifications');
User.reset = require('./user/reset');
User.digest = require('./user/digest');
var User = module.exports;
require('./user/data')(User);
require('./user/auth')(User);
require('./user/bans')(User);
require('./user/create')(User);
require('./user/posts')(User);
require('./user/topics')(User);
require('./user/categories')(User);
require('./user/follow')(User);
require('./user/profile')(User);
require('./user/admin')(User);
require('./user/delete')(User);
require('./user/settings')(User);
require('./user/search')(User);
require('./user/jobs')(User);
require('./user/picture')(User);
require('./user/approval')(User);
require('./user/invite')(User);
require('./user/password')(User);
require('./user/info')(User);
User.email = require('./user/email');
User.notifications = require('./user/notifications');
User.reset = require('./user/reset');
User.digest = require('./user/digest');
User.updateLastOnlineTime = function (uid, callback) {
callback = callback || function () {};
db.getObjectFields('user:' + uid, ['status', 'lastonline'], function (err, userData) {
var now = Date.now();
if (err || userData.status === 'offline' || now - parseInt(userData.lastonline, 10) < 300000) {
return callback(err);
}
User.setUserField(uid, 'lastonline', now, callback);
});
};
User.updateOnlineUsers = function (uid, callback) {
callback = callback || function () {};
require('./user/data')(User);
require('./user/auth')(User);
require('./user/bans')(User);
require('./user/create')(User);
require('./user/posts')(User);
require('./user/topics')(User);
require('./user/categories')(User);
require('./user/follow')(User);
require('./user/profile')(User);
require('./user/admin')(User);
require('./user/delete')(User);
require('./user/settings')(User);
require('./user/search')(User);
require('./user/jobs')(User);
require('./user/picture')(User);
require('./user/approval')(User);
require('./user/invite')(User);
require('./user/password')(User);
require('./user/info')(User);
require('./user/online')(User);
User.getUidsFromSet = function (set, start, stop, callback) {
if (set === 'users:online') {
var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1;
var now = Date.now();
async.waterfall([
function (next) {
db.sortedSetScore('users:online', uid, next);
},
function (userOnlineTime, next) {
if (now - parseInt(userOnlineTime, 10) < 300000) {
return callback();
}
db.sortedSetAdd('users:online', now, uid, next);
},
function (next) {
topics.pushUnreadCount(uid);
plugins.fireHook('action:user.online', { uid: uid, timestamp: now });
next();
},
], callback);
};
db.getSortedSetRevRangeByScore(set, start, count, '+inf', now - 300000, callback);
} else {
db.getSortedSetRevRange(set, start, stop, callback);
}
};
User.getUidsFromSet = function (set, start, stop, callback) {
if (set === 'users:online') {
var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1;
var now = Date.now();
db.getSortedSetRevRangeByScore(set, start, count, '+inf', now - 300000, callback);
} else {
db.getSortedSetRevRange(set, start, stop, callback);
}
};
User.getUsersFromSet = function (set, uid, start, stop, callback) {
async.waterfall([
function (next) {
User.getUidsFromSet(set, start, stop, next);
},
function (uids, next) {
User.getUsers(uids, uid, next);
},
], callback);
};
User.getUsersFromSet = function (set, uid, start, stop, callback) {
async.waterfall([
function (next) {
User.getUidsFromSet(set, start, stop, next);
},
function (uids, next) {
User.getUsers(uids, uid, next);
},
], callback);
};
User.getUsersWithFields = function (uids, fields, uid, callback) {
async.waterfall([
function (next) {
plugins.fireHook('filter:users.addFields', { fields: fields }, next);
},
function (data, next) {
data.fields = data.fields.filter(function (field, index, array) {
return array.indexOf(field) === index;
});
async.parallel({
userData: function (next) {
User.getUsersFields(uids, data.fields, next);
},
isAdmin: function (next) {
User.isAdministrator(uids, next);
},
}, next);
},
function (results, next) {
results.userData.forEach(function (user, index) {
if (user) {
user.status = User.getStatus(user);
user.administrator = results.isAdmin[index];
user.banned = parseInt(user.banned, 10) === 1;
user.banned_until = parseInt(user['banned:expire'], 10) || 0;
user.banned_until_readable = user.banned_until ? new Date(user.banned_until).toString() : 'Not Banned';
user['email:confirmed'] = parseInt(user['email:confirmed'], 10) === 1;
}
});
plugins.fireHook('filter:userlist.get', { users: results.userData, uid: uid }, next);
},
function (data, next) {
next(null, data.users);
},
], callback);
};
User.getUsers = function (uids, uid, callback) {
var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'flags',
'banned', 'banned:expire', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline'];
User.getUsersWithFields(uids, fields, uid, callback);
};
User.getStatus = function (userData) {
var isOnline = (Date.now() - parseInt(userData.lastonline, 10)) < 300000;
return isOnline ? (userData.status || 'online') : 'offline';
};
User.isOnline = function (uid, callback) {
if (Array.isArray(uid)) {
db.sortedSetScores('users:online', uid, function (err, lastonline) {
if (err) {
return callback(err);
}
var now = Date.now();
var isOnline = uid.map(function (uid, index) {
return now - lastonline[index] < 300000;
});
callback(null, isOnline);
User.getUsersWithFields = function (uids, fields, uid, callback) {
async.waterfall([
function (next) {
plugins.fireHook('filter:users.addFields', { fields: fields }, next);
},
function (data, next) {
data.fields = data.fields.filter(function (field, index, array) {
return array.indexOf(field) === index;
});
} else {
db.sortedSetScore('users:online', uid, function (err, lastonline) {
if (err) {
return callback(err);
async.parallel({
userData: function (next) {
User.getUsersFields(uids, data.fields, next);
},
isAdmin: function (next) {
User.isAdministrator(uids, next);
},
}, next);
},
function (results, next) {
results.userData.forEach(function (user, index) {
if (user) {
user.status = User.getStatus(user);
user.administrator = results.isAdmin[index];
user.banned = parseInt(user.banned, 10) === 1;
user.banned_until = parseInt(user['banned:expire'], 10) || 0;
user.banned_until_readable = user.banned_until ? new Date(user.banned_until).toString() : 'Not Banned';
user['email:confirmed'] = parseInt(user['email:confirmed'], 10) === 1;
}
var isOnline = Date.now() - parseInt(lastonline, 10) < 300000;
callback(null, isOnline);
});
}
};
plugins.fireHook('filter:userlist.get', { users: results.userData, uid: uid }, next);
},
function (data, next) {
next(null, data.users);
},
], callback);
};
User.exists = function (uid, callback) {
db.isSortedSetMember('users:joindate', uid, callback);
};
User.getUsers = function (uids, uid, callback) {
var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'flags',
'banned', 'banned:expire', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline'];
User.existsBySlug = function (userslug, callback) {
User.getUidByUserslug(userslug, function (err, exists) {
callback(err, !!exists);
});
};
User.getUsersWithFields(uids, fields, uid, callback);
};
User.getUidByUsername = function (username, callback) {
if (!username) {
return callback(null, 0);
}
db.sortedSetScore('username:uid', username, callback);
};
User.getStatus = function (userData) {
var isOnline = (Date.now() - parseInt(userData.lastonline, 10)) < 300000;
return isOnline ? (userData.status || 'online') : 'offline';
};
User.getUidsByUsernames = function (usernames, callback) {
db.sortedSetScores('username:uid', usernames, callback);
};
User.exists = function (uid, callback) {
db.isSortedSetMember('users:joindate', uid, callback);
};
User.getUidByUserslug = function (userslug, callback) {
if (!userslug) {
return callback(null, 0);
}
db.sortedSetScore('userslug:uid', userslug, callback);
};
User.existsBySlug = function (userslug, callback) {
User.getUidByUserslug(userslug, function (err, exists) {
callback(err, !!exists);
});
};
User.getUsernamesByUids = function (uids, callback) {
User.getUsersFields(uids, ['username'], function (err, users) {
if (err) {
return callback(err);
}
User.getUidByUsername = function (username, callback) {
if (!username) {
return callback(null, 0);
}
db.sortedSetScore('username:uid', username, callback);
};
User.getUidsByUsernames = function (usernames, callback) {
db.sortedSetScores('username:uid', usernames, callback);
};
User.getUidByUserslug = function (userslug, callback) {
if (!userslug) {
return callback(null, 0);
}
db.sortedSetScore('userslug:uid', userslug, callback);
};
User.getUsernamesByUids = function (uids, callback) {
async.waterfall([
function (next) {
User.getUsersFields(uids, ['username'], next);
},
function (users, next) {
users = users.map(function (user) {
return user.username;
});
callback(null, users);
});
};
next(null, users);
},
], callback);
};
User.getUsernameByUserslug = function (slug, callback) {
async.waterfall([
function (next) {
User.getUidByUserslug(slug, next);
},
function (uid, next) {
User.getUserField(uid, 'username', next);
},
], callback);
};
User.getUsernameByUserslug = function (slug, callback) {
async.waterfall([
function (next) {
User.getUidByUserslug(slug, next);
},
function (uid, next) {
User.getUserField(uid, 'username', next);
},
], callback);
};
User.getUidByEmail = function (email, callback) {
db.sortedSetScore('email:uid', email.toLowerCase(), callback);
};
User.getUidByEmail = function (email, callback) {
db.sortedSetScore('email:uid', email.toLowerCase(), callback);
};
User.getUidsByEmails = function (emails, callback) {
emails = emails.map(function (email) {
return email && email.toLowerCase();
});
db.sortedSetScores('email:uid', emails, callback);
};
User.getUidsByEmails = function (emails, callback) {
emails = emails.map(function (email) {
return email && email.toLowerCase();
});
db.sortedSetScores('email:uid', emails, callback);
};
User.getUsernameByEmail = function (email, callback) {
db.sortedSetScore('email:uid', email.toLowerCase(), function (err, uid) {
if (err) {
return callback(err);
User.getUsernameByEmail = function (email, callback) {
async.waterfall([
function (next) {
db.sortedSetScore('email:uid', email.toLowerCase(), next);
},
function (uid, next) {
User.getUserField(uid, 'username', next);
},
], callback);
};
User.isModerator = function (uid, cid, callback) {
privileges.users.isModerator(uid, cid, callback);
};
User.isModeratorOfAnyCategory = function (uid, callback) {
User.getModeratedCids(uid, function (err, cids) {
callback(err, Array.isArray(cids) ? !!cids.length : false);
});
};
User.isAdministrator = function (uid, callback) {
privileges.users.isAdministrator(uid, callback);
};
User.isGlobalModerator = function (uid, callback) {
privileges.users.isGlobalModerator(uid, callback);
};
User.isAdminOrGlobalMod = function (uid, callback) {
async.parallel({
isAdmin: async.apply(User.isAdministrator, uid),
isGlobalMod: async.apply(User.isGlobalModerator, uid),
}, function (err, results) {
callback(err, results ? (results.isAdmin || results.isGlobalMod) : false);
});
};
User.isAdminOrSelf = function (callerUid, uid, callback) {
isSelfOrMethod(callerUid, uid, User.isAdministrator, callback);
};
User.isAdminOrGlobalModOrSelf = function (callerUid, uid, callback) {
isSelfOrMethod(callerUid, uid, User.isAdminOrGlobalMod, callback);
};
function isSelfOrMethod(callerUid, uid, method, callback) {
if (parseInt(callerUid, 10) === parseInt(uid, 10)) {
return callback();
}
async.waterfall([
function (next) {
method(callerUid, next);
},
function (isPass, next) {
if (!isPass) {
return next(new Error('[[error:no-privileges]]'));
}
User.getUserField(uid, 'username', callback);
});
};
next();
},
], callback);
}
User.isModerator = function (uid, cid, callback) {
privileges.users.isModerator(uid, cid, callback);
};
User.getAdminsandGlobalMods = function (callback) {
async.waterfall([
function (next) {
async.parallel([
async.apply(groups.getMembers, 'administrators', 0, -1),
async.apply(groups.getMembers, 'Global Moderators', 0, -1),
], next);
},
function (results, next) {
User.getUsersData(_.union(results), next);
},
], callback);
};
User.isModeratorOfAnyCategory = function (uid, callback) {
User.getModeratedCids(uid, function (err, cids) {
callback(err, Array.isArray(cids) ? !!cids.length : false);
});
};
User.getAdminsandGlobalModsandModerators = function (callback) {
async.waterfall([
function (next) {
async.parallel([
async.apply(groups.getMembers, 'administrators', 0, -1),
async.apply(groups.getMembers, 'Global Moderators', 0, -1),
async.apply(User.getModeratorUids),
], next);
},
function (results, next) {
User.getUsersData(_.union.apply(_, results), next);
},
], callback);
};
User.isAdministrator = function (uid, callback) {
privileges.users.isAdministrator(uid, callback);
};
User.isGlobalModerator = function (uid, callback) {
privileges.users.isGlobalModerator(uid, callback);
};
User.isPrivileged = function (uid, callback) {
async.parallel([
async.apply(User.isAdministrator, uid),
async.apply(User.isGlobalModerator, uid),
async.apply(User.isModeratorOfAnyCategory, uid),
], function (err, results) {
callback(err, results ? results.some(Boolean) : false);
});
};
User.isAdminOrGlobalMod = function (uid, callback) {
async.parallel({
isAdmin: async.apply(User.isAdministrator, uid),
isGlobalMod: async.apply(User.isGlobalModerator, uid),
}, function (err, results) {
callback(err, results ? (results.isAdmin || results.isGlobalMod) : false);
});
};
User.isAdminOrSelf = function (callerUid, uid, callback) {
if (parseInt(callerUid, 10) === parseInt(uid, 10)) {
return callback();
}
User.isAdministrator(callerUid, function (err, isAdmin) {
if (err || !isAdmin) {
return callback(err || new Error('[[error:no-privileges]]'));
}
callback();
});
};
User.isAdminOrGlobalModOrSelf = function (callerUid, uid, callback) {
if (parseInt(callerUid, 10) === parseInt(uid, 10)) {
return callback();
}
User.isAdminOrGlobalMod(callerUid, function (err, isAdminOrGlobalMod) {
if (err || !isAdminOrGlobalMod) {
return callback(err || new Error('[[error:no-privileges]]'));
}
callback();
});
};
User.getAdminsandGlobalMods = function (callback) {
async.parallel({
admins: async.apply(groups.getMembers, 'administrators', 0, -1),
mods: async.apply(groups.getMembers, 'Global Moderators', 0, -1),
}, function (err, results) {
if (err) {
return callback(err);
}
var uids = results.admins.concat(results.mods).filter(function (uid, index, array) {
return uid && array.indexOf(uid) === index;
User.getModeratorUids = function (callback) {
async.waterfall([
async.apply(db.getSortedSetRange, 'categories:cid', 0, -1),
function (cids, next) {
var groupNames = cids.map(function (cid) {
return 'cid:' + cid + ':privileges:mods';
});
User.getUsersData(uids, callback);
});
};
User.getAdminsandGlobalModsandModerators = function (callback) {
async.parallel([
async.apply(groups.getMembers, 'administrators', 0, -1),
async.apply(groups.getMembers, 'Global Moderators', 0, -1),
async.apply(User.getModeratorUids),
], function (err, results) {
if (err) {
return callback(err);
groups.getMembersOfGroups(groupNames, next);
},
function (memberSets, next) {
next(null, _.union.apply(_, memberSets));
},
], callback);
};
User.getModeratedCids = function (uid, callback) {
var cids;
async.waterfall([
function (next) {
db.getSortedSetRange('categories:cid', 0, -1, next);
},
function (_cids, next) {
cids = _cids;
User.isModerator(uid, cids, next);
},
function (isMods, next) {
cids = cids.filter(function (cid, index) {
return cid && isMods[index];
});
next(null, cids);
},
], callback);
};
User.addInterstitials = function (callback) {
plugins.registerHook('core', {
hook: 'filter:register.interstitial',
method: function (data, callback) {
if (meta.config.termsOfUse && !data.userData.acceptTos) {
data.interstitials.push({
template: 'partials/acceptTos',
data: {
termsOfUse: meta.config.termsOfUse,
},
callback: function (userData, formData, next) {
if (formData['agree-terms'] === 'on') {
userData.acceptTos = true;
}
next(userData.acceptTos ? null : new Error('[[register:terms_of_use_error]]'));
},
});
}
User.getUsersData(_.union.apply(_, results), callback);
});
};
callback(null, data);
},
});
User.getModeratorUids = function (callback) {
async.waterfall([
async.apply(db.getSortedSetRange, 'categories:cid', 0, -1),
function (cids, next) {
var groupNames = cids.map(function (cid) {
return 'cid:' + cid + ':privileges:mods';
});
callback();
};
groups.getMembersOfGroups(groupNames, function (err, memberSets) {
if (err) {
return next(err);
}
next(null, _.union.apply(_, memberSets));
});
},
], callback);
};
User.getModeratedCids = function (uid, callback) {
var cids;
async.waterfall([
function (next) {
db.getSortedSetRange('categories:cid', 0, -1, next);
},
function (_cids, next) {
cids = _cids;
User.isModerator(uid, cids, next);
},
function (isMods, next) {
cids = cids.filter(function (cid, index) {
return cid && isMods[index];
});
next(null, cids);
},
], callback);
};
User.addInterstitials = function (callback) {
plugins.registerHook('core', {
hook: 'filter:register.interstitial',
method: function (data, callback) {
if (meta.config.termsOfUse && !data.userData.acceptTos) {
data.interstitials.push({
template: 'partials/acceptTos',
data: {
termsOfUse: meta.config.termsOfUse,
},
callback: function (userData, formData, next) {
if (formData['agree-terms'] === 'on') {
userData.acceptTos = true;
}
next(userData.acceptTos ? null : new Error('[[register:terms_of_use_error]]'));
},
});
}
callback(null, data);
},
});
callback();
};
}(exports));

View File

@@ -16,13 +16,7 @@ module.exports = function (User) {
};
User.getIPs = function (uid, stop, callback) {
db.getSortedSetRevRange('uid:' + uid + ':ip', 0, stop, function (err, ips) {
if (err) {
return callback(err);
}
callback(null, ips);
});
db.getSortedSetRevRange('uid:' + uid + ':ip', 0, stop, callback);
};
User.getUsersCSV = function (callback) {

View File

@@ -1,5 +1,6 @@
'use strict';
var async = require('async');
var validator = require('validator');
var nconf = require('nconf');
var winston = require('winston');
@@ -63,6 +64,7 @@ module.exports = function (User) {
addField('lastonline');
}
<<<<<<< HEAD
db.getObjectsFields(keys, fields, function (err, users) {
if (err) {
return callback(err);
@@ -74,6 +76,16 @@ module.exports = function (User) {
modifyUserData(users, fieldsToRemove, callback);
});
=======
async.waterfall([
function (next) {
db.getObjectsFields(keys, fields, next);
},
function (users, next) {
modifyUserData(users, fieldsToRemove, next);
},
], callback);
>>>>>>> master
};
User.getMultipleUserFields = function (uids, fields, callback) {
@@ -105,6 +117,7 @@ module.exports = function (User) {
return 'user:' + uid;
});
<<<<<<< HEAD
db.getObjects(keys, function (err, users) {
if (err) {
return callback(err);
@@ -116,6 +129,16 @@ module.exports = function (User) {
modifyUserData(users, [], callback);
});
=======
async.waterfall([
function (next) {
db.getObjects(keys, next);
},
function (users, next) {
modifyUserData(users, [], next);
},
], callback);
>>>>>>> master
};
function modifyUserData(users, fieldsToRemove, callback) {
@@ -178,51 +201,53 @@ module.exports = function (User) {
User.setUserField = function (uid, field, value, callback) {
callback = callback || function () {};
db.setObjectField('user:' + uid, field, value, function (err) {
if (err) {
return callback(err);
}
plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: 'set' });
callback();
});
async.waterfall([
function (next) {
db.setObjectField('user:' + uid, field, value, next);
},
function (next) {
plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: 'set' });
next();
},
], callback);
};
User.setUserFields = function (uid, data, callback) {
callback = callback || function () {};
db.setObject('user:' + uid, data, function (err) {
if (err) {
return callback(err);
}
for (var field in data) {
if (data.hasOwnProperty(field)) {
plugins.fireHook('action:user.set', { uid: uid, field: field, value: data[field], type: 'set' });
async.waterfall([
function (next) {
db.setObject('user:' + uid, data, next);
},
function (next) {
for (var field in data) {
if (data.hasOwnProperty(field)) {
plugins.fireHook('action:user.set', { uid: uid, field: field, value: data[field], type: 'set' });
}
}
}
callback();
});
next();
},
], callback);
};
User.incrementUserFieldBy = function (uid, field, value, callback) {
callback = callback || function () {};
db.incrObjectFieldBy('user:' + uid, field, value, function (err, value) {
if (err) {
return callback(err);
}
plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: 'increment' });
callback(null, value);
});
incrDecrUserFieldBy(uid, field, value, 'increment', callback);
};
User.decrementUserFieldBy = function (uid, field, value, callback) {
callback = callback || function () {};
db.incrObjectFieldBy('user:' + uid, field, -value, function (err, value) {
if (err) {
return callback(err);
}
plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: 'decrement' });
callback(null, value);
});
incrDecrUserFieldBy(uid, field, -value, 'decrement', callback);
};
function incrDecrUserFieldBy(uid, field, value, type, callback) {
callback = callback || function () {};
async.waterfall([
function (next) {
db.incrObjectFieldBy('user:' + uid, field, value, next);
},
function (value, next) {
plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: type });
next(null, value);
},
], callback);
}
};

70
src/user/online.js Normal file
View File

@@ -0,0 +1,70 @@
'use strict';
var async = require('async');
var db = require('../database');
var topics = require('../topics');
var plugins = require('../plugins');
module.exports = function (User) {
User.updateLastOnlineTime = function (uid, callback) {
callback = callback || function () {};
db.getObjectFields('user:' + uid, ['status', 'lastonline'], function (err, userData) {
var now = Date.now();
if (err || userData.status === 'offline' || now - parseInt(userData.lastonline, 10) < 300000) {
return callback(err);
}
User.setUserField(uid, 'lastonline', now, callback);
});
};
User.updateOnlineUsers = function (uid, callback) {
callback = callback || function () {};
var now = Date.now();
async.waterfall([
function (next) {
db.sortedSetScore('users:online', uid, next);
},
function (userOnlineTime, next) {
if (now - parseInt(userOnlineTime, 10) < 300000) {
return callback();
}
db.sortedSetAdd('users:online', now, uid, next);
},
function (next) {
topics.pushUnreadCount(uid);
plugins.fireHook('action:user.online', { uid: uid, timestamp: now });
next();
},
], callback);
};
User.isOnline = function (uid, callback) {
var now = Date.now();
async.waterfall([
function (next) {
if (Array.isArray(uid)) {
db.sortedSetScores('users:online', uid, next);
} else {
db.sortedSetScore('users:online', uid, next);
}
},
function (lastonline, next) {
function checkOnline(lastonline) {
return now - lastonline < 300000;
}
var isOnline;
if (Array.isArray(uid)) {
isOnline = uid.map(function (uid, index) {
return checkOnline(lastonline[index]);
});
} else {
isOnline = checkOnline(lastonline);
}
next(null, isOnline);
},
], callback);
};
};

View File

@@ -50,12 +50,7 @@ module.exports = function (User) {
}, next);
},
function (image, next) {
User.setUserFields(uid, {
uploadedpicture: image.url,
picture: image.url,
}, function (err) {
next(err, image);
});
next(null, image);
},
], callback);
};

View File

@@ -18,6 +18,14 @@ module.exports = function (User) {
var updateUid = data.uid;
var oldData;
if (data.aboutme !== undefined && data.aboutme.length > meta.config.maximumAboutMeLength) {
return callback(new Error('[[error:about-me-too-long, ' + meta.config.maximumAboutMeLength + ']]'));
}
if (data.signature !== undefined && data.signature.length > meta.config.maximumSignatureLength) {
return callback(new Error('[[error:signature-too-long, ' + meta.config.maximumSignatureLength + ']]'));
}
async.waterfall([
function (next) {
plugins.fireHook('filter:user.updateProfile', { uid: uid, data: data, fields: fields }, next);
@@ -27,8 +35,6 @@ module.exports = function (User) {
data = data.data;
async.series([
async.apply(isAboutMeValid, data),
async.apply(isSignatureValid, data),
async.apply(isEmailAvailable, data, updateUid),
async.apply(isUsernameAvailable, data, updateUid),
async.apply(isGroupTitleValid, data),
@@ -68,22 +74,6 @@ module.exports = function (User) {
], callback);
};
function isAboutMeValid(data, callback) {
if (data.aboutme !== undefined && data.aboutme.length > meta.config.maximumAboutMeLength) {
callback(new Error('[[error:about-me-too-long, ' + meta.config.maximumAboutMeLength + ']]'));
} else {
callback();
}
}
function isSignatureValid(data, callback) {
if (data.signature !== undefined && data.signature.length > meta.config.maximumSignatureLength) {
callback(new Error('[[error:signature-too-long, ' + meta.config.maximumSignatureLength + ']]'));
} else {
callback();
}
}
function isEmailAvailable(data, uid, callback) {
if (!data.email) {
return callback();

View File

@@ -74,7 +74,7 @@ module.exports = function (User) {
settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1;
settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1;
settings.delayImageLoading = parseInt(getSetting(settings, 'delayImageLoading', 1), 10) === 1;
settings.bootswatchSkin = settings.bootswatchSkin || 'default';
settings.bootswatchSkin = settings.bootswatchSkin || meta.config.bootswatchSkin || 'default';
settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1;
callback(null, settings);

View File

@@ -36,13 +36,7 @@
<li data-plugin-id="{installed.id}" class="clearfix">
<div class="pull-right">
<button class="btn btn-default disabled"><i class="fa fa-exclamation-triangle"></i> [[admin/extend/plugins:plugin-item.unknown]]</button>
<<<<<<< HEAD
<button data-action="toggleInstall" data-installed="1" class="btn btn-danger"><i class="fa fa-trash-o"></i> Uninstall</button>
=======
<button data-action="toggleInstall" data-installed="1" class="btn btn-danger"><i class="fa fa-trash-o"></i> [[admin/extend/plugins:plugin-item.uninstall]]</button>
>>>>>>> `admin/extend` translations
</div>
<h2><strong>{installed.id}</strong></h2>

View File

@@ -61,21 +61,20 @@ module.exports.listen = function (callback) {
logger.init(app);
initializeNodeBB(function (err) {
if (err) {
return callback(err);
}
async.waterfall([
initializeNodeBB,
function (next) {
winston.info('NodeBB Ready');
winston.info('NodeBB Ready');
require('./socket.io').server.emit('event:nodebb.ready', {
'cache-buster': meta.config['cache-buster'],
});
require('./socket.io').server.emit('event:nodebb.ready', {
'cache-buster': meta.config['cache-buster'],
});
plugins.fireHook('action:nodebb.ready');
plugins.fireHook('action:nodebb.ready');
listen(callback);
});
listen(next);
},
], callback);
};
function initializeNodeBB(callback) {
@@ -107,7 +106,9 @@ function initializeNodeBB(callback) {
meta.blacklist.load,
], next);
},
], callback);
], function (err) {
callback(err);
});
}
function setupExpressApp(app) {