UNPKG

13.6 kBJavaScriptView Raw
1'use strict';
2
3var _isPlainObject = require('lodash/isPlainObject');
4var _isFunction = require('lodash/isFunction');
5var _isArray = require('lodash/isArray');
6var _clone = require('lodash/clone');
7var _defaults = require('lodash/defaults');
8var _omit = require('lodash/omit');
9
10var Util = require('util');
11
12var toArray = require('./helper/toArray');
13var eachSeries = require('./helper/eachSeries');
14var stringFillEndBy = require('./helper/stringFillEndBy');
15var addSlashToStringEnd = require('./helper/addSlashToStringEnd');
16
17var UUID = require('uuid');
18var Express = require('express');
19var Log = require('./Log');
20
21/**
22 *
23 * @param {string} method
24 * @returns {function}
25 */
26function make_sugar(method) {
27 return function() {
28 if(this._generated_by_map) {
29 Log.error('controllers', 'Controller generated by .map options');
30 }
31
32 //var args = toArray(arguments, 1),
33 // params = _regulize_route_params.call(this, args),
34 //
35 // url = params[0],
36 // options = params[1],
37 // user_callbacks = params[2],
38 //
39 // map_key = [method, url].join(' '),
40 // map_value = _defaults({
41 // action: user_callbacks
42 // }, options);
43 //
44 //this.map[map_key] = map_value;
45
46 //return this;
47 return this.method.apply(this, [method].concat(toArray(arguments)));
48 };
49}
50
51/**
52 *
53 * @class Controller
54 *
55 * @param {ControllerOptions} options
56 */
57function Controller(options) {
58 var config = this._process_config(options);
59 var map_config = config.map;
60
61 this.id = UUID.v4();
62 this.name = config.name;
63 this.root = addSlashToStringEnd(config.root);
64 this.router = Express.Router(config.router);
65 this.map = map_config ?
66 map_config :
67 null;
68
69 this._generated_by_map = !!map_config;
70 this._common_options = _omit(config, [
71 'name',
72 'root',
73 'router',
74 'map'
75 ]);
76 this._common_options.before = [];
77 this._compiled = false;
78}
79
80Controller.prototype._config_processors = [];
81Controller.prototype._populates = [];
82Controller.prototype._middlewares = [];
83
84/**
85 *
86 * @param {...function} processors
87 */
88Controller.process_config = function(processors) {
89 processors = toArray(arguments);
90
91 var config_processors = this.prototype._config_processors;
92 config_processors.push.apply(config_processors, processors);
93};
94
95Controller.processConfig = Controller.process_config;
96
97/**
98 *
99 * @param {...function} populates
100 */
101Controller.populate = function(populates) {
102 populates = toArray(arguments);
103
104 var controller_populates = this.prototype._populates;
105 controller_populates.push.apply(controller_populates, populates);
106};
107
108/**
109 *
110 * @param {...function} middleware
111 */
112Controller.middleware = function(middleware) {
113 middleware = toArray(arguments);
114
115 var middlewares = this.prototype._middlewares;
116 middlewares.push.apply(middlewares, middleware);
117};
118
119Controller.middleware(
120 function add_options_middleware(options) {
121 return function add_options_middleware(request, response, next) {
122 request.controller_options = request.controllerOptions = options;
123 next();
124 };
125 },
126 function ajax_middleware(options) {
127 var both_request_types,
128 only_ajax, without_ajax;
129
130 switch(typeof options.ajax) {
131 case 'boolean':
132 both_request_types = false;
133 only_ajax = options.ajax;
134 without_ajax = !options.ajax;
135 break;
136 default:
137 both_request_types = true;
138 }
139
140 return function ajax_middleware(request, response, next) {
141 if (!both_request_types) {
142 if (only_ajax && !request.xhr) {
143 return response.bad_request('Only AJAX request');
144 }
145 if (without_ajax && request.xhr) {
146 return response.bad_request('AJAX request is denied');
147 }
148 }
149
150 next();
151 };
152 }
153);
154
155/**
156 *
157 * @param {...routeHandler} callbacks
158 */
159Controller.prototype.before = function(callbacks) {
160 callbacks = toArray(arguments);
161
162 this._common_options.before = callbacks;
163};
164
165/**
166 *
167 * @param {string} name
168 * @param {function} expression
169 */
170Controller.prototype.param = function(name, expression) {
171 if(typeof name !== 'string') {
172 Log.error('controllers', 'Param name must be String');
173 }
174 if(typeof expression !== 'function') {
175 Log.error('controllers', 'Param name must be Function');
176 }
177
178 this.router.param.call(this.router, name, expression);
179};
180
181/**
182 *
183 * @param {string|Array.<string>} methods
184 * @param {string|Object|routeHandler} [path]
185 * @param {Object|routeHandler} [options]
186 * @param {...routeHandler} callbacks
187 */
188Controller.prototype.method = function(methods, path, options, callbacks) {
189 var self = this;
190 var args = toArray(arguments, 1);
191
192 toArray(methods).forEach(function(method) {
193 method = method === 'del' ?
194 'delete' :
195 method;
196
197 self._generate_url.apply(self, [method].concat(args));
198 });
199
200 return this;
201};
202
203/**
204 *
205 * @param {string|Object|routeHandler} [path]
206 * @param {Object|routeHandler} [options]
207 * @param {...routeHandler} callbacks
208 */
209Controller.prototype.get = make_sugar('get');
210
211/**
212 *
213 * @param {string|Object|routeHandler} [path]
214 * @param {Object|routeHandler} [options]
215 * @param {...routeHandler} callbacks
216 */
217Controller.prototype.post = make_sugar('post');
218
219/**
220 *
221 * @param {string|Object|routeHandler} [path]
222 * @param {Object|routeHandler} [options]
223 * @param {...routeHandler} callbacks
224 */
225Controller.prototype.put = make_sugar('put');
226
227/**
228 *
229 * @param {string|Object|routeHandler} [path]
230 * @param {Object|routeHandler} [options]
231 * @param {...routeHandler} callbacks
232 */
233Controller.prototype.patch = make_sugar('patch');
234
235/**
236 *
237 * @param {string|Object|routeHandler} [path]
238 * @param {Object|routeHandler} [options]
239 * @param {...routeHandler} callbacks
240 */
241Controller.prototype.delete = make_sugar('delete');
242
243/**
244 *
245 * @param {string|Object|routeHandler} [path]
246 * @param {Object|routeHandler} [options]
247 * @param {...routeHandler} callbacks
248 */
249Controller.prototype.del = Util.deprecate(
250 make_sugar('delete'),
251 'controller.del() is deprecated and will be removed from 2.0.0 version. Instead of use controller.delete()'
252);
253
254/**
255 *
256 * @param {errorHandler} custom_error_handler
257 */
258Controller.prototype.error = function(custom_error_handler) {
259 var self = this;
260
261 this.error_handler = custom_error_handler;
262 this.use(function(err, request, response, next) {
263 custom_error_handler.apply(self, arguments);
264 });
265
266 return this;
267};
268
269/**
270 *
271 * @returns {Controller}
272 */
273Controller.prototype.end = function() {
274 this.use(function(request, response) {
275 response.not_found();
276 });
277 return this;
278};
279
280/**
281 *
282 * @param {...routeHandler} routes
283 * @returns {Controller}
284 */
285Controller.prototype.use = function(routes) {
286 this.router.use.apply(this.router, toArray(arguments));
287
288 return this;
289};
290
291/**
292 *
293 */
294Controller.prototype.compile = function() {
295 if(this._compiled) {
296 return;
297 }
298
299 if(this.map) {
300 this._build_by_map();
301 }
302
303 this._compiled = true;
304};
305
306/**
307 *
308 * @private
309 * @param {Object} controller_config
310 * @returns {Object}
311 */
312Controller.prototype._process_config = function(controller_config) {
313 var self = this;
314
315 this._config_processors.forEach(function(processor) {
316 processor.call(self, controller_config);
317 });
318
319 return controller_config;
320};
321
322/**
323 *
324 * @private
325 * @param {Object} options
326 * @returns {Object}
327 */
328Controller.prototype._regulize_options = function(options) {
329 var common_options = this._common_options;
330
331 return options?
332 _defaults(this._process_config(options), common_options) :
333 _clone(common_options);
334};
335
336/**
337 *
338 * @private
339 * @param {...*} args
340 * @returns {[string, Object, function|Array.<function>]}
341 */
342Controller.prototype._regulize_route_params = function(args) {
343 if(_isFunction(args[0])) {
344 return ['/', this._regulize_options(null), args];
345 }
346
347 if(_isPlainObject(args[0])) {
348 return ['/', this._regulize_options(args[0]), args.slice(1)];
349 }
350
351 var options;
352 var callbacks;
353
354 if(_isPlainObject(args[1])) {
355 options = this._regulize_options(args[1]);
356 callbacks = args.slice(2);
357 } else {
358 options = this._regulize_options(null);
359 callbacks = args.slice(1);
360 }
361
362 return [args[0], options, callbacks];
363};
364
365/**
366 *
367 * @private
368 * @param {string} method
369 */
370Controller.prototype._generate_url = function(method) {
371 var args = toArray(arguments, 1);
372 var params = this._regulize_route_params(args);
373
374 var url = params[0];
375 var options = params[1];
376 var user_callbacks = params[2];
377
378 Log(
379 stringFillEndBy(method.toUpperCase(), ' ', 7),
380 (this.root + url).replace(/\/+/g, '/')
381 );
382
383 var callbacks = [];
384 var index, length;
385
386 for(index = 0, length = this._populates.length; index < length; ++index) {
387 callbacks.push(this._populates[index].bind(this));
388 }
389 for(index = 0, length = this._middlewares.length; index < length; ++index) {
390 callbacks.push(this._middlewares[index].call(this, options));
391 }
392
393 var before_callbacks = this._common_options.before;
394
395 for(index = 0, length = before_callbacks.length; index < length; ++index) {
396 callbacks.push(before_callbacks[index].bind(this));
397 }
398 for(index = 0, length = user_callbacks.length; index < length; ++index) {
399 callbacks.push(user_callbacks[index].bind(this));
400 }
401
402 this.router[method](url, function(request, response, next_route) {
403 eachSeries(callbacks, function(callback, next_callback, interrupt) {
404 /**
405 *
406 * @param {*|Error} error
407 */
408 function unexpected_error_handler(error) {
409 interrupt();
410 next_route(error);
411 }
412
413 try {
414 var result = callback(
415 request,
416 response,
417 /**
418 *
419 * @param {*|Error} options
420 */
421 function next_handler(options) {
422 if(options instanceof Error) {
423 interrupt();
424 next_route(options);
425 } else {
426 next_callback();
427 }
428 },
429 next_route
430 );
431
432 if(result) {
433 var Class = result.constructor;
434
435 /**
436 * Rough detection of Promise's instance
437 *
438 * @type {boolean}
439 */
440 var is_promise = !!(Class.all && Class.race && result.then && result.catch);
441
442 if(is_promise) {
443 result.catch(unexpected_error_handler);
444 }
445 }
446 } catch(error) {
447 unexpected_error_handler(error);
448 }
449 }, next_route);
450 });
451};
452
453/**
454 *
455 * @private
456 */
457Controller.prototype._build_by_map = function() {
458 var self = this;
459
460 /**
461 *
462 * @private
463 * @param {*} handler
464 * @returns {function}
465 * @throws {TypeError}
466 */
467 function _get_route_handler(handler) {
468 if(typeof handler === 'function') {
469 return handler;
470 } else {
471 handler = self[handler];
472
473 if (typeof handler !== 'function') {
474 Log.error('controllers', 'Route handler must be a Function');
475 }
476
477 return handler;
478 }
479 }
480
481 /**
482 *
483 * @private
484 * @param {function|Array.<function>} handlers
485 * @returns {Array.<function>}
486 */
487 function _make_handlers(handlers) {
488 return toArray(handlers).map(function(handler) {
489 return _get_route_handler(handler);
490 });
491 }
492
493 /**
494 *
495 * @private
496 * @param method
497 * @param path
498 * @param options
499 * @param handlers
500 */
501 function _route_by_function(method, path, options, handlers) {
502 self.method.apply(self, [method, path, options].concat(_make_handlers(handlers)));
503 }
504
505 /**
506 *
507 * @private
508 * @param method
509 * @param path
510 * @param options
511 */
512 function _route_by_object(method, path, options) {
513 var handlers = options.action;
514 var route_options = _omit(options, 'action');
515
516 _route_by_function(method, path, route_options, handlers);
517 }
518
519 var map = this.map;
520 var routes = Object.keys(map);
521
522 routes.forEach(function(route) {
523 var handler = map[route];
524 var route_splitter = /\s+/;
525
526 route = route.split(route_splitter);
527
528 var method = route[0];
529 var path = route[1];
530
531 if(typeof handler === 'string' || _isArray(handler)) {
532 _route_by_function(method, path, {}, handler);
533 } else {
534 _route_by_object(method, path, handler);
535 }
536 });
537};
538
539Controller.fn = Controller.prototype;
540
541module.exports = Controller;