var path = require ( 'path' ) ,
fs = require ( 'fs' ) ,
nconf = require ( 'nconf' ) ,
express = require ( 'express' ) ,
express _namespace = require ( 'express-namespace' ) ,
WebServer = express ( ) ,
server ,
winston = require ( 'winston' ) ,
validator = require ( 'validator' ) ,
async = require ( 'async' ) ,
utils = require ( '../public/src/utils' ) ,
db = require ( './database' ) ,
user = require ( './user' ) ,
notifications = require ( './notifications' ) ,
auth = require ( './routes/authentication' ) ,
meta = require ( './meta' ) ,
plugins = require ( './plugins' ) ,
logger = require ( './logger' ) ,
templates = require ( './../public/src/templates' ) ,
translator = require ( './../public/src/translator' ) ,
controllers = require ( './controllers' ) ,
admin = require ( './routes/admin' ) ,
apiRoute = require ( './routes/api' ) ,
feedsRoute = require ( './routes/feeds' ) ,
metaRoute = require ( './routes/meta' ) ;
if ( nconf . get ( 'ssl' ) ) {
server = require ( 'https' ) . createServer ( {
key : fs . readFileSync ( nconf . get ( 'ssl' ) . key ) ,
cert : fs . readFileSync ( nconf . get ( 'ssl' ) . cert )
} , WebServer ) ;
} else {
server = require ( 'http' ) . createServer ( WebServer ) ;
}
module . exports . server = server ;
// Signals
var shutdown = function ( code ) {
winston . info ( '[app] Shutdown (SIGTERM/SIGINT) Initialised.' ) ;
db . close ( ) ;
winston . info ( '[app] Database connection closed.' ) ;
winston . info ( '[app] Shutdown complete.' ) ;
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 ( ) ;
} ) ;
( function ( app ) {
"use strict" ;
var clientScripts ;
plugins . ready ( function ( ) {
// Minify client-side libraries
meta . js . get ( function ( err , scripts ) {
clientScripts = scripts . map ( function ( script ) {
script = {
script : script
} ;
return script ;
} ) ;
} ) ;
} ) ;
app . prepareAPI = function ( req , res , next ) {
res . locals . isAPI = true ;
next ( ) ;
} ;
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 ( ) ;
}
} ;
app . checkGlobalPrivacySettings = function ( req , res , next ) {
var callerUID = req . user ? parseInt ( req . user . uid , 10 ) : 0 ;
if ( ! callerUID && ! ! parseInt ( meta . config . privateUserInfo , 10 ) ) {
if ( res . locals . isAPI ) {
return res . json ( 403 , 'not-allowed' ) ;
} else {
return res . redirect ( '403' ) ;
}
}
next ( ) ;
} ;
app . checkAccountPermissions = function ( req , res , next ) {
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
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' ) ;
}
} ) ;
} ) ;
} ;
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 ) {
// 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 ) ;
} ) ;
} ) ;
} ) ;
}
] , function ( err ) {
next ( ) ;
} ) ;
} ;
/ * *
* ` options ` object requires : req , res
* accepts : metaTags , linkTags
* /
app . build _header = function ( options , callback ) {
var custom _header = {
'navigation' : [ ]
} ;
plugins . fireHook ( 'filter:header.build' , custom _header , function ( err , custom _header ) {
var defaultMetaTags = [ {
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' ,
content : meta . config . keywords || ''
} ] ,
defaultLinkTags = [ {
rel : 'apple-touch-icon' ,
href : '/apple-touch-icon'
} ] ,
templateValues = {
bootswatchCSS : meta . config [ 'theme:src' ] ,
pluginCSS : plugins . cssFiles . map ( function ( file ) { return { path : nconf . get ( 'relative_path' ) + file . replace ( /\\/g , '/' ) } ; } ) ,
title : meta . config . title || '' ,
description : meta . config . description || '' ,
'brand:logo' : meta . config [ 'brand:logo' ] || '' ,
'brand:logo:display' : meta . config [ 'brand:logo' ] ? '' : 'hide' ,
csrf : options . res . locals . csrf _token ,
relative _path : nconf . get ( 'relative_path' ) ,
clientScripts : clientScripts ,
navigation : custom _header . navigation ,
'cache-buster' : meta . config [ 'cache-buster' ] ? 'v=' + meta . config [ 'cache-buster' ] : '' ,
allowRegistration : meta . config . allowRegistration === undefined || parseInt ( meta . config . allowRegistration , 10 ) === 1 ,
searchEnabled : plugins . hasListeners ( 'filter:search.query' ) ? true : false
} ,
escapeList = {
'&' : '&' ,
'<' : '<' ,
'>' : '>' ,
"'" : ''' ,
'"' : '"'
} ;
var uid = '0' ;
// Meta Tags
/ * 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 ) {
if ( ! tag || typeof tag . content !== 'string' ) {
winston . warn ( 'Invalid meta tag. ' , tag ) ;
return tag ;
}
tag . content = tag . content . replace ( /[&<>'"]/g , function ( tag ) {
return escapeList [ tag ] || tag ;
} ) ;
return tag ;
} ) ; * /
// Link Tags
/ * 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 | | [ ] ) ;
templateValues . linkTags . push ( {
rel : "icon" ,
type : "image/x-icon" ,
href : nconf . get ( 'relative_path' ) + '/favicon.ico'
} ) ; * /
if ( options . req . user && options . req . user . uid ) {
uid = options . req . user . uid ;
}
// Custom CSS
templateValues . useCustomCSS = false ;
if ( meta . config . useCustomCSS === '1' ) {
templateValues . useCustomCSS = true ;
templateValues . customCSS = meta . config . customCSS ;
}
async . parallel ( [
function ( next ) {
translator . get ( 'pages:' + path . basename ( options . req . url ) , function ( translated ) {
/ * 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 ) {
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' ;
} * /
next ( ) ;
} ) ;
} ,
function ( next ) {
user . isAdministrator ( uid , function ( err , isAdmin ) {
templateValues . isAdmin = isAdmin || false ;
next ( ) ;
} ) ;
}
] , function ( ) {
/ * 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 ) {
callback ( null , template ) ;
} ) ; * /
app . render ( 'header' , templateValues , function ( err , template ) {
callback ( null , template )
} ) ;
} ) ;
} ) ;
} ;
// Cache static files on production
if ( global . env !== 'development' ) {
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 ( ) ;
// winston.info('[init] Cache buster value set to: ' + stdOut);
} else {
fs . stat ( path . join ( _ _dirname , '../package.json' ) , function ( err , stats ) {
meta . config [ 'cache-buster' ] = new Date ( stats . mtime ) . getTime ( ) ;
} ) ;
}
} ) ;
}
// Middlewares
app . configure ( function ( ) {
app . engine ( 'tpl' , templates . _ _express ) ;
app . set ( 'view engine' , 'tpl' ) ;
app . set ( 'views' , path . join ( _ _dirname , '../public/templates' ) ) ;
async . series ( [
function ( next ) {
// Pre-router middlewares
app . use ( express . compress ( ) ) ;
logger . init ( app ) ;
// favicon & apple-touch-icon middleware
app . use ( express . favicon ( path . join ( _ _dirname , '../' , 'public' , meta . config [ 'brand:favicon' ] ? meta . config [ 'brand:favicon' ] : 'favicon.ico' ) ) ) ;
app . use ( '/apple-touch-icon' , function ( req , res ) {
if ( meta . config [ 'brand:logo' ] && validator . isURL ( meta . config [ 'brand:logo' ] ) ) {
return res . redirect ( meta . config [ 'brand:logo' ] ) ;
} else {
return res . sendfile ( path . join ( _ _dirname , '../public' , meta . config [ 'brand:logo' ] || nconf . get ( 'relative_path' ) + '/logo.png' ) , {
maxAge : app . enabled ( 'cache' ) ? 5184000000 : 0
} ) ;
}
} ) ;
app . use ( require ( 'less-middleware' ) ( {
src : path . join ( _ _dirname , '../' , 'public' ) ,
prefix : nconf . get ( 'relative_path' ) ,
yuicompress : app . enabled ( 'minification' ) ? true : false
} ) ) ;
app . use ( express . bodyParser ( ) ) ; // Puts POST vars in request.body
app . use ( express . cookieParser ( ) ) ; // If you want to parse cookies (res.cookies)
app . use ( express . session ( {
store : db . sessionStore ,
secret : nconf . get ( 'secret' ) ,
key : 'express.sid' ,
cookie : {
maxAge : 1000 * 60 * 60 * 24 * parseInt ( meta . configs . loginDays || 14 , 10 )
}
} ) ) ;
app . use ( express . csrf ( ) ) ;
if ( nconf . get ( 'port' ) != 80 && nconf . get ( 'port' ) != 443 && nconf . get ( 'use_port' ) === false ) {
winston . info ( 'Enabling \'trust proxy\'' ) ;
app . enable ( 'trust proxy' ) ;
}
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' ) ;
}
// Local vars, other assorted setup
app . use ( function ( req , res , next ) {
res . locals . csrf _token = req . session . _csrf ;
// Disable framing
res . setHeader ( 'X-Frame-Options' , 'SAMEORIGIN' ) ;
next ( ) ;
} ) ;
app . use ( function ( req , res , next ) {
// res.render post-processing middleware, modified from here: https://gist.github.com/mrlannigan/5051687
var render = res . render ;
res . render = function ( template , options , fn ) {
var self = this ,
options = options || { } ,
req = this . req ,
app = req . app ,
defaultFn = function ( err , str ) {
if ( err ) {
return req . next ( err ) ;
}
self . send ( str ) ;
} ;
if ( 'function' == typeof options ) {
fn = options , options = { } ;
}
if ( 'function' != typeof fn ) {
fn = defaultFn ;
}
render . call ( self , template , options , function ( err , str ) {
if ( res . locals . header ) {
str = res . locals . header + str ;
}
if ( res . locals . footer ) {
str = str + res . locals . footer ;
}
if ( str ) {
translator . translate ( str , function ( translated ) {
fn ( err , translated ) ;
} ) ;
} else {
fn ( err , str ) ;
}
} ) ;
} ;
next ( ) ;
} ) ;
// Authentication Routes
auth . initialize ( app ) ;
next ( ) ;
} ,
function ( next ) {
async . parallel ( [
function ( next ) {
db . getObjectFields ( 'config' , [ 'theme:type' , 'theme:id' , 'theme:staticDir' , 'theme:templates' ] , function ( err , themeData ) {
var themeId = ( themeData [ 'theme:id' ] || 'nodebb-theme-vanilla' ) ;
// Detect if a theme has been selected, and handle appropriately
if ( ! themeData [ 'theme:type' ] || themeData [ 'theme:type' ] === 'local' ) {
// Local theme
if ( process . env . NODE _ENV === 'development' ) {
winston . info ( '[themes] Using theme ' + themeId ) ;
}
// Theme's static directory
if ( themeData [ 'theme:staticDir' ] ) {
app . use ( '/css/assets' , express . static ( path . join ( nconf . get ( 'themes_path' ) , themeData [ 'theme:id' ] , themeData [ 'theme:staticDir' ] ) , {
maxAge : app . enabled ( 'cache' ) ? 5184000000 : 0
} ) ) ;
if ( process . env . NODE _ENV === 'development' ) {
winston . info ( 'Static directory routed for theme: ' + themeData [ 'theme:id' ] ) ;
}
}
if ( themeData [ 'theme:templates' ] ) {
app . use ( '/templates' , express . static ( path . join ( nconf . get ( 'themes_path' ) , themeData [ 'theme:id' ] , themeData [ 'theme:templates' ] ) , {
maxAge : app . enabled ( 'cache' ) ? 5184000000 : 0
} ) ) ;
if ( process . env . NODE _ENV === 'development' ) {
winston . info ( 'Custom templates directory routed for theme: ' + themeData [ 'theme:id' ] ) ;
}
}
next ( ) ;
} else {
// If not using a local theme (bootswatch, etc), drop back to vanilla
if ( process . env . NODE _ENV === 'development' ) {
winston . info ( '[themes] Using theme ' + themeId ) ;
}
app . use ( require ( 'less-middleware' ) ( {
src : path . join ( nconf . get ( 'themes_path' ) , '/nodebb-theme-vanilla' ) ,
dest : path . join ( _ _dirname , '../public/css' ) ,
prefix : nconf . get ( 'relative_path' ) + '/css' ,
yuicompress : app . enabled ( 'minification' ) ? true : false
} ) ) ;
next ( ) ;
}
} ) ;
// Route paths to screenshots for installed themes
meta . themes . get ( function ( err , themes ) {
var screenshotPath ;
async . each ( themes , function ( themeObj , next ) {
if ( themeObj . screenshot ) {
screenshotPath = path . join ( nconf . get ( 'themes_path' ) , themeObj . id , themeObj . screenshot ) ;
( function ( id , path ) {
fs . exists ( path , function ( exists ) {
if ( exists ) {
app . get ( '/css/previews/' + id , function ( req , res ) {
res . sendfile ( path ) ;
} ) ;
}
} ) ;
} ) ( themeObj . id , screenshotPath ) ;
} else {
next ( false ) ;
}
} ) ;
} ) ;
}
] , next ) ;
} ,
function ( next ) {
// Router & post-router middlewares
app . use ( app . router ) ;
// Static directory /public
app . use ( nconf . get ( 'relative_path' ) , express . static ( path . join ( _ _dirname , '../' , 'public' ) , {
maxAge : app . enabled ( 'cache' ) ? 5184000000 : 0
} ) ) ;
// 404 catch-all
app . use ( function ( req , res , next ) {
var isLanguage = new RegExp ( '^' + nconf . get ( 'relative_path' ) + '/language/[\\w]{2,}/.*.json' ) ,
isClientScript = new RegExp ( '^' + nconf . get ( 'relative_path' ) + '\\/src\\/forum(\\/admin)?\\/[\\w]+\\.js' ) ;
res . status ( 404 ) ;
if ( isClientScript . test ( req . url ) ) {
// Handle missing client-side scripts
res . type ( 'text/javascript' ) . send ( 200 , '' ) ;
} else if ( isLanguage . test ( req . url ) ) {
// Handle languages by sending an empty object
res . json ( 200 , { } ) ;
} else if ( req . accepts ( 'html' ) ) {
// respond with html page
if ( process . env . NODE _ENV === 'development' ) {
winston . warn ( 'Route requested but not found: ' + req . url ) ;
}
res . redirect ( nconf . get ( 'relative_path' ) + '/404' ) ;
} else if ( req . accepts ( 'json' ) ) {
// respond with json
if ( process . env . NODE _ENV === 'development' ) {
winston . warn ( 'Route requested but not found: ' + req . url ) ;
}
res . json ( {
error : 'Not found'
} ) ;
} else {
// default to plain-text. send()
res . type ( 'txt' ) . send ( 'Not found' ) ;
}
} ) ;
app . use ( function ( err , req , res , next ) {
// we may use properties of the error object
// here and next(err) appropriately, or if
// we possibly recovered from the error, simply next().
console . error ( err . stack ) ;
var status = err . status || 500 ;
res . status ( status ) ;
res . json ( status , {
error : err . message
} ) ;
} ) ;
next ( ) ;
}
] , function ( err ) {
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.' ) ;
}
}
} ) ;
} ) ;
module . exports . init = function ( ) {
// translate all static templates served by webserver here. ex. footer, logout
plugins . fireHook ( 'action:app.load' , app ) ;
/ * 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 ) {
templates . logout = parsedTemplate ;
} ) ; * /
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' ) ;
} ) ;
} ;
app . create _route = function ( url , tpl ) { // to remove
var routerScript = ' < script > \
ajaxify . initialLoad = true ; \
templates . ready ( function ( ) { ajaxify . go ( "' + url + '" , null , true ) ; } ) ; \
< / s c r i p t > ' ;
return routerScript ;
} ;
app . namespace ( nconf . get ( 'relative_path' ) , function ( ) {
auth . registerApp ( app ) ;
metaRoute . createRoutes ( app ) ;
admin . createRoutes ( app ) ;
apiRoute . createRoutes ( app ) ;
feedsRoute . createRoutes ( app ) ;
// Basic Routes (entirely client-side parsed, goal is to move the rest of the crap in this file into this one section)
( function ( ) {
var routes = [ ] ,
loginRequired = [ 'notifications' ] ;
async . each ( routes . concat ( loginRequired ) , function ( route , next ) {
app . get ( '/' + route , function ( req , res ) {
if ( loginRequired . indexOf ( route ) !== - 1 && ! req . user ) {
return res . redirect ( '/403' ) ;
}
app . build _header ( {
req : req ,
res : res
} , function ( err , header ) {
res . send ( ( isNaN ( parseInt ( route , 10 ) ) ? 200 : parseInt ( route , 10 ) ) , header + app . create _route ( route ) + templates . footer ) ;
} ) ;
} ) ;
} ) ;
} ( ) ) ;
/* Main */
app . get ( '/' , app . buildHeader , controllers . home ) ;
app . get ( '/api/home' , app . prepareAPI , controllers . home ) ;
app . get ( '/login' , app . buildHeader , controllers . login ) ;
app . get ( '/api/login' , app . prepareAPI , controllers . login ) ;
app . get ( '/register' , app . buildHeader , controllers . register ) ;
app . get ( '/api/register' , app . prepareAPI , controllers . register ) ;
app . get ( '/confirm/:code' , app . buildHeader , controllers . confirmEmail ) ;
app . get ( '/api/confirm/:code' , app . prepareAPI , controllers . confirmEmail ) ;
app . get ( '/sitemap.xml' , controllers . sitemap ) ;
app . get ( '/robots.txt' , controllers . robots ) ;
app . get ( '/outgoing' , app . buildHeader , controllers . outgoing ) ;
app . get ( '/api/outgoing' , app . prepareAPI , controllers . outgoing ) ;
/* Static Pages */
app . get ( '/404' , app . buildHeader , controllers . static [ '404' ] ) ;
app . get ( '/api/404' , app . prepareAPI , controllers . static [ '404' ] ) ;
app . get ( '/403' , app . buildHeader , controllers . static [ '403' ] ) ;
app . get ( '/api/403' , app . prepareAPI , controllers . static [ '403' ] ) ;
app . get ( '/500' , app . buildHeader , controllers . static [ '500' ] ) ;
app . get ( '/api/500' , app . prepareAPI , controllers . static [ '500' ] ) ;
/* Topics */
app . get ( '/topic/:topic_id/:slug?' , app . buildHeader , controllers . topics . get ) ;
app . get ( '/api/topic/:topic_id/:slug?' , app . prepareAPI , controllers . topics . get ) ;
/* Categories */
app . get ( '/popular/:set?' , app . buildHeader , controllers . categories . popular ) ;
app . get ( '/api/popular/:set?' , app . prepareAPI , controllers . categories . popular ) ;
app . get ( '/recent/:term?' , app . buildHeader , controllers . categories . recent ) ;
app . get ( '/api/recent/:term?' , app . prepareAPI , controllers . categories . recent ) ;
app . get ( '/unread/' , app . buildHeader , app . authenticate , controllers . categories . unread ) ;
app . get ( '/api/unread/' , app . prepareAPI , app . authenticate , controllers . categories . unread ) ;
app . get ( '/unread/total' , app . buildHeader , app . authenticate , controllers . categories . unreadTotal ) ;
app . get ( '/api/unread/total' , app . prepareAPI , app . authenticate , controllers . categories . unreadTotal ) ;
app . get ( '/category/:category_id/:slug?' , app . buildHeader , controllers . categories . get ) ;
app . get ( '/api/category/:category_id/:slug?' , app . prepareAPI , controllers . categories . get ) ;
/* Accounts */
app . get ( '/user/:userslug' , app . buildHeader , app . checkGlobalPrivacySettings , controllers . accounts . getAccount ) ;
app . get ( '/api/user/:userslug' , app . prepareAPI , app . checkGlobalPrivacySettings , controllers . accounts . getAccount ) ;
app . get ( '/user/:userslug/following' , app . buildHeader , app . checkGlobalPrivacySettings , controllers . accounts . getFollowing ) ;
app . get ( '/api/user/:userslug/following' , app . prepareAPI , app . checkGlobalPrivacySettings , controllers . accounts . getFollowing ) ;
app . get ( '/user/:userslug/followers' , app . buildHeader , app . checkGlobalPrivacySettings , controllers . accounts . getFollowers ) ;
app . get ( '/api/user/:userslug/followers' , app . prepareAPI , app . checkGlobalPrivacySettings , controllers . accounts . getFollowers ) ;
app . get ( '/user/:userslug/favourites' , app . buildHeader , app . checkGlobalPrivacySettings , app . checkAccountPermissions , controllers . accounts . getFavourites ) ;
app . get ( '/api/user/:userslug/favourites' , app . prepareAPI , app . checkGlobalPrivacySettings , app . checkAccountPermissions , controllers . accounts . getFavourites ) ;
app . get ( '/user/:userslug/posts' , app . buildHeader , app . checkGlobalPrivacySettings , controllers . accounts . getPosts ) ;
app . get ( '/api/user/:userslug/posts' , app . prepareAPI , app . checkGlobalPrivacySettings , controllers . accounts . getPosts ) ;
app . get ( '/user/:userslug/edit' , app . buildHeader , app . checkGlobalPrivacySettings , app . checkAccountPermissions , controllers . accounts . accountEdit ) ;
app . get ( '/api/user/:userslug/edit' , app . prepareAPI , app . checkGlobalPrivacySettings , app . checkAccountPermissions , controllers . accounts . accountEdit ) ;
// todo: admin recently gained access to this page, pls check if it actually works
app . get ( '/user/:userslug/settings' , app . buildHeader , app . checkGlobalPrivacySettings , app . checkAccountPermissions , controllers . accounts . accountSettings ) ;
app . get ( '/api/user/:userslug/settings' , app . prepareAPI , app . checkGlobalPrivacySettings , app . checkAccountPermissions , controllers . accounts . accountSettings ) ;
app . get ( '/api/user/uid/:uid' , app . checkGlobalPrivacySettings , controllers . accounts . getUserByUID ) ;
// this should have been in the API namespace
// also, perhaps pass in :userslug so we can use checkAccountPermissions middleware, in future will allow admins to upload a picture for a user
app . post ( '/user/uploadpicture' , app . prepareAPI , app . checkGlobalPrivacySettings , /*app.checkAccountPermissions,*/ controllers . accounts . uploadPicture ) ;
/* Users */
app . get ( '/users' , app . buildHeader , app . checkGlobalPrivacySettings , controllers . users . getOnlineUsers ) ;
app . get ( '/api/users' , app . prepareAPI , app . checkGlobalPrivacySettings , controllers . users . getOnlineUsers ) ;
// was this duped by accident or purpose?
app . get ( '/users/online' , app . buildHeader , app . checkGlobalPrivacySettings , controllers . users . getOnlineUsers ) ;
app . get ( '/api/users/online' , app . prepareAPI , app . checkGlobalPrivacySettings , controllers . users . getOnlineUsers ) ;
app . get ( '/users/sort-posts' , app . buildHeader , app . checkGlobalPrivacySettings , controllers . users . getUsersSortedByPosts ) ;
app . get ( '/api/users/sort-posts' , app . prepareAPI , app . checkGlobalPrivacySettings , controllers . users . getUsersSortedByPosts ) ;
app . get ( '/users/sort-reputation' , app . buildHeader , app . checkGlobalPrivacySettings , controllers . users . getUsersSortedByReputation ) ;
app . get ( '/api/users/sort-reputation' , app . prepareAPI , app . checkGlobalPrivacySettings , controllers . users . getUsersSortedByReputation ) ;
app . get ( '/users/latest' , app . buildHeader , app . checkGlobalPrivacySettings , controllers . users . getUsersSortedByJoinDate ) ;
app . get ( '/api/users/latest' , app . prepareAPI , app . checkGlobalPrivacySettings , controllers . users . getUsersSortedByJoinDate ) ;
app . get ( '/users/search' , app . buildHeader , app . checkGlobalPrivacySettings , controllers . users . getUsersForSearch ) ;
app . get ( '/api/users/search' , app . prepareAPI , app . checkGlobalPrivacySettings , controllers . users . getUsersForSearch ) ;
app . get ( '/search/:term?' , function ( req , res ) {
if ( ! req . user && meta . config . allowGuestSearching !== '1' ) {
return res . redirect ( '/403' ) ;
}
if ( ! req . params . term ) {
req . params . term = '' ;
}
app . build _header ( {
req : req ,
res : res
} , function ( err , header ) {
res . send ( header + app . create _route ( 'search/' + req . params . term , null , 'search' ) + templates . footer ) ;
} ) ;
} ) ;
// Other routes
require ( './routes/plugins' ) ( app ) ;
// Debug routes
if ( process . env . NODE _ENV === 'development' ) {
require ( './routes/debug' ) ( app ) ;
}
var custom _routes = {
'routes' : [ ] ,
'api' : [ ] ,
'templates' : [ ]
} ;
app . get _custom _templates = function ( ) {
return custom _routes . templates . map ( function ( tpl ) {
return tpl . template . split ( '.tpl' ) [ 0 ] ;
} ) ;
} ;
plugins . ready ( function ( ) {
plugins . fireHook ( 'filter:server.create_routes' , custom _routes , function ( err , custom _routes ) {
var routes = custom _routes . routes ;
for ( var route in routes ) {
if ( routes . hasOwnProperty ( route ) ) {
( function ( route ) {
app [ routes [ route ] . method || 'get' ] ( routes [ route ] . route , function ( req , res ) {
routes [ route ] . options ( req , res , function ( options ) {
app . build _header ( {
req : options . req || req ,
res : options . res || res
} , function ( err , header ) {
res . send ( header + options . content + templates . footer ) ;
} ) ;
} ) ;
} ) ;
} ( route ) ) ;
}
}
var apiRoutes = custom _routes . api ;
for ( var route in apiRoutes ) {
if ( apiRoutes . hasOwnProperty ( route ) ) {
( function ( route ) {
app [ apiRoutes [ route ] . method || 'get' ] ( '/api' + apiRoutes [ route ] . route , function ( req , res ) {
apiRoutes [ route ] . callback ( req , res , function ( data ) {
res . json ( data ) ;
} ) ;
} ) ;
} ( route ) ) ;
}
}
var templateRoutes = custom _routes . templates ;
for ( var route in templateRoutes ) {
if ( templateRoutes . hasOwnProperty ( route ) ) {
( function ( route ) {
app . get ( '/templates/' + templateRoutes [ route ] . template , function ( req , res ) {
res . send ( templateRoutes [ route ] . content ) ;
} ) ;
} ( route ) ) ;
}
}
} ) ;
} ) ;
} ) ;
} ( WebServer ) ) ;