2013-11-18 02:39:08 -06:00
var path = require ( 'path' ) ,
fs = require ( 'fs' ) ,
2014-01-04 18:05:15 -05:00
nconf = require ( 'nconf' ) ,
2013-11-18 02:39:08 -06:00
express = require ( 'express' ) ,
2013-07-10 16:22:03 -04:00
express _namespace = require ( 'express-namespace' ) ,
2013-04-22 15:17:41 -04:00
WebServer = express ( ) ,
2014-01-04 18:05:15 -05:00
server ,
2013-09-30 16:28:22 -04:00
winston = require ( 'winston' ) ,
2013-10-04 01:46:50 -04:00
validator = require ( 'validator' ) ,
2013-10-14 16:41:34 -04:00
async = require ( 'async' ) ,
2013-11-18 02:39:08 -06:00
utils = require ( '../public/src/utils' ) ,
2014-03-01 17:26:26 -05:00
templates = require ( './../public/src/templates' ) , // todo remove
translator = require ( './../public/src/translator' ) ,
2013-12-02 17:10:26 -05:00
db = require ( './database' ) ,
2013-11-18 02:39:08 -06:00
user = require ( './user' ) ,
notifications = require ( './notifications' ) ,
auth = require ( './routes/authentication' ) ,
meta = require ( './meta' ) ,
plugins = require ( './plugins' ) ,
2014-01-25 16:39:27 -05:00
logger = require ( './logger' ) ,
2014-03-01 17:26:26 -05:00
middleware = require ( './middleware' ) ,
2014-03-01 17:35:47 -05:00
routes = require ( './routes' ) ,
2014-02-25 14:13:09 -05:00
admin = require ( './routes/admin' ) ,
apiRoute = require ( './routes/api' ) ,
feedsRoute = require ( './routes/feeds' ) ,
2014-02-25 16:50:58 -05:00
metaRoute = require ( './routes/meta' ) ;
2013-05-02 15:57:43 -04:00
2014-01-04 18:05:15 -05:00
if ( nconf . get ( 'ssl' ) ) {
server = require ( 'https' ) . createServer ( {
key : fs . readFileSync ( nconf . get ( 'ssl' ) . key ) ,
2014-01-04 18:09:43 -05:00
cert : fs . readFileSync ( nconf . get ( 'ssl' ) . cert )
2014-01-04 18:05:15 -05:00
} , WebServer ) ;
} else {
server = require ( 'http' ) . createServer ( WebServer ) ;
}
2014-02-21 23:52:23 -05:00
// Signals
2014-02-22 02:27:14 -05:00
var shutdown = function ( code ) {
winston . info ( '[app] Shutdown (SIGTERM/SIGINT) Initialised.' ) ;
db . close ( ) ;
winston . info ( '[app] Database connection closed.' ) ;
2014-02-21 23:52:23 -05:00
2014-02-27 10:06:31 -05:00
winston . info ( '[app] Shutdown complete.' ) ;
2014-02-22 02:27:14 -05:00
process . exit ( ) ;
} ,
restart = function ( ) {
if ( process . send ) {
winston . info ( '[app] Restarting...' ) ;
process . send ( 'nodebb:restart' ) ;
} else {
winston . error ( '[app] Could not restart server. Shutting down.' ) ;
shutdown ( ) ;
}
} ;
process . on ( 'SIGTERM' , shutdown ) ;
process . on ( 'SIGINT' , shutdown ) ;
process . on ( 'SIGHUP' , restart ) ;
process . on ( 'uncaughtException' , function ( err ) {
winston . error ( '[app] Encountered Uncaught Exception: ' + err . message ) ;
console . log ( err . stack ) ;
restart ( ) ;
2014-02-21 23:52:23 -05:00
} ) ;
2013-09-23 12:50:27 -04:00
( function ( app ) {
2013-11-11 13:25:54 -05:00
"use strict" ;
2014-03-01 17:26:26 -05:00
// this can be moved to app.js
2014-01-25 16:39:27 -05:00
var clientScripts ;
2013-11-25 16:28:07 -05:00
2013-11-25 12:35:54 -05:00
plugins . ready ( function ( ) {
// Minify client-side libraries
meta . js . get ( function ( err , scripts ) {
clientScripts = scripts . map ( function ( script ) {
script = {
script : script
} ;
return script ;
} ) ;
2013-09-24 12:33:51 -04:00
} ) ;
} ) ;
2013-08-23 13:14:36 -04:00
2014-03-01 17:26:26 -05:00
logger . init ( app ) ;
2014-03-01 17:35:47 -05:00
auth . registerApp ( app ) ;
2014-03-01 17:26:26 -05:00
async . series ( {
themesData : meta . themes . get ,
currentThemeData : function ( next ) {
db . getObjectFields ( 'config' , [ 'theme:type' , 'theme:id' , 'theme:staticDir' , 'theme:templates' ] , next ) ;
}
} , function ( err , data ) {
middleware ( app , data ) ;
2014-03-01 17:35:47 -05:00
routes ( app , nconf . get ( 'relative_path' ) ) ;
2014-03-01 17:26:26 -05:00
if ( err ) {
winston . error ( 'Errors were encountered while attempting to initialise NodeBB.' ) ;
process . exit ( ) ;
} else {
if ( process . env . NODE _ENV === 'development' ) {
winston . info ( 'Middlewares loaded.' ) ;
}
}
} ) ;
2014-02-27 14:55:41 -05:00
app . prepareAPI = function ( req , res , next ) {
res . locals . isAPI = true ;
next ( ) ;
} ;
2014-02-27 16:34:58 -05:00
app . authenticate = function ( req , res , next ) {
if ( ! req . user ) {
if ( res . locals . isAPI ) {
return res . json ( 403 , 'not-allowed' ) ;
} else {
return res . redirect ( '403' ) ;
}
} else {
next ( ) ;
}
} ;
2014-02-28 14:04:21 -05:00
app . checkGlobalPrivacySettings = function ( req , res , next ) {
2014-02-28 14:34:35 -05:00
var callerUID = req . user ? parseInt ( req . user . uid , 10 ) : 0 ;
if ( ! callerUID && ! ! parseInt ( meta . config . privateUserInfo , 10 ) ) {
2014-02-28 14:04:21 -05:00
if ( res . locals . isAPI ) {
return res . json ( 403 , 'not-allowed' ) ;
} else {
return res . redirect ( '403' ) ;
}
}
next ( ) ;
} ;
2014-02-28 14:34:35 -05:00
app . checkAccountPermissions = function ( req , res , next ) {
2014-02-28 15:58:29 -05:00
var callerUID = req . user ? parseInt ( req . user . uid , 10 ) : 0 ;
// this function requires userslug to be passed in. todo: /user/uploadpicture should pass in userslug I think
2014-02-28 14:34:35 -05:00
user . getUidByUserslug ( req . params . userslug , function ( err , uid ) {
if ( err ) {
return next ( err ) ;
}
// not sure if this check really should belong here. also make sure we're not doing this check again in the actual method
if ( ! uid ) {
if ( res . locals . isAPI ) {
return res . json ( 404 ) ;
} else {
return res . redirect ( '404' ) ;
}
}
if ( parseInt ( uid , 10 ) === callerUID ) {
return next ( ) ;
}
user . isAdministrator ( callerUID , function ( err , isAdmin ) {
if ( err ) {
return next ( err ) ;
}
if ( isAdmin ) {
next ( ) ;
}
if ( res . locals . isAPI ) {
return res . json ( 403 , 'not-allowed' ) ;
} else {
return res . redirect ( '403' ) ;
}
} ) ;
} ) ;
} ;
2014-02-27 14:55:41 -05:00
app . buildHeader = function ( req , res , next ) {
async . parallel ( [
function ( next ) {
// temp, don't forget to set metaTags and linkTags to res.locals.header
app . build _header ( {
req : req ,
res : res
} , function ( err , template ) {
res . locals . header = template ;
next ( err ) ;
} ) ;
} ,
function ( next ) {
2014-02-28 15:15:00 -05:00
// this is slower than the original implementation because the rendered template is not cached
// but I didn't bother to fix this because we will deprecate [filter:footer.build] in favour of the widgets system by 0.4x
plugins . fireHook ( 'filter:footer.build' , '' , function ( err , appendHTML ) {
app . render ( 'footer' , { footerHTML : appendHTML } , function ( err , template ) {
translator . translate ( template , function ( parsedTemplate ) {
res . locals . footer = template ;
next ( err ) ;
} ) ;
} ) ;
2014-02-27 14:55:41 -05:00
} ) ;
}
] , function ( err ) {
next ( ) ;
} ) ;
} ;
2013-07-24 18:54:14 -04:00
/ * *
* ` options ` object requires : req , res
2013-12-31 20:28:31 -05:00
* accepts : metaTags , linkTags
2013-07-24 18:54:14 -04:00
* /
2013-09-23 12:50:27 -04:00
app . build _header = function ( options , callback ) {
2013-10-12 13:05:47 -04:00
var custom _header = {
'navigation' : [ ]
} ;
2013-07-24 15:19:26 -04:00
2013-10-12 13:05:47 -04:00
plugins . fireHook ( 'filter:header.build' , custom _header , function ( err , custom _header ) {
var defaultMetaTags = [ {
2013-10-22 12:23:07 -04:00
name : 'viewport' ,
content : 'width=device-width, initial-scale=1.0, user-scalable=no'
} , {
name : 'content-type' ,
content : 'text/html; charset=UTF-8'
} , {
name : 'apple-mobile-web-app-capable' ,
content : 'yes'
} , {
property : 'og:site_name' ,
content : meta . config . title || 'NodeBB'
} , {
property : 'keywords' ,
2013-11-11 13:25:54 -05:00
content : meta . config . keywords || ''
2013-10-22 12:23:07 -04:00
} ] ,
2014-02-19 21:47:26 -05:00
defaultLinkTags = [ {
2013-11-26 11:34:38 -05:00
rel : 'apple-touch-icon' ,
2014-02-19 21:47:26 -05:00
href : '/apple-touch-icon'
} ] ,
2013-10-12 13:05:47 -04:00
templateValues = {
2014-01-18 16:13:01 -05:00
bootswatchCSS : meta . config [ 'theme:src' ] ,
2014-01-30 09:20:43 -05:00
pluginCSS : plugins . cssFiles . map ( function ( file ) { return { path : nconf . get ( 'relative_path' ) + file . replace ( /\\/g , '/' ) } ; } ) ,
2013-10-22 12:39:14 -04:00
title : meta . config . title || '' ,
2013-10-29 17:27:23 -04:00
description : meta . config . description || '' ,
2013-10-22 12:39:14 -04:00
'brand:logo' : meta . config [ 'brand:logo' ] || '' ,
'brand:logo:display' : meta . config [ 'brand:logo' ] ? '' : 'hide' ,
2013-10-12 13:05:47 -04:00
csrf : options . res . locals . csrf _token ,
relative _path : nconf . get ( 'relative_path' ) ,
clientScripts : clientScripts ,
2013-12-09 14:50:40 -05:00
navigation : custom _header . navigation ,
2013-12-26 15:58:46 -05:00
'cache-buster' : meta . config [ 'cache-buster' ] ? 'v=' + meta . config [ 'cache-buster' ] : '' ,
2014-02-20 21:21:41 -05:00
allowRegistration : meta . config . allowRegistration === undefined || parseInt ( meta . config . allowRegistration , 10 ) === 1 ,
searchEnabled : plugins . hasListeners ( 'filter:search.query' ) ? true : false
2013-12-31 20:53:24 -05:00
} ,
escapeList = {
'&' : '&' ,
'<' : '<' ,
'>' : '>' ,
"'" : ''' ,
'"' : '"'
2013-10-12 13:05:47 -04:00
} ;
2013-10-28 15:49:12 -04:00
var uid = '0' ;
2013-12-31 20:28:31 -05:00
// Meta Tags
2014-02-27 14:55:41 -05:00
/ * t e m p l a t e V a l u e s . m e t a T a g s = d e f a u l t M e t a T a g s . c o n c a t ( o p t i o n s . m e t a T a g s | | [ ] ) . m a p ( f u n c t i o n ( t a g ) {
2014-02-27 01:51:33 -05:00
if ( ! tag || typeof tag . content !== 'string' ) {
winston . warn ( 'Invalid meta tag. ' , tag ) ;
2014-02-27 01:43:24 -05:00
return tag ;
}
2013-12-31 20:53:24 -05:00
tag . content = tag . content . replace ( /[&<>'"]/g , function ( tag ) {
return escapeList [ tag ] || tag ;
} ) ;
2013-12-31 20:28:31 -05:00
return tag ;
2014-02-27 14:55:41 -05:00
} ) ; * /
2014-01-30 09:20:43 -05:00
// Link Tags
2014-02-27 14:55:41 -05:00
/ * t e m p l a t e V a l u e s . l i n k T a g s = d e f a u l t L i n k T a g s . c o n c a t ( o p t i o n s . l i n k T a g s | | [ ] ) ;
2014-01-30 09:20:43 -05:00
templateValues . linkTags . push ( {
rel : "icon" ,
type : "image/x-icon" ,
2014-02-10 22:24:36 -05:00
href : nconf . get ( 'relative_path' ) + '/favicon.ico'
2014-02-27 14:55:41 -05:00
} ) ; * /
2013-12-31 20:28:31 -05:00
2013-11-11 13:25:54 -05:00
if ( options . req . user && options . req . user . uid ) {
2013-10-28 15:49:12 -04:00
uid = options . req . user . uid ;
2013-11-11 13:25:54 -05:00
}
2013-10-28 15:49:12 -04:00
2014-01-31 13:17:28 -05:00
// Custom CSS
templateValues . useCustomCSS = false ;
if ( meta . config . useCustomCSS === '1' ) {
templateValues . useCustomCSS = true ;
templateValues . customCSS = meta . config . customCSS ;
}
2014-02-19 14:33:34 -05:00
async . parallel ( [
function ( next ) {
translator . get ( 'pages:' + path . basename ( options . req . url ) , function ( translated ) {
2014-02-27 14:55:41 -05:00
/ * v a r m e t a T i t l e = t e m p l a t e V a l u e s . m e t a T a g s . f i l t e r ( f u n c t i o n ( t a g ) {
2014-02-19 14:33:34 -05:00
return tag . name === 'title' ;
} ) ;
if ( translated ) {
templateValues . browserTitle = translated ;
} else if ( metaTitle . length > 0 && metaTitle [ 0 ] . content ) {
templateValues . browserTitle = metaTitle [ 0 ] . content ;
} else {
templateValues . browserTitle = meta . config . browserTitle || 'NodeBB' ;
2014-02-27 14:55:41 -05:00
} * /
2014-02-19 14:33:34 -05:00
next ( ) ;
} ) ;
} ,
function ( next ) {
user . isAdministrator ( uid , function ( err , isAdmin ) {
templateValues . isAdmin = isAdmin || false ;
next ( ) ;
} ) ;
}
] , function ( ) {
2014-02-27 14:55:41 -05:00
/ * t r a n s l a t o r . t r a n s l a t e ( t e m p l a t e s . h e a d e r . p a r s e ( t e m p l a t e V a l u e s ) , f u n c t i o n ( t e m p l a t e ) {
2013-10-28 15:49:12 -04:00
callback ( null , template ) ;
2014-02-27 14:55:41 -05:00
} ) ; * /
app . render ( 'header' , templateValues , function ( err , template ) {
callback ( null , template )
2013-10-28 15:49:12 -04:00
} ) ;
2013-11-11 13:25:54 -05:00
} ) ;
2013-09-24 14:14:26 -04:00
} ) ;
2013-06-20 16:04:58 -04:00
} ;
2013-04-22 16:51:32 +00:00
2013-11-28 17:37:17 -05:00
// Cache static files on production
if ( global . env !== 'development' ) {
app . enable ( 'cache' ) ;
2013-12-04 20:37:13 -05:00
app . enable ( 'minification' ) ;
2013-12-09 14:50:40 -05:00
2013-12-10 22:20:11 -05:00
// 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 ( ) ;
2013-12-26 15:58:46 -05:00
// winston.info('[init] Cache buster value set to: ' + stdOut);
2013-12-10 22:20:11 -05:00
} else {
2014-02-13 12:26:43 -05:00
fs . stat ( path . join ( _ _dirname , '../package.json' ) , function ( err , stats ) {
meta . config [ 'cache-buster' ] = new Date ( stats . mtime ) . getTime ( ) ;
} ) ;
2013-12-09 14:50:40 -05:00
}
2013-12-10 22:20:11 -05:00
} ) ;
}
2013-12-09 14:50:40 -05:00
2014-03-01 17:26:26 -05:00
if ( nconf . get ( 'port' ) != 80 && nconf . get ( 'port' ) != 443 && nconf . get ( 'use_port' ) === false ) {
winston . info ( 'Enabling \'trust proxy\'' ) ;
app . enable ( 'trust proxy' ) ;
}
2013-09-29 20:27:52 -04:00
2014-03-01 17:26:26 -05:00
if ( ( nconf . get ( 'port' ) == 80 || nconf . get ( 'port' ) == 443 ) && process . env . NODE _ENV !== 'development' ) {
winston . info ( 'Using ports 80 and 443 is not recommend; use a proxy instead. See README.md' ) ;
}
2013-09-13 11:10:17 -04:00
2014-03-01 17:26:26 -05:00
module . exports . server = server ;
2013-09-23 12:50:27 -04:00
module . exports . init = function ( ) {
2013-09-24 14:14:26 -04:00
// translate all static templates served by webserver here. ex. footer, logout
2014-01-23 17:08:33 -05:00
plugins . fireHook ( 'action:app.load' , app ) ;
2013-11-22 11:42:42 -05:00
2014-02-27 14:55:41 -05:00
/ * t r a n s l a t o r . t r a n s l a t e ( t e m p l a t e s . l o g o u t . t o S t r i n g ( ) , f u n c t i o n ( p a r s e d T e m p l a t e ) {
2013-11-11 13:25:54 -05:00
templates . logout = parsedTemplate ;
2014-02-27 14:55:41 -05:00
} ) ; * /
2013-09-24 14:14:26 -04:00
2014-02-09 23:07:12 +00:00
server . on ( "error" , function ( e ) {
if ( e . code === 'EADDRINUSE' ) {
winston . error ( 'NodeBB address in use, exiting...' ) ;
process . exit ( 1 ) ;
} else {
throw e ;
}
} ) ;
var port = nconf . get ( 'PORT' ) || nconf . get ( 'port' ) ;
winston . info ( 'NodeBB attempting to listen on: ' + ( ( nconf . get ( 'bind_address' ) === "0.0.0.0" || ! nconf . get ( 'bind_address' ) ) ? '0.0.0.0' : nconf . get ( 'bind_address' ) ) + ':' + port ) ;
server . listen ( port , nconf . get ( 'bind_address' ) , function ( ) {
winston . info ( 'NodeBB Ready' ) ;
} ) ;
2013-11-11 13:25:54 -05:00
} ;
2013-05-17 13:36:35 -04:00
2013-09-23 12:50:27 -04:00
app . create _route = function ( url , tpl ) { // to remove
2014-01-24 10:00:57 -05:00
var routerScript = ' < script > \
2014-01-24 10:09:28 -05:00
ajaxify . initialLoad = true ; \
2014-01-24 10:00:57 -05:00
templates . ready ( function ( ) { ajaxify . go ( "' + url + '" , null , true ) ; } ) ; \
< / s c r i p t > ' ;
return routerScript ;
2013-05-09 06:26:32 +00:00
} ;
2013-04-22 16:51:32 +00:00
} ( WebServer ) ) ;