まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:api

無料のグループウェアを SaaS で提供していたサイボウズ Live が 2019 年4月15日をもってサービスを終了する、というアナウンスがありました:
サイボウズLiveサービス終了のお知らせ

個人的にも仲間内サークル活動の中で使っていたりしていたもので、このニュース自体はとても残念なものでした。が、終了まで1年半も猶予を持ったアナウンスであり、また(上記リンクによると)今後データのエクスポート(CSVファイル?機能?)などを提供する予定もあるらしいです。データを他のサービスに移行するための準備期間としては充分にあるようにも感じました。

データ移行に関しては時間的な猶予もあるので、しばらくは謹製のデータエクスポートが出されるのを待ってもいいとは思っています。が、個人的/技術的な興味もあって自分でも PHP で作ってみました。詳しくは後述しますが、もしご利用になる場合、現時点ではまだ不完全なものだと理解の上で使ってください。なおこのツールではデータを XML ファイルでエクスポートします(掲示板データなど、改行情報を含むデータは CSV に向かないと判断したので):
https://github.com/dotnsf/cbl_export/blob/master/cbl_export.php


実際に使うには、まず PHP 実行環境が必要です(自分自身がテストした範囲では PHP 5.3.3 でも動きました、かなり古いバージョンでも大丈夫だと思います):
(例 CentOS の場合)
# yum install php php-mbstring php-xml php-pear

加えてプログラムの中で OAuth などの外部ライブラリモジュールを使うため、これらのモジュールをあらかじめインストールしておいてください:
(例 pear を使う場合)
# pear install Net_URL2
# pear install HTTP_Request2
# pear install HTTP_OAuth

またこのツールを使う準備としてサイボウズ Developer Center のアカウントが必要です。同アカウントをお持ちでない場合はリンク先の「デベロッパー登録」ボタンから申請してアカウントを作成してください:
2017102901


アカウント作成後にログインし、「Myアプリケーション」の一覧で「アプリケーションを登録する」を選択し、今から動かすアプリケーションの登録を行います:
2017102902


アプリケーションを登録する際のアプリケーション名などは任意に設定いただいていいのですが、「アプリケーションの種類」は「クライアント」、そして「アクセスレベル」は「レベルZ」を選択する点に注意してください(ここを間違えると正しく動かなくなります。なおアプリケーション種類をクライアントにしないとレベルZのアクセスレベルは選択できないはずです):
2017102903


こうしてアプリケーションを登録し、登録後の画面に表示される Consumer Key と、Consumer Secret をメモしておきます(他人に教えてはいけません)。またアプリケーションの種類とアクセスレベルが正しく登録されていることを確認してください:
20171026



ここまでの準備ができたところで、改めて今回作成したエクスポートアプリを使います。こちらのサイトから git clone するなどして cbl_export.php をコピーします:
https://github.com/dotnsf/cbl_export/blob/master/cbl_export.php


cbl_export.php をテキストエディタで開き、一部を編集します:
<?php
//. cbl_export.php
//. Referer: https://developer.cybozulive.com/doc/current/#id1
//. For XAuth(Ones of Level Z)
$consumer_key           = '(CybozuLive Developer Center Consumer Key)';
$consumer_secret        = '(CybozuLive Developer Center Consumer Secret)';
$xauth_access_token_url = 'https://api.cybozulive.com/oauth/token';
 
$params = array(
    'x_auth_username' =--> '(CybozuLive Email)',
    'x_auth_password' => '(CybozuLive Password)',
    'x_auth_mode'     => 'client_auth',
);

:
:

上記の青字部分を編集します。$consumer_key には上記サイボウズ Developer Center でのアプリケーション登録時に確認した Consumer Key を、$consumer_secret には同 Consumer Secret を代入します。また $params 'x_auth_username' には自分のサイボウズ Live ログイン時のメールアドレスを、そして 'x_auth_password' には同パスワードを指定します。


ここまでの準備ができたら php コマンドで cbl_export.php を実行します:
# php -f cbl_export.php

実行が成功するとカレントディレクトリに自分のメールアドレスと同じ名前のフォルダと、自分がサイボウズ Live で所属しているグループごとの ID(X:XXXXX みたいなフォーマットです)のフォルダが作成され、各データがそれぞれのフォルダ内にエクスポートされます。例えば1つのグループにしか所属していない場合であれば、自分のメールアドレスのフォルダが1つと、そのグループのIDのフォルダが1つ生成されます:
2017102904
(↑自分が実行した場合の結果サンプル)


そして個人のデータはメールアドレスのフォルダ内に、要素毎に XML ファイルでエクスポートされています(現在の仕様ではスケジュール、チャット、ToDo、コネクションのデータがエクスポートされます):
2017102905


またグループ ID のフォルダには各グループ毎のメンバー、掲示板、イベント、ToDo、ファイル共有の情報が XML ファイルでエクスポートされています:
2017102906


なお、このツールを開発するにあたり、サイボウズ Live API を使っています。その仕様はこちらです:
サイボウズLive データ API ドキュメント


