2014-01-27 15:36:35 -08:00
express = require ' express '
path = require ' path '
2014-02-04 23:08:20 +01:00
authentication = require ' passport '
2014-01-27 15:36:35 -08:00
useragent = require ' express-useragent '
fs = require ' graceful-fs '
2014-04-17 10:12:23 -07:00
log = require ' winston '
compressible = require ' compressible '
2015-03-19 15:25:24 -04:00
geoip = require ' geoip-lite '
2014-01-27 15:36:35 -08:00
2014-02-04 21:30:05 +01:00
database = require ' ./server/commons/database '
2014-02-04 22:29:13 +01:00
baseRoute = require ' ./server/routes/base '
2014-01-27 15:36:35 -08:00
user = require ' ./server/users/user_handler '
logging = require ' ./server/commons/logging '
config = require ' ./server_config '
2014-04-02 13:12:24 -07:00
auth = require ' ./server/routes/auth '
2014-04-17 10:12:23 -07:00
UserHandler = require ' ./server/users/user_handler '
2014-11-24 20:07:29 -05:00
hipchat = require ' ./server/hipchat '
2014-08-29 12:41:25 -07:00
global.tv4 = require ' tv4 ' # required for TreemaUtils to work
2014-10-27 17:11:48 -07:00
global.jsondiffpatch = require ' jsondiffpatch '
2014-12-05 16:47:44 -08:00
global.stripe = require ( ' stripe ' ) ( config . stripe . secretKey )
2014-01-27 15:36:35 -08:00
2014-03-15 23:08:22 +09:00
productionLogging = (tokens, req, res) ->
2014-03-02 01:48:21 +05:30
status = res . statusCode
2014-03-03 08:10:36 -08:00
color = 32
if status >= 500 then color = 31
else if status >= 400 then color = 33
else if status >= 300 then color = 36
elapsed = ( new Date ( ) ) - req . _startTime
elapsedColor = if elapsed < 500 then 90 else 31
2014-11-25 09:28:42 -08:00
if ( status isnt 200 and status isnt 201 and status isnt 204 and status isnt 304 and status isnt 302 ) or elapsed > 500
2014-03-03 08:10:36 -08:00
return " \x 1b[90m #{ req . method } #{ req . originalUrl } \x 1b[ #{ color } m #{ res . statusCode } \x 1b[ #{ elapsedColor } m #{ elapsed } ms \x 1b[0m "
null
2014-03-02 01:48:21 +05:30
2014-10-26 10:52:22 -07:00
developmentLogging = (tokens, req, res) ->
status = res . statusCode
color = 32
if status >= 500 then color = 31
else if status >= 400 then color = 33
else if status >= 300 then color = 36
elapsed = ( new Date ( ) ) - req . _startTime
elapsedColor = if elapsed < 500 then 90 else 31
" \x 1b[90m #{ req . method } #{ req . originalUrl } \x 1b[ #{ color } m #{ res . statusCode } \x 1b[ #{ elapsedColor } m #{ elapsed } ms \x 1b[0m "
2014-11-24 20:07:29 -05:00
setupErrorMiddleware = (app) ->
app . use (err, req, res, next) ->
if err
2014-11-29 11:05:19 -08:00
if err . status and 400 <= err . status < 500
res . status ( err . status ) . send ( " Error #{ err . status } " )
return
res . status ( err . status ? 500 ) . send ( error: " Something went wrong! " )
2014-11-29 08:43:40 -08:00
message = " Express error: #{ req . method } #{ req . path } : #{ err . message } "
2014-11-28 14:37:54 -05:00
log . error " #{ message } , stack: #{ err . stack } "
2015-02-11 20:24:12 -08:00
hipchat . sendHipChatMessage ( message , [ ' tower ' ] , { papertrail: true } )
2014-11-24 20:07:29 -05:00
else
next ( err )
2015-02-26 17:20:27 -08:00
2014-01-27 15:36:35 -08:00
setupExpressMiddleware = (app) ->
2014-03-03 08:10:36 -08:00
if config . isProduction
express . logger . format ( ' prod ' , productionLogging )
app . use ( express . logger ( ' prod ' ) )
2014-04-17 10:12:23 -07:00
app . use express . compress filter: (req, res) ->
2014-12-18 20:34:59 -08:00
return false if req . headers . host is ' codecombat.com ' # CloudFlare will gzip it for us on codecombat.com
2014-04-17 10:12:23 -07:00
compressible res . getHeader ( ' Content-Type ' )
2014-03-03 08:10:36 -08:00
else
2014-10-26 10:52:22 -07:00
express . logger . format ( ' dev ' , developmentLogging )
2014-03-03 08:10:36 -08:00
app . use ( express . logger ( ' dev ' ) )
2014-12-20 13:39:40 -08:00
app . use ( express . static ( path . join ( __dirname , ' public ' ) , maxAge: 0 ) ) # CloudFlare overrides maxAge, and we don't want local development caching.
2014-01-27 15:36:35 -08:00
app . use ( useragent . express ( ) )
app . use ( express . favicon ( ) )
app . use ( express . cookieParser ( config . cookie_secret ) )
app . use ( express . bodyParser ( ) )
app . use ( express . methodOverride ( ) )
app . use ( express . cookieSession ( { secret : ' defenestrate ' } ) )
setupPassportMiddleware = (app) ->
2014-02-04 23:08:20 +01:00
app . use ( authentication . initialize ( ) )
app . use ( authentication . session ( ) )
2014-01-27 15:36:35 -08:00
2015-03-19 15:25:24 -04:00
setupChinaRedirectMiddleware = (app) ->
2015-03-23 15:26:44 -07:00
shouldRedirectToChinaVersion = (req) ->
speaksChinese = req . acceptedLanguages [ 0 ] ? . indexOf ( ' zh ' ) isnt - 1
2015-03-19 15:25:24 -04:00
unless config . tokyo
2015-03-23 15:26:44 -07:00
ip = req . headers [ ' x-forwarded-for ' ] or req . connection . remoteAddress
2015-03-19 15:25:24 -04:00
geo = geoip . lookup ( ip )
2015-03-23 15:26:44 -07:00
return geo ? . country is " CN " and speaksChinese
2015-03-19 15:25:24 -04:00
else
2015-03-23 17:00:11 -07:00
req.chinaVersion = true if speaksChinese
2015-03-23 15:26:44 -07:00
return false # If the user is already redirected, don't redirect them!
2015-03-19 15:25:24 -04:00
app . use (req, res, next) ->
2015-03-23 15:26:44 -07:00
if shouldRedirectToChinaVersion req
2015-03-19 15:25:24 -04:00
res . writeHead 302 , " Location " : config . chinaDomain + req . url
res . end ( )
else
next ( )
2014-06-09 00:35:59 +08:00
setupOneSecondDelayMiddleware = (app) ->
2014-01-27 15:36:35 -08:00
if ( config . slow_down )
app . use ( (req, res, next) -> setTimeout ( ( -> next ( ) ) , 1000 ) )
setupMiddlewareToSendOldBrowserWarningWhenPlayersViewLevelDirectly = (app) ->
isOldBrowser = (req) ->
# https://github.com/biggora/express-useragent/blob/master/lib/express-useragent.js
return false unless ua = req . useragent
return true if ua . isiPad or ua . isiPod or ua . isiPhone or ua . isOpera
2014-07-01 10:16:26 +08:00
return false unless ua and ua . Browser in [ ' Chrome ' , ' Safari ' , ' Firefox ' , ' IE ' ] and ua . Version
2014-01-27 15:36:35 -08:00
b = ua . Browser
v = parseInt ua . Version . split ( ' . ' ) [ 0 ] , 10
return true if b is ' Chrome ' and v < 17
return true if b is ' Safari ' and v < 6
return true if b is ' Firefox ' and v < 21
return true if b is ' IE ' and v < 10
false
app . use ' /play/ ' , (req, res, next) ->
return next ( ) if req . query [ ' try-old-browser-anyway ' ] or not isOldBrowser req
res . sendfile ( path . join ( __dirname , ' public ' , ' index_old_browser.html ' ) )
2014-07-13 22:34:32 +02:00
setupRedirectMiddleware = (app) ->
app . all ' /account/profile/* ' , (req, res, next) ->
nameOrID = req . path . split ( ' / ' ) [ 3 ]
res . redirect 301 , " /user/ #{ nameOrID } /profile "
2014-07-15 16:15:21 +02:00
2014-07-13 20:49:13 +02:00
setupTrailingSlashRemovingMiddleware = (app) ->
app . use (req, res, next) ->
2014-08-26 15:42:33 -07:00
# Remove trailing slashes except for in /file/.../ URLs, because those are treated as directory listings.
return res . redirect 301 , req . url [ . . . - 1 ] if req . url . length > 1 and req . url . slice ( - 1 ) is ' / ' and not /\/file\// . test req . url
2014-07-13 20:49:13 +02:00
next ( )
2014-07-13 22:34:32 +02:00
2014-01-27 15:36:35 -08:00
exports.setupMiddleware = (app) ->
2015-03-21 21:49:32 -04:00
setupChinaRedirectMiddleware app
2014-01-27 15:36:35 -08:00
setupMiddlewareToSendOldBrowserWarningWhenPlayersViewLevelDirectly app
setupExpressMiddleware app
setupPassportMiddleware app
2014-06-09 00:35:59 +08:00
setupOneSecondDelayMiddleware app
2014-07-13 20:49:13 +02:00
setupTrailingSlashRemovingMiddleware app
2014-07-13 22:34:32 +02:00
setupRedirectMiddleware app
2014-11-24 20:07:29 -05:00
setupErrorMiddleware app
2014-11-28 16:38:50 -08:00
setupJavascript404s app
2014-11-29 08:43:40 -08:00
2014-01-27 15:36:35 -08:00
###Routing function implementations###
2014-11-28 16:38:50 -08:00
setupJavascript404s = (app) ->
app . get ' /javascripts/* ' , (req, res) ->
res . status ( 404 ) . send ( ' Not found ' )
2014-01-27 15:36:35 -08:00
setupFallbackRouteToIndex = (app) ->
2014-03-12 10:07:18 -07:00
app . all ' * ' , (req, res) ->
2015-01-05 14:43:20 -08:00
fs . readFile path . join ( __dirname , ' public ' , ' main.html ' ) , ' utf8 ' , (err, data) ->
log . error " Error modifying main.html: #{ err } " if err
# insert the user object directly into the html so the application can have it immediately. Sanitize </script>
user = if req . user then JSON . stringify ( UserHandler . formatEntity ( req , req . user ) ) . replace ( /\//g , ' \\ / ' ) else ' {} '
data = data . replace ( ' " userObjectTag " ' , user )
res . header ' Cache-Control ' , ' no-cache, no-store, must-revalidate '
res . header ' Pragma ' , ' no-cache '
res . header ' Expires ' , 0
res . send 200 , data
2014-01-27 15:36:35 -08:00
setupFacebookCrossDomainCommunicationRoute = (app) ->
app . get ' /channel.html ' , (req, res) ->
res . sendfile path . join ( __dirname , ' public ' , ' channel.html ' )
exports.setupRoutes = (app) ->
app . use app . router
2014-02-05 09:42:25 -08:00
2014-02-04 22:29:13 +01:00
baseRoute . setup app
2014-01-27 15:36:35 -08:00
setupFacebookCrossDomainCommunicationRoute app
setupFallbackRouteToIndex app
###Miscellaneous configuration functions###
exports.setupLogging = ->
logging . setup ( )
exports.connectToDatabase = ->
2014-02-04 21:30:05 +01:00
database . connect ( )
2014-01-27 15:36:35 -08:00
exports.setupMailchimp = ->
mcapi = require ' mailchimp-api '
mc = new mcapi . Mailchimp ( config . mail . mailchimpAPIKey )
GLOBAL.mc = mc
exports.setExpressConfigurationOptions = (app) ->
app . set ( ' port ' , config . port )
app . set ( ' views ' , __dirname + ' /app/views ' )
app . set ( ' view engine ' , ' jade ' )
app . set ( ' view options ' , { layout: false } )
2014-03-03 16:19:35 -08:00
app . set ( ' env ' , if config . isProduction then ' production ' else ' development ' )
2014-10-29 14:17:07 -07:00
app . set ( ' json spaces ' , 0 ) if config . isProduction