Skip to content

Commit 67de8c8

Browse files
committed
Merge branch 'master-v0.3'
* master-v0.3: * support search index parameters * add "all documents" option * accept cloudant search index params in payload * send cloudant results in message object update credential access for node-red v0.10 initial migration to cloudant package Conflicts: 77-cloudant-cf.js
2 parents 9d72efe + 1ff72ad commit 67de8c8

File tree

3 files changed

+174
-101
lines changed

3 files changed

+174
-101
lines changed

77-cloudant-cf.html

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@
161161
<select id="node-input-search">
162162
<option value="_id_">_id</option>
163163
<option value="_idx_">search index</option>
164+
<option value="_all_">all documents</option>
164165
</select>
165166
</div>
166167

@@ -318,19 +319,34 @@
318319
A node for searching documents in a Cloudant database.
319320
</p>
320321
<p>
321-
Searching for a document can be done in two modes: directly by using the
322-
document's <b>_id</b> or by using an existing <a
322+
Searching for documents can be done in three modes: directly by using the
323+
document's <b>_id</b>, by using an existing <a
323324
href="https://cloudant.com/for-developers/search/" target="_blank">Search
324-
Index</a>.
325+
Index</a> or retrieving <b>all documents</b> stored in your database.
325326
</p>
326327
<p>
327-
When querying using the <b>_id</b> option, the value for <code>_id</code>
328-
should be passed in the <code>msg.payload</code> as a string.
328+
When querying using the <b>_id</b> option, the value for the document's
329+
<code>_id</code> should be passed in the <code>msg.payload</code> as a
330+
string.
329331
</p>
330332
<p>
331-
To use an existing <b>Search Index</b> stored on the desired database, the
332-
query argument should be passed on the <code>msg.payload</code> following
333-
the <code>indexName:value</code> pattern. <i>The index must be created
334-
beforehand on the database.</i>
333+
To use an existing <b>Search Index</b> stored on the desired database,
334+
the query argument should be passed on the <code>msg.payload</code> as a
335+
string following the <code>indexName:value</code> pattern. Keep in mind
336+
that <i>the index must be created beforehand in the database.</i> and
337+
referenced here by its <code>design document/index name</code>.
338+
</p>
339+
<p>
340+
When querying using a <b>Search Index</b> you can pass the search
341+
parameters as an object in <code>msg.payload</code>. For example, you
342+
can pass an object like this: <code>{ query: "abc*", limit: 100 }</code>
343+
to change the value of <code>limit</code>. You can find more information
344+
on the accepted parameters in the <a
345+
href="https://docs.cloudant.com/api.html?http#queries" target="_blank">
346+
official Cloudant documentation</a>.
347+
</p>
348+
<p>
349+
The last method to retrieve documents is to simply get all of them by
350+
selecting the option <b>all documents</b>.
335351
</p>
336352
</script>

77-cloudant-cf.js

Lines changed: 147 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module.exports = function(RED) {
1919
var url = require('url');
2020
var querystring = require('querystring');
2121
var cfEnv = require("cfenv");
22+
var Cloudant = require("cloudant");
2223

2324
var MAX_ATTEMPTS = 3;
2425

@@ -64,31 +65,23 @@ module.exports = function(RED) {
6465
});
6566

6667
RED.httpAdmin.post('/cloudant/:id', function(req,res) {
67-
var body = "";
68+
var newCreds = req.body;
69+
var credentials = RED.nodes.getCredentials(req.params.id) || {};
6870

69-
req.on('data', function(chunk) {
70-
body += chunk;
71-
});
72-
73-
req.on('end', function() {
74-
var newCreds = querystring.parse(body);
75-
var credentials = RED.nodes.getCredentials(req.params.id) || {};
76-
77-
if (newCreds.user == null || newCreds.user == "") {
78-
delete credentials.user;
79-
} else {
80-
credentials.user = newCreds.user;
81-
}
71+
if (newCreds.user == null || newCreds.user == "") {
72+
delete credentials.user;
73+
} else {
74+
credentials.user = newCreds.user;
75+
}
8276

83-
if (newCreds.password == "") {
84-
delete credentials.password;
85-
} else {
86-
credentials.password = newCreds.password || credentials.password;
87-
}
77+
if (newCreds.password == "") {
78+
delete credentials.password;
79+
} else {
80+
credentials.password = newCreds.password || credentials.password;
81+
}
8882

89-
RED.nodes.addCredentials(req.params.id, credentials);
90-
res.send(200);
91-
});
83+
RED.nodes.addCredentials(req.params.id, credentials);
84+
res.send(200);
9285
});
9386

