Add Cloudant read and write actions (#62)
diff --git a/actions/database-actions/write-document.js b/actions/database-actions/write-document.js
new file mode 100755
index 0000000..b96b6af
--- /dev/null
+++ b/actions/database-actions/write-document.js
@@ -0,0 +1,122 @@
+function main(message) {
+
+ var cloudantOrError = getCloudantAccount(message);
+
+ if (typeof cloudantOrError !== 'object') {
+ return Promise.reject(cloudantOrError);
+ }
+
+ var cloudant = cloudantOrError;
+ var dbName = message.dbname;
+ var doc = message.doc;
+ var overwrite;
+
+ if (!dbName) {
+ return Promise.reject('dbname is required.');
+ }
+ if (!doc) {
+ return Promise.reject('doc is required.');
+ }
+
+ if (typeof message.doc === 'object') {
+ doc = message.doc;
+ } else if (typeof message.doc === 'string') {
+ try {
+ doc = JSON.parse(message.doc);
+ } catch (e) {
+ return Promise.reject('doc field cannot be parsed. Ensure it is valid JSON.');
+ }
+ } else {
+ return Promise.reject('doc field is ' + (typeof doc) + ' and should be an object or a JSON string.');
+ }
+
+
+ if (typeof message.overwrite === 'boolean') {
+ overwrite = message.overwrite;
+ } else if (typeof message.overwrite === 'string') {
+ overwrite = message.overwrite.trim().toLowerCase() === 'true';
+ } else {
+ overwrite = false;
+ }
+
+ var cloudantDb = cloudant.use(dbName);
+ return insertOrUpdate(cloudantDb, overwrite, doc);
+}
+
+/**
+ * If id defined and overwrite is true, checks if doc exists to retrieve version
+ * before insert. Else inserts.
+ */
+function insertOrUpdate(cloudantDb, overwrite, doc) {
+ if (doc._id) {
+ if (overwrite) {
+ return new Promise(function(resolve, reject) {
+ cloudantDb.get(doc._id, function(error, body) {
+ if (!error) {
+ doc._rev = body._rev;
+ insert(cloudantDb, doc)
+ .then(function (response) {
+ resolve(response);
+ })
+ .catch(function (err) {
+ reject(err);
+ });
+ } else {
+ console.error('error', error);
+ reject(error);
+ }
+ });
+ });
+ } else {
+ // May fail due to conflict.
+ return insert(cloudantDb, doc);
+ }
+ } else {
+ // There is no option of overwrite because id is not defined.
+ return insert(cloudantDb, doc);
+ }
+}
+
+/**
+ * Inserts updated document into database.
+ */
+function insert(cloudantDb, doc) {
+ return new Promise(function(resolve, reject) {
+ cloudantDb.insert(doc, function(error, response) {
+ if (!error) {
+ console.log('success', response);
+ resolve(response);
+ } else {
+ console.log('error', error);
+ reject(error);
+ }
+ });
+ });
+}
+
+function getCloudantAccount(message) {
+ // full cloudant URL - Cloudant NPM package has issues creating valid URLs
+ // when the username contains dashes (common in Bluemix scenarios)
+ var cloudantUrl;
+
+ if (message.url) {
+ // use bluemix binding
+ cloudantUrl = message.url;
+ } else {
+ if (!message.host) {
+ return 'cloudant account host is required.';
+ }
+ if (!message.username) {
+ return 'cloudant account username is required.';
+ }
+ if (!message.password) {
+ return 'cloudant account password is required.';
+ }
+
+ cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;
+ }
+
+ return require('cloudant')({
+ url: cloudantUrl
+ });
+}
diff --git a/installCatalog.sh b/installCatalog.sh
index 19bb768..e895f36 100755
--- a/installCatalog.sh
+++ b/installCatalog.sh
@@ -93,12 +93,24 @@
-a description 'Create document in database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"doc", "required":true, "description": "The JSON document to insert"}, {"name":"params", "required":false} ]' \
+$WSK_CLI -i --apihost "$APIHOST" action update --auth "$AUTH" cloudant/read \
+ "$PACKAGE_HOME/actions/database-actions/read-document.js" \
+ -a description 'Read document from database' \
+ -a parameters '[ {"name":"dbname", "required":true}, {"name":"id", "required":true, "description": "The Cloudant document id to fetch"}, {"name":"params", "required":false}]' \
+ -p id ''
+
$WSK_CLI -i --apihost "$APIHOST" action update --auth "$AUTH" cloudant/read-document \
"$PACKAGE_HOME/actions/database-actions/read-document.js" \
-a description 'Read document from database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true, "description": "The Cloudant document id to fetch"}, {"name":"params", "required":false}]' \
-p docid ''
+$WSK_CLI -i --apihost "$APIHOST" action update --auth "$AUTH" cloudant/write \
+ "$PACKAGE_HOME/actions/database-actions/write-document.js" \
+ -a description 'Write document in database' \
+ -a parameters '[ {"name":"dbname", "required":true}, {"name":"doc", "required":true} ]' \
+ -p doc '{}'
+
$WSK_CLI -i --apihost "$APIHOST" action update --auth "$AUTH" cloudant/update-document \
"$PACKAGE_HOME/actions/database-actions/update-document.js" \
-a description 'Update document in database' \
diff --git a/tests/src/catalog/cloudant/CloudantDatabaseActionsTests.scala b/tests/src/catalog/cloudant/CloudantDatabaseActionsTests.scala
index a248350..69b3875 100644
--- a/tests/src/catalog/cloudant/CloudantDatabaseActionsTests.scala
+++ b/tests/src/catalog/cloudant/CloudantDatabaseActionsTests.scala
@@ -124,6 +124,49 @@
}
}
+ it should """read cloudant document with read action""" in withAssetCleaner(wskprops) {
+ (wp, assetHelper) =>
+ implicit val wskprops = wp
+ val packageName = "dummyCloudantPackage"
+
+ try {
+ CloudantUtil.setUp(credential)
+
+ val packageGetResult = wsk.pkg.get("/whisk.system/cloudant")
+ println("Fetching cloudant package.")
+ packageGetResult.stdout should include("ok")
+
+ println("Creating cloudant package binding.")
+ assetHelper.withCleaner(wsk.pkg, packageName) {
+ (pkg, name) =>
+ pkg.bind("/whisk.system/cloudant", name,
+ Map("username" -> credential.user.toJson,
+ "password" -> credential.password.toJson,
+ "host" -> credential.host().toJson,
+ "dbname" -> credential.dbname.toJson))
+ }
+ //Create test doc
+ val doc = CloudantUtil.createDocParameterForWhisk.get("doc").getAsString
+ val response = CloudantUtil.createDocument(credential, doc)
+ response.get("ok").getAsString shouldBe "true"
+
+ println("Invoking the read action.")
+ withActivation(wsk.activation, wsk.action.invoke(s"${packageName}/read",
+ Map(
+ "docid" -> response.get("id").getAsString.toJson,
+ "params" -> JsObject("revs_info" -> JsBoolean(true))))) {
+ activation =>
+ activation.response.success shouldBe true
+ activation.response.result.get.fields.get("date") shouldBe defined
+ activation.response.result.get.fields.get("_rev") shouldBe defined
+ activation.response.result.get.fields.get("_revs_info") shouldBe defined
+ }
+ }
+ finally {
+ CloudantUtil.unsetUp(credential)
+ }
+ }
+
it should """read cloudant document with undefined docid""" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
implicit val wskprops = wp
@@ -160,6 +203,91 @@
}
}
+ it should """write cloudant document""" in withAssetCleaner(wskprops) {
+ (wp, assetHelper) =>
+ implicit val wskprops = wp
+ val packageName = "dummyCloudantPackage"
+
+ try {
+ CloudantUtil.setUp(credential)
+
+ val packageGetResult = wsk.pkg.get("/whisk.system/cloudant")
+ println("Fetching cloudant package.")
+ packageGetResult.stdout should include("ok")
+
+ println("Creating cloudant package binding.")
+ assetHelper.withCleaner(wsk.pkg, packageName) {
+ (pkg, name) =>
+ pkg.bind("/whisk.system/cloudant", name,
+ Map("username" -> credential.user.toJson,
+ "password" -> credential.password.toJson,
+ "host" -> credential.host().toJson,
+ "dbname" -> credential.dbname.toJson))
+ }
+
+ val doc = CloudantUtil.createDocParameterForWhisk().get("doc").getAsString
+ val docJSObj = doc.parseJson.asJsObject
+
+ println("Invoking the write action.")
+ withActivation(wsk.activation, wsk.action.invoke(s"${packageName}/write",
+ Map("doc" -> docJSObj))) {
+ activation =>
+ activation.response.success shouldBe true
+ activation.response.result.get.fields.get("id") shouldBe defined
+ }
+ val getResponse = CloudantUtil.getDocument(credential, "testId")
+ Some(JsString(getResponse.get("date").getAsString)) shouldBe docJSObj.fields.get("date")
+ }
+ finally {
+ CloudantUtil.unsetUp(credential)
+ }
+ }
+
+ it should """write cloudant document with overwrite""" in withAssetCleaner(wskprops) {
+ (wp, assetHelper) =>
+ implicit val wskprops = wp
+ val packageName = "dummyCloudantPackage"
+
+ try {
+ CloudantUtil.setUp(credential)
+
+ val packageGetResult = wsk.pkg.get("/whisk.system/cloudant")
+ println("Fetching cloudant package.")
+ packageGetResult.stdout should include("ok")
+
+ println("Creating cloudant package binding.")
+ assetHelper.withCleaner(wsk.pkg, packageName) {
+ (pkg, name) =>
+ pkg.bind("/whisk.system/cloudant", name,
+ Map("username" -> credential.user.toJson,
+ "password" -> credential.password.toJson,
+ "host" -> credential.host().toJson,
+ "dbname" -> credential.dbname.toJson))
+ }
+
+ //Create test doc
+ val doc = CloudantUtil.createDocParameterForWhisk.get("doc").getAsString
+ val response = CloudantUtil.createDocument(credential, doc)
+ response.get("ok").getAsString shouldBe "true"
+
+ val docJSObj = doc.parseJson.asJsObject
+
+ println("Invoking the write action.")
+ withActivation(wsk.activation, wsk.action.invoke(s"${packageName}/write",
+ Map("doc" -> docJSObj,
+ "overwrite" -> "true".toJson))) {
+ activation =>
+ activation.response.success shouldBe true
+ activation.response.result.get.fields.get("id") shouldBe defined
+ }
+ val getResponse = CloudantUtil.getDocument(credential, "testId")
+ Some(JsString(getResponse.get("date").getAsString)) shouldBe docJSObj.fields.get("date")
+ }
+ finally {
+ CloudantUtil.unsetUp(credential)
+ }
+ }
+
it should """update cloudant document""" in withAssetCleaner(wskprops) {
(wp, assetHelper) =>
implicit val wskprops = wp