Elixir で OAuth2 を使って、 STRAVA のApiを叩いてみたい ②


この記事は、fukuoka.ex Elixir／Phoenix Advent Calendar 2019 の 9日目で書いた、

Elixir で OAuth2 を使って、 [STRAVA のApiを叩いてみたい ①](https://qiita.com/tomoaki-kimura/items/0f8ed648303e9e12ab5d)の続編です。

この記事では、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)
```

### 前提

前回の続きからとなりますので、

もし、まだの方は、冒頭でもご紹介しました、①の記事

https://qiita.com/tomoaki-kimura/items/0f8ed648303e9e12ab5d

こちらを覗いてみて下さい。

## 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) %>
```


この結果取れていたのは、下記のような情報です。

![](https://gyazo.com/d7a348815b1d1ff2747386185ddab06e.png)

こいつの構造は、

```
{: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
```

ページを辿って、

![](https://gyazo.com/673b6007a49410922fe8e5047adecf96.png)

はい。取得できました。

これで次のリクエスト用のパラメーターが取得出来ます。

### 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を調べたいので、一旦

https://developers.strava.com/playground/

こちらのプレイグラウンドを確認して下さい。

![](https://gyazo.com/25580b25e8714d438d1fd54c4fb870e4.png)

ここでは、実際にご自身の クライアントIDとクライアントシークレット からリクエストに必要な URLやリクエストヘッダー等を調べる事が出来ます。

ただ、このサービスを利用するには、このページのドメイン、 `developers.strava.com` をコールバック先に変更する必要があります。

https://www.strava.com/settings/api

設定ページより、

![](https://gyazo.com/c22492954ad49d1ef762bcdad805e37a.png)

このように変更して更新しておきましょう。

では、調べてみます。

今回欲しいのは、

![](https://gyazo.com/65191b74358109c5df9620bdbf90bfde.png)

`/athlete/activities` です。

まずは、赤枠で囲った部分の右側の鍵マークをクリックして下さい。

するとモーダルウインドウが開きます。

![](https://gyazo.com/b1e4d88a6cabb1416adeef5cf6ae3231.png)

- client_id
- client_secret

にご自身のデーターをコピペし、下の方に行って、

![](https://gyazo.com/1b0a6c88366904b11e1928b8d756ce33.png)

- activity:read_all

にチェックを入れて下さい。

リクエストが通れば、

![](https://gyazo.com/3a7d2661d90246812cb1e7f727350f5f.png)

例の画面が出てきますので、許可をします。

![](https://gyazo.com/92ab75d133fafecb390759de441b7527.png)

この画面が出ればOKですので、モーダルは close で閉じましょう。

すると、このようにパラメーターを入れて実験する事が出来ます。

![](https://gyazo.com/c3ef894b536f2d85a7924326861a8af2.png)


今回はパラメーターは何も入れずにテストしますので、Excute ボタンを押してリクエストの状態を見ましょう。

![](https://gyazo.com/1ba4202ac871cf200ab64747d438c700.png)

200が出て、「ランチタイム ライド」と出ているようですので、リクエスト成功ですね。

この時、リクエスト用の URLと、何やらヘッダーに書き込みがあるようです。

- URL

```
https://www.strava.com/api/v3/athlete/activities?per_page=30
```

- header

```
 authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

(xxxxxの部分が 取得した access_token となります。)

これを元にlocalhostでのリクエストを試してみたいと思います。

プレイグラウンドでの実験はここまでです。

![](https://gyazo.com/9215f5239ce5a87dd68f33f05dc77a9b.png)

忘れずにコールバックドメインを元の `localhost` に戻しておきましょう。

### 2.2 Postman での確認

今回 ヘッダーの設定がありますので、Postman を使ってテストしています。

![](https://gyazo.com/f619a32bf2066e7ee9b45c3b6166897c.png)

- 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}")
```

引数を３つ取っていますが、それぞれ、

1. ドメイン
2. ドメイン以下のURL
3. ヘッダー（配列でも可）

となっているようです。

続いて、 テンプレートも変更します。


/lib/oauth_test_web/templates/page/auth.html.eex

```
<%= inspect @datas %>
```

これで再びサーバーを起動して、ページを辿ります。

![](https://gyazo.com/9cc4657ff7862f24a545fcc61c9a43c9.png)

ついに来ました！アクティビティデーターです。

## 3. 地図での表示

### 3.1 ダミーの地図を表示する

地図の表示に際しては、サンプルを書かれている方がいらっしゃったので、一旦丸写しします。

https://markhneedham.com/blog/2017/04/29/leaflet-strava-polylines-osm/

こちらのコードがそのまま使えます。

ただし、１箇所、 `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です。

![](https://gyazo.com/6050d5348cd95ed1fe68d86c780d0062.png)

### 3.2 データーの挿入

今回は１件だけ表示させてみたいと思います。

ですので、一旦全件取り出してしまいましたが、

```
<% data = hd @datas %>
```

このようにして、頭の１件だけ抽出します。

そして、必要な情報である、

- 緯度
- 経度
- 経路情報

この３つを取り出したいと思います。

幸い、緯度経度はJS側でも配列で渡しているようでしたので、以下の２つのデーターがあれば良さそうです。

```
<%= 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>
```

このようになります。

確認しましょう。

![](https://gyazo.com/4938a5837f0e9a7eb6e8441e09c31179.png)

出来ました。

これで、STRAVA Api からの地図表示 終了です！！

長くなりましたが、お疲れ様でした。

## 終わりに

今回は、目標のデーターを地図で表示出来たので、結果としてはOKですが、

あまりエリクサーらしい事が出来なかったので、続編をやりたいですね。

折角最後に集合体でデーターを取ったので、これをゴニョゴニョしてやりたいです。

ここまで見て頂いた方々、ありがとうございました。

何か不備や至らない点ございましたら、ご指摘いただけると幸いです。