1 | 'use strict';
|
2 |
|
3 | var _isPlainObject = require('lodash/isPlainObject');
|
4 | var _isFunction = require('lodash/isFunction');
|
5 | var _isArray = require('lodash/isArray');
|
6 | var _clone = require('lodash/clone');
|
7 | var _defaults = require('lodash/defaults');
|
8 | var _omit = require('lodash/omit');
|
9 |
|
10 | var Util = require('util');
|
11 |
|
12 | var toArray = require('./helper/toArray');
|
13 | var eachSeries = require('./helper/eachSeries');
|
14 | var stringFillEndBy = require('./helper/stringFillEndBy');
|
15 | var addSlashToStringEnd = require('./helper/addSlashToStringEnd');
|
16 |
|
17 | var UUID = require('uuid');
|
18 | var Express = require('express');
|
19 | var Log = require('./Log');
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | function make_sugar(method) {
|
27 | return function() {
|
28 | if(!this._by_autogenerated_map) {
|
29 | Log.error('controllers', 'Controller generated by .map options');
|
30 | }
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | return this.method.apply(this, [method].concat(toArray(arguments)));
|
48 | };
|
49 | }
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 | function 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 |
|
81 | Controller.prototype._config_processors = [];
|
82 | Controller.prototype._populates = [];
|
83 | Controller.prototype._middlewares = [];
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 | Controller.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 |
|
96 | Controller.processConfig = Controller.process_config;
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 | Controller.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 |
|
112 |
|
113 | Controller.middleware = function(middleware) {
|
114 | middleware = toArray(arguments);
|
115 |
|
116 | var middlewares = this.prototype._middlewares;
|
117 | middlewares.push.apply(middlewares, middleware);
|
118 | };
|
119 |
|
120 | Controller.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 | );
|
149 | Controller.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 |
|
188 |
|
189 | Controller.prototype.before = function(callbacks) {
|
190 | callbacks = toArray(arguments);
|
191 |
|
192 | this._common_options.before = callbacks;
|
193 | };
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 | Controller.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 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 | Controller.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 |
|
236 |
|
237 |
|
238 |
|
239 | Controller.prototype.get = make_sugar('get');
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 | Controller.prototype.post = make_sugar('post');
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 | Controller.prototype.put = make_sugar('put');
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 | Controller.prototype.patch = make_sugar('patch');
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 | Controller.prototype.delete = make_sugar('delete');
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | Controller.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 |
|
287 |
|
288 | Controller.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 |
|
302 |
|
303 | Controller.prototype.end = function() {
|
304 | this.use(function(request, response) {
|
305 | response.not_found();
|
306 | });
|
307 | return this;
|
308 | };
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 | Controller.prototype.use = function(routes) {
|
316 | this.router.use.apply(this.router, toArray(arguments));
|
317 |
|
318 | return this;
|
319 | };
|
320 |
|
321 |
|
322 |
|
323 |
|
324 | Controller.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 |
|
339 |
|
340 |
|
341 |
|
342 | Controller.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 |
|
359 |
|
360 |
|
361 |
|
362 | Controller.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 |
|
373 |
|
374 |
|
375 |
|
376 | Controller.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 |
|
402 |
|
403 |
|
404 | Controller.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 |
|
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 |
|
467 |
|
468 | Controller.prototype._build_by_map = function() {
|
469 | var self = this;
|
470 |
|
471 | |
472 |
|
473 |
|
474 |
|
475 |
|
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 |
|
490 |
|
491 |
|
492 |
|
493 |
|
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 |
|
502 |
|
503 |
|
504 |
|
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 |
|
533 | Controller.fn = Controller.prototype;
|
534 |
|
535 | module.exports = Controller;
|