9487
//
@@ -124,52 +117,59 @@ module.exports = function(RED) {
124117
this.operation = n.operation;
125118
this.payonly = n.payonly || false;
126119
this.database = n.database;
127-
this.cloudant = n.cloudant;
128-
this.url = _getUrl(this, n);
129-
130-
if (this.url) {
131-
var node = this;
132-
133-
var nano = require('nano')(this.url);
134-
var db = nano.use(node.database);
120+
this.cloudantConfig = RED.nodes.getNode(n.cloudant);
121+
122+
var node = this;
123+
var credentials = {
124+
account: node.cloudantConfig.credentials.user,
125+
password: node.cloudantConfig.credentials.password
126+
};
127+
128+
Cloudant(credentials, function(err, cloudant) {
129+
if (err) { node.error(err); }
130+
else {
131+
// check if the database exists and create it if it doesn't
132+
createDatabase(cloudant, node);
133+
134+
node.on("input", function(msg) {
135+
handleMessage(cloudant, node, msg);
136+
});
137+
}
138+
});
135139

136-
// check if the database exists and create it if it doesn't
137-
nano.db.list(function(err, body) {
140+
function createDatabase(cloudant, node) {
141+
cloudant.db.list(function(err, all_dbs) {
138142
if (err) { node.error(err); }
139143
else {
140-
if (body && body.indexOf(node.database) < 0) {
141-
nano.db.create(node.database, function(err, body) {
142-
if (err) { node.error(err); }
143-
});
144+
if (all_dbs && all_dbs.indexOf(node.database) < 0) {
145+
cloudant.db.create(node.database);
144146
}
145147
}
146148
});
149+
}
147150

148-
node.on("input", function(msg) {
149-
if (node.operation === "insert") {
150-
var msg = node.payonly ? msg.payload : msg;
151-
var root = node.payonly ? "payload" : "msg";
152-
var doc = parseMessage(msg, root);
151+
function handleMessage(cloudant, node, msg) {
152+
if (node.operation === "insert") {
153+
var msg = node.payonly ? msg.payload : msg;
154+
var root = node.payonly ? "payload" : "msg";
155+
var doc = parseMessage(msg, root);
153156

154-
insertDocument(doc, db, MAX_ATTEMPTS, function(err, body) {
157+
insertDocument(cloudant, node, doc, MAX_ATTEMPTS, function(err, body) {
158+
if (err) { node.error(err); }
159+
});
160+
}
161+
else if (node.operation === "delete") {
162+
var doc = parseMessage(msg.payload || msg, "");
163+
164+
if ("_rev" in doc && "_id" in doc) {
165+
var db = cloudant.use(node.database);
166+
db.destroy(doc._id, doc._rev, function(err, body) {
155167
if (err) { node.error(err); }
156168
});
169+
} else {
170+
node.error("_rev and _id are required to delete a document");
157171
}
158-
else if (node.operation === "delete") {
159-
var doc = parseMessage(msg.payload || msg, "");
160-
161-
if ("_rev" in doc && "_id" in doc) {
162-
db.destroy(doc._id, doc._rev, function(err, body) {
163-
if (err) { node.error(err); }
164-
});
165-
} else {
166-
node.error("_rev and _id are required to delete a document");
167-
}
168-
}
169-
});
170-
171-
} else {
172-
this.error("missing cloudant configuration");
172+
}
173173
}
174174

175175
function parseMessage(msg, root) {
@@ -194,12 +194,13 @@ module.exports = function(RED) {
194194
// beforehand. If the database doesn't exist, it will create one
195195
// with the name specified in +db+. To prevent loops, it only tries
196196
// +attempts+ number of times.
197-
function insertDocument(doc, db, attempts, callback) {
197+
function insertDocument(cloudant, node, doc, attempts, callback) {
198+
var db = cloudant.use(node.database);
198199
db.insert(doc, function(err, body) {
199200
if (err && err.status_code === 404 && attempts > 0) {
200201
// status_code 404 means the database was not found
201-
return nano.db.create(db.config.db, function() {
202-
insertDocument(doc, db, attempts-1, callback);
202+
return cloudant.db.create(db.config.db, function() {
203+
insertDocument(cloudant, node, doc, attempts-1, callback);
203204
});
204205
}
205206

@@ -212,60 +213,116 @@ module.exports = function(RED) {
212213
function CloudantInNode(n) {
213214
RED.nodes.createNode(this,n);
214215

215-
this.cloudant = n.cloudant;
216-
this.url = _getUrl(this, n);
217-
this.database = n.database;
218-
this.search = n.search;
219-
this.design = n.design;
220-
this.index = n.index;
221-
this.payloadIn = "";
222-
223-
if (this.url) {
224-
var node = this;
216+
this.cloudantConfig = RED.nodes.getNode(n.cloudant);
217+
this.database = n.database;
218+
this.search = n.search;
219+
this.design = n.design;
220+
this.index = n.index;
221+
this.inputId = "";
222+
223+
var node = this;
224+
var credentials = {
225+
account: node.cloudantConfig.credentials.user,
226+
password: node.cloudantConfig.credentials.password
227+
};
228+
229+
Cloudant(credentials, function(err, cloudant) {
230+
if (err) { node.error(err); }
231+
else {
232+
node.on("input", function(msg) {
233+
var db = cloudant.use(node.database);
234+
var options = (typeof msg.payload === "object") ? msg.payload : {};
235+
236+
if (node.search === "_id_") {
237+
var id = getDocumentId(msg.payload);
238+
node.inputId = id;
239+
240+
db.get(id, function(err, body) {
241+
sendDocumentOnPayload(err, body, msg);
242+
});
243+
}
244+
else if (node.search === "_idx_") {
245+
options.query = options.query || options.q || formatSearchQuery(msg.payload);
246+
options.include_docs = options.include_docs || true;
247+
options.limit = options.limit || 200;
225248

226-
var nano = require('nano')(node.url);
227-
var db = nano.use(node.database);
249+
db.search(node.design, node.index, options, function(err, body) {
250+
sendDocumentOnPayload(err, body, msg);
251+
});
252+
}
253+
else if (node.search === "_all_") {
254+
options.include_docs = options.include_docs || true;
228255

229-
node.on("input", function(msg) {
230-
node.payloadIn = msg.payload;
256+
db.list(options, function(err, body) {
257+
sendDocumentOnPayload(err, body, msg);
258+
});
259+
}
260+
});
261+
}
262+
});
231263

232-
if (node.search === "_id_") {
233-
var id = msg.payload;
234-
db.get(id, function(err, body) {
235-
sendDocumentOnPayload(err, body, msg);
236-
});
264+
function getDocumentId(payload) {
265+
if (typeof payload === "object") {
266+
if ("_id" in payload || "id" in payload) {
267+
return payload.id || payload._id;
237268
}
238-
else if (node.search === "_idx_") {
239-
var query = { q: msg.payload, limit: 200 };
240-
db.search(node.design, node.index, query, function(err, body) {
241-
sendDocumentOnPayload(err, body, msg);
242-
});
269+
}
270+
271+
return payload;
272+
}
273+
274+
function formatSearchQuery(query) {
275+
if (typeof query === "object") {
276+
// useful when passing the query on HTTP params
277+
if ("q" in query) { return query.q; }
278+
279+
var queryString = "";
280+
for (var key in query) {
281+
queryString += key + ":" + query[key] + " ";
243282
}
244-
});
283+
284+
return queryString.trim();
285+
}
286+
return query;
245287
}
246288

247289
function sendDocumentOnPayload(err, body, msg) {
248290
if (!err) {
249-
msg.payload = body;
250-
} else {
291+
msg.cloudant = body;
292+
293+
if ("rows" in body) {
294+
msg.payload = body.rows.
295+
map(function(el) {
296+
if (el.doc._id.indexOf("_design/") < 0) {
297+
return el.doc;
298+
}
299+
}).
300+
filter(function(el) {
301+
return el !== null && el !== undefined;
302+
});
303+
} else {
304+
msg.payload = body;
305+
}
306+
}
307+
else {
251308
msg.payload = null;
252309

253310
if (err.description === "missing") {
254-
node.warn("Document '" + node.payloadIn+ "' not found in database '" +
311+
node.warn("Document '" + node.inputId + "' not found in database '" +
255312
node.database + "'.");
256313
} else {
257314
node.error(err.reason);
258315
}
259316
}
260-
317+
261318
node.send(msg);
262319
}
263320
}
264321
RED.nodes.registerType("cloudant in", CloudantInNode);
265322

266323
function _getUrl(node, n) {
267324
if (n.service == "_ext_") {
268-
var cloudantConfig = RED.nodes.getNode(node.cloudant);
325+
var cloudantConfig = RED.nodes.getNode(node.cloudantConfig);
269326
if (cloudantConfig) {
270327
return cloudantConfig.url;
271328
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"version" : "0.2.9",
44
"description" : "A Node-RED node to access a Cloudant database on Bluemix",
55
"dependencies" : {
6-
"cfenv": "1.0.0",
7-
"nano" : "5.10.0"
6+
"cfenv" : "1.0.0",
7+
"cloudant": "1.0.0-beta3"
88
},
99
"repository" : {
1010
"type" : "git",

0 commit comments

Comments
 (0)