more line ending conversion #3343

v1.18.x
Julian Lam 10 years ago
parent c781e55ea9
commit 7e6703c18e

@ -1,75 +1,75 @@
# <img alt="NodeBB" src="http://i.imgur.com/mYxPPtB.png" />
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/NodeBB/NodeBB?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://travis-ci.org/NodeBB/NodeBB.svg?branch=master)](https://travis-ci.org/NodeBB/NodeBB)
[![Dependency Status](https://david-dm.org/nodebb/nodebb.svg)](https://david-dm.org/nodebb/nodebb)
[![Code Climate](https://codeclimate.com/github/NodeBB/NodeBB/badges/gpa.svg)](https://codeclimate.com/github/NodeBB/NodeBB)
[![Documentation Status](https://readthedocs.org/projects/nodebb/badge/?version=latest)](https://readthedocs.org/projects/nodebb/?badge=latest)
**NodeBB Forum Software** is powered by Node.js and built on either a Redis or MongoDB database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB is compatible down to IE8 and has many modern features out of the box such as social network integration and streaming discussions.
Additional functionality is enabled through the use of third-party plugins.
* [Get NodeBB](http://www.nodebb.org/ "NodeBB")
* [Demo & Meta Discussion](http://community.nodebb.org)
* [Documentation & Installation Instructions](http://docs.nodebb.org)
* [Help translate NodeBB](https://www.transifex.com/projects/p/nodebb/)
* [NodeBB Blog](http://blog.nodebb.org)
* [Join us on IRC](https://kiwiirc.com/client/irc.freenode.net/nodebb) - #nodebb on Freenode
* [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter")
* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
## Screenshots
[![](http://i.imgur.com/VCoOFyqb.png)](http://i.imgur.com/VCoOFyq.png)
[![](http://i.imgur.com/FLOUuIqb.png)](http://i.imgur.com/FLOUuIq.png)
[![](http://i.imgur.com/Ud1LrfIb.png)](http://i.imgur.com/Ud1LrfI.png)
[![](http://i.imgur.com/h6yZ66sb.png)](http://i.imgur.com/h6yZ66s.png)
[![](http://i.imgur.com/o90kVPib.png)](http://i.imgur.com/o90kVPi.png)
[![](http://i.imgur.com/AaRRrU2b.png)](http://i.imgur.com/AaRRrU2.png)
[![](http://i.imgur.com/LmHtPhob.png)](http://i.imgur.com/LmHtPho.png)
[![](http://i.imgur.com/paiJPJkb.jpg)](http://i.imgur.com/paiJPJk.jpg)
[![](http://i.imgur.com/8OLssij.png)](http://i.imgur.com/8OLssij.png)
[![](http://i.imgur.com/JKOc0LZ.png)](http://i.imgur.com/JKOc0LZ.png)
## How can I follow along/contribute?
* Our feature roadmap is hosted on the project wiki's [Version History / Roadmap](https://github.com/NodeBB/NodeBB/wiki/Version-History-%26-Roadmap)
* If you are a developer, feel free to check out the source and submit pull requests. We also have a wide array of [plugins](http://community.nodebb.org/category/7/nodebb-plugins) which would be a great starting point for learning the codebase.
* If you are a designer, [NodeBB needs themes](http://community.nodebb.org/category/10/nodebb-themes)! NodeBB's theming system allows extention of the base templates as well as styling via LESS or CSS. NodeBB's base theme utilizes [Bootstrap 3](http://getbootstrap.com/) but themes can choose to use a different framework altogether.
* If you know languages other than English you can help us translate NodeBB. We use [Transifex](https://www.transifex.com/projects/p/nodebb/) for internationalization.
* Please don't forget to **like**, **follow**, and **star our repo**! Join our growing [community](http://community.nodebb.org) to keep up to date with the latest NodeBB development.
## Requirements
NodeBB requires the following software to be installed:
* A version of Node.js at least 0.10 or greater
* Redis, version 2.8.9 or greater **or** MongoDB, version 2.6 or greater
* nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB)
## Installation
[Please refer to platform-specific installation documentation](http://docs.nodebb.org/en/latest/installing/os.html)
## Securing NodeBB
It is important to ensure that your NodeBB and database servers are secured. Bear these points in mind:
1. While some distributions set up Redis with a more restrictive configuration, Redis by default listens to all interfaces, which is especially dangerous when a server is open to the public. Some suggestions:
* Set `bind_address` to `127.0.0.1` so as to restrict access to the local machine only
* Use `requirepass` to secure Redis behind a password (preferably a long one)
* Familiarise yourself with [Redis Security](http://redis.io/topics/security)
2. Use `iptables` to secure your server from unintended open ports. In Ubuntu, `ufw` provides a friendlier interface to working with `iptables`.
* e.g. If your NodeBB is proxied, no ports should be open except 80 (and possibly 22, for SSH access)
## Upgrading NodeBB
Detailed upgrade instructions are listed in [Upgrading NodeBB](https://docs.nodebb.org/en/latest/upgrading/index.html)
## License
NodeBB is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html).
# <img alt="NodeBB" src="http://i.imgur.com/mYxPPtB.png" />
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/NodeBB/NodeBB?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://travis-ci.org/NodeBB/NodeBB.svg?branch=master)](https://travis-ci.org/NodeBB/NodeBB)
[![Dependency Status](https://david-dm.org/nodebb/nodebb.svg)](https://david-dm.org/nodebb/nodebb)
[![Code Climate](https://codeclimate.com/github/NodeBB/NodeBB/badges/gpa.svg)](https://codeclimate.com/github/NodeBB/NodeBB)
[![Documentation Status](https://readthedocs.org/projects/nodebb/badge/?version=latest)](https://readthedocs.org/projects/nodebb/?badge=latest)
**NodeBB Forum Software** is powered by Node.js and built on either a Redis or MongoDB database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB is compatible down to IE8 and has many modern features out of the box such as social network integration and streaming discussions.
Additional functionality is enabled through the use of third-party plugins.
* [Get NodeBB](http://www.nodebb.org/ "NodeBB")
* [Demo & Meta Discussion](http://community.nodebb.org)
* [Documentation & Installation Instructions](http://docs.nodebb.org)
* [Help translate NodeBB](https://www.transifex.com/projects/p/nodebb/)
* [NodeBB Blog](http://blog.nodebb.org)
* [Join us on IRC](https://kiwiirc.com/client/irc.freenode.net/nodebb) - #nodebb on Freenode
* [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter")
* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
## Screenshots
[![](http://i.imgur.com/VCoOFyqb.png)](http://i.imgur.com/VCoOFyq.png)
[![](http://i.imgur.com/FLOUuIqb.png)](http://i.imgur.com/FLOUuIq.png)
[![](http://i.imgur.com/Ud1LrfIb.png)](http://i.imgur.com/Ud1LrfI.png)
[![](http://i.imgur.com/h6yZ66sb.png)](http://i.imgur.com/h6yZ66s.png)
[![](http://i.imgur.com/o90kVPib.png)](http://i.imgur.com/o90kVPi.png)
[![](http://i.imgur.com/AaRRrU2b.png)](http://i.imgur.com/AaRRrU2.png)
[![](http://i.imgur.com/LmHtPhob.png)](http://i.imgur.com/LmHtPho.png)
[![](http://i.imgur.com/paiJPJkb.jpg)](http://i.imgur.com/paiJPJk.jpg)
[![](http://i.imgur.com/8OLssij.png)](http://i.imgur.com/8OLssij.png)
[![](http://i.imgur.com/JKOc0LZ.png)](http://i.imgur.com/JKOc0LZ.png)
## How can I follow along/contribute?
* Our feature roadmap is hosted on the project wiki's [Version History / Roadmap](https://github.com/NodeBB/NodeBB/wiki/Version-History-%26-Roadmap)
* If you are a developer, feel free to check out the source and submit pull requests. We also have a wide array of [plugins](http://community.nodebb.org/category/7/nodebb-plugins) which would be a great starting point for learning the codebase.
* If you are a designer, [NodeBB needs themes](http://community.nodebb.org/category/10/nodebb-themes)! NodeBB's theming system allows extention of the base templates as well as styling via LESS or CSS. NodeBB's base theme utilizes [Bootstrap 3](http://getbootstrap.com/) but themes can choose to use a different framework altogether.
* If you know languages other than English you can help us translate NodeBB. We use [Transifex](https://www.transifex.com/projects/p/nodebb/) for internationalization.
* Please don't forget to **like**, **follow**, and **star our repo**! Join our growing [community](http://community.nodebb.org) to keep up to date with the latest NodeBB development.
## Requirements
NodeBB requires the following software to be installed:
* A version of Node.js at least 0.10 or greater
* Redis, version 2.8.9 or greater **or** MongoDB, version 2.6 or greater
* nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB)
## Installation
[Please refer to platform-specific installation documentation](http://docs.nodebb.org/en/latest/installing/os.html)
## Securing NodeBB
It is important to ensure that your NodeBB and database servers are secured. Bear these points in mind:
1. While some distributions set up Redis with a more restrictive configuration, Redis by default listens to all interfaces, which is especially dangerous when a server is open to the public. Some suggestions:
* Set `bind_address` to `127.0.0.1` so as to restrict access to the local machine only
* Use `requirepass` to secure Redis behind a password (preferably a long one)
* Familiarise yourself with [Redis Security](http://redis.io/topics/security)
2. Use `iptables` to secure your server from unintended open ports. In Ubuntu, `ufw` provides a friendlier interface to working with `iptables`.
* e.g. If your NodeBB is proxied, no ports should be open except 80 (and possibly 22, for SSH access)
## Upgrading NodeBB
Detailed upgrade instructions are listed in [Upgrading NodeBB](https://docs.nodebb.org/en/latest/upgrading/index.html)
## License
NodeBB is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html).
Interested in a sublicense agreement for use of NodeBB in a non-free/restrictive environment? Contact us at sales@nodebb.org.

832
app.js

@ -1,416 +1,416 @@
/*
NodeBB - A better forum platform for the modern web
https://github.com/NodeBB/NodeBB/
Copyright (C) 2013-2014 NodeBB Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
/*global require, global, process*/
var nconf = require('nconf');
nconf.argv().env('__');
var fs = require('fs'),
os = require('os'),
url = require('url'),
async = require('async'),
semver = require('semver'),
winston = require('winston'),
colors = require('colors'),
path = require('path'),
pkg = require('./package.json'),
utils = require('./public/src/utils.js');
global.env = process.env.NODE_ENV || 'production';
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {
colorize: true,
timestamp: function() {
var date = new Date();
return date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0,5) + ' [' + global.process.pid + ']';
},
level: nconf.get('log-level') || (global.env === 'production' ? 'info' : 'verbose')
});
if(os.platform() === 'linux') {
require('child_process').exec('/usr/bin/which convert', function(err, stdout, stderr) {
if(err || !stdout) {
winston.warn('Couldn\'t find convert. Did you install imagemagick?');
}
});
}
// Alternate configuration file support
var configFile = path.join(__dirname, '/config.json'),
configExists;
if (nconf.get('config')) {
configFile = path.resolve(__dirname, nconf.get('config'));
}
configExists = fs.existsSync(configFile);
if (!nconf.get('setup') && !nconf.get('install') && !nconf.get('upgrade') && !nconf.get('reset') && configExists) {
start();
} else if (nconf.get('setup') || nconf.get('install')) {
setup();
} else if (!configExists) {
require('./install/web').install(nconf.get('port'));
} else if (nconf.get('upgrade')) {
upgrade();
} else if (nconf.get('reset')) {
reset();
}
function loadConfig() {
nconf.file({
file: configFile
});
nconf.defaults({
base_dir: __dirname,
themes_path: path.join(__dirname, 'node_modules'),
views_dir: path.join(__dirname, '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-vanilla/templates'));
if (!process.send) {
// If run using `node app`, log GNU copyright info along with server info
winston.info('NodeBB v' + nconf.get('version') + ' Copyright (C) 2013-2014 NodeBB Inc.');
winston.info('This program comes with ABSOLUTELY NO WARRANTY.');
winston.info('This is free software, and you are welcome to redistribute it under certain conditions.');
winston.info('');
}
}
function start() {
loadConfig();
var db = require('./src/database');
// nconf defaults, if not set in config
if (!nconf.get('upload_path')) {
nconf.set('upload_path', '/public/uploads');
}
// Parse out the relative_url and other goodies from the configured URL
var urlObject = url.parse(nconf.get('url'));
var relativePath = urlObject.pathname !== '/' ? urlObject.pathname : '';
nconf.set('base_url', urlObject.protocol + '//' + urlObject.host);
nconf.set('use_port', !!urlObject.port);
nconf.set('relative_path', relativePath);
nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || 4567);
nconf.set('upload_url', '/uploads/');
if (nconf.get('isPrimary') === 'true') {
winston.info('Time: %s', (new Date()).toString());
winston.info('Initializing NodeBB v%s', nconf.get('version'));
winston.verbose('* using configuration stored in: %s', configFile);
var host = nconf.get(nconf.get('database') + ':host'),
storeLocation = host ? 'at ' + host + (host.indexOf('/') === -1 ? ':' + nconf.get(nconf.get('database') + ':port') : '') : '';
winston.verbose('* using %s store %s', nconf.get('database'), storeLocation);
winston.verbose('* using themes stored in: %s', nconf.get('themes_path'));
}
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
process.on('SIGHUP', restart);
process.on('message', function(message) {
if (typeof message !== 'object') {
return;
}
var meta = require('./src/meta');
var emitter = require('./src/emitter');
switch (message.action) {
case 'reload':
meta.reload();
break;
case 'js-propagate':
meta.js.cache = message.cache;
meta.js.map = message.map;
meta.js.hash = message.hash;
emitter.emit('meta:js.compiled');
winston.verbose('[cluster] Client-side javascript and mapping propagated to worker %s', process.pid);
break;
case 'css-propagate':
meta.css.cache = message.cache;
meta.css.acpCache = message.acpCache;
meta.css.hash = message.hash;
emitter.emit('meta:css.compiled');
winston.verbose('[cluster] Stylesheets propagated to worker %s', process.pid);
break;
case 'templates:compiled':
emitter.emit('templates:compiled');
break;
}
});
process.on('uncaughtException', function(err) {
winston.error(err.stack);
console.log(err.stack);
require('./src/meta').js.killMinifier();
shutdown(1);
});
async.waterfall([
async.apply(db.init),
async.apply(db.checkCompatibility),
function(next) {
require('./src/meta').configs.init(next);
},
function(next) {
require('./src/meta').dependencies.check(next);
},
function(next) {
require('./src/upgrade').check(next);
},
function(next) {
var webserver = require('./src/webserver');
require('./src/socket.io').init(webserver.server);
if (nconf.get('isPrimary') === 'true' && !nconf.get('jobsDisabled')) {
require('./src/notifications').init();
require('./src/user').startJobs();
}
webserver.listen();
}
], function(err) {
if (err) {
switch(err.message) {
case 'schema-out-of-date':
winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:');
winston.warn(' ./nodebb upgrade');
break;
case 'dependencies-out-of-date':
winston.warn('One or more of NodeBB\'s dependent packages are out-of-date. Please run the following command to update them:');
winston.warn(' ./nodebb upgrade');
break;
default:
if (err.stacktrace !== false) {
winston.error(err.stack);
} else {
winston.error(err.message);
}
break;
}
// Either way, bad stuff happened. Abort start.
process.exit();
}
});
}
function setup() {
loadConfig();
winston.info('NodeBB Setup Triggered via Command Line');
var install = require('./src/install');
process.stdout.write('\nWelcome to NodeBB!\n');
process.stdout.write('\nThis looks like a new installation, so you\'ll have to answer a few questions about your environment before we can proceed.\n');
process.stdout.write('Press enter to accept the default setting (shown in brackets).\n');
install.setup(function (err, data) {
var separator = ' ';
if (process.stdout.columns > 10) {
for(var x=0,cols=process.stdout.columns-10;x<cols;x++) {
separator += '=';
}
}
process.stdout.write('\n' + separator + '\n\n');
if (err) {
winston.error('There was a problem completing NodeBB setup: ', err.message);
} else {
if (data.hasOwnProperty('password')) {
process.stdout.write('An administrative user was automatically created for you:\n');
process.stdout.write(' Username: ' + data.username + '\n');
process.stdout.write(' Password: ' + data.password + '\n');
process.stdout.write('\n');
}
process.stdout.write('NodeBB Setup Completed. Run \'./nodebb start\' to manually start your NodeBB server.\n');
// 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();
});
}
function upgrade() {
loadConfig();
require('./src/database').init(function(err) {
if (err) {
winston.error(err.stack);
process.exit();
}
require('./src/meta').configs.init(function () {
require('./src/upgrade').upgrade();
});
});
}
function reset() {
loadConfig();
require('./src/database').init(function(err) {
if (err) {
winston.error(err.message);
process.exit();
}
if (nconf.get('t')) {
resetThemes();
} else if (nconf.get('p')) {
if (nconf.get('p') === true) {
resetPlugins();
} else {
resetPlugin(nconf.get('p'));
}
} else if (nconf.get('w')) {
resetWidgets();
} else if (nconf.get('s')) {
resetSettings();
} 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.message);
}
process.exit();
});
} 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');
process.stdout.write('\nPlugin reset flag (-p) can take a single argument\n');
process.stdout.write(' e.g. ./nodebb reset -p nodebb-plugin-mentions\n');
process.exit();
}
});
}
function resetSettings(callback) {
var meta = require('./src/meta');
meta.configs.set('allowLocalLogin', 1, function(err) {
winston.info('[reset] Settings reset to default');
if (typeof callback === 'function') {
callback(err);
} else {
process.exit();
}
});
}
function resetThemes(callback) {
var meta = require('./src/meta');
meta.themes.set({
type: 'local',
id: 'nodebb-theme-vanilla'
}, function(err) {
winston.info('[reset] Theme reset to Vanilla');
if (typeof callback === 'function') {
callback(err);
} else {
process.exit();
}
});
}
function resetPlugin(pluginId) {
var db = require('./src/database');
db.sortedSetRemove('plugins:active', pluginId, function(err) {
if (err) {
winston.error('[reset] Could not disable plugin: %s encountered error %s', pluginId, err.message);
} else {
winston.info('[reset] Plugin `%s` disabled', pluginId);
}
process.exit();
});
}
function resetPlugins(callback) {
var db = require('./src/database');
db.delete('plugins:active', function(err) {
winston.info('[reset] All Plugins De-activated');
if (typeof callback === 'function') {
callback(err);
} else {
process.exit();
}
});
}
function resetWidgets(callback) {
require('./src/widgets').reset(function(err) {
winston.info('[reset] All Widgets moved to Draft Zone');
if (typeof callback === 'function') {
callback(err);
} else {
process.exit();
}
});
}
function shutdown(code) {
winston.info('[app] Shutdown (SIGTERM/SIGINT) Initialised.');
require('./src/database').close();
winston.info('[app] Database connection closed.');
require('./src/webserver').server.close();
winston.info('[app] Web server closed to connections.');
winston.info('[app] Shutdown complete.');
process.exit(code || 0);
}
function restart() {
if (process.send) {
winston.info('[app] Restarting...');
process.send({
action: 'restart'
});
} else {
winston.error('[app] Could not restart server. Shutting down.');
shutdown(1);
}
}
/*
NodeBB - A better forum platform for the modern web
https://github.com/NodeBB/NodeBB/
Copyright (C) 2013-2014 NodeBB Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
/*global require, global, process*/
var nconf = require('nconf');
nconf.argv().env('__');
var fs = require('fs'),
os = require('os'),
url = require('url'),
async = require('async'),
semver = require('semver'),
winston = require('winston'),
colors = require('colors'),
path = require('path'),
pkg = require('./package.json'),
utils = require('./public/src/utils.js');
global.env = process.env.NODE_ENV || 'production';
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {
colorize: true,
timestamp: function() {
var date = new Date();
return date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0,5) + ' [' + global.process.pid + ']';
},
level: nconf.get('log-level') || (global.env === 'production' ? 'info' : 'verbose')
});
if(os.platform() === 'linux') {
require('child_process').exec('/usr/bin/which convert', function(err, stdout, stderr) {
if(err || !stdout) {
winston.warn('Couldn\'t find convert. Did you install imagemagick?');
}
});
}
// Alternate configuration file support
var configFile = path.join(__dirname, '/config.json'),
configExists;
if (nconf.get('config')) {
configFile = path.resolve(__dirname, nconf.get('config'));
}
configExists = fs.existsSync(configFile);
if (!nconf.get('setup') && !nconf.get('install') && !nconf.get('upgrade') && !nconf.get('reset') && configExists) {
start();
} else if (nconf.get('setup') || nconf.get('install')) {
setup();
} else if (!configExists) {
require('./install/web').install(nconf.get('port'));
} else if (nconf.get('upgrade')) {
upgrade();
} else if (nconf.get('reset')) {
reset();
}
function loadConfig() {
nconf.file({
file: configFile
});
nconf.defaults({
base_dir: __dirname,
themes_path: path.join(__dirname, 'node_modules'),
views_dir: path.join(__dirname, '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-vanilla/templates'));
if (!process.send) {
// If run using `node app`, log GNU copyright info along with server info
winston.info('NodeBB v' + nconf.get('version') + ' Copyright (C) 2013-2014 NodeBB Inc.');
winston.info('This program comes with ABSOLUTELY NO WARRANTY.');
winston.info('This is free software, and you are welcome to redistribute it under certain conditions.');
winston.info('');
}
}
function start() {
loadConfig();
var db = require('./src/database');
// nconf defaults, if not set in config
if (!nconf.get('upload_path')) {
nconf.set('upload_path', '/public/uploads');
}
// Parse out the relative_url and other goodies from the configured URL
var urlObject = url.parse(nconf.get('url'));
var relativePath = urlObject.pathname !== '/' ? urlObject.pathname : '';
nconf.set('base_url', urlObject.protocol + '//' + urlObject.host);
nconf.set('use_port', !!urlObject.port);
nconf.set('relative_path', relativePath);
nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || 4567);
nconf.set('upload_url', '/uploads/');
if (nconf.get('isPrimary') === 'true') {
winston.info('Time: %s', (new Date()).toString());
winston.info('Initializing NodeBB v%s', nconf.get('version'));
winston.verbose('* using configuration stored in: %s', configFile);
var host = nconf.get(nconf.get('database') + ':host'),
storeLocation = host ? 'at ' + host + (host.indexOf('/') === -1 ? ':' + nconf.get(nconf.get('database') + ':port') : '') : '';
winston.verbose('* using %s store %s', nconf.get('database'), storeLocation);
winston.verbose('* using themes stored in: %s', nconf.get('themes_path'));
}
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
process.on('SIGHUP', restart);
process.on('message', function(message) {
if (typeof message !== 'object') {
return;
}
var meta = require('./src/meta');
var emitter = require('./src/emitter');
switch (message.action) {
case 'reload':
meta.reload();
break;
case 'js-propagate':
meta.js.cache = message.cache;
meta.js.map = message.map;
meta.js.hash = message.hash;
emitter.emit('meta:js.compiled');
winston.verbose('[cluster] Client-side javascript and mapping propagated to worker %s', process.pid);
break;
case 'css-propagate':
meta.css.cache = message.cache;
meta.css.acpCache = message.acpCache;
meta.css.hash = message.hash;
emitter.emit('meta:css.compiled');
winston.verbose('[cluster] Stylesheets propagated to worker %s', process.pid);
break;
case 'templates:compiled':
emitter.emit('templates:compiled');
break;
}
});
process.on('uncaughtException', function(err) {
winston.error(err.stack);
console.log(err.stack);
require('./src/meta').js.killMinifier();
shutdown(1);
});
async.waterfall([
async.apply(db.init),
async.apply(db.checkCompatibility),
function(next) {
require('./src/meta').configs.init(next);
},
function(next) {
require('./src/meta').dependencies.check(next);
},
function(next) {
require('./src/upgrade').check(next);
},
function(next) {
var webserver = require('./src/webserver');
require('./src/socket.io').init(webserver.server);
if (nconf.get('isPrimary') === 'true' && !nconf.get('jobsDisabled')) {
require('./src/notifications').init();
require('./src/user').startJobs();
}
webserver.listen();
}
], function(err) {
if (err) {
switch(err.message) {
case 'schema-out-of-date':
winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:');
winston.warn(' ./nodebb upgrade');
break;
case 'dependencies-out-of-date':
winston.warn('One or more of NodeBB\'s dependent packages are out-of-date. Please run the following command to update them:');
winston.warn(' ./nodebb upgrade');
break;
default:
if (err.stacktrace !== false) {
winston.error(err.stack);
} else {
winston.error(err.message);
}
break;
}
// Either way, bad stuff happened. Abort start.
process.exit();
}
});
}
function setup() {
loadConfig();
winston.info('NodeBB Setup Triggered via Command Line');
var install = require('./src/install');
process.stdout.write('\nWelcome to NodeBB!\n');
process.stdout.write('\nThis looks like a new installation, so you\'ll have to answer a few questions about your environment before we can proceed.\n');
process.stdout.write('Press enter to accept the default setting (shown in brackets).\n');
install.setup(function (err, data) {
var separator = ' ';
if (process.stdout.columns > 10) {
for(var x=0,cols=process.stdout.columns-10;x<cols;x++) {
separator += '=';
}
}
process.stdout.write('\n' + separator + '\n\n');
if (err) {
winston.error('There was a problem completing NodeBB setup: ', err.message);
} else {
if (data.hasOwnProperty('password')) {
process.stdout.write('An administrative user was automatically created for you:\n');
process.stdout.write(' Username: ' + data.username + '\n');
process.stdout.write(' Password: ' + data.password + '\n');
process.stdout.write('\n');
}
process.stdout.write('NodeBB Setup Completed. Run \'./nodebb start\' to manually start your NodeBB server.\n');
// 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();
});
}
function upgrade() {
loadConfig();
require('./src/database').init(function(err) {
if (err) {
winston.error(err.stack);
process.exit();
}
require('./src/meta').configs.init(function () {
require('./src/upgrade').upgrade();
});
});
}
function reset() {
loadConfig();
require('./src/database').init(function(err) {
if (err) {
winston.error(err.message);
process.exit();
}
if (nconf.get('t')) {
resetThemes();
} else if (nconf.get('p')) {
if (nconf.get('p') === true) {
resetPlugins();
} else {
resetPlugin(nconf.get('p'));
}
} else if (nconf.get('w')) {
resetWidgets();
} else if (nconf.get('s')) {
resetSettings();
} 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.message);
}
process.exit();
});
} 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');
process.stdout.write('\nPlugin reset flag (-p) can take a single argument\n');
process.stdout.write(' e.g. ./nodebb reset -p nodebb-plugin-mentions\n');
process.exit();
}
});
}
function resetSettings(callback) {
var meta = require('./src/meta');
meta.configs.set('allowLocalLogin', 1, function(err) {
winston.info('[reset] Settings reset to default');
if (typeof callback === 'function') {
callback(err);
} else {
process.exit();
}
});
}
function resetThemes(callback) {
var meta = require('./src/meta');
meta.themes.set({
type: 'local',
id: 'nodebb-theme-vanilla'
}, function(err) {
winston.info('[reset] Theme reset to Vanilla');
if (typeof callback === 'function') {
callback(err);
} else {
process.exit();
}
});
}
function resetPlugin(pluginId) {
var db = require('./src/database');
db.sortedSetRemove('plugins:active', pluginId, function(err) {
if (err) {
winston.error('[reset] Could not disable plugin: %s encountered error %s', pluginId, err.message);
} else {
winston.info('[reset] Plugin `%s` disabled', pluginId);
}
process.exit();
});
}
function resetPlugins(callback) {
var db = require('./src/database');
db.delete('plugins:active', function(err) {
winston.info('[reset] All Plugins De-activated');
if (typeof callback === 'function') {
callback(err);
} else {
process.exit();
}
});
}
function resetWidgets(callback) {
require('./src/widgets').reset(function(err) {
winston.info('[reset] All Widgets moved to Draft Zone');
if (typeof callback === 'function') {
callback(err);
} else {
process.exit();
}
});
}
function shutdown(code) {
winston.info('[app] Shutdown (SIGTERM/SIGINT) Initialised.');
require('./src/database').close();
winston.info('[app] Database connection closed.');
require('./src/webserver').server.close();
winston.info('[app] Web server closed to connections.');
winston.info('[app] Shutdown complete.');
process.exit(code || 0);
}
function restart() {
if (process.send) {
winston.info('[app] Restarting...');
process.send({
action: 'restart'
});
} else {
winston.error('[app] Could not restart server. Shutting down.');
shutdown(1);
}
}

@ -1,34 +1,34 @@
[
{
"name": "Announcements",
"description": "Announcements regarding our community",
"bgColor": "#fda34b",
"color": "#fff",
"icon" : "fa-bullhorn",
"order": 1
},
{
"name": "General Discussion",
"description": "A place to talk about whatever you want",
"bgColor": "#59b3d0",
"color": "#fff",
"icon" : "fa-comments-o",
"order": 2
},
{
"name": "Blogs",
"description": "Blog posts from individual members",
"bgColor": "#86ba4b",
"color": "#fff",
"icon" : "fa-newspaper-o",
"order": 4
},
{
"name": "Comments & Feedback",
"description": "Got a question? Ask away!",
"bgColor": "#e95c5a",
"color": "#fff",
"icon" : "fa-question",
"order": 3
}
[
{
"name": "Announcements",
"description": "Announcements regarding our community",
"bgColor": "#fda34b",
"color": "#fff",
"icon" : "fa-bullhorn",
"order": 1
},
{
"name": "General Discussion",
"description": "A place to talk about whatever you want",
"bgColor": "#59b3d0",
"color": "#fff",
"icon" : "fa-comments-o",
"order": 2
},
{
"name": "Blogs",
"description": "Blog posts from individual members",
"bgColor": "#86ba4b",
"color": "#fff",
"icon" : "fa-newspaper-o",
"order": 4
},
{
"name": "Comments & Feedback",
"description": "Got a question? Ask away!",
"bgColor": "#e95c5a",
"color": "#fff",
"icon" : "fa-question",
"order": 3
}
]

@ -1,312 +1,312 @@
"use strict";
var ajaxify = ajaxify || {};
$(document).ready(function() {
/*global app, templates, utils, socket, config, RELATIVE_PATH*/
var location = document.location || window.location,
rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''),
apiXHR = null,
translator;
// Dumb hack to fool ajaxify into thinking translator is still a global
// When ajaxify is migrated to a require.js module, then this can be merged into the "define" call
require(['translator'], function(_translator) {
translator = _translator;
});
$(window).on('popstate', function (ev) {
ev = ev.originalEvent;
if (ev !== null && ev.state && ev.state.url !== undefined) {
ajaxify.go(ev.state.url, function() {
$(window).trigger('action:popstate', {url: ev.state.url});
}, true);
}
});
ajaxify.currentPage = null;
ajaxify.go = function (url, callback, quiet, search) {
if (!socket.connected) {
if (ajaxify.reconnectAction) {
$(window).off('action:reconnected', ajaxify.reconnectAction);
}
ajaxify.reconnectAction = function(e) {
ajaxify.go(url, callback, quiet, search);
$(window).off(e);
}
$(window).on('action:reconnected', ajaxify.reconnectAction);
}
if (ajaxify.handleRedirects(url)) {
return true;
}
app.enterRoom('');
$(window).off('scroll');
if ($('#content').hasClass('ajaxifying') && apiXHR) {
apiXHR.abort();
}
url = ajaxify.start(url, quiet, search);
$('#footer, #content').removeClass('hide').addClass('ajaxifying');
ajaxify.variables.flush();
ajaxify.loadData(url, function(err, data) {
if (err) {
return onAjaxError(err, url, callback, quiet);
}
app.template = data.template.name;
require(['translator'], function(translator) {
translator.load(config.defaultLang, data.template.name);
renderTemplate(url, data.template.name, data, callback);
});
});
return true;
};
ajaxify.handleRedirects = function(url) {
url = ajaxify.removeRelativePath(url.replace(/\/$/, '')).toLowerCase();
var isAdminRoute = url.startsWith('admin') && window.location.pathname.indexOf(RELATIVE_PATH + '/admin') !== 0;
var uploadsOrApi = url.startsWith('uploads') || url.startsWith('api');
if (isAdminRoute || uploadsOrApi) {
window.open(RELATIVE_PATH + '/' + url, '_top');
return true;
}
return false;
};
ajaxify.start = function(url, quiet, search) {
url = ajaxify.removeRelativePath(url.replace(/^\/|\/$/g, ''));
var hash = window.location.hash;
search = search || '';
$(window).trigger('action:ajaxify.start', {url: url});
if (!window.location.pathname.match(/\/(403|404)$/g)) {
app.previousUrl = window.location.href;
}
ajaxify.currentPage = url;
if (window.history && window.history.pushState) {
window.history[!quiet ? 'pushState' : 'replaceState']({
url: url + search + hash
}, url, RELATIVE_PATH + '/' + url + search + hash);
}
return url;
};
function onAjaxError(err, url, callback, quiet) {
var data = err.data,
textStatus = err.textStatus;
if (data) {
var status = parseInt(data.status, 10);
if (status === 403 || status === 404 || status === 500 || status === 502) {
if (status === 502) {
status = 500;
}
$('#footer, #content').removeClass('hide').addClass('ajaxifying');
return renderTemplate(url, status.toString(), data.responseJSON, (new Date()).getTime(), callback);
} else if (status === 401) {
app.alertError('[[global:please_log_in]]');
app.previousUrl = url;
return ajaxify.go('login');
} else if (status === 302) {
if (data.responseJSON.external) {
window.location.href = data.responseJSON.external;
} else if (typeof data.responseJSON === 'string') {
ajaxify.go(data.responseJSON.slice(1), callback, quiet);
}
}
} else if (textStatus !== 'abort') {
app.alertError(data.responseJSON.error);
}
}
function renderTemplate(url, tpl_url, data, callback) {
$(window).trigger('action:ajaxify.loadingTemplates', {});
templates.parse(tpl_url, data, function(template) {
translator.translate(template, function(translatedTemplate) {
$('#content').html(translatedTemplate);
ajaxify.end(url, tpl_url);
if (typeof callback === 'function') {
callback();
}
$('#content, #footer').removeClass('ajaxifying');
app.refreshTitle(url);
});
});
}
ajaxify.end = function(url, tpl_url) {
function done() {
if (--count === 0) {
$(window).trigger('action:ajaxify.end', {url: url});
}
}
var count = 2;
ajaxify.variables.parse();
ajaxify.loadScript(tpl_url, done);
ajaxify.widgets.render(tpl_url, url, done);
$(window).trigger('action:ajaxify.contentLoaded', {url: url, tpl: tpl_url});
app.processPage();
};
ajaxify.removeRelativePath = function(url) {
if (url.startsWith(RELATIVE_PATH.slice(1))) {
url = url.slice(RELATIVE_PATH.length);
}
return url;
};
ajaxify.refresh = function(e) {
if (e && e instanceof jQuery.Event) {
e.preventDefault();
}
ajaxify.go(ajaxify.currentPage, null, true);
};
ajaxify.loadScript = function(tpl_url, callback) {
var location = !app.inAdmin ? 'forum/' : '';
require([location + tpl_url], function(script) {
if (script && script.init) {
script.init();
}
if (callback) {
callback();
}
});
};
ajaxify.loadData = function(url, callback) {
url = ajaxify.removeRelativePath(url);
$(window).trigger('action:ajaxify.loadingData', {url: url});
apiXHR = $.ajax({
url: RELATIVE_PATH + '/api/' + url,
cache: false,
success: function(data) {
if (!data) {
return;
}
ajaxify.data = data;
data.relative_path = RELATIVE_PATH;
$(window).trigger('action:ajaxify.dataLoaded', {url: url, data: data});
if (callback) {
callback(null, data);
}
},
error: function(data, textStatus) {
if (data.status === 0 && textStatus === 'error') {
data.status = 500;
}
callback({
data: data,
textStatus: textStatus
});
}
});
};
ajaxify.loadTemplate = function(template, callback) {
if (templates.cache[template]) {
callback(templates.cache[template]);
} else {
$.ajax({
url: RELATIVE_PATH + '/templates/' + template + '.tpl' + (config['cache-buster'] ? '?v=' + config['cache-buster'] : ''),
type: 'GET',
success: function(data) {
callback(data.toString());
},
error: function(error) {
throw new Error("Unable to load template: " + template + " (" + error.statusText + ")");
}
});
}
};
function ajaxifyAnchors() {
templates.registerLoader(ajaxify.loadTemplate);
function hrefEmpty(href) {
return href === undefined || href === '' || href === 'javascript:;';
}
// Enhancing all anchors to ajaxify...
$(document.body).on('click', 'a', function (e) {
if (this.target !== '' || (this.protocol !== 'http:' && this.protocol !== 'https:')) {
return;
} else if (hrefEmpty(this.href) || this.protocol === 'javascript:' || $(this).attr('data-ajaxify') === 'false' || $(this).attr('href') === '#') {
return e.preventDefault();
}
if (!e.ctrlKey && !e.shiftKey && !e.metaKey && e.which === 1) {
if (
this.host === '' || // Relative paths are always internal links...
(this.host === window.location.host && this.protocol === window.location.protocol && // Otherwise need to check that protocol and host match
(RELATIVE_PATH.length > 0 ? this.pathname.indexOf(RELATIVE_PATH) === 0 : true)) // Subfolder installs need this additional check
) {
// Internal link
var url = this.pathname.replace(RELATIVE_PATH + '/', '');
// Special handling for urls with hashes
if (window.location.pathname === this.pathname && this.hash.length) {
window.location.hash = this.hash;
} else {
window.location.hash = '';
if (ajaxify.go(url)) {
e.preventDefault();
}
}
} else if (window.location.pathname !== '/outgoing') {
// External Link
if (config.openOutgoingLinksInNewTab) {
window.open(this.href, '_blank');
e.preventDefault();
} else if (config.useOutgoingLinksPage) {
ajaxify.go('outgoing?url=' + encodeURIComponent(this.href));
e.preventDefault();
}
}
}
});
}
if (window.history && window.history.pushState) {
// Progressive Enhancement, ajaxify available only to modern browsers
ajaxifyAnchors();
}
app.load();
templates.cache['500'] = $('.tpl-500').html();
"use strict";
var ajaxify = ajaxify || {};
$(document).ready(function() {
/*global app, templates, utils, socket, config, RELATIVE_PATH*/
var location = document.location || window.location,
rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''),
apiXHR = null,
translator;
// Dumb hack to fool ajaxify into thinking translator is still a global
// When ajaxify is migrated to a require.js module, then this can be merged into the "define" call
require(['translator'], function(_translator) {
translator = _translator;
});
$(window).on('popstate', function (ev) {
ev = ev.originalEvent;
if (ev !== null && ev.state && ev.state.url !== undefined) {
ajaxify.go(ev.state.url, function() {
$(window).trigger('action:popstate', {url: ev.state.url});
}, true);
}
});
ajaxify.currentPage = null;
ajaxify.go = function (url, callback, quiet, search) {
if (!socket.connected) {
if (ajaxify.reconnectAction) {
$(window).off('action:reconnected', ajaxify.reconnectAction);
}
ajaxify.reconnectAction = function(e) {
ajaxify.go(url, callback, quiet, search);
$(window).off(e);
}
$(window).on('action:reconnected', ajaxify.reconnectAction);
}
if (ajaxify.handleRedirects(url)) {
return true;
}
app.enterRoom('');
$(window).off('scroll');
if ($('#content').hasClass('ajaxifying') && apiXHR) {
apiXHR.abort();
}
url = ajaxify.start(url, quiet, search);
$('#footer, #content').removeClass('hide').addClass('ajaxifying');
ajaxify.variables.flush();
ajaxify.loadData(url, function(err, data) {
if (err) {
return onAjaxError(err, url, callback, quiet);
}
app.template = data.template.name;
require(['translator'], function(translator) {
translator.load(config.defaultLang, data.template.name);
renderTemplate(url, data.template.name, data, callback);
});
});
return true;
};
ajaxify.handleRedirects = function(url) {
url = ajaxify.removeRelativePath(url.replace(/\/$/, '')).toLowerCase();
var isAdminRoute = url.startsWith('admin') && window.location.pathname.indexOf(RELATIVE_PATH + '/admin') !== 0;
var uploadsOrApi = url.startsWith('uploads') || url.startsWith('api');
if (isAdminRoute || uploadsOrApi) {
window.open(RELATIVE_PATH + '/' + url, '_top');
return true;
}
return false;
};
ajaxify.start = function(url, quiet, search) {
url = ajaxify.removeRelativePath(url.replace(/^\/|\/$/g, ''));
var hash = window.location.hash;
search = search || '';
$(window).trigger('action:ajaxify.start', {url: url});
if (!window.location.pathname.match(/\/(403|404)$/g)) {
app.previousUrl = window.location.href;
}
ajaxify.currentPage = url;
if (window.history && window.history.pushState) {
window.history[!quiet ? 'pushState' : 'replaceState']({
url: url + search + hash
}, url, RELATIVE_PATH + '/' + url + search + hash);
}
return url;
};
function onAjaxError(err, url, callback, quiet) {
var data = err.data,
textStatus = err.textStatus;
if (data) {
var status = parseInt(data.status, 10);
if (status === 403 || status === 404 || status === 500 || status === 502) {
if (status === 502) {
status = 500;
}
$('#footer, #content').removeClass('hide').addClass('ajaxifying');
return renderTemplate(url, status.toString(), data.responseJSON, (new Date()).getTime(), callback);
} else if (status === 401) {
app.alertError('[[global:please_log_in]]');
app.previousUrl = url;
return ajaxify.go('login');
} else if (status === 302) {
if (data.responseJSON.external) {
window.location.href = data.responseJSON.external;
} else if (typeof data.responseJSON === 'string') {
ajaxify.go(data.responseJSON.slice(1), callback, quiet);
}
}
} else if (textStatus !== 'abort') {
app.alertError(data.responseJSON.error);
}
}
function renderTemplate(url, tpl_url, data, callback) {
$(window).trigger('action:ajaxify.loadingTemplates', {});
templates.parse(tpl_url, data, function(template) {
translator.translate(template, function(translatedTemplate) {
$('#content').html(translatedTemplate);
ajaxify.end(url, tpl_url);
if (typeof callback === 'function') {
callback();
}
$('#content, #footer').removeClass('ajaxifying');
app.refreshTitle(url);
});
});
}
ajaxify.end = function(url, tpl_url) {
function done() {
if (--count === 0) {
$(window).trigger('action:ajaxify.end', {url: url});
}
}
var count = 2;
ajaxify.variables.parse();
ajaxify.loadScript(tpl_url, done);
ajaxify.widgets.render(tpl_url, url, done);
$(window).trigger('action:ajaxify.contentLoaded', {url: url, tpl: tpl_url});
app.processPage();
};
ajaxify.removeRelativePath = function(url) {
if (url.startsWith(RELATIVE_PATH.slice(1))) {
url = url.slice(RELATIVE_PATH.length);
}
return url;
};
ajaxify.refresh = function(e) {
if (e && e instanceof jQuery.Event) {
e.preventDefault();
}
ajaxify.go(ajaxify.currentPage, null, true);
};
ajaxify.loadScript = function(tpl_url, callback) {
var location = !app.inAdmin ? 'forum/' : '';
require([location + tpl_url], function(script) {
if (script && script.init) {
script.init();
}
if (callback) {
callback();
}
});
};
ajaxify.loadData = function(url, callback) {
url = ajaxify.removeRelativePath(url);
$(window).trigger('action:ajaxify.loadingData', {url: url});
apiXHR = $.ajax({
url: RELATIVE_PATH + '/api/' + url,
cache: false,
success: function(data) {
if (!data) {
return;
}
ajaxify.data = data;
data.relative_path = RELATIVE_PATH;
$(window).trigger('action:ajaxify.dataLoaded', {url: url, data: data});
if (callback) {
callback(null, data);
}
},
error: function(data, textStatus) {
if (data.status === 0 && textStatus === 'error') {
data.status = 500;
}
callback({
data: data,
textStatus: textStatus
});
}
});
};
ajaxify.loadTemplate = function(template, callback) {
if (templates.cache[template]) {
callback(templates.cache[template]);
} else {
$.ajax({
url: RELATIVE_PATH + '/templates/' + template + '.tpl' + (config['cache-buster'] ? '?v=' + config['cache-buster'] : ''),
type: 'GET',
success: function(data) {
callback(data.toString());
},
error: function(error) {
throw new Error("Unable to load template: " + template + " (" + error.statusText + ")");
}
});
}
};
function ajaxifyAnchors() {
templates.registerLoader(ajaxify.loadTemplate);
function hrefEmpty(href) {
return href === undefined || href === '' || href === 'javascript:;';
}
// Enhancing all anchors to ajaxify...
$(document.body).on('click', 'a', function (e) {
if (this.target !== '' || (this.protocol !== 'http:' && this.protocol !== 'https:')) {
return;
} else if (hrefEmpty(this.href) || this.protocol === 'javascript:' || $(this).attr('data-ajaxify') === 'false' || $(this).attr('href') === '#') {
return e.preventDefault();
}
if (!e.ctrlKey && !e.shiftKey && !e.metaKey && e.which === 1) {
if (
this.host === '' || // Relative paths are always internal links...
(this.host === window.location.host && this.protocol === window.location.protocol && // Otherwise need to check that protocol and host match
(RELATIVE_PATH.length > 0 ? this.pathname.indexOf(RELATIVE_PATH) === 0 : true)) // Subfolder installs need this additional check
) {
// Internal link
var url = this.pathname.replace(RELATIVE_PATH + '/', '');
// Special handling for urls with hashes
if (window.location.pathname === this.pathname && this.hash.length) {
window.location.hash = this.hash;
} else {
window.location.hash = '';
if (ajaxify.go(url)) {
e.preventDefault();
}
}
} else if (window.location.pathname !== '/outgoing') {
// External Link
if (config.openOutgoingLinksInNewTab) {
window.open(this.href, '_blank');
e.preventDefault();
} else if (config.useOutgoingLinksPage) {
ajaxify.go('outgoing?url=' + encodeURIComponent(this.href));
e.preventDefault();
}
}
}
});
}
if (window.history && window.history.pushState) {
// Progressive Enhancement, ajaxify available only to modern browsers
ajaxifyAnchors();
}
app.load();
templates.cache['500'] = $('.tpl-500').html();
});

File diff suppressed because it is too large Load Diff

@ -1,29 +1,29 @@
*.pyc
*.egg-info
*.db
*.db.old
*.swp
*.db-journal
.coverage
.DS_Store
.installed.cfg
.idea/*
.svn/*
src/website/static/*
src/website/media/*
bin
build
cfcache
develop-eggs
dist
downloads
eggs
parts
*.pyc
*.egg-info
*.db
*.db.old
*.swp
*.db-journal
.coverage
.DS_Store
.installed.cfg
.idea/*
.svn/*
src/website/static/*
src/website/media/*
bin
build
cfcache
develop-eggs
dist
downloads
eggs
parts
tmp
.sass-cache
src/website/settingslocal.py
.sass-cache
src/website/settingslocal.py
stunnel.log

@ -1,22 +1,22 @@

// Persian
// Use DIR attribute for RTL text in Persian Language for ABBR tag .
// By MB.seifollahi@gmail.com
jQuery.timeago.settings.strings = {
prefixAgo: null,
prefixFromNow: null,
suffixAgo: "پیش",
suffixFromNow: "از حال",
seconds: "کمتر از یک دقیقه",
minute: "حدود یک دقیقه",
minutes: "%d دقیقه",
hour: "حدود یک ساعت",
hours: "حدود %d ساعت",
day: "یک روز",
days: "%d روز",
month: "حدود یک ماه",
months: "%d ماه",
year: "حدود یک سال",
years: "%d سال",
wordSeparator: " "
// Persian
// Use DIR attribute for RTL text in Persian Language for ABBR tag .
// By MB.seifollahi@gmail.com
jQuery.timeago.settings.strings = {
prefixAgo: null,
prefixFromNow: null,
suffixAgo: "پیش",
suffixFromNow: "از حال",
seconds: "کمتر از یک دقیقه",
minute: "حدود یک دقیقه",
minutes: "%d دقیقه",
hour: "حدود یک ساعت",
hours: "حدود %d ساعت",
day: "یک روز",
days: "%d روز",
month: "حدود یک ماه",
months: "%d ماه",
year: "حدود یک سال",
years: "%d سال",
wordSeparator: " "
};

@ -1,19 +1,19 @@
//Uzbek
jQuery.timeago.settings.strings = {
prefixAgo: null,
prefixFromNow: "keyin",
suffixAgo: "avval",
suffixFromNow: null,
seconds: "bir necha soniya",
minute: "1 daqiqa",
minutes: function(value) { return "%d daqiqa" },
hour: "1 soat",
hours: function(value) { return "%d soat" },
day: "1 kun",
days: function(value) { return "%d kun" },
month: "1 oy",
months: function(value) { return "%d oy" },
year: "1 yil",
years: function(value) { return "%d yil" },
wordSeparator: " "
};
//Uzbek
jQuery.timeago.settings.strings = {
prefixAgo: null,
prefixFromNow: "keyin",
suffixAgo: "avval",
suffixFromNow: null,
seconds: "bir necha soniya",
minute: "1 daqiqa",
minutes: function(value) { return "%d daqiqa" },
hour: "1 soat",
hours: function(value) { return "%d soat" },
day: "1 kun",
days: function(value) { return "%d kun" },
month: "1 oy",
months: function(value) { return "%d oy" },
year: "1 yil",
years: function(value) { return "%d yil" },
wordSeparator: " "
};

@ -1,85 +1,85 @@
"use strict";
var express = require('express');
function apiRoutes(router, middleware, controllers) {
router.get('/users/csv', middleware.authenticate, controllers.admin.users.getCSV);
var multipart = require('connect-multiparty');
var multipartMiddleware = multipart();
var middlewares = [multipartMiddleware, middleware.validateFiles, middleware.applyCSRF, middleware.authenticate];
router.post('/category/uploadpicture', middlewares, controllers.admin.uploads.uploadCategoryPicture);
router.post('/uploadfavicon', middlewares, controllers.admin.uploads.uploadFavicon);
router.post('/uploadlogo', middlewares, controllers.admin.uploads.uploadLogo);
router.post('/uploadgravatardefault', middlewares, controllers.admin.uploads.uploadGravatarDefault);
}
function adminRouter(middleware, controllers) {
var router = express.Router();
router.use(middleware.admin.buildHeader);
addRoutes(router, middleware, controllers);
return router;
}
function apiRouter(middleware, controllers) {
var router = express.Router();
addRoutes(router, middleware, controllers);
apiRoutes(router, middleware, controllers);
return router;
}
function addRoutes(router, middleware, controllers) {
router.get('/', controllers.admin.home);
router.get('/general/dashboard', controllers.admin.home);
router.get('/general/languages', controllers.admin.languages.get);
router.get('/general/sounds', controllers.admin.sounds.get);
router.get('/general/navigation', controllers.admin.navigation.get);
router.get('/general/homepage', controllers.admin.homepage.get);
router.get('/manage/categories', controllers.admin.categories.getAll);
router.get('/manage/categories/:category_id', controllers.admin.categories.get);
router.get('/manage/tags', controllers.admin.tags.get);
router.get('/manage/flags', controllers.admin.flags.get);
router.get('/manage/users', controllers.admin.users.sortByJoinDate);
router.get('/manage/users/search', controllers.admin.users.search);
router.get('/manage/users/latest', controllers.admin.users.sortByJoinDate);
router.get('/manage/users/sort-posts', controllers.admin.users.sortByPosts);
router.get('/manage/users/sort-reputation', controllers.admin.users.sortByReputation);
router.get('/manage/users/banned', controllers.admin.users.banned);
router.get('/manage/users/registration', controllers.admin.users.registrationQueue);
router.get('/manage/groups', controllers.admin.groups.list);
router.get('/manage/groups/:name', controllers.admin.groups.get);
router.get('/settings/:term?', controllers.admin.settings.get);
router.get('/appearance/:term?', controllers.admin.appearance.get);
router.get('/extend/plugins', controllers.admin.plugins.get);
router.get('/extend/widgets', controllers.admin.extend.widgets);
router.get('/extend/rewards', controllers.admin.extend.rewards);
router.get('/advanced/database', controllers.admin.database.get);
router.get('/advanced/events', controllers.admin.events.get);
router.get('/advanced/logs', controllers.admin.logs.get);
router.get('/advanced/post-cache', controllers.admin.postCache.get);
router.get('/development/logger', controllers.admin.logger.get);
}
module.exports = function(app, middleware, controllers) {
app.use('/admin/', adminRouter(middleware, controllers));
app.use('/api/admin/', apiRouter(middleware, controllers));
};
"use strict";
var express = require('express');
function apiRoutes(router, middleware, controllers) {
router.get('/users/csv', middleware.authenticate, controllers.admin.users.getCSV);
var multipart = require('connect-multiparty');
var multipartMiddleware = multipart();
var middlewares = [multipartMiddleware, middleware.validateFiles, middleware.applyCSRF, middleware.authenticate];
router.post('/category/uploadpicture', middlewares, controllers.admin.uploads.uploadCategoryPicture);
router.post('/uploadfavicon', middlewares, controllers.admin.uploads.uploadFavicon);
router.post('/uploadlogo', middlewares, controllers.admin.uploads.uploadLogo);
router.post('/uploadgravatardefault', middlewares, controllers.admin.uploads.uploadGravatarDefault);
}
function adminRouter(middleware, controllers) {
var router = express.Router();
router.use(middleware.admin.buildHeader);
addRoutes(router, middleware, controllers);
return router;
}
function apiRouter(middleware, controllers) {
var router = express.Router();
addRoutes(router, middleware, controllers);
apiRoutes(router, middleware, controllers);
return router;
}
function addRoutes(router, middleware, controllers) {
router.get('/', controllers.admin.home);
router.get('/general/dashboard', controllers.admin.home);
router.get('/general/languages', controllers.admin.languages.get);
router.get('/general/sounds', controllers.admin.sounds.get);
router.get('/general/navigation', controllers.admin.navigation.get);
router.get('/general/homepage', controllers.admin.homepage.get);
router.get('/manage/categories', controllers.admin.categories.getAll);
router.get('/manage/categories/:category_id', controllers.admin.categories.get);
router.get('/manage/tags', controllers.admin.tags.get);
router.get('/manage/flags', controllers.admin.flags.get);
router.get('/manage/users', controllers.admin.users.sortByJoinDate);
router.get('/manage/users/search', controllers.admin.users.search);
router.get('/manage/users/latest', controllers.admin.users.sortByJoinDate);
router.get('/manage/users/sort-posts', controllers.admin.users.sortByPosts);
router.get('/manage/users/sort-reputation', controllers.admin.users.sortByReputation);
router.get('/manage/users/banned', controllers.admin.users.banned);
router.get('/manage/users/registration', controllers.admin.users.registrationQueue);
router.get('/manage/groups', controllers.admin.groups.list);
router.get('/manage/groups/:name', controllers.admin.groups.get);
router.get('/settings/:term?', controllers.admin.settings.get);
router.get('/appearance/:term?', controllers.admin.appearance.get);
router.get('/extend/plugins', controllers.admin.plugins.get);
router.get('/extend/widgets', controllers.admin.extend.widgets);
router.get('/extend/rewards', controllers.admin.extend.rewards);
router.get('/advanced/database', controllers.admin.database.get);
router.get('/advanced/events', controllers.admin.events.get);
router.get('/advanced/logs', controllers.admin.logs.get);
router.get('/advanced/post-cache', controllers.admin.postCache.get);
router.get('/development/logger', controllers.admin.logger.get);
}
module.exports = function(app, middleware, controllers) {
app.use('/admin/', adminRouter(middleware, controllers));
app.use('/api/admin/', apiRouter(middleware, controllers));
};

@ -1,369 +1,369 @@
"use strict";
var async = require('async'),
validator = require('validator'),
_ = require('underscore'),
db = require('./database'),
posts = require('./posts'),
utils = require('../public/src/utils'),
plugins = require('./plugins'),
user = require('./user'),
categories = require('./categories'),
privileges = require('./privileges');
(function(Topics) {
require('./topics/create')(Topics);
require('./topics/delete')(Topics);
require('./topics/unread')(Topics);
require('./topics/recent')(Topics);
require('./topics/popular')(Topics);
require('./topics/user')(Topics);
require('./topics/fork')(Topics);
require('./topics/posts')(Topics);
require('./topics/follow')(Topics);
require('./topics/tags')(Topics);
require('./topics/teaser')(Topics);
require('./topics/suggested')(Topics);
Topics.exists = function(tid, callback) {
db.isSortedSetMember('topics:tid', tid, callback);
};
Topics.getTopicData = function(tid, callback) {
db.getObject('topic:' + tid, function(err, topic) {
if (err || !topic) {
return callback(err);
}
modifyTopic(topic, callback);
});
};
Topics.getTopicsData = function(tids, callback) {
var keys = [];
for (var i=0; i<tids.length; ++i) {
keys.push('topic:' + tids[i]);
}
db.getObjects(keys, function(err, topics) {
if (err) {
return callback(err);
}
async.map(topics, modifyTopic, callback);
});
};
function modifyTopic(topic, callback) {
if (!topic) {
return callback(null, topic);
}
topic.title = validator.escape(topic.title);
topic.relativeTime = utils.toISOString(topic.timestamp);
topic.lastposttimeISO = utils.toISOString(topic.lastposttime);
callback(null, topic);
}
Topics.getPageCount = function(tid, uid, callback) {
Topics.getTopicField(tid, 'postcount', function(err, postCount) {
if (err) {
return callback(err);
}
if (!parseInt(postCount, 10)) {
return callback(null, 1);
}
user.getSettings(uid, function(err, settings) {
if (err) {
return callback(err);
}
callback(null, Math.ceil((parseInt(postCount, 10) - 1) / settings.postsPerPage));
});
});
};
Topics.getTidPage = function(tid, uid, callback) {
if(!tid) {
return callback(new Error('[[error:invalid-tid]]'));
}
async.parallel({
index: function(next) {
categories.getTopicIndex(tid, next);
},
settings: function(next) {
user.getSettings(uid, next);
}
}, function(err, results) {
if(err) {
return callback(err);
}
callback(null, Math.ceil((results.index + 1) / results.settings.topicsPerPage));
});
};
Topics.getCategoryData = function(tid, callback) {
Topics.getTopicField(tid, 'cid', function(err, cid) {
if (err) {
callback(err);
}
categories.getCategoryData(cid, callback);
});
};
Topics.getTopicsFromSet = function(set, uid, start, stop, callback) {
async.waterfall([
function(next) {
db.getSortedSetRevRange(set, start, stop, next);
},
function(tids, next) {
Topics.getTopics(tids, uid, next);
},
function(topics, next) {
next(null, {topics: topics, nextStart: stop + 1});
}
], callback);
};
Topics.getTopics = function(tids, uid, callback) {
async.waterfall([
function(next) {
privileges.topics.filterTids('read', tids, uid, next);
},
function(tids, next) {
Topics.getTopicsByTids(tids, uid, next);
}
], callback);
};
Topics.getTopicsByTids = function(tids, uid, callback) {
if (!Array.isArray(tids) || !tids.length) {
return callback(null, []);
}
Topics.getTopicsData(tids, function(err, topics) {
function mapFilter(array, field) {
return array.map(function(topic) {
return topic && topic[field] && topic[field].toString();
}).filter(function(value, index, array) {
return utils.isNumber(value) && array.indexOf(value) === index;
});
}
if (err) {
return callback(err);
}
var uids = mapFilter(topics, 'uid');
var cids = mapFilter(topics, 'cid');
async.parallel({
teasers: function(next) {
Topics.getTeasers(topics, next);
},
users: function(next) {
user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], next);
},
categories: function(next) {
categories.getMultipleCategoryFields(cids, ['cid', 'name', 'slug', 'icon', 'bgColor', 'color', 'disabled'], next);
},
hasRead: function(next) {
Topics.hasReadTopics(tids, uid, next);
},
tags: function(next) {
Topics.getTopicsTagsObjects(tids, next);
}
}, function(err, results) {
if (err) {
return callback(err);
}
var users = _.object(uids, results.users);
var categories = _.object(cids, results.categories);
for (var i=0; i<topics.length; ++i) {
if (topics[i]) {
topics[i].category = categories[topics[i].cid];
topics[i].user = users[topics[i].uid];
topics[i].teaser = results.teasers[i];
topics[i].tags = results.tags[i];
topics[i].isOwner = parseInt(topics[i].uid, 10) === parseInt(uid, 10);
topics[i].pinned = parseInt(topics[i].pinned, 10) === 1;
topics[i].locked = parseInt(topics[i].locked, 10) === 1;
topics[i].deleted = parseInt(topics[i].deleted, 10) === 1;
topics[i].unread = !results.hasRead[i];
topics[i].unreplied = parseInt(topics[i].postcount, 10) <= 1;
}
}
topics = topics.filter(function(topic) {
return topic && topic.category && !topic.category.disabled;
});
plugins.fireHook('filter:topics.get', {topics: topics, uid: uid}, function(err, topicData) {
callback(err, topicData.topics);
});
});
});
};
Topics.getTopicWithPosts = function(tid, set, uid, start, stop, reverse, callback) {
Topics.getTopicData(tid, function(err, topicData) {
if (err || !topicData) {
return callback(err || new Error('[[error:no-topic]]'));
}
async.parallel({
posts: async.apply(getMainPostAndReplies, topicData, set, uid, start, stop, reverse),
category: async.apply(Topics.getCategoryData, tid),
threadTools: async.apply(plugins.fireHook, 'filter:topic.thread_tools', {topic: topicData, uid: uid, tools: []}),
tags: async.apply(Topics.getTopicTagsObjects, tid),
isFollowing: async.apply(Topics.isFollowing, [tid], uid)
}, function(err, results) {
if (err) {
return callback(err);
}
topicData.posts = results.posts;
topicData.category = results.category;
topicData.thread_tools = results.threadTools.tools;
topicData.tags = results.tags;
topicData.isFollowing = results.isFollowing[0];
topicData.unreplied = parseInt(topicData.postcount, 10) === 1;
topicData.deleted = parseInt(topicData.deleted, 10) === 1;
topicData.locked = parseInt(topicData.locked, 10) === 1;
topicData.pinned = parseInt(topicData.pinned, 10) === 1;
plugins.fireHook('filter:topic.get', {topic: topicData, uid: uid}, function(err, data) {
callback(err, data ? data.topic : null);
});
});
});
};
function getMainPostAndReplies(topic, set, uid, start, stop, reverse, callback) {
async.waterfall([
function(next) {
posts.getPidsFromSet(set, start, stop, reverse, next);
},
function(pids, next) {
if ((!Array.isArray(pids) || !pids.length) && !topic.mainPid) {
return callback(null, []);
}
if (topic.mainPid) {
pids.unshift(topic.mainPid);
}
posts.getPostsByPids(pids, uid, next);
},
function(posts, next) {
if (!posts.length) {
return next(null, []);
}
if (topic.mainPid) {
posts[0].index = 0;
}
var indices = Topics.calculatePostIndices(start, stop, topic.postcount, reverse);
for (var i=1; i<posts.length; ++i) {
if (posts[i]) {
posts[i].index = indices[i - 1];
}
}
Topics.addPostData(posts, uid, callback);
}
]);
}
Topics.getMainPost = function(tid, uid, callback) {
Topics.getMainPosts([tid], uid, function(err, mainPosts) {
callback(err, Array.isArray(mainPosts) && mainPosts.length ? mainPosts[0] : null);
});
};
Topics.getMainPids = function(tids, callback) {
if (!Array.isArray(tids) || !tids.length) {
return callback(null, []);
}
Topics.getTopicsFields(tids, ['mainPid'], function(err, topicData) {
if (err) {
return callback(err);
}
var mainPids = topicData.map(function(topic) {
return topic && topic.mainPid;
});
callback(null, mainPids);
});
};
Topics.getMainPosts = function(tids, uid, callback) {
Topics.getMainPids(tids, function(err, mainPids) {
if (err) {
return callback(err);
}
getMainPosts(mainPids, uid, callback);
});
};
function getMainPosts(mainPids, uid, callback) {
posts.getPostsByPids(mainPids, uid, function(err, postData) {
if (err) {
return callback(err);
}
postData.forEach(function(post) {
if (post) {
post.index = 0;
}
});
Topics.addPostData(postData, uid, callback);
});
}
Topics.getTopicField = function(tid, field, callback) {
db.getObjectField('topic:' + tid, field, callback);
};
Topics.getTopicFields = function(tid, fields, callback) {
db.getObjectFields('topic:' + tid, fields, callback);
};
Topics.getTopicsFields = function(tids, fields, callback) {
if (!Array.isArray(tids) || !tids.length) {
return callback(null, []);
}
var keys = tids.map(function(tid) {
return 'topic:' + tid;
});
db.getObjectsFields(keys, fields, callback);
};
Topics.setTopicField = function(tid, field, value, callback) {
db.setObjectField('topic:' + tid, field, value, callback);
};
Topics.isLocked = function(tid, callback) {
Topics.getTopicField(tid, 'locked', function(err, locked) {
callback(err, parseInt(locked, 10) === 1);
});
};
Topics.search = function(tid, term, callback) {
if (plugins.hasListeners('filter:topic.search')) {
plugins.fireHook('filter:topic.search', {
tid: tid,
term: term
}, callback);
} else {
callback(new Error('no-plugins-available'), []);
}
};
}(exports));
"use strict";
var async = require('async'),
validator = require('validator'),
_ = require('underscore'),
db = require('./database'),
posts = require('./posts'),
utils = require('../public/src/utils'),
plugins = require('./plugins'),
user = require('./user'),
categories = require('./categories'),
privileges = require('./privileges');
(function(Topics) {
require('./topics/create')(Topics);
require('./topics/delete')(Topics);
require('./topics/unread')(Topics);
require('./topics/recent')(Topics);
require('./topics/popular')(Topics);
require('./topics/user')(Topics);
require('./topics/fork')(Topics);
require('./topics/posts')(Topics);
require('./topics/follow')(Topics);
require('./topics/tags')(Topics);
require('./topics/teaser')(Topics);
require('./topics/suggested')(Topics);
Topics.exists = function(tid, callback) {
db.isSortedSetMember('topics:tid', tid, callback);
};
Topics.getTopicData = function(tid, callback) {
db.getObject('topic:' + tid, function(err, topic) {
if (err || !topic) {
return callback(err);
}
modifyTopic(topic, callback);
});
};
Topics.getTopicsData = function(tids, callback) {
var keys = [];
for (var i=0; i<tids.length; ++i) {
keys.push('topic:' + tids[i]);
}
db.getObjects(keys, function(err, topics) {
if (err) {
return callback(err);
}
async.map(topics, modifyTopic, callback);
});
};
function modifyTopic(topic, callback) {
if (!topic) {
return callback(null, topic);
}
topic.title = validator.escape(topic.title);
topic.relativeTime = utils.toISOString(topic.timestamp);
topic.lastposttimeISO = utils.toISOString(topic.lastposttime);
callback(null, topic);
}
Topics.getPageCount = function(tid, uid, callback) {
Topics.getTopicField(tid, 'postcount', function(err, postCount) {
if (err) {
return callback(err);
}
if (!parseInt(postCount, 10)) {
return callback(null, 1);
}
user.getSettings(uid, function(err, settings) {
if (err) {
return callback(err);
}
callback(null, Math.ceil((parseInt(postCount, 10) - 1) / settings.postsPerPage));
});
});
};
Topics.getTidPage = function(tid, uid, callback) {
if(!tid) {
return callback(new Error('[[error:invalid-tid]]'));
}
async.parallel({
index: function(next) {
categories.getTopicIndex(tid, next);
},
settings: function(next) {
user.getSettings(uid, next);
}
}, function(err, results) {
if(err) {
return callback(err);
}
callback(null, Math.ceil((results.index + 1) / results.settings.topicsPerPage));
});
};
Topics.getCategoryData = function(tid, callback) {
Topics.getTopicField(tid, 'cid', function(err, cid) {
if (err) {
callback(err);
}
categories.getCategoryData(cid, callback);
});
};
Topics.getTopicsFromSet = function(set, uid, start, stop, callback) {
async.waterfall([
function(next) {
db.getSortedSetRevRange(set, start, stop, next);
},
function(tids, next) {
Topics.getTopics(tids, uid, next);
},
function(topics, next) {
next(null, {topics: topics, nextStart: stop + 1});
}
], callback);
};
Topics.getTopics = function(tids, uid, callback) {
async.waterfall([
function(next) {
privileges.topics.filterTids('read', tids, uid, next);
},
function(tids, next) {
Topics.getTopicsByTids(tids, uid, next);
}
], callback);
};
Topics.getTopicsByTids = function(tids, uid, callback) {
if (!Array.isArray(tids) || !tids.length) {
return callback(null, []);
}
Topics.getTopicsData(tids, function(err, topics) {
function mapFilter(array, field) {
return array.map(function(topic) {
return topic && topic[field] && topic[field].toString();
}).filter(function(value, index, array) {
return utils.isNumber(value) && array.indexOf(value) === index;
});
}
if (err) {
return callback(err);
}
var uids = mapFilter(topics, 'uid');
var cids = mapFilter(topics, 'cid');
async.parallel({
teasers: function(next) {
Topics.getTeasers(topics, next);
},
users: function(next) {
user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], next);
},
categories: function(next) {
categories.getMultipleCategoryFields(cids, ['cid', 'name', 'slug', 'icon', 'bgColor', 'color', 'disabled'], next);
},
hasRead: function(next) {
Topics.hasReadTopics(tids, uid, next);
},
tags: function(next) {
Topics.getTopicsTagsObjects(tids, next);
}
}, function(err, results) {
if (err) {
return callback(err);
}
var users = _.object(uids, results.users);
var categories = _.object(cids, results.categories);
for (var i=0; i<topics.length; ++i) {
if (topics[i]) {
topics[i].category = categories[topics[i].cid];
topics[i].user = users[topics[i].uid];
topics[i].teaser = results.teasers[i];
topics[i].tags = results.tags[i];
topics[i].isOwner = parseInt(topics[i].uid, 10) === parseInt(uid, 10);
topics[i].pinned = parseInt(topics[i].pinned, 10) === 1;
topics[i].locked = parseInt(topics[i].locked, 10) === 1;
topics[i].deleted = parseInt(topics[i].deleted, 10) === 1;
topics[i].unread = !results.hasRead[i];
topics[i].unreplied = parseInt(topics[i].postcount, 10) <= 1;
}
}
topics = topics.filter(function(topic) {
return topic && topic.category && !topic.category.disabled;
});
plugins.fireHook('filter:topics.get', {topics: topics, uid: uid}, function(err, topicData) {
callback(err, topicData.topics);
});
});
});
};
Topics.getTopicWithPosts = function(tid, set, uid, start, stop, reverse, callback) {
Topics.getTopicData(tid, function(err, topicData) {
if (err || !topicData) {
return callback(err || new Error('[[error:no-topic]]'));
}
async.parallel({
posts: async.apply(getMainPostAndReplies, topicData, set, uid, start, stop, reverse),
category: async.apply(Topics.getCategoryData, tid),
threadTools: async.apply(plugins.fireHook, 'filter:topic.thread_tools', {topic: topicData, uid: uid, tools: []}),
tags: async.apply(Topics.getTopicTagsObjects, tid),
isFollowing: async.apply(Topics.isFollowing, [tid], uid)
}, function(err, results) {
if (err) {
return callback(err);
}
topicData.posts = results.posts;
topicData.category = results.category;
topicData.thread_tools = results.threadTools.tools;
topicData.tags = results.tags;
topicData.isFollowing = results.isFollowing[0];
topicData.unreplied = parseInt(topicData.postcount, 10) === 1;
topicData.deleted = parseInt(topicData.deleted, 10) === 1;
topicData.locked = parseInt(topicData.locked, 10) === 1;
topicData.pinned = parseInt(topicData.pinned, 10) === 1;
plugins.fireHook('filter:topic.get', {topic: topicData, uid: uid}, function(err, data) {
callback(err, data ? data.topic : null);
});
});
});
};
function getMainPostAndReplies(topic, set, uid, start, stop, reverse, callback) {
async.waterfall([
function(next) {
posts.getPidsFromSet(set, start, stop, reverse, next);
},
function(pids, next) {
if ((!Array.isArray(pids) || !pids.length) && !topic.mainPid) {
return callback(null, []);
}
if (topic.mainPid) {
pids.unshift(topic.mainPid);
}
posts.getPostsByPids(pids, uid, next);
},
function(posts, next) {
if (!posts.length) {
return next(null, []);
}
if (topic.mainPid) {
posts[0].index = 0;
}
var indices = Topics.calculatePostIndices(start, stop, topic.postcount, reverse);
for (var i=1; i<posts.length; ++i) {
if (posts[i]) {
posts[i].index = indices[i - 1];
}
}
Topics.addPostData(posts, uid, callback);
}
]);
}
Topics.getMainPost = function(tid, uid, callback) {
Topics.getMainPosts([tid], uid, function(err, mainPosts) {
callback(err, Array.isArray(mainPosts) && mainPosts.length ? mainPosts[0] : null);
});
};
Topics.getMainPids = function(tids, callback) {
if (!Array.isArray(tids) || !tids.length) {
return callback(null, []);
}
Topics.getTopicsFields(tids, ['mainPid'], function(err, topicData) {
if (err) {
return callback(err);
}
var mainPids = topicData.map(function(topic) {
return topic && topic.mainPid;
});
callback(null, mainPids);
});
};
Topics.getMainPosts = function(tids, uid, callback) {
Topics.getMainPids(tids, function(err, mainPids) {
if (err) {
return callback(err);
}
getMainPosts(mainPids, uid, callback);
});
};
function getMainPosts(mainPids, uid, callback) {
posts.getPostsByPids(mainPids, uid, function(err, postData) {
if (err) {
return callback(err);
}
postData.forEach(function(post) {
if (post) {
post.index = 0;
}
});
Topics.addPostData(postData, uid, callback);
});
}
Topics.getTopicField = function(tid, field, callback) {
db.getObjectField('topic:' + tid, field, callback);
};
Topics.getTopicFields = function(tid, fields, callback) {
db.getObjectFields('topic:' + tid, fields, callback);
};
Topics.getTopicsFields = function(tids, fields, callback) {
if (!Array.isArray(tids) || !tids.length) {
return callback(null, []);
}
var keys = tids.map(function(tid) {
return 'topic:' + tid;
});
db.getObjectsFields(keys, fields, callback);
};
Topics.setTopicField = function(tid, field, value, callback) {
db.setObjectField('topic:' + tid, field, value, callback);
};
Topics.isLocked = function(tid, callback) {
Topics.getTopicField(tid, 'locked', function(err, locked) {
callback(err, parseInt(locked, 10) === 1);
});
};
Topics.search = function(tid, term, callback) {
if (plugins.hasListeners('filter:topic.search')) {
plugins.fireHook('filter:topic.search', {
tid: tid,
term: term
}, callback);
} else {
callback(new Error('no-plugins-available'), []);
}
};
}(exports));

@ -1,230 +1,230 @@
'use strict';
var path = require('path'),
fs = require('fs'),
nconf = require('nconf'),
express = require('express'),
app = express(),
server,
winston = require('winston'),
async = require('async'),
emailer = require('./emailer'),
meta = require('./meta'),
logger = require('./logger'),
plugins = require('./plugins'),
middleware = require('./middleware'),
routes = require('./routes'),
emitter = require('./emitter'),
helpers = require('../public/src/modules/helpers');
if (nconf.get('ssl')) {
server = require('https').createServer({
key: fs.readFileSync(nconf.get('ssl').key),
cert: fs.readFileSync(nconf.get('ssl').cert)
}, app);
} else {
server = require('http').createServer(app);
}
module.exports.server = server;
server.on('error', function(err) {
winston.error(err);
if (err.code === 'EADDRINUSE') {
winston.error('NodeBB address in use, exiting...');
process.exit(0);
} else {
throw err;
}
});
module.exports.listen = function() {
emailer.registerApp(app);
middleware = middleware(app);
helpers.register();
logger.init(app);
emitter.all(['templates:compiled', 'meta:js.compiled', 'meta:css.compiled'], function() {
winston.info('NodeBB Ready');
emitter.emit('nodebb:ready');
listen();
});
initializeNodeBB(function(err) {
if (err) {
winston.error(err);
process.exit();
}
if (process.send) {
process.send({
action: 'ready'
});
}
});
};
function initializeNodeBB(callback) {
var skipJS, skipLess, fromFile = nconf.get('from-file') || '';
if (fromFile.match('js')) {
winston.info('[minifier] Minifying client-side JS skipped');
skipJS = true;
}
if (fromFile.match('less')) {
winston.info('[minifier] Compiling LESS files skipped');
skipLess = true;
}
async.waterfall([
async.apply(cacheStaticFiles),
async.apply(meta.themes.setupPaths),
function(next) {
plugins.init(app, middleware, next);
},
function(next) {
async.parallel([
async.apply(meta.templates.compile),
async.apply(!skipJS ? meta.js.minify : meta.js.getFromFile, app.enabled('minification')),
async.apply(!skipLess ? meta.css.minify : meta.css.getFromFile),
async.apply(meta.sounds.init)
], next);
},
function(results, next) {
plugins.fireHook('static:app.preload', {
app: app,
middleware: middleware
}, next);
},
function(next) {
routes(app, middleware);
next();
}
], callback);
}
function cacheStaticFiles(callback) {
if (global.env === 'development') {
return callback();
}
app.enable('cache');
app.enable('minification');
// Configure cache-buster timestamp
require('child_process').exec('git describe --tags', {
cwd: path.join(__dirname, '../')
}, function(err, stdOut) {
if (!err) {
meta.config['cache-buster'] = stdOut.trim();
callback();
} else {
fs.stat(path.join(__dirname, '../package.json'), function(err, stats) {
if (err) {
return callback(err);
}
meta.config['cache-buster'] = new Date(stats.mtime).getTime();
callback();
});
}
});
}
function listen(callback) {
var port = nconf.get('port');
if (Array.isArray(port)) {
if (!port.length) {
winston.error('[startup] empty ports array in config.json');
process.exit();
}
winston.warn('[startup] If you want to start nodebb on multiple ports please use loader.js');
winston.warn('[startup] Defaulting to first port in array, ' + port[0]);
port = port[0];
if (!port) {
winston.error('[startup] Invalid port, exiting');
process.exit();
}
}
if (port !== 80 && port !== 443 && nconf.get('use_port') === false) {
winston.info('Enabling \'trust proxy\'');
app.enable('trust proxy');
}
if ((port === 80 || port === 443) && process.env.NODE_ENV !== 'development') {
winston.info('Using ports 80 and 443 is not recommend; use a proxy instead. See README.md');
}
var isSocket = isNaN(port),
args = isSocket ? [port] : [port, nconf.get('bind_address')],
bind_address = ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? '0.0.0.0' : nconf.get('bind_address')) + ':' + port,
oldUmask;
args.push(function(err) {
if (err) {
winston.info('[startup] NodeBB was unable to listen on: ' + bind_address);
process.exit();
}
winston.info('NodeBB is now listening on: ' + (isSocket ? port : bind_address));
if (oldUmask) {
process.umask(oldUmask);
}
});
// Alter umask if necessary
if (isSocket) {
oldUmask = process.umask('0000');
module.exports.testSocket(port, function(err) {
if (!err) {
server.listen.apply(server, args);
} else {
winston.error('[startup] NodeBB was unable to secure domain socket access (' + port + ')');
winston.error('[startup] ' + err.message);
process.exit();
}
});
} else {
server.listen.apply(server, args);
}
}
module.exports.testSocket = function(socketPath, callback) {
if (typeof socketPath !== 'string') {
return callback(new Error('invalid socket path : ' + socketPath));
}
var net = require('net');
async.series([
function(next) {
fs.exists(socketPath, function(exists) {
if (exists) {
next();
} else {
callback();
}
});
},
function(next) {
var testSocket = new net.Socket();
testSocket.on('error', function(err) {
next(err.code !== 'ECONNREFUSED' ? err : null);
});
testSocket.connect({ path: socketPath }, function() {
// Something's listening here, abort
callback(new Error('port-in-use'));
});
},
async.apply(fs.unlink, socketPath), // The socket was stale, kick it out of the way
], callback);
};
'use strict';
var path = require('path'),
fs = require('fs'),
nconf = require('nconf'),
express = require('express'),
app = express(),
server,
winston = require('winston'),
async = require('async'),
emailer = require('./emailer'),
meta = require('./meta'),
logger = require('./logger'),
plugins = require('./plugins'),
middleware = require('./middleware'),
routes = require('./routes'),
emitter = require('./emitter'),
helpers = require('../public/src/modules/helpers');
if (nconf.get('ssl')) {
server = require('https').createServer({
key: fs.readFileSync(nconf.get('ssl').key),
cert: fs.readFileSync(nconf.get('ssl').cert)
}, app);
} else {
server = require('http').createServer(app);
}
module.exports.server = server;
server.on('error', function(err) {
winston.error(err);
if (err.code === 'EADDRINUSE') {
winston.error('NodeBB address in use, exiting...');
process.exit(0);
} else {
throw err;
}
});
module.exports.listen = function() {
emailer.registerApp(app);
middleware = middleware(app);
helpers.register();
logger.init(app);
emitter.all(['templates:compiled', 'meta:js.compiled', 'meta:css.compiled'], function() {
winston.info('NodeBB Ready');
emitter.emit('nodebb:ready');
listen();
});
initializeNodeBB(function(err) {
if (err) {
winston.error(err);
process.exit();
}
if (process.send) {
process.send({
action: 'ready'
});
}
});
};
function initializeNodeBB(callback) {
var skipJS, skipLess, fromFile = nconf.get('from-file') || '';
if (fromFile.match('js')) {
winston.info('[minifier] Minifying client-side JS skipped');
skipJS = true;
}
if (fromFile.match('less')) {
winston.info('[minifier] Compiling LESS files skipped');
skipLess = true;
}
async.waterfall([
async.apply(cacheStaticFiles),
async.apply(meta.themes.setupPaths),
function(next) {
plugins.init(app, middleware, next);
},
function(next) {
async.parallel([
async.apply(meta.templates.compile),
async.apply(!skipJS ? meta.js.minify : meta.js.getFromFile, app.enabled('minification')),
async.apply(!skipLess ? meta.css.minify : meta.css.getFromFile),
async.apply(meta.sounds.init)
], next);
},
function(results, next) {
plugins.fireHook('static:app.preload', {
app: app,
middleware: middleware
}, next);
},
function(next) {
routes(app, middleware);
next();
}
], callback);
}
function cacheStaticFiles(callback) {
if (global.env === 'development') {
return callback();
}
app.enable('cache');
app.enable('minification');
// Configure cache-buster timestamp
require('child_process').exec('git describe --tags', {
cwd: path.join(__dirname, '../')
}, function(err, stdOut) {
if (!err) {
meta.config['cache-buster'] = stdOut.trim();
callback();
} else {
fs.stat(path.join(__dirname, '../package.json'), function(err, stats) {
if (err) {
return callback(err);
}
meta.config['cache-buster'] = new Date(stats.mtime).getTime();
callback();
});
}
});
}
function listen(callback) {
var port = nconf.get('port');
if (Array.isArray(port)) {
if (!port.length) {
winston.error('[startup] empty ports array in config.json');
process.exit();
}
winston.warn('[startup] If you want to start nodebb on multiple ports please use loader.js');
winston.warn('[startup] Defaulting to first port in array, ' + port[0]);
port = port[0];
if (!port) {
winston.error('[startup] Invalid port, exiting');
process.exit();
}
}
if (port !== 80 && port !== 443 && nconf.get('use_port') === false) {
winston.info('Enabling \'trust proxy\'');
app.enable('trust proxy');
}
if ((port === 80 || port === 443) && process.env.NODE_ENV !== 'development') {
winston.info('Using ports 80 and 443 is not recommend; use a proxy instead. See README.md');
}
var isSocket = isNaN(port),
args = isSocket ? [port] : [port, nconf.get('bind_address')],
bind_address = ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? '0.0.0.0' : nconf.get('bind_address')) + ':' + port,
oldUmask;
args.push(function(err) {
if (err) {
winston.info('[startup] NodeBB was unable to listen on: ' + bind_address);
process.exit();
}
winston.info('NodeBB is now listening on: ' + (isSocket ? port : bind_address));
if (oldUmask) {
process.umask(oldUmask);
}
});
// Alter umask if necessary
if (isSocket) {
oldUmask = process.umask('0000');
module.exports.testSocket(port, function(err) {
if (!err) {
server.listen.apply(server, args);
} else {
winston.error('[startup] NodeBB was unable to secure domain socket access (' + port + ')');
winston.error('[startup] ' + err.message);
process.exit();
}
});
} else {
server.listen.apply(server, args);
}
}
module.exports.testSocket = function(socketPath, callback) {
if (typeof socketPath !== 'string') {
return callback(new Error('invalid socket path : ' + socketPath));
}
var net = require('net');
async.series([
function(next) {
fs.exists(socketPath, function(exists) {
if (exists) {
next();
} else {
callback();
}
});
},
function(next) {
var testSocket = new net.Socket();
testSocket.on('error', function(err) {
next(err.code !== 'ECONNREFUSED' ? err : null);
});
testSocket.connect({ path: socketPath }, function() {
// Something's listening here, abort
callback(new Error('port-in-use'));
});
},
async.apply(fs.unlink, socketPath), // The socket was stale, kick it out of the way
], callback);
};

Loading…
Cancel
Save