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._generated_by_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 | 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 |
|
80 | Controller.prototype._config_processors = [];
|
81 | Controller.prototype._populates = [];
|
82 | Controller.prototype._middlewares = [];
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 | Controller.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 |
|
95 | Controller.processConfig = Controller.process_config;
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 | Controller.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 |
|
111 |
|
112 | Controller.middleware = function(middleware) {
|
113 | middleware = toArray(arguments);
|
114 |
|
115 | var middlewares = this.prototype._middlewares;
|
116 | middlewares.push.apply(middlewares, middleware);
|
117 | };
|
118 |
|
119 | Controller.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 |
|
158 |
|
159 | Controller.prototype.before = function(callbacks) {
|
160 | callbacks = toArray(arguments);
|
161 |
|
162 | this._common_options.before = callbacks;
|
163 | };
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 | Controller.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 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 | Controller.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 |
|
206 |
|
207 |
|
208 |
|
209 | Controller.prototype.get = make_sugar('get');
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 | Controller.prototype.post = make_sugar('post');
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 | Controller.prototype.put = make_sugar('put');
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 | Controller.prototype.patch = make_sugar('patch');
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 | Controller.prototype.delete = make_sugar('delete');
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 | Controller.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 |
|
257 |
|
258 | Controller.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 |
|
272 |
|
273 | Controller.prototype.end = function() {
|
274 | this.use(function(request, response) {
|
275 | response.not_found();
|
276 | });
|
277 | return this;
|
278 | };
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 | Controller.prototype.use = function(routes) {
|
286 | this.router.use.apply(this.router, toArray(arguments));
|
287 |
|
288 | return this;
|
289 | };
|
290 |
|
291 |
|
292 |
|
293 |
|
294 | Controller.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 |
|
309 |
|
310 |
|
311 |
|
312 | Controller.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 |
|
325 |
|
326 |
|
327 |
|
328 | Controller.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 |
|
339 |
|
340 |
|
341 |
|
342 | Controller.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 |
|
368 |
|
369 |
|
370 | Controller.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 | try {
|
405 | callback(
|
406 | request,
|
407 | response,
|
408 | |
409 |
|
410 |
|
411 |
|
412 | function next_handler(options) {
|
413 | if(options instanceof Error) {
|
414 | interrupt();
|
415 | next_route(options);
|
416 | } else {
|
417 | next_callback();
|
418 | }
|
419 | },
|
420 | next_route
|
421 | );
|
422 | } catch(err) {
|
423 | interrupt();
|
424 | next_route(err);
|
425 | }
|
426 | }, next_route);
|
427 | });
|
428 | };
|
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 | Controller.prototype._build_by_map = function() {
|
435 | var self = this;
|
436 |
|
437 | |
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 | function _get_route_handler(handler) {
|
445 | if(typeof handler === 'function') {
|
446 | return handler;
|
447 | } else {
|
448 | handler = self[handler];
|
449 |
|
450 | if (typeof handler !== 'function') {
|
451 | Log.error('controllers', 'Route handler must be a Function');
|
452 | }
|
453 |
|
454 | return handler;
|
455 | }
|
456 | }
|
457 |
|
458 | |
459 |
|
460 |
|
461 |
|
462 |
|
463 |
|
464 | function _make_handlers(handlers) {
|
465 | return toArray(handlers).map(function(handler) {
|
466 | return _get_route_handler(handler);
|
467 | });
|
468 | }
|
469 |
|
470 | |
471 |
|
472 |
|
473 |
|
474 |
|
475 |
|
476 |
|
477 |
|
478 | function _route_by_function(method, path, options, handlers) {
|
479 | self.method.apply(self, [method, path, options].concat(_make_handlers(handlers)));
|
480 | }
|
481 |
|
482 | |
483 |
|
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
489 | function _route_by_object(method, path, options) {
|
490 | var handlers = options.action;
|
491 | var route_options = _omit(options, 'action');
|
492 |
|
493 | _route_by_function(method, path, route_options, handlers);
|
494 | }
|
495 |
|
496 | var map = this.map;
|
497 | var routes = Object.keys(map);
|
498 |
|
499 | routes.forEach(function(route) {
|
500 | var handler = map[route];
|
501 | var route_splitter = /\s+/;
|
502 |
|
503 | route = route.split(route_splitter);
|
504 |
|
505 | var method = route[0];
|
506 | var path = route[1];
|
507 |
|
508 | if(typeof handler === 'string' || _isArray(handler)) {
|
509 | _route_by_function(method, path, {}, handler);
|
510 | } else {
|
511 | _route_by_object(method, path, handler);
|
512 | }
|
513 | });
|
514 | };
|
515 |
|
516 | Controller.fn = Controller.prototype;
|
517 |
|
518 | module.exports = Controller;
|