Data API 勉強会 Vol.1 2014.6.11 @ Co-Edo YUJI Takayama@Six Apart
Jun 27, 2015
Data API 勉強会 Vol.12014.6.11 @ Co-Edo
YUJI Takayama@Six Apart
About Me• 名前: 高山 裕司
• 会社: シックス・アパート
• 仕事: Movable Type 全般
• プロダクト・マネージャ
• リード・エンジニア
github.com/yuji
@yuji
Yuji Takayama
Agenda• Data APIってなに?
• 初めての Data API
• JavaScript ライブラリ
• サインイン ~ 記事の投稿
• こんな使い方も・・・
Data APIって何?
• Movable Type 6 から搭載
• Movable Type に保存されているデータを操る
• REST API
• MT のユーザー管理、認証機構を利用
• JavaScript のライブラリを提供
• プラグインで API 自体を拡張可能
• 公開サイトでも、管理サイトでも、別のアプリケーションとでも
• まだまだ発展途上
なぜ、Data API を リリースしたのか
• 様々なデバイスの登場
• ダイナミック・コンテンツ
• MT ≠ Perl
• ユーザーの役割に応じた画面作り
• “管理画面” は、管理する人が使う
• コンテンツの作成は、”管理画面”である必要はない
初めての Data API
Data API で出来ることCreate Read Update Delete
Entry ○ ○ ○ ○Comment ○ ○ ○ ○Trackback ○ ○ ○
User ○ ○Site(Blog, Website) ○
Category ○Site Statistics ○
Asset ○
Data API の使い方
http://localhost/mt/mt-data-api.cgi/v1/sites/1
エンドポイント と呼ばれる URL を呼び出す
Data API のスクリプトへのパス
Data API のバージョン
呼び出すメソッドとパラメータ
https://github.com/movabletype/Documentation/wiki/Quick-reference
呼び出し方で処理が変わるPOST GET PUT DELETE
Entry ○ ○ ○ ○Comment ○ ○ ○ ○Trackback ○ ○ ○
User ○ ○Site(Blog, Website) ○
Category ○Site Statistics ○
Asset ○
同じエンドポイントへのリクエストで変わる
結果は JSON 形式
{! "url" : "http://localhost/blog/20140609-1/",! "archiveUrl" : "http://localhost/blog/20140609-1/",! "name" : "First Website",! "class" : "website",! "id" : "1",! "description" : null!}
https://github.com/movabletype/Documentation/wiki/Quick-reference
• HTTP(s) を呼び出せれば呼び出し元はブラウザだろうが、アプリだろうが、コンソールだろうが、何でもいい
• 戻りは JSON 形式なので、JSON パーサがある方がラクと言えばラク
サインイン から 記事のポストまで
実践
ユーザー認証curl -i -d clientId=test -d username=takayama -d password=passwordhttp://localhost/cgi-bin/mt/mt-data-api.cgi/v1/authentication -X POST
リクエスト
応答HTTP/1.1 200 OKDate: Mon, 09 Jun 2014 07:01:38 GMTServer: Apache/2.2.26 (Unix) mod_ssl/2.2.26 OpenSSL/1.0.1e DAV/2 PHP/5.3.27X-content-type: nosniffCache-control: no-cacheTransfer-Encoding: chunkedContent-Type: application/json; charset=UTF-8X-Pad: avoid browser bug!{"sessionId":"vjqLFlFxEki5ElAHy9DyUe6k3jAuevXJyGdjXk8j","accessToken":"h8YOleAJoaBkNCJl3kmOgmlsocNo0J5LiwZljAV9","expiresIn":3600,"remember":false}
記事の投稿curl -i http://localhost/cgi-bin/mt/mt-data-api.cgi/v1/sites/1/entries -X POST-H "X-MT-Authorization: MTAuth accessToken=h8YOleAJoaBkNCJl3kmOgmlsocNo0J5LiwZljAV9”-d entry="{\"status\" : \"Publish\", \"title\" : \"This is test post\", \"body\" : \"Yeah\!\" }"
リクエスト
応答HTTP/1.1 200 OKDate: Mon, 09 Jun 2014 07:27:00 GMTServer: Apache/2.2.26 (Unix) mod_ssl/2.2.26 OpenSSL/1.0.1e DAV/2 PHP/5.3.27X-content-type: nosniffCache-control: no-cacheTransfer-Encoding: chunkedContent-Type: application/json; charset=UTF-8X-Pad: avoid browser bug!{"excerpt":"Yeah!...","date":"2014-06-09T16:27:01\u002b09:00","status":"Publish","updatable":true,"author":{"userpicUrl":null,"id":"1","displayName":"Yuji Takayama"},"comments":[],"allowComments":true,"permalink":"http://localhost/blog/20140609-1/2014/06/this-is-test-post.html","keywords":"","body":"Yeah!","trackbacks":[],"id":4,"allowTrackbacks":false,"modifiedDate":"2014-06-09T16:27:01\u002b09:00","trackbackCount":0,"blog":{"id":"1"},"categories":[],"tags":[],"commentCount":0,"assets":[],"basename":"this_is_test_post","createdDate":"2014-06-09T16:27:01\u002b09:00","class":"entry","title":"This is test post","pingsSentUrl":[],"more":"","customFields":[]}% /Users/takayama% curl -i http://localhost/cgi-bin/mt/mt-data-api.cgi/v1/sites/1/entries -X POST -H "X-MT-Authorization: MTAuth accessToken=h8YOleAJoaBkNCJl3kmOgmlsocNo0J5LiwZljAV9" -d entry="{\"status\" : \"Publish\", \"title\" : \"This is test post\", \"body\" : \"Yeah\!\" }"
まぁ、無理ですよね
(́・ω・`)
JavaScript ライブラリ
• REST API の呼び出しをラッピング
• シンプルな実装なので、他のライブラリと干渉しにくい
• 他の言語系も順次サポート予定
使い方
var api = new MT.DataAPI({ baseUrl: "https://your-‐host/mt/mt-‐data-‐api.cgi", clientId: "your-‐client-‐id" }); !api.listEntries(siteId, function(response) { if (response.error) { // Handle error return; } ! for (var i = 0; i < response.items.length; i++) { var entry = response.items[i]; // Render an entry } });
<script type=“text/javascript” src=“http://your-‐host/path/to/mt-‐static/data-‐api/v1/js/mt-‐data-‐api.min.js”></script>
var api = new MT.DataAPI({ baseUrl: "https://your-‐host/mt/mt-‐data-‐api.cgi", clientId: "your-‐client-‐id" }); !api.getToken(function(response) { if (response.error) { if (response.error.code === 401) { // You have not been authenticated yet. location.href = api.getAuthorizationUrl(location.href); } else { /* Handle error */ } } else { // You have been authenticated. api.listEntries(siteId, {status: 'Draft'}, function(response) { if (response.error) { /* Handle error */ return; } // Fetched a list of drafts. for (var i = 0; i < response.items.length; i++) { var entry = response.items[i]; // Render an entry } }); } });
サインイン から 記事のポストまで
実践
Movable Type Writer(仮)https://github.com/movabletype/MovableTypeWriter
• Movable Type powered by JavaScript
• 記事の投稿に特化した Chrome アプリ
• Data API + jQuery + Bootstrap3
• MIT でフォーク自由
使っているメソッド• authenticate(サインイン)
• getToken(トークン取得)
• listBlogForUser(権限を持っているサイトの一覧を取得)
• createEntry(記事の作成)
初期化 jQuery(document).ready( function() { getSettings().then( function(settings) { if (!settings || !settings.apipath) { return jQuery('#setting-‐panel-‐dialog').modal(); } ! api = new MT.DataAPI({ baseUrl: settings.apipath, clientId: "movabletype-‐writer" }); ! doSignIn(settings.username, settings.password) .then( loadSiteList );
.
.
.
初期化 jQuery(document).ready( function() { getSettings().then( function(settings) { if (!settings || !settings.apipath) { return jQuery('#setting-‐panel-‐dialog').modal(); } ! api = new MT.DataAPI({ baseUrl: settings.apipath, clientId: "movabletype-‐writer" }); ! doSignIn(settings.username, settings.password) .then( loadSiteList );
.
.
.ローカルストレージから、設定内容を読み込む。
設定されていなければ、設定用のダイアログを表示する
初期化 jQuery(document).ready( function() { getSettings().then( function(settings) { if (!settings || !settings.apipath) { return jQuery('#setting-‐panel-‐dialog').modal(); } ! api = new MT.DataAPI({ baseUrl: settings.apipath, clientId: "movabletype-‐writer" }); ! doSignIn(settings.username, settings.password) .then( loadSiteList );
.
.
.Data API のインスタンスを生成する baseUrl には Data API へのURL
clientId は任意の文字列
初期化 jQuery(document).ready( function() { getSettings().then( function(settings) { if (!settings || !settings.apipath) { return jQuery('#setting-‐panel-‐dialog').modal(); } ! api = new MT.DataAPI({ baseUrl: settings.apipath, clientId: "movabletype-‐writer" }); ! doSignIn(settings.username, settings.password) .then( loadSiteList );
.
.
.ユーザー認証を行う
その後、サイト一覧を更新する
サインイン var doSignIn = function(user, passwd) { var def = new jQuery.Deferred(); ! api.authenticate({ username: user, password: passwd }, function(response) { if (response.error) { var code = response.error.code; var msg; if (code === 404 ) { } else if (code === 401 ) { } else { } return def.reject(msg); } else { api.storeTokenData(response); return def.resolve(); } }); ! return def.promise(); };
サインイン var doSignIn = function(user, passwd) { var def = new jQuery.Deferred(); ! api.authenticate({ username: user, password: passwd }, function(response) { if (response.error) { var code = response.error.code; var msg; if (code === 404 ) { } else if (code === 401 ) { } else { } return def.reject(msg); } else { api.storeTokenData(response); return def.resolve(); } }); ! return def.promise(); };
認証用のエンドポイントを呼び出す。 認証エラーは、response.error の有無で判定
認証に成功したら、storeTokenData でセッションデータを保存
サイト一覧の取得 var loadSiteList = function() { var def = new jQuery.Deferred(); ! api.listBlogsForUser('me', function(response) { if (response.error) { var code = response.error.code; var msg; if (code === 404 ) { } else if (code === 403 ) { } else { } return def.reject(msg); } ! var $blogListBox = jQuery('#form-‐blog-‐list'); response.items.forEach( function(x, i) { $blogListBox.append($('<option>').html(x.name).val(x.id)); }); $blogListBox.removeAttr('disabled'); });
サイト一覧の取得 var loadSiteList = function() { var def = new jQuery.Deferred(); ! api.listBlogsForUser('me', function(response) { if (response.error) { var code = response.error.code; var msg; if (code === 404 ) { } else if (code === 403 ) { } else { } return def.reject(msg); } ! var $blogListBox = jQuery('#form-‐blog-‐list'); response.items.forEach( function(x, i) { $blogListBox.append($('<option>').html(x.name).val(x.id)); }); $blogListBox.removeAttr('disabled'); });
ユーザーが権限を持つサイトの一覧を取得する エラー 404: サイトが見つからない
エラー: 403: 認証エラー
サイト一覧の取得 var loadSiteList = function() { var def = new jQuery.Deferred(); ! api.listBlogsForUser('me', function(response) { if (response.error) { var code = response.error.code; var msg; if (code === 404 ) { } else if (code === 403 ) { } else { } return def.reject(msg); } ! var $blogListBox = jQuery('#form-‐blog-‐list'); response.items.forEach( function(x, i) { $blogListBox.append($('<option>').html(x.name).val(x.id)); }); $blogListBox.removeAttr('disabled'); });
セレクトボックスに読み込んだサイトの一覧を追加
記事の投稿 jQuery('#button-‐post').click( function() { var entry = {}; entry['title'] = jQuery('#entry-‐title').val(); entry['body'] = jQuery('#entry-‐body').code(); entry['status'] = 'Publish'; var siteId = jQuery('#form-‐blog-‐list option:selected').val(); api.getToken(function(response) { if (response.error) { } api.createEntry(siteId, entry, function(response) { if (response.error) { var code = response.error.code; var msg; if (code === 404 ) { } else if (code === 401 ) { } else if (code === 403 ) { } else { } } } } }
記事の投稿 jQuery('#button-‐post').click( function() { var entry = {}; entry['title'] = jQuery('#entry-‐title').val(); entry['body'] = jQuery('#entry-‐body').code(); entry['status'] = 'Publish'; var siteId = jQuery('#form-‐blog-‐list option:selected').val(); api.getToken(function(response) { if (response.error) { } api.createEntry(siteId, entry, function(response) { if (response.error) { var code = response.error.code; var msg; if (code === 404 ) { } else if (code === 401 ) { } else if (code === 403 ) { } else { } } } } }
エントリーデータをハッシュで作成
記事の投稿 jQuery('#button-‐post').click( function() { var entry = {}; entry['title'] = jQuery('#entry-‐title').val(); entry['body'] = jQuery('#entry-‐body').code(); entry['status'] = 'Publish'; var siteId = jQuery('#form-‐blog-‐list option:selected').val(); api.getToken(function(response) { if (response.error) { } api.createEntry(siteId, entry, function(response) { if (response.error) { var code = response.error.code; var msg; if (code === 404 ) { } else if (code === 401 ) { } else if (code === 403 ) { } else { } } } } }
トークンの有効期限が過ぎている場合は、再取得が必要
記事の投稿 jQuery('#button-‐post').click( function() { var entry = {}; entry['title'] = jQuery('#entry-‐title').val(); entry['body'] = jQuery('#entry-‐body').code();; entry['status'] = 'Publish'; var siteId = jQuery('#form-‐blog-‐list option:selected').val(); api.getToken(function(response) { if (response.error) { } api.createEntry(siteId, entry, function(response) { if (response.error) { var code = response.error.code; var msg; if (code === 404 ) { } else if (code === 401 ) { } else if (code === 403 ) { } else { } } } } }
エントリの作成をおこなう エラー 404: 投稿先のサイトが見つからない
エラー 403: 権限がない エラー 401: 認証エラー
ユーザー専用 投稿ツール
タイトル
カスタムフィールド
カスタムフィールド
本文
JavaScript ライブラリ
勘所
API の呼び出しは非同期
初期化パラメータで同期にすることも可能 !
var api = new MT.DataAPI({ clientId: ‘your-client-id’, baseUrl: ‘https://your-host/your-mt-api.cgi', async: false });
認証URL vs 認証呼び出し
ブラウザからの利用であれば、authenticate ではなく、 getAuthorizationUrl を使って MT 標準のサインイン画面を利用する方がいい(セキュリティ、信頼性) !
var url = api.getAuthorizationUrl( “https://your-host/your-app/“) // リダイレクト先 );
クロスドメインクロスドメイン間の通信は原則禁止されているので、 適宜 環境変数を設定する !
少なくとも DataAPICORSAllowOrigin * DataAPICORSAllowOrigin http://www.example.com, http://beta.example.com !
ついでに DataAPICORSAllowHeaders DataAPICORSExposeHeaders DataAPICORSAllowMethods
など
Data API こんな使い方も
スマートフォンアプリ
• スマフォサイトをウェブアプリとして
• ネイティブアプリから利用して
• Movable Type as Backend Service
Ajax 検索
• インクリメンタルサーチ
• MT-Search.cgi よりも軽量
ページネーション
• 画面のスクロールでインクリメンタルな読み込み
• 検索結果などページネーション
社内システムなど
• 社内ポータルに、記事作成のインターフェイスを用意して、簡単に記事をポスト
• 一般ユーザーは、MTの画面を見る必要がない
Google Analytics
• MTにGoogle Analyticsの設定があれば
• Data API から Google Analyticsのデータを読み込める
Chrome
• タグ・環境変数の検索 by Omnibox
• 再構築の定期実行 by Alarm
• スニペット挿入 by BrowserAction
• 詳しくは、この後のお話で!
Next Data API…
• 色々まだ足りてないですよね?
• 次以降のリリースで含まれる Data API の機能をプラグインで随時リリース予定
• 震えて、待て!