1 | const { Router } = require('express')
|
2 | const _ = require('lodash')
|
3 | const path = require('path')
|
4 | const glob = require('glob')
|
5 | const numParser = require('num-parser')
|
6 |
|
7 | const mainRouter = Router()
|
8 | const routes = require(`${process.cwd()}/config/routes`)
|
9 | const {
|
10 | controllerPath
|
11 | } = require('../constants')
|
12 | const {
|
13 | logger
|
14 | } = require('./middleware')
|
15 |
|
16 |
|
17 | let controllers = {}
|
18 | _.each(glob.sync(`${process.cwd()}/${controllerPath}/*`), file => {
|
19 | let name = path.basename(file, '.js')
|
20 |
|
21 | let Controller = require(file)
|
22 | controllers[name] = new Controller
|
23 | })
|
24 |
|
25 | let genResourceRoutes = root => {
|
26 | return [
|
27 | [root, [
|
28 | [`get /`, 'fetchAll'],
|
29 | [`get /:id`, 'fetch'],
|
30 | [`post /`, 'create'],
|
31 | [`patch /:id`, 'update'],
|
32 | [`put /:id`, 'replace'],
|
33 | [`delete /:id`, 'destroy']
|
34 | ]]
|
35 | ]
|
36 | }
|
37 |
|
38 | let buildRoute = (router, verb, url, to, root) => {
|
39 |
|
40 | let cName, action
|
41 | if(/.+#.+/.test(to))
|
42 |
|
43 | [cName, action] = to.split('#')
|
44 | else
|
45 |
|
46 |
|
47 | [cName, action] = [(root || url.split('/')[0]), to]
|
48 |
|
49 |
|
50 | let controller
|
51 |
|
52 | if(!cName || /:.+/.test(cName))
|
53 | throw new Error('A controller must be specified.')
|
54 | else {
|
55 |
|
56 | let fileName = `${cName}_controller`,
|
57 | className = fileName
|
58 | .replace(/^[a-z]/, c => c.toUpperCase())
|
59 | .replace(/_([a-z])/g, (m, c) => c.toUpperCase())
|
60 | controller = controllers[fileName]
|
61 | if(!controller)
|
62 | throw new Error(`${className} does not exist for route "${path}".`)
|
63 | }
|
64 |
|
65 |
|
66 | let args = [`/${url.replace(/^(\/)/, '')}`, logger]
|
67 |
|
68 | args = args.concat(getFilterMethods('before', controller, action))
|
69 |
|
70 | args.push(numParser)
|
71 |
|
72 | args.push((req, res) => {
|
73 |
|
74 |
|
75 | let jsonProxy = res.json.bind(res)
|
76 | res.json = body => {
|
77 |
|
78 | _.each(getFilterMethods('after', controller, action), afterFilter => {
|
79 | try {
|
80 | let response = afterFilter(req, body)
|
81 | body = response || body
|
82 | } catch(err) {
|
83 | jsonProxy({
|
84 | 'Error': err.mess
|
85 | })
|
86 | }
|
87 | })
|
88 | jsonProxy(body)
|
89 | }
|
90 |
|
91 |
|
92 | let mainAction = controller[action]
|
93 | if(mainAction) {
|
94 | mainAction.call(controller, req, res)
|
95 | } else {
|
96 | jsonProxy('Action does not exist.').status(404)
|
97 | }
|
98 | })
|
99 | router[verb].apply(router, args)
|
100 | }
|
101 | let buildRoutes = (router, routes, root) => {
|
102 |
|
103 |
|
104 | _.each(routes, route => {
|
105 | let [ path, toAction ] = route
|
106 |
|
107 |
|
108 | if(Array.isArray(toAction)) {
|
109 | let baseRoot
|
110 | if(root) {
|
111 | baseRoot = root
|
112 | } else {
|
113 | if(!/^\w+$/.test(path))
|
114 | throw new Error('Routes that contain subroutes must only be described by a single word.')
|
115 | baseRoot = path
|
116 | }
|
117 |
|
118 |
|
119 | let subRouter = Router({mergeParams: true})
|
120 | buildRoutes(subRouter, toAction, baseRoot)
|
121 |
|
122 |
|
123 | router.use(`/${path}`, subRouter)
|
124 | } else {
|
125 | let [ verb, url ] = path.split(' ')
|
126 |
|
127 | if(verb === 'resources') {
|
128 | if(!/^\w+$/.test(url))
|
129 | throw new Error(`Resource routes must only be described by a single word. ("${url}")`)
|
130 |
|
131 |
|
132 | buildRoutes(router, genResourceRoutes(url), root)
|
133 | } else {
|
134 | if(!toAction)
|
135 | throw new Error(`No action method or subroutes are defined for route "${path}". Please check proper route definitions.`)
|
136 |
|
137 |
|
138 | buildRoute(router, verb, url, toAction, root)
|
139 | }
|
140 | }
|
141 | })
|
142 | }
|
143 |
|
144 | buildRoutes(mainRouter, routes)
|
145 |
|
146 | module.exports = mainRouter
|
147 |
|
148 |
|
149 | function getFilterMethods(type, controller, action) {
|
150 |
|
151 | let filters = getFilters(type, controller, action)
|
152 |
|
153 | type = `skip${type[0].toUpperCase() + type.slice(1)}`
|
154 | let skipFilterMethods = _.map(getFilters(type, controller, action), 'action')
|
155 |
|
156 |
|
157 | filters = _.filter(filters, f => {
|
158 | for(let method of skipFilterMethods)
|
159 | if(method === f.action)
|
160 | return false
|
161 | return true
|
162 | })
|
163 |
|
164 |
|
165 | return _.map(filters, f => {
|
166 | let { action } = f
|
167 | action = typeof action === 'string' ? controller[action] : action
|
168 | return action.bind(controller)
|
169 | })
|
170 | }
|
171 |
|
172 |
|
173 | function getFilters(type, controller, action) {
|
174 |
|
175 |
|
176 | let useOptions = { only: true, except: false }
|
177 |
|
178 |
|
179 | return _.filter(controller[`__${type}Filters`], filter => {
|
180 |
|
181 | if(!('action' in filter) || !filter.action)
|
182 | throw new Error(`No action defined in filter for [${controller.constructor.name}.${type}: ${JSON.stringify(filter)}].`)
|
183 |
|
184 |
|
185 | let containsBoth = _.every(_.keys(useOptions), o => o in filter)
|
186 | if(containsBoth)
|
187 | throw new Error(`${type[0].toUpperCase() + type.slice(1)} filter cannot have both \'only\' and \'except\' keys.`)
|
188 |
|
189 | let option = _.first(_.intersection(_.keys(useOptions), _.keys(filter)))
|
190 |
|
191 | if(!option)
|
192 | return true
|
193 |
|
194 | let useActions, use = useOptions[option]
|
195 | useActions = typeof (useActions = filter[option]) === 'string' ? [useActions] : useActions
|
196 |
|
197 | for(let ua of useActions)
|
198 | if(ua === action)
|
199 | return use
|
200 |
|
201 | return !use
|
202 | })
|
203 | }
|