Skip to content

Commit 0319840

Browse files
committed
Merge branch 'develop'
* develop: update package.json create database if it does't exist when inserting document
2 parents 924b9a9 + c637074 commit 0319840

File tree

3 files changed

+181
-163
lines changed

3 files changed

+181
-163
lines changed

77-cloudant-cf.js

Lines changed: 173 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -13,189 +13,211 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
**/
16-
var url = require('url');
17-
var querystring = require('querystring');
18-
var cfEnv = require("cf-env");
19-
var RED = require(process.env.NODE_RED_HOME + "/red/red");
20-
21-
var cfCore = cfEnv.getCore();
22-
var services = [];
23-
24-
// load the services bindded to this application
25-
for (var i in cfCore.services) {
26-
// filter the services to include only the Cloudant ones
27-
if (i.match(/^(cloudant)/i)) {
28-
services = services.concat(cfCore.services[i].map(function(v) {
29-
return { name: v.name, label: v.label };
30-
}));
31-
}
32-
}
33-
34-
//
35-
// HTTP endpoints that will be accessed from the HTML file
36-
//
37-
RED.httpAdmin.get('/cloudant/vcap', function(req,res) {
38-
res.send(JSON.stringify(services));
39-
});
40-
41-
// REMINDER: routes are order dependent
42-
RED.httpAdmin.get('/cloudant/:id', function(req,res) {
43-
var credentials = RED.nodes.getCredentials(req.params.id);
44-
45-
if (credentials) {
46-
res.send(JSON.stringify(
47-
{
48-
user: credentials.user,
49-
hasPassword: (credentials.password && credentials.password !== "")
50-
}
51-
));
52-
} else {
53-
res.send(JSON.stringify({}));
54-
}
55-
});
56-
57-
RED.httpAdmin.delete('/cloudant/:id', function(req,res) {
58-
RED.nodes.deleteCredentials(req.params.id);
59-
res.send(200);
60-
});
16+
module.exports = function(RED) {
17+
"use strict";
6118

62-
RED.httpAdmin.post('/cloudant/:id', function(req,res) {
63-
var body = "";
19+
var url = require('url');
20+
var querystring = require('querystring');
21+
var cfEnv = require("cf-env");
6422

65-
req.on('data', function(chunk) {
66-
body += chunk;
67-
});
23+
var MAX_ATTEMPTS = 3;
6824

69-
req.on('end', function() {
70-
var newCreds = querystring.parse(body);
71-
var credentials = RED.nodes.getCredentials(req.params.id) || {};
25+
var cfCore = cfEnv.getCore();
26+
var services = [];
7227

73-
if (newCreds.user == null || newCreds.user == "") {
74-
delete credentials.user;
75-
} else {
76-
credentials.user = newCreds.user;
28+
// load the services bindded to this application
29+
for (var i in cfCore.services) {
30+
// filter the services to include only the Cloudant ones
31+
if (i.match(/^(cloudant)/i)) {
32+
services = services.concat(cfCore.services[i].map(function(v) {
33+
return { name: v.name, label: v.label };
34+
}));
7735
}
36+
}
7837

79-
if (newCreds.password == "") {
80-
delete credentials.password;
38+
//
39+
// HTTP endpoints that will be accessed from the HTML file
40+
//
41+
RED.httpAdmin.get('/cloudant/vcap', function(req,res) {
42+
res.send(JSON.stringify(services));
43+
});
44+
45+
// REMINDER: routes are order dependent
46+
RED.httpAdmin.get('/cloudant/:id', function(req,res) {
47+
var credentials = RED.nodes.getCredentials(req.params.id);
48+
49+
if (credentials) {
50+
res.send(JSON.stringify(
51+
{
52+
user: credentials.user,
53+
hasPassword: (credentials.password && credentials.password !== "")
54+
}
55+
));
8156
} else {
82-
credentials.password = newCreds.password || credentials.password;
57+
res.send(JSON.stringify({}));
8358
}
59+
});
8460

85-
RED.nodes.addCredentials(req.params.id, credentials);
61+
RED.httpAdmin.delete('/cloudant/:id', function(req,res) {
62+
RED.nodes.deleteCredentials(req.params.id);
8663
res.send(200);
8764
});
88-
});
8965

90-
//
91-
// Create and register nodes
92-
//
93-
function CloudantNode(n) {
94-
RED.nodes.createNode(this, n);
66+
RED.httpAdmin.post('/cloudant/:id', function(req,res) {
67+
var body = "";
9568

96-
this.name = n.name;
97-
this.hostname = n.hostname;
69+
req.on('data', function(chunk) {
70+
body += chunk;
71+
});
9872

99-
var credentials = RED.nodes.getCredentials(n.id);
100-
if (credentials) {
101-
this.username = credentials.user;
102-
this.password = credentials.password;
103-
}
73+
req.on('end', function() {
74+
var newCreds = querystring.parse(body);
75+
var credentials = RED.nodes.getCredentials(req.params.id) || {};
10476

105-
var parsedUrl = url.parse(this.hostname);
106-
var authUrl = parsedUrl.protocol+'//';
77+
if (newCreds.user == null || newCreds.user == "") {
78+
delete credentials.user;
79+
} else {
80+
credentials.user = newCreds.user;
81+
}
10782

108-
if (this.username && this.password) {
109-
authUrl += this.username + ":" + encodeURIComponent(this.password) + "@";
110-
}
111-
authUrl += parsedUrl.hostname;
83+
if (newCreds.password == "") {
84+
delete credentials.password;
85+
} else {
86+
credentials.password = newCreds.password || credentials.password;
87+
}
11288

113-
this.url = authUrl;
114-
}
115-
RED.nodes.registerType("cloudant", CloudantNode);
89+
RED.nodes.addCredentials(req.params.id, credentials);
90+
res.send(200);
91+
});
92+
});
11693

117-
function CloudantOutNode(n) {
118-
RED.nodes.createNode(this,n);
94+
//
95+
// Create and register nodes
96+
//
97+
function CloudantNode(n) {
98+
RED.nodes.createNode(this, n);
11999

120-
this.operation = n.operation;
121-
this.payonly = n.payonly || false;
122-
this.database = n.database;
123-
this.cloudant = n.cloudant;
100+
this.name = n.name;
101+
this.hostname = n.hostname;
124102

125-
if (n.service == "_ext_") {
126-
var cloudantConfig = RED.nodes.getNode(this.cloudant);
127-
if (cloudantConfig) {
128-
this.url = cloudantConfig.url;
103+
var credentials = RED.nodes.getCredentials(n.id);
104+
if (credentials) {
105+
this.username = credentials.user;
106+
this.password = credentials.password;
129107
}
130-
}
131-
else if (n.service != "") {
132-
var cloudantConfig = cfEnv.getService(n.service);
133-
if (cloudantConfig) {
134-
this.url = cloudantConfig.credentials.url;
108+
109+
var parsedUrl = url.parse(this.hostname);
110+
var authUrl = parsedUrl.protocol+'//';
111+
112+
if (this.username && this.password) {
113+
authUrl += this.username + ":" + encodeURIComponent(this.password) + "@";
135114
}
115+
authUrl += parsedUrl.hostname;
116+
117+
this.url = authUrl;
136118
}
119+
RED.nodes.registerType("cloudant", CloudantNode);
120+
121+
function CloudantOutNode(n) {
122+
RED.nodes.createNode(this,n);
137123

138-
if (this.url) {
139-
var node = this;
140-
141-
var nano = require('nano')(this.url);
142-
var db = nano.use(node.database);
143-
144-
// check if the database exists and create it if it doesn't
145-
nano.db.list(function(err, body) {
146-
if (err) { node.error(err); }
147-
else {
148-
if (body && body.indexOf(node.database) < 0) {
149-
nano.db.create(node.database, function(err, body) {
150-
if (err) { node.error(err); }
151-
});
152-
}
153-
}
154-
});
155-
156-
node.on("input", function(msg) {
157-
if (node.operation === "insert") {
158-
var msg = node.payonly ? msg.payload : msg;
159-
var root = node.payonly ? "payload" : "msg";
160-
var doc = parseMessage(msg, root);
161-
162-
db.insert(doc, function(err, body) {
163-
if (err) { node.error(err); }
164-
});
124+
this.operation = n.operation;
125+
this.payonly = n.payonly || false;
126+
this.database = n.database;
127+
this.cloudant = n.cloudant;
128+
129+
if (n.service == "_ext_") {
130+
var cloudantConfig = RED.nodes.getNode(this.cloudant);
131+
if (cloudantConfig) {
132+
this.url = cloudantConfig.url;
133+
}
134+
}
135+
else if (n.service != "") {
136+
var cloudantConfig = cfEnv.getService(n.service);
137+
if (cloudantConfig) {
138+
this.url = cloudantConfig.credentials.url;
165139
}
166-
else if (node.operation === "delete") {
167-
var doc = parseMessage(msg.payload || msg, "");
168-
169-
if ("_rev" in doc && "_id" in doc) {
170-
db.destroy(doc._id, doc._rev, function(err, body) {
140+
}
141+
142+
if (this.url) {
143+
var node = this;
144+
145+
var nano = require('nano')(this.url);
146+
var db = nano.use(node.database);
147+
148+
// check if the database exists and create it if it doesn't
149+
nano.db.list(function(err, body) {
150+
if (err) { node.error(err); }
151+
else {
152+
if (body && body.indexOf(node.database) < 0) {
153+
nano.db.create(node.database, function(err, body) {
154+
if (err) { node.error(err); }
155+
});
156+
}
157+
}
158+
});
159+
160+
node.on("input", function(msg) {
161+
if (node.operation === "insert") {
162+
var msg = node.payonly ? msg.payload : msg;
163+
var root = node.payonly ? "payload" : "msg";
164+
var doc = parseMessage(msg, root);
165+
166+
insertDocument(doc, db, MAX_ATTEMPTS, function(err, body) {
171167
if (err) { node.error(err); }
172168
});
173-
} else {
174-
node.error("_rev and _id are required to delete a document");
175169
}
176-
}
177-
});
178-
179-
} else {
180-
this.error("missing cloudant configuration");
181-
}
170+
else if (node.operation === "delete") {
171+
var doc = parseMessage(msg.payload || msg, "");
172+
173+
if ("_rev" in doc && "_id" in doc) {
174+
db.destroy(doc._id, doc._rev, function(err, body) {
175+
if (err) { node.error(err); }
176+
});
177+
} else {
178+
node.error("_rev and _id are required to delete a document");
179+
}
180+
}
181+
});
182182

183-
function parseMessage(msg, root) {
184-
if (typeof msg !== "object") {
185-
try {
186-
msg = JSON.parse(msg);
183+
} else {
184+
this.error("missing cloudant configuration");
185+
}
187186

188-
// JSON.parse accepts numbers, so make sure that an
189-
// object is return, otherwise create a new one
190-
if (typeof msg !== "object") {
187+
function parseMessage(msg, root) {
188+
if (typeof msg !== "object") {
189+
try {
190+
msg = JSON.parse(msg);
191+
192+
// JSON.parse accepts numbers, so make sure that an
193+
// object is return, otherwise create a new one
194+
if (typeof msg !== "object") {
195+
msg = JSON.parse('{"' + root + '":"' + msg + '"}');
196+
}
197+
} catch (e) {
198+
// payload is not in JSON format
191199
msg = JSON.parse('{"' + root + '":"' + msg + '"}');
192200
}
193-
} catch (e) {
194-
// payload is not in JSON format
195-
msg = JSON.parse('{"' + root + '":"' + msg + '"}');
196201
}
202+
return msg;
197203
}
198-
return msg;
199-
}
204+
205+
// Inserts a document +doc+ in a database +db+ that migh not exist
206+
// beforehand. If the database doesn't exist, it will create one
207+
// with the name specified in +db+. To prevent loops, it only tries
208+
// +attempts+ number of times.
209+
function insertDocument(doc, db, attempts, callback) {
210+
db.insert(doc, function(err, body) {
211+
if (err && err.status_code === 404 && attempts > 0) {
212+
// status_code 404 means the database was not found
213+
return nano.db.create(db.config.db, function() {
214+
insertDocument(doc, db, attempts-1, callback);
215+
});
216+
}
217+
218+
callback(err, body);
219+
});
220+
}
221+
};
222+
RED.nodes.registerType("cloudant out", CloudantOutNode);
200223
};
201-
RED.nodes.registerType("cloudant out", CloudantOutNode);

README.md

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
node-red-node-cf-cloudant
22
=========================
3-
A [Node-RED](http://nodered.org) node to write (and read soon...) to a
4-
[Cloudant](http://cloudant.com) database that is integrated with
3+
A [Node-RED](http://nodered.org) node to `insert`, `update` and `delete` documents
4+
in a [Cloudant](http://cloudant.com) database that is integrated with
55
[IBM Bluemix](http://bluemix.net).
66

77
Install
88
-------
9-
Place these files inside your `nodes/` folder.
10-
11-
This node will soon be published to [npm](https://www.npmjs.org/).
9+
Install from [npm](http://npmjs.org)
10+
```
11+
npm install node-red-node-cf-cloudant
12+
```
1213

1314
Usage
1415
-----
1516
Allows basic access to a [Cloudant](http://cloudant.com) database. Currently
16-
it only have one node that supports `insert` and `delete`
17+
it only have one node that supports `insert`, `update` and `delete`
1718
operations.
1819

1920
To **insert** a new document into the database you have the option to store
@@ -22,8 +23,3 @@ in JSON format, it will be transformed before being stored.
2223

2324
For **update** and **delete**, you must pass the `_id` and the `_rev`as part
2425
of the input `msg` object.
25-
26-
Known issues
27-
------------
28-
* weird stuffs happen when you delete a database that is being used (redeploy
29-
the flow to recreate the databases)

0 commit comments

Comments
 (0)