-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathapi.js
155 lines (139 loc) · 3.62 KB
/
api.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/**
* @file Load *.js files in the api folder and setup express
* Each file should export:
*
* @property {Object} fields The request data format. See validate-fields for details
*
* @callback handler
* @param {Object} dbs A map of mongodb connections (indexed by connection name)
* @param {Object} body The request body
* @param {successCallback} success A callback to answer the request with success
* @param {errorCallback} error A callback to answer the request with error
*/
/**
* Success and error callbacks:
*
* @callback successCallback
* @param {Object} [response={}] The response object (default fields like 'status' will be put automatically)
*
* @callback errorCallback
* @param {(number|Error)} error An error instance or code (like 101, 200)
* @param {string} [message=''] The error message
*/
'use strict'
let express = require('express'),
fs = require('fs'),
validate = require('validate-fields')(),
dbs = require('./dbs'),
json = require('./json')
/**
* Setup API routes and call done(err, api) when done
* @param {Function} done
*/
module.exports = function (done) {
// Connect to mongo
dbs((err, dbs) => {
if (err) {
return done(err)
}
let api = new express.Router()
api.use((req, res, next) => {
if (!req.is('json')) {
return next(new APIError(101, 'Invalid Content-Type header, application/json was expected'))
}
next()
})
// JSON parser
api.use(require('body-parser').json({
reviver: json.reviver
}))
// End points
fs.readdirSync('./api').forEach(item => {
if (item.substr(-3).toLowerCase() === '.js') {
processFile('./api/' + item, '/' + item.substr(0, item.length - 3), api, dbs)
}
})
// Error handler
// next isn't used on purpose, because express demands a 4-arity function
// eslint-disable-next-line no-unused-vars
api.use((err, req, res, _) => {
let code, message
if (err instanceof APIError) {
code = err.code
message = err.message
} else {
console.log(err.stack)
code = 100
message = err.message || 'Unknown error'
}
res.json({
error: {
code,
message
}
})
})
done(null, api)
})
}
/**
* @class
* @param {number} code
* @param {string} message
*/
function APIError(code, message) {
this.code = code
this.message = message
}
/**
* Process a file as an api end point
* @param {string} fileName
* @param {string} url
* @param {Express} api
* @param {Object.<String, Db>} dbs
* @throws
*/
function processFile(fileName, url, api, dbs) {
let file = require(fileName),
fields = validate.parse(file.fields)
api.post(url, (req, res, next) => {
// Fields validation
if (fields.validate(req.body)) {
return next()
}
next(new APIError(101, fields.lastError))
}, wrapHandler(file.handler, dbs))
}
/**
* Return an express middleware for the given API endpoint handler
* @param {handler} handler
* @param {Object.<String, Db>} dbs
* @returns {function}
*/
function wrapHandler(handler, dbs) {
return function (req, res, next) {
// Filter allowed dbs
let allowedDbs
if (!req.user.connections) {
allowedDbs = dbs
} else {
allowedDbs = Object.create(null)
req.user.connections.forEach(conn => {
if (conn in dbs) {
allowedDbs[conn] = dbs[conn]
}
})
}
handler(allowedDbs, req.body, response => {
response = response || {}
if (typeof response !== 'object') {
throw new Error('The response must be an object')
}
response.error = null
response = json.stringify(response)
res.set('Content-Type', 'application/json').send(response)
}, (error, msg) => {
next(typeof error === 'number' ? new APIError(error, msg || '') : error)
})
}
}