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