UNPKG

8.6 kBJavaScriptView Raw
1// Copyright (c) 2018 Kinvey Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4// in compliance with the License. You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software distributed under the License
9// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10// or implied. See the License for the specific language governing permissions and limitations under
11// the License.
12
13const express = require('express');
14const bodyParser = require('body-parser');
15const log = require('../log/logger');
16
17module.exports = (() => {
18 let server = null;
19
20 function healthCheck(req, res, next) {
21 return res.status(200).json({ healthy: true });
22 }
23
24 function mapPostToElements(req, res, next) {
25 res.locals.body = req.body.body;
26 res.locals.hookType = req.body.hookType;
27 res.locals.method = req.body.method;
28 res.locals.query = req.body.query;
29 res.locals.objectName = req.body.objectName;
30 res.locals.entityId = req.body.entityId;
31 res.locals.tempObjectStore = req.body.tempObjectStore || {};
32 next();
33 }
34
35 function generateBaseTask(req, res, next) {
36 let appMetadata;
37 let requestHeaders;
38 const response = {};
39 const method = res.locals.method || req.method;
40
41 if (req.body.response) {
42 response.body = req.body.response.body;
43 response.headers = req.body.response.headers;
44 response.status = req.body.response.status;
45 }
46
47 try {
48 appMetadata = JSON.parse(req.get('X-Kinvey-App-Metadata')) || {};
49 } catch (e) {
50 appMetadata = {};
51 }
52
53 try {
54 requestHeaders = JSON.parse(req.get('X-Kinvey-Original-Request-Headers')) || {};
55 } catch (e) {
56 requestHeaders = {};
57 }
58
59 req.task = {
60 appId: req.get('X-Kinvey-Environment-Id') || '',
61 appMetadata,
62 authKey: req.get('X-Auth-Key'),
63 requestId: req.get('X-Kinvey-Request-Id') || '',
64 method: method.toUpperCase(),
65 request: {
66 method,
67 headers: requestHeaders,
68 username: req.get('X-Kinvey-Username') || '',
69 userId: req.get('X-Kinvey-User-Id') || ''
70 },
71 response: {
72 status: req.get('X-Kinvey-Response-Status') || response.status || 0,
73 headers: req.get('X-Kinvey-Response-Headers') || response.headers || {},
74 body: req.get('X-Kinvey-Response-Body') || response.body || {}
75 }
76 };
77 next();
78 }
79
80 function addFunctionsTaskAttributes(req, res, next) {
81 req.task.taskType = 'functions';
82 req.task.request.objectName = res.locals.objectName || '';
83 req.task.taskName = req.params.handlerName;
84 req.task.hookType = res.locals.hookType || 'customEndpoint';
85 req.task.request.tempObjectStore = res.locals.tempObjectStore;
86 next();
87 }
88
89 function addDataTaskAttributes(req, res, next) {
90 req.task.taskType = 'data';
91 req.task.request.serviceObjectName = req.params.serviceObjectName;
92 next();
93 }
94
95 function appendId(req, res, next) {
96 if (req.params && req.params.id) {
97 req.task.request.entityId = req.params.id;
98 } else if (res.locals.entityId) {
99 req.task.request.entityId = res.locals.entityId;
100 }
101 next();
102 }
103
104 function appendQuery(req, res, next) {
105 if (req.query && Object.keys(req.query).length > 0) {
106 req.task.request.query = req.query;
107 } else if (res.locals.query && Object.keys(res.locals.query).length > 0) {
108 req.task.request.query = res.locals.query;
109 }
110 next();
111 }
112
113 function appendCount(req, res, next) {
114 req.task.endpoint = '_count';
115 next();
116 }
117
118 function appendBody(req, res, next) {
119 req.task.request.body = res.locals.body || req.body;
120 next();
121 }
122
123 function getDataBody(result) {
124 return result.response.body;
125 }
126
127 function getFunctionsBody(result) {
128 return {
129 request: result.request,
130 response: result.response
131 };
132 }
133
134 function getAuthBody(result) {
135 return result.response.body;
136 }
137
138 function getDiscoveryBody(result) {
139 return result.discoveryObjects;
140 }
141
142 function sendTask(req, res, next) {
143 const taskReceivedCallback = req.app.get('taskReceivedCallback');
144 taskReceivedCallback(req.task, (err, result) => {
145 if (err) {
146 let errorResponse;
147 try {
148 errorResponse = JSON.parse(err);
149 } catch (e) {
150 errorResponse = err;
151 }
152 log.debug('Responding with error');
153 log.error(errorResponse);
154 res.set('X-Kinvey-Request-Continue', errorResponse.continue || true);
155
156 if (errorResponse.headers) {
157 res.set(errorResponse.headers);
158 }
159
160 return res.status(errorResponse.statusCode || 550).json(errorResponse.body);
161 } else if (result) {
162 try {
163 result.response.body = JSON.parse(result.response.body);
164 } catch (e) {
165 // Ignore
166 log.debug('Responding with success');
167 log.debug(result);
168 }
169
170 if (result.headers) {
171 res.set(result.headers);
172 }
173
174 if (typeof result.continue !== 'undefined' && result.continue !== null) {
175 res.set('X-Kinvey-Request-Continue', result.continue);
176 }
177
178 let body;
179
180 switch (req.task.taskType) {
181 case 'data':
182 body = getDataBody(result);
183 break;
184 case 'functions':
185 body = getFunctionsBody(result);
186 break;
187 case 'serviceDiscovery':
188 body = getDiscoveryBody(result);
189 break;
190 case 'auth':
191 body = getAuthBody(result);
192 break;
193 default:
194 body = {};
195 }
196
197 return res.status(result.response.statusCode || 200).json(body);
198 }
199
200 log.debug('Responding with error - no result or error');
201 const errorResponse = {
202 error: 'InternalError',
203 description: 'Bad Request: An Internal Error occurred',
204 debug: ''
205 };
206
207 log.error(errorResponse);
208 return res.status(500).json(errorResponse);
209 });
210 }
211
212 function buildDiscoverTask(req, res, next) {
213 req.task = {
214 taskType: 'serviceDiscovery',
215 request: {},
216 response: {}
217 };
218
219 next();
220 }
221
222 function addAuthTaskAttributes(req, res, next) {
223 req.task.taskType = 'auth';
224 req.task.taskName = req.params.handlerName;
225 req.task.request.body = {
226 username: req.body.username,
227 password: req.body.password,
228 options: req.body.options
229 };
230
231 next();
232 }
233
234 function checkIfFlexFunction(req, res, next) {
235 if (req.params.serviceObjectName === '_flexFunctions' || req.params.serviceObjectName === '_command') {
236 next('route');
237 } else {
238 next();
239 }
240 }
241
242 function startServer(taskReceivedCallback, startedCallback, options) {
243 const app = express();
244
245 app.set('taskReceivedCallback', taskReceivedCallback);
246 app.use(bodyParser.json({ limit: options.requestBodyLimit || 26214400 }));
247 app.use((req, res, next) => {
248 req.task = {};
249 next();
250 });
251
252 const router = express.Router();
253
254 // healthcheck endpoint
255 router.get('/healthcheck', healthCheck);
256
257 // FlexData routes
258 router.get('/:serviceObjectName/_count', generateBaseTask, addDataTaskAttributes, appendQuery, appendCount, sendTask);
259 router.get('/:serviceObjectName/:id?', generateBaseTask, addDataTaskAttributes, appendQuery, appendId, sendTask);
260 router.post('/:serviceObjectName/', checkIfFlexFunction, generateBaseTask, addDataTaskAttributes, appendBody, sendTask);
261 router.put('/:serviceObjectName/:id', generateBaseTask, addDataTaskAttributes, appendId, appendBody, sendTask);
262 router.delete('/:serviceObjectName/:id?', generateBaseTask, addDataTaskAttributes, appendId, appendQuery, sendTask);
263
264 // FlexFunctions route
265 router.post('/_flexFunctions/:handlerName', mapPostToElements, generateBaseTask, addFunctionsTaskAttributes, appendQuery, appendId, appendBody, sendTask);
266 router.post('/_auth/:handlerName', generateBaseTask, addAuthTaskAttributes, sendTask);
267
268 router.post('/_command/discover', buildDiscoverTask, sendTask);
269
270 app.use('/', router);
271
272 options.host = options.host || 'localhost';
273 options.port = options.port || 10001;
274
275 server = app.listen(options.port, options.host, () => {
276 console.log(`Service listening on ${options.port}`);
277 startedCallback();
278 });
279 }
280
281 function stop(callback) {
282 server.close(callback);
283 }
284
285 return {
286 startServer,
287 stop
288 };
289})();