@@ -90,24 +90,23 @@ module.exports = function(RED) {
9090 function CloudantNode ( n ) {
9191 RED . nodes . createNode ( this , n ) ;
9292
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 ( '.' ) ) ;
95104
96105 var credentials = RED . nodes . getCredentials ( n . id ) ;
97106 if ( credentials ) {
98107 this . username = credentials . username ;
99108 this . password = credentials . password ;
100109 }
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 ;
111110 }
112111 RED . nodes . registerType ( "cloudant" , CloudantNode ) ;
113112
@@ -116,17 +115,18 @@ module.exports = function(RED) {
116115
117116 this . operation = n . operation ;
118117 this . payonly = n . payonly || false ;
119- this . database = n . database ;
118+ this . database = _cleanDatabaseName ( n . database , this ) ;
120119 this . cloudantConfig = _getCloudantConfig ( n ) ;
121120
122121 var node = this ;
123122 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
126126 } ;
127127
128128 Cloudant ( credentials , function ( err , cloudant ) {
129- if ( err ) { node . error ( err ) ; }
129+ if ( err ) { node . error ( err . description , err ) ; }
130130 else {
131131 // check if the database exists and create it if it doesn't
132132 createDatabase ( cloudant , node ) ;
@@ -139,10 +139,24 @@ module.exports = function(RED) {
139139
140140 function createDatabase ( cloudant , node ) {
141141 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+ }
143150 else {
144151 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+ } ) ;
146160 }
147161 }
148162 } ) ;
@@ -155,7 +169,12 @@ module.exports = function(RED) {
155169 var doc = parseMessage ( msg , root ) ;
156170
157171 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+ }
159178 } ) ;
160179 }
161180 else if ( node . operation === "delete" ) {
@@ -164,10 +183,16 @@ module.exports = function(RED) {
164183 if ( "_rev" in doc && "_id" in doc ) {
165184 var db = cloudant . use ( node . database ) ;
166185 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+ }
168192 } ) ;
169193 } 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 ) ;
171196 }
172197 }
173198 }
@@ -187,9 +212,36 @@ module.exports = function(RED) {
187212 msg = JSON . parse ( '{"' + root + '":"' + msg + '"}' ) ;
188213 }
189214 }
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+
190233 return msg ;
191234 }
192235
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+
193245 // Inserts a document +doc+ in a database +db+ that migh not exist
194246 // beforehand. If the database doesn't exist, it will create one
195247 // with the name specified in +db+. To prevent loops, it only tries
@@ -214,20 +266,21 @@ module.exports = function(RED) {
214266 RED . nodes . createNode ( this , n ) ;
215267
216268 this . cloudantConfig = _getCloudantConfig ( n ) ;
217- this . database = n . database ;
269+ this . database = _cleanDatabaseName ( n . database , this ) ;
218270 this . search = n . search ;
219271 this . design = n . design ;
220272 this . index = n . index ;
221273 this . inputId = "" ;
222274
223275 var node = this ;
224276 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
227280 } ;
228281
229282 Cloudant ( credentials , function ( err , cloudant ) {
230- if ( err ) { node . error ( err ) ; }
283+ if ( err ) { node . error ( err . description , err ) ; }
231284 else {
232285 node . on ( "input" , function ( msg ) {
233286 var db = cloudant . use ( node . database ) ;
@@ -245,7 +298,7 @@ module.exports = function(RED) {
245298 options . query = options . query || options . q || formatSearchQuery ( msg . payload ) ;
246299 options . include_docs = options . include_docs || true ;
247300 options . limit = options . limit || 200 ;
248-
301+
249302 if ( options . sort ) {
250303 options . sort = JSON . stringify ( options . sort ) ;
251304 }
@@ -312,10 +365,13 @@ module.exports = function(RED) {
312365 msg . payload = null ;
313366
314367 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+ ) ;
317373 } else {
318- node . error ( err . reason ) ;
374+ node . error ( err . description , err ) ;
319375 }
320376 }
321377
@@ -324,11 +380,42 @@ module.exports = function(RED) {
324380 }
325381 RED . nodes . registerType ( "cloudant in" , CloudantInNode ) ;
326382
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
327385 function _getCloudantConfig ( n ) {
328386 if ( n . service === "_ext_" ) {
329387 return RED . nodes . getNode ( n . cloudant ) ;
388+
330389 } 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 + "'." ) ;
332417 }
418+
419+ return newDatabase ;
333420 }
334421} ;
0 commit comments