Merge branch 'master' into develop

v1.18.x
Julian Lam 8 years ago
commit af2d9fb85b

@ -52,7 +52,7 @@
"morgan": "^1.3.2", "morgan": "^1.3.2",
"mousetrap": "^1.5.3", "mousetrap": "^1.5.3",
"nconf": "~0.8.2", "nconf": "~0.8.2",
"nodebb-plugin-composer-default": "4.4.1", "nodebb-plugin-composer-default": "4.4.2",
"nodebb-plugin-dbsearch": "2.0.2", "nodebb-plugin-dbsearch": "2.0.2",
"nodebb-plugin-emoji-extended": "1.1.1", "nodebb-plugin-emoji-extended": "1.1.1",
"nodebb-plugin-emoji-one": "1.1.5", "nodebb-plugin-emoji-one": "1.1.5",

@ -1,7 +1,7 @@
{ {
"alert.confirm-reload": "Are you sure you wish to reload NodeBB?", "alert.confirm-reload": "Bạn có thật sự muốn tải lại NodeBB",
"alert.confirm-restart": "Are you sure you wish to restart NodeBB?", "alert.confirm-restart": "Bạn có thật sự muốn khởi động lại NodeBB",
"acp-title": "%1 | NodeBB Admin Control Panel", "acp-title": "%1 | Bảng điểu khiển",
"settings-header-contents": "Contents" "settings-header-contents": "Nội dung"
} }

@ -1,11 +1,11 @@
{ {
"post-cache": "Post Cache", "post-cache": "Cache bài viết",
"posts-in-cache": "Posts in Cache", "posts-in-cache": "Cache cho bài viết",
"average-post-size": "Average Post Size", "average-post-size": "Kích thước bài viết",
"length-to-max": "Length / Max", "length-to-max": "Độ dài / Tối Đa",
"percent-full": "%1% Full", "percent-full": "%1% Đầy",
"post-cache-size": "Post Cache Size", "post-cache-size": "Kích thước cache bài viết",
"items-in-cache": "Items in Cache", "items-in-cache": "Thành phần trong Cache",
"control-panel": "Control Panel", "control-panel": "Bảng điều khiển",
"update-settings": "Update Cache Settings" "update-settings": "Cập nhật thiết lập Cache"
} }

@ -1,30 +1,30 @@
{ {
"x-b": "%1 b", "x-b": "%1 b",
"x-mb": "%1 mb", "x-mb": "%1 mb",
"uptime-seconds": "Uptime in Seconds", "uptime-seconds": "Thời gian hoạt động(giây)",
"uptime-days": "Uptime in Days", "uptime-days": "Thời gian hoạt động(Ngày)",
"mongo": "Mongo", "mongo": "Mongo",
"mongo.version": "MongoDB Version", "mongo.version": "Phiên bản MongoDB ",
"mongo.storage-engine": "Storage Engine", "mongo.storage-engine": "Lưu Trữ",
"mongo.collections": "Collections", "mongo.collections": "Tập dữ liệu",
"mongo.objects": "Objects", "mongo.objects": "Đối tượng",
"mongo.avg-object-size": "Avg. Object Size", "mongo.avg-object-size": "Kích thước trung bình",
"mongo.data-size": "Data Size", "mongo.data-size": "Kích thước dữ liệu",
"mongo.storage-size": "Storage Size", "mongo.storage-size": "Kích thước lưu trữ",
"mongo.index-size": "Index Size", "mongo.index-size": "Kích thước chỉ mục",
"mongo.file-size": "File Size", "mongo.file-size": "Kích thước tập tin",
"mongo.resident-memory": "Resident Memory", "mongo.resident-memory": "Resident Memory",
"mongo.virtual-memory": "Virtual Memory", "mongo.virtual-memory": "Bộ nhớ ảo",
"mongo.mapped-memory": "Mapped Memory", "mongo.mapped-memory": "Mapped Memory",
"mongo.raw-info": "MongoDB Raw Info", "mongo.raw-info": "Thông tin MongoDB",
"redis": "Redis", "redis": "Redis",
"redis.version": "Redis Version", "redis.version": "Phiên bản Redis",
"redis.connected-clients": "Connected Clients", "redis.connected-clients": "Người dùng kết nối",
"redis.connected-slaves": "Connected Slaves", "redis.connected-slaves": "Connected Slaves",
"redis.blocked-clients": "Blocked Clients", "redis.blocked-clients": "Người dùng vi phạm",
"redis.used-memory": "Used Memory", "redis.used-memory": "Bộ nhớ đã sử dụng",
"redis.memory-frag-ratio": "Memory Fragmentation Ratio", "redis.memory-frag-ratio": "Memory Fragmentation Ratio",
"redis.total-connections-recieved": "Total Connections Received", "redis.total-connections-recieved": "Total Connections Received",
"redis.total-commands-processed": "Total Commands Processed", "redis.total-commands-processed": "Total Commands Processed",

@ -103,5 +103,5 @@
"cookies.message": "Trang web này sử dụng cookie để đảm bảo trải nghiệm tốt nhất cho người dùng", "cookies.message": "Trang web này sử dụng cookie để đảm bảo trải nghiệm tốt nhất cho người dùng",
"cookies.accept": "Đã rõ!", "cookies.accept": "Đã rõ!",
"cookies.learn_more": "Xem thêm", "cookies.learn_more": "Xem thêm",
"edited": "Edited" "edited": "Đã cập nhật"
} }

