Merge branch 'develop' into upgrades-refactor

v1.18.x
Julian Lam 8 years ago
commit 513f72c0e9

@ -2,7 +2,6 @@
var async = require('async');
var nconf = require('nconf');
var validator = require('validator');
var categories = require('../categories');
var meta = require('../meta');
@ -13,10 +12,10 @@ var categoriesController = {};
categoriesController.list = function (req, res, next) {
res.locals.metaTags = [{
name: 'title',
content: validator.escape(String(meta.config.title || 'NodeBB')),
content: String(meta.config.title || 'NodeBB'),
}, {
name: 'description',
content: validator.escape(String(meta.config.description || '')),
content: String(meta.config.description || ''),
}, {
property: 'og:title',
content: '[[pages:categories]]',

@ -201,37 +201,35 @@ Controllers.registerInterstitial = function (req, res, next) {
return res.redirect(nconf.get('relative_path') + '/register');
}
plugins.fireHook('filter:register.interstitial', {
userData: req.session.registration,
interstitials: [],
}, function (err, data) {
if (err) {
return next(err);
}
if (!data.interstitials.length) {
// No interstitials, redirect to home
delete req.session.registration;
return res.redirect('/');
}
var renders = data.interstitials.map(function (interstitial) {
return async.apply(req.app.render.bind(req.app), interstitial.template, interstitial.data || {});
});
var errors = req.flash('error');
async.parallel(renders, function (err, sections) {
if (err) {
return next(err);
async.waterfall([
function (next) {
plugins.fireHook('filter:register.interstitial', {
userData: req.session.registration,
interstitials: [],
}, next);
},
function (data, next) {
if (!data.interstitials.length) {
// No interstitials, redirect to home
delete req.session.registration;
return res.redirect('/');
}
var renders = data.interstitials.map(function (interstitial) {
return async.apply(req.app.render.bind(req.app), interstitial.template, interstitial.data || {});
});
async.parallel(renders, next);
},
function (sections) {
var errors = req.flash('error');
res.render('registerComplete', {
title: '[[pages:registration-complete]]',
errors: errors,
sections: sections,
});
});
});
},
], next);
};
Controllers.compose = function (req, res, next) {

@ -1,5 +1,6 @@
'use strict';
var async = require('async');
var validator = require('validator');
var db = require('../database');
@ -16,18 +17,19 @@ module.exports = function (Meta) {
};
Meta.errors.get = function (escape, callback) {
db.getSortedSetRevRangeWithScores('errors:404', 0, -1, function (err, data) {
if (err) {
return callback(err);
}
data = data.map(function (nfObject) {
nfObject.value = escape ? validator.escape(String(nfObject.value || '')) : nfObject.value;
return nfObject;
});
callback(null, data);
});
async.waterfall([
function (next) {
db.getSortedSetRevRangeWithScores('errors:404', 0, -1, next);
},
function (data, next) {
data = data.map(function (nfObject) {
nfObject.value = escape ? validator.escape(String(nfObject.value || '')) : nfObject.value;
return nfObject;
});
next(null, data);
},
], callback);
};
Meta.errors.clear = function (callback) {

@ -78,6 +78,13 @@ module.exports = function (Plugins) {
}
};
Plugins.unregisterHook = function (id, hook, method) {
var hooks = Plugins.loadedHooks[hook] || [];
Plugins.loadedHooks[hook] = hooks.filter(function (hookData) {
return hookData && hookData.id !== id && hookData.method !== method;
});
};
Plugins.fireHook = function (hook, params, callback) {
callback = typeof callback === 'function' ? callback : function () {};

@ -248,6 +248,9 @@ var plugins = require('./plugins');
},
], next);
},
function (next) {
db.sortedSetAdd('posts:votes', postData.votes, postData.pid, next);
},
function (next) {
Posts.setPostFields(postData.pid, { upvotes: postData.upvotes, downvotes: postData.downvotes }, next);
},

@ -93,17 +93,18 @@ SocketAdmin.themes.set = function (socket, data, callback) {
return callback(new Error('[[error:invalid-data]]'));
}
var wrappedCallback = function (err) {
if (err) {
return callback(err);
}
meta.themes.set(data, callback);
};
if (data.type === 'bootswatch') {
wrappedCallback();
} else {
widgets.reset(wrappedCallback);
}
async.waterfall([
function (next) {
if (data.type === 'bootswatch') {
setImmediate(next);
} else {
widgets.reset(next);
}
},
function (next) {
meta.themes.set(data, next);
},
], callback);
};
SocketAdmin.plugins.toggleActive = function (socket, plugin_id, callback) {
@ -125,7 +126,7 @@ SocketAdmin.plugins.orderActivePlugins = function (socket, data, callback) {
if (plugin && plugin.name) {
db.sortedSetAdd('plugins:active', plugin.order || 0, plugin.name, next);
} else {
next();
setImmediate(next);
}
}, callback);
};
@ -148,7 +149,7 @@ SocketAdmin.config.set = function (socket, data, callback) {
}
var _data = {};
_data[data.key] = data.value;
SocketAdmin.config.setMultiple(socket, data, callback);
SocketAdmin.config.setMultiple(socket, _data, callback);
};
SocketAdmin.config.setMultiple = function (socket, data, callback) {
@ -205,6 +206,10 @@ SocketAdmin.email.test = function (socket, data, callback) {
};
SocketAdmin.analytics.get = function (socket, data, callback) {
if (!data || !data.graph || !data.units) {
return callback(new Error('[[error:invalid-data]]'));
}
// Default returns views from past 24 hours, by hour
if (data.units === 'days') {
data.amount = 30;
@ -212,34 +217,30 @@ SocketAdmin.analytics.get = function (socket, data, callback) {
data.amount = 24;
}
if (data && data.graph && data.units && data.amount) {
if (data.graph === 'traffic') {
async.parallel({
uniqueVisitors: function (next) {
if (data.units === 'days') {
analytics.getDailyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next);
} else {
analytics.getHourlyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next);
}
},
pageviews: function (next) {
if (data.units === 'days') {
analytics.getDailyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next);
} else {
analytics.getHourlyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next);
}
},
monthlyPageViews: function (next) {
analytics.getMonthlyPageViews(next);
},
}, function (err, data) {
data.pastDay = data.pageviews.reduce(function (a, b) { return parseInt(a, 10) + parseInt(b, 10); });
data.pageviews[data.pageviews.length - 1] = parseInt(data.pageviews[data.pageviews.length - 1], 10) + analytics.getUnwrittenPageviews();
callback(err, data);
});
}
} else {
callback(new Error('Invalid analytics call'));
if (data.graph === 'traffic') {
async.parallel({
uniqueVisitors: function (next) {
if (data.units === 'days') {
analytics.getDailyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next);
} else {
analytics.getHourlyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next);
}
},
pageviews: function (next) {
if (data.units === 'days') {
analytics.getDailyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next);
} else {
analytics.getHourlyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next);
}
},
monthlyPageViews: function (next) {
analytics.getMonthlyPageViews(next);
},
}, function (err, data) {
data.pastDay = data.pageviews.reduce(function (a, b) { return parseInt(a, 10) + parseInt(b, 10); });
data.pageviews[data.pageviews.length - 1] = parseInt(data.pageviews[data.pageviews.length - 1], 10) + analytics.getUnwrittenPageviews();
callback(err, data);
});
}
};
@ -260,13 +261,15 @@ SocketAdmin.deleteAllEvents = function (socket, data, callback) {
};
SocketAdmin.getSearchDict = function (socket, data, callback) {
user.getSettings(socket.uid, function (err, settings) {
if (err) {
return callback(err);
}
var lang = settings.userLang || meta.config.defaultLang || 'en-GB';
getAdminSearchDict(lang, callback);
});
async.waterfall([
function (next) {
user.getSettings(socket.uid, next);
},
function (settings, next) {
var lang = settings.userLang || meta.config.defaultLang || 'en-GB';
getAdminSearchDict(lang, next);
},
], callback);
};
SocketAdmin.deleteAllSessions = function (socket, data, callback) {

@ -46,7 +46,7 @@ var Upgrade = {
},
{
version: 'master', // rename this to whenever the next NodeBB version is (non-breaking)
upgrades: ['sound_settings'],
upgrades: ['sound_settings', 'post_votes_zset'],
},
{
version: 'develop', // rename this to whatever the next NodeBB version is (breaking)

@ -0,0 +1,26 @@
/* jslint node: true */
'use strict';
var db = require('../database');
var async = require('async');
module.exports = {
name: 'New sorted set posts:votes',
timestamp: Date.UTC(2017, 1, 27),
method: function (callback) {
require('../batch').processSortedSet('posts:pid', function (pids, next) {
async.each(pids, function (pid, next) {
db.getObjectFields('post:' + pid, ['upvotes', 'downvotes'], function (err, postData) {
if (err || !postData) {
return next(err);
}
var votes = parseInt(postData.upvotes || 0, 10) - parseInt(postData.downvotes || 0, 10);
db.sortedSetAdd('posts:votes', votes, pid, next);
});
}, next);
}, {}, callback);
},
};

@ -136,6 +136,59 @@ describe('Controllers', function () {
});
});
it('should load /register/complete', function (done) {
var plugins = require('../src/plugins');
function hookMethod(data, next) {
data.interstitials.push({ template: 'topic.tpl', data: {} });
next(null, data);
}
plugins.registerHook('myTestPlugin', {
hook: 'filter:register.interstitial',
method: hookMethod,
});
var data = {
username: 'interstitial',
password: '123456',
email: 'test@me.com',
};
var jar = request.jar();
request({
url: nconf.get('url') + '/api/config',
json: true,
jar: jar,
}, function (err, response, body) {
assert.ifError(err);
request.post(nconf.get('url') + '/register', {
form: data,
json: true,
jar: jar,
headers: {
'x-csrf-token': body.csrf_token,
},
}, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.equal(body.referrer, nconf.get('relative_path') + '/register/complete');
request(nconf.get('url') + '/api/register/complete', {
jar: jar,
json: true,
}, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body.sections);
assert(body.errors);
assert(body.title);
plugins.unregisterHook('myTestPlugin', 'filter:register.interstitial', hookMethod);
done();
});
});
});
});
it('should load /robots.txt', function (done) {
request(nconf.get('url') + '/robots.txt', function (err, res, body) {
assert.ifError(err);
@ -471,7 +524,7 @@ describe('Controllers', function () {
hidden: 1,
}, function (err) {
assert.ifError(err);
request(nconf.get('url') + '/groups/hidden-group/members', function (err, res, body) {
request(nconf.get('url') + '/groups/hidden-group/members', function (err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 404);
done();
@ -531,7 +584,7 @@ describe('Controllers', function () {
headers: {
'x-csrf-token': csrf_token,
},
}, function (err, res, body) {
}, function (err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 404);
done();
@ -689,7 +742,7 @@ describe('Controllers', function () {
});
it('should return 503 in maintenance mode', function (done) {
request(nconf.get('url') + '/recent', { json: true }, function (err, res, body) {
request(nconf.get('url') + '/recent', { json: true }, function (err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 503);
done();

@ -123,6 +123,17 @@ describe('meta', function () {
});
});
it('should set single config value', function (done) {
socketAdmin.config.set({ uid: fooUid }, { key: 'someKey', value: 'someValue' }, function (err) {
assert.ifError(err);
meta.configs.getFields(['someKey'], function (err, data) {
assert.ifError(err);
assert.equal(data.someKey, 'someValue');
done();
});
});
});
it('should set config value', function (done) {
meta.configs.set('someField', 'someValue', function (err) {
assert.ifError(err);

@ -16,6 +16,9 @@ var user = require('../src/user');
var groups = require('../src/groups');
var categories = require('../src/categories');
var helpers = require('./helpers');
var meta = require('../src/meta');
var socketAdmin = require('../src/socket.io/admin');
describe('socket.io', function () {
var io;
@ -156,7 +159,6 @@ describe('socket.io', function () {
});
it('should make user admin', function (done) {
var socketAdmin = require('../src/socket.io/admin');
socketAdmin.user.makeAdmins({ uid: adminUid }, [regularUid], function (err) {
assert.ifError(err);
groups.isMember(regularUid, 'administrators', function (err, isMember) {
@ -168,7 +170,6 @@ describe('socket.io', function () {
});
it('should make user non-admin', function (done) {
var socketAdmin = require('../src/socket.io/admin');
socketAdmin.user.removeAdmins({ uid: adminUid }, [regularUid], function (err) {
assert.ifError(err);
groups.isMember(regularUid, 'administrators', function (err, isMember) {
@ -180,7 +181,6 @@ describe('socket.io', function () {
});
describe('create/delete', function () {
var socketAdmin = require('../src/socket.io/admin');
var uid;
it('should create a user', function (done) {
socketAdmin.user.createUser({ uid: adminUid }, { username: 'foo1' }, function (err, _uid) {
@ -214,7 +214,6 @@ describe('socket.io', function () {
});
it('should error with invalid data', function (done) {
var socketAdmin = require('../src/socket.io/admin');
socketAdmin.user.createUser({ uid: adminUid }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
@ -222,7 +221,6 @@ describe('socket.io', function () {
});
it('should reset lockouts', function (done) {
var socketAdmin = require('../src/socket.io/admin');
socketAdmin.user.resetLockouts({ uid: adminUid }, [regularUid], function (err) {
assert.ifError(err);
done();
@ -230,7 +228,6 @@ describe('socket.io', function () {
});
describe('validation emails', function () {
var socketAdmin = require('../src/socket.io/admin');
var meta = require('../src/meta');
it('should validate emails', function (done) {
@ -245,7 +242,6 @@ describe('socket.io', function () {
});
it('should error with invalid uids', function (done) {
var socketAdmin = require('../src/socket.io/admin');
socketAdmin.user.sendValidationEmail({ uid: adminUid }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
@ -253,7 +249,6 @@ describe('socket.io', function () {
});
it('should error if email validation is not required', function (done) {
var socketAdmin = require('../src/socket.io/admin');
socketAdmin.user.sendValidationEmail({ uid: adminUid }, [regularUid], function (err) {
assert.equal(err.message, '[[error:email-confirmations-are-disabled]]');
done();
@ -261,7 +256,6 @@ describe('socket.io', function () {
});
it('should send validation email', function (done) {
var socketAdmin = require('../src/socket.io/admin');
meta.config.requireEmailConfirmation = 1;
socketAdmin.user.sendValidationEmail({ uid: adminUid }, [regularUid], function (err) {
assert.ifError(err);
@ -272,7 +266,6 @@ describe('socket.io', function () {
});
it('should search users', function (done) {
var socketAdmin = require('../src/socket.io/admin');
socketAdmin.user.search({ uid: adminUid }, { query: 'reg', searchBy: 'username' }, function (err, data) {
assert.ifError(err);
assert.equal(data.matchCount, 1);
@ -328,6 +321,13 @@ describe('socket.io', function () {
});
});
it('should error to get daily analytics with invalid data', function (done) {
io.emit('admin.analytics.get', null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should get daily analytics', function (done) {
io.emit('admin.analytics.get', { graph: 'traffic', units: 'days' }, function (err, data) {
assert.ifError(err);
@ -347,7 +347,6 @@ describe('socket.io', function () {
});
it('should return error', function (done) {
var socketAdmin = require('../src/socket.io/admin');
socketAdmin.before({ uid: 10 }, 'someMethod', {}, function (err) {
assert.equal(err.message, '[[error:no-privileges]]');
done();
@ -355,8 +354,6 @@ describe('socket.io', function () {
});
it('should get room stats', function (done) {
var socketAdmin = require('../src/socket.io/admin');
io.emit('meta.rooms.enter', { enter: 'topic_1' }, function (err) {
assert.ifError(err);
socketAdmin.rooms.getAll({ uid: 10 }, {}, function (err) {
@ -378,8 +375,6 @@ describe('socket.io', function () {
});
it('should get room stats', function (done) {
var socketAdmin = require('../src/socket.io/admin');
io.emit('meta.rooms.enter', { enter: 'category_1' }, function (err) {
assert.ifError(err);
socketAdmin.rooms.getAll({ uid: 10 }, {}, function (err) {
@ -396,7 +391,6 @@ describe('socket.io', function () {
});
it('should get admin search dictionary', function (done) {
var socketAdmin = require('../src/socket.io/admin');
socketAdmin.getSearchDict({ uid: adminUid }, {}, function (err, data) {
assert.ifError(err);
assert(Array.isArray(data));
@ -407,6 +401,173 @@ describe('socket.io', function () {
});
});
it('should fire event', function (done) {
io.on('testEvent', function (data) {
assert.equal(data.foo, 1);
done();
});
socketAdmin.fireEvent({ uid: adminUid }, { name: 'testEvent', payload: { foo: 1 } }, function (err) {
assert.ifError(err);
});
});
it('should error with invalid data', function (done) {
socketAdmin.themes.set({ uid: adminUid }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should set theme to bootswatch', function (done) {
socketAdmin.themes.set({ uid: adminUid }, { type: 'bootswatch', src: 'darkly' }, function (err) {
assert.ifError(err);
meta.configs.get('theme:src', function (err, id) {
assert.ifError(err);
assert.equal(id, 'darkly');
done();
});
});
});
it('should set theme to local persona', function (done) {
socketAdmin.themes.set({ uid: adminUid }, { type: 'local', id: 'nodebb-theme-persona' }, function (err) {
assert.ifError(err);
meta.configs.get('theme:id', function (err, id) {
assert.ifError(err);
assert.equal(id, 'nodebb-theme-persona');
done();
});
});
});
it('should toggle plugin active', function (done) {
socketAdmin.plugins.toggleActive({ uid: adminUid }, 'nodebb-plugin-location-to-map', function (err, data) {
assert.ifError(err);
assert.deepEqual(data, { id: 'nodebb-plugin-location-to-map', active: true });
done();
});
});
it('should toggle plugin install', function (done) {
socketAdmin.plugins.toggleInstall({ uid: adminUid }, { id: 'nodebb-plugin-location-to-map', version: 'latest' }, function (err, data) {
assert.ifError(err);
assert.equal(data.name, 'nodebb-plugin-location-to-map');
done();
});
});
it('should get list of active plugins', function (done) {
socketAdmin.plugins.getActive({ uid: adminUid }, {}, function (err, data) {
assert.ifError(err);
assert(Array.isArray(data));
done();
});
});
it('should order active plugins', function (done) {
var data = [
{ name: 'nodebb-theme-persona', order: 0 },
{ name: 'nodebb-plugin-dbsearch', order: 1 },
{ ignoreme: 'wrong data' },
];
socketAdmin.plugins.orderActivePlugins({ uid: adminUid }, data, function (err) {
assert.ifError(err);
db.sortedSetRank('plugins:active', 'nodebb-plugin-dbsearch', function (err, rank) {
assert.ifError(err);
assert.equal(rank, 1);
done();
});
});
});
it('should upgrade plugin', function (done) {
socketAdmin.plugins.upgrade({ uid: adminUid }, { id: 'nodebb-plugin-location-to-map', version: 'latest' }, function (err) {
assert.ifError(err);
done();
});
});
it('should error with invalid data', function (done) {
socketAdmin.widgets.set({ uid: adminUid }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should error with invalid data', function (done) {
var data = { template: 'global', location: 'sidebar', widgets: [{ widget: 'html', data: { html: 'test', title: 'test', container: '' } }] };
socketAdmin.widgets.set({ uid: adminUid }, data, function (err) {
assert.ifError(err);
db.getObjectField('widgets:global', 'sidebar', function (err, widgetData) {
assert.ifError(err);
assert.equal(JSON.parse(widgetData)[0].data.html, 'test');
done();
});
});
});
it('should clear sitemap cache', function (done) {
socketAdmin.settings.clearSitemapCache({ uid: adminUid }, {}, function (err) {
assert.ifError(err);
done();
});
});
it('should send test email', function (done) {
socketAdmin.email.test({ uid: adminUid }, { template: 'digest.tpl' }, function (err) {
assert.ifError(err);
done();
});
});
it('should get logs', function (done) {
var fs = require('fs');
var path = require('path');
meta.logs.path = path.join(nconf.get('base_dir'), 'test/files', 'output.log');
fs.appendFile(meta.logs.path, 'some logs', function (err) {
assert.ifError(err);
socketAdmin.logs.get({ uid: adminUid }, {}, function (err, data) {
assert.ifError(err);
assert(data);
done();
});
});
});
it('should clear logs', function (done) {
socketAdmin.logs.clear({ uid: adminUid }, {}, function (err) {
assert.ifError(err);
socketAdmin.logs.get({ uid: adminUid }, {}, function (err, data) {
assert.ifError(err);
assert.equal(data.length, 0);
done();
});
});
});
it('should clear errors', function (done) {
socketAdmin.errors.clear({ uid: adminUid }, {}, function (err) {
assert.ifError(err);
db.exists('error:404', function (err, exists) {
assert.ifError(err);
assert(!exists);
done();
});
});
});
it('shoudl delete all events', function (done) {
socketAdmin.deleteAllEvents({ uid: adminUid }, {}, function (err) {
assert.ifError(err);
db.sortedSetCard('events:time', function (err, count) {
assert.ifError(err);
assert.equal(count, 0);
done();
});
});
});
after(function (done) {
db.emptydb(done);

Loading…
Cancel
Save