この記事は、fukuoka.ex Elixir/Phoenix Advent Calendar 2019 の 9日目で書いた、
Elixir で OAuth2 を使って、 STRAVA のApiを叩いてみたい ①の続編です。
この記事では、Elixir で OAuth2 を使った認証の後、実際にアクティビティデーターを取得する所までを行っていきます。
書いてる人
Elixir初心者のRails経験者のおっさん。
小倉のとあるもくもく会でご本人曰く、エンジニアもやるパン屋さん?に出会い、kokuraex に参加させて頂くようになりました。
参考サイト
- STRAVAの APIリファレンス
- https://developers.strava.com/docs/reference/
- 地図の表示をされていた先駆者様
- https://markhneedham.com/blog/2017/04/29/leaflet-strava-polylines-osm/
環境
$ mix -v
Erlang/OTP 22 [erts-10.5.6] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
Mix 1.9.4 (compiled with Erlang/OTP 22)
前提
前回の続きからとなりますので、
もし、まだの方は、冒頭でもご紹介しました、①の記事
こちらを覗いてみて下さい。
1.前回のコードを改善
1.1 タプル
前回の最後では、このようなコントローラーになっておりました。
コードがブサイクなのもあるのですが、取得した情報から必要なものを取り出せておりませんので諸々下準備です。
まずは、確認です。
/lib/oauth_test_web/controllers/page_controller.ex
defmodule OauthTestWeb.PageController do
use OauthTestWeb, :controller
def index(conn, _params) do
client = OAuth2.Client.new([
strategy: OAuth2.Strategy.AuthCode,
client_id: "xxxxx",
client_secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
site: "https://www.strava.com",
redirect_uri: "http://localhost:4000/auth",
authorize_url: "https://www.strava.com/oauth/authorize",
token_url: "https://www.strava.com/oauth/token",
token_method: :post,
params: %{grant_type: "authorization_code"}
])
uri = OAuth2.Client.authorize_url!(client, scope: "activity:read_all")
render(conn, "index.html", uri: uri)
end
def auth(conn, %{"code" => code}) do
client = OAuth2.Client.new([
strategy: OAuth2.Strategy.AuthCode,
client_id: "xxxxx",
client_secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
site: "https://www.strava.com",
redirect_uri: "http://localhost:4000/auth",
authorize_url: "https://www.strava.com/oauth/authorize",
token_url: "https://www.strava.com/oauth/token",
token_method: :post,
params: %{grant_type: "authorization_code"}
])
client = OAuth2.Client.get_token!(client, code: code, client_id: client.client_id, client_secret: client.client_secret)
access_token = client.token.access_token
render(conn, "auth.html", access_token: access_token)
end
end
<%= inspect Poison.decode(@access_token) %>
この結果取れていたのは、下記のような情報です。
こいつの構造は、
{:OK, %{ここに目的のデーター}}
このようになっており、目的の %{}
を抽出するのにちょっと悩みました。
なぜなら、RubyのHashっぽい構造なのですが、キーがなく、また、配列のように [1]
を書いても駄目なのでした。
これはタプルと言って、 elen/2 を使って取り出す事が出来ますので、中身の抽出が可能になるはずです。
そして、ついでに テンプレート側に書いてある
inspect Poison.decode(@access_token)
このコードもコントローラー側に持ってきましょう。
ちなみに inspect
を使っていたのは、表示するとエラーが出る場合の仮出力などに使い、本来表示出来ない情報を文字として表示させる事が出来るためでした。
タプルのままでは、テンプレートで表示出来ないんですね。
では、書き換えます。
(略)
def auth(conn, %{"code" => code}) do
client = OAuth2.Client.new([
strategy: OAuth2.Strategy.AuthCode,
client_id: "xxxxx",
client_secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
site: "https://www.strava.com",
redirect_uri: "http://localhost:4000/auth",
authorize_url: "https://www.strava.com/oauth/authorize",
token_url: "https://www.strava.com/oauth/token",
token_method: :post,
params: %{grant_type: "authorization_code"}
])
client = OAuth2.Client.get_token!(client, code: code,
client_id: client.client_id,
client_secret: client.client_secret)
result = client.token.access_token #JSON形式でデーターを抽出
result = Poison.decode(result) #JSONをElixirのタプル型に変更
result = elem(result, 1) #タプルの中にある、Mapを取り出す
access_token = result["access_token"] #Map内のaccess_tokenを抽出
render(conn, "auth.html", access_token: access_token)
end
(略)
テンプレー側は以下のようになります。
<%= @access_token %>
では、サーバーを起動します。
$ iex -S mix phx.server
ページを辿って、
はい。取得できました。
これで次のリクエスト用のパラメーターが取得出来ます。
1.2 重複コードをまとめる
今は client の内容が重複したままとなっていますので、 oauth_params
という名で、メソッドに切り出しておきましょう。
最終的に下記のようになります。
defmodule OauthTestWeb.PageController do
use OauthTestWeb, :controller
def index(conn, _params) do
client = OauthTestWeb.PageController.oauth_params
uri = OAuth2.Client.authorize_url!(client, scope: "activity:read_all")
render(conn, "index.html", uri: uri)
end
def auth(conn, %{"code" => code}) do
client = OauthTestWeb.PageController.oauth_params
client = OAuth2.Client.get_token!(client, code: code,
client_id: client.client_id,
client_secret: client.client_secret)
result = client.token.access_token #Json形式でデーターを抽出
result = Poison.decode(result) #JsonをElixirのタプルという型に変更
result = elem(result, 1) #タプルの中にある、Mapを取り出す
access_token = result["access_token"] #Map内のデーターにアクセス
render(conn, "auth.html", access_token: access_token)
end
def oauth_params do
OAuth2.Client.new([
strategy: OAuth2.Strategy.AuthCode,
client_id: "xxxxx",
client_secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
site: "https://www.strava.com",
redirect_uri: "http://localhost:4000/auth",
authorize_url: "https://www.strava.com/oauth/authorize",
token_url: "https://www.strava.com/oauth/token",
token_method: :post,
params: %{grant_type: "authorization_code"}
])
end
end
ここでもちょっとハマったのですが、どうやらメソッドの呼び出しは モジュール名から呼び出す必要があるようです。(作法的にそもそもコントローラーに定義して良いのかは謎ですが・・)
OauthTestWeb.PageController.oauth_params
この部分ですね。
念の為、書き換え後も同じ動作が出来ているか確認しておくと良いですね。
2.データーの取得
2.1 プレイグラウンドで確認
いよいよリクエストが出来るかの確認をします。
URLを調べたいので、一旦
こちらのプレイグラウンドを確認して下さい。
ここでは、実際にご自身の クライアントIDとクライアントシークレット からリクエストに必要な URLやリクエストヘッダー等を調べる事が出来ます。
ただ、このサービスを利用するには、このページのドメイン、 developers.strava.com
をコールバック先に変更する必要があります。
設定ページより、
このように変更して更新しておきましょう。
では、調べてみます。
今回欲しいのは、
/athlete/activities
です。
まずは、赤枠で囲った部分の右側の鍵マークをクリックして下さい。
するとモーダルウインドウが開きます。
- client_id
- client_secret
にご自身のデーターをコピペし、下の方に行って、
- activity:read_all
にチェックを入れて下さい。
リクエストが通れば、
例の画面が出てきますので、許可をします。
この画面が出ればOKですので、モーダルは close で閉じましょう。
すると、このようにパラメーターを入れて実験する事が出来ます。
今回はパラメーターは何も入れずにテストしますので、Excute ボタンを押してリクエストの状態を見ましょう。
200が出て、「ランチタイム ライド」と出ているようですので、リクエスト成功ですね。
この時、リクエスト用の URLと、何やらヘッダーに書き込みがあるようです。
- URL
https://www.strava.com/api/v3/athlete/activities?per_page=30
- header
authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
(xxxxxの部分が 取得した access_token となります。)
これを元にlocalhostでのリクエストを試してみたいと思います。
プレイグラウンドでの実験はここまでです。
忘れずにコールバックドメインを元の localhost
に戻しておきましょう。
2.2 Postman での確認
今回 ヘッダーの設定がありますので、Postman を使ってテストしています。
- URL
https://www.strava.com/api/v3/athlete/activities?per_page=30
- header
authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
(xxxの部分に access_token
を入れて下さい)
画像のように書き込んで send ボタンを押しましょう。
プレイグラウンドの時と同じように、データーが取得できればOKです。
2.3 smallex のインストール
ここで、ローカル環境で値を取り出します。が・・・
smallex
というライブラリを入れて、 JSON の取得を行うようにしましょう。
インストールに際して、ちょっと競合してしまいました。
poison が 4系ではインストール出来なかったので、ここでダウングレードもしておきます。
/mix.exs
{:poison, "~> 4.0"}
の部分を、
{:poison, "~> 3.1"},
{:smallex, "~> 0.2"}
このように書き換えて下さい。
そして、
$ mix deps.get
でインストールしましょう。
次に、コントローラーを以下のように書き換えます。
/lib/oauth_test_web/contorollers/page_controller.ex
(略)
def auth(conn, %{"code" => code}) do
client = OauthTestWeb.PageController.oauth_params
client = OAuth2.Client.get_token!(client,
code: code,
client_id: client.client_id,
client_secret: client.client_secret)
result = client.token.access_token
result = Poison.decode(result)
result = elem(result, 1)
access_token = result["access_token"]
datas = Json.get("https://www.strava.com",
"/api/v3/athlete/activities?per_page=30",
authorization: "Bearer #{access_token}")
render(conn, "auth.html", datas: datas)
end
(略)
この部分が追加されています。
datas = Json.get("https://www.strava.com",
"/api/v3/athlete/activities?per_page=30",
authorization: "Bearer #{access_token}")
引数を3つ取っていますが、それぞれ、
- ドメイン
- ドメイン以下のURL
- ヘッダー(配列でも可)
となっているようです。
続いて、 テンプレートも変更します。
/lib/oauth_test_web/templates/page/auth.html.eex
<%= inspect @datas %>
これで再びサーバーを起動して、ページを辿ります。
ついに来ました!アクティビティデーターです。
3. 地図での表示
3.1 ダミーの地図を表示する
地図の表示に際しては、サンプルを書かれている方がいらっしゃったので、一旦丸写しします。
こちらのコードがそのまま使えます。
ただし、1箇所、 height: 100%;
の部分がPhoenix上でうまく動作しなかったので、少しだけ修正します。
<div id="map" style="width: 100%; height: 100%;"></div>
この部分が、
<div id="map" style="width: 100%; height: 500px;"></div>
こうなります。
また、先程の表示用のコードは一旦コメントアウトか削除しておきましょう。
ここまでのコードは以下で動きます。
/lib/oauth_test_web/templates/page/auth.html.eex
<% inspect @datas %>
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script type="text/javascript" src="https://rawgit.com/jieter/Leaflet.encoded/master/Polyline.encoded.js"></script>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<div id="map" style="width: 100%; height: 500px;"></div>
<script>
var map = L.map('map').setView([55.609818, 13.003286], 13);
L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
}).addTo(map);
var encodedRoutes = [
"{zkrIm`inANPD?BDXGPKLATHNRBRFtAR~AFjAHl@D|ALtATj@HHJBL?`@EZ?NQ\\Y^MZURGJKR]RMXYh@QdAWf@[~@aAFGb@?j@YJKBU@m@FKZ[NSPKTCRJD?`@Wf@Wb@g@HCp@Qh@]z@SRMRE^EHJZnDHbBGPHb@NfBTxBN|DVbCBdA^lBFl@Lz@HbBDl@Lr@Bb@ApCAp@Ez@g@bEMl@g@`B_AvAq@l@ QF]Rs@Nq@CmAVKCK?_@Nw@h@UJIHOZa@xA]~@UfASn@U`@_@~@[d@Sn@s@rAs@dAGN?NVhAB\\Ox@@b@S|A?Tl@jBZpAt@vBJhATfGJn@b@fARp@H^Hx@ARGNSTIFWHe@AGBOTAP@^\\zBMpACjEWlEIrCKl@i@nAk@}@}@yBOWSg@kAgBUk@Mu@[mC?QLIEUAuAS_E?uCKyCA{BH{DDgF`AaEr@uAb@oA~@{AE}AKw@ g@qAU[_@w@[gAYm@]qAEa@FOXg@JGJ@j@o@bAy@NW?Qe@oCCc@SaBEOIIEQGaAe@kC_@{De@cE?KD[H[P]NcAJ_@DGd@Gh@UHI@Ua@}Bg@yBa@uDSo@i@UIICQUkCi@sCKe@]aAa@oBG{@G[CMOIKMQe@IIM@KB]Tg@Nw@^QL]NMPMn@@\\Lb@P~@XT",
"u}krIq_inA_@y@My@Yu@OqAUsA]mAQc@CS@o@FSHSp@e@n@Wl@]ZCFEBK?OC_@Qw@?m@CSK[]]EMBeAA_@m@qEAg@UoCAaAMs@IkBMoACq@SwAGOYa@IYIyA_@kEMkC]{DEaAScC@yEHkGA_ALsCBiA@mCD{CCuAZcANOH@HDZl@Z`@RFh@\\TDT@ZVJBPMVGLM\\Mz@c@NCPMXERO|@a@^Ut@s@p@KJAJ Bd@EHEXi@f@a@\\g@b@[HUD_B@uADg@DQLCLD~@l@`@J^TF?JANQ\\UbAyABEZIFG`@o@RAJEl@_@ZENDDIA[Ki@BURQZaARODKVs@LSdAiAz@G`BU^A^GT@PRp@zARXRn@`BlDHt@ZlAFh@^`BX|@HHHEf@i@FAHHp@bBd@v@DRAVMl@i@v@SROXm@tBILOTOLs@NON_@t@KX]h@Un@k@\\c@h@Ud@]ZGNKp@Sj@KJo@ b@W`@UPOX]XWd@UF]b@WPOAIBSf@QVi@j@_@V[b@Uj@YtAEFCCELARBn@`@lBjAzD^vB^hB?LENURkAv@[Ze@Xg@Py@p@QHONMA[HGAWE_@Em@Hg@AMCG@QHq@Cm@M[Jy@?UJIA{@Ae@KI@GFKNIX[QGAcAT[JK?OVMFK@IAIUKAYJI?QKUCGFIZCXDtAHl@@p@LjBCZS^ERAn@Fj@Br@Hn@HzAHh@RfD?j@TnCTlA NjANb@\\z@TtARr@P`AFnAGfBG`@CFE?"
]
for (let encoded of encodedRoutes) {
var coordinates = L.Polyline.fromEncoded(encoded).getLatLngs();
L.polyline(
coordinates,
{
color: 'blue',
weight: 2,
opacity: .7,
lineJoin: 'round'
}
).addTo(map);
}
</script>
これと同じ地図が表示できればOKです。
3.2 データーの挿入
今回は1件だけ表示させてみたいと思います。
ですので、一旦全件取り出してしまいましたが、
<% data = hd @datas %>
このようにして、頭の1件だけ抽出します。
そして、必要な情報である、
- 緯度
- 経度
- 経路情報
この3つを取り出したいと思います。
幸い、緯度経度はJS側でも配列で渡しているようでしたので、以下の2つのデーターがあれば良さそうです。
<%= inspect data["start_latlng"] %>
<%= inspect data["map"]["summary_polyline"] %>
また、JS側ですが、
/lib/oauth_test_web/templates/page/auth.html.eex
(略)
<script>
var map = L.map('map').setView(<%= inspect data["start_latlng"] %>, 14);
L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
}).addTo(map);
var encodedRoutes = [
"<%= escape_javascript(data["map"]["summary_polyline"]) %>"
]
for (let encoded of encodedRoutes) {
var coordinates = L.Polyline.fromEncoded(encoded).getLatLngs();
L.polyline(
coordinates,
{
color: 'blue',
weight: 4,
opacity: .7,
lineJoin: 'round'
}
).addTo(map);
}
</script>
このように記述して下さい。
地図の大きさは
var map = L.map('map').setView(<%= inspect data["start_latlng"] %>, 14);
ここの 14
の数字を触ると変更できます。サンプルは 12
でした。
経路の線は
L.polyline(
coordinates,
{
color: 'blue',
weight: 4,
opacity: .7,
lineJoin: 'round'
}
).addTo(map);
ここで変更出来るようです。
サンプルは weight: 2
でしたが、 weight: 4
に変更しています。
また、JSコード内に コード展開させる必要がありますので、一部記号がエスケープされないように、 escape_javascript
を使う必要があります。
var encodedRoutes = [
"<%= escape_javascript(data["map"]["summary_polyline"]) %>"
]
このような感じですね。
という事で、最終的なコードは、
/lib/oauth_test_web/templates/page/auth.html.eex
<% data = hd @datas %>
緯度経度:<br>
<%= inspect data["start_latlng"] %>
<br>
経路:<br>
<%= inspect data["map"]["summary_polyline"] %>
<br>
地図:<br>
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script type="text/javascript" src="https://rawgit.com/jieter/Leaflet.encoded/master/Polyline.encoded.js"></script>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<div id="map" style="width: 100%; height: 500px;"></div>
<script>
var map = L.map('map').setView(<%= inspect data["start_latlng"] %>, 14);
L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
}).addTo(map);
var encodedRoutes = [
"<%= escape_javascript(data["map"]["summary_polyline"]) %>"
]
for (let encoded of encodedRoutes) {
var coordinates = L.Polyline.fromEncoded(encoded).getLatLngs();
L.polyline(
coordinates,
{
color: 'blue',
weight: 4,
opacity: .7,
lineJoin: 'round'
}
).addTo(map);
}
</script>
このようになります。
確認しましょう。
出来ました。
これで、STRAVA Api からの地図表示 終了です!!
長くなりましたが、お疲れ様でした。
終わりに
今回は、目標のデーターを地図で表示出来たので、結果としてはOKですが、
あまりエリクサーらしい事が出来なかったので、続編をやりたいですね。
折角最後に集合体でデーターを取ったので、これをゴニョゴニョしてやりたいです。
ここまで見て頂いた方々、ありがとうございました。
何か不備や至らない点ございましたら、ご指摘いただけると幸いです。