@ -13,8 +13,8 @@
"notify_me": "Được thông báo khi có trả lời mới trong chủ đề này", "notify_me": "Được thông báo khi có trả lời mới trong chủ đề này",
"quote": "Trích dẫn", "quote": "Trích dẫn",
"reply": "Trả lời", "reply": "Trả lời",
"replies_to_this_post": "%1 Replies", "replies_to_this_post": "%1 trả lời",
"last_reply_time": "Last reply", "last_reply_time": "Trả lời cuối cùng",
"reply-as-topic": "Trả lời dưới dạng chủ đề", "reply-as-topic": "Trả lời dưới dạng chủ đề",
"guest-login-reply": "Hãy đăng nhập để trả lời", "guest-login-reply": "Hãy đăng nhập để trả lời",
"edit": "Chỉnh sửa", "edit": "Chỉnh sửa",

@ -6,7 +6,7 @@
"search": "搜索", "search": "搜索",
"enter_username": "输入用户名搜索", "enter_username": "输入用户名搜索",
"load_more": "加载更多", "load_more": "加载更多",
"users-found-search-took": "找到 %1 位用户!耗时 %2 秒。", "users-found-search-took": "找到 %1 位用户!耗时 %2 秒。",
"filter-by": "过滤选项", "filter-by": "过滤选项",
"online-only": "只看在线", "online-only": "只看在线",
"invite": "邀请注册", "invite": "邀请注册",

@ -241,22 +241,29 @@ define('forum/account/edit', ['forum/account/header', 'translator', 'components'
uploadModal.find('.upload-btn').on('click', function () { uploadModal.find('.upload-btn').on('click', function () {
var url = uploadModal.find('#uploadFromUrl').val(); var url = uploadModal.find('#uploadFromUrl').val();
if (!url) { if (!url) {
return; return false;
} }
socket.emit('user.uploadProfileImageFromUrl', {
uploadModal.modal('hide'); uid: ajaxify.data.uid,
pictureCropper.handleImageCrop({
url: url, url: url,
socketMethod: 'user.uploadCroppedPicture', }, function (err, url) {
aspectRatio: '1 / 1', if (err) {
allowSkippingCrop: false, return app.alertError(err);
restrictImageDimension: true, }
imageDimension: ajaxify.data.profileImageDimension,
paramName: 'uid',
paramValue: ajaxify.data.theirid,
}, onUploadComplete);
uploadModal.modal('hide');
pictureCropper.handleImageCrop({
url: url,
socketMethod: 'user.uploadCroppedPicture',
aspectRatio: '1 / 1',
allowSkippingCrop: false,
restrictImageDimension: true,
imageDimension: ajaxify.data.profileImageDimension,
paramName: 'uid',
paramValue: ajaxify.data.theirid,
}, onUploadComplete);
});
return false; return false;
}); });
}); });

