UNPKG

6.21 kBJavaScriptView Raw
1const fs = require('fs');
2const path = require('path');
3const { chalk } = require('./../namespace/console');
4const policy = require('./policy');
5const bodyParser = require('body-parser');
6const args = require('optimist').argv;
7const lodash = require('lodash');
8const debug = require('debug');
9
10module.exports = class Router {
11
12 constructor (project, server) {
13 this.debug = debug('glad');
14 this.debug('Router:constructor');
15 this.project = project;
16 this.server = server;
17 this.routes = {};
18 this.controllers = {};
19 this.models = {};
20 this.errors = [];
21 this.config = require(this.project.configPath);
22 this.policies = require(path.join(project.projectPath, "policies.js"));
23 this.logging = require(project.configPath).logHTTP;
24 }
25
26 /**
27 * Builds out the router object
28 */
29 buildRoutes () {
30 this.debug('Router:buildRoutes');
31 return new Promise( (resolve, reject) => {
32 this.setRoutes();
33 this.setControllers();
34 this.setModels();
35 if (this.errors.length) {
36 reject(this.errors);
37 } else {
38 resolve();
39 }
40 });
41 }
42
43 /**
44 * Requires all of the routes for the application and stores them at Router.routes
45 */
46 setRoutes () {
47 this.debug('Router:setRoutes');
48 this.setObjectsForSegment(this.project.routesPath, 'routes', 'Router');
49 }
50
51 /**
52 * Requires all of the models for the application and stores them at Router.models
53 */
54 setModels () {
55 this.debug('Router:setModels');
56 this.setObjectsForSegment(this.project.modelsPath, 'models', 'Model');
57 }
58
59 /**
60 * Requires all of the controllers for the application and stores them at Router.controllers
61 */
62 setControllers () {
63 this.debug('Router:setControllers');
64 this.setObjectsForSegment(this.project.controllersPath, 'controllers', 'Controller');
65 }
66
67 /**
68 * Gets the file paths for all of the .js files in a directory [path]
69 */
70 getFilesForPath (path) {
71 this.debug(`Router:getFilesForPath - gets all js files in ${path}`);
72 let paths = fs.readdirSync(path).filter(file => /(\.js)$/.test(file));
73 let only = args.only && args.only.split(',').map(x => `${x}.js`);
74 /**
75 * Implement the --only flag.
76 * This allows you to have the server only handle routes for a specific controller.
77 * This use case is primarily for occasions such as booting up a server to warm caches for
78 * a specific controller, or for adding additional servers to handle an impacted resource.
79 **/
80 if (only && only.length) {
81 paths = paths.filter( p => lodash.includes(only, p));
82 }
83 return paths;
84 }
85
86 /**
87 * Scans a directory for .js files,
88 * Requires all of the js files in `fpath` to Router[segment]
89 * fpath fully qualified path to a folder
90 * segment `routes` | `models` | `controllers`
91 * error The humanized name of the segment that is being created
92 */
93 setObjectsForSegment (fpath, segment, error) {
94 this.debug(`Router:setObjectsForSegment ${segment}`);
95 this.getFilesForPath(fpath).forEach(file => {
96 let ref = path.join (fpath, file);
97 try {
98 this[segment][file.replace('.js', '')] = require(ref);
99 } catch (err) {
100 chalk.error(`Glad > ${error}`, err.stack);
101 this.errors.push({
102 message : "> Could Not Bind " + ref.split('/').pop().toString() + '::' + file.replace('.js', '') + " To Any Route!",
103 err : err
104 });
105 }
106 });
107 }
108
109 /**
110 * Implement the default body parser, override the default if the bodyParser key is defined on the route
111 * - Allow the engineer to override the default options but still use the default parser.
112 * - Allow the engineer to override the parser type but still use the default options.
113 * - Allow the engineer to override both the parser and the options.
114 */
115 bodyParser (config) {
116 this.debug('Router:bodyParser');
117 let type, options;
118 if (config.bodyParser && config.bodyParser.custom) {
119 return config.bodyParser.custom;
120 } else if (config.bodyParser) {
121 type = config.bodyParser.parser || this.config.defaultBodyParser && this.config.defaultBodyParser.type;
122 options = config.bodyParser || this.config.defaultBodyParser;
123 } else {
124 type = this.config.defaultBodyParser && this.config.defaultBodyParser.type;
125 options = this.config.defaultBodyParser;
126 }
127 type = type || 'json';
128 options = options || {};
129 return bodyParser[type](options);
130 }
131
132 setViewPath (config) {
133 this.debug('Router:setViewPath');
134 return (req, res, next) => {
135 req.__rootViewPath = `${this.project.viewsPath}`;
136 req.viewPath = req.__rootViewPath + config.action;
137 next();
138 }
139 }
140
141 /**
142 * Create the route in express
143 * - Get a body parser
144 * - Register the route and send the request through the body-parser then check the policy then finally the controller.
145 */
146 route (method, config) {
147 if (!config.controller) {
148 this.noControllerError(config);
149 }
150 this.debug(`Router:route ${config.path} => ${config.controller.name}#${config.action}`);
151 let { path, controller, action } = config;
152 let bodyParser = this.bodyParser(config);
153 let viewPath = this.setViewPath(config);
154 method = method.toLowerCase();
155 return this.server.app[method](path, bodyParser, viewPath, (req, res) => {
156 debug('glad')('Router:apply %s => %s#%s with policy %s', path, controller.name, action, config.policy || 'none');
157 return new policy(this.policies, this.logging, this.server, config.policy, controller, action).restrict(req, res);
158 });
159 }
160
161 noControllerError (config) {
162 chalk.error([
163 '',
164 '-------------------------------------',
165 'Error: NO_CONTROLLER',
166 'You can not have a route wthout a controller to handle requests made to it.',
167 'If you feel that you have encountered this message unexpectedly...',
168 'Please check that the name of the route file matches the exact name of the intended controller file.',
169 'Please review the route config that caused this error below.',
170 '-------------------------------------',
171 JSON.stringify(config), ''
172 ].join('\n'));
173
174 process.exit(1);
175 }
176
177}