UNPKG

8.51 kBJavaScriptView Raw
1/*
2ExpressJS for volebo.net
3
4Copyright (C) 2016-2017 Volebo <dev@volebo.net>
5Copyright (C) 2016-2017 Koryukov Maksim <maxkoryukov@gmail.com>
6
7This program is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or
10(at your option) any later version.
11
12This program is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with this program. If not, see <http://www.gnu.org/licenses/>.
19*/
20
21"use strict";
22
23const path = require('path');
24
25const debug = require('debug')('volebo:express:server');
26const express = require('express');
27// #2
28//const logger = require('express-bunyan-logger');
29const logger = require('morgan');
30
31const bodyParser = require('body-parser');
32const handlebars = require('express-handlebars');
33
34const session = require('express-session');
35const langGen = require('express-mw-lang');
36const _ = require('lodash');
37
38const moment = require('moment');
39const passport = require('passport');
40
41const helmet = require('helmet')
42
43// Loading polyfill for intl
44// required, as soon we want to use several locales (not only EN)
45if (global.Intl) {
46 const IntlPolyfill = require('intl');
47 Intl.NumberFormat = IntlPolyfill.NumberFormat;
48 Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
49} else {
50 global.Intl = require('intl');
51}
52
53// TODO : #17 replace with custom set of handlers!
54// BUG: #17
55const handlebarsIntl = require('handlebars-intl');
56
57const [nodeEnv, isProduction] = require('./getnodeenv')();
58debug('NODE_ENV', nodeEnv);
59
60const Config = require('./config');
61
62debug('initializing');
63
64
65let main = function main(serverConfig) {
66 let app = express();
67
68 // securing with HTTP-headers
69 app.use(helmet());
70 app.set('env', nodeEnv);
71
72 /*
73 ========================================================
74 CONFIG
75
76 Custom config, allows to put dirty crutches
77 without breaking core settings of the express
78 ========================================================
79 */
80 app.config = new Config(serverConfig);
81
82 // TODO : fix #2 determine what to do with winston-logger
83 // app.use(logger('dev'));
84 app.use(logger('common'));
85
86 app.use(bodyParser.json());
87 app.use(bodyParser.urlencoded({ extended: false }));
88
89 app.set('trust proxy', app.config.proxy.list);
90
91 /*
92 ========================================================
93 STATIC
94 ========================================================
95 */
96
97 if (_.get(app.config, 'debug.staticPath') && !isProduction) {
98 //let staticPath = path.join(__dirname, app.config.debug.staticPath);
99 let staticPath = app.config.debug.staticPath;
100
101 debug('Use static path', staticPath);
102 app.use(express.static(staticPath));
103 }
104
105
106 /*
107 ========================================================
108 JSON
109 ========================================================
110 */
111 if (app.config.debug && !isProduction) {
112 app.set('json spaces', 4);
113 }
114
115 /*
116 ========================================================
117 HTTP->HTTPS redirect (working behind NGINX)
118 ========================================================
119 */
120 app.use((req, _unused_res, next) => {
121 req.forwardedSecure = _.lowerCase(req.headers["x-forwarded-proto"]) === "https";
122 next();
123 });
124
125 /*
126 ========================================================
127 SESSIONS
128 ========================================================
129 */
130
131 if (app.config.session && app.config.session.enabled) {
132 let session_config = {
133 name: app.config.session.name || 'sessionId',
134 secret: app.config.session.secret,
135 resave: false,
136 saveUninitialized: false,
137
138 // DOC: https://www.npmjs.com/package/express-session#cookie
139 cookie: {
140 //expires
141 maxAge: 1000*60*30, // ms, 30 minutes now
142 httpOnly: true,
143
144 secure : app.config.session.secure,
145 },
146 /*genid: function(req) { return genuuid() // use UUIDs for session IDs },*/
147 };
148
149 if (app.config.session.domain.length > 0) {
150 session_config.cookie.domain = app.config.session.domain.join(',');
151 }
152
153 app.use(session(session_config));
154 }
155
156 /*
157 ========================================================
158 PASSPORT
159 ========================================================
160 */
161
162 if (app.config.auth.enabled) {
163 app.use(passport.initialize());
164 if (app.config.auth.session){
165 app.use(passport.session());
166 }
167
168 passport.serializeUser(function(user, done) {
169 debug('serializing', user);
170 done(null, user);
171 });
172
173 passport.deserializeUser(function(obj, done) {
174 debug('deserializing', obj);
175 done(null, obj);
176 });
177 }
178
179 /*
180 ========================================================
181 Handler for lang detection and other stuff..
182 ========================================================
183 */
184
185 let langmw = langGen({
186 defaultLanguage: 'en',
187 availableLanguages: ['en', 'ru', 'zh'],
188 onLangCodeReady: function(lang_code, _unused_req, res) {
189
190 moment.locale(lang_code);
191
192 // REF #6: We could use `defaultOptions`, attached to the
193 // `res` object. This approach could make the code
194 // more readable.
195 // See also: _renderTemplate hook
196 // https://github.com/ericf/express-handlebars#_rendertemplatetemplate-context-options
197
198 if (!res._renderOriginal) {
199 res._renderOriginal = res.render;
200 }
201 res.render = function overloadedRender(){
202
203 let nargs = Array.prototype.slice.call(arguments);
204 let opt = nargs[1];
205
206 if (_.isNil(opt)) {
207 opt = {};
208 }
209
210 if (res.helpers) {
211 debug('Going to append per-request HBS helpers');
212
213 // check, whether some helpers are already attached:
214 if(opt.helpers) {
215 _.assign(opt.helpers, res.helpers);
216 } else {
217 _.set(opt, 'helpers', res.helpers);
218 }
219 }
220
221 // set locale for HBS-intl formatter (format dates, numbers and messages)
222 _.set(opt, 'data.intl.locales', lang_code);
223
224 nargs[1] = opt;
225 return res._renderOriginal.apply(this, nargs);
226 }
227 }
228 });
229 app.lang = langmw;
230 langmw.esu(app);
231
232 /*
233 ========================================================
234 VIEW ENGINE SETUP
235 ========================================================
236 */
237 let hbs = handlebars.create({
238 layoutsDir: path.join(__dirname, '..', 'views', 'layouts'),
239 partialsDir: path.join(__dirname, '..', 'views', 'partials'), // TODO : #13 use NAMESPACES
240 defaultLayout: 'default',
241 helpers: require('./helpers').helpers, // TODO : #17 remove this: require('./views/helpers'),
242 extname: '.hbs'
243 });
244 app.hbs = hbs;
245
246 handlebarsIntl.registerWith(hbs.handlebars);
247
248 app.engine('hbs', hbs.engine);
249 app.set('view engine', 'hbs');
250
251 /*
252 ========================================================
253 AUTOLOAD MODEL
254 ========================================================
255 */
256 if (app.config.model.enabled) {
257 // require here.
258 // add VOLEBO-DATA dependency only when it is required
259 const Model = require('volebo-data');
260 app.model = new Model(app.config.model);
261 }
262
263 app._onStarting = function() {
264 /*
265 ====================================
266 NOT FOUND HANDLER
267 catch 404 and forward to error handler
268 ====================================
269 */
270 app.use(function(_unused_req, _unused_res, next) {
271 // TODO : pass required info to the error, such as URL, params...
272 let err = new Error('Not Found');
273 err.status = 404;
274
275 next(err);
276 });
277
278 /*
279 ====================================
280 ERROR HANDLERS
281 TODO: fix #2 - robust error handler
282 ====================================
283 */
284
285 let error_view_path = path.join(__dirname, '..', 'views', 'error.hbs');
286
287 if (app.config.debug && app.config.debug.renderStack && !isProduction) {
288
289 // development error handler
290 // will print stacktrace
291 app.use(function global_error_dev(err, _unused_req, res, next) {
292 res.status(err.status || 500);
293 return res.render(error_view_path, {
294 message: err.message,
295 error: err,
296 status: err.status
297 });
298 });
299 } else {
300
301 // production error handler
302 // no stacktraces leaked to user
303 app.use(function global_error(err, _unused_req, res, next) {
304 res.status(err.status || 500);
305 return res.render(error_view_path, {
306 message: err.message,
307 error: {},
308 status: err.status
309 });
310 });
311 }
312 }
313
314 return app;
315}
316
317exports = module.exports = main;