@ -4,6 +4,12 @@
define('forum/account/settings', ['forum/account/header', 'components', 'sounds'], function (header, components, sounds) { define('forum/account/settings', ['forum/account/header', 'components', 'sounds'], function (header, components, sounds) {
var AccountSettings = {}; var AccountSettings = {};
$(window).on('action:ajaxify.start', function () {
if (ajaxify.data.template.name === 'account/settings' && $('#bootswatchSkin').val() !== config.bootswatchSkin) {
changePageSkin(config.bootswatchSkin);
}
});
AccountSettings.init = function () { AccountSettings.init = function () {
header.init(); header.init();
@ -24,10 +30,7 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds'
}); });
$('#bootswatchSkin').on('change', function () { $('#bootswatchSkin').on('change', function () {
var css = $('#bootswatchCSS'); changePageSkin($(this).val());
var val = $(this).val() === 'default' ? config['theme:src'] : '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + $(this).val() + '/bootstrap.min.css';
css.attr('href', val);
}); });
$('[data-property="homePageRoute"]').on('change', toggleCustomRoute); $('[data-property="homePageRoute"]').on('change', toggleCustomRoute);
@ -44,6 +47,26 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds'
components.get('user/sessions').find('.timeago').timeago(); components.get('user/sessions').find('.timeago').timeago();
}; };
function changePageSkin(skinName) {
var css = $('#bootswatchCSS');
if (skinName === 'default') {
css.remove();
} else {
var cssSource = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + skinName + '/bootstrap.min.css';
if (css.length) {
css.attr('href', cssSource);
} else {
css = $('<link id="bootswatchCSS" href="' + cssSource + '" rel="stylesheet" media="screen">');
$('head').append(css);
}
}
var currentSkinClassName = $('body').attr('class').split(/\s+/).filter(function (className) {
return className.startsWith('skin-');
});
$('body').removeClass(currentSkinClassName.join(' ')).addClass('skin-' + skinName);
}
function loadSettings() { function loadSettings() {
var settings = {}; var settings = {};

@ -7,11 +7,12 @@ define('forum/topic', [
'forum/topic/postTools', 'forum/topic/postTools',
'forum/topic/events', 'forum/topic/events',
'forum/topic/posts', 'forum/topic/posts',
'forum/topic/images',
'forum/topic/replies', 'forum/topic/replies',
'navigator', 'navigator',
'sort', 'sort',
'components', 'components',
], function (infinitescroll, threadTools, postTools, events, posts, replies, navigator, sort, components) { ], function (infinitescroll, threadTools, postTools, events, posts, images, replies, navigator, sort, components) {
var Topic = {}; var Topic = {};
var currentUrl = ''; var currentUrl = '';
@ -238,7 +239,7 @@ define('forum/topic', [
return; return;
} }
posts.loadImages(threshold); images.loadImages(threshold);
var newUrl = 'topic/' + ajaxify.data.slug + (index > 1 ? ('/' + index) : ''); var newUrl = 'topic/' + ajaxify.data.slug + (index > 1 ? ('/' + index) : '');

@ -6,9 +6,10 @@ define('forum/topic/events', [
'forum/topic/postTools', 'forum/topic/postTools',
'forum/topic/threadTools', 'forum/topic/threadTools',
'forum/topic/posts', 'forum/topic/posts',
'forum/topic/images',
'components', 'components',
'translator', 'translator',
], function (postTools, threadTools, posts, components, translator) { ], function (postTools, threadTools, posts, images, components, translator) {
var Events = {}; var Events = {};
var events = { var events = {
@ -128,9 +129,9 @@ define('forum/topic/events', [
editedPostEl.html(translator.unescape(data.post.content)); editedPostEl.html(translator.unescape(data.post.content));
editedPostEl.find('img:not(.not-responsive)').addClass('img-responsive'); editedPostEl.find('img:not(.not-responsive)').addClass('img-responsive');
app.replaceSelfLinks(editedPostEl.find('a')); app.replaceSelfLinks(editedPostEl.find('a'));
posts.wrapImagesInLinks(editedPostEl.parent()); images.wrapImagesInLinks(editedPostEl.parent());
posts.unloadImages(editedPostEl.parent()); images.unloadImages(editedPostEl.parent());
posts.loadImages(); images.loadImages();
editedPostEl.fadeIn(250); editedPostEl.fadeIn(250);
var editData = { var editData = {

@ -0,0 +1,119 @@
'use strict';
define('forum/topic/images', [
'forum/topic/postTools',
'navigator',
'components',
], function (postTools, navigator, components) {
var Images = {
_imageLoaderTimeout: undefined,
};
Images.unloadImages = function (posts) {
var images = posts.find('[component="post/content"] img:not(.not-responsive)');
if (config.delayImageLoading) {
images.each(function () {
$(this).attr('data-src', $(this).attr('src'));
}).attr('data-state', 'unloaded').attr('src', 'about:blank');
} else {
images.attr('data-state', 'loaded');
Images.wrapImagesInLinks(posts);
}
};
Images.loadImages = function (threshold) {
if (Images._imageLoaderTimeout) {
clearTimeout(Images._imageLoaderTimeout);
}
Images._imageLoaderTimeout = setTimeout(function () {
/*
If threshold is defined, images loaded above this threshold will modify
the user's scroll position so they are not scrolled away from content
they were reading. Images loaded below this threshold will push down content.
If no threshold is defined, loaded images will push down content, as per
default
*/
var images = components.get('post/content').find('img[data-state="unloaded"]');
var visible = images.filter(function () {
return utils.isElementInViewport(this);
});
var posts = $.unique(visible.map(function () {
return $(this).parents('[component="post"]').get(0);
}));
var scrollTop = $(window).scrollTop();
var adjusting = false;
var adjustQueue = [];
var oldHeight;
var newHeight;
function adjustPosition() {
adjusting = true;
oldHeight = document.body.clientHeight;
// Display the image
$(this).attr('data-state', 'loaded');
newHeight = document.body.clientHeight;
var imageRect = this.getBoundingClientRect();
if (imageRect.top < threshold) {
scrollTop += newHeight - oldHeight;
$(window).scrollTop(scrollTop);
}
if (adjustQueue.length) {
adjustQueue.pop()();
} else {
adjusting = false;
Images.wrapImagesInLinks(posts);
posts.length = 0;
}
}
// For each image, reset the source and adjust scrollTop when loaded
visible.attr('data-state', 'loading');
visible.each(function (index, image) {
image = $(image);
image.on('load', function () {
if (!adjusting) {
adjustPosition.call(this);
} else {
adjustQueue.push(adjustPosition.bind(this));
}
});
image.attr('src', image.attr('data-src'));
image.removeAttr('data-src');
});
}, 250);
};
Images.wrapImagesInLinks = function (posts) {
posts.find('[component="post/content"] img:not(.emoji)').each(function () {
var $this = $(this);
var src = $this.attr('src');
var suffixRegex = /-resized(\.[\w]+)?$/;
if (src === 'about:blank') {
return;
}
if (utils.isRelativeUrl(src) && suffixRegex.test(src)) {
src = src.replace(suffixRegex, '$1');
}
if (!$this.parent().is('a')) {
$this.wrap('<a href="' + src + '" target="_blank">');
}
});
};
return Images;
});

@ -5,19 +5,14 @@ define('forum/topic/posts', [
'forum/pagination', 'forum/pagination',
'forum/infinitescroll', 'forum/infinitescroll',
'forum/topic/postTools', 'forum/topic/postTools',
'forum/topic/images',
'navigator', 'navigator',
'components', 'components',
], function (pagination, infinitescroll, postTools, navigator, components) { ], function (pagination, infinitescroll, postTools, images, navigator, components) {
var Posts = { var Posts = { };
_imageLoaderTimeout: undefined,
};
Posts.onNewPost = function (data) { Posts.onNewPost = function (data) {
if (!data || !data.posts || !data.posts.length) { if (!data || !data.posts || !data.posts.length || parseInt(data.posts[0].tid, 10) !== parseInt(ajaxify.data.tid, 10)) {
return;
}
if (parseInt(data.posts[0].tid, 10) !== parseInt(ajaxify.data.tid, 10)) {
return; return;
} }
@ -63,7 +58,7 @@ define('forum/topic/posts', [
function onNewPostPagination(data) { function onNewPostPagination(data) {
function scrollToPost() { function scrollToPost() {
scrollToPostIfSelf(data.posts[0]); scrollToPostIfSelf(data.posts[0]);
Posts.loadImages(); images.loadImages();
} }
var posts = data.posts; var posts = data.posts;
@ -107,7 +102,7 @@ define('forum/topic/posts', [
html.addClass('new'); html.addClass('new');
} }
scrollToPostIfSelf(data.posts[0]); scrollToPostIfSelf(data.posts[0]);
Posts.loadImages(); images.loadImages();
}); });
} }
@ -247,7 +242,7 @@ define('forum/topic/posts', [
}; };
Posts.processPage = function (posts) { Posts.processPage = function (posts) {
Posts.unloadImages(posts); images.unloadImages(posts);
Posts.showBottomPostBar(); Posts.showBottomPostBar();
posts.find('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive'); posts.find('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive');
app.createUserTooltips(posts); app.createUserTooltips(posts);
@ -260,110 +255,6 @@ define('forum/topic/posts', [
hidePostToolsForDeletedPosts(posts); hidePostToolsForDeletedPosts(posts);
}; };
Posts.unloadImages = function (posts) {
var images = posts.find('[component="post/content"] img:not(.not-responsive)');
if (config.delayImageLoading) {
images.each(function () {
$(this).attr('data-src', $(this).attr('src'));
}).attr('data-state', 'unloaded').attr('src', 'about:blank');
} else {
images.attr('data-state', 'loaded');
Posts.wrapImagesInLinks(posts);
}
};
Posts.loadImages = function (threshold) {
if (Posts._imageLoaderTimeout) {
clearTimeout(Posts._imageLoaderTimeout);
}
Posts._imageLoaderTimeout = setTimeout(function () {
/*
If threshold is defined, images loaded above this threshold will modify
the user's scroll position so they are not scrolled away from content
they were reading. Images loaded below this threshold will push down content.
If no threshold is defined, loaded images will push down content, as per
default
*/
var images = components.get('post/content').find('img[data-state="unloaded"]');
var visible = images.filter(function () {
return utils.isElementInViewport(this);
});
var posts = $.unique(visible.map(function () {
return $(this).parents('[component="post"]').get(0);
}));
var scrollTop = $(window).scrollTop();
var adjusting = false;
var adjustQueue = [];
var oldHeight;
var newHeight;
function adjustPosition() {
adjusting = true;
oldHeight = document.body.clientHeight;
// Display the image
$(this).attr('data-state', 'loaded');
newHeight = document.body.clientHeight;
var imageRect = this.getBoundingClientRect();
if (imageRect.top < threshold) {
scrollTop += newHeight - oldHeight;
$(window).scrollTop(scrollTop);
}
if (adjustQueue.length) {
adjustQueue.pop()();
} else {
adjusting = false;
Posts.wrapImagesInLinks(posts);
posts.length = 0;
}
}
// For each image, reset the source and adjust scrollTop when loaded
visible.attr('data-state', 'loading');
visible.each(function (index, image) {
image = $(image);
image.on('load', function () {
if (!adjusting) {
adjustPosition.call(this);
} else {
adjustQueue.push(adjustPosition.bind(this));
}
});
image.attr('src', image.attr('data-src'));
image.removeAttr('data-src');
});
}, 250);
};
Posts.wrapImagesInLinks = function (posts) {
posts.find('[component="post/content"] img:not(.emoji)').each(function () {
var $this = $(this);
var src = $this.attr('src');
var suffixRegex = /-resized(\.[\w]+)?$/;
if (src === 'about:blank') {
return;
}
if (utils.isRelativeUrl(src) && suffixRegex.test(src)) {
src = src.replace(suffixRegex, '$1');
}
if (!$this.parent().is('a')) {
$this.wrap('<a href="' + src + '" target="_blank">');
}
});
};
Posts.showBottomPostBar = function () { Posts.showBottomPostBar = function () {
var mainPost = components.get('post', 'index', 0); var mainPost = components.get('post', 'index', 0);
var placeHolder = $('.post-bar-placeholder'); var placeHolder = $('.post-bar-placeholder');

@ -16,9 +16,9 @@
return false; return false;
} }
var properties = item.properties; var properties = item.properties;
var loggedIn = data.config ? data.config.loggedIn : false;
if (properties) { if (properties) {
if ((properties.loggedIn && !data.config.loggedIn) || if ((properties.loggedIn && !loggedIn) ||
(properties.globalMod && !data.isGlobalMod && !data.isAdmin) || (properties.globalMod && !data.isGlobalMod && !data.isAdmin) ||
(properties.adminOnly && !data.isAdmin) || (properties.adminOnly && !data.isAdmin) ||
(properties.searchInstalled && !data.searchEnabled)) { (properties.searchInstalled && !data.searchEnabled)) {
@ -26,11 +26,11 @@
} }
} }
if (item.route.match('/users') && data.privateUserInfo && !data.config.loggedIn) { if (item.route.match('/users') && data.privateUserInfo && !loggedIn) {
return false; return false;
} }
if (item.route.match('/tags') && data.privateTagListing && !data.config.loggedIn) { if (item.route.match('/tags') && data.privateTagListing && !loggedIn) {
return false; return false;
} }

@ -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');
}
};

@ -13,9 +13,8 @@ var privileges = require('../privileges');
var plugins = require('../plugins'); var plugins = require('../plugins');
var widgets = require('../widgets'); var widgets = require('../widgets');
var translator = require('../../public/src/modules/translator'); 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) { apiController.getConfig = function (req, res, next) {
var config = {}; var config = {};
@ -62,7 +61,7 @@ apiController.getConfig = function (req, res, next) {
config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest'; config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest';
config.csrf_token = req.csrfToken(); config.csrf_token = req.csrfToken();
config.searchEnabled = plugins.hasListeners('filter:search.query'); 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; var timeagoCutoff = meta.config.timeagoCutoff === undefined ? 30 : meta.config.timeagoCutoff;
config.timeagoCutoff = timeagoCutoff !== '' ? Math.max(0, parseInt(timeagoCutoff, 10)) : 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) { apiController.getModerators = function (req, res, next) {
categories.getModerators(req.params.cid, function (err, moderators) { categories.getModerators(req.params.cid, function (err, moderators) {
if (err) { if (err) {
@ -314,16 +227,3 @@ apiController.getModerators = function (req, res, next) {
res.json({ moderators: moderators }); 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;

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

@ -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)) });
});
}
};

@ -3,34 +3,35 @@
var async = require('async'); var async = require('async');
var nconf = require('nconf'); var nconf = require('nconf');
var validator = require('validator'); var validator = require('validator');
var winston = require('winston');
var meta = require('../meta'); var meta = require('../meta');
var user = require('../user'); var user = require('../user');
var plugins = require('../plugins'); var plugins = require('../plugins');
var helpers = require('./helpers'); var helpers = require('./helpers');
var Controllers = { var Controllers = module.exports;
topics: require('./topics'),
posts: require('./posts'), Controllers.topics = require('./topics');
categories: require('./categories'), Controllers.posts = require('./posts');
category: require('./category'), Controllers.categories = require('./categories');
unread: require('./unread'), Controllers.category = require('./category');
recent: require('./recent'), Controllers.unread = require('./unread');
popular: require('./popular'), Controllers.recent = require('./recent');
tags: require('./tags'), Controllers.popular = require('./popular');
search: require('./search'), Controllers.tags = require('./tags');
users: require('./users'), Controllers.search = require('./search');
groups: require('./groups'), Controllers.user = require('./user');
accounts: require('./accounts'), Controllers.users = require('./users');
authentication: require('./authentication'), Controllers.groups = require('./groups');
api: require('./api'), Controllers.accounts = require('./accounts');
admin: require('./admin'), Controllers.authentication = require('./authentication');
globalMods: require('./globalmods'), Controllers.api = require('./api');
mods: require('./mods'), Controllers.admin = require('./admin');
sitemap: require('./sitemap'), 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) { Controllers.home = function (req, res, next) {
var route = meta.config.homePageRoute || (meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories'; var route = meta.config.homePageRoute || (meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories';
@ -321,19 +322,18 @@ Controllers.manifest = function (req, res) {
res.status(200).json(manifest); res.status(200).json(manifest);
}; };
Controllers.outgoing = function (req, res) { Controllers.outgoing = function (req, res, next) {
var url = req.query.url || ''; var url = req.query.url || '';
var data = {
if (!url) {
return next();
}
res.render('outgoing', {
outgoing: validator.escape(String(url)), outgoing: validator.escape(String(url)),
title: meta.config.title, title: meta.config.title,
breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[notifications:outgoing_link]]' }]), 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) { Controllers.termsOfUse = function (req, res, next) {
@ -346,102 +346,3 @@ Controllers.termsOfUse = function (req, res, next) {
Controllers.ping = function (req, res) { Controllers.ping = function (req, res) {
res.status(200).send(req.path === '/sping' ? 'healthy' : '200'); 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;

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

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

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

@ -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);
});
};

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

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

@ -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);
};
};

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

@ -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);
};
};

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

@ -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);
};
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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);
};
};

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

@ -18,6 +18,14 @@ module.exports = function (User) {
var updateUid = data.uid; var updateUid = data.uid;
var oldData; 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([ async.waterfall([
function (next) { function (next) {
plugins.fireHook('filter:user.updateProfile', { uid: uid, data: data, fields: fields }, next); plugins.fireHook('filter:user.updateProfile', { uid: uid, data: data, fields: fields }, next);
@ -27,8 +35,6 @@ module.exports = function (User) {
data = data.data; data = data.data;
async.series([ async.series([
async.apply(isAboutMeValid, data),
async.apply(isSignatureValid, data),
async.apply(isEmailAvailable, data, updateUid), async.apply(isEmailAvailable, data, updateUid),
async.apply(isUsernameAvailable, data, updateUid), async.apply(isUsernameAvailable, data, updateUid),
async.apply(isGroupTitleValid, data), async.apply(isGroupTitleValid, data),
@ -68,22 +74,6 @@ module.exports = function (User) {
], callback); ], 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) { function isEmailAvailable(data, uid, callback) {
if (!data.email) { if (!data.email) {
return callback(); return callback();

@ -74,7 +74,7 @@ module.exports = function (User) {
settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1; settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1;
settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1; settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1;
settings.delayImageLoading = parseInt(getSetting(settings, 'delayImageLoading', 1), 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; settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1;
callback(null, settings); callback(null, settings);

@ -36,13 +36,7 @@
<li data-plugin-id="{installed.id}" class="clearfix"> <li data-plugin-id="{installed.id}" class="clearfix">
<div class="pull-right"> <div class="pull-right">
<button class="btn btn-default disabled"><i class="fa fa-exclamation-triangle"></i> [[admin/extend/plugins:plugin-item.unknown]]</button> <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> <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> </div>
<h2><strong>{installed.id}</strong></h2> <h2><strong>{installed.id}</strong></h2>

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

@ -390,6 +390,7 @@ describe('Categories', function () {
it('should get all categories', function (done) { it('should get all categories', function (done) {
socketCategories.getAll({ uid: adminUid }, {}, function (err, data) { socketCategories.getAll({ uid: adminUid }, {}, function (err, data) {
assert.ifError(err); assert.ifError(err);
assert(data);
done(); done();
}); });
}); });
@ -615,6 +616,72 @@ describe('Categories', function () {
}); });
describe('privileges', function () {
var privileges = require('../src/privileges');
it('should return empty array if uids is empty array', function (done) {
privileges.categories.filterUids('find', categoryObj.cid, [], function (err, uids) {
assert.ifError(err);
assert.equal(uids.length, 0);
done();
});
});
it('should filter uids by privilege', function (done) {
privileges.categories.filterUids('find', categoryObj.cid, [1, 2, 3, 4], function (err, uids) {
assert.ifError(err);
assert.deepEqual(uids, [1, 2]);
done();
});
});
it('should load user privileges', function (done) {
privileges.categories.userPrivileges(categoryObj.cid, 1, function (err, data) {
assert.ifError(err);
assert.deepEqual(data, {
find: false,
mods: false,
'posts:delete': false,
read: false,
'topics:reply': false,
'topics:read': false,
'topics:create': false,
'topics:delete': false,
'posts:edit': false,
});
done();
});
});
it('should load group privileges', function (done) {
privileges.categories.groupPrivileges(categoryObj.cid, 'registered-users', function (err, data) {
assert.ifError(err);
assert.deepEqual(data, {
'groups:find': true,
'groups:posts:edit': true,
'groups:topics:delete': false,
'groups:topics:create': true,
'groups:topics:reply': true,
'groups:posts:delete': true,
'groups:read': true,
'groups:topics:read': true,
});
done();
});
});
it('should return false if cid is falsy', function (done) {
privileges.categories.isUserAllowedTo('find', null, adminUid, function (err, isAllowed) {
assert.ifError(err);
assert.equal(isAllowed, false);
done();
});
});
});
after(function (done) { after(function (done) {
db.emptydb(done); db.emptydb(done);
}); });

@ -21,6 +21,7 @@ describe('Admin Controllers', function () {
var jar; var jar;
before(function (done) { before(function (done) {
groups.resetCache();
async.series({ async.series({
category: function (next) { category: function (next) {
categories.create({ categories.create({
@ -43,9 +44,10 @@ describe('Admin Controllers', function () {
cid = results.category.cid; cid = results.category.cid;
topics.post({ uid: adminUid, title: 'test topic title', content: 'test topic content', cid: results.category.cid }, function (err, result) { topics.post({ uid: adminUid, title: 'test topic title', content: 'test topic content', cid: results.category.cid }, function (err, result) {
assert.ifError(err);
tid = result.topicData.tid; tid = result.topicData.tid;
pid = result.postData.pid; pid = result.postData.pid;
done(err); done();
}); });
}); });
}); });

@ -27,7 +27,7 @@ describe('Controllers', function () {
}, next); }, next);
}, },
user: function (next) { user: function (next) {
user.create({ username: 'foo', password: 'barbar' }, next); user.create({ username: 'foo', password: 'barbar', email: 'foo@test.com' }, next);
}, },
navigation: function (next) { navigation: function (next) {
var navigation = require('../src/navigation/admin'); var navigation = require('../src/navigation/admin');
@ -498,11 +498,23 @@ describe('Controllers', function () {
hidden: 0, hidden: 0,
}, function (err) { }, function (err) {
assert.ifError(err); assert.ifError(err);
request(nconf.get('url') + '/groups/group-details', function (err, res, body) { groups.join('group-details', fooUid, function (err) {
assert.ifError(err); assert.ifError(err);
assert.equal(res.statusCode, 200); topics.post({
assert(body); uid: fooUid,
done(); title: 'topic title',
content: 'test topic content',
cid: cid,
}, function (err) {
assert.ifError(err);
request(nconf.get('url') + '/api/groups/group-details', { json: true }, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
assert.equal(body.posts[0].content, 'test topic content');
done();
});
});
}); });
}); });
}); });
@ -532,6 +544,15 @@ describe('Controllers', function () {
}); });
}); });
it('should get recent posts', function (done) {
request(nconf.get('url') + '/api/recent/posts/month', function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
it('should get post data', function (done) { it('should get post data', function (done) {
request(nconf.get('url') + '/api/post/pid/' + pid, function (err, res, body) { request(nconf.get('url') + '/api/post/pid/' + pid, function (err, res, body) {
assert.ifError(err); assert.ifError(err);
@ -890,6 +911,42 @@ describe('Controllers', function () {
}, },
], done); ], done);
}); });
it('should 404 if user does not exist', function (done) {
request(nconf.get('url') + '/api/user/email/doesnotexist', function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 404);
assert(body);
done();
});
});
it('should load user by uid', function (done) {
request(nconf.get('url') + '/api/user/uid/' + fooUid, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
it('should load user by username', function (done) {
request(nconf.get('url') + '/api/user/username/foo', function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
it('should load user by email', function (done) {
request(nconf.get('url') + '/api/user/email/foo@test.com', function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
}); });
describe('account follow page', function () { describe('account follow page', function () {
@ -943,7 +1000,7 @@ describe('Controllers', function () {
describe('post redirect', function () { describe('post redirect', function () {
it('should 404 for invalid pid', function (done) { it('should 404 for invalid pid', function (done) {
request(nconf.get('url') + '/post/fail', function (err, res) { request(nconf.get('url') + '/api/post/fail', function (err, res) {
assert.ifError(err); assert.ifError(err);
assert.equal(res.statusCode, 404); assert.equal(res.statusCode, 404);
done(); done();

@ -11,6 +11,7 @@ var categories = require('../src/categories');
var privileges = require('../src/privileges'); var privileges = require('../src/privileges');
var user = require('../src/user'); var user = require('../src/user');
var groups = require('../src/groups'); var groups = require('../src/groups');
var socketPosts = require('../src/socket.io/posts');
describe('Post\'s', function () { describe('Post\'s', function () {
var voterUid; var voterUid;
@ -66,7 +67,6 @@ describe('Post\'s', function () {
}); });
describe('voting', function () { describe('voting', function () {
var socketPosts = require('../src/socket.io/posts');
it('should upvote a post', function (done) { it('should upvote a post', function (done) {
socketPosts.upvote({ uid: voterUid }, { pid: postData.pid, room_id: 'topic_1' }, function (err, result) { socketPosts.upvote({ uid: voterUid }, { pid: postData.pid, room_id: 'topic_1' }, function (err, result) {
assert.ifError(err); assert.ifError(err);
@ -138,7 +138,7 @@ describe('Post\'s', function () {
describe('bookmarking', function () { describe('bookmarking', function () {
it('should bookmark a post', function (done) { it('should bookmark a post', function (done) {
posts.bookmark(postData.pid, voterUid, function (err, data) { socketPosts.bookmark({ uid: voterUid }, { pid: postData.pid, room_id: 'topic_' + postData.tid }, function (err, data) {
assert.ifError(err); assert.ifError(err);
assert.equal(data.isBookmarked, true); assert.equal(data.isBookmarked, true);
posts.hasBookmarked(postData.pid, voterUid, function (err, hasBookmarked) { posts.hasBookmarked(postData.pid, voterUid, function (err, hasBookmarked) {
@ -150,7 +150,7 @@ describe('Post\'s', function () {
}); });
it('should unbookmark a post', function (done) { it('should unbookmark a post', function (done) {
posts.unbookmark(postData.pid, voterUid, function (err, data) { socketPosts.unbookmark({ uid: voterUid }, { pid: postData.pid, room_id: 'topic_' + postData.tid }, function (err, data) {
assert.ifError(err); assert.ifError(err);
assert.equal(data.isBookmarked, false); assert.equal(data.isBookmarked, false);
posts.hasBookmarked([postData.pid], voterUid, function (err, hasBookmarked) { posts.hasBookmarked([postData.pid], voterUid, function (err, hasBookmarked) {
@ -163,8 +163,6 @@ describe('Post\'s', function () {
}); });
describe('post tools', function () { describe('post tools', function () {
var socketPosts = require('../src/socket.io/posts');
it('should error if data is invalid', function (done) { it('should error if data is invalid', function (done) {
socketPosts.loadPostTools({ uid: globalModUid }, null, function (err) { socketPosts.loadPostTools({ uid: globalModUid }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]'); assert.equal(err.message, '[[error:invalid-data]]');
@ -209,7 +207,6 @@ describe('Post\'s', function () {
var mainPid; var mainPid;
var replyPid; var replyPid;
var socketPosts = require('../src/socket.io/posts');
before(function (done) { before(function (done) {
createTopicWithReply(function (topicPostData, replyData) { createTopicWithReply(function (topicPostData, replyData) {
tid = topicPostData.topicData.tid; tid = topicPostData.topicData.tid;
@ -299,7 +296,6 @@ describe('Post\'s', function () {
var pid; var pid;
var replyPid; var replyPid;
var tid; var tid;
var socketPosts = require('../src/socket.io/posts');
var meta = require('../src/meta'); var meta = require('../src/meta');
before(function (done) { before(function (done) {
topics.post({ topics.post({
@ -430,7 +426,6 @@ describe('Post\'s', function () {
var replyPid; var replyPid;
var tid; var tid;
var moveTid; var moveTid;
var socketPosts = require('../src/socket.io/posts');
before(function (done) { before(function (done) {
async.waterfall([ async.waterfall([
@ -554,7 +549,6 @@ describe('Post\'s', function () {
}); });
}); });
var socketPosts = require('../src/socket.io/posts');
it('should error with invalid data', function (done) { it('should error with invalid data', function (done) {
socketPosts.reply({ uid: 0 }, null, function (err) { socketPosts.reply({ uid: 0 }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]'); assert.equal(err.message, '[[error:invalid-data]]');

@ -419,11 +419,16 @@ describe('socket.io', function () {
}); });
it('should set theme to bootswatch', function (done) { it('should set theme to bootswatch', function (done) {
socketAdmin.themes.set({ uid: adminUid }, { type: 'bootswatch', src: 'darkly' }, function (err) { socketAdmin.themes.set({ uid: adminUid }, {
type: 'bootswatch',
src: '//maxcdn.bootstrapcdn.com/bootswatch/latest/darkly/bootstrap.min.css',
id: 'darkly',
}, function (err) {
assert.ifError(err); assert.ifError(err);
meta.configs.get('theme:src', function (err, id) { meta.configs.getFields(['theme:src', 'bootswatchSkin'], function (err, fields) {
assert.ifError(err); assert.ifError(err);
assert.equal(id, 'darkly'); assert.equal(fields['theme:src'], '//maxcdn.bootstrapcdn.com/bootswatch/latest/darkly/bootstrap.min.css');
assert.equal(fields.bootswatchSkin, 'darkly');
done(); done();
}); });
}); });

@ -12,6 +12,7 @@ var User = require('../src/user');
var groups = require('../src/groups'); var groups = require('../src/groups');
var helpers = require('./helpers'); var helpers = require('./helpers');
var socketPosts = require('../src/socket.io/posts'); var socketPosts = require('../src/socket.io/posts');
var socketTopics = require('../src/socket.io/topics');
describe('Topic\'s', function () { describe('Topic\'s', function () {
var topic; var topic;
@ -49,11 +50,34 @@ describe('Topic\'s', function () {
}); });
describe('.post', function () { describe('.post', function () {
it('should fail to create topic with invalid data', function (done) {
socketTopics.post({ uid: 0 }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should create a new topic with proper parameters', function (done) { it('should create a new topic with proper parameters', function (done) {
topics.post({ uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId }, function (err, result) { topics.post({ uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId }, function (err, result) {
assert.equal(err, null, 'was created with error'); assert.ifError(err);
assert.ok(result); assert(result);
topic.tid = result.topicData.tid;
done();
});
});
it('should get post count', function (done) {
socketTopics.postcount({ uid: adminUid }, topic.tid, function (err, count) {
assert.ifError(err);
assert.equal(count, 1);
done();
});
});
it('should load topic', function (done) {
socketTopics.getTopic({ uid: adminUid }, topic.tid, function (err, data) {
assert.ifError(err);
assert.equal(data.tid, topic.tid);
done(); done();
}); });
}); });
@ -246,7 +270,7 @@ describe('Topic\'s', function () {
var newTopic; var newTopic;
var followerUid; var followerUid;
var moveCid; var moveCid;
var socketTopics = require('../src/socket.io/topics');
before(function (done) { before(function (done) {
async.waterfall([ async.waterfall([
function (next) { function (next) {
@ -589,8 +613,7 @@ describe('Topic\'s', function () {
assert.ok(result); assert.ok(result);
replies.push(result); replies.push(result);
next(); next();
} });
);
} }
before(function (done) { before(function (done) {
@ -619,25 +642,45 @@ describe('Topic\'s', function () {
function (next) { postReply(next); }, function (next) { postReply(next); },
function (next) { function (next) {
topicPids = replies.map(function (reply) { return reply.pid; }); topicPids = replies.map(function (reply) { return reply.pid; });
topics.setUserBookmark(newTopic.tid, topic.userId, originalBookmark, next); socketTopics.bookmark({ uid: topic.userId }, { tid: newTopic.tid, index: originalBookmark }, next);
}], }],
done); done);
}); });
it('should fail with invalid data', function (done) {
socketTopics.bookmark({ uid: topic.userId }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should have 12 replies', function (done) { it('should have 12 replies', function (done) {
assert.equal(12, replies.length); assert.equal(12, replies.length);
done(); done();
}); });
it('should fail with invalid data', function (done) {
socketTopics.createTopicFromPosts({ uid: 0 }, null, function (err) {
assert.equal(err.message, '[[error:not-logged-in]]');
done();
});
});
it('should fail with invalid data', function (done) {
socketTopics.createTopicFromPosts({ uid: 1 }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should not update the user\'s bookmark', function (done) { it('should not update the user\'s bookmark', function (done) {
async.waterfall([ async.waterfall([
function (next) { function (next) {
topics.createTopicFromPosts( socketTopics.createTopicFromPosts({ uid: topic.userId }, {
topic.userId, title: 'Fork test, no bookmark update',
'Fork test, no bookmark update', pids: topicPids.slice(-2),
topicPids.slice(-2), fromTid: newTopic.tid,
newTopic.tid, }, next);
next);
}, },
function (forkedTopicData, next) { function (forkedTopicData, next) {
topics.getUserBookmark(newTopic.tid, topic.userId, next); topics.getUserBookmark(newTopic.tid, topic.userId, next);
@ -1388,6 +1431,13 @@ describe('Topic\'s', function () {
}); });
}); });
it('should error if not logged in', function (done) {
socketTopics.changeWatching({ uid: 0 }, { tid: tid, type: 'ignore' }, function (err) {
assert.equal(err.message, '[[error:not-logged-in]]');
done();
});
});
it('should filter ignoring uids', function (done) { it('should filter ignoring uids', function (done) {
socketTopics.changeWatching({ uid: followerUid }, { tid: tid, type: 'ignore' }, function (err) { socketTopics.changeWatching({ uid: followerUid }, { tid: tid, type: 'ignore' }, function (err) {
assert.ifError(err); assert.ifError(err);
@ -1418,7 +1468,7 @@ describe('Topic\'s', function () {
topics.toggleFollow(tid, followerUid, function (err, isFollowing) { topics.toggleFollow(tid, followerUid, function (err, isFollowing) {
assert.ifError(err); assert.ifError(err);
assert(isFollowing); assert(isFollowing);
topics.isFollowing([tid], followerUid, function (err, isFollowing) { socketTopics.isFollowed({ uid: followerUid }, tid, function (err, isFollowing) {
assert.ifError(err); assert.ifError(err);
assert(isFollowing); assert(isFollowing);
done(); done();
@ -1427,6 +1477,44 @@ describe('Topic\'s', function () {
}); });
}); });
describe('topics search', function () {
it('should error with invalid data', function (done) {
socketTopics.search({ uid: adminUid }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should error if no search plugin', function (done) {
socketTopics.search({ uid: adminUid }, { tid: topic.tid, term: 'test' }, function (err) {
assert.equal(err.message, '[[error:no-plugins-available]]');
done();
});
});
it('should return results', function (done) {
var plugins = require('../src/plugins');
plugins.registerHook('myTestPlugin', {
hook: 'filter:topic.search',
method: function (data, callback) {
callback(null, [1, 2, 3]);
},
});
socketTopics.search({ uid: adminUid }, { tid: topic.tid, term: 'test' }, function (err, results) {
assert.ifError(err);
assert.deepEqual(results, [1, 2, 3]);
done();
});
});
});
it('should check if user is moderator', function (done) {
socketTopics.isModerator({ uid: adminUid }, topic.tid, function (err, isModerator) {
assert.ifError(err);
assert(!isModerator);
done();
});
});
after(function (done) { after(function (done) {
db.emptydb(done); db.emptydb(done);

Loading…
Cancel
Save