@@ -90,24 +90,23 @@ module.exports = function(RED) {
90
90
function CloudantNode ( n ) {
91
91
RED . nodes . createNode ( this , n ) ;
92
92
93
- this . name = n . name ;
94
- this . hostname = n . hostname ;
93
+ this . name = n . name ;
94
+ this . host = n . host ;
95
+
96
+ // remove unnecessary parts from host value
97
+ var parsedUrl = url . parse ( this . host ) ;
98
+ if ( parsedUrl . host ) {
99
+ this . host = parsedUrl . host ;
100
+ }
101
+
102
+ // extract only the account name
103
+ this . account = this . host . substring ( 0 , this . host . indexOf ( '.' ) ) ;
95
104
96
105
var credentials = RED . nodes . getCredentials ( n . id ) ;
97
106
if ( credentials ) {
98
107
this . username = credentials . username ;
99
108
this . password = credentials . password ;
100
109
}
101
-
102
- var parsedUrl = url . parse ( this . hostname ) ;
103
- var authUrl = parsedUrl . protocol + '//' ;
104
-
105
- if ( this . username && this . password ) {
106
- authUrl += this . username + ":" + encodeURIComponent ( this . password ) + "@" ;
107
- }
108
- authUrl += parsedUrl . hostname ;
109
-
110
- this . url = authUrl ;
111
110
}
112
111
RED . nodes . registerType ( "cloudant" , CloudantNode ) ;
113
112
@@ -116,17 +115,18 @@ module.exports = function(RED) {
116
115
117
116
this . operation = n . operation ;
118
117
this . payonly = n . payonly || false ;
119
- this . database = n . database ;
118
+ this . database = _cleanDatabaseName ( n . database , this ) ;
120
119
this . cloudantConfig = _getCloudantConfig ( n ) ;
121
120
122
121
var node = this ;
123
122
var credentials = {
124
- account : node . cloudantConfig . credentials . username ,
125
- password : node . cloudantConfig . credentials . password
123
+ account : node . cloudantConfig . account ,
124
+ key : node . cloudantConfig . username ,
125
+ password : node . cloudantConfig . password
126
126
} ;
127
127
128
128
Cloudant ( credentials , function ( err , cloudant ) {
129
- if ( err ) { node . error ( err ) ; }
129
+ if ( err ) { node . error ( err . description , err ) ; }
130
130
else {
131
131
// check if the database exists and create it if it doesn't
132
132
createDatabase ( cloudant , node ) ;
@@ -139,10 +139,24 @@ module.exports = function(RED) {
139
139
140
140
function createDatabase ( cloudant , node ) {
141
141
cloudant . db . list ( function ( err , all_dbs ) {
142
- if ( err ) { node . error ( err ) ; }
142
+ if ( err ) {
143
+ if ( err . error !== 'forbidden' ) {
144
+ // if err.error is 'forbidden' then we are using an api
145
+ // key, so we can assume the database already exists
146
+ return ;
147
+ }
148
+ node . error ( "Failed to list databases: " + err . description , err ) ;
149
+ }
143
150
else {
144
151
if ( all_dbs && all_dbs . indexOf ( node . database ) < 0 ) {
145
- cloudant . db . create ( node . database ) ;
152
+ cloudant . db . create ( node . database , function ( err , body ) {
153
+ if ( err ) {
154
+ node . error (
155
+ "Failed to create database: " + err . description ,
156
+ err
157
+ ) ;
158
+ }
159
+ } ) ;
146
160
}
147
161
}
148
162
} ) ;
@@ -155,7 +169,12 @@ module.exports = function(RED) {
155
169
var doc = parseMessage ( msg , root ) ;
156
170
157
171
insertDocument ( cloudant , node , doc , MAX_ATTEMPTS , function ( err , body ) {
158
- if ( err ) { node . error ( err ) ; }
172
+ if ( err ) {
173
+ node . error (
174
+ "Failed to insert document: " + err . description ,
175
+ err
176
+ ) ;
177
+ }
159
178
} ) ;
160
179
}
161
180
else if ( node . operation === "delete" ) {
@@ -164,10 +183,16 @@ module.exports = function(RED) {
164
183
if ( "_rev" in doc && "_id" in doc ) {
165
184
var db = cloudant . use ( node . database ) ;
166
185
db . destroy ( doc . _id , doc . _rev , function ( err , body ) {
167
- if ( err ) { node . error ( err ) ; }
186
+ if ( err ) {
187
+ node . error (
188
+ "Failed to delete document: " + err . description ,
189
+ err
190
+ ) ;
191
+ }
168
192
} ) ;
169
193
} else {
170
- node . error ( "_rev and _id are required to delete a document" ) ;
194
+ var err = new Error ( "_id and _rev are required to delete a document" ) ;
195
+ node . error ( err . message , err ) ;
171
196
}
172
197
}
173
198
}
@@ -187,9 +212,36 @@ module.exports = function(RED) {
187
212
msg = JSON . parse ( '{"' + root + '":"' + msg + '"}' ) ;
188
213
}
189
214
}
215
+ return cleanMessage ( msg ) ;
216
+ }
217
+
218
+ // fix field values that start with _
219
+ // https://wiki.apache.org/couchdb/HTTP_Document_API#Special_Fields
220
+ function cleanMessage ( msg ) {
221
+ for ( var key in msg ) {
222
+ if ( msg . hasOwnProperty ( key ) && ! isFieldNameValid ( key ) ) {
223
+ // remove _ from the start of the field name
224
+ var newKey = key . substring ( 1 , msg . length ) ;
225
+
226
+ msg [ newKey ] = msg [ key ] ;
227
+ delete msg [ key ] ;
228
+
229
+ node . warn ( "Property '" + key + "' renamed to '" + newKey + "'." ) ;
230
+ }
231
+ }
232
+
190
233
return msg ;
191
234
}
192
235
236
+ function isFieldNameValid ( key ) {
237
+ var allowedWords = [
238
+ '_id' , '_rev' , '_attachments' , '_deleted' , '_revisions' ,
239
+ '_revs_info' , '_conflicts' , '_deleted_conflicts' , '_local_seq'
240
+ ] ;
241
+
242
+ return key [ 0 ] !== '_' || allowedWords . indexOf ( key ) >= 0 ;
243
+ }
244
+
193
245
// Inserts a document +doc+ in a database +db+ that migh not exist
194
246
// beforehand. If the database doesn't exist, it will create one
195
247
// with the name specified in +db+. To prevent loops, it only tries
@@ -214,20 +266,21 @@ module.exports = function(RED) {
214
266
RED . nodes . createNode ( this , n ) ;
215
267
216
268
this . cloudantConfig = _getCloudantConfig ( n ) ;
217
- this . database = n . database ;
269
+ this . database = _cleanDatabaseName ( n . database , this ) ;
218
270
this . search = n . search ;
219
271
this . design = n . design ;
220
272
this . index = n . index ;
221
273
this . inputId = "" ;
222
274
223
275
var node = this ;
224
276
var credentials = {
225
- account : node . cloudantConfig . credentials . username ,
226
- password : node . cloudantConfig . credentials . password
277
+ account : node . cloudantConfig . account ,
278
+ key : node . cloudantConfig . username ,
279
+ password : node . cloudantConfig . password
227
280
} ;
228
281
229
282
Cloudant ( credentials , function ( err , cloudant ) {
230
- if ( err ) { node . error ( err ) ; }
283
+ if ( err ) { node . error ( err . description , err ) ; }
231
284
else {
232
285
node . on ( "input" , function ( msg ) {
233
286
var db = cloudant . use ( node . database ) ;
@@ -245,7 +298,7 @@ module.exports = function(RED) {
245
298
options . query = options . query || options . q || formatSearchQuery ( msg . payload ) ;
246
299
options . include_docs = options . include_docs || true ;
247
300
options . limit = options . limit || 200 ;
248
-
301
+
249
302
if ( options . sort ) {
250
303
options . sort = JSON . stringify ( options . sort ) ;
251
304
}
@@ -312,10 +365,13 @@ module.exports = function(RED) {
312
365
msg . payload = null ;
313
366
314
367
if ( err . description === "missing" ) {
315
- node . warn ( "Document '" + node . inputId + "' not found in database '" +
316
- node . database + "'." ) ;
368
+ node . warn (
369
+ "Document '" + node . inputId +
370
+ "' not found in database '" + node . database + "'." ,
371
+ err
372
+ ) ;
317
373
} else {
318
- node . error ( err . reason ) ;
374
+ node . error ( err . description , err ) ;
319
375
}
320
376
}
321
377
@@ -324,11 +380,42 @@ module.exports = function(RED) {
324
380
}
325
381
RED . nodes . registerType ( "cloudant in" , CloudantInNode ) ;
326
382
383
+ // must return an object with, at least, values for account, username and
384
+ // password for the Cloudant service at the top-level of the object
327
385
function _getCloudantConfig ( n ) {
328
386
if ( n . service === "_ext_" ) {
329
387
return RED . nodes . getNode ( n . cloudant ) ;
388
+
330
389
} else if ( n . service !== "" ) {
331
- return appEnv . getService ( n . service ) ;
390
+ var service = appEnv . getService ( n . service ) ;
391
+ var cloudantConfig = { } ;
392
+
393
+ var host = service . credentials . host ;
394
+
395
+ cloudantConfig . username = service . credentials . username ;
396
+ cloudantConfig . password = service . credentials . password ;
397
+ cloudantConfig . account = host . substring ( 0 , host . indexOf ( '.' ) ) ;
398
+
399
+ return cloudantConfig ;
400
+ }
401
+ }
402
+
403
+ // remove invalid characters from the database name
404
+ // https://wiki.apache.org/couchdb/HTTP_database_API#Naming_and_Addressing
405
+ function _cleanDatabaseName ( database , node ) {
406
+ var newDatabase = database ;
407
+
408
+ // caps are not allowed
409
+ newDatabase = newDatabase . toLowerCase ( ) ;
410
+ // remove trailing underscore
411
+ newDatabase = newDatabase . replace ( / ^ _ / , '' ) ;
412
+ // remove spaces and slashed
413
+ newDatabase = newDatabase . replace ( / [ \s \\ / ] + / g, '-' ) ;
414
+
415
+ if ( newDatabase !== database ) {
416
+ node . warn ( "Database renamed as '" + newDatabase + "'." ) ;
332
417
}
418
+
419
+ return newDatabase ;
333
420
}
334
421
} ;
0 commit comments