adapt-authoring-server 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/Router.js CHANGED
@@ -1,272 +1,272 @@
1
- import _ from 'lodash'
2
- import { App } from 'adapt-authoring-core'
3
- import express from 'express'
4
- import ServerUtils from './ServerUtils.js'
5
- /**
6
- * Handles the Express routing functionality
7
- * @memberof server
8
- */
9
- class Router {
10
- /**
11
- * If passing an {@link ExpressRouter} as the parentRouter, it is assumed that the Express Router is the top of the router 'heirarchy' (which will have an impact of some of the {@link Router} methods)
12
- * @param {String} root Route API endpoint for this router
13
- * @param {Router|ExpressRouter} parentRouter Parent to mount router
14
- * @param {Array<Route>} routes Array of routes
15
- */
16
- constructor (root, parentRouter, routes) {
17
- /**
18
- * The root route the router will be mounted at
19
- * @type {String}
20
- */
21
- this.root = root
22
- /**
23
- * Routes config
24
- * @type {Array<Route>}
25
- */
26
- this.routes = routes ?? []
27
- /**
28
- * Express router instance
29
- * @type {external:ExpressRouter}
30
- */
31
- this.expressRouter = express.Router()
32
- /**
33
- * Express router instance
34
- * @type {ExpressApp|Router}
35
- */
36
- this.parentRouter = parentRouter
37
- /**
38
- * List of sub-routers
39
- * @type {Array<Router>}
40
- */
41
- this.childRouters = []
42
- /**
43
- * Middleware stack to be added directly to the router
44
- * @type {Array<Function>}
45
- */
46
- this.routerMiddleware = [
47
- ServerUtils.addErrorHandler
48
- ]
49
- /**
50
- * Middleware stack to be added before route handlers (useful if you need access to specific request attributes that don't exist when standard middleware runs)
51
- * @type {Array<Function>}
52
- */
53
- this.handlerMiddleware = [
54
- ServerUtils.addExistenceProps,
55
- ServerUtils.handleInternalRoutes
56
- ]
57
- /** @ignore */this._initialised = false
58
- }
59
-
60
- /**
61
- * Returns the map of routes attached to this router
62
- * @type {Object}
63
- */
64
- get map () {
65
- return ServerUtils.generateRouterMap(this)
66
- }
67
-
68
- /**
69
- * Generates this router's path from its ancestors
70
- * @type {String}
71
- */
72
- get path () {
73
- let p = ''
74
-
75
- if (_.isString(this.parentRouter.path)) {
76
- p += this.parentRouter.path
77
- }
78
- if (p[p.length - 1] !== '/' && this.root[0] !== '/') {
79
- p += '/'
80
- }
81
- return p + this.root
82
- }
83
-
84
- /**
85
- * Returns the URL for the router
86
- * @return {String} The URL
87
- */
88
- get url () {
89
- try {
90
- const serverUrl = App.instance.dependencyloader.instances['adapt-authoring-server'].url
91
- return serverUrl + this.path
92
- } catch (e) {
93
- this.log('error', e)
94
- return ''
95
- }
96
- }
97
-
98
- /**
99
- * Adds middleware to the router stack. Accepts multiple params.
100
- * @param {...Function} func Middleware function(s) to be added
101
- * @return {AbstractApiModule} This instance, for chaining
102
- * @see https://expressjs.com/en/guide/using-middleware.html
103
- */
104
- addMiddleware (...func) {
105
- return this._addMiddleware(this.routerMiddleware, ...func)
106
- }
107
-
108
- /**
109
- * Adds middleware to be called prior to any route handlers. Accepts multiple params. Useful if you need access to specific request attributes that don't exist when standard middleware runs.
110
- * @param {...Function} func Middleware function(s) to be added
111
- * @return {AbstractApiModule} This instance, for chaining
112
- * @see https://expressjs.com/en/guide/using-middleware.html
113
- */
114
- addHandlerMiddleware (...func) {
115
- return this._addMiddleware(this.handlerMiddleware, ...func)
116
- }
117
-
118
- /** @ignore */ _addMiddleware (stack, ...func) {
119
- if (func.length) {
120
- this.warnOnInited('middleware may not be called before any route handlers')
121
- func.forEach(f => _.isFunction(f) && !stack.includes(f) && stack.push(f))
122
- }
123
- return this
124
- }
125
-
126
- /**
127
- * Recursively gets middleware of the current router heirarchy
128
- * @param {Router} router The current router (used when run recursively)
129
- * @param {Array<function>} middleware Middleware function(s) (used when run recursively)
130
- * @return {Array<Function>}
131
- */
132
- getHandlerMiddleware (router = this, middleware = []) {
133
- if (!(router instanceof Router)) {
134
- return middleware
135
- }
136
- return _.uniq(this.getHandlerMiddleware(router.parentRouter, [...router.handlerMiddleware, ...middleware]))
137
- }
138
-
139
- /**
140
- * Store route definition. Accepts multiple params.
141
- * @param {...Route} route Config of route(s) to be added
142
- * @return {AbstractApiModule} This instance, for chaining
143
- */
144
- addRoute (...route) {
145
- const inited = this.warnOnInited(`cannot set further routes (${this.path} ${route.map(r => r.route).join(', ')})`)
146
- if (!inited && route.length) {
147
- this.routes.push(...route.filter(this.validateRoute, this))
148
- }
149
- return this
150
- }
151
-
152
- /**
153
- * Function for filtering bad route configs
154
- * @param {Route} r Route config
155
- * @return {Boolean}
156
- */
157
- validateRoute (r) {
158
- const ePrefix = `invalid route config for ${this.route} router`
159
- if (!_.isString(r.route)) {
160
- this.log('warn', `${ePrefix}, route must be a string`)
161
- return false
162
- }
163
- if (!r.handlers) {
164
- this.log('warn', `${ePrefix}, no route handlers defined`)
165
- return false
166
- }
167
- // handlers can be single function or array of functions
168
- const allHandlersFuncs = Object.entries(r.handlers).every(([m, h]) => {
169
- if (this.expressRouter[m] === undefined) {
170
- this.log('warn', `${ePrefix}, ${m} must be a valid Express.js function`)
171
- return false
172
- }
173
- if (!_.isFunction(h) && !(_.isArray(h) && h.every(_.isFunction))) {
174
- this.log('warn', `${ePrefix} ${m.toUpperCase()} ${r.route}, all route handlers must be functions`)
175
- return false
176
- }
177
- return true
178
- })
179
- if (!allHandlersFuncs) {
180
- return false
181
- }
182
- return true
183
- }
184
-
185
- /**
186
- * Creates and adds a sub-router to this router.
187
- * @param {string} root The root of the child router
188
- * @param {Array<Route>} routes Array of Routes to add
189
- * @return {Router} The new router instance
190
- */
191
- createChildRouter (root, routes) {
192
- if (this.warnOnInited(`cannot create further child routers (${this.path}/${root})`)) {
193
- return this
194
- }
195
- const router = new Router(root, this, routes)
196
-
197
- this.childRouters.push(router)
198
-
199
- this.log('debug', 'ADD_ROUTER', router.path)
200
-
201
- return router
202
- }
203
-
204
- /**
205
- * Initialises the API
206
- */
207
- init () {
208
- if (this.warnOnInited(`(${this.path})`)) {
209
- return
210
- }
211
- if (this.routerMiddleware.length) {
212
- this.expressRouter.use(...this.routerMiddleware)
213
- }
214
- if (this.childRouters.length) {
215
- this.childRouters.forEach(c => {
216
- c.init()
217
- this.expressRouter.use(c.root, c.expressRouter)
218
- })
219
- }
220
- if (this.routes.length) {
221
- this.routes.forEach(r => {
222
- Object.entries(r.handlers).forEach(([method, handler]) => {
223
- this.expressRouter[method](r.route, ServerUtils.cacheRouteConfig(r), ...this.getHandlerMiddleware(), handler)
224
- this.log('debug', 'ADD_ROUTE', method.toUpperCase(), `${this.path !== '/' ? this.path : ''}${r.route}`)
225
- })
226
- })
227
- }
228
- // add to the parent stack
229
- if (this.parentRouter instanceof Router) {
230
- this.parentRouter.expressRouter.use(`/${this.root}`, this.expressRouter)
231
- } else {
232
- const route = this.root[0] !== '/' ? `/${this.root}` : this.root
233
- this.parentRouter.use(route, this.expressRouter)
234
- }
235
- this._initialised = true
236
- }
237
-
238
- /**
239
- * Shortcut for checking Router has initialised, logging a warning if not
240
- * @param {String} message Message to log on error
241
- * @return {Boolean}
242
- */
243
- warnOnInited (message) {
244
- if (this._initialised) {
245
- this.log('warn', `router has already initialised, ${message}`)
246
- }
247
- return this._initialised
248
- }
249
-
250
- /**
251
- * Creates an array defining the router inheritance hierarchy
252
- * @param {Router} router The root router
253
- * @return {Array}
254
- */
255
- flattenRouters (router = this) {
256
- return router.childRouters.reduce((a, c) => {
257
- c.childRouters ? a.push(...this.flattenRouters(c)) : a.push(c)
258
- return a
259
- }, [router])
260
- }
261
-
262
- /**
263
- * Logs a message
264
- * @param {String} level Level of log
265
- * @param {...*} args Arguments to be logged
266
- */
267
- log (level, ...args) {
268
- App.instance.logger.log(level, this.constructor.name.toLowerCase(), ...args)
269
- }
270
- }
271
-
272
- export default Router
1
+ import _ from 'lodash'
2
+ import { App } from 'adapt-authoring-core'
3
+ import express from 'express'
4
+ import ServerUtils from './ServerUtils.js'
5
+ /**
6
+ * Handles the Express routing functionality
7
+ * @memberof server
8
+ */
9
+ class Router {
10
+ /**
11
+ * If passing an {@link ExpressRouter} as the parentRouter, it is assumed that the Express Router is the top of the router 'heirarchy' (which will have an impact of some of the {@link Router} methods)
12
+ * @param {String} root Route API endpoint for this router
13
+ * @param {Router|ExpressRouter} parentRouter Parent to mount router
14
+ * @param {Array<Route>} routes Array of routes
15
+ */
16
+ constructor (root, parentRouter, routes) {
17
+ /**
18
+ * The root route the router will be mounted at
19
+ * @type {String}
20
+ */
21
+ this.root = root
22
+ /**
23
+ * Routes config
24
+ * @type {Array<Route>}
25
+ */
26
+ this.routes = routes ?? []
27
+ /**
28
+ * Express router instance
29
+ * @type {external:ExpressRouter}
30
+ */
31
+ this.expressRouter = express.Router()
32
+ /**
33
+ * Express router instance
34
+ * @type {ExpressApp|Router}
35
+ */
36
+ this.parentRouter = parentRouter
37
+ /**
38
+ * List of sub-routers
39
+ * @type {Array<Router>}
40
+ */
41
+ this.childRouters = []
42
+ /**
43
+ * Middleware stack to be added directly to the router
44
+ * @type {Array<Function>}
45
+ */
46
+ this.routerMiddleware = [
47
+ ServerUtils.addErrorHandler
48
+ ]
49
+ /**
50
+ * Middleware stack to be added before route handlers (useful if you need access to specific request attributes that don't exist when standard middleware runs)
51
+ * @type {Array<Function>}
52
+ */
53
+ this.handlerMiddleware = [
54
+ ServerUtils.addExistenceProps,
55
+ ServerUtils.handleInternalRoutes
56
+ ]
57
+ /** @ignore */this._initialised = false
58
+ }
59
+
60
+ /**
61
+ * Returns the map of routes attached to this router
62
+ * @type {Object}
63
+ */
64
+ get map () {
65
+ return ServerUtils.generateRouterMap(this)
66
+ }
67
+
68
+ /**
69
+ * Generates this router's path from its ancestors
70
+ * @type {String}
71
+ */
72
+ get path () {
73
+ let p = ''
74
+
75
+ if (_.isString(this.parentRouter.path)) {
76
+ p += this.parentRouter.path
77
+ }
78
+ if (p[p.length - 1] !== '/' && this.root[0] !== '/') {
79
+ p += '/'
80
+ }
81
+ return p + this.root
82
+ }
83
+
84
+ /**
85
+ * Returns the URL for the router
86
+ * @return {String} The URL
87
+ */
88
+ get url () {
89
+ try {
90
+ const serverUrl = App.instance.dependencyloader.instances['adapt-authoring-server'].url
91
+ return serverUrl + this.path
92
+ } catch (e) {
93
+ this.log('error', e)
94
+ return ''
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Adds middleware to the router stack. Accepts multiple params.
100
+ * @param {...Function} func Middleware function(s) to be added
101
+ * @return {AbstractApiModule} This instance, for chaining
102
+ * @see https://expressjs.com/en/guide/using-middleware.html
103
+ */
104
+ addMiddleware (...func) {
105
+ return this._addMiddleware(this.routerMiddleware, ...func)
106
+ }
107
+
108
+ /**
109
+ * Adds middleware to be called prior to any route handlers. Accepts multiple params. Useful if you need access to specific request attributes that don't exist when standard middleware runs.
110
+ * @param {...Function} func Middleware function(s) to be added
111
+ * @return {AbstractApiModule} This instance, for chaining
112
+ * @see https://expressjs.com/en/guide/using-middleware.html
113
+ */
114
+ addHandlerMiddleware (...func) {
115
+ return this._addMiddleware(this.handlerMiddleware, ...func)
116
+ }
117
+
118
+ /** @ignore */ _addMiddleware (stack, ...func) {
119
+ if (func.length) {
120
+ this.warnOnInited('middleware may not be called before any route handlers')
121
+ func.forEach(f => _.isFunction(f) && !stack.includes(f) && stack.push(f))
122
+ }
123
+ return this
124
+ }
125
+
126
+ /**
127
+ * Recursively gets middleware of the current router heirarchy
128
+ * @param {Router} router The current router (used when run recursively)
129
+ * @param {Array<function>} middleware Middleware function(s) (used when run recursively)
130
+ * @return {Array<Function>}
131
+ */
132
+ getHandlerMiddleware (router = this, middleware = []) {
133
+ if (!(router instanceof Router)) {
134
+ return middleware
135
+ }
136
+ return _.uniq(this.getHandlerMiddleware(router.parentRouter, [...router.handlerMiddleware, ...middleware]))
137
+ }
138
+
139
+ /**
140
+ * Store route definition. Accepts multiple params.
141
+ * @param {...Route} route Config of route(s) to be added
142
+ * @return {AbstractApiModule} This instance, for chaining
143
+ */
144
+ addRoute (...route) {
145
+ const inited = this.warnOnInited(`cannot set further routes (${this.path} ${route.map(r => r.route).join(', ')})`)
146
+ if (!inited && route.length) {
147
+ this.routes.push(...route.filter(this.validateRoute, this))
148
+ }
149
+ return this
150
+ }
151
+
152
+ /**
153
+ * Function for filtering bad route configs
154
+ * @param {Route} r Route config
155
+ * @return {Boolean}
156
+ */
157
+ validateRoute (r) {
158
+ const ePrefix = `invalid route config for ${this.route} router`
159
+ if (!_.isString(r.route)) {
160
+ this.log('warn', `${ePrefix}, route must be a string`)
161
+ return false
162
+ }
163
+ if (!r.handlers) {
164
+ this.log('warn', `${ePrefix}, no route handlers defined`)
165
+ return false
166
+ }
167
+ // handlers can be single function or array of functions
168
+ const allHandlersFuncs = Object.entries(r.handlers).every(([m, h]) => {
169
+ if (this.expressRouter[m] === undefined) {
170
+ this.log('warn', `${ePrefix}, ${m} must be a valid Express.js function`)
171
+ return false
172
+ }
173
+ if (!_.isFunction(h) && !(_.isArray(h) && h.every(_.isFunction))) {
174
+ this.log('warn', `${ePrefix} ${m.toUpperCase()} ${r.route}, all route handlers must be functions`)
175
+ return false
176
+ }
177
+ return true
178
+ })
179
+ if (!allHandlersFuncs) {
180
+ return false
181
+ }
182
+ return true
183
+ }
184
+
185
+ /**
186
+ * Creates and adds a sub-router to this router.
187
+ * @param {string} root The root of the child router
188
+ * @param {Array<Route>} routes Array of Routes to add
189
+ * @return {Router} The new router instance
190
+ */
191
+ createChildRouter (root, routes) {
192
+ if (this.warnOnInited(`cannot create further child routers (${this.path}/${root})`)) {
193
+ return this
194
+ }
195
+ const router = new Router(root, this, routes)
196
+
197
+ this.childRouters.push(router)
198
+
199
+ this.log('debug', 'ADD_ROUTER', router.path)
200
+
201
+ return router
202
+ }
203
+
204
+ /**
205
+ * Initialises the API
206
+ */
207
+ init () {
208
+ if (this.warnOnInited(`(${this.path})`)) {
209
+ return
210
+ }
211
+ if (this.routerMiddleware.length) {
212
+ this.expressRouter.use(...this.routerMiddleware)
213
+ }
214
+ if (this.childRouters.length) {
215
+ this.childRouters.forEach(c => {
216
+ c.init()
217
+ this.expressRouter.use(c.root, c.expressRouter)
218
+ })
219
+ }
220
+ if (this.routes.length) {
221
+ this.routes.forEach(r => {
222
+ Object.entries(r.handlers).forEach(([method, handler]) => {
223
+ this.expressRouter[method](r.route, ServerUtils.cacheRouteConfig(r), ...this.getHandlerMiddleware(), handler)
224
+ this.log('debug', 'ADD_ROUTE', method.toUpperCase(), `${this.path !== '/' ? this.path : ''}${r.route}`)
225
+ })
226
+ })
227
+ }
228
+ // add to the parent stack
229
+ if (this.parentRouter instanceof Router) {
230
+ this.parentRouter.expressRouter.use(`/${this.root}`, this.expressRouter)
231
+ } else {
232
+ const route = this.root[0] !== '/' ? `/${this.root}` : this.root
233
+ this.parentRouter.use(route, this.expressRouter)
234
+ }
235
+ this._initialised = true
236
+ }
237
+
238
+ /**
239
+ * Shortcut for checking Router has initialised, logging a warning if not
240
+ * @param {String} message Message to log on error
241
+ * @return {Boolean}
242
+ */
243
+ warnOnInited (message) {
244
+ if (this._initialised) {
245
+ this.log('warn', `router has already initialised, ${message}`)
246
+ }
247
+ return this._initialised
248
+ }
249
+
250
+ /**
251
+ * Creates an array defining the router inheritance hierarchy
252
+ * @param {Router} router The root router
253
+ * @return {Array}
254
+ */
255
+ flattenRouters (router = this) {
256
+ return router.childRouters.reduce((a, c) => {
257
+ c.childRouters ? a.push(...this.flattenRouters(c)) : a.push(c)
258
+ return a
259
+ }, [router])
260
+ }
261
+
262
+ /**
263
+ * Logs a message
264
+ * @param {String} level Level of log
265
+ * @param {...*} args Arguments to be logged
266
+ */
267
+ log (level, ...args) {
268
+ App.instance.logger.log(level, this.constructor.name.toLowerCase(), ...args)
269
+ }
270
+ }
271
+
272
+ export default Router