(↓2017/Oct/29 時点での制限事項)
上記のように一応動くものが作れたつもりなのですが、2017/Oct/29 時点ではファイルのダウンロード機能が正しく動いていません。例えばサイボウズ Live のファイル共有機能や掲示板の中でファイルを添付しているケースは珍しくないと思っています。その「どんなファイルが添付されているか?」といった情報は取得できて、その情報は現在取得できるファイルの中にも正しくエクスポートできているのですが、肝心のファイルそのもののダウンロードができていません。具体的にはこの API の実行時にだけ想定外のエラーになってしまい、まだその理由や解決策が分からずにいます:
https://developer.cybozulive.com/doc/current/pub/fileDownload.html


この部分については私のミスなのか、API 側の問題なのかの判別ができず、現在は問い合わせ中です。分かり次第に対応するつもりです。
(↑2017/Oct/29 時点での制限事項)


(↓2017/Nov/06 追記)
サイボウズより返答あり。サイボウズ側では問題なく動いているとのこと。むむむ・・ (--;
(↑2017/Nov/06 追記)


添付ファイルのダウンロード以外についてはある程度動くようになっているつもりです。早めのデータ移行を検討していたり、移行先候補へのテストを早めに実行したい場合に活用ください。

なおサイボウズから謹製のエクスポートデータやエクスポートツールが提供された場合は、(きっと面倒なアカウント登録なども不要になると思うので)そちらの利用をオススメします。 (^^;


(↓2017/Dec/19 追記)
kintone エバンジェリストでもある長井様( @akvabit)からコメントをいただき、現在ダウンロード API は画像ファイルのみが対象となっていることがわかりました。つまり画像以外のファイルは現時点では API ではダウンロードできないようです。
併せて長井様からのコメントを参考に PHP ファイルを改良しました。現在は「画像ファイルについてはダウンロードする(画像以外のファイルは無視する)」ような仕様にした上で動いています。
(↑2017/Dec/19 追記)

(↓2017/Dec/24 追記)
ファイルダウンロード機能ですが、動いていたように思えたのですが、やはり正しく動いていませんでした。ロジックを間違えていたせいで気付かなかったのですが、元のエラーが取れていませんでした(画像ファイルを対象にしても同じエラーが発生しています)。
(↑2017/Dec/24 追記)

Node-RED Advent Calendar 2017 に参加しました(ちなみにアドベントカレンダーに参加するのは初めて)! 12月9日のネタは「Node-RED で(普通の)ウェブアプリケーションを作る」です。

Node-RED を早くから活用している方の多くは IoT 連携であったり、組み込み機械との連携のデータフローとして使っているケースが多いと思っています。でもそんなケースばかりではなく、自分自身は Node-RED でデータベースを読み書きする REST API だけを作ったり、その REST API を呼び出すフロントエンドまでも含めて Node-RED で作ることがあります。今日はその作り方をスクリーンショットを交えて紹介します。


【普通のウェブアプリケーション】
最初に、今回作成する「(普通の)ウェブアプリケーション」を定義します。ここでの「ウェブアプリケーション」とは「アプリケーションサーバーとデータベースサーバーから構成」されていて、「アプリケーションサーバーのユーザー画面を通じて、データの保存、読み込み、検索、・・といった処理ができる」ものとします:
2017111702


これを Node-RED で作ります。具体的にはアプリケーションサーバー部分を Node-RED として、外部のデータベースサーバーに対する読み書きを行う API を Node-RED 上に定義します。そしてそれらの API を使う UI となる HTML も Node-RED で作ることで、ウェブアプリケーションとしての機能を実現します。なお今回はデータベースサーバーに IBM Cloudant を使い、全ての機能を IBM Cloud 上に作成することにします:
2017111901


【環境準備】
というわけで、まず Node-RED を用意します。Node-RED 環境が既に手元にある方は読み飛ばしていただいてもいいのですが、この後 IBM Cloudant を利用するので IBM Cloud のアカウントを取得し、IBM Cloudant のサービスインスタンスを用意するようにしてください(まあそこまでやるなら Node-RED も IBM Cloud で作っちゃうのがいいと思います)。

Node-RED はローカルで作ってしまってもいいと思いますが、このあとデータベース連携を行うので、(IBM Bluemix 改め)IBM Cloud の Node-RED 環境を使うことにします。無料のライトプランで契約している場合は Internet of Things Platform Starter ボイラープレートからランタイムを作成してください:
2017111701


ランタイムが稼働するまでしばらく待ちます。このボイラープレートから作成すると Node-RED の(Node.js の)ランタイムに加えて、Cloudant データベースのサービスインスタンスも同時に使えるようになります:
2017111701


実際に Node-RED を使おうとすると、初回のみアクセス用の ID とパスワードの設定を求められます。推測しにくいものを指定して、設定してください:
2017111702


改めて作成したアプリケーションの URL を指定し、Node-RED の画面になったら "Go to your Node-RED flow editor" をクリックしてフローエディタ画面に移動します(この際に認証が求められるので、直前に設定した ID とパスワードを指定します):
2017111703


Node-RED フローエディタ画面が表示されました。IBM Cloud 環境では画面左のパレットリスト内にデータベースや IBM Watson など初めから多くのサービスノードが有効になっているのが便利です(この後実際に使います):
2017111704


なお、Node-RED 環境をローカルで(IBM Cloud を使わずに)用意した場合は別途データベースを用意する必要があります。以下では IBM Cloudant を使う前提で紹介するので、IBM Cloud から Cloudant サービスをインスタンス化してください:
2017112101


【API を作成】
ではここから「普通のウェブアプリケーション」を作っていきます。改めて今回作成するウェブアプリケーションの構成を確認しておきます:
2017111901


今回のアプリケーションでは Node-RED 上に用意する HTML 内のフォームで入力した値を Cloudant に格納し、また格納済みの値を Cloudant から取り出して一覧表示できるようにします。 ということは、今回作成する必要があるのは以下の3つです:
 (1) ユーザー画面の HTML ページ
 (2) (1) 画面で入力した値を Cloudant に保存する API
 (3) (2) で保存した値を Cloudant から一括で取り出す API

この (2) と (3) を最初に作ることにします。

最初に (2) の格納用 API を作ることにします。機能としては((1) で用意する)ユーザー画面で指定した値を受け取ってデータベース(IBM Cloudant)に格納する API です。なので、このような仕様の REST API を作ります:
メソッドパスデータ挙動
POST/myrecordCloudant に保存するデータ(JSON)送信したデータを Cloudant データベースに保存


ではこの API を Node-RED で作ります。Node-RED のキャンバスをクリアします(初期状態で何か配置されているものは全て消します(マウスで選んで DELETE キー))。そして、以下のように3つのノードを配置&接続します:
2017112102


一番左は HTTP Request ノードです(パレット上では "http" と表示されていて、右側だけにジョイントが設定されています)。このノードをダブルクリックして、以下のような属性を指定し、POST /myrecord として動くようにします。最後に「完了」をクリックします:
2017112103


水色のノードは IBM Cloudant out ノードです("cloudant" と表示され、左側にだけジョイントが設置されているものです)。HTTP Request ノードの payload の値を受け取って、そのまま IBM Cloudant に格納する、という目的のノードです。IBM Cloud のボイラープレートから Node-RED 環境を作成した場合はこの部分は自動的にバインド先のサービス名が代入されるはずなので特に変更は不要です。格納先のデータベース名は属性として指定する必要があるので、ダブルクリックしてデータベース名を指定します(以下の例では mydb という名前のデータベースを指定しています)。また msg.payload 内の値だけを格納してほしいので、"Only store msg.payload object?" にチェックを入れておきます:
2017112104


なお、IBM Cloud のボイラープレートを使わずに、IBM Cloudant のインスタンスを別途作った場合は、ここで IBM Cloudant に接続するための username や password も指定する必要があります。


右下にあるのは HTTP Response ノードです。HTTP Request とペアで設定する必要があります。HTTP Request ノードからは IBM Cloudant ノードと HTTP Response ノードに分岐しています。この結果この POST API では HTTP Request ノードに送信されたデータがそのまま HTTP Response として返されると同時に、同じ値が IBM Cloudant の指定データベース(mydb)内に格納される、という挙動になります。ここまでの設定で下のような見た目になります。これだけで (2) のデータ保存用 REST API が出来てしまいました。簡単ですね:
2017112105



では続けて (3) の、データベースから保存されている全レコードを取り出す REST API も追加します。(2) で格納したデータを全て取り出せるように、以下のような API を定義します:
メソッドパスデータ挙動
GET/myrecords(なし)Cloudant データベースから全データを取り出して JSON 配列で返す


先程と同様に、キャンバスに以下のように3つのノードを配置して接続します:
2017112106



一番左は HTTP Request ノードです。このノードをダブルクリックして、以下のような属性を指定し、GET /myrecords として動くようにします:
2017112107


真ん中にあるのは IBM Cloudant in ノードです("cloudant" と表示され、左右両方にジョイントが設置されているものです)。HTTP Request に対して、IBM Cloudant の指定データベースから全レコードを取り出して返すようにするため、ダブルクリックして以下のように属性を指定します(以下の例では mydb という名前のデータベースから全データを取り出す指定をしています):
2017112108


右側にあるのが HTTP Response ノードです。直前の IBM Cloudant in ノードで取得した値を HTTP レスポンスの結果として返します。これで (3) の REST API も完成です:
2017112101


【HTML を作成】
では最後にここまでに作った API を呼び出して使う HTML ページ(利用者向けページ)を作ります。最終的にはそれなりに凝ったものを作りますが、とりあえずは (2) の API の挙動を確認するためのページを作ってみます。キャンバスに以下のように3つのノードを配置して接続します:
2017112102


一番左は HTTP Request ノードです。このノードをダブルクリックして、以下のような属性を指定し、GET /home として動くように(ブラウザから /home にアクセスした時に以下の HTML が表示されるように)します:
2017112103


真ん中は template ノードです。ノードをダブルクリックして以下の HTML 構文を入力します:
2017112104


(テンプレートの入力内容)
<form method="post" action="/myrecord">
Country:<input type="text" name="country" value=""/><br/>
Capital:<input type="text" name="capital" value=""/><br/>
<input type="submit" value="Submit"/>
</form>

↑ country (国名)と capital (首都)を指定して送信ボタンをクリックすると POST /myrecord を呼び出して保存する、という HTML です。


ここまでの作業で3本のフローができました。この状態で右上の「デプロイ」をクリックしてデプロイします:
2017112101


【動作確認】
まずデータを作成する前に IBM Cloud のダッシュボードから、IBM Cloudant のサービスインスタンスを選び、"LAUNCH" ボタンから Cloudant のダッシュボード画面を表示します:
2017112101


IBM Cloudant のダッシュボード画面の左ペインの上から2番目のデータベースアイコンを選択し、現在の Cloudant データベースの一覧を確認します(nodered というデータベースが1つだけ存在していて、この時点では mydb データベースも、その中身のドキュメントも存在していません):
2017112102


ではデータを格納する前に、格納先である mydb データベースを作っておきます。画面右上の "Create Database" をクリックします:
2017112106


データベース作成のダイアログが表示されるので、作成するデータベースの名前(今回の場合は "mydb")を指定して "Create" ボタンをクリックします:
2017112107


すると mydb データベースが作成され、同時に画面は mydb データベースを表示するページに切り替わります(現時点では1つもドキュメントは入っていないので何も表示されません)。元のデータベース一覧に戻るにはデータベース一覧アイコンを再度クリックするか、画面左上の mydb の左にある "<" 印をクリックします:
2017112108


データベース一覧画面に戻りました。今度は nodered データベースに加えて、mydb データベースが追加されていること(そしてドキュメント数がゼロになっていること)が確認できます:
2017112109



改めて先程作成した HTML ページにウェブブラウザでアクセスします。上記では /home というパスに GET リクエストした際に表示される HTML を定義したので、ウェブブラウザで https://(Node-RED のサーバー名)/home にアクセスして、以下のような画面が表示されることを確認します:
2017112103


この Country に国名、Capital にその国の首都を入力して Submit ボタンをクリックします。たとえばこんな感じで:
2017112104


Submit ボタンをクリックすると(実際には POST /myrecord が実行されて)アドレスのパスが /myrecord に変わります。そして画面には POST /myrecord のフローで最後に定義した HTTP Response ノードから結果(送信データを同じもの)が返されます:
2017112105


同時にこのデータは Cloudant out ノードによって Cloudant の mydb データベースに格納されています。Cloudant のデータベース一覧ページをリロードすると、先程まで文書数がゼロだった mydb データベースに1件データが格納されていることが確認できます:
2017112104


具体的なデータの内容を確認するために mydb データベースを選択して中身を見てみましょう。All Documents を Table 状態で確認すると、一件のドキュメントが格納されていて、その capital は Tokyo、country は Japan となっていることが確認できます。期待通りにデータ保存 API が動いていることが確認できました:
2017112102


次にデータ一覧取得 API の挙動を確認します。その前に同様の手順を繰り返して、もう何件かデータを格納しておきます:
2017112103


とりあえず、以下のような5件のデータが入っている状態にしました。このデータを (3) の API で取り出します:
2017112105


ウェブブラウザのアドレス欄に https://(Node-RED のサーバー名)/myrecords と入力してアクセスします。すると (3) で作成した API が実行され、その結果が返ります。以下のように mydb データベース内の5件のドキュメントレコードが JSON 配列になって返されることが確認できます:
2017112106


API が正しく動くことを確認できたので、改めて HTML の見た目を改良します。(1) の(GET /home のフローの) template ノードの属性にて、HTML の内容を以下のように変更して、デプロイします:
<html>
<head>
<title>Countries & Capitals</title>
<script src='//code.jquery.com/jquery-2.2.4.min.js'></script>
<script>
$(function(){
  $('#myform').submit( function(){
    $.ajax({
      type: 'POST',
      url: '/myrecord',
      data: { country: $('#country').val(), capital: $('#capital').val() },
      success: function( data ){
        console.log( data );
        setTimeout( 'list()', 1000 );
      },
      error: function(){
        console.log( 'error' );
      }
    });
    return false;
  });
  list();
});
function list(){
  $('#list').html( '' );
  $.ajax({
    type: 'GET',
    url: '/myrecords',
    success: function( data ){
      if( data && data.length > 0 ){
        for( var i = 0; i < data.length; i ++ ){
          var doc = data[i];
          var tr = '<tr><td>' + doc.country + '</td><td>' + doc.capital + '</td></tr>';
          $('#list').append( tr );
        }
      }
    },
    error: function(){
      console.log( 'error' );
    }
  });
}
</script>
</head>
<body>
  <table border='1'>
  <thead>
  <tr><th>Country</th><th>Capital</th><tr>
  </thead>
  <tbody id='list'>
  </tbody>
</table>
<hr/>

<form method='POST' id='myform'>
  Country: <input type='text' id='country' name='country' value=''/><br/>
  Capital: <input type='text' id='capital' name="capital" value=''/><br/>
  <input type='submit' value='click'/>
</form>
</body>
</html>

具体的には jQuery で AJAX を使ってシングルページの中でデータの追加と一覧表示ができるようにしています。/home にアクセスしてロードすると、まず現在のデータベース内のレコード一覧が表示され、新しいデータを POST すると一覧が同一ページ内で更新されます:
2017112101


※完成形の Node-RED フロー定義をこちらに用意しました。クリップボード経由で読み込むことで再現できると思います:
https://raw.githubusercontent.com/dotnsf/samples/master/nodered_webapp.json




以前に PHP で同様のコードを作ったことがあった(知る人ぞ知る)のですが、訳あって Node.js で動かしたくなって移植に挑戦しました。

2017120600


Amazon の Product Advertising API はどちらかというと「AWS ではない Amazon の API」で、エンジニアには馴染みのない人がいるかもしれませんが、アフィリエイトのための商品情報を集める場合に使う API です。こちらで紹介している方法を使うなどして、事前にアクセスキーとアクセスシークレットを取得した上で利用します:
http://dotnsf.blog.jp/archives/1064227473.html


ここで取得したアクセスキーとアクセスシークレットを(7、8行目に)使い、以下のような Node.js コード(amazon.js)を作りました:
//. amazon.js
var crypto = require( 'crypto' );
var request = require( 'request' );
var urlencode = require( 'urlencode' );
var xml2js = require( 'xml2js' );

var aws_key = 'aws_key'; //. アクセスキー
var aws_secret = 'aws_secret'; //. アクセスシークレット

var node = '52905051'; //. スキンケアのカテゴリコード


var request_url = 'http://ecs.amazonaws.jp/onca/xml?';
var local_dt = new Date();
var dt = local_dt.toISOString();  //. GMT timestamp

//. パラメータ
var params = 'AWSAccessKeyId=' + aws_key + '&BrowseNode=' + node;
params += ( '&MaximumPrice=1000&MinimumPrice=1&Operation=ItemSearch&ResponseGroup=ItemAttributes%2CSmall%2CImages&SearchIndex=Beauty&Service=AWSECommerceService&Timestamp=' + urlencode( dt ) + '&Version=2009-01-06' );

//. 署名
var str = 'GET\necs.amazonaws.jp\n/onca/xml\n' + params;
var hash = crypto.createHmac( 'sha256', aws_secret );
hash.update( str );
var hashed_str = hash.digest( 'base64' );

request_url += ( params + '&Signature=' + urlencode( hashed_str ) );

var options = { url: request_url, headers: { 'Host': 'ecs.amazonaws.jp' }, method: 'GET' };

request( options, ( err, res, body ) => {
  if( err ){
    console.log( 'error: ' );
    console.log( err );
  }else{
    //. XML で送られてくる結果を JSON 化して解析
    xml2js.parseString( body, ( err0, xml ) => {
      if( err0 ){
        console.log( err0 );
      }else{
        if( xml && xml.ItemSearchResponse && xml.ItemSearchResponse.Items ){
          var Items = xml.ItemSearchResponse.Items[0];

          var totalpages = Items.TotalPages[0];
          for( var idx = 0; idx < Items.Item.length; idx ++ ){
            var Item = Items.Item[idx];

            var image_url = '', manufacturer = '', brand = '', title = '', listprice = '', ean = '', asin = '';

            try{
              image_url = Item.MediumImage[0].URL[0];
            }catch( e ){
            }
            try{
              manufacturer = Item.ItemAttributes[0].Manufacturer[0];
            }catch( e ){
            }
            try{
              brand = Item.ItemAttributes[0].Brand[0];
            }catch( e ){
            }
            try{
              title = Item.ItemAttributes[0].Title[0];
            }catch( e ){
            }
            try{
              listprice = Item.ItemAttributes[0].ListPrice[0].Amount[0];
            }catch( e ){
            }
            try{
              ean = Item.ItemAttributes[0].EAN[0];
            }catch( e ){
            }
            try{
              asin = Item.ASIN[0];
            }catch( e ){
            }

            if( listprice == '' ){
              listprice = 0;
            }

            console.log( 'ean = ' + ean + ', title = ' + title + ', image_url = ' + image_url + ', manufacturer = ' + manufacturer + ', brand = ' + brand + ', listprice = ' + listprice + ', asin = ' + asin );
          }
        }
      }
    });
  }
});

10行目に検索する商品のカテゴリーブラウズノードを指定します。上記のデフォルト状態では「スキンケア製品」を意味する '52905051' が指定されていますが、変更することもできます。その場合は以下のサイトからブラウズノード一覧をダウンロード&参照し、希望のカテゴリーのノード番号に書き換えて使ってください:
https://affiliate.amazon.co.jp/help/topic/t100


コードの内容はカテゴリや価格帯(今回の例では1円以上1000円未満)を含めた URL に署名付きのパラメータを付けてアクセスし、XML で取得した結果を JSON に変換して取り出す、というシンプルなものです。署名の方法について Node.js でのサンプルがなかったので、別の言語向けに書かれたものを移植して作ってみました。なお、この API では1回の実行で条件を満たす商品を最大10個同時に取得できます。今回のコードでは指定したカテゴリーの商品を10個だけ取得するようになっています(ページング非対応です)。

このコード(amazon.js)を node コマンドで実行すると、以下のように指定したカテゴリーの商品情報を取得して表示します:
$ node amazon.js

ean = 3253581244760, title = ロクシタン(L'OCCITANE) ボンメールソープ ヴァーベナ 100g, image_url = https://images-fe.ssl-images-amazon.com/images/I/41QyQaO75HL._SL160_.jpg, manufacturer = ロクシタンジャポン, brand = ロクシタン(L'OCCITANE), listprice = 864, asin = B00AJFBDKM
ean = 3253581244777, title = ロクシタン(L'OCCITANE) ボンメールソープ ラベンダー 100g, image_url = https://images-fe.ssl-images-amazon.com/images/I/412BXX-d86L._SL160_.jpg, manufacturer = ロクシタンジャポン, brand = ロクシタン(L'OCCITANE), listprice = 864, asin = B00BMW80M0
  :
  :

今回紹介した Amazon Product Advertising API は(特にアフィリエイトサイトの管理を自動化する場合などに)非常に有用なのですが、サイトトップによると「セルフサービスでのご利用を前提としており、Amazon での技術的なサポートは現在行っておりません」とのことです。ハードル高め:
2017120601


自分は「ねっぴ」(http://neppi.co/)の運用を通じて 2014 年くらいから本格的に使っていて、最新情報を追いかけながらとかいうわけではないのですが、ノウハウなども分かってきた所もあるので、こういった場を通じて(今回みたいな形で)少しずつコミュニティに還元できればと思っています。


これらの記事の続きです(シリーズとしては今回が最終回です):
Cloudant の便利な API (1) : バルクインサート
Cloudant の便利な API (2) : View Design Document
Cloudant の便利な API (3) : List Design Document

DBaaS である IBM Cloudant の便利で特徴的な API を紹介しています。前回までは Design Document の1つである View Design Document と List Design Document を紹介して、データベース内の特定の条件を満たすドキュメントデータだけを「ビュー」としてまとめ、かつそのビューの UI も格納ドキュメントの一部として定義する、という内容を紹介しました(47都道府県のドキュメントデータから海なし県だけを取り出して HTML の UI で表示する、というところまでを作りました):
2017100602

↑ https://username.cloudant.com/mydb/_design/nosea/_list/nosea/nosea にアクセスした結果(username は Cloudant インスタンスに接続するための username です)

最終回である今回は、この一覧からクリックした各ドキュメントも HTML で表示するための Show Design Document と、その API を紹介します。

前回のおさらいとして、このビューで表示される各ドキュメントをクリックすると、以下の URL に移動します(現時点ではエラーになります):
https://username.cloudant.com/mydb/_design/nosea/_show/nosea/(doc.id)

これは doc.id で示される id 値を持つドキュメントデータを nosea という Show Design Document の設計内容を使って表示する際の URL です(そして現時点では nosea という Show Design Document を定義していないためエラーになります)。

ではこの nosea Show Design Document を定義します。前回までに紹介した View Design Document と List Design Document を含む JSON の内容に赤字部分を加えて以下のようなドキュメント(nosea_show.json)を作ります:
{
 "language": "javascript",
 "views": {
  "nosea": {
   "map": "function(doc){ if( doc.code && [9,10,11,19,20,21,25,29].indexOf(doc.code) > -1 ){ emit( doc._id, {code:doc.code,prefecture:doc.prefecture,capital:doc.capital,lat:doc.lat,lng:doc.lng} ); } }"
  }
 },
 "lists": {
  "nosea": "function( head, row ){ start( { 'headers': { 'content-type': 'text/html' } } ); send( '<ul>' ); var row; while( row = getRow() ){  var url = '../../_show/nosea/';  send( ' <li><a href=\"' + url + row.id + '\">' + row.value.prefecture + '(' + row.value.capital + ')</a></li>' ); } send( '</ul>' );}"
 },
 "shows": {
  "nosea": "(function( doc, req ){ if( doc ){  var str = '<h2>' + doc.prefecture + '</h2><h3>' + doc.capital + '</h3><hr/>緯度: ' + doc.lat + '<br/>経度: ' + doc.lng;  return str; }else{  return 'empty'; }})"
 }
}

赤字部分が Show Desgin Document に相当する部分です。この中では同ファイル内の上位部分で定義された nosea ビューからクリックされた各ドキュメントが表示する際に実行される処理が記載されています。上記例では理解しやすさを優先して、ドキュメントの各属性(県名、県庁所在地名、緯度、経度)を単純に HTML で表示する内容にしています。

なお、この内容と同じファイル(nosea_show.json)をこちらからダウンロードできるよう用意しました:
https://raw.githubusercontent.com/dotnsf/samples/master/nosea_show.json


前回同様に、この JSON ファイルを指定して Design Document を更新します。まずは API で現在の Design Document を確認し、現在のリビジョンを確認します:
$ curl -u "username:password" -XGET "https://username.cloudant.com/mydb/_design/nosea"

{"_id":"_design/nosea","_rev":"YYYY..YYYY","language":"javascript", ... }

この例の場合の YYYY..YYYY 部分("_rev" の値)が現在のリビジョン ID なので、この値をダウンロードした nesea_show.json ファイルに追加します:
{
 "_rev": "YYYY..YYYY",
 "language": "javascript",
 "views": {
  "nosea": {
   "map": "function(doc){ if( doc.code && [9,10,11,19,20,21,25,29].indexOf(doc.code) > -1 ){ emit( doc._id, {code:doc.code,prefecture:doc.prefecture,capital:doc.capital,lat:doc.lat,lng:doc.lng} ); } }"
  }
 },
 "lists": {
  "nosea": "function( head, row ){ start( { 'headers': { 'content-type': 'text/html' } } ); send( '<ul>' ); var row; while( row = getRow() ){  var url = '../../_show/nosea/';  send( ' <li><a href=\"' + url + row.id + '\">' + row.value.prefecture + '(' + row.value.capital + ')</a></li>' ); } send( '</ul>' );}"
 },
 "shows": {
  "nosea": "(function( doc, req ){ if( doc ){  var str = '<h2>' + doc.prefecture + '</h2><h3>' + doc.capital + '</h3><hr/>緯度: ' + doc.lat + '<br/>経度: ' + doc.lng;  return str; }else{  return 'empty'; }})"
 }
}

これで更新用の nosea_show.json ファイルが完成しました。改めてこのファイルを指定して Design Document を更新します:
$ curl -u "username:password" -XPUT -H "Content-Type: application/json" "https://username.cloudant.com/mydb/_design/nosea" -d@nosea_show.json

{"ok":true, "id":_design/nosea", "rev":"ZZZZ..ZZZZ"}

これで Design Document が更新されました。改めてウェブブラウザで https://username.cloudant.com/mydb/_design/nosea/_list/nosea/nosea にアクセスすると、List Design Document で定義された内容に従って nosea ビューが表示されます(ここまでは前回と同様):
2017100602


そしていずれかのドキュメント(県名)をクリックすると、そのドキュメントデータが Show Design Document に定義された内容で表示されます。これで一覧から詳細情報まで表示するアプリケーションとして繋がりました!:
2017100604


先程は Design Document の理解のため比較的シンプルな Show Design Document を使いましたが、少し UI にも凝ったバージョンも用意しました:
https://raw.githubusercontent.com/dotnsf/samples/master/nosea_show2.json

こちらの JSON ファイルをダウンロードして、上記同様にリビジョン ID を調べて追加し、API で PUT すると、少しだけ凝った UI で海なし県の一覧と詳細画面を確認することができます(View Design Document は変更せずに、List Design Document と Show Design Document を変更したものです)。

nosea_show2.json を PUT した場合、ビューはテーブル形式で表示されます。県名または県庁所在地名がクリック可能になっています:
2017100605


クリックすると OpenStreetMap API を使って、その県庁所在地周辺の地図が表示されます。海なし県の海までの遠さを視覚的にも確認できる UI にしました(笑):
2017100606



4回に渡って IBM Cloudant の特徴的な Design Document とその API を中心に紹介してきました。以前にも触れましたが、IBM Cloudant のベースとなった CouchDB は IBM Notes/Domino と似た生まれであり、その設計思想などにも似た部分が多くあると感じています。私自身がノーツ大好きということもあってそんな Cloudant の特徴的な機能をまとめて紹介してみました。


これらの記事の続きです:
Cloudant の便利な API (1) : バルクインサート
Cloudant の便利な API (2) : View Design Document


DBaaS である IBM Cloudant の便利で特徴的な API を紹介しています。前回は Design Document の1つである View Design Document とその API を紹介して、データベース内の特定の条件を満たすドキュメントデータだけを「ビュー」としてまとめる方法を紹介しました。

今回紹介するのはやはり Design Document の1つで、「ビューの見た目を定義する」List Design Document です。前回の「ビュー」がドキュメント集合の条件を定義していたのに対して、今回の「リスト」は「ビューの見た目」を定義します。


一般的にデータベースを使ったウェブアプリケーションを作る場合、データベースサーバーにデータを格納した上で、そのデータを読み/書き/更新/検索して画面に表示する部分はアプリケーションサーバーに実装されることが多いです。しかし(HTTP をベースとした) REST API で動かす Cloudant は、その HTTP 部分を更に活用してデータの見た目に関わる部分までを定義・実装することができます。特に今回紹介する List Design Document ではデータの集合体であるビューの見た目を実装して格納します。個別の文書の見た目を実装する Show Design Document については次回紹介する予定です。


※余談ですが、この「見た目を定義する情報がドキュメントの一部として格納される」という点も「ノーツのビューやフォーム」に近い考え方や設計だと思っています。

このリスト(List)も Design Document の1つなので、ビューと同様に設計文書を用意します。前回紹介した View Design Document の JSON の内容に赤字部分を加えて以下のようなドキュメント(nosea_list.json)を作ります:

{
 "language": "javascript",
 "views": {
  "nosea": {
   "map": "function(doc){ if( 

doc.code && [9,10,11,19,20,21,25,29].indexOf(doc.code) > -1 ){ emit( doc._id, 

{code:doc.code,prefecture:doc.prefecture,capital:doc.capital} ); } }"
  }
 },
 "lists": {
  "nosea": "function( head, row ){ 

start( { 'headers': { 'content-type': 'text/html' } } ); send( '<ul>' ); var row; while( row = getRow() ){  var url = 

'../../_show/nosea/';  send( ' <li><a href=\"' + url + row.id + '\">' + row.value.prefecture + '(' + row.value.capital + ')

</a></li>' ); } send( '</ul>' );}"
 }
}

赤字部分が List Desgin Document に相当する部分です。この中では同ファイル内の上位部分で定義された nosea ビューを表示する際に実行される処理が記載されています。

なお、この内容と同じファイル(nosea_list.json)をこちらからダウンロードできるよう用意しました:
https://raw.githubusercontent.com/dotnsf/samples/master/nosea_list.json


赤字部分について簡単に内容を紹介すると、まず HTTP レスポンスヘッダとして 'Content-Type: text/html' を指定し、全体が HTML として返されるよう宣言しています。次に <ul> と </ul> の間が while でループ処理され、このビューに含まれる各文書(doc)の値を row として取り出します。そして
  <li><a href="../../_show/nosea/(doc.id)">県名(県庁所在地名)</a></li>
という <li> 要素を動的に作って <ul> と </ul> の間に挿入し、最後に全体を返しています。 要するに nosea ビューに含まれる海無し県の一覧を <ul> タグを使ってリスト表示している、というものです。

この JSON ファイルを指定して API を実行すれば List Design Document が作成される、、、のですが、もう1つだけ準備が必要です。今回作成する Design Document は前回作成した Design Document を上書きして保存する必要があります。そのため単にこのままのファイルを指定して API を実行しても(同一 ID への新規作成コマンドと解釈され)コンフリクトを起こしてしまい、更新できません。Cloudant の既存ドキュメントを更新する場合は既存ドキュメントのリビジョン ID を明示して実行する必要があるのでした。

というわけで、まずは API で現在の Design Document を確認し、現在のリビジョンを確認します。前回同様、コマンド内の username, password はそれぞれ Cloudant インスタンスに接続するための username, password で、実行結果を青字で表しています(以下同様):
$ curl -u "username:password" -XGET "https://username.cloudant.com/mydb/_design/nosea"

{"_id":"_design/nosea","_rev":"XXXX..XXXX","language":"javascript","views":{"nosea":{"map":"function(doc){ if( doc.code && [9,10,11,19,20,21,25,29].indexOf(doc.code) > -1 ){ emit( doc._id, {code:doc.code,prefecture:doc.prefecture,capital:doc.capital} ); } }"}}}

この例の場合の XXXX..XXXX 部分("_rev" の値)が現在のリビジョンIDです。nosea_list.json をテキストエディタで開いて、この値を使って以下のように書き換えます:
{
 "_rev": "xxxx..xxxx",
"language": "javascript", "views": { "nosea": { "map": "function(doc){ if( doc.code && [9,10,11,19,20,21,25,29].indexOf(doc.code) > -1 ){ emit( doc._id, {code:doc.code,prefecture:doc.prefecture,capital:doc.capital} ); } }" } }, "lists": { "nosea": "function( head, row ){ start( { 'headers': { 'content-type': 'text/html' } } ); send( '<ul>' ); var row; while( row = getRow() ){ var url = '../../_show/nosea/'; send( ' <li><a href=\"' + url + row.id + '\">' + row.value.prefecture + '(' + row.value.capital + ') </a></li>' ); } send( '</ul>' );}" } }

これで更新用の nosea_list.json ファイルが完成しました。改めてこのファイルを指定して Design Document を更新します:
$ curl -u "username:password" -XPUT -H "Content-Type: application/json" "https://username.cloudant.com/mydb/_design/nosea" -d@nosea_list.json

{"ok":true, "id":_design/nosea", "rev":"YYYY..YYYY"}

これで Design Document が更新されました。この時点でダッシュボードの nosea ビューを確認すると・・・一見、何も変わっていないようにみえます:
2017100601


しかしウェブブラウザで https://username.cloudant.com/mydb/_design/nosea/_list/nosea/nosea にアクセスすると、List Design Document で定義された内容に従って nosea ビューが表示されます。結果としてこのような画面が表示されます:
2017100602


これが Cloudant(CouchDB) の特徴ともいえる Design Document の機能です。通常であれば別途アプリケーションサーバーを用意した上で UI やロジックを実現するのですが、Cloudant の場合は Design Document を使うことで Cloudant の REST API(HTTP) の機能を使って UI を実現することができるようになるのでした。 ただ上のUIはシンプルすぎるので実用段階ではもう少しちゃんとしたほうがいいとは思います(苦笑)。

なお、この結果表示されている画面の各海なし都道府県へのリンクをクリックすると、以下のようなエラーになります:
2017100603


これはビューの UI とリンクまでは作成したのですが、各ドキュメントの内容(UI)を表示する Design Document についてはまだ作成していないために(リンク先が見当たらずに)エラーとなっているのでした。というわけで、次回は各ドキュメントの UI を定義する Show Design Document とその API について紹介し、各ドキュメントの内容まで表示できるようなアプリケーションを完成させる予定です。



このページのトップヘ