mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-08-05 16:39:02 +02:00
Merge commit 'a19537dc25e406a98048a561f45b3b321c9d3509' into v1.7.x
This commit is contained in:
@@ -63,12 +63,11 @@ var fallbackCacheInProgress = {};
|
||||
var fallbackCache = {};
|
||||
|
||||
function initFallback(namespace, callback) {
|
||||
fs.readFile(path.resolve(nconf.get('views_dir'), namespace + '.tpl'), function (err, file) {
|
||||
fs.readFile(path.resolve(nconf.get('views_dir'), namespace + '.tpl'), 'utf8', function (err, template) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var template = file.toString();
|
||||
var title = nsToTitle(namespace);
|
||||
|
||||
var translations = sanitize(template);
|
||||
|
||||
@@ -153,10 +153,10 @@ Categories.getCategories = function (cids, uid, callback) {
|
||||
uid = parseInt(uid, 10);
|
||||
results.categories.forEach(function (category, i) {
|
||||
if (category) {
|
||||
category['unread-class'] = (parseInt(category.topic_count, 10) === 0 || (results.hasRead[i] && uid !== 0)) ? '' : 'unread';
|
||||
category.children = results.children[i];
|
||||
category.parent = results.parents[i] || undefined;
|
||||
category.tagWhitelist = results.tagWhitelist[i];
|
||||
category['unread-class'] = (parseInt(category.topic_count, 10) === 0 || (results.hasRead[i] && uid !== 0)) ? '' : 'unread';
|
||||
calculateTopicPostCount(category);
|
||||
}
|
||||
});
|
||||
@@ -259,9 +259,25 @@ function getChildrenRecursive(category, uid, callback) {
|
||||
}
|
||||
Categories.getCategoriesData(children, next);
|
||||
},
|
||||
function (childrenData, next) {
|
||||
childrenData = childrenData.filter(Boolean);
|
||||
category.children = childrenData;
|
||||
function (children, next) {
|
||||
children = children.filter(Boolean);
|
||||
category.children = children;
|
||||
|
||||
var cids = children.map(function (child) {
|
||||
return child.cid;
|
||||
});
|
||||
|
||||
Categories.hasReadCategories(cids, uid, next);
|
||||
},
|
||||
function (hasRead, next) {
|
||||
hasRead.forEach(function (read, i) {
|
||||
var child = category.children[i];
|
||||
child['unread-class'] = (parseInt(child.topic_count, 10) === 0 || (read && uid !== 0)) ? '' : 'unread';
|
||||
});
|
||||
|
||||
next();
|
||||
},
|
||||
function (next) {
|
||||
async.each(category.children, function (child, next) {
|
||||
getChildrenRecursive(child, uid, next);
|
||||
}, next);
|
||||
|
||||
127
src/cli/colors.js
Normal file
127
src/cli/colors.js
Normal file
@@ -0,0 +1,127 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
// override commander functions
|
||||
// to include color styling in the output
|
||||
// so the CLI looks nice
|
||||
|
||||
var Command = require('commander').Command;
|
||||
|
||||
var commandColor = 'yellow';
|
||||
var optionColor = 'cyan';
|
||||
var argColor = 'magenta';
|
||||
var subCommandColor = 'green';
|
||||
var subOptionColor = 'blue';
|
||||
var subArgColor = 'red';
|
||||
|
||||
Command.prototype.helpInformation = function () {
|
||||
var desc = [];
|
||||
if (this._description) {
|
||||
desc = [
|
||||
' ' + this._description,
|
||||
'',
|
||||
];
|
||||
}
|
||||
|
||||
var cmdName = this._name;
|
||||
if (this._alias) {
|
||||
cmdName = cmdName + ' | ' + this._alias;
|
||||
}
|
||||
var usage = [
|
||||
'',
|
||||
' Usage: ' + cmdName[commandColor] + ' '.reset + this.usage(),
|
||||
'',
|
||||
];
|
||||
|
||||
var cmds = [];
|
||||
var commandHelp = this.commandHelp();
|
||||
if (commandHelp) {
|
||||
cmds = [commandHelp];
|
||||
}
|
||||
|
||||
var options = [
|
||||
'',
|
||||
' Options:',
|
||||
'',
|
||||
'' + this.optionHelp().replace(/^/gm, ' '),
|
||||
'',
|
||||
];
|
||||
|
||||
return usage
|
||||
.concat(desc)
|
||||
.concat(options)
|
||||
.concat(cmds)
|
||||
.join('\n'.reset);
|
||||
};
|
||||
|
||||
function humanReadableArgName(arg) {
|
||||
var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
|
||||
|
||||
return arg.required ? '<' + nameOutput + '>' : '[' + nameOutput + ']';
|
||||
}
|
||||
|
||||
Command.prototype.usage = function () {
|
||||
var args = this._args.map(function (arg) {
|
||||
return humanReadableArgName(arg);
|
||||
});
|
||||
|
||||
var usage = '[options]'[optionColor] +
|
||||
(this.commands.length ? ' [command]' : '')[subCommandColor] +
|
||||
(this._args.length ? ' ' + args.join(' ') : '')[argColor];
|
||||
|
||||
return usage;
|
||||
};
|
||||
|
||||
function pad(str, width) {
|
||||
var len = Math.max(0, width - str.length);
|
||||
return str + Array(len + 1).join(' ');
|
||||
}
|
||||
|
||||
Command.prototype.commandHelp = function () {
|
||||
if (!this.commands.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var commands = this.commands.filter(function (cmd) {
|
||||
return !cmd._noHelp;
|
||||
}).map(function (cmd) {
|
||||
var args = cmd._args.map(function (arg) {
|
||||
return humanReadableArgName(arg);
|
||||
}).join(' ');
|
||||
|
||||
return [
|
||||
cmd._name[subCommandColor] +
|
||||
(cmd._alias ? ' | ' + cmd._alias : '')[subCommandColor] +
|
||||
(cmd.options.length ? ' [options]' : '')[subOptionColor] +
|
||||
' ' + args[subArgColor],
|
||||
cmd._description,
|
||||
];
|
||||
});
|
||||
|
||||
var width = commands.reduce(function (max, command) {
|
||||
return Math.max(max, command[0].length);
|
||||
}, 0);
|
||||
|
||||
return [
|
||||
'',
|
||||
' Commands:',
|
||||
'',
|
||||
commands.map(function (cmd) {
|
||||
var desc = cmd[1] ? ' ' + cmd[1] : '';
|
||||
return pad(cmd[0], width) + desc;
|
||||
}).join('\n').replace(/^/gm, ' '),
|
||||
'',
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
Command.prototype.optionHelp = function () {
|
||||
var width = this.largestOptionLength();
|
||||
|
||||
// Append the help information
|
||||
return this.options
|
||||
.map(function (option) {
|
||||
return pad(option.flags, width)[optionColor] + ' ' + option.description;
|
||||
})
|
||||
.concat([pad('-h, --help', width)[optionColor] + ' output usage information'])
|
||||
.join('\n');
|
||||
};
|
||||
264
src/cli/index.js
Normal file
264
src/cli/index.js
Normal file
@@ -0,0 +1,264 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var packageInstall = require('../meta/package-install');
|
||||
var dirname = require('./paths').baseDir;
|
||||
|
||||
// check to make sure dependencies are installed
|
||||
try {
|
||||
fs.readFileSync(path.join(dirname, 'package.json'));
|
||||
fs.readFileSync(path.join(dirname, 'node_modules/async/package.json'));
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
console.warn('Dependencies not yet installed.');
|
||||
console.log('Installing them now...\n');
|
||||
|
||||
packageInstall.updatePackageFile();
|
||||
packageInstall.preserveExtraneousPlugins();
|
||||
packageInstall.npmInstallProduction();
|
||||
|
||||
require('colors');
|
||||
console.log('OK'.green + '\n'.reset);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
require('colors');
|
||||
var nconf = require('nconf');
|
||||
var program = require('commander');
|
||||
|
||||
var pkg = require('../../package.json');
|
||||
var file = require('../file');
|
||||
var prestart = require('../prestart');
|
||||
|
||||
program
|
||||
.name('./nodebb')
|
||||
.description('Welcome to NodeBB')
|
||||
.version(pkg.version)
|
||||
.option('--json-logging', 'Output to logs in JSON format', false)
|
||||
.option('--log-level <level>', 'Default logging level to use', 'info')
|
||||
.option('-d, --dev', 'Development mode, including verbose logging', false)
|
||||
.option('-l, --log', 'Log subprocess output to console', false)
|
||||
.option('-c, --config <value>', 'Specify a config file', 'config.json')
|
||||
.parse(process.argv);
|
||||
|
||||
nconf.argv().env({
|
||||
separator: '__',
|
||||
});
|
||||
|
||||
var env = program.dev ? 'development' : (process.env.NODE_ENV || 'production');
|
||||
process.env.NODE_ENV = env;
|
||||
global.env = env;
|
||||
|
||||
prestart.setupWinston();
|
||||
|
||||
// Alternate configuration file support
|
||||
var configFile = path.resolve(dirname, program.config);
|
||||
var configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database'));
|
||||
|
||||
prestart.loadConfig(configFile);
|
||||
prestart.versionCheck();
|
||||
|
||||
if (!configExists && process.argv[2] !== 'setup') {
|
||||
require('./setup').webInstall();
|
||||
return;
|
||||
}
|
||||
|
||||
// running commands
|
||||
program
|
||||
.command('start')
|
||||
.description('Start the NodeBB server')
|
||||
.action(function () {
|
||||
require('./running').start(program);
|
||||
});
|
||||
program
|
||||
.command('slog', null, {
|
||||
noHelp: true,
|
||||
})
|
||||
.description('Start the NodeBB server and view the live output log')
|
||||
.action(function () {
|
||||
program.log = true;
|
||||
require('./running').start(program);
|
||||
});
|
||||
program
|
||||
.command('dev', null, {
|
||||
noHelp: true,
|
||||
})
|
||||
.description('Start NodeBB in verbose development mode')
|
||||
.action(function () {
|
||||
program.dev = true;
|
||||
process.env.NODE_ENV = 'development';
|
||||
global.env = 'development';
|
||||
require('./running').start(program);
|
||||
});
|
||||
program
|
||||
.command('stop')
|
||||
.description('Stop the NodeBB server')
|
||||
.action(function () {
|
||||
require('./running').stop(program);
|
||||
});
|
||||
program
|
||||
.command('restart')
|
||||
.description('Restart the NodeBB server')
|
||||
.action(function () {
|
||||
require('./running').restart(program);
|
||||
});
|
||||
program
|
||||
.command('status')
|
||||
.description('Check the running status of the NodeBB server')
|
||||
.action(function () {
|
||||
require('./running').status(program);
|
||||
});
|
||||
program
|
||||
.command('log')
|
||||
.description('Open the output log (useful for debugging)')
|
||||
.action(function () {
|
||||
require('./running').log(program);
|
||||
});
|
||||
|
||||
// management commands
|
||||
program
|
||||
.command('setup')
|
||||
.description('Run the NodeBB setup script')
|
||||
.action(function () {
|
||||
require('./setup').setup();
|
||||
});
|
||||
|
||||
program
|
||||
.command('install')
|
||||
.description('Launch the NodeBB web installer for configuration setup')
|
||||
.action(function () {
|
||||
require('./setup').webInstall();
|
||||
});
|
||||
program
|
||||
.command('build [targets...]')
|
||||
.description('Compile static assets ' + '(JS, CSS, templates, languages, sounds)'.red)
|
||||
.action(function (targets) {
|
||||
require('./manage').build(targets.length ? targets : true);
|
||||
})
|
||||
.on('--help', function () {
|
||||
require('./manage').buildTargets();
|
||||
});
|
||||
program
|
||||
.command('activate [plugin]')
|
||||
.description('Activate a plugin for the next startup of NodeBB (nodebb-plugin- prefix is optional)')
|
||||
.action(function (plugin) {
|
||||
require('./manage').activate(plugin);
|
||||
});
|
||||
program
|
||||
.command('plugins')
|
||||
.action(function () {
|
||||
require('./manage').listPlugins();
|
||||
})
|
||||
.description('List all installed plugins');
|
||||
program
|
||||
.command('events')
|
||||
.description('Outputs the last ten (10) administrative events recorded by NodeBB')
|
||||
.action(function () {
|
||||
require('./manage').listEvents();
|
||||
});
|
||||
program
|
||||
.command('info')
|
||||
.description('Outputs various system info')
|
||||
.action(function () {
|
||||
require('./manage').info();
|
||||
});
|
||||
|
||||
// reset
|
||||
var resetCommand = program.command('reset');
|
||||
|
||||
resetCommand
|
||||
.description('Reset plugins, themes, settings, etc')
|
||||
.option('-t, --theme [theme]', 'Reset to [theme] or to the default theme')
|
||||
.option('-p, --plugin [plugin]', 'Disable [plugin] or all plugins')
|
||||
.option('-w, --widgets', 'Disable all widgets')
|
||||
.option('-s, --settings', 'Reset settings to their default values')
|
||||
.option('-a, --all', 'All of the above')
|
||||
.action(function (options) {
|
||||
var valid = ['theme', 'plugin', 'widgets', 'settings', 'all'].some(function (x) {
|
||||
return options[x];
|
||||
});
|
||||
if (!valid) {
|
||||
console.warn('\n No valid options passed in, so nothing was reset.'.red);
|
||||
resetCommand.help();
|
||||
}
|
||||
|
||||
require('./reset').reset(options, function (err) {
|
||||
if (err) { throw err; }
|
||||
require('../meta/build').buildAll(function (err) {
|
||||
if (err) { throw err; }
|
||||
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// upgrades
|
||||
program
|
||||
.command('upgrade [scripts...]')
|
||||
.description('Run NodeBB upgrade scripts and ensure packages are up-to-date, or run a particular upgrade script')
|
||||
.option('-m, --package', 'Update package.json from defaults', false)
|
||||
.option('-i, --install', 'Bringing base dependencies up to date', false)
|
||||
.option('-p, --plugins', 'Check installed plugins for updates', false)
|
||||
.option('-s, --schema', 'Update NodeBB data store schema', false)
|
||||
.option('-b, --build', 'Rebuild assets', false)
|
||||
.on('--help', function () {
|
||||
console.log('\n' + [
|
||||
'When running particular upgrade scripts, options are ignored.',
|
||||
'By default all options are enabled. Passing any options disables that default.',
|
||||
'Only package and dependency updates: ' + './nodebb upgrade -mi'.yellow,
|
||||
'Only database update: ' + './nodebb upgrade -d'.yellow,
|
||||
].join('\n'));
|
||||
})
|
||||
.action(function (scripts, options) {
|
||||
require('./upgrade').upgrade(scripts.length ? scripts : true, options);
|
||||
});
|
||||
|
||||
program
|
||||
.command('upgrade-plugins', null, {
|
||||
noHelp: true,
|
||||
})
|
||||
.alias('upgradePlugins')
|
||||
.description('Upgrade plugins')
|
||||
.action(function () {
|
||||
require('./upgrade-plugins').upgradePlugins(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
console.log('OK'.green);
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
|
||||
program
|
||||
.command('help [command]')
|
||||
.description('Display help for [command]')
|
||||
.action(function (name) {
|
||||
if (!name) {
|
||||
return program.help();
|
||||
}
|
||||
|
||||
var command = program.commands.find(function (command) { return command._name === name; });
|
||||
if (command) {
|
||||
command.help();
|
||||
} else {
|
||||
program.help();
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('*', {}, {
|
||||
noHelp: true,
|
||||
})
|
||||
.action(function () {
|
||||
program.help();
|
||||
});
|
||||
|
||||
require('./colors');
|
||||
|
||||
program.executables = false;
|
||||
|
||||
program.parse(process.argv);
|
||||
142
src/cli/manage.js
Normal file
142
src/cli/manage.js
Normal file
@@ -0,0 +1,142 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
var childProcess = require('child_process');
|
||||
var _ = require('lodash');
|
||||
|
||||
var build = require('../meta/build');
|
||||
var db = require('../database');
|
||||
var plugins = require('../plugins');
|
||||
var events = require('../events');
|
||||
var reset = require('./reset');
|
||||
|
||||
function buildTargets() {
|
||||
var aliases = build.aliases;
|
||||
var length = 0;
|
||||
var output = Object.keys(aliases).map(function (name) {
|
||||
var arr = aliases[name];
|
||||
if (name.length > length) {
|
||||
length = name.length;
|
||||
}
|
||||
|
||||
return [name, arr.join(', ')];
|
||||
}).map(function (tuple) {
|
||||
return ' ' + _.padEnd('"' + tuple[0] + '"', length + 2).magenta + ' | ' + tuple[1];
|
||||
}).join('\n');
|
||||
console.log(
|
||||
'\n\n Build targets:\n' +
|
||||
('\n ' + _.padEnd('Target', length + 2) + ' | Aliases').green +
|
||||
'\n ------------------------------------------------------\n'.blue +
|
||||
output + '\n'
|
||||
);
|
||||
}
|
||||
|
||||
function activate(plugin) {
|
||||
if (plugin.startsWith('nodebb-theme-')) {
|
||||
reset.reset({
|
||||
theme: plugin,
|
||||
}, function (err) {
|
||||
if (err) { throw err; }
|
||||
process.exit();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.init(next);
|
||||
},
|
||||
function (next) {
|
||||
if (!plugin.startsWith('nodebb-')) {
|
||||
// Allow omission of `nodebb-plugin-`
|
||||
plugin = 'nodebb-plugin-' + plugin;
|
||||
}
|
||||
plugins.isInstalled(plugin, next);
|
||||
},
|
||||
function (isInstalled, next) {
|
||||
if (!isInstalled) {
|
||||
return next(new Error('plugin not installed'));
|
||||
}
|
||||
|
||||
winston.info('Activating plugin `%s`', plugin);
|
||||
db.sortedSetAdd('plugins:active', 0, plugin, next);
|
||||
},
|
||||
function (next) {
|
||||
events.log({
|
||||
type: 'plugin-activate',
|
||||
text: plugin,
|
||||
}, next);
|
||||
},
|
||||
], function (err) {
|
||||
if (err) {
|
||||
winston.error('An error occurred during plugin activation', err);
|
||||
throw err;
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
function listPlugins() {
|
||||
async.waterfall([
|
||||
db.init,
|
||||
function (next) {
|
||||
db.getSortedSetRange('plugins:active', 0, -1, next);
|
||||
},
|
||||
function (plugins) {
|
||||
winston.info('Active plugins: \n\t - ' + plugins.join('\n\t - '));
|
||||
process.exit();
|
||||
},
|
||||
], function (err) {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function listEvents() {
|
||||
async.series([
|
||||
db.init,
|
||||
events.output,
|
||||
]);
|
||||
}
|
||||
|
||||
function info() {
|
||||
console.log('');
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
var version = require('../../package.json').version;
|
||||
console.log(' version: ' + version);
|
||||
|
||||
console.log(' Node ver: ' + process.version);
|
||||
next();
|
||||
},
|
||||
function (next) {
|
||||
var hash = childProcess.execSync('git rev-parse HEAD');
|
||||
console.log(' git hash: ' + hash);
|
||||
next();
|
||||
},
|
||||
function (next) {
|
||||
var config = require('../../config.json');
|
||||
console.log(' database: ' + config.database);
|
||||
next();
|
||||
},
|
||||
db.init,
|
||||
function (next) {
|
||||
db.info(db.client, next);
|
||||
},
|
||||
function (info, next) {
|
||||
console.log(' version: ' + info.version);
|
||||
console.log(' engine: ' + info.storageEngine);
|
||||
next();
|
||||
},
|
||||
], function (err) {
|
||||
if (err) { throw err; }
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
exports.build = build.build;
|
||||
exports.buildTargets = buildTargets;
|
||||
exports.activate = activate;
|
||||
exports.listPlugins = listPlugins;
|
||||
exports.listEvents = listEvents;
|
||||
exports.info = info;
|
||||
15
src/cli/paths.js
Normal file
15
src/cli/paths.js
Normal file
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
|
||||
var baseDir = path.join(__dirname, '../../');
|
||||
var loader = path.join(baseDir, 'loader.js');
|
||||
var app = path.join(baseDir, 'app.js');
|
||||
var pidfile = path.join(baseDir, 'pidfile');
|
||||
|
||||
module.exports = {
|
||||
baseDir: baseDir,
|
||||
loader: loader,
|
||||
app: app,
|
||||
pidfile: pidfile,
|
||||
};
|
||||
@@ -3,79 +3,86 @@
|
||||
require('colors');
|
||||
var path = require('path');
|
||||
var winston = require('winston');
|
||||
var nconf = require('nconf');
|
||||
var async = require('async');
|
||||
var db = require('./database');
|
||||
var events = require('./events');
|
||||
var fs = require('fs');
|
||||
|
||||
var Reset = {};
|
||||
var db = require('../database');
|
||||
var events = require('../events');
|
||||
var meta = require('../meta');
|
||||
var plugins = require('../plugins');
|
||||
var widgets = require('../widgets');
|
||||
|
||||
Reset.reset = function (callback) {
|
||||
db.init(function (err) {
|
||||
if (err) {
|
||||
winston.error(err);
|
||||
throw err;
|
||||
}
|
||||
var dirname = require('./paths').baseDir;
|
||||
|
||||
if (nconf.get('t')) {
|
||||
var themeId = nconf.get('t');
|
||||
exports.reset = function (options, callback) {
|
||||
var map = {
|
||||
theme: function (next) {
|
||||
var themeId = options.theme;
|
||||
if (themeId === true) {
|
||||
resetThemes(callback);
|
||||
resetThemes(next);
|
||||
} else {
|
||||
if (themeId.indexOf('nodebb-') !== 0) {
|
||||
if (!themeId.startsWith('nodebb-theme-')) {
|
||||
// Allow omission of `nodebb-theme-`
|
||||
themeId = 'nodebb-theme-' + themeId;
|
||||
}
|
||||
|
||||
resetTheme(themeId, callback);
|
||||
resetTheme(themeId, next);
|
||||
}
|
||||
} else if (nconf.get('p')) {
|
||||
var pluginId = nconf.get('p');
|
||||
},
|
||||
plugin: function (next) {
|
||||
var pluginId = options.plugin;
|
||||
if (pluginId === true) {
|
||||
resetPlugins(callback);
|
||||
resetPlugins(next);
|
||||
} else {
|
||||
if (pluginId.indexOf('nodebb-') !== 0) {
|
||||
if (!pluginId.startsWith('nodebb-plugin-')) {
|
||||
// Allow omission of `nodebb-plugin-`
|
||||
pluginId = 'nodebb-plugin-' + pluginId;
|
||||
}
|
||||
|
||||
resetPlugin(pluginId, callback);
|
||||
resetPlugin(pluginId, next);
|
||||
}
|
||||
} else if (nconf.get('w')) {
|
||||
resetWidgets(callback);
|
||||
} else if (nconf.get('s')) {
|
||||
resetSettings(callback);
|
||||
} else if (nconf.get('a')) {
|
||||
require('async').series([resetWidgets, resetThemes, resetPlugins, resetSettings], function (err) {
|
||||
if (!err) {
|
||||
winston.info('[reset] Reset complete.');
|
||||
} else {
|
||||
winston.error('[reset] Errors were encountered while resetting your forum settings: %s', err);
|
||||
}
|
||||
},
|
||||
widgets: resetWidgets,
|
||||
settings: resetSettings,
|
||||
all: function (next) {
|
||||
async.series([resetWidgets, resetThemes, resetPlugins, resetSettings], next);
|
||||
},
|
||||
};
|
||||
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
process.stdout.write('\nNodeBB Reset\n'.bold);
|
||||
process.stdout.write('No arguments passed in, so nothing was reset.\n\n'.yellow);
|
||||
process.stdout.write('Use ./nodebb reset ' + '{-t|-p|-w|-s|-a}\n'.red);
|
||||
process.stdout.write(' -t\tthemes\n');
|
||||
process.stdout.write(' -p\tplugins\n');
|
||||
process.stdout.write(' -w\twidgets\n');
|
||||
process.stdout.write(' -s\tsettings\n');
|
||||
process.stdout.write(' -a\tall of the above\n');
|
||||
var tasks = Object.keys(map)
|
||||
.filter(function (x) { return options[x]; })
|
||||
.map(function (x) { return map[x]; });
|
||||
|
||||
process.stdout.write('\nPlugin and theme reset flags (-p & -t) can take a single argument\n');
|
||||
process.stdout.write(' e.g. ./nodebb reset -p nodebb-plugin-mentions, ./nodebb reset -t nodebb-theme-persona\n');
|
||||
process.stdout.write(' Prefix is optional, e.g. ./nodebb reset -p markdown, ./nodebb reset -t persona\n');
|
||||
if (!tasks.length) {
|
||||
console.log([
|
||||
'No arguments passed in, so nothing was reset.\n'.yellow,
|
||||
'Use ./nodebb reset ' + '{-t|-p|-w|-s|-a}'.red,
|
||||
' -t\tthemes',
|
||||
' -p\tplugins',
|
||||
' -w\twidgets',
|
||||
' -s\tsettings',
|
||||
' -a\tall of the above',
|
||||
'',
|
||||
'Plugin and theme reset flags (-p & -t) can take a single argument',
|
||||
' e.g. ./nodebb reset -p nodebb-plugin-mentions, ./nodebb reset -t nodebb-theme-persona',
|
||||
' Prefix is optional, e.g. ./nodebb reset -p markdown, ./nodebb reset -t persona',
|
||||
].join('\n'));
|
||||
|
||||
process.exit(0);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async.series([db.init].concat(tasks), function (err) {
|
||||
if (err) {
|
||||
winston.error('[reset] Errors were encountered during reset', err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
winston.info('[reset] Reset complete');
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
function resetSettings(callback) {
|
||||
var meta = require('./meta');
|
||||
meta.configs.set('allowLocalLogin', 1, function (err) {
|
||||
winston.info('[reset] Settings reset to default');
|
||||
callback(err);
|
||||
@@ -83,10 +90,7 @@ function resetSettings(callback) {
|
||||
}
|
||||
|
||||
function resetTheme(themeId, callback) {
|
||||
var meta = require('./meta');
|
||||
var fs = require('fs');
|
||||
|
||||
fs.access(path.join(__dirname, '../node_modules', themeId, 'package.json'), function (err) {
|
||||
fs.access(path.join(dirname, 'node_modules', themeId, 'package.json'), function (err) {
|
||||
if (err) {
|
||||
winston.warn('[reset] Theme `%s` is not installed on this forum', themeId);
|
||||
callback(new Error('theme-not-found'));
|
||||
@@ -108,8 +112,6 @@ function resetTheme(themeId, callback) {
|
||||
}
|
||||
|
||||
function resetThemes(callback) {
|
||||
var meta = require('./meta');
|
||||
|
||||
meta.themes.set({
|
||||
type: 'local',
|
||||
id: 'nodebb-theme-persona',
|
||||
@@ -163,13 +165,11 @@ function resetPlugins(callback) {
|
||||
|
||||
function resetWidgets(callback) {
|
||||
async.waterfall([
|
||||
require('./plugins').reload,
|
||||
require('./widgets').reset,
|
||||
plugins.reload,
|
||||
widgets.reset,
|
||||
function (next) {
|
||||
winston.info('[reset] All Widgets moved to Draft Zone');
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
module.exports = Reset;
|
||||
124
src/cli/running.js
Normal file
124
src/cli/running.js
Normal file
@@ -0,0 +1,124 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var childProcess = require('child_process');
|
||||
|
||||
var fork = require('../meta/debugFork');
|
||||
var paths = require('./paths');
|
||||
|
||||
var dirname = paths.baseDir;
|
||||
|
||||
function getRunningPid(callback) {
|
||||
fs.readFile(paths.pidfile, {
|
||||
encoding: 'utf-8',
|
||||
}, function (err, pid) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
pid = parseInt(pid, 10);
|
||||
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
callback(null, pid);
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function start(options) {
|
||||
if (options.dev) {
|
||||
process.env.NODE_ENV = 'development';
|
||||
fork(paths.loader, ['--no-daemon', '--no-silent'], {
|
||||
env: process.env,
|
||||
cwd: dirname,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (options.log) {
|
||||
console.log('\n' + [
|
||||
'Starting NodeBB with logging output'.bold,
|
||||
'Hit '.red + 'Ctrl-C '.bold + 'to exit'.red,
|
||||
'The NodeBB process will continue to run in the background',
|
||||
'Use "' + './nodebb stop'.yellow + '" to stop the NodeBB server',
|
||||
].join('\n'));
|
||||
} else if (!options.silent) {
|
||||
console.log('\n' + [
|
||||
'Starting NodeBB'.bold,
|
||||
' "' + './nodebb stop'.yellow + '" to stop the NodeBB server',
|
||||
' "' + './nodebb log'.yellow + '" to view server output',
|
||||
' "' + './nodebb help'.yellow + '" for more commands\n'.reset,
|
||||
].join('\n'));
|
||||
}
|
||||
|
||||
// Spawn a new NodeBB process
|
||||
var child = fork(paths.loader, process.argv.slice(3), {
|
||||
env: process.env,
|
||||
cwd: dirname,
|
||||
});
|
||||
if (options.log) {
|
||||
childProcess.spawn('tail', ['-F', './logs/output.log'], {
|
||||
cwd: dirname,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
function stop() {
|
||||
getRunningPid(function (err, pid) {
|
||||
if (!err) {
|
||||
process.kill(pid, 'SIGTERM');
|
||||
console.log('Stopping NodeBB. Goodbye!');
|
||||
} else {
|
||||
console.log('NodeBB is already stopped.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function restart(options) {
|
||||
getRunningPid(function (err, pid) {
|
||||
if (!err) {
|
||||
console.log('\nRestarting NodeBB'.bold);
|
||||
process.kill(pid, 'SIGTERM');
|
||||
|
||||
options.silent = true;
|
||||
start(options);
|
||||
} else {
|
||||
console.warn('NodeBB could not be restarted, as a running instance could not be found.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function status() {
|
||||
getRunningPid(function (err, pid) {
|
||||
if (!err) {
|
||||
console.log('\n' + [
|
||||
'NodeBB Running '.bold + ('(pid ' + pid.toString() + ')').cyan,
|
||||
'\t"' + './nodebb stop'.yellow + '" to stop the NodeBB server',
|
||||
'\t"' + './nodebb log'.yellow + '" to view server output',
|
||||
'\t"' + './nodebb restart'.yellow + '" to restart NodeBB\n',
|
||||
].join('\n'));
|
||||
} else {
|
||||
console.log('\nNodeBB is not running'.bold);
|
||||
console.log('\t"' + './nodebb start'.yellow + '" to launch the NodeBB server\n'.reset);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function log() {
|
||||
console.log('\nHit '.red + 'Ctrl-C '.bold + 'to exit\n'.red + '\n'.reset);
|
||||
childProcess.spawn('tail', ['-F', './logs/output.log'], {
|
||||
cwd: dirname,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
|
||||
exports.start = start;
|
||||
exports.stop = stop;
|
||||
exports.restart = restart;
|
||||
exports.status = status;
|
||||
exports.log = log;
|
||||
59
src/cli/setup.js
Normal file
59
src/cli/setup.js
Normal file
@@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
var winston = require('winston');
|
||||
var async = require('async');
|
||||
|
||||
var install = require('../../install/web').install;
|
||||
|
||||
function setup() {
|
||||
var install = require('../install');
|
||||
var build = require('../meta/build');
|
||||
var prestart = require('../prestart');
|
||||
|
||||
winston.info('NodeBB Setup Triggered via Command Line');
|
||||
|
||||
console.log('\nWelcome to NodeBB!');
|
||||
console.log('\nThis looks like a new installation, so you\'ll have to answer a few questions about your environment before we can proceed.');
|
||||
console.log('Press enter to accept the default setting (shown in brackets).');
|
||||
|
||||
async.series([
|
||||
install.setup,
|
||||
prestart.loadConfig,
|
||||
build.buildAll,
|
||||
], function (err, data) {
|
||||
// Disregard build step data
|
||||
data = data[0];
|
||||
|
||||
var separator = ' ';
|
||||
if (process.stdout.columns > 10) {
|
||||
for (var x = 0, cols = process.stdout.columns - 10; x < cols; x += 1) {
|
||||
separator += '=';
|
||||
}
|
||||
}
|
||||
console.log('\n' + separator + '\n');
|
||||
|
||||
if (err) {
|
||||
winston.error('There was a problem completing NodeBB setup', err);
|
||||
throw err;
|
||||
} else {
|
||||
if (data.hasOwnProperty('password')) {
|
||||
console.log('An administrative user was automatically created for you:');
|
||||
console.log(' Username: ' + data.username + '');
|
||||
console.log(' Password: ' + data.password + '');
|
||||
console.log('');
|
||||
}
|
||||
console.log('NodeBB Setup Completed. Run "./nodebb start" to manually start your NodeBB server.');
|
||||
|
||||
// If I am a child process, notify the parent of the returned data before exiting (useful for notifying
|
||||
// hosts of auto-generated username/password during headless setups)
|
||||
if (process.send) {
|
||||
process.send(data);
|
||||
}
|
||||
}
|
||||
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
exports.setup = setup;
|
||||
exports.webInstall = install;
|
||||
216
src/cli/upgrade-plugins.js
Normal file
216
src/cli/upgrade-plugins.js
Normal file
@@ -0,0 +1,216 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var prompt = require('prompt');
|
||||
var request = require('request');
|
||||
var cproc = require('child_process');
|
||||
var semver = require('semver');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var paths = require('./paths');
|
||||
|
||||
var dirname = paths.baseDir;
|
||||
|
||||
function getModuleVersions(modules, callback) {
|
||||
var versionHash = {};
|
||||
|
||||
async.eachLimit(modules, 50, function (module, next) {
|
||||
fs.readFile(path.join(dirname, 'node_modules', module, 'package.json'), { encoding: 'utf-8' }, function (err, pkg) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
try {
|
||||
pkg = JSON.parse(pkg);
|
||||
versionHash[module] = pkg.version;
|
||||
next();
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
}, function (err) {
|
||||
callback(err, versionHash);
|
||||
});
|
||||
}
|
||||
|
||||
function getInstalledPlugins(callback) {
|
||||
async.parallel({
|
||||
files: async.apply(fs.readdir, path.join(dirname, 'node_modules')),
|
||||
deps: async.apply(fs.readFile, path.join(dirname, 'package.json'), { encoding: 'utf-8' }),
|
||||
}, function (err, payload) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var isNbbModule = /^nodebb-(?:plugin|theme|widget|rewards)-[\w-]+$/;
|
||||
var moduleName;
|
||||
var isGitRepo;
|
||||
|
||||
payload.files = payload.files.filter(function (file) {
|
||||
return isNbbModule.test(file);
|
||||
});
|
||||
|
||||
try {
|
||||
payload.deps = JSON.parse(payload.deps).dependencies;
|
||||
payload.bundled = [];
|
||||
payload.installed = [];
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
for (moduleName in payload.deps) {
|
||||
if (isNbbModule.test(moduleName)) {
|
||||
payload.bundled.push(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
// Whittle down deps to send back only extraneously installed plugins/themes/etc
|
||||
payload.files.forEach(function (moduleName) {
|
||||
try {
|
||||
fs.accessSync(path.join(dirname, 'node_modules', moduleName, '.git'));
|
||||
isGitRepo = true;
|
||||
} catch (e) {
|
||||
isGitRepo = false;
|
||||
}
|
||||
|
||||
if (
|
||||
payload.files.indexOf(moduleName) !== -1 && // found in `node_modules/`
|
||||
payload.bundled.indexOf(moduleName) === -1 && // not found in `package.json`
|
||||
!fs.lstatSync(path.join(dirname, 'node_modules', moduleName)).isSymbolicLink() && // is not a symlink
|
||||
!isGitRepo // .git/ does not exist, so it is not a git repository
|
||||
) {
|
||||
payload.installed.push(moduleName);
|
||||
}
|
||||
});
|
||||
|
||||
getModuleVersions(payload.installed, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function getCurrentVersion(callback) {
|
||||
fs.readFile(path.join(dirname, 'package.json'), { encoding: 'utf-8' }, function (err, pkg) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
try {
|
||||
pkg = JSON.parse(pkg);
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, pkg.version);
|
||||
});
|
||||
}
|
||||
|
||||
function checkPlugins(standalone, callback) {
|
||||
if (standalone) {
|
||||
console.log('Checking installed plugins and themes for updates... ');
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
async.apply(async.parallel, {
|
||||
plugins: async.apply(getInstalledPlugins),
|
||||
version: async.apply(getCurrentVersion),
|
||||
}),
|
||||
function (payload, next) {
|
||||
var toCheck = Object.keys(payload.plugins);
|
||||
|
||||
if (!toCheck.length) {
|
||||
console.log('OK'.green + ''.reset);
|
||||
return next(null, []); // no extraneous plugins installed
|
||||
}
|
||||
|
||||
request({
|
||||
method: 'GET',
|
||||
url: 'https://packages.nodebb.org/api/v1/suggest?version=' + payload.version + '&package[]=' + toCheck.join('&package[]='),
|
||||
json: true,
|
||||
}, function (err, res, body) {
|
||||
if (err) {
|
||||
console.log('error'.red + ''.reset);
|
||||
return next(err);
|
||||
}
|
||||
console.log('OK'.green + ''.reset);
|
||||
|
||||
if (!Array.isArray(body) && toCheck.length === 1) {
|
||||
body = [body];
|
||||
}
|
||||
|
||||
var current;
|
||||
var suggested;
|
||||
var upgradable = body.map(function (suggestObj) {
|
||||
current = payload.plugins[suggestObj.package];
|
||||
suggested = suggestObj.version;
|
||||
|
||||
if (suggestObj.code === 'match-found' && semver.gt(suggested, current)) {
|
||||
return {
|
||||
name: suggestObj.package,
|
||||
current: current,
|
||||
suggested: suggested,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}).filter(Boolean);
|
||||
|
||||
next(null, upgradable);
|
||||
});
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function upgradePlugins(callback) {
|
||||
var standalone = false;
|
||||
if (typeof callback !== 'function') {
|
||||
callback = function () {};
|
||||
standalone = true;
|
||||
}
|
||||
|
||||
checkPlugins(standalone, function (err, found) {
|
||||
if (err) {
|
||||
console.log('Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability'.reset);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (found && found.length) {
|
||||
console.log('\nA total of ' + String(found.length).bold + ' package(s) can be upgraded:');
|
||||
found.forEach(function (suggestObj) {
|
||||
console.log(' * '.yellow + suggestObj.name.reset + ' (' + suggestObj.current.yellow + ' -> '.reset + suggestObj.suggested.green + ')\n'.reset);
|
||||
});
|
||||
console.log('');
|
||||
} else {
|
||||
if (standalone) {
|
||||
console.log('\nAll packages up-to-date!'.green + ''.reset);
|
||||
}
|
||||
return callback();
|
||||
}
|
||||
|
||||
prompt.message = '';
|
||||
prompt.delimiter = '';
|
||||
|
||||
prompt.start();
|
||||
prompt.get({
|
||||
name: 'upgrade',
|
||||
description: 'Proceed with upgrade (y|n)?'.reset,
|
||||
type: 'string',
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (['y', 'Y', 'yes', 'YES'].indexOf(result.upgrade) !== -1) {
|
||||
console.log('\nUpgrading packages...');
|
||||
var args = ['i'];
|
||||
found.forEach(function (suggestObj) {
|
||||
args.push(suggestObj.name + '@' + suggestObj.suggested);
|
||||
});
|
||||
|
||||
cproc.execFile((process.platform === 'win32') ? 'npm.cmd' : 'npm', args, { stdio: 'ignore' }, callback);
|
||||
} else {
|
||||
console.log('Package upgrades skipped'.yellow + '. Check for upgrades at any time by running "'.reset + './nodebb upgrade-plugins'.green + '".'.reset);
|
||||
callback();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
exports.upgradePlugins = upgradePlugins;
|
||||
113
src/cli/upgrade.js
Normal file
113
src/cli/upgrade.js
Normal file
@@ -0,0 +1,113 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var nconf = require('nconf');
|
||||
|
||||
var packageInstall = require('../meta/package-install');
|
||||
var upgrade = require('../upgrade');
|
||||
var build = require('../meta/build');
|
||||
var db = require('../database');
|
||||
var meta = require('../meta');
|
||||
var upgradePlugins = require('./upgrade-plugins').upgradePlugins;
|
||||
|
||||
var steps = {
|
||||
package: {
|
||||
message: 'Updating package.json file with defaults...',
|
||||
handler: function (next) {
|
||||
packageInstall.updatePackageFile();
|
||||
packageInstall.preserveExtraneousPlugins();
|
||||
next();
|
||||
},
|
||||
},
|
||||
install: {
|
||||
message: 'Bringing base dependencies up to date...',
|
||||
handler: function (next) {
|
||||
packageInstall.npmInstallProduction();
|
||||
next();
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
message: 'Checking installed plugins for updates...',
|
||||
handler: function (next) {
|
||||
async.series([
|
||||
db.init,
|
||||
upgradePlugins,
|
||||
], next);
|
||||
},
|
||||
},
|
||||
schema: {
|
||||
message: 'Updating NodeBB data store schema...',
|
||||
handler: function (next) {
|
||||
async.series([
|
||||
db.init,
|
||||
upgrade.run,
|
||||
], next);
|
||||
},
|
||||
},
|
||||
build: {
|
||||
message: 'Rebuilding assets...',
|
||||
handler: build.buildAll,
|
||||
},
|
||||
};
|
||||
|
||||
function runSteps(tasks) {
|
||||
tasks = tasks.map(function (key, i) {
|
||||
return function (next) {
|
||||
console.log(((i + 1) + '. ').bold + steps[key].message.yellow);
|
||||
return steps[key].handler(function (err) {
|
||||
if (err) { return next(err); }
|
||||
console.log(' OK'.green);
|
||||
next();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
async.series(tasks, function (err) {
|
||||
if (err) {
|
||||
console.error('Error occurred during upgrade');
|
||||
throw err;
|
||||
}
|
||||
|
||||
var message = 'NodeBB Upgrade Complete!';
|
||||
// some consoles will return undefined/zero columns, so just use 2 spaces in upgrade script if we can't get our column count
|
||||
var columns = process.stdout.columns;
|
||||
var spaces = columns ? new Array(Math.floor(columns / 2) - (message.length / 2) + 1).join(' ') : ' ';
|
||||
|
||||
console.log('\n' + spaces + message.green.bold + '\n'.reset);
|
||||
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
function runUpgrade(upgrades, options) {
|
||||
console.log('\nUpdating NodeBB...'.cyan);
|
||||
|
||||
// disable mongo timeouts during upgrade
|
||||
nconf.set('mongo:options:socketTimeoutMS', 0);
|
||||
|
||||
if (upgrades === true) {
|
||||
var tasks = Object.keys(steps);
|
||||
if (options.package || options.install ||
|
||||
options.plugins || options.schema || options.build) {
|
||||
tasks = tasks.filter(function (key) {
|
||||
return options[key];
|
||||
});
|
||||
}
|
||||
runSteps(tasks);
|
||||
return;
|
||||
}
|
||||
|
||||
async.series([
|
||||
db.init,
|
||||
meta.configs.init,
|
||||
async.apply(upgrade.runParticular, upgrades),
|
||||
], function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
exports.upgrade = runUpgrade;
|
||||
@@ -84,13 +84,19 @@ settingsController.get = function (req, res, callback) {
|
||||
plugins.fireHook('filter:user.customSettings', { settings: results.settings, customSettings: [], uid: req.uid }, next);
|
||||
},
|
||||
function (data, next) {
|
||||
getHomePageRoutes(userData, function (err, routes) {
|
||||
userData.homePageRoutes = routes;
|
||||
next(err, data);
|
||||
});
|
||||
},
|
||||
function (data) {
|
||||
userData.customSettings = data.customSettings;
|
||||
async.parallel({
|
||||
notificationSettings: function (next) {
|
||||
getNotificationSettings(userData, next);
|
||||
},
|
||||
routes: function (next) {
|
||||
getHomePageRoutes(userData, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results) {
|
||||
userData.homePageRoutes = results.routes;
|
||||
userData.notificationSettings = results.notificationSettings;
|
||||
userData.disableEmailSubscriptions = parseInt(meta.config.disableEmailSubscriptions, 10) === 1;
|
||||
|
||||
userData.dailyDigestFreqOptions = [
|
||||
@@ -129,6 +135,20 @@ settingsController.get = function (req, res, callback) {
|
||||
language.selected = language.code === userData.settings.userLang;
|
||||
});
|
||||
|
||||
var notifFreqOptions = [
|
||||
'all',
|
||||
'everyTen',
|
||||
'logarithmic',
|
||||
'disabled',
|
||||
];
|
||||
|
||||
userData.upvoteNotifFreq = notifFreqOptions.map(function (name) {
|
||||
return {
|
||||
name: name,
|
||||
selected: name === userData.notifFreqOptions,
|
||||
};
|
||||
});
|
||||
|
||||
userData.disableCustomUserSkins = parseInt(meta.config.disableCustomUserSkins, 10) === 1;
|
||||
|
||||
userData.allowUserHomePage = parseInt(meta.config.allowUserHomePage, 10) === 1;
|
||||
@@ -149,6 +169,56 @@ settingsController.get = function (req, res, callback) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
function getNotificationSettings(userData, callback) {
|
||||
var types = [
|
||||
'notificationType_upvote',
|
||||
'notificationType_new-topic',
|
||||
'notificationType_new-reply',
|
||||
'notificationType_follow',
|
||||
'notificationType_new-chat',
|
||||
'notificationType_group-invite',
|
||||
];
|
||||
|
||||
var privilegedTypes = [];
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.getPrivileges(userData.uid, next);
|
||||
},
|
||||
function (privileges, next) {
|
||||
if (privileges.isAdmin) {
|
||||
privilegedTypes.push('notificationType_new-register');
|
||||
}
|
||||
if (privileges.isAdmin || privileges.isGlobalMod || privileges.isModeratorOfAnyCategory) {
|
||||
privilegedTypes.push('notificationType_post-queue', 'notificationType_new-post-flag');
|
||||
}
|
||||
if (privileges.isAdmin || privileges.isGlobalMod) {
|
||||
privilegedTypes.push('notificationType_new-user-flag');
|
||||
}
|
||||
plugins.fireHook('filter:user.notificationTypes', {
|
||||
userData: userData,
|
||||
types: types,
|
||||
privilegedTypes: privilegedTypes,
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
function modifyType(type) {
|
||||
var setting = userData.settings[type] || 'notification';
|
||||
|
||||
return {
|
||||
name: type,
|
||||
label: '[[notifications:' + type + ']]',
|
||||
none: setting === 'none',
|
||||
notification: setting === 'notification',
|
||||
email: setting === 'email',
|
||||
notificationemail: setting === 'notificationemail',
|
||||
};
|
||||
}
|
||||
var notificationSettings = results.types.map(modifyType).concat(results.privilegedTypes.map(modifyType));
|
||||
next(null, notificationSettings);
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function getHomePageRoutes(userData, callback) {
|
||||
async.waterfall([
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var nconf = require('nconf');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var meta = require('../../meta');
|
||||
var file = require('../../file');
|
||||
var emailer = require('../../emailer');
|
||||
|
||||
var settingsController = module.exports;
|
||||
@@ -26,42 +22,8 @@ settingsController.get = function (req, res, next) {
|
||||
|
||||
|
||||
function renderEmail(req, res, next) {
|
||||
var emailsPath = path.join(nconf.get('views_dir'), 'emails');
|
||||
|
||||
async.parallel({
|
||||
emails: function (cb) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
file.walk(emailsPath, next);
|
||||
},
|
||||
function (emails, next) {
|
||||
// exclude .js files
|
||||
emails = emails.filter(function (email) {
|
||||
return !email.endsWith('.js');
|
||||
});
|
||||
|
||||
async.map(emails, function (email, next) {
|
||||
var path = email.replace(emailsPath, '').substr(1).replace('.tpl', '');
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
fs.readFile(email, next);
|
||||
},
|
||||
function (original, next) {
|
||||
var text = meta.config['email:custom:' + path] ? meta.config['email:custom:' + path] : original.toString();
|
||||
|
||||
next(null, {
|
||||
path: path,
|
||||
fullpath: email,
|
||||
text: text,
|
||||
original: original.toString(),
|
||||
});
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
},
|
||||
], cb);
|
||||
},
|
||||
emails: async.apply(emailer.getTemplates, meta.config),
|
||||
services: emailer.listServices,
|
||||
}, function (err, results) {
|
||||
if (err) {
|
||||
|
||||
@@ -23,7 +23,7 @@ themesController.get = function (req, res, next) {
|
||||
return next(Error('invalid-data'));
|
||||
}
|
||||
|
||||
fs.readFile(themeConfigPath, next);
|
||||
fs.readFile(themeConfigPath, 'utf8', next);
|
||||
},
|
||||
function (themeConfig, next) {
|
||||
try {
|
||||
|
||||
@@ -13,9 +13,6 @@ categoriesController.list = function (req, res, next) {
|
||||
res.locals.metaTags = [{
|
||||
name: 'title',
|
||||
content: String(meta.config.title || 'NodeBB'),
|
||||
}, {
|
||||
property: 'og:title',
|
||||
content: '[[pages:categories]]',
|
||||
}, {
|
||||
property: 'og:type',
|
||||
content: 'website',
|
||||
@@ -42,6 +39,10 @@ categoriesController.list = function (req, res, next) {
|
||||
|
||||
if (req.originalUrl.startsWith(nconf.get('relative_path') + '/api/categories') || req.originalUrl.startsWith(nconf.get('relative_path') + '/categories')) {
|
||||
data.breadcrumbs = helpers.buildBreadcrumbs([{ text: data.title }]);
|
||||
res.locals.metaTags.push({
|
||||
property: 'og:title',
|
||||
content: '[[pages:categories]]',
|
||||
});
|
||||
}
|
||||
|
||||
data.categories.forEach(function (category) {
|
||||
|
||||
@@ -8,13 +8,13 @@ var plugins = require('../plugins');
|
||||
var topics = require('../topics');
|
||||
var helpers = require('./helpers');
|
||||
|
||||
exports.get = function (req, res, next) {
|
||||
exports.get = function (req, res, callback) {
|
||||
async.waterfall([
|
||||
function (_next) {
|
||||
plugins.fireHook('filter:composer.build', {
|
||||
req: req,
|
||||
res: res,
|
||||
next: next,
|
||||
next: callback,
|
||||
templateData: {},
|
||||
}, _next);
|
||||
},
|
||||
@@ -28,7 +28,7 @@ exports.get = function (req, res, next) {
|
||||
res.render('compose', data.templateData);
|
||||
}
|
||||
},
|
||||
], next);
|
||||
], callback);
|
||||
};
|
||||
|
||||
exports.post = function (req, res) {
|
||||
|
||||
@@ -36,7 +36,7 @@ function getRouteAllowUserHomePage(uid, next) {
|
||||
pubsub.on('config:update', configUpdated);
|
||||
configUpdated();
|
||||
|
||||
module.exports = function (req, res, next) {
|
||||
function rewrite(req, res, next) {
|
||||
if (req.path !== '/' && req.path !== '/api/' && req.path !== '/api') {
|
||||
return next();
|
||||
}
|
||||
@@ -48,15 +48,26 @@ module.exports = function (req, res, next) {
|
||||
|
||||
var hook = 'action:homepage.get:' + route;
|
||||
|
||||
if (plugins.hasListeners(hook)) {
|
||||
return plugins.fireHook(hook, {
|
||||
req: req,
|
||||
res: res,
|
||||
next: next,
|
||||
});
|
||||
if (!plugins.hasListeners(hook)) {
|
||||
req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + route;
|
||||
} else {
|
||||
res.locals.homePageRoute = route;
|
||||
}
|
||||
|
||||
req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + route;
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
exports.rewrite = rewrite;
|
||||
|
||||
function pluginHook(req, res, next) {
|
||||
var hook = 'action:homepage.get:' + res.locals.homePageRoute;
|
||||
|
||||
plugins.fireHook(hook, {
|
||||
req: req,
|
||||
res: res,
|
||||
next: next,
|
||||
});
|
||||
}
|
||||
|
||||
exports.pluginHook = pluginHook;
|
||||
|
||||
@@ -286,7 +286,7 @@ Controllers.outgoing = function (req, res, next) {
|
||||
var allowedProtocols = ['http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'svn', 'tel', 'fax', 'xmpp', 'webcal'];
|
||||
var parsed = require('url').parse(url);
|
||||
|
||||
if (!url || !allowedProtocols.includes(parsed.protocol.slice(0, -1))) {
|
||||
if (!url || !parsed.protocol || !allowedProtocols.includes(parsed.protocol.slice(0, -1))) {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ mongoModule.questions = [
|
||||
name: 'mongo:uri',
|
||||
description: 'MongoDB connection URI: (leave blank if you wish to specify host, port, username/password and database individually)\nFormat: mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]',
|
||||
default: nconf.get('mongo:uri') || '',
|
||||
hideOnWebInstall: true,
|
||||
},
|
||||
{
|
||||
name: 'mongo:host',
|
||||
|
||||
@@ -102,7 +102,7 @@ module.exports = function (redisClient, module) {
|
||||
|
||||
module.deleteObjectField = function (key, field, callback) {
|
||||
callback = callback || function () {};
|
||||
if (field === null) {
|
||||
if (key === undefined || key === null || field === undefined || field === null) {
|
||||
return setImmediate(callback);
|
||||
}
|
||||
redisClient.hdel(key, field, function (err) {
|
||||
|
||||
152
src/emailer.js
152
src/emailer.js
@@ -8,14 +8,19 @@ var nodemailer = require('nodemailer');
|
||||
var wellKnownServices = require('nodemailer/lib/well-known/services');
|
||||
var htmlToText = require('html-to-text');
|
||||
var url = require('url');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
|
||||
var User = require('./user');
|
||||
var Plugins = require('./plugins');
|
||||
var meta = require('./meta');
|
||||
var translator = require('./translator');
|
||||
var pubsub = require('./pubsub');
|
||||
var file = require('./file');
|
||||
|
||||
var transports = {
|
||||
var Emailer = module.exports;
|
||||
|
||||
Emailer.transports = {
|
||||
sendmail: nodemailer.createTransport({
|
||||
sendmail: true,
|
||||
newline: 'unix',
|
||||
@@ -25,9 +30,45 @@ var transports = {
|
||||
};
|
||||
|
||||
var app;
|
||||
var fallbackTransport;
|
||||
|
||||
var Emailer = module.exports;
|
||||
var viewsDir = nconf.get('views_dir');
|
||||
var emailsPath = path.join(viewsDir, 'emails');
|
||||
|
||||
Emailer.getTemplates = function (config, cb) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
file.walk(emailsPath, next);
|
||||
},
|
||||
function (emails, next) {
|
||||
// exclude .js files
|
||||
emails = emails.filter(function (email) {
|
||||
return !email.endsWith('.js');
|
||||
});
|
||||
|
||||
async.map(emails, function (email, next) {
|
||||
var path = email.replace(emailsPath, '').substr(1).replace('.tpl', '');
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
fs.readFile(email, 'utf8', next);
|
||||
},
|
||||
function (original, next) {
|
||||
var isCustom = !!config['email:custom:' + path];
|
||||
var text = config['email:custom:' + path] || original;
|
||||
|
||||
next(null, {
|
||||
path: path,
|
||||
fullpath: email,
|
||||
text: text,
|
||||
original: original,
|
||||
isCustom: isCustom,
|
||||
});
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
},
|
||||
], cb);
|
||||
};
|
||||
|
||||
Emailer.listServices = function (callback) {
|
||||
var services = Object.keys(wellKnownServices);
|
||||
@@ -71,13 +112,30 @@ Emailer.setupFallbackTransport = function (config) {
|
||||
smtpOptions.service = config['email:smtpTransport:service'];
|
||||
}
|
||||
|
||||
transports.smtp = nodemailer.createTransport(smtpOptions);
|
||||
fallbackTransport = transports.smtp;
|
||||
Emailer.transports.smtp = nodemailer.createTransport(smtpOptions);
|
||||
Emailer.fallbackTransport = Emailer.transports.smtp;
|
||||
} else {
|
||||
fallbackTransport = transports.sendmail;
|
||||
Emailer.fallbackTransport = Emailer.transports.sendmail;
|
||||
}
|
||||
};
|
||||
|
||||
var prevConfig = meta.config;
|
||||
function smtpSettingsChanged(config) {
|
||||
var settings = [
|
||||
'email:smtpTransport:enabled',
|
||||
'email:smtpTransport:user',
|
||||
'email:smtpTransport:pass',
|
||||
'email:smtpTransport:service',
|
||||
'email:smtpTransport:port',
|
||||
'email:smtpTransport:host',
|
||||
'email:smtpTransport:security',
|
||||
];
|
||||
|
||||
return settings.some(function (key) {
|
||||
return config[key] !== prevConfig[key];
|
||||
});
|
||||
}
|
||||
|
||||
Emailer.registerApp = function (expressApp) {
|
||||
app = expressApp;
|
||||
|
||||
@@ -97,16 +155,21 @@ Emailer.registerApp = function (expressApp) {
|
||||
};
|
||||
|
||||
Emailer.setupFallbackTransport(meta.config);
|
||||
buildCustomTemplates(meta.config);
|
||||
|
||||
// Update default payload if new logo is uploaded
|
||||
pubsub.on('config:update', function (config) {
|
||||
if (config) {
|
||||
if ('email:smtpTransport:enabled' in config) {
|
||||
Emailer.setupFallbackTransport(config);
|
||||
}
|
||||
Emailer._defaultPayload.logo.src = config['brand:emailLogo'];
|
||||
Emailer._defaultPayload.logo.height = config['brand:emailLogo:height'];
|
||||
Emailer._defaultPayload.logo.width = config['brand:emailLogo:width'];
|
||||
|
||||
if (smtpSettingsChanged(config)) {
|
||||
Emailer.setupFallbackTransport(config);
|
||||
}
|
||||
buildCustomTemplates(config);
|
||||
|
||||
prevConfig = config;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -150,7 +213,7 @@ Emailer.sendToEmail = function (template, email, language, params, callback) {
|
||||
function (next) {
|
||||
async.parallel({
|
||||
html: function (next) {
|
||||
renderAndTranslate('emails/' + template, params, lang, next);
|
||||
Emailer.renderAndTranslate(template, params, lang, next);
|
||||
},
|
||||
subject: function (next) {
|
||||
translator.translate(params.subject, lang, function (translated) {
|
||||
@@ -203,7 +266,7 @@ Emailer.sendViaFallback = function (data, callback) {
|
||||
delete data.from_name;
|
||||
|
||||
winston.verbose('[emailer] Sending email to uid ' + data.uid + ' (' + data.to + ')');
|
||||
fallbackTransport.sendMail(data, function (err) {
|
||||
Emailer.fallbackTransport.sendMail(data, function (err) {
|
||||
if (err) {
|
||||
winston.error(err);
|
||||
}
|
||||
@@ -211,23 +274,64 @@ Emailer.sendViaFallback = function (data, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
function render(tpl, params, next) {
|
||||
var customTemplate = meta.config['email:custom:' + tpl.replace('emails/', '')];
|
||||
if (customTemplate) {
|
||||
Benchpress.compileParse(customTemplate, params, next);
|
||||
} else {
|
||||
app.render(tpl, params, next);
|
||||
}
|
||||
}
|
||||
function buildCustomTemplates(config) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Emailer.getTemplates(config, next);
|
||||
},
|
||||
function (templates, next) {
|
||||
templates = templates.filter(function (template) {
|
||||
return template.isCustom && template.text !== prevConfig['email:custom:' + path];
|
||||
});
|
||||
async.each(templates, function (template, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
file.walk(viewsDir, next);
|
||||
},
|
||||
function (paths, next) {
|
||||
paths = paths.reduce(function (obj, p) {
|
||||
var relative = path.relative(viewsDir, p);
|
||||
obj['/' + relative] = p;
|
||||
return obj;
|
||||
}, {});
|
||||
meta.templates.processImports(paths, template.path, template.text, next);
|
||||
},
|
||||
function (source, next) {
|
||||
Benchpress.precompile(source, {
|
||||
minify: global.env !== 'development',
|
||||
}, next);
|
||||
},
|
||||
function (compiled, next) {
|
||||
fs.writeFile(template.fullpath.replace(/\.tpl$/, '.js'), compiled, next);
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
},
|
||||
function (next) {
|
||||
Benchpress.flush();
|
||||
next();
|
||||
},
|
||||
], function (err) {
|
||||
if (err) {
|
||||
winston.error('[emailer] Failed to build custom email templates', err);
|
||||
return;
|
||||
}
|
||||
|
||||
function renderAndTranslate(tpl, params, lang, callback) {
|
||||
render(tpl, params, function (err, html) {
|
||||
translator.translate(html, lang, function (translated) {
|
||||
callback(err, translated);
|
||||
});
|
||||
winston.verbose('[emailer] Built custom email templates');
|
||||
});
|
||||
}
|
||||
|
||||
Emailer.renderAndTranslate = function (template, params, lang, callback) {
|
||||
app.render('emails/' + template, params, function (err, html) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
translator.translate(html, lang, function (translated) {
|
||||
callback(null, translated);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function getHostname() {
|
||||
var configUrl = nconf.get('url');
|
||||
var parsed = url.parse(configUrl);
|
||||
|
||||
@@ -141,7 +141,7 @@ events.deleteAll = function (callback) {
|
||||
};
|
||||
|
||||
events.output = function () {
|
||||
process.stdout.write('\nDisplaying last ten administrative events...\n'.bold);
|
||||
console.log('\nDisplaying last ten administrative events...'.bold);
|
||||
events.getEvents(0, 9, function (err, events) {
|
||||
if (err) {
|
||||
winston.error('Error fetching events', err);
|
||||
@@ -149,7 +149,7 @@ events.output = function () {
|
||||
}
|
||||
|
||||
events.forEach(function (event) {
|
||||
process.stdout.write(' * ' + String(event.timestampISO).green + ' ' + String(event.type).yellow + (event.text ? ' ' + event.text : '') + ' (uid: '.reset + (event.uid ? event.uid : 0) + ')\n');
|
||||
console.log(' * ' + String(event.timestampISO).green + ' ' + String(event.type).yellow + (event.text ? ' ' + event.text : '') + ' (uid: '.reset + (event.uid ? event.uid : 0) + ')');
|
||||
});
|
||||
|
||||
process.exit(0);
|
||||
|
||||
@@ -7,9 +7,12 @@ var winston = require('winston');
|
||||
var jimp = require('jimp');
|
||||
var mkdirp = require('mkdirp');
|
||||
var mime = require('mime');
|
||||
var graceful = require('graceful-fs');
|
||||
|
||||
var utils = require('./utils');
|
||||
|
||||
graceful.gracefulify(fs);
|
||||
|
||||
var file = module.exports;
|
||||
|
||||
/**
|
||||
|
||||
@@ -241,7 +241,7 @@ Flags.validate = function (payload, callback) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 1;
|
||||
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 0;
|
||||
// Check if reporter meets rep threshold (or can edit the target post, in which case threshold does not apply)
|
||||
if (!editable.flag && parseInt(data.reporter.reputation, 10) < minimumReputation) {
|
||||
return callback(new Error('[[error:not-enough-reputation-to-flag]]'));
|
||||
@@ -257,7 +257,7 @@ Flags.validate = function (payload, callback) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 1;
|
||||
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 0;
|
||||
// Check if reporter meets rep threshold (or can edit the target user, in which case threshold does not apply)
|
||||
if (!editable && parseInt(data.reporter.reputation, 10) < minimumReputation) {
|
||||
return callback(new Error('[[error:not-enough-reputation-to-flag]]'));
|
||||
@@ -696,6 +696,7 @@ Flags.notify = function (flagObj, uid, callback) {
|
||||
}
|
||||
|
||||
notifications.create({
|
||||
type: 'new-user-flag',
|
||||
bodyShort: '[[notifications:user_flagged_user, ' + flagObj.reporter.username + ', ' + flagObj.target.username + ']]',
|
||||
bodyLong: flagObj.description,
|
||||
path: '/uid/' + flagObj.targetId,
|
||||
|
||||
@@ -161,6 +161,7 @@ module.exports = function (Groups) {
|
||||
async.waterfall([
|
||||
async.apply(inviteOrRequestMembership, groupName, uid, 'invite'),
|
||||
async.apply(notifications.create, {
|
||||
type: 'group-invite',
|
||||
bodyShort: '[[groups:invited.notification_title, ' + groupName + ']]',
|
||||
bodyLong: '',
|
||||
nid: 'group:' + groupName + ':uid:' + uid + ':invite',
|
||||
|
||||
@@ -120,9 +120,7 @@ image.size = function (path, callback) {
|
||||
};
|
||||
|
||||
image.convertImageToBase64 = function (path, callback) {
|
||||
fs.readFile(path, function (err, data) {
|
||||
callback(err, data ? data.toString('base64') : null);
|
||||
});
|
||||
fs.readFile(path, 'base64', callback);
|
||||
};
|
||||
|
||||
image.mimeFromBase64 = function (imageData) {
|
||||
|
||||
@@ -174,7 +174,7 @@ function completeConfigSetup(config, next) {
|
||||
}
|
||||
|
||||
function setupDefaultConfigs(next) {
|
||||
process.stdout.write('Populating database with default configs, if not already set...\n');
|
||||
console.log('Populating database with default configs, if not already set...');
|
||||
var meta = require('./meta');
|
||||
var defaults = require(path.join(__dirname, '../', 'install/data/defaults.json'));
|
||||
|
||||
@@ -192,11 +192,11 @@ function enableDefaultTheme(next) {
|
||||
|
||||
meta.configs.get('theme:id', function (err, id) {
|
||||
if (err || id) {
|
||||
process.stdout.write('Previous theme detected, skipping enabling default theme\n');
|
||||
console.log('Previous theme detected, skipping enabling default theme');
|
||||
return next(err);
|
||||
}
|
||||
var defaultTheme = nconf.get('defaultTheme') || 'nodebb-theme-persona';
|
||||
process.stdout.write('Enabling default theme: ' + defaultTheme + '\n');
|
||||
console.log('Enabling default theme: ' + defaultTheme);
|
||||
meta.themes.set({
|
||||
type: 'local',
|
||||
id: defaultTheme,
|
||||
@@ -211,7 +211,7 @@ function createAdministrator(next) {
|
||||
return next(err);
|
||||
}
|
||||
if (memberCount > 0) {
|
||||
process.stdout.write('Administrator found, skipping Admin setup\n');
|
||||
console.log('Administrator found, skipping Admin setup');
|
||||
next();
|
||||
} else {
|
||||
createAdmin(next);
|
||||
@@ -315,7 +315,7 @@ function createAdmin(callback) {
|
||||
} else {
|
||||
// If automated setup did not provide a user password, generate one, it will be shown to the user upon setup completion
|
||||
if (!install.values.hasOwnProperty('admin:password') && !nconf.get('admin:password')) {
|
||||
process.stdout.write('Password was not provided during automated setup, generating one...\n');
|
||||
console.log('Password was not provided during automated setup, generating one...');
|
||||
password = utils.generateUUID().slice(0, 8);
|
||||
}
|
||||
|
||||
@@ -365,13 +365,13 @@ function createCategories(next) {
|
||||
}
|
||||
|
||||
if (Array.isArray(categoryData) && categoryData.length) {
|
||||
process.stdout.write('Categories OK. Found ' + categoryData.length + ' categories.\n');
|
||||
console.log('Categories OK. Found ' + categoryData.length + ' categories.');
|
||||
return next();
|
||||
}
|
||||
|
||||
process.stdout.write('No categories found, populating instance with default categories\n');
|
||||
console.log('No categories found, populating instance with default categories');
|
||||
|
||||
fs.readFile(path.join(__dirname, '../', 'install/data/categories.json'), function (err, default_categories) {
|
||||
fs.readFile(path.join(__dirname, '../', 'install/data/categories.json'), 'utf8', function (err, default_categories) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -402,7 +402,7 @@ function createWelcomePost(next) {
|
||||
|
||||
async.parallel([
|
||||
function (next) {
|
||||
fs.readFile(path.join(__dirname, '../', 'install/data/welcome.md'), next);
|
||||
fs.readFile(path.join(__dirname, '../', 'install/data/welcome.md'), 'utf8', next);
|
||||
},
|
||||
function (next) {
|
||||
db.getObjectField('global', 'topicCount', next);
|
||||
@@ -416,12 +416,12 @@ function createWelcomePost(next) {
|
||||
var numTopics = results[1];
|
||||
|
||||
if (!parseInt(numTopics, 10)) {
|
||||
process.stdout.write('Creating welcome post!\n');
|
||||
console.log('Creating welcome post!');
|
||||
Topics.post({
|
||||
uid: 1,
|
||||
cid: 2,
|
||||
title: 'Welcome to your NodeBB!',
|
||||
content: content.toString(),
|
||||
content: content,
|
||||
}, next);
|
||||
} else {
|
||||
next();
|
||||
@@ -430,7 +430,7 @@ function createWelcomePost(next) {
|
||||
}
|
||||
|
||||
function enableDefaultPlugins(next) {
|
||||
process.stdout.write('Enabling default plugins\n');
|
||||
console.log('Enabling default plugins');
|
||||
|
||||
var defaultEnabled = [
|
||||
'nodebb-plugin-composer-default',
|
||||
@@ -439,8 +439,8 @@ function enableDefaultPlugins(next) {
|
||||
'nodebb-widget-essentials',
|
||||
'nodebb-rewards-essentials',
|
||||
'nodebb-plugin-soundpack-default',
|
||||
'nodebb-plugin-emoji-extended',
|
||||
'nodebb-plugin-emoji-one',
|
||||
'nodebb-plugin-emoji',
|
||||
'nodebb-plugin-emoji-android',
|
||||
];
|
||||
var customDefaults = nconf.get('defaultplugins') || nconf.get('defaultPlugins');
|
||||
|
||||
@@ -473,7 +473,7 @@ function setCopyrightWidget(next) {
|
||||
var db = require('./database');
|
||||
async.parallel({
|
||||
footerJSON: function (next) {
|
||||
fs.readFile(path.join(__dirname, '../', 'install/data/footer.json'), next);
|
||||
fs.readFile(path.join(__dirname, '../', 'install/data/footer.json'), 'utf8', next);
|
||||
},
|
||||
footer: function (next) {
|
||||
db.getObjectField('widgets:global', 'footer', next);
|
||||
@@ -484,7 +484,7 @@ function setCopyrightWidget(next) {
|
||||
}
|
||||
|
||||
if (!results.footer && results.footerJSON) {
|
||||
db.setObjectField('widgets:global', 'footer', results.footerJSON.toString(), next);
|
||||
db.setObjectField('widgets:global', 'footer', results.footerJSON, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
@@ -546,7 +546,7 @@ install.save = function (server_conf, callback) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
process.stdout.write('Configuration Saved OK\n');
|
||||
console.log('Configuration Saved OK');
|
||||
|
||||
nconf.file({
|
||||
file: path.join(__dirname, '..', 'config.json'),
|
||||
|
||||
@@ -29,7 +29,7 @@ Languages.listCodes = function (callback) {
|
||||
return callback(null, codeCache);
|
||||
}
|
||||
|
||||
fs.readFile(path.join(languagesPath, 'metadata.json'), function (err, buffer) {
|
||||
fs.readFile(path.join(languagesPath, 'metadata.json'), 'utf8', function (err, file) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
return callback(null, []);
|
||||
}
|
||||
@@ -39,7 +39,7 @@ Languages.listCodes = function (callback) {
|
||||
|
||||
var parsed;
|
||||
try {
|
||||
parsed = JSON.parse(buffer.toString());
|
||||
parsed = JSON.parse(file);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
@@ -64,7 +64,7 @@ Languages.list = function (callback) {
|
||||
async.map(codes, function (folder, next) {
|
||||
var configPath = path.join(languagesPath, folder, 'language.json');
|
||||
|
||||
fs.readFile(configPath, function (err, buffer) {
|
||||
fs.readFile(configPath, 'utf8', function (err, file) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
return next();
|
||||
}
|
||||
@@ -72,7 +72,7 @@ Languages.list = function (callback) {
|
||||
return next(err);
|
||||
}
|
||||
try {
|
||||
var lang = JSON.parse(buffer.toString());
|
||||
var lang = JSON.parse(file);
|
||||
next(null, lang);
|
||||
} catch (e) {
|
||||
next(e);
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
|
||||
var user = require('../user');
|
||||
var emailer = require('../emailer');
|
||||
var notifications = require('../notifications');
|
||||
var meta = require('../meta');
|
||||
var sockets = require('../socket.io');
|
||||
var plugins = require('../plugins');
|
||||
|
||||
@@ -92,46 +89,6 @@ module.exports = function (Messaging) {
|
||||
if (notification) {
|
||||
notifications.push(notification, uids);
|
||||
}
|
||||
sendNotificationEmails(uids, messageObj);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendNotificationEmails(uids, messageObj) {
|
||||
if (parseInt(meta.config.disableEmailSubscriptions, 10) === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
userData: function (next) {
|
||||
user.getUsersFields(uids, ['uid', 'username', 'userslug'], next);
|
||||
},
|
||||
userSettings: function (next) {
|
||||
user.getMultipleUserSettings(uids, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
|
||||
function (results, next) {
|
||||
results.userData = results.userData.filter(function (userData, index) {
|
||||
return userData && results.userSettings[index] && results.userSettings[index].sendChatNotifications;
|
||||
});
|
||||
async.each(results.userData, function (userData, next) {
|
||||
emailer.send('notif_chat', userData.uid, {
|
||||
subject: '[[email:notif.chat.subject, ' + messageObj.fromUser.username + ']]',
|
||||
summary: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]',
|
||||
message: messageObj,
|
||||
roomId: messageObj.roomId,
|
||||
username: userData.username,
|
||||
userslug: userData.userslug,
|
||||
}, next);
|
||||
}, next);
|
||||
},
|
||||
], function (err) {
|
||||
if (err) {
|
||||
return winston.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,6 +83,8 @@ var aliases = {
|
||||
sounds: ['sound'],
|
||||
};
|
||||
|
||||
exports.aliases = aliases;
|
||||
|
||||
aliases = Object.keys(aliases).reduce(function (prev, key) {
|
||||
var arr = aliases[key];
|
||||
arr.forEach(function (alias) {
|
||||
|
||||
@@ -31,18 +31,18 @@ exports.read = function read(callback) {
|
||||
return callback(null, cached);
|
||||
}
|
||||
|
||||
fs.readFile(filePath, function (err, buffer) {
|
||||
fs.readFile(filePath, 'utf8', function (err, buster) {
|
||||
if (err) {
|
||||
winston.warn('[cache-buster] could not read cache buster', err);
|
||||
return callback(null, generate());
|
||||
}
|
||||
|
||||
if (!buffer || buffer.toString().length !== 11) {
|
||||
winston.warn('[cache-buster] cache buster string invalid: expected /[a-z0-9]{11}/, got `' + buffer + '`');
|
||||
if (!buster || buster.length !== 11) {
|
||||
winston.warn('[cache-buster] cache buster string invalid: expected /[a-z0-9]{11}/, got `' + buster + '`');
|
||||
return callback(null, generate());
|
||||
}
|
||||
|
||||
cached = buffer.toString();
|
||||
cached = buster;
|
||||
callback(null, cached);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -83,9 +83,12 @@ function processConfig(data, callback) {
|
||||
var image = require('../image');
|
||||
if (data['brand:logo']) {
|
||||
image.size(path.join(nconf.get('upload_path'), 'system', 'site-logo-x50.png'), function (err, size) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
data['brand:emailLogo:height'] = size.height;
|
||||
data['brand:emailLogo:width'] = size.width;
|
||||
next(err);
|
||||
next();
|
||||
});
|
||||
} else {
|
||||
setImmediate(next);
|
||||
|
||||
@@ -76,6 +76,19 @@ JS.scripts = {
|
||||
'public/src/modules/storage.js',
|
||||
],
|
||||
|
||||
admin: [
|
||||
'node_modules/material-design-lite/material.js',
|
||||
'public/vendor/jquery/sortable/Sortable.js',
|
||||
'public/vendor/colorpicker/colorpicker.js',
|
||||
'public/src/admin/admin.js',
|
||||
'public/vendor/semver/semver.browser.js',
|
||||
'public/vendor/jquery/serializeObject/jquery.ba-serializeobject.min.js',
|
||||
'public/vendor/jquery/deserialize/jquery.deserialize.min.js',
|
||||
'public/vendor/snackbar/snackbar.min.js',
|
||||
'public/vendor/slideout/slideout.min.js',
|
||||
'public/vendor/nprogress.min.js',
|
||||
],
|
||||
|
||||
// modules listed below are built (/src/modules) so they can be defined anonymously
|
||||
modules: {
|
||||
'Chart.js': 'node_modules/chart.js/dist/Chart.min.js',
|
||||
@@ -106,7 +119,7 @@ function minifyModules(modules, fork, callback) {
|
||||
return prev;
|
||||
}, []);
|
||||
|
||||
async.eachLimit(moduleDirs, 1000, mkdirp, function (err) {
|
||||
async.each(moduleDirs, mkdirp, function (err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -126,7 +139,7 @@ function minifyModules(modules, fork, callback) {
|
||||
minifier.js.minifyBatch(filtered.minify, fork, cb);
|
||||
},
|
||||
function (cb) {
|
||||
async.eachLimit(filtered.skip, 500, function (mod, next) {
|
||||
async.each(filtered.skip, function (mod, next) {
|
||||
linkIfLinux(mod.srcPath, mod.destPath, next);
|
||||
}, cb);
|
||||
},
|
||||
@@ -137,7 +150,7 @@ function minifyModules(modules, fork, callback) {
|
||||
function linkModules(callback) {
|
||||
var modules = JS.scripts.modules;
|
||||
|
||||
async.eachLimit(Object.keys(modules), 1000, function (relPath, next) {
|
||||
async.each(Object.keys(modules), function (relPath, next) {
|
||||
var srcPath = path.join(__dirname, '../../', modules[relPath]);
|
||||
var destPath = path.join(__dirname, '../../build/public/src/modules', relPath);
|
||||
|
||||
@@ -183,7 +196,7 @@ function getModuleList(callback) {
|
||||
modules = modules.concat(coreDirs);
|
||||
|
||||
var moduleFiles = [];
|
||||
async.eachLimit(modules, 1000, function (module, next) {
|
||||
async.each(modules, function (module, next) {
|
||||
var srcPath = module.srcPath;
|
||||
var destPath = module.destPath;
|
||||
|
||||
@@ -255,7 +268,7 @@ JS.linkStatics = function (callback) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
async.eachLimit(Object.keys(plugins.staticDirs), 1000, function (mappedPath, next) {
|
||||
async.each(Object.keys(plugins.staticDirs), function (mappedPath, next) {
|
||||
var sourceDir = plugins.staticDirs[mappedPath];
|
||||
var destDir = path.join(__dirname, '../../build/public/plugins', mappedPath);
|
||||
|
||||
@@ -299,13 +312,15 @@ function getBundleScriptList(target, callback) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var scripts = JS.scripts.base.concat(pluginScripts);
|
||||
var scripts = JS.scripts.base;
|
||||
|
||||
if (target === 'client' && global.env !== 'development') {
|
||||
scripts = scripts.concat(JS.scripts.rjs);
|
||||
} else if (target === 'acp') {
|
||||
scripts = scripts.concat(JS.scripts.admin);
|
||||
}
|
||||
|
||||
scripts = scripts.map(function (script) {
|
||||
scripts = scripts.concat(pluginScripts).map(function (script) {
|
||||
var srcPath = path.resolve(basePath, script).replace(/\\/g, '/');
|
||||
return {
|
||||
srcPath: srcPath,
|
||||
|
||||
@@ -13,7 +13,7 @@ var Plugins = require('../plugins');
|
||||
var buildLanguagesPath = path.join(__dirname, '../../build/public/language');
|
||||
var coreLanguagesPath = path.join(__dirname, '../../public/language');
|
||||
|
||||
function getTranslationTree(callback) {
|
||||
function getTranslationMetadata(callback) {
|
||||
async.waterfall([
|
||||
// generate list of languages and namespaces
|
||||
function (next) {
|
||||
@@ -49,129 +49,113 @@ function getTranslationTree(callback) {
|
||||
// save a list of languages to `${buildLanguagesPath}/metadata.json`
|
||||
// avoids readdirs later on
|
||||
function (ref, next) {
|
||||
async.waterfall([
|
||||
async.series([
|
||||
function (next) {
|
||||
mkdirp(buildLanguagesPath, next);
|
||||
},
|
||||
function (x, next) {
|
||||
function (next) {
|
||||
fs.writeFile(path.join(buildLanguagesPath, 'metadata.json'), JSON.stringify({
|
||||
languages: ref.languages,
|
||||
namespaces: ref.namespaces,
|
||||
}), next);
|
||||
},
|
||||
function (next) {
|
||||
next(null, ref);
|
||||
},
|
||||
], next);
|
||||
},
|
||||
|
||||
// for each language and namespace combination,
|
||||
// run through core and all plugins to generate
|
||||
// a full translation hash
|
||||
function (ref, next) {
|
||||
var languages = ref.languages;
|
||||
var namespaces = ref.namespaces;
|
||||
var plugins = _.values(Plugins.pluginsData).filter(function (plugin) {
|
||||
return typeof plugin.languages === 'string';
|
||||
});
|
||||
|
||||
var tree = {};
|
||||
|
||||
async.eachLimit(languages, 10, function (lang, next) {
|
||||
async.eachLimit(namespaces, 10, function (namespace, next) {
|
||||
var translations = {};
|
||||
|
||||
async.series([
|
||||
// core first
|
||||
function (cb) {
|
||||
fs.readFile(path.join(coreLanguagesPath, lang, namespace + '.json'), function (err, buffer) {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return cb();
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
try {
|
||||
Object.assign(translations, JSON.parse(buffer.toString()));
|
||||
cb();
|
||||
} catch (err) {
|
||||
cb(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
function (cb) {
|
||||
// for each plugin, fallback in this order:
|
||||
// 1. correct language string (en-GB)
|
||||
// 2. old language string (en_GB)
|
||||
// 3. corrected plugin defaultLang (en-US)
|
||||
// 4. old plugin defaultLang (en_US)
|
||||
async.eachLimit(plugins, 20, function (pluginData, done) {
|
||||
var pluginLanguages = path.join(__dirname, '../../node_modules/', pluginData.id, pluginData.languages);
|
||||
var defaultLang = pluginData.defaultLang || 'en-GB';
|
||||
|
||||
async.eachSeries([
|
||||
defaultLang.replace('-', '_').replace('-x-', '@'),
|
||||
defaultLang.replace('_', '-').replace('@', '-x-'),
|
||||
lang.replace('-', '_').replace('-x-', '@'),
|
||||
lang,
|
||||
], function (language, next) {
|
||||
fs.readFile(path.join(pluginLanguages, language, namespace + '.json'), function (err, buffer) {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return next(null, false);
|
||||
}
|
||||
return next(err);
|
||||
}
|
||||
|
||||
try {
|
||||
Object.assign(translations, JSON.parse(buffer.toString()));
|
||||
next(null, true);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
}, done);
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if (Object.keys(translations).length) {
|
||||
tree[lang] = tree[lang] || {};
|
||||
tree[lang][namespace] = translations;
|
||||
}
|
||||
cb();
|
||||
});
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
}, function (err) {
|
||||
next(err, tree);
|
||||
], function (err) {
|
||||
next(err, ref);
|
||||
});
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
// write translation hashes from the generated tree to language files
|
||||
function writeLanguageFiles(tree, callback) {
|
||||
// iterate over languages and namespaces
|
||||
async.eachLimit(Object.keys(tree), 100, function (language, cb) {
|
||||
var namespaces = tree[language];
|
||||
async.eachLimit(Object.keys(namespaces), 10, function (namespace, next) {
|
||||
var translations = namespaces[namespace];
|
||||
function writeLanguageFile(language, namespace, translations, callback) {
|
||||
var dev = global.env === 'development';
|
||||
var filePath = path.join(buildLanguagesPath, language, namespace + '.json');
|
||||
|
||||
var filePath = path.join(buildLanguagesPath, language, namespace + '.json');
|
||||
async.series([
|
||||
async.apply(mkdirp, path.dirname(filePath)),
|
||||
async.apply(fs.writeFile, filePath, JSON.stringify(translations, null, dev ? 2 : 0)),
|
||||
], callback);
|
||||
}
|
||||
|
||||
mkdirp(path.dirname(filePath), function (err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
// for each language and namespace combination,
|
||||
// run through core and all plugins to generate
|
||||
// a full translation hash
|
||||
function buildTranslations(ref, next) {
|
||||
var namespaces = ref.namespaces;
|
||||
var languages = ref.languages;
|
||||
var plugins = _.values(Plugins.pluginsData).filter(function (plugin) {
|
||||
return typeof plugin.languages === 'string';
|
||||
});
|
||||
|
||||
fs.writeFile(filePath, JSON.stringify(translations), next);
|
||||
});
|
||||
}, cb);
|
||||
}, callback);
|
||||
async.each(namespaces, function (namespace, next) {
|
||||
async.each(languages, function (lang, next) {
|
||||
var translations = {};
|
||||
|
||||
async.series([
|
||||
// core first
|
||||
function (cb) {
|
||||
fs.readFile(path.join(coreLanguagesPath, lang, namespace + '.json'), 'utf8', function (err, file) {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return cb();
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
try {
|
||||
Object.assign(translations, JSON.parse(file));
|
||||
cb();
|
||||
} catch (err) {
|
||||
cb(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
function (cb) {
|
||||
// for each plugin, fallback in this order:
|
||||
// 1. correct language string (en-GB)
|
||||
// 2. old language string (en_GB)
|
||||
// 3. corrected plugin defaultLang (en-US)
|
||||
// 4. old plugin defaultLang (en_US)
|
||||
async.each(plugins, function (pluginData, done) {
|
||||
var pluginLanguages = path.join(__dirname, '../../node_modules/', pluginData.id, pluginData.languages);
|
||||
var defaultLang = pluginData.defaultLang || 'en-GB';
|
||||
|
||||
async.eachSeries([
|
||||
defaultLang.replace('-', '_').replace('-x-', '@'),
|
||||
defaultLang.replace('_', '-').replace('@', '-x-'),
|
||||
lang.replace('-', '_').replace('-x-', '@'),
|
||||
lang,
|
||||
], function (language, next) {
|
||||
fs.readFile(path.join(pluginLanguages, language, namespace + '.json'), 'utf8', function (err, file) {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return next(null, false);
|
||||
}
|
||||
return next(err);
|
||||
}
|
||||
|
||||
try {
|
||||
Object.assign(translations, JSON.parse(file));
|
||||
next(null, true);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
}, done);
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if (Object.keys(translations).length) {
|
||||
writeLanguageFile(lang, namespace, translations, cb);
|
||||
return;
|
||||
}
|
||||
cb();
|
||||
});
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
}, next);
|
||||
}
|
||||
|
||||
exports.build = function buildLanguages(callback) {
|
||||
@@ -179,7 +163,7 @@ exports.build = function buildLanguages(callback) {
|
||||
function (next) {
|
||||
rimraf(buildLanguagesPath, next);
|
||||
},
|
||||
getTranslationTree,
|
||||
writeLanguageFiles,
|
||||
getTranslationMetadata,
|
||||
buildTranslations,
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ var autoprefixer = require('autoprefixer');
|
||||
var clean = require('postcss-clean');
|
||||
|
||||
var fork = require('./debugFork');
|
||||
require('../file'); // for graceful-fs
|
||||
|
||||
var Minifier = module.exports;
|
||||
|
||||
@@ -139,12 +140,12 @@ function executeAction(action, fork, callback) {
|
||||
function concat(data, callback) {
|
||||
if (data.files && data.files.length) {
|
||||
async.mapLimit(data.files, 1000, function (ref, next) {
|
||||
fs.readFile(ref.srcPath, function (err, buffer) {
|
||||
fs.readFile(ref.srcPath, 'utf8', function (err, file) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
next(null, buffer.toString());
|
||||
next(null, file);
|
||||
});
|
||||
}, function (err, files) {
|
||||
if (err) {
|
||||
@@ -163,18 +164,18 @@ function concat(data, callback) {
|
||||
actions.concat = concat;
|
||||
|
||||
function minifyJS_batch(data, callback) {
|
||||
async.eachLimit(data.files, 1000, function (ref, next) {
|
||||
async.each(data.files, function (ref, next) {
|
||||
var srcPath = ref.srcPath;
|
||||
var destPath = ref.destPath;
|
||||
var filename = ref.filename;
|
||||
|
||||
fs.readFile(srcPath, function (err, buffer) {
|
||||
fs.readFile(srcPath, 'utf8', function (err, file) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var scripts = {};
|
||||
scripts[filename] = buffer.toString();
|
||||
scripts[filename] = file;
|
||||
|
||||
try {
|
||||
var minified = uglifyjs.minify(scripts, {
|
||||
@@ -203,7 +204,7 @@ function minifyJS(data, callback) {
|
||||
var srcPath = ref.srcPath;
|
||||
var filename = ref.filename;
|
||||
|
||||
fs.readFile(srcPath, function (err, buffer) {
|
||||
fs.readFile(srcPath, 'utf8', function (err, file) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -211,7 +212,7 @@ function minifyJS(data, callback) {
|
||||
next(null, {
|
||||
srcPath: srcPath,
|
||||
filename: filename,
|
||||
source: buffer.toString(),
|
||||
source: file,
|
||||
});
|
||||
});
|
||||
}, function (err, files) {
|
||||
|
||||
@@ -5,7 +5,7 @@ var fs = require('fs');
|
||||
var cproc = require('child_process');
|
||||
|
||||
var packageFilePath = path.join(__dirname, '../../package.json');
|
||||
var packageDefaultFilePath = path.join(__dirname, '../../package.default.json');
|
||||
var packageDefaultFilePath = path.join(__dirname, '../../install/package.json');
|
||||
var modulesPath = path.join(__dirname, '../../node_modules');
|
||||
|
||||
function updatePackageFile() {
|
||||
@@ -53,13 +53,16 @@ function preserveExtraneousPlugins() {
|
||||
var packageContents = JSON.parse(fs.readFileSync(packageFilePath, 'utf8'));
|
||||
|
||||
var extraneous = packages
|
||||
// only extraneous plugins (ones not in package.json)
|
||||
// only extraneous plugins (ones not in package.json) which are not links
|
||||
.filter(function (pkgName) {
|
||||
return !packageContents.dependencies.hasOwnProperty(pkgName);
|
||||
const extraneous = !packageContents.dependencies.hasOwnProperty(pkgName);
|
||||
const isLink = fs.lstatSync(path.join(modulesPath, pkgName)).isSymbolicLink();
|
||||
|
||||
return extraneous && !isLink;
|
||||
})
|
||||
// reduce to a map of package names to package versions
|
||||
.reduce(function (map, pkgName) {
|
||||
var pkgConfig = JSON.parse(fs.readFileSync(path.join(modulesPath, pkgName, 'package.json')));
|
||||
var pkgConfig = JSON.parse(fs.readFileSync(path.join(modulesPath, pkgName, 'package.json'), 'utf8'));
|
||||
map[pkgName] = pkgConfig.version;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
@@ -124,7 +124,7 @@ Tags.parse = function (req, data, meta, link, callback) {
|
||||
|
||||
addIfNotExists(meta, 'property', 'og:title', Meta.config.title || 'NodeBB');
|
||||
|
||||
var ogUrl = nconf.get('url') + req.path;
|
||||
var ogUrl = nconf.get('url') + (req.originalUrl !== '/' ? req.originalUrl : '');
|
||||
addIfNotExists(meta, 'property', 'og:url', ogUrl);
|
||||
|
||||
addIfNotExists(meta, 'name', 'description', Meta.config.description);
|
||||
|
||||
@@ -15,41 +15,40 @@ var viewsPath = nconf.get('views_dir');
|
||||
|
||||
var Templates = module.exports;
|
||||
|
||||
function processImports(paths, templatePath, source, callback) {
|
||||
var regex = /<!-- IMPORT (.+?) -->/;
|
||||
|
||||
var matches = source.match(regex);
|
||||
|
||||
if (!matches) {
|
||||
return callback(null, source);
|
||||
}
|
||||
|
||||
var partial = '/' + matches[1];
|
||||
if (paths[partial] && templatePath !== partial) {
|
||||
fs.readFile(paths[partial], 'utf8', function (err, partialSource) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
source = source.replace(regex, partialSource);
|
||||
processImports(paths, templatePath, source, callback);
|
||||
});
|
||||
} else {
|
||||
winston.warn('[meta/templates] Partial not loaded: ' + matches[1]);
|
||||
source = source.replace(regex, '');
|
||||
|
||||
processImports(paths, templatePath, source, callback);
|
||||
}
|
||||
}
|
||||
Templates.processImports = processImports;
|
||||
|
||||
Templates.compile = function (callback) {
|
||||
callback = callback || function () {};
|
||||
|
||||
var themeConfig = require(nconf.get('theme_config'));
|
||||
var baseTemplatesPaths = themeConfig.baseTheme ? getBaseTemplates(themeConfig.baseTheme) : [nconf.get('base_templates_path')];
|
||||
|
||||
function processImports(paths, relativePath, source, callback) {
|
||||
var regex = /<!-- IMPORT (.+?) -->/;
|
||||
|
||||
var matches = source.match(regex);
|
||||
|
||||
if (!matches) {
|
||||
return callback(null, source);
|
||||
}
|
||||
|
||||
var partial = '/' + matches[1];
|
||||
if (paths[partial] && relativePath !== partial) {
|
||||
fs.readFile(paths[partial], function (err, file) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var partialSource = file.toString();
|
||||
source = source.replace(regex, partialSource);
|
||||
|
||||
processImports(paths, relativePath, source, callback);
|
||||
});
|
||||
} else {
|
||||
winston.warn('[meta/templates] Partial not loaded: ' + matches[1]);
|
||||
source = source.replace(regex, '');
|
||||
|
||||
processImports(paths, relativePath, source, callback);
|
||||
}
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
preparePaths(baseTemplatesPaths, next);
|
||||
@@ -58,10 +57,9 @@ Templates.compile = function (callback) {
|
||||
async.each(Object.keys(paths), function (relativePath, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
fs.readFile(paths[relativePath], next);
|
||||
fs.readFile(paths[relativePath], 'utf8', next);
|
||||
},
|
||||
function (file, next) {
|
||||
var source = file.toString();
|
||||
function (source, next) {
|
||||
processImports(paths, relativePath, source, next);
|
||||
},
|
||||
function (source, next) {
|
||||
|
||||
@@ -42,7 +42,7 @@ Themes.get = function (callback) {
|
||||
async.map(themes, function (theme, next) {
|
||||
var config = path.join(themePath, theme, 'theme.json');
|
||||
|
||||
fs.readFile(config, function (err, file) {
|
||||
fs.readFile(config, 'utf8', function (err, file) {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return next(null, null);
|
||||
@@ -50,7 +50,7 @@ Themes.get = function (callback) {
|
||||
return next(err);
|
||||
}
|
||||
try {
|
||||
var configObj = JSON.parse(file.toString());
|
||||
var configObj = JSON.parse(file);
|
||||
|
||||
// Minor adjustments for API output
|
||||
configObj.type = 'local';
|
||||
@@ -96,9 +96,9 @@ Themes.set = function (data, callback) {
|
||||
});
|
||||
},
|
||||
function (next) {
|
||||
fs.readFile(path.join(nconf.get('themes_path'), data.id, 'theme.json'), function (err, config) {
|
||||
fs.readFile(path.join(nconf.get('themes_path'), data.id, 'theme.json'), 'utf8', function (err, config) {
|
||||
if (!err) {
|
||||
config = JSON.parse(config.toString());
|
||||
config = JSON.parse(config);
|
||||
next(null, config);
|
||||
} else {
|
||||
next(err);
|
||||
|
||||
@@ -218,11 +218,11 @@ middleware.templatesOnDemand = function (req, res, next) {
|
||||
return next();
|
||||
}
|
||||
|
||||
fs.readFile(filePath.replace(/\.js$/, '.tpl'), cb);
|
||||
fs.readFile(filePath.replace(/\.js$/, '.tpl'), 'utf8', cb);
|
||||
},
|
||||
function (source, cb) {
|
||||
Benchpress.precompile({
|
||||
source: source.toString(),
|
||||
source: source,
|
||||
minify: global.env !== 'development',
|
||||
}, cb);
|
||||
},
|
||||
|
||||
@@ -140,6 +140,22 @@ module.exports = function (middleware) {
|
||||
], next);
|
||||
};
|
||||
|
||||
middleware.redirectMeToUserslug = function (req, res, next) {
|
||||
var uid = req.uid;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.getUserField(uid, 'userslug', next);
|
||||
},
|
||||
function (userslug) {
|
||||
if (!userslug) {
|
||||
return res.status(401).send('not-authorized');
|
||||
}
|
||||
var path = req.path.replace(/^(\/api)?\/me/, '/user/' + userslug);
|
||||
controllers.helpers.redirect(res, path);
|
||||
},
|
||||
], next);
|
||||
};
|
||||
|
||||
middleware.isAdmin = function (req, res, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
@@ -157,10 +173,12 @@ module.exports = function (middleware) {
|
||||
}
|
||||
|
||||
var loginTime = req.session.meta ? req.session.meta.datetime : 0;
|
||||
if (loginTime && parseInt(loginTime, 10) > Date.now() - 3600000) {
|
||||
var timeLeft = parseInt(loginTime, 10) - (Date.now() - 3600000);
|
||||
if (timeLeft < 300000) {
|
||||
req.session.meta.datetime += 300000;
|
||||
var adminReloginDuration = (meta.config.adminReloginDuration || 60) * 60000;
|
||||
var disabled = parseInt(meta.config.adminReloginDuration, 10) === 0;
|
||||
if (disabled || (loginTime && parseInt(loginTime, 10) > Date.now() - adminReloginDuration)) {
|
||||
var timeLeft = parseInt(loginTime, 10) - (Date.now() - adminReloginDuration);
|
||||
if (timeLeft < Math.min(300000, adminReloginDuration)) {
|
||||
req.session.meta.datetime += Math.min(300000, adminReloginDuration);
|
||||
}
|
||||
|
||||
return next();
|
||||
|
||||
@@ -13,6 +13,7 @@ var meta = require('./meta');
|
||||
var batch = require('./batch');
|
||||
var plugins = require('./plugins');
|
||||
var utils = require('./utils');
|
||||
var emailer = require('./emailer');
|
||||
|
||||
var Notifications = module.exports;
|
||||
|
||||
@@ -178,9 +179,78 @@ Notifications.push = function (notification, uids, callback) {
|
||||
};
|
||||
|
||||
function pushToUids(uids, notification, callback) {
|
||||
var oneWeekAgo = Date.now() - 604800000;
|
||||
var unreadKeys = [];
|
||||
var readKeys = [];
|
||||
function sendNotification(uids, callback) {
|
||||
if (!uids.length) {
|
||||
return callback();
|
||||
}
|
||||
var oneWeekAgo = Date.now() - 604800000;
|
||||
var unreadKeys = [];
|
||||
var readKeys = [];
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
uids.forEach(function (uid) {
|
||||
unreadKeys.push('uid:' + uid + ':notifications:unread');
|
||||
readKeys.push('uid:' + uid + ':notifications:read');
|
||||
});
|
||||
|
||||
db.sortedSetsAdd(unreadKeys, notification.datetime, notification.nid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemove(readKeys, notification.nid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemoveRangeByScore(unreadKeys, '-inf', oneWeekAgo, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemoveRangeByScore(readKeys, '-inf', oneWeekAgo, next);
|
||||
},
|
||||
function (next) {
|
||||
var websockets = require('./socket.io');
|
||||
if (websockets.server) {
|
||||
uids.forEach(function (uid) {
|
||||
websockets.in('uid_' + uid).emit('event:new_notification', notification);
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function sendEmail(uids, callback) {
|
||||
async.eachLimit(uids, 3, function (uid, next) {
|
||||
emailer.send('notification', uid, {
|
||||
path: notification.path,
|
||||
subject: '[[notifications:new_notification_from, ' + meta.config.title + ']]',
|
||||
intro: utils.stripHTMLTags(notification.bodyShort),
|
||||
body: utils.stripHTMLTags(notification.bodyLong || ''),
|
||||
showUnsubscribe: true,
|
||||
}, next);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function getUidsBySettings(uids, callback) {
|
||||
var uidsToNotify = [];
|
||||
var uidsToEmail = [];
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
User.getMultipleUserSettings(uids, next);
|
||||
},
|
||||
function (usersSettings, next) {
|
||||
usersSettings.forEach(function (userSettings) {
|
||||
var setting = userSettings['notificationType_' + notification.type] || 'notification';
|
||||
|
||||
if (setting === 'notification' || setting === 'notificationemail') {
|
||||
uidsToNotify.push(userSettings.uid);
|
||||
}
|
||||
|
||||
if (setting === 'email' || setting === 'notificationemail') {
|
||||
uidsToEmail.push(userSettings.uid);
|
||||
}
|
||||
});
|
||||
next(null, { uidsToNotify: uidsToNotify, uidsToEmail: uidsToEmail });
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
@@ -190,35 +260,32 @@ function pushToUids(uids, notification, callback) {
|
||||
if (!data || !data.notification || !data.uids || !data.uids.length) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
uids = data.uids;
|
||||
notification = data.notification;
|
||||
|
||||
uids.forEach(function (uid) {
|
||||
unreadKeys.push('uid:' + uid + ':notifications:unread');
|
||||
readKeys.push('uid:' + uid + ':notifications:read');
|
||||
});
|
||||
|
||||
db.sortedSetsAdd(unreadKeys, notification.datetime, notification.nid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemove(readKeys, notification.nid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemoveRangeByScore(unreadKeys, '-inf', oneWeekAgo, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemoveRangeByScore(readKeys, '-inf', oneWeekAgo, next);
|
||||
},
|
||||
function (next) {
|
||||
var websockets = require('./socket.io');
|
||||
if (websockets.server) {
|
||||
uids.forEach(function (uid) {
|
||||
websockets.in('uid_' + uid).emit('event:new_notification', notification);
|
||||
});
|
||||
if (notification.type) {
|
||||
getUidsBySettings(data.uids, next);
|
||||
} else {
|
||||
next(null, { uidsToNotify: data.uids, uidsToEmail: [] });
|
||||
}
|
||||
|
||||
plugins.fireHook('action:notification.pushed', { notification: notification, uids: uids });
|
||||
},
|
||||
function (results, next) {
|
||||
async.parallel([
|
||||
function (next) {
|
||||
sendNotification(results.uidsToNotify, next);
|
||||
},
|
||||
function (next) {
|
||||
sendEmail(results.uidsToEmail, next);
|
||||
},
|
||||
], function (err) {
|
||||
next(err, results);
|
||||
});
|
||||
},
|
||||
function (results, next) {
|
||||
plugins.fireHook('action:notification.pushed', {
|
||||
notification: notification,
|
||||
uids: results.uidsToNotify,
|
||||
uidsNotified: results.uidsToNotify,
|
||||
uidsEmailed: results.uidsToEmail,
|
||||
});
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
|
||||
@@ -97,12 +97,12 @@ Plugins.reload = function (callback) {
|
||||
function (next) {
|
||||
// If some plugins are incompatible, throw the warning here
|
||||
if (Plugins.versionWarning.length && nconf.get('isPrimary') === 'true') {
|
||||
process.stdout.write('\n');
|
||||
console.log('');
|
||||
winston.warn('[plugins/load] The following plugins may not be compatible with your version of NodeBB. This may cause unintended behaviour or crashing. In the event of an unresponsive NodeBB caused by this plugin, run `./nodebb reset -p PLUGINNAME` to disable it.');
|
||||
for (var x = 0, numPlugins = Plugins.versionWarning.length; x < numPlugins; x += 1) {
|
||||
process.stdout.write(' * '.yellow + Plugins.versionWarning[x] + '\n');
|
||||
console.log(' * '.yellow + Plugins.versionWarning[x]);
|
||||
}
|
||||
process.stdout.write('\n');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
Object.keys(Plugins.loadedHooks).forEach(function (hook) {
|
||||
@@ -119,10 +119,6 @@ Plugins.reload = function (callback) {
|
||||
|
||||
Plugins.reloadRoutes = function (callback) {
|
||||
var router = express.Router();
|
||||
// var ensureLoggedIn = require('connect-ensure-login');
|
||||
|
||||
// router.all('(/api/admin|/api/admin/*?)', middleware.isAdmin);
|
||||
// router.all('(/admin|/admin/*?)', ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login?local=1'), middleware.applyCSRF, middleware.isAdmin);
|
||||
|
||||
router.hotswapId = 'plugins';
|
||||
router.render = function () {
|
||||
@@ -219,7 +215,7 @@ Plugins.list = function (matching, callback) {
|
||||
}, function (err, res, body) {
|
||||
if (err) {
|
||||
winston.error('Error parsing plugins', err);
|
||||
return callback(err);
|
||||
return Plugins.normalise([], callback);
|
||||
}
|
||||
|
||||
Plugins.normalise(body, callback);
|
||||
|
||||
@@ -33,10 +33,10 @@ Data.getPluginPaths = getPluginPaths;
|
||||
function loadPluginInfo(pluginPath, callback) {
|
||||
async.parallel({
|
||||
package: function (next) {
|
||||
fs.readFile(path.join(pluginPath, 'package.json'), next);
|
||||
fs.readFile(path.join(pluginPath, 'package.json'), 'utf8', next);
|
||||
},
|
||||
plugin: function (next) {
|
||||
fs.readFile(path.join(pluginPath, 'plugin.json'), next);
|
||||
fs.readFile(path.join(pluginPath, 'plugin.json'), 'utf8', next);
|
||||
},
|
||||
}, function (err, results) {
|
||||
if (err) {
|
||||
|
||||
@@ -53,17 +53,25 @@ module.exports = function (Posts) {
|
||||
user.setUserField(data.uid, 'lastqueuetime', Date.now(), next);
|
||||
},
|
||||
function (next) {
|
||||
notifications.create({
|
||||
nid: 'post-queued-' + id,
|
||||
mergeId: 'post-queue',
|
||||
bodyShort: '[[notifications:post_awaiting_review]]',
|
||||
bodyLong: data.content,
|
||||
path: '/post-queue',
|
||||
async.parallel({
|
||||
notification: function (next) {
|
||||
notifications.create({
|
||||
type: 'post-queue',
|
||||
nid: 'post-queue-' + id,
|
||||
mergeId: 'post-queue',
|
||||
bodyShort: '[[notifications:post_awaiting_review]]',
|
||||
bodyLong: data.content,
|
||||
path: '/post-queue',
|
||||
}, next);
|
||||
},
|
||||
cid: function (next) {
|
||||
getCid(type, data, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (notification, next) {
|
||||
if (notification) {
|
||||
notifications.pushGroups(notification, ['administrators', 'Global Moderators'], next);
|
||||
function (results, next) {
|
||||
if (results.notification) {
|
||||
notifications.pushGroups(results.notification, ['administrators', 'Global Moderators', 'cid:' + results.cid + ':privileges:moderate'], next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
@@ -79,20 +87,26 @@ module.exports = function (Posts) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
function getCid(type, data, callback) {
|
||||
if (type === 'topic') {
|
||||
return setImmediate(callback, null, data.cid);
|
||||
} else if (type === 'reply') {
|
||||
topics.getTopicField(data.tid, 'cid', callback);
|
||||
} else {
|
||||
return setImmediate(callback, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
function canPost(type, data, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
if (type === 'topic') {
|
||||
next(null, data.cid);
|
||||
} else if (type === 'reply') {
|
||||
topics.getTopicField(data.tid, 'cid', next);
|
||||
}
|
||||
getCid(type, data, next);
|
||||
},
|
||||
function (cid, next) {
|
||||
async.parallel({
|
||||
canPost: function (next) {
|
||||
if (type === 'topic') {
|
||||
privileges.categories.can('topics:create', data.cid, data.uid, next);
|
||||
privileges.categories.can('topics:create', cid, data.uid, next);
|
||||
} else if (type === 'reply') {
|
||||
privileges.categories.can('topics:reply', cid, data.uid, next);
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ module.exports = function (Posts) {
|
||||
},
|
||||
function (results, next) {
|
||||
if (parseInt(uid, 10) === parseInt(results.owner, 10)) {
|
||||
return callback(new Error('self-vote'));
|
||||
return callback(new Error('[[error:self-vote]]'));
|
||||
}
|
||||
|
||||
if (command === 'downvote' && parseInt(results.reputation, 10) < parseInt(meta.config['privileges:downvote'], 10)) {
|
||||
@@ -232,6 +232,7 @@ module.exports = function (Posts) {
|
||||
user: {
|
||||
reputation: newreputation,
|
||||
},
|
||||
fromuid: uid,
|
||||
post: postData,
|
||||
upvote: type === 'upvote' && !unvote,
|
||||
downvote: type === 'downvote' && !unvote,
|
||||
|
||||
84
src/prestart.js
Normal file
84
src/prestart.js
Normal file
@@ -0,0 +1,84 @@
|
||||
'use strict';
|
||||
|
||||
var nconf = require('nconf');
|
||||
var url = require('url');
|
||||
var winston = require('winston');
|
||||
var path = require('path');
|
||||
|
||||
var pkg = require('../package.json');
|
||||
var dirname = require('./cli/paths').baseDir;
|
||||
|
||||
function setupWinston() {
|
||||
winston.remove(winston.transports.Console);
|
||||
winston.add(winston.transports.Console, {
|
||||
colorize: true,
|
||||
timestamp: function () {
|
||||
var date = new Date();
|
||||
return nconf.get('json-logging') ? date.toJSON() :
|
||||
date.getDate() + '/' + (date.getMonth() + 1) + ' ' +
|
||||
date.toTimeString().substr(0, 8) + ' [' + global.process.pid + ']';
|
||||
},
|
||||
level: nconf.get('log-level') || (global.env === 'production' ? 'info' : 'verbose'),
|
||||
json: !!nconf.get('json-logging'),
|
||||
stringify: !!nconf.get('json-logging'),
|
||||
});
|
||||
}
|
||||
|
||||
function loadConfig(configFile) {
|
||||
winston.verbose('* using configuration stored in: %s', configFile);
|
||||
|
||||
nconf.file({
|
||||
file: configFile,
|
||||
});
|
||||
|
||||
nconf.defaults({
|
||||
base_dir: dirname,
|
||||
themes_path: path.join(dirname, 'node_modules'),
|
||||
upload_path: 'public/uploads',
|
||||
views_dir: path.join(dirname, 'build/public/templates'),
|
||||
version: pkg.version,
|
||||
});
|
||||
|
||||
if (!nconf.get('isCluster')) {
|
||||
nconf.set('isPrimary', 'true');
|
||||
nconf.set('isCluster', 'false');
|
||||
}
|
||||
|
||||
// Ensure themes_path is a full filepath
|
||||
nconf.set('themes_path', path.resolve(dirname, nconf.get('themes_path')));
|
||||
nconf.set('core_templates_path', path.join(dirname, 'src/views'));
|
||||
nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-persona/templates'));
|
||||
|
||||
nconf.set('upload_path', path.resolve(nconf.get('base_dir'), nconf.get('upload_path')));
|
||||
|
||||
if (nconf.get('url')) {
|
||||
nconf.set('url_parsed', url.parse(nconf.get('url')));
|
||||
}
|
||||
|
||||
// Explicitly cast 'jobsDisabled' as Bool
|
||||
var castAsBool = ['jobsDisabled'];
|
||||
nconf.stores.env.readOnly = false;
|
||||
castAsBool.forEach(function (prop) {
|
||||
var value = nconf.get(prop);
|
||||
if (value) {
|
||||
nconf.set(prop, typeof value === 'boolean' ? value : String(value).toLowerCase() === 'true');
|
||||
}
|
||||
});
|
||||
nconf.stores.env.readOnly = true;
|
||||
}
|
||||
|
||||
function versionCheck() {
|
||||
var version = process.version.slice(1);
|
||||
var range = pkg.engines.node;
|
||||
var semver = require('semver');
|
||||
var compatible = semver.satisfies(version, range);
|
||||
|
||||
if (!compatible) {
|
||||
winston.warn('Your version of Node.js is too outdated for NodeBB. Please update your version of Node.js.');
|
||||
winston.warn('Recommended ' + range.green + ', '.reset + version.yellow + ' provided\n'.reset);
|
||||
}
|
||||
}
|
||||
|
||||
exports.setupWinston = setupWinston;
|
||||
exports.loadConfig = loadConfig;
|
||||
exports.versionCheck = versionCheck;
|
||||
@@ -200,7 +200,7 @@ module.exports = function (privileges) {
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 1;
|
||||
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 0;
|
||||
var canFlag = results.isAdminOrMod || parseInt(results.userReputation, 10) >= minimumReputation;
|
||||
next(null, { flag: canFlag });
|
||||
},
|
||||
|
||||
@@ -7,7 +7,8 @@ module.exports = function (app, middleware, controllers) {
|
||||
var middlewares = [middleware.checkGlobalPrivacySettings];
|
||||
var accountMiddlewares = [middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions];
|
||||
|
||||
setupPageRoute(app, '/uid/:uid/:section1?/:section2?', middleware, [], middleware.redirectUidToUserslug);
|
||||
setupPageRoute(app, '/me/*', middleware, [], middleware.redirectMeToUserslug);
|
||||
setupPageRoute(app, '/uid/:uid*', middleware, [], middleware.redirectUidToUserslug);
|
||||
|
||||
setupPageRoute(app, '/user/:userslug', middleware, middlewares, controllers.accounts.profile.get);
|
||||
setupPageRoute(app, '/user/:userslug/following', middleware, middlewares, controllers.accounts.follow.getFollowing);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
var helpers = {};
|
||||
var helpers = module.exports;
|
||||
|
||||
helpers.setupPageRoute = function (router, name, middleware, middlewares, controller) {
|
||||
middlewares = [middleware.maintenanceMode, middleware.registrationComplete, middleware.pageView, middleware.pluginHooks].concat(middlewares);
|
||||
@@ -13,5 +13,3 @@ helpers.setupAdminPageRoute = function (router, name, middleware, middlewares, c
|
||||
router.get(name, middleware.admin.buildHeader, middlewares, controller);
|
||||
router.get('/api' + name, middlewares, controller);
|
||||
};
|
||||
|
||||
module.exports = helpers;
|
||||
|
||||
@@ -122,7 +122,9 @@ module.exports = function (app, middleware, hotswapIds, callback) {
|
||||
app.use(middleware.stripLeadingSlashes);
|
||||
|
||||
// handle custom homepage routes
|
||||
app.use(relativePath, controllers.home);
|
||||
app.use(relativePath, controllers.home.rewrite);
|
||||
// homepage handled by `action:homepage.get:[route]`
|
||||
setupPageRoute(app, '/', middleware, [], controllers.home.pluginHook);
|
||||
|
||||
adminRoutes(router, middleware, controllers);
|
||||
metaRoutes(router, middleware, controllers);
|
||||
|
||||
@@ -232,7 +232,7 @@ SocketAdmin.email.test = function (socket, data, callback) {
|
||||
switch (data.template) {
|
||||
case 'digest':
|
||||
userDigest.execute({
|
||||
interval: 'day',
|
||||
interval: 'alltime',
|
||||
subscribers: [socket.uid],
|
||||
}, callback);
|
||||
break;
|
||||
|
||||
@@ -13,7 +13,7 @@ var notifications = require('../notifications');
|
||||
var plugins = require('../plugins');
|
||||
var utils = require('../utils');
|
||||
|
||||
var SocketHelpers = {};
|
||||
var SocketHelpers = module.exports;
|
||||
|
||||
SocketHelpers.notifyOnlineUsers = function (uid, result) {
|
||||
winston.warn('[deprecated] SocketHelpers.notifyOnlineUsers, consider using socketHelpers.notifyNew(uid, \'newPost\', result);');
|
||||
@@ -171,6 +171,51 @@ SocketHelpers.sendNotificationToTopicOwner = function (tid, fromuid, command, no
|
||||
});
|
||||
};
|
||||
|
||||
SocketHelpers.upvote = function (data, notification) {
|
||||
if (!data || !data.post || !data.post.uid || !data.post.votes || !data.post.pid || !data.fromuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
var votes = data.post.votes;
|
||||
var touid = data.post.uid;
|
||||
var fromuid = data.fromuid;
|
||||
var pid = data.post.pid;
|
||||
|
||||
var shouldNotify = {
|
||||
all: function () {
|
||||
return votes > 0;
|
||||
},
|
||||
everyTen: function () {
|
||||
return votes > 0 && votes % 10 === 0;
|
||||
},
|
||||
logarithmic: function () {
|
||||
return votes > 1 && Math.log10(votes) % 1 === 0;
|
||||
},
|
||||
disabled: function () {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.getSettings(touid, next);
|
||||
},
|
||||
function (settings, next) {
|
||||
var should = shouldNotify[settings.upvoteNotifFreq] || shouldNotify.all;
|
||||
|
||||
if (should()) {
|
||||
SocketHelpers.sendNotificationToPostOwner(pid, fromuid, 'upvote', notification);
|
||||
}
|
||||
|
||||
next();
|
||||
},
|
||||
], function (err) {
|
||||
if (err) {
|
||||
winston.error(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
SocketHelpers.rescindUpvoteNotification = function (pid, fromuid) {
|
||||
var uid;
|
||||
async.waterfall([
|
||||
@@ -199,5 +244,3 @@ SocketHelpers.emitToTopicAndCategory = function (event, data) {
|
||||
websockets.in('topic_' + data.tid).emit(event, data);
|
||||
websockets.in('category_' + data.cid).emit(event, data);
|
||||
};
|
||||
|
||||
module.exports = SocketHelpers;
|
||||
|
||||
@@ -21,17 +21,31 @@ SocketModules.settings = {};
|
||||
/* Chat */
|
||||
|
||||
SocketModules.chats.getRaw = function (socket, data, callback) {
|
||||
if (!data || !data.hasOwnProperty('mid') || !data.hasOwnProperty('roomId')) {
|
||||
if (!data || !data.hasOwnProperty('mid')) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Messaging.isUserInRoom(socket.uid, data.roomId, next);
|
||||
Messaging.getMessageField(data.mid, 'roomId', next);
|
||||
},
|
||||
function (inRoom, next) {
|
||||
if (!inRoom) {
|
||||
function (roomId, next) {
|
||||
async.parallel({
|
||||
isAdmin: function (next) {
|
||||
user.isAdministrator(socket.uid, next);
|
||||
},
|
||||
hasMessage: function (next) {
|
||||
db.isSortedSetMember('uid:' + socket.uid + ':chat:room:' + roomId + ':mids', data.mid, next);
|
||||
},
|
||||
inRoom: function (next) {
|
||||
Messaging.isUserInRoom(socket.uid, roomId, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
if (!results.isAdmin && (!results.inRoom || !results.hasMessage)) {
|
||||
return next(new Error('[[error:not-allowed]]'));
|
||||
}
|
||||
|
||||
Messaging.getMessageField(data.mid, 'content', next);
|
||||
},
|
||||
], callback);
|
||||
|
||||
@@ -69,7 +69,9 @@ function executeCommand(socket, command, eventName, notification, data, callback
|
||||
websockets.in(data.room_id).emit('event:' + eventName, result);
|
||||
}
|
||||
|
||||
if (result && notification) {
|
||||
if (result && command === 'upvote') {
|
||||
socketHelpers.upvote(result, notification);
|
||||
} else if (result && notification) {
|
||||
socketHelpers.sendNotificationToPostOwner(data.pid, socket.uid, command, notification);
|
||||
} else if (result && command === 'unvote') {
|
||||
socketHelpers.rescindUpvoteNotification(data.pid, socket.uid);
|
||||
|
||||
@@ -75,7 +75,7 @@ module.exports = function (SocketPosts) {
|
||||
},
|
||||
function (results, next) {
|
||||
if (results.isMain && results.isLast) {
|
||||
deleteTopicOf(data.pid, socket, next);
|
||||
deleteOrRestoreTopicOf('delete', data.pid, socket, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
@@ -99,12 +99,23 @@ module.exports = function (SocketPosts) {
|
||||
if (!data || !data.pid) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
var postData;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
posts.tools.restore(socket.uid, data.pid, next);
|
||||
},
|
||||
function (postData, next) {
|
||||
function (_postData, next) {
|
||||
postData = _postData;
|
||||
isMainAndLastPost(data.pid, next);
|
||||
},
|
||||
function (results, next) {
|
||||
if (results.isMain && results.isLast) {
|
||||
deleteOrRestoreTopicOf('restore', data.pid, socket, next);
|
||||
} else {
|
||||
setImmediate(next);
|
||||
}
|
||||
},
|
||||
function (next) {
|
||||
websockets.in('topic_' + data.tid).emit('event:post_restored', postData);
|
||||
|
||||
events.log({
|
||||
@@ -185,13 +196,19 @@ module.exports = function (SocketPosts) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
function deleteTopicOf(pid, socket, callback) {
|
||||
function deleteOrRestoreTopicOf(command, pid, socket, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
posts.getTopicFields(pid, ['tid', 'cid'], next);
|
||||
posts.getTopicFields(pid, ['tid', 'cid', 'deleted'], next);
|
||||
},
|
||||
function (topic, next) {
|
||||
socketTopics.doTopicAction('delete', 'event:topic_deleted', socket, { tids: [topic.tid], cid: topic.cid }, next);
|
||||
if (parseInt(topic.deleted, 10) !== 1 && command === 'delete') {
|
||||
socketTopics.doTopicAction('delete', 'event:topic_deleted', socket, { tids: [topic.tid], cid: topic.cid }, next);
|
||||
} else if (parseInt(topic.deleted, 10) === 1 && command === 'restore') {
|
||||
socketTopics.doTopicAction('restore', 'event:topic_restored', socket, { tids: [topic.tid], cid: topic.cid }, next);
|
||||
} else {
|
||||
setImmediate(next);
|
||||
}
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ var utils = require('../../utils');
|
||||
|
||||
module.exports = function (SocketTopics) {
|
||||
SocketTopics.isTagAllowed = function (socket, data, callback) {
|
||||
if (!data || !data.cid || !data.tag) {
|
||||
if (!data || !utils.isNumber(data.cid) || !data.tag) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
async.waterfall([
|
||||
@@ -15,10 +15,7 @@ module.exports = function (SocketTopics) {
|
||||
db.getSortedSetRange('cid:' + data.cid + ':tag:whitelist', 0, -1, next);
|
||||
},
|
||||
function (tagWhitelist, next) {
|
||||
if (!tagWhitelist.length) {
|
||||
return next(null, true);
|
||||
}
|
||||
next(null, tagWhitelist.indexOf(data.tag) !== -1);
|
||||
next(null, !tagWhitelist.length || tagWhitelist.includes(data.tag));
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -2,15 +2,11 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
|
||||
var db = require('../database');
|
||||
var user = require('../user');
|
||||
var posts = require('../posts');
|
||||
var notifications = require('../notifications');
|
||||
var privileges = require('../privileges');
|
||||
var meta = require('../meta');
|
||||
var emailer = require('../emailer');
|
||||
var plugins = require('../plugins');
|
||||
var utils = require('../utils');
|
||||
|
||||
@@ -239,36 +235,6 @@ module.exports = function (Topics) {
|
||||
notifications.push(notification, followers);
|
||||
}
|
||||
|
||||
if (parseInt(meta.config.disableEmailSubscriptions, 10) === 1) {
|
||||
return next();
|
||||
}
|
||||
|
||||
async.eachLimit(followers, 3, function (toUid, next) {
|
||||
async.parallel({
|
||||
userData: async.apply(user.getUserFields, toUid, ['username', 'userslug']),
|
||||
userSettings: async.apply(user.getSettings, toUid),
|
||||
}, function (err, data) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (data.userSettings.sendPostNotifications) {
|
||||
emailer.send('notif_post', toUid, {
|
||||
pid: postData.pid,
|
||||
subject: '[' + (meta.config.title || 'NodeBB') + '] ' + title,
|
||||
intro: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + titleEscaped + ']]',
|
||||
postBody: postData.content.replace(/"\/\//g, '"https://'),
|
||||
username: data.userData.username,
|
||||
userslug: data.userData.userslug,
|
||||
topicSlug: postData.topic.slug,
|
||||
showUnsubscribe: true,
|
||||
}, next);
|
||||
} else {
|
||||
winston.debug('[topics.notifyFollowers] uid ' + toUid + ' does not have post notifications enabled, skipping.');
|
||||
next();
|
||||
}
|
||||
});
|
||||
});
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
|
||||
@@ -18,7 +18,7 @@ var file = require('../src/file');
|
||||
* 3. Add your script under the "method" property
|
||||
*/
|
||||
|
||||
var Upgrade = {};
|
||||
var Upgrade = module.exports;
|
||||
|
||||
Upgrade.getAll = function (callback) {
|
||||
async.waterfall([
|
||||
@@ -91,7 +91,7 @@ Upgrade.check = function (callback) {
|
||||
};
|
||||
|
||||
Upgrade.run = function (callback) {
|
||||
process.stdout.write('\nParsing upgrade scripts... ');
|
||||
console.log('\nParsing upgrade scripts... ');
|
||||
var queue = [];
|
||||
var skipped = 0;
|
||||
|
||||
@@ -120,7 +120,7 @@ Upgrade.run = function (callback) {
|
||||
};
|
||||
|
||||
Upgrade.runParticular = function (names, callback) {
|
||||
process.stdout.write('\nParsing upgrade scripts... ');
|
||||
console.log('\nParsing upgrade scripts... ');
|
||||
|
||||
async.waterfall([
|
||||
async.apply(file.walk, path.join(__dirname, './upgrades')),
|
||||
@@ -135,7 +135,7 @@ Upgrade.runParticular = function (names, callback) {
|
||||
};
|
||||
|
||||
Upgrade.process = function (files, skipCount, callback) {
|
||||
process.stdout.write('OK'.green + ' | '.reset + String(files.length).cyan + ' script(s) found'.cyan + (skipCount > 0 ? ', '.cyan + String(skipCount).cyan + ' skipped'.cyan : '') + '\n'.reset);
|
||||
console.log('OK'.green + ' | '.reset + String(files.length).cyan + ' script(s) found'.cyan + (skipCount > 0 ? ', '.cyan + String(skipCount).cyan + ' skipped'.cyan : ''));
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
@@ -157,14 +157,14 @@ Upgrade.process = function (files, skipCount, callback) {
|
||||
date: date,
|
||||
};
|
||||
|
||||
process.stdout.write(' → '.white + String('[' + [date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()].join('/') + '] ').gray + String(scriptExport.name).reset + '...\n');
|
||||
console.log(' → '.white + String('[' + [date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()].join('/') + '] ').gray + String(scriptExport.name).reset + '...');
|
||||
|
||||
// For backwards compatibility, cross-reference with schemaDate (if found). If a script's date is older, skip it
|
||||
if ((!results.schemaDate && !results.schemaLogCount) || (scriptExport.timestamp <= results.schemaDate && semver.lt(version, '1.5.0'))) {
|
||||
readline.clearLine(process.stdout, 0);
|
||||
readline.cursorTo(process.stdout, 0);
|
||||
readline.moveCursor(process.stdout, 0, -1);
|
||||
process.stdout.write(' → '.white + String('[' + [date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()].join('/') + '] ').gray + String(scriptExport.name).reset + '... ' + 'skipped\n'.grey);
|
||||
console.log(' → '.white + String('[' + [date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()].join('/') + '] ').gray + String(scriptExport.name).reset + '... ' + 'skipped'.grey);
|
||||
db.sortedSetAdd('schemaLog', Date.now(), path.basename(file, '.js'), next);
|
||||
return;
|
||||
}
|
||||
@@ -174,14 +174,14 @@ Upgrade.process = function (files, skipCount, callback) {
|
||||
progress: progress,
|
||||
})(function (err) {
|
||||
if (err) {
|
||||
process.stdout.write('error\n'.red);
|
||||
console.error('Error occurred');
|
||||
return next(err);
|
||||
}
|
||||
|
||||
readline.clearLine(process.stdout, 0);
|
||||
readline.cursorTo(process.stdout, 0);
|
||||
readline.moveCursor(process.stdout, 0, -1);
|
||||
process.stdout.write(' → '.white + String('[' + [date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()].join('/') + '] ').gray + String(scriptExport.name).reset + '... ' + 'OK\n'.green);
|
||||
console.log(' → '.white + String('[' + [date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()].join('/') + '] ').gray + String(scriptExport.name).reset + '... ' + 'OK'.green);
|
||||
|
||||
// Record success in schemaLog
|
||||
db.sortedSetAdd('schemaLog', Date.now(), path.basename(file, '.js'), next);
|
||||
@@ -189,7 +189,7 @@ Upgrade.process = function (files, skipCount, callback) {
|
||||
}, next);
|
||||
},
|
||||
function (next) {
|
||||
process.stdout.write('Upgrade complete!\n\n'.green);
|
||||
console.log('Upgrade complete!\n'.green);
|
||||
setImmediate(next);
|
||||
},
|
||||
], callback);
|
||||
@@ -212,4 +212,3 @@ Upgrade.incrementProgress = function (value) {
|
||||
process.stdout.write(' [' + (filled ? new Array(filled).join('#') : '') + new Array(unfilled).join(' ') + '] (' + this.current + '/' + (this.total || '??') + ') ' + percentage + ' ');
|
||||
};
|
||||
|
||||
module.exports = Upgrade;
|
||||
|
||||
48
src/upgrades/1.7.1/notification-settings.js
Normal file
48
src/upgrades/1.7.1/notification-settings.js
Normal file
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var batch = require('../../batch');
|
||||
var db = require('../../database');
|
||||
|
||||
module.exports = {
|
||||
name: 'Convert old notification digest settings',
|
||||
timestamp: Date.UTC(2017, 10, 15),
|
||||
method: function (callback) {
|
||||
var progress = this.progress;
|
||||
|
||||
batch.processSortedSet('users:joindate', function (uids, next) {
|
||||
async.eachLimit(uids, 500, function (uid, next) {
|
||||
progress.incr();
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getObjectFields('user:' + uid + ':settings', ['sendChatNotifications', 'sendPostNotifications'], next);
|
||||
},
|
||||
function (userSettings, _next) {
|
||||
if (!userSettings) {
|
||||
return next();
|
||||
}
|
||||
var tasks = [];
|
||||
if (parseInt(userSettings.sendChatNotifications, 10) === 1) {
|
||||
tasks.push(async.apply(db.setObjectField, 'user:' + uid + ':settings', 'notificationType_new-chat', 'notificationemail'));
|
||||
}
|
||||
if (parseInt(userSettings.sendPostNotifications, 10) === 1) {
|
||||
tasks.push(async.apply(db.setObjectField, 'user:' + uid + ':settings', 'notificationType_new-reply', 'notificationemail'));
|
||||
}
|
||||
if (!tasks.length) {
|
||||
return next();
|
||||
}
|
||||
|
||||
async.series(tasks, function (err) {
|
||||
_next(err);
|
||||
});
|
||||
},
|
||||
function (next) {
|
||||
db.deleteObjectFields('user:' + uid + ':settings', ['sendChatNotifications', 'sendPostNotifications'], next);
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
}, {
|
||||
progress: progress,
|
||||
}, callback);
|
||||
},
|
||||
};
|
||||
16
src/user.js
16
src/user.js
@@ -208,13 +208,17 @@ User.isGlobalModerator = function (uid, callback) {
|
||||
privileges.users.isGlobalModerator(uid, callback);
|
||||
};
|
||||
|
||||
User.getPrivileges = function (uid, callback) {
|
||||
async.parallel({
|
||||
isAdmin: async.apply(User.isAdministrator, uid),
|
||||
isGlobalModerator: async.apply(User.isGlobalModerator, uid),
|
||||
isModeratorOfAnyCategory: async.apply(User.isModeratorOfAnyCategory, uid),
|
||||
}, callback);
|
||||
};
|
||||
|
||||
User.isPrivileged = function (uid, callback) {
|
||||
async.parallel([
|
||||
async.apply(User.isAdministrator, uid),
|
||||
async.apply(User.isGlobalModerator, uid),
|
||||
async.apply(User.isModeratorOfAnyCategory, uid),
|
||||
], function (err, results) {
|
||||
callback(err, results ? results.some(Boolean) : false);
|
||||
User.getPrivileges(uid, function (err, results) {
|
||||
callback(err, results ? (results.isAdmin || results.isGlobalModerator || results.isModeratorOfAnyCategory) : false);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ module.exports = function (User) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
notifications.create({
|
||||
type: 'new-register',
|
||||
bodyShort: '[[notifications:new_register, ' + username + ']]',
|
||||
nid: 'new_register:' + username,
|
||||
path: '/admin/manage/registration',
|
||||
|
||||
@@ -78,7 +78,11 @@ module.exports = function (User) {
|
||||
function (results, next) {
|
||||
if (fields.length) {
|
||||
fields = fields.filter(function (field) {
|
||||
return field && results.whitelist.includes(field);
|
||||
var isFieldWhitelisted = field && results.whitelist.includes(field);
|
||||
if (!isFieldWhitelisted) {
|
||||
winston.verbose('[user/getUsersFields] ' + field + ' removed because it is not whitelisted, see `filter:user.whietlistFields`');
|
||||
}
|
||||
return isFieldWhitelisted;
|
||||
});
|
||||
} else {
|
||||
fields = results.whitelist;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
var async = require('async');
|
||||
var request = require('request');
|
||||
var mime = require('mime');
|
||||
var winston = require('winston');
|
||||
|
||||
var plugins = require('../plugins');
|
||||
var file = require('../file');
|
||||
@@ -53,6 +54,12 @@ module.exports = function (User) {
|
||||
};
|
||||
|
||||
User.updateCoverPosition = function (uid, position, callback) {
|
||||
// Reject anything that isn't two percentages
|
||||
if (!/^[\d.]+%\s[\d.]+%$/.test(position)) {
|
||||
winston.warn('[user/updateCoverPosition] Invalid position received: ' + position);
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
User.setUserField(uid, 'cover:position', position, callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ module.exports = function (User) {
|
||||
|
||||
function updateUsername(uid, newUsername, callback) {
|
||||
if (!newUsername) {
|
||||
return callback();
|
||||
return setImmediate(callback);
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
@@ -204,6 +204,9 @@ module.exports = function (User) {
|
||||
User.getUserFields(uid, ['username', 'userslug'], next);
|
||||
},
|
||||
function (userData, next) {
|
||||
if (userData.username === newUsername) {
|
||||
return callback();
|
||||
}
|
||||
async.parallel([
|
||||
function (next) {
|
||||
updateUidMapping('username', uid, newUsername, userData.username, next);
|
||||
|
||||
@@ -74,8 +74,7 @@ module.exports = function (User) {
|
||||
settings.categoryTopicSort = getSetting(settings, 'categoryTopicSort', 'newest_to_oldest');
|
||||
settings.followTopicsOnCreate = parseInt(getSetting(settings, 'followTopicsOnCreate', 1), 10) === 1;
|
||||
settings.followTopicsOnReply = parseInt(getSetting(settings, 'followTopicsOnReply', 0), 10) === 1;
|
||||
settings.sendChatNotifications = parseInt(getSetting(settings, 'sendChatNotifications', 0), 10) === 1;
|
||||
settings.sendPostNotifications = parseInt(getSetting(settings, 'sendPostNotifications', 0), 10) === 1;
|
||||
settings.upvoteNotifFreq = getSetting(settings, 'upvoteNotifFreq', 'all');
|
||||
settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1;
|
||||
settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1;
|
||||
settings.delayImageLoading = parseInt(getSetting(settings, 'delayImageLoading', 1), 10) === 1;
|
||||
@@ -131,6 +130,13 @@ module.exports = function (User) {
|
||||
notificationSound: data.notificationSound,
|
||||
incomingChatSound: data.incomingChatSound,
|
||||
outgoingChatSound: data.outgoingChatSound,
|
||||
upvoteNotifFreq: data.upvoteNotifFreq,
|
||||
notificationType_upvote: data.notificationType_upvote,
|
||||
'notificationType_new-topic': data['notificationType_new-topic'],
|
||||
'notificationType_new-reply': data['notificationType_new-reply'],
|
||||
notificationType_follow: data.notificationType_follow,
|
||||
'notificationType_new-chat': data['notificationType_new-chat'],
|
||||
'notificationType_group-invite': data['notificationType_group-invite'],
|
||||
};
|
||||
|
||||
if (data.bootswatchSkin) {
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
<div class="well inline-block">
|
||||
<label for="condition">[[admin/extend/rewards:condition-then]]</label><br />
|
||||
<select name="rid" data-selected="{active.rid}">
|
||||
<!-- BEGIN rewards -->
|
||||
<!-- BEGIN ../../rewards -->
|
||||
<option value="{rewards.rid}">{rewards.name}</option>
|
||||
<!-- END rewards -->
|
||||
<!-- END ../../rewards -->
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -19,17 +19,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<script src="https://storage.googleapis.com/code.getmdl.io/1.3.0/material.min.js"></script>
|
||||
<script type="text/javascript" src="{relative_path}/assets/vendor/jquery/sortable/Sortable.js?{cache-buster}"></script>
|
||||
<script type="text/javascript" src="{relative_path}/assets/acp.min.js?{cache-buster}"></script>
|
||||
<script type="text/javascript" src="{relative_path}/assets/vendor/colorpicker/colorpicker.js?{cache-buster}"></script>
|
||||
<script type="text/javascript" src="{relative_path}/assets/src/admin/admin.js?{cache-buster}"></script>
|
||||
<script type="text/javascript" src="{relative_path}/assets/vendor/semver/semver.browser.js?{cache-buster}"></script>
|
||||
<script type="text/javascript" src="{relative_path}/assets/vendor/jquery/serializeObject/jquery.ba-serializeobject.min.js?{cache-buster}"></script>
|
||||
<script type="text/javascript" src="{relative_path}/assets/vendor/jquery/deserialize/jquery.deserialize.min.js?{cache-buster}"></script>
|
||||
<script type="text/javascript" src="{relative_path}/assets/vendor/snackbar/snackbar.min.js?{cache-buster}"></script>
|
||||
<script type="text/javascript" src="{relative_path}/assets/vendor/slideout/slideout.min.js?{cache-buster}"></script>
|
||||
<script type="text/javascript" src="{relative_path}/assets/vendor/nprogress.min.js?{cache-buster}"></script>
|
||||
|
||||
<!-- BEGIN scripts -->
|
||||
<script type="text/javascript" src="{scripts.src}"></script>
|
||||
|
||||
@@ -105,6 +105,13 @@
|
||||
<div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/user:account-protection]]</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="adminReloginDuration">[[admin/settings/user:admin-relogin-duration]]</label>
|
||||
<input id="adminReloginDuration" type="text" class="form-control" data-field="adminReloginDuration" placeholder="60" />
|
||||
<p class="help-block">
|
||||
[[admin/settings/user:admin-relogin-duration-help]]
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="loginAttempts">[[admin/settings/user:login-attempts]]</label>
|
||||
<input id="loginAttempts" type="text" class="form-control" data-field="loginAttempts" placeholder="5" />
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<!-- Hero Image, Flush : BEGIN -->
|
||||
<tr>
|
||||
<td bgcolor="#efeff0" style="text-align: center; background-image: url({url}/assets/images/emails/triangularbackground.png); background-size: cover; background-repeat: no-repeat;">
|
||||
<img src="{url}/assets/images/emails/digestheader.png" width="600" height="243" border="0" align="center" style="width: 600px; height: 243px; max-height: 300px; height: auto; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;" class="g-img">
|
||||
<img src="{url}/assets/images/emails/digestheader.jpg" width="600" height="208" border="0" align="center" style="width: 600px; height: 208px; max-height: 300px; height: auto; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;" class="g-img">
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Hero Image, Flush : END -->
|
||||
|
||||
57
src/views/emails/notification.tpl
Normal file
57
src/views/emails/notification.tpl
Normal file
@@ -0,0 +1,57 @@
|
||||
<!-- IMPORT emails/partials/header.tpl -->
|
||||
|
||||
<!-- Email Body : BEGIN -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||
|
||||
<!-- Hero Image, Flush : BEGIN -->
|
||||
<tr>
|
||||
<td bgcolor="#efeff0" style="text-align: center; background-image: url({url}/assets/images/emails/triangularbackground.png); background-size: cover; background-repeat: no-repeat;">
|
||||
<img src="{url}/assets/images/emails/notification.png" width="300" height="300" border="0" align="center" style="width: 300px; height: 300px; max-width: 300px; height: auto; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;" class="g-img">
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Hero Image, Flush : END -->
|
||||
|
||||
<!-- 1 Column Text + Button : BEGIN -->
|
||||
<tr>
|
||||
<td bgcolor="#efeff0">
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td style="padding: 40px 40px 0px 40px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;">
|
||||
<p style="margin: 0 0 20px 0;">{intro}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0px 60px 40px 60px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;">
|
||||
{body}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0 40px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;">
|
||||
<!-- Button : BEGIN -->
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" style="margin: auto;">
|
||||
<tr>
|
||||
<td style="border-radius: 3px; background: #222222; text-align: center;" class="button-td">
|
||||
<a href="{url}{path}" style="background: #222222; border: 15px solid #222222; font-family: sans-serif; font-size: 13px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 3px; font-weight: bold;" class="button-a">
|
||||
<span style="color:#ffffff;" class="button-link"> [[email:notif.cta]] </span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- Button : END -->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 40px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;">
|
||||
<h2 style="margin: 0 0 10px 0; font-family: sans-serif; font-size: 18px; line-height: 21px; color: #333333; font-weight: bold;">[[email:closing]]</h2>
|
||||
<p style="margin: 0;">{site_title}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 1 Column Text + Button : END -->
|
||||
|
||||
</table>
|
||||
<!-- Email Body : END -->
|
||||
|
||||
<!-- IMPORT emails/partials/footer.tpl -->
|
||||
@@ -4,7 +4,7 @@
|
||||
<td style="padding: 40px 10px;width: 100%;font-size: 12px; font-family: sans-serif; line-height:18px; text-align: center; color: #888888;">
|
||||
<br><br>
|
||||
<!-- IF showUnsubscribe -->
|
||||
[[email:notif.post.unsub.info]] <a href="{url}/user/{userslug}/settings">[[email:unsub.cta]]</a>.
|
||||
[[email:notif.post.unsub.info]] <a href="{url}/uid/{uid}/settings">[[email:unsub.cta]]</a>.
|
||||
<!-- ENDIF showUnsubscribe -->
|
||||
<br><br>
|
||||
</td>
|
||||
|
||||
@@ -2,37 +2,28 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>NodeBB Web Installer</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/united/bootstrap.min.css">
|
||||
<link href='https://fonts.googleapis.com/css?family=Roboto:400,300,500,700' rel='stylesheet' type='text/css'>
|
||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="installer.css">
|
||||
|
||||
|
||||
<script type="text/javascript" async defer src="installer.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#">NodeBB</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="active"><a href="/">Installer</a></li>
|
||||
<li><a href="https://docs.nodebb.org" target="_blank">Get Help</a></li>
|
||||
<li><a href="https://community.nodebb.org" target="_blank">Community</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="active"><a href="/">Installer</a></li>
|
||||
<li><a href="https://docs.nodebb.org" target="_blank">Get Help</a></li>
|
||||
<li><a href="https://community.nodebb.org" target="_blank">Community</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -104,7 +95,7 @@
|
||||
</div>
|
||||
<!-- ENDIF !skipDatabaseSetup -->
|
||||
|
||||
<button id="submit" type="submit" class="btn btn-lg btn-success">Install NodeBB <i class="fa fa-spinner fa-spin hide"></i></button>
|
||||
<button id="submit" type="submit" class="btn btn-lg btn-success">Install NodeBB <i class="working hide"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -113,7 +104,7 @@
|
||||
<p>
|
||||
<h1>Congratulations! Your NodeBB has been set-up.</h1>
|
||||
|
||||
<button id="launch" class="btn btn-lg btn-success">Launch NodeBB <i class="fa fa-spinner fa-spin hide"></i></button>
|
||||
<button id="launch" class="btn btn-lg btn-success">Launch NodeBB <i class="working hide"></i></button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -131,9 +122,5 @@
|
||||
</div>
|
||||
<!-- END databases -->
|
||||
</div>
|
||||
|
||||
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="installer.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user