-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapiserver.js
More file actions
172 lines (145 loc) · 4.25 KB
/
apiserver.js
File metadata and controls
172 lines (145 loc) · 4.25 KB
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
const DbUrl = process.env.DB_URL || "http://localhost:5984";
const ApiPort = process.env.API_PORT || 8080;
const staticDir = "public";
const EventEmitter = require("events");
const evt = new EventEmitter();
const express = require("express");
const join = require("path").join;
const bodyParser = require("body-parser");
const PouchDB = require("pouchdb-node");
const actions = new PouchDB(`${DbUrl}/actions`);
const requests = new PouchDB(`${DbUrl}/requests`);
const app = express();
const maxTime = 2000; // time in ms before timing out sync requests
const retryMs = 500; // time in ms to wait before trying to resubscribe to the change feed
function handleResult(res, statusCode = 200) {
return (err, body) => {
if (err) {
res.status(err.status);
return res.send(err.message);
}
res.status(statusCode);
res.send(body);
};
}
// async request, return the requestID so user can use /requests/:requestid to pickup the result
function handleAsync(res) {
return handleResult(res, 201); // request created
}
function returnSyncResult(res, id, type) {
if (type === "timeout") {
res.status(504); // timeout
return res.send(`request ${id} timed out`);
}
// return result
requests.get(id, handleResult(res));
}
// for synchronous requests we need to return the result of the action
function handleSync(res) {
return (error, body) => {
if (error) {
res.status(error.statusCode);
return res.send(error.message);
}
// wait for an event to pickup the result from the queue and return it to the requester
evt.once(body.id, (type) => {
returnSyncResult(res, body.id, type);
});
// if it takes longer than maxTime, return a timeout
setTimeout(() => {
evt.emit(body.id, "timeout");
}, maxTime);
};
}
// for parsing application/json
app.use(bodyParser.json());
// the UI
app.use("/dash/ui", express.static(staticDir));
app.get("/dash/ui/*", (req, res, next) => {
res.sendFile(join(__dirname, staticDir, "/index.html"));
});
// the actions
// list actions
app.get("/dash/actions", (req, res) => {
actions.query("actions/all", handleResult(res));
});
// get info on an action
app.get("/dash/actions/:id", (req, res) => {
actions.get(req.params.id, handleResult(res));
});
// create a new action or update an existing action
app.put("/dash/actions/:id", (req, res) => {
actions.put(req.body, req.params.id, handleResult(res));
});
// remove an action
app.delete("/dash/actions/:id", (req, res) => {
const rev = req.params.rev || req.query.rev;
actions.remove(req.params.id, rev, handleResult(res));
});
// the requests
// list all requests
app.get("/dash/requests", (req, res) => {
requests.query("requests/all", handleResult(res));
});
// get a specific request
app.get("/dash/requests/:requestid", (req, res) => {
requests.get(req.params.requestid, handleResult(res));
});
// root path
app.get("/", (req, res) => {
res.redirect("/dash/ui");
});
// handle requests for execution of an action
app.get("/*", (req, res) => {
// check if the action exists in the DB
actions.get(req.path, (error, doc) => {
if (error) {
res.status(404);
return res.send("No such action");
}
// store request in the database
var record = {
timestamp: new Date().toISOString(),
path: req.path,
status: "new",
params: req.query,
};
var handler = handleAsync(res);
if (typeof req.query.sync !== "undefined") {
handler = handleSync(res);
}
requests.post(record, handler);
});
});
// setup handling of changes to the requests database to facilitate sync requests
function waitForChanges() {
requests
.changes({
filter: "requests/iscompleted",
live: true,
})
.on("change", (change) => {
evt.emit(change.id);
})
.on("error", () => {
if (process.env.NODE_ENV !== "test") {
console.log(
"lost connection to database at",
DbUrl,
"trying to reconnect in",
retryMs,
"ms"
);
}
setTimeout(() => {
waitForChanges();
}, retryMs);
});
}
// start the show
waitForChanges();
const server = app.listen(ApiPort, () => {
console.log("Api server listening on port", ApiPort);
});
// to facilitate testing
module.exports = server;